diff --git a/.build/Gruntfile.js b/.build/Gruntfile.js index ec48f3d4ceee0ad6bf425aeabd20ae85d2c4aea6..75c80b26ba227f702a46e5fe6b23ccb8626988cc 100644 --- a/.build/Gruntfile.js +++ b/.build/Gruntfile.js @@ -4,7 +4,11 @@ module.exports = function(grunt) { grunt.initConfig({ clean: { dist: './dist/', - bower_components: './bower_components' // jshint ignore:line + bower_components: './bower_components', // jshint ignore:line + fonts: '../gui/slick/css/*.ttf', + options: { + force: true + } }, bower: { install: { @@ -28,6 +32,10 @@ module.exports = function(grunt) { 'dist/js/widgets/widget-stickyHeaders.min.js', 'dist/css/theme.blue.min.css' ], + 'bootstrap': [ + 'dist/css/bootstrap.min.css', + 'dist/js/bootstrap.min.js' + ], 'bootstrap-formhelpers': [ 'dist/js/bootstrap-formhelpers.min.js' ], @@ -44,6 +52,34 @@ module.exports = function(grunt) { } } }, + copy: { + openSans: { + files: [{ + expand: true, + dot: true, + cwd: 'bower_components/openSans', + src: [ + '*.ttf' + ], + dest: '../gui/slick/css/' + }] + }, + glyphicon: { + files: [{ + expand: true, + dot: true, + cwd: 'bower_components/bootstrap/fonts', + src: [ + '*.eot', + '*.svg', + '*.ttf', + '*.woff', + '*.woff2' + ], + dest: '../gui/slick/fonts/' + }] + } + }, uglify: { bower: { files: { @@ -106,6 +142,7 @@ module.exports = function(grunt) { grunt.loadNpmTasks('grunt-contrib-clean'); grunt.loadNpmTasks('grunt-bower-task'); grunt.loadNpmTasks('grunt-bower-concat'); + grunt.loadNpmTasks('grunt-contrib-copy'); grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-contrib-cssmin'); grunt.loadNpmTasks('grunt-contrib-jshint'); @@ -115,6 +152,7 @@ module.exports = function(grunt) { 'clean', 'bower', 'bower_concat', + 'copy', 'uglify', 'sass', 'cssmin', diff --git a/.build/bower.json b/.build/bower.json index ecbd37d1a176433864df18c5b7a47957d5167861..47138f5c29af7280d4d0304e9783f9c58912c101 100644 --- a/.build/bower.json +++ b/.build/bower.json @@ -1,6 +1,5 @@ { "name": "SickRage", - "version": "4.0.72", "private": true, "ignore": [ "**/.*", @@ -24,7 +23,8 @@ "tablesorter": "jquery.tablesorter#~2.24.5", "jquery-confirm": "~2.5.2", "bootstrap-formhelpers": "~2.3.0", - "isotope": "~2.2.2" + "isotope": "~2.2.2", + "openSans": "https://google-fonts.azurewebsites.net/googleFonts/openSans?family=Open+Sans:400,300,300italic,400italic,600,600italic,700,700italic,800,800italic" }, "resolutions": { "bootstrap": "~3.3.5" diff --git a/.build/package.json b/.build/package.json index ab73bd48a706d57dd108d5cfbaf4618023aea6b7..f0208e63db0f2c64ef0cac177d9225300bc19d45 100644 --- a/.build/package.json +++ b/.build/package.json @@ -21,6 +21,7 @@ "grunt-bower-task": "^0.4.0", "grunt-cli": "^0.1.13", "grunt-contrib-clean": "^0.7.0", + "grunt-contrib-copy": "^0.8.2", "grunt-contrib-cssmin": "^0.14.0", "grunt-contrib-jshint": "^0.11.3", "grunt-contrib-sass": "^0.9.2", diff --git a/COPYING.txt b/COPYING.txt index d64d8b2f7f6064ff4fe25de14efea324e1d44a6f..937249688f24a6af0effa6d4ec4ef2bc1717b338 100644 --- a/COPYING.txt +++ b/COPYING.txt @@ -641,7 +641,7 @@ the "copyright" line and a pointer to where the full notice is found. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License diff --git a/SickBeard.py b/SickBeard.py index f6ce75ba73a9863d2e649cc49284aeb4f80bf2f6..72390d16304b8831170ae87eb8d7efcc83e526de 100755 --- a/SickBeard.py +++ b/SickBeard.py @@ -12,68 +12,90 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. # Check needed software dependencies to nudge users to fix their setup -# pylint: disable=broad-except -# Catching too general exception +# pylint: disable=line-too-long -import codecs +""" +Usage: SickBeard.py [OPTION]... -codecs.register(lambda name: codecs.lookup('utf-8') if name == 'cp65001' else None) +Options: + -h, --help Prints this message + -q, --quiet Disables logging to console + --nolaunch Suppress launching web browser on startup -import time + -d, --daemon Run as double forked daemon (with --quiet --nolaunch) + On Windows and MAC, this option is ignored but still + applies --quiet --nolaunch + --pidfile=[FILE] Combined with --daemon creates a pid file + + -p, --port=[PORT] Override default/configured port to listen on + --datadir=[PATH] Override folder (full path) as location for + storing database, config file, cache, and log files + Default SickRage directory + --config=[FILE] Override config filename for loading configuration + Default config.ini in SickRage directory or + location specified with --datadir + --noresize Prevent resizing of the banner/posters even if PIL + is installed +""" + +from __future__ import unicode_literals +from __future__ import print_function + +import codecs +import datetime +import getopt +import io +import locale +import os +import shutil import signal -import sys import subprocess +import sys +import threading +import time import traceback -import os +codecs.register(lambda name: codecs.lookup('utf-8') if name == 'cp65001' else None) sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), 'lib'))) -import shutil -import shutil_custom - -shutil.copyfile = shutil_custom.copyfile_custom - if sys.version_info < (2, 7): - print "Sorry, requires Python 2.7.x" + print('Sorry, requires Python 2.7.x') sys.exit(1) +# pylint: disable=wrong-import-position # https://mail.python.org/pipermail/python-dev/2014-September/136300.html if sys.version_info >= (2, 7, 9): import ssl - # pylint: disable=protected-access - # Access to a protected member of a client class - ssl._create_default_https_context = ssl._create_unverified_context + ssl._create_default_https_context = ssl._create_unverified_context # pylint: disable=protected-access -import locale -import datetime -import threading -import getopt +import shutil_custom # pylint: disable=import-error +shutil.copyfile = shutil_custom.copyfile_custom # Do this before importing sickbeard, to prevent locked files and incorrect import -oldtornado = os.path.abspath(os.path.join(os.path.dirname(__file__), 'tornado')) -if os.path.isdir(oldtornado): - shutil.move(oldtornado, oldtornado + '_kill') - shutil.rmtree(oldtornado + '_kill') +OLD_TORNADO = os.path.abspath(os.path.join(os.path.dirname(__file__), 'tornado')) +if os.path.isdir(OLD_TORNADO): + shutil.move(OLD_TORNADO, OLD_TORNADO + '_kill') + shutil.rmtree(OLD_TORNADO + '_kill') import sickbeard -from sickbeard import db, logger, network_timezones, failed_history, name_cache +from sickbeard import db, logger, network_timezones, failed_history # , name_cache from sickbeard.tv import TVShow from sickbeard.webserveInit import SRWebServer from sickbeard.event_queue import Events -from configobj import ConfigObj +from configobj import ConfigObj # pylint: disable=import-error from sickrage.helper.encoding import ek # http://bugs.python.org/issue7980#msg221094 -throwaway = datetime.datetime.strptime('20110101', '%Y%m%d') +THROWAWAY = datetime.datetime.strptime('20110101', '%Y%m%d') signal.signal(signal.SIGINT, sickbeard.sig_handler) signal.signal(signal.SIGTERM, sickbeard.sig_handler) @@ -81,73 +103,58 @@ signal.signal(signal.SIGTERM, sickbeard.sig_handler) class SickRage(object): # pylint: disable=too-many-instance-attributes - # Too many instance attributes + """ + Main SickRage module + """ + def __init__(self): # system event callback for shutdown/restart sickbeard.events = Events(self.shutdown) # daemon constants - self.runAsDaemon = False - self.CREATEPID = False - self.PIDFILE = '' + self.run_as_daemon = False + self.create_pid = False + self.pid_file = '' - # webserver constants - self.webserver = None - self.forcedPort = None - self.noLaunch = False + # web server constants + self.web_server = None + self.forced_port = None + self.no_launch = False - self.webhost = '0.0.0.0' - self.startPort = sickbeard.WEB_PORT + self.web_host = '0.0.0.0' + self.start_port = sickbeard.WEB_PORT self.web_options = {} self.log_dir = None - self.consoleLogging = True + self.console_logging = True @staticmethod def clear_cache(): + """ + Remove the Mako cache directory + """ try: cache_folder = ek(os.path.join, sickbeard.CACHE_DIR, 'mako') if os.path.isdir(cache_folder): shutil.rmtree(cache_folder) - except Exception: - logger.log(u"Unable to remove the cache/mako directory!", logger.WARNING) + except Exception: # pylint: disable=broad-except + logger.log('Unable to remove the cache/mako directory!', logger.WARNING) # pylint: disable=no-member @staticmethod def help_message(): """ - print help message for commandline options + Print help message for commandline options """ - help_msg = "\n" - help_msg += "Usage: " + sickbeard.MY_FULLNAME + " <option> <another option>\n" - help_msg += "\n" - help_msg += "Options:\n" - help_msg += "\n" - help_msg += " -h --help Prints this message\n" - help_msg += " -q --quiet Disables logging to console\n" - help_msg += " --nolaunch Suppress launching web browser on startup\n" - - if sys.platform == 'win32' or sys.platform == 'darwin': - help_msg += " -d --daemon Running as real daemon is not supported on Windows\n" - help_msg += " On Windows and MAC, --daemon is substituted with: --quiet --nolaunch\n" - else: - help_msg += " -d --daemon Run as double forked daemon (includes options --quiet --nolaunch)\n" - help_msg += " --pidfile=<path> Combined with --daemon creates a pidfile (full path including filename)\n" - - help_msg += " -p <port> --port=<port> Override default/configured port to listen on\n" - help_msg += " --datadir=<path> Override folder (full path) as location for\n" - help_msg += " storing database, configfile, cache, logfiles \n" - help_msg += " Default: " + sickbeard.PROG_DIR + "\n" - help_msg += " --config=<path> Override config filename (full path including filename)\n" - help_msg += " to load configuration from \n" - help_msg += " Default: config.ini in " + sickbeard.PROG_DIR + " or --datadir location\n" - help_msg += " --noresize Prevent resizing of the banner/posters even if PIL is installed\n" + help_msg = __doc__ + help_msg = help_msg.replace('SickBeard.py', sickbeard.MY_FULLNAME) + help_msg = help_msg.replace('SickRage directory', sickbeard.PROG_DIR) return help_msg - # pylint: disable=too-many-branches,too-many-statements - # Too many branches - # Too many statements - def start(self): + def start(self): # pylint: disable=too-many-branches,too-many-statements + """ + Start SickRage + """ # do some preliminary stuff sickbeard.MY_FULLNAME = ek(os.path.normpath, ek(os.path.abspath, __file__)) sickbeard.MY_NAME = ek(os.path.basename, sickbeard.MY_FULLNAME) @@ -156,169 +163,167 @@ class SickRage(object): sickbeard.MY_ARGS = sys.argv[1:] try: - locale.setlocale(locale.LC_ALL, "") + locale.setlocale(locale.LC_ALL, '') sickbeard.SYS_ENCODING = locale.getpreferredencoding() except (locale.Error, IOError): sickbeard.SYS_ENCODING = 'UTF-8' # pylint: disable=no-member if not sickbeard.SYS_ENCODING or sickbeard.SYS_ENCODING.lower() in ('ansi_x3.4-1968', 'us-ascii', 'ascii', 'charmap') or \ - (sys.platform.startswith('win') and sys.getwindowsversion()[0] >= 6 and str(getattr(sys.stdout, 'device', sys.stdout).encoding).lower() in ('cp65001', 'charmap')): + (sys.platform.startswith('win') and sys.getwindowsversion()[0] >= 6 and str(getattr(sys.stdout, 'device', sys.stdout).encoding).lower() in ('cp65001', 'charmap')): sickbeard.SYS_ENCODING = 'UTF-8' # TODO: Continue working on making this unnecessary, this hack creates all sorts of hellish problems - if not hasattr(sys, "setdefaultencoding"): + if not hasattr(sys, 'setdefaultencoding'): reload(sys) try: - # pylint: disable=no-member # On non-unicode builds this will raise an AttributeError, if encoding type is not valid it throws a LookupError - sys.setdefaultencoding(sickbeard.SYS_ENCODING) - except Exception: - sys.exit("Sorry, you MUST add the SickRage folder to the PYTHONPATH environment variable\n" + - "or find another way to force Python to use " + sickbeard.SYS_ENCODING + " for string encoding.") + sys.setdefaultencoding(sickbeard.SYS_ENCODING) # pylint: disable=no-member + except (AttributeError, LookupError): + sys.exit('Sorry, you MUST add the SickRage folder to the PYTHONPATH environment variable\n' + 'or find another way to force Python to use %s for string encoding.' % sickbeard.SYS_ENCODING) # Need console logging for SickBeard.py and SickBeard-console.exe - self.consoleLogging = (not hasattr(sys, "frozen")) or (sickbeard.MY_NAME.lower().find('-console') > 0) + self.console_logging = (not hasattr(sys, 'frozen')) or (sickbeard.MY_NAME.lower().find('-console') > 0) # Rename the main thread - threading.currentThread().name = u"MAIN" + threading.currentThread().name = 'MAIN' try: opts, _ = getopt.getopt( - sys.argv[1:], "hqdp::", + sys.argv[1:], 'hqdp::', ['help', 'quiet', 'nolaunch', 'daemon', 'pidfile=', 'port=', 'datadir=', 'config=', 'noresize'] ) except getopt.GetoptError: sys.exit(self.help_message()) - for o, a in opts: + for option, value in opts: # Prints help message - if o in ('-h', '--help'): + if option in ('-h', '--help'): sys.exit(self.help_message()) # For now we'll just silence the logging - if o in ('-q', '--quiet'): - self.consoleLogging = False + if option in ('-q', '--quiet'): + self.console_logging = False # Suppress launching web browser # Needed for OSes without default browser assigned # Prevent duplicate browser window when restarting in the app - if o in ('--nolaunch',): - self.noLaunch = True + if option in ('--nolaunch',): + self.no_launch = True # Override default/configured port - if o in ('-p', '--port'): + if option in ('-p', '--port'): try: - self.forcedPort = int(a) + self.forced_port = int(value) except ValueError: - sys.exit("Port: " + str(a) + " is not a number. Exiting.") + sys.exit('Port: %s is not a number. Exiting.' % value) # Run as a double forked daemon - if o in ('-d', '--daemon'): - self.runAsDaemon = True - # When running as daemon disable consoleLogging and don't start browser - self.consoleLogging = False - self.noLaunch = True + if option in ('-d', '--daemon'): + self.run_as_daemon = True + # When running as daemon disable console_logging and don't start browser + self.console_logging = False + self.no_launch = True if sys.platform == 'win32' or sys.platform == 'darwin': - self.runAsDaemon = False + self.run_as_daemon = False - # Write a pidfile if requested - if o in ('--pidfile',): - self.CREATEPID = True - self.PIDFILE = str(a) + # Write a pid file if requested + if option in ('--pidfile',): + self.create_pid = True + self.pid_file = str(value) - # If the pidfile already exists, sickbeard may still be running, so exit - if ek(os.path.exists, self.PIDFILE): - sys.exit("PID file: " + self.PIDFILE + " already exists. Exiting.") + # If the pid file already exists, SickRage may still be running, so exit + if ek(os.path.exists, self.pid_file): + sys.exit('PID file: %s already exists. Exiting.' % self.pid_file) # Specify folder to load the config file from - if o in ('--config',): - sickbeard.CONFIG_FILE = ek(os.path.abspath, a) + if option in ('--config',): + sickbeard.CONFIG_FILE = ek(os.path.abspath, value) - # Specify folder to use as the data dir - if o in ('--datadir',): - sickbeard.DATA_DIR = ek(os.path.abspath, a) + # Specify folder to use as the data directory + if option in ('--datadir',): + sickbeard.DATA_DIR = ek(os.path.abspath, value) # Prevent resizing of the banner/posters even if PIL is installed - if o in ('--noresize',): + if option in ('--noresize',): sickbeard.NO_RESIZE = True - # The pidfile is only useful in daemon mode, make sure we can write the file properly - if self.CREATEPID: - if self.runAsDaemon: - pid_dir = ek(os.path.dirname, self.PIDFILE) + # The pid file is only useful in daemon mode, make sure we can write the file properly + if self.create_pid: + if self.run_as_daemon: + pid_dir = ek(os.path.dirname, self.pid_file) if not ek(os.access, pid_dir, os.F_OK): - sys.exit("PID dir: " + pid_dir + " doesn't exist. Exiting.") + sys.exit('PID dir: %s doesn\'t exist. Exiting.' % pid_dir) if not ek(os.access, pid_dir, os.W_OK): - sys.exit("PID dir: " + pid_dir + " must be writable (write permissions). Exiting.") + sys.exit('PID dir: %s must be writable (write permissions). Exiting.' % pid_dir) else: - if self.consoleLogging: - sys.stdout.write(u"Not running in daemon mode. PID file creation disabled.\n") + if self.console_logging: + sys.stdout.write('Not running in daemon mode. PID file creation disabled.\n') - self.CREATEPID = False + self.create_pid = False # If they don't specify a config file then put it in the data dir if not sickbeard.CONFIG_FILE: - sickbeard.CONFIG_FILE = ek(os.path.join, sickbeard.DATA_DIR, "config.ini") + sickbeard.CONFIG_FILE = ek(os.path.join, sickbeard.DATA_DIR, 'config.ini') # Make sure that we can create the data dir if not ek(os.access, sickbeard.DATA_DIR, os.F_OK): try: - ek(os.makedirs, sickbeard.DATA_DIR, 0744) + ek(os.makedirs, sickbeard.DATA_DIR, 0o744) except os.error: - raise SystemExit("Unable to create datadir '" + sickbeard.DATA_DIR + "'") + raise SystemExit('Unable to create data directory: %s' % sickbeard.DATA_DIR) # Make sure we can write to the data dir if not ek(os.access, sickbeard.DATA_DIR, os.W_OK): - raise SystemExit("Datadir must be writeable '" + sickbeard.DATA_DIR + "'") + raise SystemExit('Data directory must be writeable: %s' % sickbeard.DATA_DIR) # Make sure we can write to the config file if not ek(os.access, sickbeard.CONFIG_FILE, os.W_OK): if ek(os.path.isfile, sickbeard.CONFIG_FILE): - raise SystemExit("Config file '" + sickbeard.CONFIG_FILE + "' must be writeable.") + raise SystemExit('Config file must be writeable: %s' % sickbeard.CONFIG_FILE) elif not ek(os.access, ek(os.path.dirname, sickbeard.CONFIG_FILE), os.W_OK): - raise SystemExit( - "Config file root dir '" + ek(os.path.dirname, sickbeard.CONFIG_FILE) + "' must be writeable.") + raise SystemExit('Config file root dir must be writeable: %s' % ek(os.path.dirname, sickbeard.CONFIG_FILE)) ek(os.chdir, sickbeard.DATA_DIR) # Check if we need to perform a restore first - restoreDir = ek(os.path.join, sickbeard.DATA_DIR, 'restore') - if ek(os.path.exists, restoreDir): - success = self.restoreDB(restoreDir, sickbeard.DATA_DIR) - if self.consoleLogging: - sys.stdout.write(u"Restore: restoring DB and config.ini %s!\n" % ("FAILED", "SUCCESSFUL")[success]) + restore_dir = ek(os.path.join, sickbeard.DATA_DIR, 'restore') + if ek(os.path.exists, restore_dir): + success = self.restore_db(restore_dir, sickbeard.DATA_DIR) + if self.console_logging: + sys.stdout.write('Restore: restoring DB and config.ini %s!\n' % ('FAILED', 'SUCCESSFUL')[success]) # Load the config and publish it to the sickbeard package - if self.consoleLogging and not ek(os.path.isfile, sickbeard.CONFIG_FILE): - sys.stdout.write(u"Unable to find '" + sickbeard.CONFIG_FILE + "' , all settings will be default!" + "\n") + if self.console_logging and not ek(os.path.isfile, sickbeard.CONFIG_FILE): + sys.stdout.write('Unable to find %s, all settings will be default!\n' % sickbeard.CONFIG_FILE) sickbeard.CFG = ConfigObj(sickbeard.CONFIG_FILE) # Initialize the config and our threads - sickbeard.initialize(consoleLogging=self.consoleLogging) + sickbeard.initialize(consoleLogging=self.console_logging) - if self.runAsDaemon: + if self.run_as_daemon: self.daemonize() # Get PID sickbeard.PID = os.getpid() # Build from the DB to start with - self.loadShowsFromDB() + self.load_shows_from_db() - logger.log(u"Starting up SickRage [%s] from '%s'" % (sickbeard.BRANCH, sickbeard.CONFIG_FILE)) + logger.log('Starting SickRage [%s] from \'%s\'' % (sickbeard.BRANCH, sickbeard.CONFIG_FILE)) self.clear_cache() - if self.forcedPort: - logger.log(u"Forcing web server to port %s" % self.forcedPort) - self.startPort = self.forcedPort + if self.forced_port: + logger.log('Forcing web server to port %s' % self.forced_port) + self.start_port = self.forced_port else: - self.startPort = sickbeard.WEB_PORT + self.start_port = sickbeard.WEB_PORT if sickbeard.WEB_LOG: self.log_dir = sickbeard.LOG_DIR @@ -328,17 +333,14 @@ class SickRage(object): # sickbeard.WEB_HOST is available as a configuration value in various # places but is not configurable. It is supported here for historic reasons. if sickbeard.WEB_HOST and sickbeard.WEB_HOST != '0.0.0.0': - self.webhost = sickbeard.WEB_HOST + self.web_host = sickbeard.WEB_HOST else: - if sickbeard.WEB_IPV6: - self.webhost = '' - else: - self.webhost = '0.0.0.0' + self.web_host = '' if sickbeard.WEB_IPV6 else '0.0.0.0' # web server options self.web_options = { - 'port': int(self.startPort), - 'host': self.webhost, + 'port': int(self.start_port), + 'host': self.web_host, 'data_root': ek(os.path.join, sickbeard.PROG_DIR, 'gui', sickbeard.GUI_NAME), 'web_root': sickbeard.WEB_ROOT, 'log_dir': self.log_dir, @@ -351,16 +353,16 @@ class SickRage(object): } # start web server - self.webserver = SRWebServer(self.web_options) - self.webserver.start() + self.web_server = SRWebServer(self.web_options) + self.web_server.start() # Fire up all our threads sickbeard.start() # Build internal name cache - name_cache.buildNameCache() + # name_cache.buildNameCache() - # Prepopulate network timezones, it isn't thread safe + # Pre-populate network timezones, it isn't thread safe network_timezones.update_network_dict() # sure, why not? @@ -371,8 +373,8 @@ class SickRage(object): # sickbeard.showUpdateScheduler.forceRun() # Launch browser - if sickbeard.LAUNCH_BROWSER and not (self.noLaunch or self.runAsDaemon): - sickbeard.launchBrowser('https' if sickbeard.ENABLE_HTTPS else 'http', self.startPort, sickbeard.WEB_ROOT) + if sickbeard.LAUNCH_BROWSER and not (self.no_launch or self.run_as_daemon): + sickbeard.launchBrowser('https' if sickbeard.ENABLE_HTTPS else 'http', self.start_port, sickbeard.WEB_ROOT) # main loop while True: @@ -390,8 +392,8 @@ class SickRage(object): pid = os.fork() # @UndefinedVariable - only available in UNIX if pid != 0: os._exit(0) - except OSError as e: - sys.stderr.write(u"fork #1 failed: %d (%s)\n" % (e.errno, e.strerror)) + except OSError as error_message: + sys.stderr.write('fork #1 failed: %d (%s)\n' % (error_message.errno, error_message.strerror)) sys.exit(1) os.setsid() # @UndefinedVariable - only available in UNIX @@ -399,7 +401,7 @@ class SickRage(object): # https://github.com/SickRage/sickrage-issues/issues/2969 # http://www.microhowto.info/howto/cause_a_process_to_become_a_daemon_in_c.html#idp23920 # https://www.safaribooksonline.com/library/view/python-cookbook/0596001673/ch06s08.html - # Previous code simply set the umask to whatever it was because it was ANDing instead of ORring + # Previous code simply set the umask to whatever it was because it was ANDing instead of OR-ing # Daemons traditionally run with umask 0 anyways and this should not have repercussions os.umask(0) @@ -408,28 +410,27 @@ class SickRage(object): pid = os.fork() # @UndefinedVariable - only available in UNIX if pid != 0: os._exit(0) - except OSError as e: - sys.stderr.write(u"fork #2 failed: %d (%s)\n" % (e.errno, e.strerror)) + except OSError as error_message: + sys.stderr.write('fork #2 failed: %d (%s)\n' % (error_message.errno, error_message.strerror)) sys.exit(1) # Write pid - if self.CREATEPID: - pid = str(os.getpid()) - logger.log(u"Writing PID: " + pid + " to " + str(self.PIDFILE)) + if self.create_pid: + pid = os.getpid() + logger.log('Writing PID: %s to %s' % (pid, self.pid_file)) try: - file(self.PIDFILE, 'w').write("%s\n" % pid) - except IOError as e: - logger.log_error_and_exit( - u"Unable to write PID file: " + self.PIDFILE + " Error: " + str(e.strerror) + " [" + str( - e.errno) + "]") + with io.open(self.pid_file, 'w') as f_pid: + f_pid.write('%s\n' % pid) + except EnvironmentError as error_message: + logger.log_error_and_exit('Unable to write PID file: %s Error: %s [%s]' % (self.pid_file, error_message.strerror, error_message.errno)) # Redirect all output sys.stdout.flush() sys.stderr.flush() devnull = getattr(os, 'devnull', '/dev/null') - stdin = file(devnull, 'r') + stdin = file(devnull) stdout = file(devnull, 'a+') stderr = file(devnull, 'a+') @@ -438,77 +439,90 @@ class SickRage(object): os.dup2(stderr.fileno(), getattr(sys.stderr, 'device', sys.stderr).fileno()) @staticmethod - def remove_pid_file(PIDFILE): + def remove_pid_file(pid_file): + """ + Remove pid file + + :param pid_file: to remove + :return: + """ try: - if ek(os.path.exists, PIDFILE): - ek(os.remove, PIDFILE) - except (IOError, OSError): + if ek(os.path.exists, pid_file): + ek(os.remove, pid_file) + except EnvironmentError: return False return True @staticmethod - def loadShowsFromDB(): + def load_shows_from_db(): """ Populates the showList with shows from the database """ - logger.log(u"Loading initial show list", logger.DEBUG) + logger.log('Loading initial show list', logger.DEBUG) # pylint: disable=no-member - myDB = db.DBConnection() - sqlResults = myDB.select("SELECT indexer, indexer_id, location FROM tv_shows;") + my_db = db.DBConnection() + sql_results = my_db.select('SELECT indexer, indexer_id, location FROM tv_shows;') sickbeard.showList = [] - for sqlShow in sqlResults: + for sql_show in sql_results: try: - curShow = TVShow(int(sqlShow["indexer"]), int(sqlShow["indexer_id"])) - curShow.nextEpisode() - sickbeard.showList.append(curShow) - except Exception as e: - logger.log( - u"There was an error creating the show in " + sqlShow["location"] + ": " + str(e).decode('utf-8'), - logger.ERROR) - logger.log(traceback.format_exc(), logger.DEBUG) + cur_show = TVShow(sql_show[b'indexer'], sql_show[b'indexer_id']) + cur_show.nextEpisode() + sickbeard.showList.append(cur_show) + except Exception as error_msg: # pylint: disable=broad-except + logger.log('There was an error creating the show in %s: %s' % # pylint: disable=no-member + (sql_show[b'location'], str(error_msg).decode()), logger.ERROR) + logger.log(traceback.format_exc(), logger.DEBUG) # pylint: disable=no-member @staticmethod - def restoreDB(srcDir, dstDir): + def restore_db(src_dir, dst_dir): + """ + Restore the Database from a backup + + :param src_dir: Directory containing backup + :param dst_dir: Directory to restore to + :return: + """ try: - filesList = ['sickbeard.db', 'config.ini', 'failed.db', 'cache.db'] - - for filename in filesList: - srcFile = ek(os.path.join, srcDir, filename) - dstFile = ek(os.path.join, dstDir, filename) - bakFile = ek(os.path.join, dstDir, '{0}.bak-{1}'.format(filename, datetime.datetime.now().strftime('%Y%m%d_%H%M%S'))) - if ek(os.path.isfile, dstFile): - shutil.move(dstFile, bakFile) - shutil.move(srcFile, dstFile) + files_list = ['sickbeard.db', 'config.ini', 'failed.db', 'cache.db'] + + for filename in files_list: + src_file = ek(os.path.join, src_dir, filename) + dst_file = ek(os.path.join, dst_dir, filename) + bak_file = ek(os.path.join, dst_dir, '%s.bak-%s' % (filename, datetime.datetime.now().strftime('%Y%m%d_%H%M%S'))) + if ek(os.path.isfile, dst_file): + shutil.move(dst_file, bak_file) + shutil.move(src_file, dst_file) return True - except Exception: + except Exception: # pylint: disable=broad-except return False def shutdown(self, event): - if sickbeard.started: - # stop all tasks - sickbeard.halt() + """ + Shut down SickRage - # save all shows to DB - sickbeard.saveAll() + :param event: Type of shutdown event, used to see if restart required + """ + if sickbeard.started: + sickbeard.halt() # stop all tasks + sickbeard.saveAll() # save all shows to DB # shutdown web server - if self.webserver: - logger.log(u"Shutting down Tornado") - self.webserver.shutDown() + if self.web_server: + logger.log('Shutting down Tornado') # pylint: disable=no-member + self.web_server.shutDown() try: - self.webserver.join(10) - except Exception: + self.web_server.join(10) + except Exception: # pylint: disable=broad-except pass - # Clean cache - self.clear_cache() + self.clear_cache() # Clean cache - # if run as daemon delete the pidfile - if self.runAsDaemon and self.CREATEPID: - self.remove_pid_file(self.PIDFILE) + # if run as daemon delete the pid file + if self.run_as_daemon and self.create_pid: + self.remove_pid_file(self.pid_file) if event == sickbeard.event_queue.Events.SystemEvent.RESTART: install_type = sickbeard.versionCheckScheduler.action.install_type @@ -518,23 +532,23 @@ class SickRage(object): if install_type in ('git', 'source'): popen_list = [sys.executable, sickbeard.MY_FULLNAME] elif install_type == 'win': - logger.log(u"You are using a binary Windows build of SickRage. Please switch to using git.", logger.ERROR) + logger.log('You are using a binary Windows build of SickRage. ' # pylint: disable=no-member + 'Please switch to using git.', logger.ERROR) if popen_list and not sickbeard.NO_RESTART: popen_list += sickbeard.MY_ARGS if '--nolaunch' not in popen_list: popen_list += ['--nolaunch'] - logger.log(u"Restarting SickRage with " + str(popen_list)) - logger.shutdown() # shutdown the logger to make sure it's released the logfile BEFORE it restarts SR. + logger.log('Restarting SickRage with %s' % popen_list) # pylint: disable=no-member + # shutdown the logger to make sure it's released the logfile BEFORE it restarts SR. + logger.shutdown() # pylint: disable=no-member subprocess.Popen(popen_list, cwd=os.getcwd()) - # system exit - logger.shutdown() # Make sure the logger has stopped, just in case - # pylint: disable=protected-access - # Access to a protected member of a client class - os._exit(0) + # Make sure the logger has stopped, just in case + logger.shutdown() # pylint: disable=no-member + os._exit(0) # pylint: disable=protected-access -if __name__ == "__main__": - # start sickrage +if __name__ == '__main__': + # start SickRage SickRage().start() diff --git a/gui/slick/css/1ORHCpsQm3Vp6mXoaTYnF5uFdDttMLvmWuJdhhgs.ttf b/gui/slick/css/1ORHCpsQm3Vp6mXoaTYnF5uFdDttMLvmWuJdhhgs.ttf new file mode 100644 index 0000000000000000000000000000000000000000..d7d7cd1fcc97d7aaeb1ffa373ea86d0c2ee13b5e Binary files /dev/null and b/gui/slick/css/1ORHCpsQm3Vp6mXoaTYnF5uFdDttMLvmWuJdhhgs.ttf differ diff --git a/gui/slick/css/702ZOKiLJc3WVjuplzInF5uFdDttMLvmWuJdhhgs.ttf b/gui/slick/css/702ZOKiLJc3WVjuplzInF5uFdDttMLvmWuJdhhgs.ttf new file mode 100644 index 0000000000000000000000000000000000000000..974a7c5edd7f14486c43561220b54244da886885 Binary files /dev/null and b/gui/slick/css/702ZOKiLJc3WVjuplzInF5uFdDttMLvmWuJdhhgs.ttf differ diff --git a/gui/slick/css/JXh38I15wypJXxuGMBp0EAVxt0G0biEntp43Qt6E.ttf b/gui/slick/css/JXh38I15wypJXxuGMBp0EAVxt0G0biEntp43Qt6E.ttf new file mode 100644 index 0000000000000000000000000000000000000000..53dbf96f7cde8a031fcea31baf7eaf9c9af36fbb Binary files /dev/null and b/gui/slick/css/JXh38I15wypJXxuGMBp0EAVxt0G0biEntp43Qt6E.ttf differ diff --git a/gui/slick/css/KeOuBrn4kERxqtaUH3aCWcynf_cDxXwCLxiixG1c.ttf b/gui/slick/css/KeOuBrn4kERxqtaUH3aCWcynf_cDxXwCLxiixG1c.ttf new file mode 100644 index 0000000000000000000000000000000000000000..0dae9c3bbc0b52ccd98b060849e631661a9bebdc Binary files /dev/null and b/gui/slick/css/KeOuBrn4kERxqtaUH3aCWcynf_cDxXwCLxiixG1c.ttf differ diff --git a/gui/slick/css/_ySUJH_bn48VBG8sNSonF5uFdDttMLvmWuJdhhgs.ttf b/gui/slick/css/_ySUJH_bn48VBG8sNSonF5uFdDttMLvmWuJdhhgs.ttf new file mode 100644 index 0000000000000000000000000000000000000000..314e983a4bce3320b1ebeea9d21aeea88eaab52e Binary files /dev/null and b/gui/slick/css/_ySUJH_bn48VBG8sNSonF5uFdDttMLvmWuJdhhgs.ttf differ diff --git a/gui/slick/css/bV5DfGHOiMmvb1Xr-honF5uFdDttMLvmWuJdhhgs.ttf b/gui/slick/css/bV5DfGHOiMmvb1Xr-honF5uFdDttMLvmWuJdhhgs.ttf new file mode 100644 index 0000000000000000000000000000000000000000..569e03a84014d5163582d148d6e090727eb61b53 Binary files /dev/null and b/gui/slick/css/bV5DfGHOiMmvb1Xr-honF5uFdDttMLvmWuJdhhgs.ttf differ diff --git a/gui/slick/css/browser.css b/gui/slick/css/browser.css index 571501723e875d0280fb31342a692f3f03755fce..bdc1f3ab3ddf55f015fa8b83f32cadc944dfc2bb 100644 --- a/gui/slick/css/browser.css +++ b/gui/slick/css/browser.css @@ -1,30 +1,30 @@ -#fileBrowserDialog { +.fileBrowserDialog { max-height: 480px; overflow-y: auto; } -#fileBrowserDialog ul { +.fileBrowserDialog ul { margin: 0; padding: 0; } -#fileBrowserDialog ul li { +.fileBrowserDialog ul li { margin: 2px 0; cursor: pointer; list-style-type: none; } -#fileBrowserDialog ul li a { +.fileBrowserDialog ul li a { display: block; padding: 4px 0; } -#fileBrowserDialog ul li a:hover { +.fileBrowserDialog ul li a:hover { color: #00f; background: none; } -#fileBrowserDialog ul li a span.ui-icon { +.fileBrowserDialog ul li a .ui-icon { margin: 0 4px; float: left; } diff --git a/gui/slick/css/dark.css b/gui/slick/css/dark.css index c4964a0678e270ccc1546c9ba739e19043002702..0a1231acc0facfa3be3a3b07a4df47eb2bfb10c2 100644 --- a/gui/slick/css/dark.css +++ b/gui/slick/css/dark.css @@ -880,16 +880,16 @@ pre { browser.css overrides ========================================================================== */ -#fileBrowserDialog ul li { +.fileBrowserDialog ul li { margin: 2px 0; list-style-type: none; cursor: pointer; - background: #333; + background: #333 !important; } -#fileBrowserDialog ul li a:hover { +.fileBrowserDialog ul li a:hover { color: #09a2ff; - background: none; + background: none !important; } /* ======================================================================= diff --git a/gui/slick/css/fonts/OpenSans-Bold-webfont.eot b/gui/slick/css/fonts/OpenSans-Bold-webfont.eot deleted file mode 100644 index 5d4a1c47770fd38b715d6f8e119869fed1f4d8ab..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-Bold-webfont.eot and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-Bold-webfont.svg b/gui/slick/css/fonts/OpenSans-Bold-webfont.svg deleted file mode 100644 index 1557f6807473930ef85ef8eb6a5ce9e54ec657f5..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-Bold-webfont.svg and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-Bold-webfont.ttf b/gui/slick/css/fonts/OpenSans-Bold-webfont.ttf deleted file mode 100644 index 7ab5d85bfdd1886b621b98b46e5a9d3609f2429c..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-Bold-webfont.ttf and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-Bold-webfont.woff b/gui/slick/css/fonts/OpenSans-Bold-webfont.woff deleted file mode 100644 index 869a9ed8af705e6a216a97d137e5e4c838657a73..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-Bold-webfont.woff and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-BoldItalic-webfont.eot b/gui/slick/css/fonts/OpenSans-BoldItalic-webfont.eot deleted file mode 100644 index ad6518076e32b52ad56d50ccb0e48d8a093b7efb..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-BoldItalic-webfont.eot and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-BoldItalic-webfont.svg b/gui/slick/css/fonts/OpenSans-BoldItalic-webfont.svg deleted file mode 100644 index 24661f35f983413963336ab60be8470cb1941e36..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-BoldItalic-webfont.svg and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-BoldItalic-webfont.ttf b/gui/slick/css/fonts/OpenSans-BoldItalic-webfont.ttf deleted file mode 100644 index 6a30fa9dd37f5da9f303e421c1e9d52f45bd3433..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-BoldItalic-webfont.ttf and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-BoldItalic-webfont.woff b/gui/slick/css/fonts/OpenSans-BoldItalic-webfont.woff deleted file mode 100644 index 46778a21796d14b21754178d21ded6431905dbee..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-BoldItalic-webfont.woff and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-ExtraBold-webfont.eot b/gui/slick/css/fonts/OpenSans-ExtraBold-webfont.eot deleted file mode 100644 index 2f7ae28d4c5686783e10dfd0dc835f5434e2c589..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-ExtraBold-webfont.eot and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-ExtraBold-webfont.svg b/gui/slick/css/fonts/OpenSans-ExtraBold-webfont.svg deleted file mode 100644 index c3d6642a2c76659ac540115d8ac13765f2ae0450..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-ExtraBold-webfont.svg and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-ExtraBold-webfont.ttf b/gui/slick/css/fonts/OpenSans-ExtraBold-webfont.ttf deleted file mode 100644 index dacc5bbb58035bc5203e206454b92cd3250843fa..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-ExtraBold-webfont.ttf and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-ExtraBold-webfont.woff b/gui/slick/css/fonts/OpenSans-ExtraBold-webfont.woff deleted file mode 100644 index de4f8e77e890aa401db7bf258ec31b02ee184053..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-ExtraBold-webfont.woff and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-ExtraBoldItalic-webfont.eot b/gui/slick/css/fonts/OpenSans-ExtraBoldItalic-webfont.eot deleted file mode 100644 index e4f4ab0db8c0172efd8c0b6e49c9db1d916a19b8..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-ExtraBoldItalic-webfont.eot and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-ExtraBoldItalic-webfont.svg b/gui/slick/css/fonts/OpenSans-ExtraBoldItalic-webfont.svg deleted file mode 100644 index a699015a37eb6c8e47a2e25e794857b608dcdb6e..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-ExtraBoldItalic-webfont.svg and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-ExtraBoldItalic-webfont.ttf b/gui/slick/css/fonts/OpenSans-ExtraBoldItalic-webfont.ttf deleted file mode 100644 index 7e636eb4c051bfed4971d912387eb4554ee47cbb..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-ExtraBoldItalic-webfont.ttf and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-ExtraBoldItalic-webfont.woff b/gui/slick/css/fonts/OpenSans-ExtraBoldItalic-webfont.woff deleted file mode 100644 index f81b21618a3a5665123072c94044839f6871023c..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-ExtraBoldItalic-webfont.woff and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-Italic-webfont.eot b/gui/slick/css/fonts/OpenSans-Italic-webfont.eot deleted file mode 100644 index c31595212fcbd4ff65267f36b92bb733e85f64ff..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-Italic-webfont.eot and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-Italic-webfont.svg b/gui/slick/css/fonts/OpenSans-Italic-webfont.svg deleted file mode 100644 index 537d20ca6ff85eec8e2e6588989f04d616674631..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-Italic-webfont.svg and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-Italic-webfont.ttf b/gui/slick/css/fonts/OpenSans-Italic-webfont.ttf deleted file mode 100644 index cb3fda65e9f6e05f49f7cd67ffa9c2850cfffc32..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-Italic-webfont.ttf and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-Italic-webfont.woff b/gui/slick/css/fonts/OpenSans-Italic-webfont.woff deleted file mode 100644 index 03eaf5861873c5b7fdc011e53917d082b0651c6f..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-Italic-webfont.woff and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-Light-webfont.eot b/gui/slick/css/fonts/OpenSans-Light-webfont.eot deleted file mode 100644 index f17617e0396d6395a3fd3db07604c1754d2e7b1b..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-Light-webfont.eot and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-Light-webfont.svg b/gui/slick/css/fonts/OpenSans-Light-webfont.svg deleted file mode 100644 index c7ae13a29c9b2ace73247e9ed3ce7f7f8539771c..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-Light-webfont.svg and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-Light-webfont.ttf b/gui/slick/css/fonts/OpenSans-Light-webfont.ttf deleted file mode 100644 index b83078a6075d5d7800466c2981158fc721fd9419..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-Light-webfont.ttf and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-Light-webfont.woff b/gui/slick/css/fonts/OpenSans-Light-webfont.woff deleted file mode 100644 index ff882b6acaa0fb595bb7658ec36d663394de3fba..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-Light-webfont.woff and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-LightItalic-webfont.eot b/gui/slick/css/fonts/OpenSans-LightItalic-webfont.eot deleted file mode 100644 index 95c6c619d305e4aec571cad1d0af35ce677d0236..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-LightItalic-webfont.eot and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-LightItalic-webfont.svg b/gui/slick/css/fonts/OpenSans-LightItalic-webfont.svg deleted file mode 100644 index 535e688236feb0b4de25780be74d80c5b1a4e354..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-LightItalic-webfont.svg and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-LightItalic-webfont.ttf b/gui/slick/css/fonts/OpenSans-LightItalic-webfont.ttf deleted file mode 100644 index 3162ff8eb1ecc63453f2c88a43e57fbbee5d5c6c..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-LightItalic-webfont.ttf and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-LightItalic-webfont.woff b/gui/slick/css/fonts/OpenSans-LightItalic-webfont.woff deleted file mode 100644 index f6e97d5afd5fe6aaa8d357af39256b24ae6e5f88..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-LightItalic-webfont.woff and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-Regular-webfont.eot b/gui/slick/css/fonts/OpenSans-Regular-webfont.eot deleted file mode 100644 index 545b7c15e54a1399b315ebc761936289283eeb90..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-Regular-webfont.eot and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-Regular-webfont.svg b/gui/slick/css/fonts/OpenSans-Regular-webfont.svg deleted file mode 100644 index ead219a56987b4ebd7ead6c1d9d07af6efe6bb75..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-Regular-webfont.svg and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-Regular-webfont.ttf b/gui/slick/css/fonts/OpenSans-Regular-webfont.ttf deleted file mode 100644 index a5b2378e5c2f3c0614d06c1d6696e8c6e31c84d3..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-Regular-webfont.ttf and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-Regular-webfont.woff b/gui/slick/css/fonts/OpenSans-Regular-webfont.woff deleted file mode 100644 index 11698afc2c2ae5626334495334d62e3574d839ae..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-Regular-webfont.woff and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-Semibold-webfont.eot b/gui/slick/css/fonts/OpenSans-Semibold-webfont.eot deleted file mode 100644 index acc32c425dd91d5d9012d14c634bcc3e6f00724f..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-Semibold-webfont.eot and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-Semibold-webfont.svg b/gui/slick/css/fonts/OpenSans-Semibold-webfont.svg deleted file mode 100644 index 9eaa0b710f45c71d40ac3a929593f58a9b954a8f..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-Semibold-webfont.svg and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-Semibold-webfont.ttf b/gui/slick/css/fonts/OpenSans-Semibold-webfont.ttf deleted file mode 100644 index a5b9691c1f329bff2acd569b3c82da44d96bb1a2..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-Semibold-webfont.ttf and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-Semibold-webfont.woff b/gui/slick/css/fonts/OpenSans-Semibold-webfont.woff deleted file mode 100644 index 17fb5dc324fece20ea9acca96ae149804e94e4fa..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-Semibold-webfont.woff and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-SemiboldItalic-webfont.eot b/gui/slick/css/fonts/OpenSans-SemiboldItalic-webfont.eot deleted file mode 100644 index 0048da006db46c22f9d527bd4c0809f8b94c3eb4..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-SemiboldItalic-webfont.eot and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-SemiboldItalic-webfont.svg b/gui/slick/css/fonts/OpenSans-SemiboldItalic-webfont.svg deleted file mode 100644 index 316b8186d4efb4487362aa0e51b34d1039633892..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-SemiboldItalic-webfont.svg and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-SemiboldItalic-webfont.ttf b/gui/slick/css/fonts/OpenSans-SemiboldItalic-webfont.ttf deleted file mode 100644 index 61d58bfa37b1f2b143527d72084130a59a5eb053..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-SemiboldItalic-webfont.ttf and /dev/null differ diff --git a/gui/slick/css/fonts/OpenSans-SemiboldItalic-webfont.woff b/gui/slick/css/fonts/OpenSans-SemiboldItalic-webfont.woff deleted file mode 100644 index 611b39028f17d21ffed411076b8457c66dda9199..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/OpenSans-SemiboldItalic-webfont.woff and /dev/null differ diff --git a/gui/slick/css/fonts/droidsansmono-webfont.eot b/gui/slick/css/fonts/droidsansmono-webfont.eot deleted file mode 100644 index aca857ba6567dad052ca8825f4146371ad5212fd..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/droidsansmono-webfont.eot and /dev/null differ diff --git a/gui/slick/css/fonts/droidsansmono-webfont.svg b/gui/slick/css/fonts/droidsansmono-webfont.svg deleted file mode 100644 index 584c09ecbf2095b3fcd4642fda2ac80114311898..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/droidsansmono-webfont.svg and /dev/null differ diff --git a/gui/slick/css/fonts/droidsansmono-webfont.ttf b/gui/slick/css/fonts/droidsansmono-webfont.ttf deleted file mode 100644 index 1e576a8b31e115c4f14188ba5d7ed6948e309e72..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/droidsansmono-webfont.ttf and /dev/null differ diff --git a/gui/slick/css/fonts/droidsansmono-webfont.woff b/gui/slick/css/fonts/droidsansmono-webfont.woff deleted file mode 100644 index ef6964d237603fcc8ad45e4e287aa9616347fa07..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/fonts/droidsansmono-webfont.woff and /dev/null differ diff --git a/gui/slick/css/iXeptR36kaC0GEAetxi8cqLH4MEiSE0ROcU-qHOA.ttf b/gui/slick/css/iXeptR36kaC0GEAetxi8cqLH4MEiSE0ROcU-qHOA.ttf new file mode 100644 index 0000000000000000000000000000000000000000..ee892609576fbea2b53f7e6506bba3a8aebca10e Binary files /dev/null and b/gui/slick/css/iXeptR36kaC0GEAetxi8cqLH4MEiSE0ROcU-qHOA.ttf differ diff --git a/gui/slick/css/iXeptR36kaC0GEAetxlDMrAYtoOisqqMDW9M_Mqc.ttf b/gui/slick/css/iXeptR36kaC0GEAetxlDMrAYtoOisqqMDW9M_Mqc.ttf new file mode 100644 index 0000000000000000000000000000000000000000..57e8b7d54ffc5568301c942c4a7bfece55d080f7 Binary files /dev/null and b/gui/slick/css/iXeptR36kaC0GEAetxlDMrAYtoOisqqMDW9M_Mqc.ttf differ diff --git a/gui/slick/css/iXeptR36kaC0GEAetxp_TkvowlIOtbR7ePgFOpF4.ttf b/gui/slick/css/iXeptR36kaC0GEAetxp_TkvowlIOtbR7ePgFOpF4.ttf new file mode 100644 index 0000000000000000000000000000000000000000..1e54a3172d1f65d35e75139e5c22def8e95f0435 Binary files /dev/null and b/gui/slick/css/iXeptR36kaC0GEAetxp_TkvowlIOtbR7ePgFOpF4.ttf differ diff --git a/gui/slick/css/iXeptR36kaC0GEAetxrfB31yxOzP-czbf6AAKCVo.ttf b/gui/slick/css/iXeptR36kaC0GEAetxrfB31yxOzP-czbf6AAKCVo.ttf new file mode 100644 index 0000000000000000000000000000000000000000..716c390d8f955339bda9fa605908e27038f5d892 Binary files /dev/null and b/gui/slick/css/iXeptR36kaC0GEAetxrfB31yxOzP-czbf6AAKCVo.ttf differ diff --git a/gui/slick/css/imports/config.less b/gui/slick/css/imports/config.less deleted file mode 100644 index a9fba3b2c0dbce77481c539d88084e8202994378..0000000000000000000000000000000000000000 --- a/gui/slick/css/imports/config.less +++ /dev/null @@ -1,78 +0,0 @@ -/* Variables */ -@base-font-face: "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif; -@alt-font-face: "Trebuchet MS", Helvetica, Arial, sans-serif; -@base-font-size: 12px; -@text-color: #343434; -@swatch-blue: #4183C4; -@swatch-green: #BDE433; -@swatch-grey: #666666; -@link-color: #555555; -@border-color: #CCCCCC; -@msg-bg: #FFF6A9; -@msg-bg-success: #D3FFD7; -@msg-bg-error: #FFD3D3; - -/* Mixins */ -.rounded(@radius: 5px) { - -moz-border-radius: @radius; - -webkit-border-radius: @radius; - border-radius: @radius; -} -.roundedTop(@radius: 5px) { - -moz-border-radius-topleft: @radius; - -moz-border-radius-topright: @radius; - -webkit-border-top-right-radius: @radius; - -webkit-border-top-left-radius: @radius; - border-top-left-radius: @radius; - border-top-right-radius: @radius; -} -.roundedLeftTop(@radius: 5px) { - -moz-border-radius-topleft: @radius; - -webkit-border-top-left-radius: @radius; - border-top-left-radius: @radius; -} -.roundedRightTop(@radius: 5px) { - -moz-border-radius-topright: @radius; - -webkit-border-top-right-radius: @radius; - border-top-right-radius: @radius; -} -.roundedBottom(@radius: 5px) { - -moz-border-radius-bottomleft: @radius; - -moz-border-radius-bottomright: @radius; - -webkit-border-bottom-right-radius: @radius; - -webkit-border-bottom-left-radius: @radius; - border-bottom-left-radius: @radius; - border-bottom-right-radius: @radius; -} -.roundedLeftBottom(@radius: 5px) { - -moz-border-radius-bottomleft: @radius; - -webkit-border-bottom-left-radius: @radius; - border-bottom-left-radius: @radius; -} -.roundedRightBottom(@radius: 5px) { - -moz-border-radius-bottomright: @radius; - -webkit-border-bottom-right-radius: @radius; - border-bottom-right-radius: @radius; -} -.shadow(@shadow: 0 17px 11px -1px #ced8d9) { - -moz-box-shadow: @shadow; - -webkit-box-shadow: @shadow; - -o-box-shadow: @shadow; - box-shadow: @shadow; -} -.gradient(@gradientFrom: #FFFFFF, @gradientTo: #EEEEEE){ - background-image: -moz-linear-gradient(@gradientFrom, @gradientTo) !important; - background-image: linear-gradient(@gradientFrom, @gradientTo) !important; - background-image: -webkit-linear-gradient(@gradientFrom, @gradientTo) !important; - background-image: -o-linear-gradient(@gradientFrom, @gradientTo) !important; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=@gradientFrom, endColorstr=@gradientTo) !important; - -ms-filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=@gradientFrom, endColorstr=@gradientTo) !important; -} -.opacity(@opacity_percent:85) { - filter: ~"alpha(opacity=85)"; - -moz-opacity: @opacity_percent / 100 !important; - -khtml-opacity:@opacity_percent / 100 !important; - -o-opacity:@opacity_percent / 100 !important; - opacity:@opacity_percent / 100 !important; -} - diff --git a/gui/slick/css/lib/bootstrap.min.css b/gui/slick/css/lib/bootstrap.min.css deleted file mode 100644 index 6dc17a6ab2052a25d92d460e255d70d8688c6f25..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/lib/bootstrap.min.css and /dev/null differ diff --git a/gui/slick/css/lib/pnotify.custom.min.css b/gui/slick/css/lib/pnotify.custom.min.css deleted file mode 100644 index 8f2d3eb74d4cf473f30609b7d6711947e16b47d1..0000000000000000000000000000000000000000 Binary files a/gui/slick/css/lib/pnotify.custom.min.css and /dev/null differ diff --git a/gui/slick/css/style.css b/gui/slick/css/style.css index 7617360f8b1e73f49d34bbd9316b64ba7c40b17b..9d0bd556156a63bbc4e33f5d70bac1049e098aa2 100644 --- a/gui/slick/css/style.css +++ b/gui/slick/css/style.css @@ -1,145 +1,3 @@ -/* ======================================================================= -fonts -========================================================================== */ -/* Open Sans */ -/* Regular */ -@font-face { - font-family: 'Open Sans'; - - src: url('fonts/OpenSans-Regular-webfont.eot'); - src: url('fonts/OpenSans-Regular-webfont.eot?#iefix') format('embedded-opentype'), - url('fonts/OpenSans-Regular-webfont.woff') format('woff'), - url('fonts/OpenSans-Regular-webfont.ttf') format('truetype'), - url('fonts/OpenSans-Regular-webfont.svg#OpenSansRegular') format('svg'); - font-weight: normal; - font-weight: 400; - font-style: normal; -} - -/* Italic */ -@font-face { - font-family: 'Open Sans'; - src: url('fonts/OpenSans-Italic-webfont.eot'); - src: url('fonts/OpenSans-Italic-webfont.eot?#iefix') format('embedded-opentype'), - url('fonts/OpenSans-Italic-webfont.woff') format('woff'), - url('fonts/OpenSans-Italic-webfont.ttf') format('truetype'), - url('fonts/OpenSans-Italic-webfont.svg#OpenSansItalic') format('svg'); - font-weight: normal; - font-weight: 400; - font-style: italic; -} - -/* Light */ -@font-face { - font-family: 'Open Sans'; - src: url('fonts/OpenSans-Light-webfont.eot'); - src: url('fonts/OpenSans-Light-webfont.eot?#iefix') format('embedded-opentype'), - url('fonts/OpenSans-Light-webfont.woff') format('woff'), - url('fonts/OpenSans-Light-webfont.ttf') format('truetype'), - url('fonts/OpenSans-Light-webfont.svg#OpenSansLight') format('svg'); - font-weight: 200; - font-style: normal; -} - -/* Light Italic */ -@font-face { - font-family: 'Open Sans'; - src: url('fonts/OpenSans-LightItalic-webfont.eot'); - src: url('fonts/OpenSans-LightItalic-webfont.eot?#iefix') format('embedded-opentype'), - url('fonts/OpenSans-LightItalic-webfont.woff') format('woff'), - url('fonts/OpenSans-LightItalic-webfont.ttf') format('truetype'), - url('fonts/OpenSans-LightItalic-webfont.svg#OpenSansLightItalic') format('svg'); - font-weight: 200; - font-style: italic; -} - -/* Semibold */ -@font-face { - font-family: 'Open Sans'; - src: url('fonts/OpenSans-Semibold-webfont.eot'); - src: url('fonts/OpenSans-Semibold-webfont.eot?#iefix') format('embedded-opentype'), - url('fonts/OpenSans-Semibold-webfont.woff') format('woff'), - url('fonts/OpenSans-Semibold-webfont.ttf') format('truetype'), - url('fonts/OpenSans-Semibold-webfont.svg#OpenSansSemibold') format('svg'); - font-weight: 600; - font-style: normal; -} - -/* Semibold Italic */ -@font-face { - font-family: 'Open Sans'; - src: url('fonts/OpenSans-SemiboldItalic-webfont.eot'); - src: url('fonts/OpenSans-SemiboldItalic-webfont.eot?#iefix') format('embedded-opentype'), - url('fonts/OpenSans-SemiboldItalic-webfont.woff') format('woff'), - url('fonts/OpenSans-SemiboldItalic-webfont.ttf') format('truetype'), - url('fonts/OpenSans-SemiboldItalic-webfont.svg#OpenSansSemiboldItalic') format('svg'); - font-weight: 600; - font-style: italic; -} - -/* Bold */ -@font-face { - font-family: 'Open Sans'; - src: url('fonts/OpenSans-Semibold-webfont.eot'); - src: url('fonts/OpenSans-Semibold-webfont.eot?#iefix') format('embedded-opentype'), - url('fonts/OpenSans-Semibold-webfont.woff') format('woff'), - url('fonts/OpenSans-Semibold-webfont.ttf') format('truetype'), - url('fonts/OpenSans-Semibold-webfont.svg#OpenSansSemibold') format('svg'); - font-weight: bold; - font-weight: 700; - font-style: normal; -} - -/* Bold Italic */ -@font-face { - font-family: 'Open Sans'; - src: url('fonts/OpenSans-SemiboldItalic-webfont.eot'); - src: url('fonts/OpenSans-SemiboldItalic-webfont.eot?#iefix') format('embedded-opentype'), - url('fonts/OpenSans-SemiboldItalic-webfont.woff') format('woff'), - url('fonts/OpenSans-SemiboldItalic-webfont.ttf') format('truetype'), - url('fonts/OpenSans-SemiboldItalic-webfont.svg#OpenSansSemiboldItalic') format('svg'); - font-weight: bold; - font-weight: 700; - font-style: italic; -} - -/* Extra Bold */ -@font-face { - font-family: 'Open Sans'; - src: url('fonts/OpenSans-Bold-webfont.eot'); - src: url('fonts/OpenSans-Bold-webfont.eot?#iefix') format('embedded-opentype'), - url('fonts/OpenSans-Bold-webfont.woff') format('woff'), - url('fonts/OpenSans-Bold-webfont.ttf') format('truetype'), - url('fonts/OpenSans-Bold-webfont.svg#OpenSansBold') format('svg'); - font-weight: 900; - font-style: normal; -} - -/* Extra Bold Italic */ -@font-face { - font-family: 'Open Sans'; - src: url('fonts/OpenSans-BoldItalic-webfont.eot'); - src: url('fonts/OpenSans-BoldItalic-webfont.eot?#iefix') format('embedded-opentype'), - url('fonts/OpenSans-BoldItalic-webfont.woff') format('woff'), - url('fonts/OpenSans-BoldItalic-webfont.ttf') format('truetype'), - url('fonts/OpenSans-BoldItalic-webfont.svg#OpenSansBoldItalic') format('svg'); - font-weight: 900; - font-style: italic; -} - -/* Droid Sans */ -@font-face { - font-family: 'droid_sans_mono'; - src: url('fonts/droidsansmono-webfont.eot'); - src: url('fonts/droidsansmono-webfont.eot?#iefix') format('embedded-opentype'), - url('fonts/droidsansmono-webfont.woff') format('woff'), - url('fonts/droidsansmono-webfont.ttf') format('truetype'), - url('fonts/droidsansmono-webfont.svg#droid_sans_monoregular') format('svg'); - font-weight: normal; - font-style: normal; -} - - /* ======================================================================= inc_top.mako ========================================================================== */ diff --git a/gui/slick/css/vender.min.css b/gui/slick/css/vender.min.css index c23d4637c1e42dbea7d6a89964b962a45fffef9a..8517ed95fe6109d4ec5f505f940db29ede8e54e6 100644 Binary files a/gui/slick/css/vender.min.css and b/gui/slick/css/vender.min.css differ diff --git a/gui/slick/css/fonts/glyphicons-halflings-regular.eot b/gui/slick/fonts/glyphicons-halflings-regular.eot similarity index 100% rename from gui/slick/css/fonts/glyphicons-halflings-regular.eot rename to gui/slick/fonts/glyphicons-halflings-regular.eot diff --git a/gui/slick/css/fonts/glyphicons-halflings-regular.svg b/gui/slick/fonts/glyphicons-halflings-regular.svg similarity index 100% rename from gui/slick/css/fonts/glyphicons-halflings-regular.svg rename to gui/slick/fonts/glyphicons-halflings-regular.svg diff --git a/gui/slick/css/fonts/glyphicons-halflings-regular.ttf b/gui/slick/fonts/glyphicons-halflings-regular.ttf similarity index 100% rename from gui/slick/css/fonts/glyphicons-halflings-regular.ttf rename to gui/slick/fonts/glyphicons-halflings-regular.ttf diff --git a/gui/slick/css/fonts/glyphicons-halflings-regular.woff b/gui/slick/fonts/glyphicons-halflings-regular.woff similarity index 100% rename from gui/slick/css/fonts/glyphicons-halflings-regular.woff rename to gui/slick/fonts/glyphicons-halflings-regular.woff diff --git a/gui/slick/css/fonts/glyphicons-halflings-regular.woff2 b/gui/slick/fonts/glyphicons-halflings-regular.woff2 similarity index 100% rename from gui/slick/css/fonts/glyphicons-halflings-regular.woff2 rename to gui/slick/fonts/glyphicons-halflings-regular.woff2 diff --git a/gui/slick/images/notifiers/telegram.png b/gui/slick/images/notifiers/telegram.png new file mode 100644 index 0000000000000000000000000000000000000000..427d6cc567cd3c5888721c57552cbe664038193d Binary files /dev/null and b/gui/slick/images/notifiers/telegram.png differ diff --git a/gui/slick/images/providers/demonoid.png b/gui/slick/images/providers/demonoid.png new file mode 100644 index 0000000000000000000000000000000000000000..ea8caa7e85ed4cc5b52052782b7c9aafb5236f7a Binary files /dev/null and b/gui/slick/images/providers/demonoid.png differ diff --git a/gui/slick/images/providers/nutech.png b/gui/slick/images/providers/nutech.png new file mode 100644 index 0000000000000000000000000000000000000000..0a89676a96065d40f29e43471155501de17e2841 Binary files /dev/null and b/gui/slick/images/providers/nutech.png differ diff --git a/gui/slick/images/xem.png b/gui/slick/images/xem.png index 634def233941b7029a04dc2e7ffc63c3af727f35..5614d0835f6d7f7a0552171a2637bc470b574a80 100644 Binary files a/gui/slick/images/xem.png and b/gui/slick/images/xem.png differ diff --git a/gui/slick/js/addShowOptions.js b/gui/slick/js/addShowOptions.js index 8fa692b068226c5de4539cc6d78358e8bd6e4437..223bf051e5c6f768982fd506dac95bb5a93e6a12 100644 --- a/gui/slick/js/addShowOptions.js +++ b/gui/slick/js/addShowOptions.js @@ -31,4 +31,11 @@ $(document).ready(function () { $('#statusSelect, #qualityPreset, #flatten_folders, #anyQualities, #bestQualities, #subtitles, #scene, #anime, #statusSelectAfter').change(function () { $('#saveDefaultsButton').attr('disabled', false); }); + + $('#qualityPreset').on('change', function() { + //fix issue #181 - force re-render to correct the height of the outer div + $('span.prev').click(); + $('span.next').click(); + }); + }); diff --git a/gui/slick/js/browser.js b/gui/slick/js/browser.js index b3a447d8159202e16541e6f0e109bd9a1ddeb43e..e9512dd67ab6fac3fa65318b74c949db901e97f3 100644 --- a/gui/slick/js/browser.js +++ b/gui/slick/js/browser.js @@ -78,7 +78,7 @@ // make a fileBrowserDialog object if one doesn't exist already if (!fileBrowserDialog) { // set up the jquery dialog - fileBrowserDialog = $('<div id="fileBrowserDialog" style="display:hidden"></div>').appendTo('body').dialog({ + fileBrowserDialog = $('<div class="fileBrowserDialog" style="display:hidden"></div>').appendTo('body').dialog({ dialogClass: 'browserDialog', title: options.title, position: { my: 'center top', at: 'center top+60', of: window }, @@ -89,30 +89,26 @@ modal: true, autoOpen: false }); - } - else { + } else { // The title may change, even if fileBrowserDialog already exists - fileBrowserDialog.dialog('option', 'title', options.title); + fileBrowserDialog.dialog('option', 'title', options.title); } - - fileBrowserDialog.dialog('option', 'buttons', [ - { - text: 'Ok', - 'class': 'btn', - click: function () { - // store the browsed path to the associated text field - callback(currentBrowserPath, options); - $(this).dialog('close'); - } - }, - { - text: 'Cancel', - 'class': 'btn', - click: function () { - $(this).dialog('close'); - } + + fileBrowserDialog.dialog('option', 'buttons', [{ + text: 'Ok', + 'class': 'btn', + click: function () { + // store the browsed path to the associated text field + callback(currentBrowserPath, options); + $(this).dialog('close'); + } + }, { + text: 'Cancel', + 'class': 'btn', + click: function () { + $(this).dialog('close'); } - ]); + }]); // set up the browser and launch the dialog var initialDir = ''; diff --git a/gui/slick/js/core.js b/gui/slick/js/core.js index 63a2a53c5d84fb6c9af01b58780ccb8aba837982..4784e58767dd08beb118a43263ba9d3875683342 100644 --- a/gui/slick/js/core.js +++ b/gui/slick/js/core.js @@ -700,6 +700,36 @@ var SICKRAGE = { }); }); + $('#testTelegram').on('click', function () { + var telegram = {}; + telegram.id = $.trim($('#telegram_id').val()); + telegram.apikey = $.trim($('#telegram_apikey').val()); + if (!telegram.id || !telegram.apikey) { + $('#testTelegram-result').html('Please fill out the necessary fields above.'); + if (!telegram.id) { + $('#telegram_id').addClass('warning'); + } else { + $('#telegram_id').removeClass('warning'); + } + if (!telegram.apikey) { + $('#telegram_apikey').addClass('warning'); + } else { + $('#telegram_apikey').removeClass('warning'); + } + return; + } + $('#telegram_id,#telegram_apikey').removeClass('warning'); + $(this).prop('disabled', true); + $('#testTelegram-result').html(loading); + $.get(srRoot + '/home/testTelegram', { + 'telegram_id': telegram.id, + 'telegram_apikey': telegram.apikey + }).done(function (data) { + $('#testTelegram-result').html(data); + $('#testTelegram').prop('disabled', false); + }); + }); + $('#TraktGetPin').on('click', function () { window.open($('#trakt_pin_url').val(), "popUp", "toolbar=no, scrollbars=no, resizable=no, top=200, left=200, width=650, height=550"); $('#trakt_pin').removeClass('hide'); @@ -745,7 +775,7 @@ var SICKRAGE = { } if (/\s/g.test(trakt.trendingBlacklist)) { - $('#testTrakt-result').html('Check blacklist name; the value need to be a trakt slug'); + $('#testTrakt-result').html('Check blacklist name; the value needs to be a trakt slug'); $('#trakt_blacklist_name').addClass('warning'); return; } @@ -1805,8 +1835,8 @@ var SICKRAGE = { $('#filterShowName').on('input', _.debounce(function() { $('.show-grid').isotope({ filter: function () { - var name = $(this).data('name'); - return name.toLowerCase(); + var name = $(this).attr('data-name').toLowerCase(); + return name.indexOf($('#filterShowName').val()) > -1; } }); }, 500)); @@ -1916,7 +1946,7 @@ var SICKRAGE = { if (f === '') { test = true; } else { - var result = f.match(/(<|<=|>=|>)\s(\d+)/i); + var result = f.match(/(<|<=|>=|>)\s+(\d+)/i); if (result) { if (result[1] === "<") { if (pct < parseInt(result[2])) { @@ -1937,7 +1967,7 @@ var SICKRAGE = { } } - result = f.match(/(\d+)\s(-|to)\s(\d+)/i); + result = f.match(/(\d+)\s(-|to)\s+(\d+)/i); if (result) { if ((result[2] === "-") || (result[2] === "to")) { if ((pct >= parseInt(result[1])) && (pct <= parseInt(result[3]))) { @@ -1982,8 +2012,8 @@ var SICKRAGE = { }, getSortData: { name: function (itemElem) { - var name = $(itemElem).attr('data-name'); - return (metaToBool('sickbeard.SORT_ARTICLE') ? (name || '') : (name || '').replace(/^(The|A|An)\s/i,'')); + var name = $(itemElem).attr('data-name') || ''; + return (metaToBool('sickbeard.SORT_ARTICLE') ? name : name.replace(/^((?:The|A|An)\s)/i, '')).toLowerCase(); }, network: '[data-network]', date: function (itemElem) { @@ -2829,7 +2859,7 @@ var SICKRAGE = { getSortData: { name: function(itemElem) { var name = $(itemElem).attr('data-name') || ''; - return (metaToBool('sickbeard.SORT_ARTICLE') ? name : name.replace(/^(The|A|An)\s/i, '')).toLowerCase(); + return (metaToBool('sickbeard.SORT_ARTICLE') ? name : name.replace(/^((?:The|A|An)\s)/i, '')).toLowerCase(); }, rating: '[data-rating] parseInt', votes: '[data-votes] parseInt', diff --git a/gui/slick/js/core.min.js b/gui/slick/js/core.min.js index 98337456ec2605e5f2ee671b6fc41e02297cb43c..0e5d8c3ab3dba6c9fa22f35b013eba434e9be19c 100644 Binary files a/gui/slick/js/core.min.js and b/gui/slick/js/core.min.js differ diff --git a/gui/slick/views/404.mako b/gui/slick/views/404.mako new file mode 100644 index 0000000000000000000000000000000000000000..e090d5be0512ec7b9d9c9f7ff06a84bd94a3c345 --- /dev/null +++ b/gui/slick/views/404.mako @@ -0,0 +1,7 @@ +<%inherit file="/layouts/main.mako"/> +<%block name="content"> +<h1 class="header">${header}</h1> +<div class="align-center"> +You have reached this page by accident, please check the url. +</div> +</%block> diff --git a/gui/slick/views/500.mako b/gui/slick/views/500.mako new file mode 100644 index 0000000000000000000000000000000000000000..4a8547d4ac1b098715758f4b9873ef205ef2b1d0 --- /dev/null +++ b/gui/slick/views/500.mako @@ -0,0 +1,25 @@ +<%inherit file="/layouts/main.mako"/> + +<%block name="content"> +<h1 class="header">${header}</h1> +<p> +A mako error has occured.<br> +If this happened during an update a simple page refresh may be the solution.<br> +Mako errors that happen during updates may be a one time error if there were significant ui changes.<br> +</p> +<hr> +<a href="#mako-error" class="btn btn-default" data-toggle="collapse">Show/Hide Error</a> +<div id="mako-error" class="collapse"> +<br> +<div class="align-center"> +<pre> +<% filename, lineno, function, line = backtrace.traceback[-1] %> +File ${filename}:${lineno}, in ${function}: +% if line: +${line} +% endif +${str(backtrace.error.__class__.__name__)}: ${backtrace.error} +</pre> +</div> +</div> +</%block> diff --git a/gui/slick/views/addShows_popularShows.mako b/gui/slick/views/addShows_popularShows.mako index 1dae0780983b63bece22c5f4f9cfcfe7d577318f..b5d33a21b56db2906a488bbe49d00e2b27e5897b 100644 --- a/gui/slick/views/addShows_popularShows.mako +++ b/gui/slick/views/addShows_popularShows.mako @@ -69,7 +69,7 @@ <p>${int(float(cur_rating)*10)}% <img src="${srRoot}/images/heart.png"></p> <i>${cur_votes} votes</i> <div class="traktShowTitleIcons"> - <a href="${srRoot}/addShows/addShowByID?indexer_id=${cur_result['imdb_tt']}&show_name=${cur_result['name']}&indexer=IMDB" class="btn btn-xs" data-no-redirect>Add Show</a> + <a href="${srRoot}/addShows/addShowByID?indexer_id=${cur_result['imdb_tt']}&show_name=${cur_result['name'] | u}&indexer=IMDB" class="btn btn-xs" data-no-redirect>Add Show</a> </div> </div> </div> diff --git a/gui/slick/views/apiBuilder.mako b/gui/slick/views/apiBuilder.mako index 460f8f8ae460443cb3253352d6fd80380caaa145..970dc6a8966129786f0e8bc54a67fbd80a888d3d 100644 --- a/gui/slick/views/apiBuilder.mako +++ b/gui/slick/views/apiBuilder.mako @@ -60,7 +60,7 @@ <link rel="apple-touch-icon" sizes="72x72" href="${srRoot}/images/ico/favicon-72.png"> <link rel="apple-touch-icon" href="${srRoot}/images/ico/favicon-57.png"> - <link rel="stylesheet" type="text/css" href="${srRoot}/css/lib/bootstrap.min.css?${sbPID}"/> + <link rel="stylesheet" type="text/css" href="${srRoot}/css/vender.min.css?${sbPID}"/> <link rel="stylesheet" type="text/css" href="${srRoot}/css/browser.css?${sbPID}" /> <link rel="stylesheet" type="text/css" href="${srRoot}/css/lib/jquery-ui-1.10.4.custom.min.css?${sbPID}" /> <link rel="stylesheet" type="text/css" href="${srRoot}/css/style.css?${sbPID}"/> diff --git a/gui/slick/views/config_notifications.mako b/gui/slick/views/config_notifications.mako index ac84c8e66d8d1bcbdfc9221543b7605f294b9c42..bed9e956943fedef75dca1d372f06b81cbee975a 100644 --- a/gui/slick/views/config_notifications.mako +++ b/gui/slick/views/config_notifications.mako @@ -1388,6 +1388,78 @@ </fieldset> </div><!-- /freemobile component-group //--> + <div class="component-group"> + <div class="component-group-desc"> + <img class="notifier-icon" src="${srRoot}/images/notifiers/telegram.png" alt="" title="Telegram" /> + <h3><a href="${anon_url('https://telegram.org/')}" rel="noreferrer" onclick="window.open(this.href, '_blank'); return false;">Telegram</a></h3> + <p>Telegram is a cloud-based instant messaging service.</p> + </div> + <fieldset class="component-group-list"> + <div class="field-pair"> + <label for="use_telegram"> + <span class="component-title">Enable</span> + <span class="component-desc"> + <input type="checkbox" class="enabler" name="use_telegram" id="use_telegram" ${('', 'checked="checked"')[bool(sickbeard.USE_TELEGRAM)]}/> + <p>Send Telegram notifications?</p> + </span> + </label> + </div> + + <div id="content_use_telegram"> + <div class="field-pair"> + <label for="telegram_notify_onsnatch"> + <span class="component-title">Notify on snatch</span> + <span class="component-desc"> + <input type="checkbox" name="telegram_notify_onsnatch" id="telegram_notify_onsnatch" ${('', 'checked="checked"')[bool(sickbeard.TELEGRAM_NOTIFY_ONSNATCH)]}/> + <p>Send a message when a download starts?</p> + </span> + </label> + </div> + <div class="field-pair"> + <label for="telegram_notify_ondownload"> + <span class="component-title">Notify on download</span> + <span class="component-desc"> + <input type="checkbox" name="telegram_notify_ondownload" id="telegram_notify_ondownload" ${('', 'checked="checked"')[bool(sickbeard.TELEGRAM_NOTIFY_ONDOWNLOAD)]}/> + <p>Send a message when a download finishes?</p> + </span> + </label> + </div> + <div class="field-pair"> + <label for="telegram_notify_onsubtitledownload"> + <span class="component-title">Notify on subtitle download</span> + <span class="component-desc"> + <input type="checkbox" name="telegram_notify_onsubtitledownload" id="telegram_notify_onsubtitledownload" ${('', 'checked="checked"')[bool(sickbeard.TELEGRAM_NOTIFY_ONSUBTITLEDOWNLOAD)]}/> + <p>Send a message when subtitles are downloaded?</p> + </span> + </label> + </div> + <div class="field-pair"> + <label for="telegram_id"> + <span class="component-title">User/group ID</span> + <input type="text" name="telegram_id" id="telegram_id" value="${sickbeard.TELEGRAM_ID}" class="form-control input-sm input250" autocapitalize="off" /> + </label> + <label> + <span class="component-title"> </span> + <span class="component-desc">Contact @myidbot on Telegram to get an ID</span> + </label> + </div> + <div class="field-pair"> + <label for="telegram_password"> + <span class="component-title">Bot API token</span> + <input type="text" name="telegram_apikey" id="telegram_apikey" value="${sickbeard.TELEGRAM_APIKEY}" class="form-control input-sm input250" autocapitalize="off" /> + </label> + <label> + <span class="component-title"> </span> + <span class="component-desc">Contact @BotFather on Telegram to set up one</span> + </label> + </div> + <div class="testNotification" id="testTelegram-result">Click below to test your settings.</div> + <input class="btn" type="button" value="Test Telegram" id="testTelegram" /> + <input type="submit" class="config_submitter btn" value="Save Changes" /> + </div><!-- /content_use_telegram //--> + + </fieldset> + </div><!-- /telegram component-group //--> </div> @@ -1404,7 +1476,7 @@ <span class="component-title">Enable</span> <span class="component-desc"> <input type="checkbox" class="enabler" name="use_twitter" id="use_twitter" ${('', 'checked="checked"')[bool(sickbeard.USE_TWITTER)]}/> - <p>should SickRage post tweets on Twitter?</p> + <p>Should SickRage post tweets on Twitter?</p> </span> </label> <label> diff --git a/gui/slick/views/config_postProcessing.mako b/gui/slick/views/config_postProcessing.mako index e45eca4df6e591acb703f1687cebde1868dafa00..6ae984827353a354b26ba4b8ca86c05b83f305eb 100644 --- a/gui/slick/views/config_postProcessing.mako +++ b/gui/slick/views/config_postProcessing.mako @@ -446,7 +446,7 @@ <td>Show.Name.S02E03.HDTV.XviD-RLSGROUP</td> </tr> <tr> - <td class="align-right"><i class="glyphicon glyphicon-info-sign" title="'SiCKRAGE' is used in place of RLSGROUP if it could not be properly detected"></i> <b>Release Group:</b></td> + <td class="align-right"><i class="glyphicon glyphicon-info-sign" title="'SickRage' is used in place of RLSGROUP if it could not be properly detected"></i> <b>Release Group:</b></td> <td>%RG</td> <td>RLSGROUP</td> </tr> @@ -654,7 +654,7 @@ <td>Show.Name.2010.03.09.HDTV.XviD-RLSGROUP</td> </tr> <tr class="even"> - <td class="align-right"><i class="glyphicon glyphicon-info-sign" title="'SiCKRAGE' is used in place of RLSGROUP if it could not be properly detected"></i> <b>Release Group:</b></td> + <td class="align-right"><i class="glyphicon glyphicon-info-sign" title="'SickRage' is used in place of RLSGROUP if it could not be properly detected"></i> <b>Release Group:</b></td> <td>%RG</td> <td>RLSGROUP</td> </tr> @@ -831,7 +831,7 @@ <td>Show.Name.9th.Mar.2011.HDTV.XviD-RLSGROUP</td> </tr> <tr class="even"> - <td class="align-right"><i class="glyphicon glyphicon-info-sign" title="'SiCKRAGE' is used in place of RLSGROUP if it could not be properly detected"></i> <b>Release Group:</b></td> + <td class="align-right"><i class="glyphicon glyphicon-info-sign" title="'SickRage' is used in place of RLSGROUP if it could not be properly detected"></i> <b>Release Group:</b></td> <td>%RG</td> <td>RLSGROUP</td> </tr> @@ -1004,7 +1004,7 @@ <td>Show.Name.S02E03.HDTV.XviD-RLSGROUP</td> </tr> <tr> - <td class="align-right"><i class="glyphicon glyphicon-info-sign" title="'SiCKRAGE' is used in place of RLSGROUP if it could not be properly detected"></i> <b>Release Group:</b></td> + <td class="align-right"><i class="glyphicon glyphicon-info-sign" title="'SickRage' is used in place of RLSGROUP if it could not be properly detected"></i> <b>Release Group:</b></td> <td>%RG</td> <td>RLSGROUP</td> </tr> diff --git a/gui/slick/views/home.mako b/gui/slick/views/home.mako index 7f72b5311227a9c0267ff3fac6c86b46b47907c7..9c0362e19f50c0d565a59a6b4c70291637e27e46 100644 --- a/gui/slick/views/home.mako +++ b/gui/slick/views/home.mako @@ -55,8 +55,8 @@ <span class="show-option"> Direction: <select id="postersortdirection" class="form-control form-control-inline input-sm"> - <option value="true" data-sort="${srRoot}/setPosterSortDir/?direction=1" ${('', 'selected="selected"')[sickbeard.POSTER_SORTDIR == 1]}>A ➜ Z</option> - <option value="false" data-sort="${srRoot}/setPosterSortDir/?direction=0" ${('', 'selected="selected"')[sickbeard.POSTER_SORTDIR == 0]}>Z ➜ A</option> + <option value="true" data-sort="${srRoot}/setPosterSortDir/?direction=1" ${('', 'selected="selected"')[sickbeard.POSTER_SORTDIR == 1]}>Ascending </option> + <option value="false" data-sort="${srRoot}/setPosterSortDir/?direction=0" ${('', 'selected="selected"')[sickbeard.POSTER_SORTDIR == 0]}>Descending</option> </select> </span> % endif diff --git a/gui/slick/views/inc_addShowOptions.mako b/gui/slick/views/inc_addShowOptions.mako index 673fb4f639549ce8382f886d973516e534dd1111..838a170f3513d4b234edb06c35865121e40273ea 100644 --- a/gui/slick/views/inc_addShowOptions.mako +++ b/gui/slick/views/inc_addShowOptions.mako @@ -4,6 +4,17 @@ from sickbeard.common import Quality, qualityPresets, qualityPresetStrings, statusStrings from sickbeard import subtitles %> + + <div class="field-pair alt"> + <label for="customQuality" class="clearfix"> + <span class="component-title">Preferred Quality</span> + <span class="component-desc"> + <% anyQualities, bestQualities = Quality.splitQuality(sickbeard.QUALITY_DEFAULT) %> + <%include file="/inc_qualityChooser.mako"/> + </span> + </label> + </div> + % if sickbeard.USE_SUBTITLES: <br><div class="field-pair"> <label for="subtitles" class="clearfix"> @@ -72,9 +83,6 @@ </label> </div> - <% anyQualities, bestQualities = Quality.splitQuality(sickbeard.QUALITY_DEFAULT) %> - <%include file="/inc_qualityChooser.mako"/> - <br> <div class="field-pair alt"> <label for="saveDefaultsButton" class="nocheck clearfix"> diff --git a/gui/slick/views/layouts/main.mako b/gui/slick/views/layouts/main.mako index 41f3702157c6cce92f5e20b01898109604545f7c..bd7b8cec570aaa70a07ef6912dfa339c2d93cad0 100644 --- a/gui/slick/views/layouts/main.mako +++ b/gui/slick/views/layouts/main.mako @@ -78,7 +78,6 @@ <link rel="apple-touch-icon" href="${srRoot}/images/ico/favicon-57.png"> <link rel="stylesheet" type="text/css" href="${srRoot}/css/vender.min.css?${sbPID}"/> - <link rel="stylesheet" type="text/css" href="${srRoot}/css/lib/bootstrap.min.css?${sbPID}"/> <link rel="stylesheet" type="text/css" href="${srRoot}/css/browser.css?${sbPID}" /> <link rel="stylesheet" type="text/css" href="${srRoot}/css/lib/jquery-ui-1.10.4.custom.min.css?${sbPID}" /> <link rel="stylesheet" type="text/css" href="${srRoot}/css/lib/jquery.qtip-2.2.1.min.css?${sbPID}"/> diff --git a/gui/slick/views/trendingShows.mako b/gui/slick/views/trendingShows.mako index 8dadb403549420755e3232896c02c12cae399377..d0c3dd29a0961db414c059ce7d14d6f683d00a1d 100644 --- a/gui/slick/views/trendingShows.mako +++ b/gui/slick/views/trendingShows.mako @@ -39,7 +39,7 @@ <p>${int(cur_show['show']['rating']*10)}% <img src="${srRoot}/images/heart.png"></p> <i>${cur_show['show']['votes']} votes</i> <div class="traktShowTitleIcons"> - <a href="${srRoot}/addShows/addShowByID?indexer_id=${cur_show['show']['ids']['tvdb']}&show_name=${cur_show['show']['title']}" class="btn btn-xs" data-no-redirect>Add Show</a> + <a href="${srRoot}/addShows/addShowByID?indexer_id=${cur_show['show']['ids']['tvdb']}&show_name=${cur_show['show']['title'] | u}" class="btn btn-xs" data-no-redirect>Add Show</a> % if blacklist: <a href="${srRoot}/addShows/addShowToBlacklist?indexer_id=${cur_show['show']['ids']['tvdb'] or cur_show['show']['ids']['tvrage']}" class="btn btn-xs">Remove Show</a> % endif diff --git a/gui/slick/views/viewlogs.mako b/gui/slick/views/viewlogs.mako index 31e2e53815067fb5b72cb965d23e7966ef3b21eb..e22e8c2472a66ba9deba66075b595acd8f599da9 100644 --- a/gui/slick/views/viewlogs.mako +++ b/gui/slick/views/viewlogs.mako @@ -2,7 +2,7 @@ <%! import sickbeard from sickbeard import classes - from sickbeard.logger import reverseNames + from sickbeard.logger import LOGGING_LEVELS %> <%block name="css"> <style> @@ -22,15 +22,15 @@ pre { <div class="h2footer pull-right">Minimum logging level to display: <select name="minLevel" id="minLevel" class="form-control form-control-inline input-sm"> <% - levels = reverseNames.keys() - levels.sort(lambda x, y: cmp(reverseNames[x], reverseNames[y])) + levels = LOGGING_LEVELS.keys() + levels.sort(lambda x, y: cmp(LOGGING_LEVELS[x], LOGGING_LEVELS[y])) if not sickbeard.DEBUG: levels.remove('DEBUG') if not sickbeard.DBDEBUG: levels.remove('DB') %> % for level in levels: - <option value="${reverseNames[level]}" ${('', 'selected="selected"')[minLevel == reverseNames[level]]}>${level.title()}</option> + <option value="${LOGGING_LEVELS[level]}" ${('', 'selected="selected"')[minLevel == LOGGING_LEVELS[level]]}>${level.title()}</option> % endfor </select> diff --git a/lib/stevedore/dispatch.py b/lib/stevedore/dispatch.py index 226d3ae251925a91b799e47e8aa96a63f2f767f7..424d39b69ed2d6109733182eb0ada736e295b97e 100644 --- a/lib/stevedore/dispatch.py +++ b/lib/stevedore/dispatch.py @@ -1,6 +1,7 @@ import logging from .enabled import EnabledExtensionManager +from .exception import NoMatches LOG = logging.getLogger(__name__) @@ -66,7 +67,7 @@ class DispatchExtensionManager(EnabledExtensionManager): """ if not self.extensions: # FIXME: Use a more specific exception class here. - raise RuntimeError('No %s extensions found' % self.namespace) + raise NoMatches('No %s extensions found' % self.namespace) response = [] for e in self.extensions: if filter_func(e, *args, **kwds): diff --git a/lib/stevedore/driver.py b/lib/stevedore/driver.py index fedc359bd5dbb7fc84b8e5cd43ad190c13ea4038..a2825aaaf14607aafa6ee78f08e01bf0406f3cbb 100644 --- a/lib/stevedore/driver.py +++ b/lib/stevedore/driver.py @@ -1,3 +1,4 @@ +from .exception import NoMatches, MultipleMatches from .named import NamedExtensionManager @@ -93,14 +94,14 @@ class DriverManager(NamedExtensionManager): if not self.extensions: name = self._names[0] - raise RuntimeError('No %r driver found, looking for %r' % - (self.namespace, name)) + raise NoMatches('No %r driver found, looking for %r' % + (self.namespace, name)) if len(self.extensions) > 1: discovered_drivers = ','.join(e.entry_point_target for e in self.extensions) - raise RuntimeError('Multiple %r drivers found: %s' % - (self.namespace, discovered_drivers)) + raise MultipleMatches('Multiple %r drivers found: %s' % + (self.namespace, discovered_drivers)) def __call__(self, func, *args, **kwds): """Invokes func() for the single loaded extension. diff --git a/lib/stevedore/example/base.py b/lib/stevedore/example/base.py index 1c8ca4ca361a7bb19504feaf68089ff7228029fc..ec95424ee177830f025bea92d3410bae651ea6eb 100644 --- a/lib/stevedore/example/base.py +++ b/lib/stevedore/example/base.py @@ -5,7 +5,7 @@ import six @six.add_metaclass(abc.ABCMeta) class FormatterBase(object): - """Base class for example plugin used in the tutoral. + """Base class for example plugin used in the tutorial. """ def __init__(self, max_width=60): diff --git a/lib/stevedore/example/setup.py b/lib/stevedore/example/setup.py index afc7789c6e4005a83ad8640a1fc5d4feff85eca4..4289971f9a416ebf0e1a0b941f3e6fde885bc3e5 100644 --- a/lib/stevedore/example/setup.py +++ b/lib/stevedore/example/setup.py @@ -7,16 +7,14 @@ setup( description='Demonstration package for stevedore', author='Doug Hellmann', - author_email='doug.hellmann@dreamhost.com', + author_email='doug@doughellmann.com', - url='https://github.com/dreamhost/stevedore', - download_url='https://github.com/dreamhost/stevedore/tarball/master', + url='http://git.openstack.org/cgit/openstack/stevedore', classifiers=['Development Status :: 3 - Alpha', 'License :: OSI Approved :: Apache Software License', 'Programming Language :: Python', 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', @@ -37,7 +35,6 @@ setup( entry_points={ 'stevedore.example.formatter': [ 'simple = stevedore.example.simple:Simple', - 'field = stevedore.example.fields:FieldList', 'plain = stevedore.example.simple:Simple', ], }, diff --git a/lib/stevedore/example2/__init__.py b/lib/stevedore/example2/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/lib/stevedore/example/fields.py b/lib/stevedore/example2/fields.py similarity index 100% rename from lib/stevedore/example/fields.py rename to lib/stevedore/example2/fields.py diff --git a/lib/stevedore/example2/setup.py b/lib/stevedore/example2/setup.py new file mode 100644 index 0000000000000000000000000000000000000000..17fe1300404e4c6ba16809a3deaecc8bb36e49d8 --- /dev/null +++ b/lib/stevedore/example2/setup.py @@ -0,0 +1,42 @@ +from setuptools import setup, find_packages + +setup( + name='stevedore-examples2', + version='1.0', + + description='Demonstration package for stevedore', + + author='Doug Hellmann', + author_email='doug@doughellmann.com', + + url='http://git.openstack.org/cgit/openstack/stevedore', + + classifiers=['Development Status :: 3 - Alpha', + 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + 'Intended Audience :: Developers', + 'Environment :: Console', + ], + + platforms=['Any'], + + scripts=[], + + provides=['stevedore.examples2', + ], + + packages=find_packages(), + include_package_data=True, + + entry_points={ + 'stevedore.example.formatter': [ + 'field = stevedore.example2.fields:FieldList', + ], + }, + + zip_safe=False, +) diff --git a/lib/stevedore/exception.py b/lib/stevedore/exception.py new file mode 100644 index 0000000000000000000000000000000000000000..10c5cc26bfe864e9e11136af851e38a7cc20cbd1 --- /dev/null +++ b/lib/stevedore/exception.py @@ -0,0 +1,10 @@ +class NoUniqueMatch(RuntimeError): + """There was more that one on no extensions matching the query.""" + + +class NoMatches(NoUniqueMatch): + """There were no extensions with the diver name found.""" + + +class MultipleMatches(NoUniqueMatch): + """There were multiple matches for the given name.""" diff --git a/lib/stevedore/extension.py b/lib/stevedore/extension.py index 5da97c5d93efe7522fbaa5a28cbfbe9896bf3cfc..d2409885c633ab701c033172ac73160937d23c04 100644 --- a/lib/stevedore/extension.py +++ b/lib/stevedore/extension.py @@ -5,6 +5,7 @@ import pkg_resources import logging +from .exception import NoMatches LOG = logging.getLogger(__name__) @@ -218,7 +219,7 @@ class ExtensionManager(object): """ if not self.extensions: # FIXME: Use a more specific exception class here. - raise RuntimeError('No %s extensions found' % self.namespace) + raise NoMatches('No %s extensions found' % self.namespace) response = [] for e in self.extensions: self._invoke_one_plugin(response.append, func, e, args, kwds) diff --git a/lib/stevedore/sphinxext.py b/lib/stevedore/sphinxext.py index 524f9c93c8d17cec963e829f8f92c7c302e0bf7d..3c9b6ce7f9fcd46004473370ec0f12d503cbdb7f 100644 --- a/lib/stevedore/sphinxext.py +++ b/lib/stevedore/sphinxext.py @@ -36,12 +36,15 @@ def _simple_list(mgr): ext.entry_point.module_name) -def _detailed_list(mgr, over='', under='-'): +def _detailed_list(mgr, over='', under='-', titlecase=False): for name in sorted(mgr.names()): ext = mgr[name] if over: yield (over * len(ext.name), ext.entry_point.module_name) - yield (ext.name, ext.entry_point.module_name) + if titlecase: + yield (ext.name.title(), ext.entry_point.module_name) + else: + yield (ext.name, ext.entry_point.module_name) if under: yield (under * len(ext.name), ext.entry_point.module_name) yield ('\n', ext.entry_point.module_name) @@ -61,6 +64,7 @@ class ListPluginsDirective(rst.Directive): option_spec = { 'class': directives.class_option, 'detailed': directives.flag, + 'titlecase': directives.flag, 'overline-style': directives.single_char_or_unicode, 'underline-style': directives.single_char_or_unicode, } @@ -86,9 +90,12 @@ class ListPluginsDirective(rst.Directive): result = ViewList() + titlecase = 'titlecase' in self.options + if 'detailed' in self.options: data = _detailed_list( - mgr, over=overline_style, under=underline_style) + mgr, over=overline_style, under=underline_style, + titlecase=titlecase) else: data = _simple_list(mgr) for text, source in data: diff --git a/lib/stevedore/tests/test_driver.py b/lib/stevedore/tests/test_driver.py index ff9c3eac05744ca7acd72e72c3e4b055cdf30190..0a919cf76b1980263d067560eae9f54f6b7f2263 100644 --- a/lib/stevedore/tests/test_driver.py +++ b/lib/stevedore/tests/test_driver.py @@ -4,6 +4,7 @@ import pkg_resources from stevedore import driver +from stevedore import exception from stevedore import extension from stevedore.tests import test_extension from stevedore.tests import utils @@ -37,7 +38,7 @@ class TestCallback(utils.TestCase): def test_no_drivers(self): try: driver.DriverManager('stevedore.test.extension.none', 't1') - except RuntimeError as err: + except exception.NoMatches as err: self.assertIn("No 'stevedore.test.extension.none' driver found", str(err)) @@ -70,7 +71,7 @@ class TestCallback(utils.TestCase): dm = driver.DriverManager.make_test_instance(extensions[0]) # Call the initialization code that verifies the extension dm._init_plugins(extensions) - except RuntimeError as err: + except exception.MultipleMatches as err: self.assertIn("Multiple", str(err)) else: self.fail('Should have had an error') diff --git a/lib/stevedore/tests/test_example_fields.py b/lib/stevedore/tests/test_example_fields.py index a3eb39775ea02c0ffb273ecd6cfa6ebfee08593a..86aebf912f0f469d657531b40be4fc71c63cf626 100644 --- a/lib/stevedore/tests/test_example_fields.py +++ b/lib/stevedore/tests/test_example_fields.py @@ -1,7 +1,7 @@ -"""Tests for stevedore.exmaple.fields +"""Tests for stevedore.example2.fields """ -from stevedore.example import fields +from stevedore.example2 import fields from stevedore.tests import utils diff --git a/lib/stevedore/tests/test_example_simple.py b/lib/stevedore/tests/test_example_simple.py index b8ef43119f539f6a9a023c16b378d4a060016c56..2558fb7badb3d75dc1439d0536593dd61683cadf 100644 --- a/lib/stevedore/tests/test_example_simple.py +++ b/lib/stevedore/tests/test_example_simple.py @@ -1,4 +1,4 @@ -"""Tests for stevedore.exmaple.simple +"""Tests for stevedore.example.simple """ from stevedore.example import simple diff --git a/lib/stevedore/tests/test_extension.py b/lib/stevedore/tests/test_extension.py index b05b377682756efbd8115349d35e6d52a7ad952a..1fe02422e1e70f5b007ab09803767ff748d2e58c 100644 --- a/lib/stevedore/tests/test_extension.py +++ b/lib/stevedore/tests/test_extension.py @@ -3,6 +3,7 @@ import mock +from stevedore import exception from stevedore import extension from stevedore.tests import utils @@ -159,7 +160,7 @@ class TestCallback(utils.TestCase): ) try: em.map(mapped, 1, 2, a='A', b='B') - except RuntimeError as err: + except exception.NoMatches as err: self.assertEqual(expected_str, str(err)) def test_map_method(self): diff --git a/lib/subliminal/__init__.py b/lib/subliminal/__init__.py index 4c0631efd474acb08cb4e46c56fc6a4968914597..25355a08ac99bae06b90931da603cea302c036ea 100644 --- a/lib/subliminal/__init__.py +++ b/lib/subliminal/__init__.py @@ -7,8 +7,8 @@ __copyright__ = 'Copyright 2015, Antoine Bertin' import logging -from .api import (ProviderPool, check_video, provider_manager, download_best_subtitles, download_subtitles, - list_subtitles, save_subtitles) +from .api import (ProviderManager, ProviderPool, check_video, provider_manager, download_best_subtitles, + download_subtitles, list_subtitles, save_subtitles) from .cache import region from .exceptions import Error, ProviderError from .providers import Provider diff --git a/lib/subliminal/api.py b/lib/subliminal/api.py index 3af8809a7d89644a099971b1839dc4bc131f94dd..81cd1e0e5a85b75746269588a25a1d12926e2c43 100644 --- a/lib/subliminal/api.py +++ b/lib/subliminal/api.py @@ -4,43 +4,102 @@ import io import logging import operator import os.path +from pkg_resources import EntryPoint import socket from babelfish import Language -from pkg_resources import EntryPoint import requests -from stevedore import EnabledExtensionManager, ExtensionManager +from stevedore import ExtensionManager from .subtitle import compute_score, get_subtitle_path logger = logging.getLogger(__name__) -class InternalExtensionManager(ExtensionManager): - """Add support for internal entry points to the :class:`~stevedore.extension.Extensionmanager` - Internal entry points are useful for libraries that ship their own plugins but still keep the entry point open. - All other parameters are passed onwards to the :class:`~stevedore.extension.Extensionmanager` constructor. - :param internal_entry_points: the internal providers - :type internal_entry_points: list of :class:`~pkg_resources.EntryPoint` +class ProviderManager(ExtensionManager): + """Manager for providers based on :class:`~stevedore.extension.ExtensionManager`. + + It allows loading of internal providers without setup and registering/unregistering additional providers. + + Loading is done in this order: + + * Entry point providers + * Internal providers + * Registered providers + """ - def __init__(self, namespace, internal_entry_points, **kwargs): - self.internal_entry_points = list(internal_entry_points) - super(InternalExtensionManager, self).__init__(namespace, **kwargs) + internal_providers = [ + 'addic7ed = subliminal.providers.addic7ed:Addic7edProvider', + 'opensubtitles = subliminal.providers.opensubtitles:OpenSubtitlesProvider', + 'podnapisi = subliminal.providers.podnapisi:PodnapisiProvider', + 'subscenter = subliminal.providers.subscenter:SubsCenterProvider', + 'thesubdb = subliminal.providers.thesubdb:TheSubDBProvider', + 'tvsubtitles = subliminal.providers.tvsubtitles:TVsubtitlesProvider' + ] + + def __init__(self): + #: Registered providers with entry point syntax + self.registered_providers = [] + + super(ProviderManager, self).__init__('subliminal.providers') def _find_entry_points(self, namespace): - return self.internal_entry_points + super(InternalExtensionManager, self)._find_entry_points(namespace) + # default entry points + eps = super(ProviderManager, self)._find_entry_points(namespace) + + # internal entry points + for iep in self.internal_providers: + ep = EntryPoint.parse(iep) + if ep.name not in [e.name for e in eps]: + eps.append(ep) + + # registered entry points + for rep in self.registered_providers: + ep = EntryPoint.parse(rep) + if ep.name not in [e.name for e in eps]: + eps.append(ep) + + return eps + def register(self, entry_point): + """Register a provider -provider_manager = InternalExtensionManager('subliminal.providers', [EntryPoint.parse(ep) for ep in ( - 'addic7ed = subliminal.providers.addic7ed:Addic7edProvider', - 'legendastv = subliminal.providers.legendastv:LegendasTvProvider', - 'napiprojekt = subliminal.providers.napiprojekt:NapiProjektProvider', - 'opensubtitles = subliminal.providers.opensubtitles:OpenSubtitlesProvider', - 'podnapisi = subliminal.providers.podnapisi:PodnapisiProvider', - 'subscenter = subliminal.providers.subscenter:SubsCenterProvider', - 'thesubdb = subliminal.providers.thesubdb:TheSubDBProvider', - 'tvsubtitles = subliminal.providers.tvsubtitles:TVsubtitlesProvider' -)]) + :param str entry_point: provider to register (entry point syntax) + :raise: ValueError if already registered + + """ + if entry_point in self.registered_providers: + raise ValueError('Entry point already registered') + + ep = EntryPoint.parse(entry_point) + if ep.name in self.names(): + raise ValueError('A provider with the same name already exist') + + ext = self._load_one_plugin(ep, False, (), {}, False) + self.extensions.append(ext) + if self._extensions_by_name is not None: + self._extensions_by_name[ext.name] = ext + self.registered_providers.insert(0, entry_point) + + def unregister(self, entry_point): + """Unregister a provider + + :param str entry_point: provider to unregister (entry point syntax) + + """ + if entry_point not in self.registered_providers: + raise ValueError('Entry point not registered') + + ep = EntryPoint.parse(entry_point) + self.registered_providers.remove(entry_point) + if self._extensions_by_name is not None: + del self._extensions_by_name[ep.name] + for i, ext in enumerate(self.extensions): + if ext.name == ep.name: + del self.extensions[i] + break + +provider_manager = ProviderManager() class ProviderPool(object): @@ -52,8 +111,7 @@ class ProviderPool(object): the providers on exit. * Automatically discard providers on failure. - :param providers: name of providers to use, if not all. - :type providers: list + :param list providers: name of providers to use, if not all. :param dict provider_configs: provider configuration as keyword arguments per provider name to pass when instanciating the :class:`~subliminal.providers.Provider`. @@ -71,9 +129,6 @@ class ProviderPool(object): #: Discarded providers self.discarded_providers = set() - #: Dedicated :data:`provider_manager` as :class:`~stevedore.enabled.EnabledExtensionManager` - self.manager = EnabledExtensionManager(provider_manager.namespace, lambda e: e.name in self.providers) - def __enter__(self): return self @@ -81,9 +136,11 @@ class ProviderPool(object): self.terminate() def __getitem__(self, name): + if name not in self.providers: + raise KeyError if name not in self.initialized_providers: logger.info('Initializing provider %s', name) - provider = self.manager[name].plugin(**self.provider_configs.get(name, {})) + provider = provider_manager[name].plugin(**self.provider_configs.get(name, {})) provider.initialize() self.initialized_providers[name] = provider @@ -126,12 +183,12 @@ class ProviderPool(object): continue # check video validity - if not self.manager[name].plugin.check(video): + if not provider_manager[name].plugin.check(video): logger.info('Skipping provider %r: not a valid video', name) continue # check supported languages - provider_languages = self.manager[name].plugin.languages & languages + provider_languages = provider_manager[name].plugin.languages & languages if not provider_languages: logger.info('Skipping provider %r: no language to search for', name) continue diff --git a/lib/subliminal/providers/addic7ed.py b/lib/subliminal/providers/addic7ed.py index 6061319cb0f55c7c8286ceca7bdc173841bb5177..f00273f9579c0a2cc2aa1239fbcef5f8ad5590de 100644 --- a/lib/subliminal/providers/addic7ed.py +++ b/lib/subliminal/providers/addic7ed.py @@ -14,6 +14,7 @@ from ..subtitle import (Subtitle, fix_line_ending, guess_matches, guess_properti from ..video import Episode logger = logging.getLogger(__name__) + language_converters.register('addic7ed = subliminal.converters.addic7ed:Addic7edConverter') series_year_re = re.compile('^(?P<series>[ \w\'.:]+)(?: \((?P<year>\d{4})\))?$') diff --git a/lib/subliminal/providers/legendastv.py b/lib/subliminal/providers/legendastv.py index 888f5cad2c4fbbda4b7acd1e0bc9a69dffd62feb..258e73647f8fb42a888512d51a5649c211dc762b 100644 --- a/lib/subliminal/providers/legendastv.py +++ b/lib/subliminal/providers/legendastv.py @@ -20,6 +20,7 @@ from ..video import Episode, Movie, SUBTITLE_EXTENSIONS TIMEOUT = 10 logger = logging.getLogger(__name__) + language_converters.register('legendastv = subliminal.converters.legendastv:LegendasTvConverter') diff --git a/lib/subliminal/providers/opensubtitles.py b/lib/subliminal/providers/opensubtitles.py index 5a06a23b84cdc0f59a175f9e2789b1a1ee06aa28..3bd49e6cc0f1f1038eb7f45dd0aa76fd5132305d 100644 --- a/lib/subliminal/providers/opensubtitles.py +++ b/lib/subliminal/providers/opensubtitles.py @@ -121,13 +121,15 @@ class OpenSubtitlesProvider(Provider): logger.debug('No operation') checked(self.server.NoOperation(self.token)) - def query(self, languages, hash=None, size=None, imdb_id=None, query=None, season=None, episode=None): + def query(self, languages, hash=None, size=None, imdb_id=None, query=None, season=None, episode=None, tag=None): # fill the search criteria criteria = [] if hash and size: criteria.append({'moviehash': hash, 'moviebytesize': str(size)}) if imdb_id: criteria.append({'imdbid': imdb_id}) + if tag: + criteria.append({'tag': tag}) if query and season and episode: criteria.append({'query': query.replace('\'', ''), 'season': season, 'episode': episode}) elif query: @@ -176,16 +178,16 @@ class OpenSubtitlesProvider(Provider): return subtitles def list_subtitles(self, video, languages): - query = season = episode = None + season = episode = None if isinstance(video, Episode): query = video.series season = video.season episode = video.episode - elif ('opensubtitles' not in video.hashes or not video.size) and not video.imdb_id: - query = video.name.split(os.sep)[-1] + else: + query = video.title return self.query(languages, hash=video.hashes.get('opensubtitles'), size=video.size, imdb_id=video.imdb_id, - query=query, season=season, episode=episode) + query=query, season=season, episode=episode, tag=os.path.basename(video.name)) def download_subtitle(self, subtitle): logger.info('Downloading subtitle %r', subtitle) diff --git a/lib/subliminal/providers/thesubdb.py b/lib/subliminal/providers/thesubdb.py index f7f1f2451bc5400e4b4b0335c8584ddee1eee4ac..da325aaf9f8ab573597c4836447bac6b54f865c7 100644 --- a/lib/subliminal/providers/thesubdb.py +++ b/lib/subliminal/providers/thesubdb.py @@ -10,8 +10,10 @@ from ..subtitle import Subtitle, fix_line_ending logger = logging.getLogger(__name__) + language_converters.register('thesubdb = subliminal.converters.thesubdb:TheSubDBConverter') + class TheSubDBSubtitle(Subtitle): provider_name = 'thesubdb' diff --git a/lib/subliminal/providers/tvsubtitles.py b/lib/subliminal/providers/tvsubtitles.py index 8c4433fc966bd5b0b54eba9b99dfab2291e6db9b..0b491c39e3f631afd5de98f3094bbb45acd8c5f4 100644 --- a/lib/subliminal/providers/tvsubtitles.py +++ b/lib/subliminal/providers/tvsubtitles.py @@ -15,6 +15,7 @@ from ..subtitle import Subtitle, fix_line_ending, guess_matches, guess_propertie from ..video import Episode logger = logging.getLogger(__name__) + language_converters.register('tvsubtitles = subliminal.converters.tvsubtitles:TVsubtitlesConverter') link_re = re.compile('^(?P<series>.+?)(?: \(?\d{4}\)?| \((?:US|UK)\))? \((?P<first_year>\d{4})-\d{4}\)$') diff --git a/lib/subliminal/video.py b/lib/subliminal/video.py index b04d535cb60f25ddfb4023837cb9d99ff278a9f1..3384e7b3e3fb8197de318e799078d681e4420ee7 100644 --- a/lib/subliminal/video.py +++ b/lib/subliminal/video.py @@ -7,7 +7,7 @@ import os import struct from babelfish import Error as BabelfishError, Language -from enzyme import Error as EnzymeError, MKV +from enzyme import MKV from guessit import guess_episode_info, guess_file_info, guess_movie_info logger = logging.getLogger(__name__) diff --git a/readme.md b/readme.md index 90d9b117a878cb1cb479daec6927c11ae372f99a..b1816fa1ed8395d4814c54de9574a0bea63a92da 100644 --- a/readme.md +++ b/readme.md @@ -39,7 +39,7 @@ Automatic Video Library Manager for TV Shows. It watches for new episodes of you #### Important Before using this with your existing database (sickbeard.db) please make a backup copy of it and delete any other database files such as cache.db and failed.db if present<br> -We HIGHLY recommend starting out with no database files at all to make this a fresh start but the choice is at your own risk +We HIGHLY recommend starting out with no database files at all to make this a fresh start but the choice is at your own risk. #### Supported providers @@ -48,7 +48,7 @@ A full list can be found here: [Link](https://github.com/SickRage/sickrage-issue #### Special Thanks to: [RARBG](https://rarbg.to) [TorrentProject](https://torrentproject.se/about) -[ThePirateBay](https://thepiratebay.la/) +[ThePirateBay](https://thepiratebay.se/) [KickAssTorrents](https://kat.cr) [NZB.cat](https://nzb.cat/) [NZBGeek](https://nzbgeek.info) diff --git a/sickbeard/__init__.py b/sickbeard/__init__.py index 904c818b9adec751b983044e6a5504824be579c7..f459eaa345995518b4b77f1239f8aba417582688 100644 --- a/sickbeard/__init__.py +++ b/sickbeard/__init__.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import webbrowser import datetime @@ -112,8 +112,7 @@ autoPostProcesserScheduler = None subtitlesFinderScheduler = None traktCheckerScheduler = None -showList = None -loadingShowList = None +showList = [] providerList = [] newznabProviderList = [] @@ -375,6 +374,13 @@ FREEMOBILE_NOTIFY_ONSUBTITLEDOWNLOAD = False FREEMOBILE_ID = '' FREEMOBILE_APIKEY = '' +USE_TELEGRAM = False +TELEGRAM_NOTIFY_ONSNATCH = False +TELEGRAM_NOTIFY_ONDOWNLOAD = False +TELEGRAM_NOTIFY_ONSUBTITLEDOWNLOAD = False +TELEGRAM_ID = '' +TELEGRAM_APIKEY = '' + USE_PROWL = False PROWL_NOTIFY_ONSNATCH = False PROWL_NOTIFY_ONDOWNLOAD = False @@ -590,10 +596,11 @@ def initialize(consoleLogging=True): USE_PLEX_SERVER, PLEX_NOTIFY_ONSNATCH, PLEX_NOTIFY_ONDOWNLOAD, PLEX_NOTIFY_ONSUBTITLEDOWNLOAD, PLEX_UPDATE_LIBRARY, USE_PLEX_CLIENT, PLEX_CLIENT_USERNAME, PLEX_CLIENT_PASSWORD, \ PLEX_SERVER_HOST, PLEX_SERVER_TOKEN, PLEX_CLIENT_HOST, PLEX_SERVER_USERNAME, PLEX_SERVER_PASSWORD, PLEX_SERVER_HTTPS, MIN_BACKLOG_FREQUENCY, SKIP_REMOVED_FILES, ALLOWED_EXTENSIONS, \ USE_EMBY, EMBY_HOST, EMBY_APIKEY, \ - showUpdateScheduler, __INITIALIZED__, INDEXER_DEFAULT_LANGUAGE, EP_DEFAULT_DELETED_STATUS, LAUNCH_BROWSER, TRASH_REMOVE_SHOW, TRASH_ROTATE_LOGS, SORT_ARTICLE, showList, loadingShowList, \ + showUpdateScheduler, __INITIALIZED__, INDEXER_DEFAULT_LANGUAGE, EP_DEFAULT_DELETED_STATUS, LAUNCH_BROWSER, TRASH_REMOVE_SHOW, TRASH_ROTATE_LOGS, SORT_ARTICLE, showList, \ NEWZNAB_DATA, NZBS, NZBS_UID, NZBS_HASH, INDEXER_DEFAULT, INDEXER_TIMEOUT, USENET_RETENTION, TORRENT_DIR, \ QUALITY_DEFAULT, FLATTEN_FOLDERS_DEFAULT, SUBTITLES_DEFAULT, STATUS_DEFAULT, STATUS_DEFAULT_AFTER, \ GROWL_NOTIFY_ONSNATCH, GROWL_NOTIFY_ONDOWNLOAD, GROWL_NOTIFY_ONSUBTITLEDOWNLOAD, TWITTER_NOTIFY_ONSNATCH, TWITTER_NOTIFY_ONDOWNLOAD, TWITTER_NOTIFY_ONSUBTITLEDOWNLOAD, USE_FREEMOBILE, FREEMOBILE_ID, FREEMOBILE_APIKEY, FREEMOBILE_NOTIFY_ONSNATCH, FREEMOBILE_NOTIFY_ONDOWNLOAD, FREEMOBILE_NOTIFY_ONSUBTITLEDOWNLOAD, \ + USE_TELEGRAM, TELEGRAM_ID, TELEGRAM_APIKEY, TELEGRAM_NOTIFY_ONSNATCH, TELEGRAM_NOTIFY_ONDOWNLOAD, TELEGRAM_NOTIFY_ONSUBTITLEDOWNLOAD, \ USE_GROWL, GROWL_HOST, GROWL_PASSWORD, USE_PROWL, PROWL_NOTIFY_ONSNATCH, PROWL_NOTIFY_ONDOWNLOAD, PROWL_NOTIFY_ONSUBTITLEDOWNLOAD, PROWL_API, PROWL_PRIORITY, PROWL_MESSAGE_TITLE, \ USE_PYTIVO, PYTIVO_NOTIFY_ONSNATCH, PYTIVO_NOTIFY_ONDOWNLOAD, PYTIVO_NOTIFY_ONSUBTITLEDOWNLOAD, PYTIVO_UPDATE_LIBRARY, PYTIVO_HOST, PYTIVO_SHARE_NAME, PYTIVO_TIVO_NAME, \ USE_NMA, NMA_NOTIFY_ONSNATCH, NMA_NOTIFY_ONDOWNLOAD, NMA_NOTIFY_ONSUBTITLEDOWNLOAD, NMA_API, NMA_PRIORITY, \ @@ -679,14 +686,14 @@ def initialize(consoleLogging=True): fileLogging = False # init logging - logger.initLogging(consoleLogging=consoleLogging, fileLogging=fileLogging, debugLogging=DEBUG, databaseLogging=DBDEBUG) + logger.init_logging(console_logging=consoleLogging, file_logging=fileLogging, debug_logging=DEBUG, database_logging=DBDEBUG) # github api try: if not (GIT_USERNAME and GIT_PASSWORD): - gh = Github(user_agent="SiCKRAGE").get_organization(GIT_ORG).get_repo(GIT_REPO) + gh = Github(user_agent="SickRage").get_organization(GIT_ORG).get_repo(GIT_REPO) else: - gh = Github(login_or_token=GIT_USERNAME, password=GIT_PASSWORD, user_agent="SiCKRAGE").get_organization(GIT_ORG).get_repo(GIT_REPO) + gh = Github(login_or_token=GIT_USERNAME, password=GIT_PASSWORD, user_agent="SickRage").get_organization(GIT_ORG).get_repo(GIT_REPO) except Exception as e: gh = None logger.log(u'Unable to setup GitHub properly. GitHub will not be available. Error: %s' % str(e), logger.WARNING) @@ -756,11 +763,12 @@ def initialize(consoleLogging=True): except Exception as e: logger.log(u"Restore: Unable to remove the restore directory: {0}".format(ex(e)), logger.ERROR) - for cleanupDir in ['mako', 'sessions', 'indexers']: + for cleanupDir in ['mako', 'sessions', 'indexers', 'rss']: try: shutil.rmtree(ek(os.path.join, CACHE_DIR, cleanupDir)) except Exception as e: - logger.log(u"Restore: Unable to remove the cache/{0} directory: {1}".format(cleanupDir, ex(e)), logger.WARNING) + if cleanupDir not in ['rss', 'sessions', 'indexers']: + logger.log(u"Restore: Unable to remove the cache/{0} directory: {1}".format(cleanupDir, ex(e)), logger.WARNING) GUI_NAME = check_setting_str(CFG, 'GUI', 'gui_name', 'slick') @@ -1021,6 +1029,13 @@ def initialize(consoleLogging=True): FREEMOBILE_ID = check_setting_str(CFG, 'FreeMobile', 'freemobile_id', '') FREEMOBILE_APIKEY = check_setting_str(CFG, 'FreeMobile', 'freemobile_apikey', '') + USE_TELEGRAM = bool(check_setting_int(CFG, 'Telegram', 'use_telegram', 0)) + TELEGRAM_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Telegram', 'telegram_notify_onsnatch', 0)) + TELEGRAM_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Telegram', 'telegram_notify_ondownload', 0)) + TELEGRAM_NOTIFY_ONSUBTITLEDOWNLOAD = bool(check_setting_int(CFG, 'Telegram', 'telegram_notify_onsubtitledownload', 0)) + TELEGRAM_ID = check_setting_str(CFG, 'Telegram', 'telegram_id', '') + TELEGRAM_APIKEY = check_setting_str(CFG, 'Telegram', 'telegram_apikey', '') + USE_PROWL = bool(check_setting_int(CFG, 'Prowl', 'use_prowl', 0)) PROWL_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Prowl', 'prowl_notify_onsnatch', 0)) PROWL_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Prowl', 'prowl_notify_ondownload', 0)) @@ -1452,9 +1467,6 @@ def initialize(consoleLogging=True): threadName="FINDSUBTITLES", silent=not USE_SUBTITLES) - showList = [] - loadingShowList = {} - __INITIALIZED__ = True return True @@ -1933,6 +1945,14 @@ def save_config(): new_config['FreeMobile']['freemobile_id'] = FREEMOBILE_ID new_config['FreeMobile']['freemobile_apikey'] = FREEMOBILE_APIKEY + new_config['Telegram'] = {} + new_config['Telegram']['use_telegram'] = int(USE_TELEGRAM) + new_config['Telegram']['telegram_notify_onsnatch'] = int(TELEGRAM_NOTIFY_ONSNATCH) + new_config['Telegram']['telegram_notify_ondownload'] = int(TELEGRAM_NOTIFY_ONDOWNLOAD) + new_config['Telegram']['telegram_notify_onsubtitledownload'] = int(TELEGRAM_NOTIFY_ONSUBTITLEDOWNLOAD) + new_config['Telegram']['telegram_id'] = TELEGRAM_ID + new_config['Telegram']['telegram_apikey'] = TELEGRAM_APIKEY + new_config['Prowl'] = {} new_config['Prowl']['use_prowl'] = int(USE_PROWL) new_config['Prowl']['prowl_notify_onsnatch'] = int(PROWL_NOTIFY_ONSNATCH) diff --git a/sickbeard/auto_postprocessor.py b/sickbeard/auto_postprocessor.py index b9a281fbd784c1f4d036e6f51cc96e1ce655e22f..ee7c06f629d7a05748ea1d5a0da77611d00bffa6 100644 --- a/sickbeard/auto_postprocessor.py +++ b/sickbeard/auto_postprocessor.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import os.path import threading @@ -47,7 +47,7 @@ class PostProcessor(object): return if not (force or ek(os.path.isabs, sickbeard.TV_DOWNLOAD_DIR)): - logger.log(u"Automatic post-processing attempted but directory is relatve " + logger.log(u"Automatic post-processing attempted but directory is relative " u"(and probably not what you really want to process): %s" % sickbeard.TV_DOWNLOAD_DIR, logger.ERROR) self.amActive = False diff --git a/sickbeard/blackandwhitelist.py b/sickbeard/blackandwhitelist.py index 5d8330fab6babf06944337c5d2057bdaad370580..cef6466b73fefef1389faf6f6ee92e8e9a9a45b5 100644 --- a/sickbeard/blackandwhitelist.py +++ b/sickbeard/blackandwhitelist.py @@ -12,11 +12,11 @@ # # Sick Beard is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import sickbeard from sickbeard import db, logger, helpers diff --git a/sickbeard/browser.py b/sickbeard/browser.py index e4853f460bb968aa57fe4e7fc45d9067b46ab066..36d4d9edbb8324c11ef9eb703b7991c62410b73c 100644 --- a/sickbeard/browser.py +++ b/sickbeard/browser.py @@ -12,11 +12,13 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. + +from __future__ import unicode_literals import os import string @@ -43,31 +45,31 @@ def getWinDrives(): def getFileList(path, includeFiles): # prune out directories to protect the user from doing stupid things (already lower case the dir to reduce calls) - hideList = ["boot", "bootmgr", "cache", "config.msi", "msocache", "recovery", "$recycle.bin", - "recycler", "system volume information", "temporary internet files"] # windows specific - hideList += [".fseventd", ".spotlight", ".trashes", ".vol", "cachedmessages", "caches", "trash"] # osx specific - hideList += [".git"] + hide_list = ['boot', 'bootmgr', 'cache', 'config.msi', 'msocache', 'recovery', '$recycle.bin', + 'recycler', 'system volume information', 'temporary internet files'] # windows specific + hide_list += ['.fseventd', '.spotlight', '.trashes', '.vol', 'cachedmessages', 'caches', 'trash'] # osx specific + hide_list += ['.git'] - fileList = [] + file_list = [] for filename in ek(os.listdir, path): - if filename.lower() in hideList: + if filename.lower() in hide_list: continue - fullFilename = ek(os.path.join, path, filename) - isDir = ek(os.path.isdir, fullFilename) + full_filename = ek(os.path.join, path, filename) + is_dir = ek(os.path.isdir, full_filename) - if not includeFiles and not isDir: + if not includeFiles and not is_dir: continue entry = { 'name': filename, - 'path': fullFilename + 'path': full_filename } - if not isDir: + if not is_dir: entry['isFile'] = True - fileList.append(entry) + file_list.append(entry) - return fileList + return file_list def foldersAtPath(path, includeParent=False, includeFiles=False): @@ -88,36 +90,36 @@ def foldersAtPath(path, includeParent=False, includeFiles=False): else: path = ek(os.path.dirname, path) - if path == "": + if path == '': if os.name == 'nt': entries = [{'currentPath': 'Root'}] for letter in getWinDrives(): - letterPath = letter + ':\\' - entries.append({'name': letterPath, 'path': letterPath}) + letter_path = letter + ':\\' + entries.append({'name': letter_path, 'path': letter_path}) return entries else: path = '/' # fix up the path and find the parent path = ek(os.path.abspath, ek(os.path.normpath, path)) - parentPath = ek(os.path.dirname, path) + parent_path = ek(os.path.dirname, path) # if we're at the root then the next step is the meta-node showing our drive letters - if path == parentPath and os.name == 'nt': - parentPath = "" + if path == parent_path and os.name == 'nt': + parent_path = '' try: - fileList = getFileList(path, includeFiles) + file_list = getFileList(path, includeFiles) except OSError as e: - logger.log(u"Unable to open " + path + ": " + repr(e) + " / " + str(e), logger.WARNING) - fileList = getFileList(parentPath, includeFiles) + logger.log('Unable to open %s: %s / %s' % (path, repr(e), str(e)), logger.WARNING) + file_list = getFileList(parent_path, includeFiles) - fileList = sorted(fileList, - lambda x, y: cmp(ek(os.path.basename, x['name']).lower(), ek(os.path.basename, y['path']).lower())) + file_list = sorted(file_list, + lambda x, y: cmp(ek(os.path.basename, x['name']).lower(), ek(os.path.basename, y['path']).lower())) entries = [{'currentPath': path}] - if includeParent and parentPath != path: - entries.append({'name': "..", 'path': parentPath}) - entries.extend(fileList) + if includeParent and parent_path != path: + entries.append({'name': '..', 'path': parent_path}) + entries.extend(file_list) return entries diff --git a/sickbeard/bs4_parser.py b/sickbeard/bs4_parser.py index 43afa8bd65f8d99475bbb6f26b6cf6e7c77b6459..5fb58c20285e69e6f64ce532dff5c05e284253b5 100644 --- a/sickbeard/bs4_parser.py +++ b/sickbeard/bs4_parser.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. from bs4 import BeautifulSoup diff --git a/sickbeard/classes.py b/sickbeard/classes.py index f59265c139258e431b46319427c4357f81c19380..6e87566fed218866337ab08cec7b9150ed77cf4b 100644 --- a/sickbeard/classes.py +++ b/sickbeard/classes.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import re import sys diff --git a/sickbeard/clients/__init__.py b/sickbeard/clients/__init__.py index f1e67523c4484d193c3389757158f6dd91d8fd3f..8be031eeb336d355d96e334a288672e52c594c88 100644 --- a/sickbeard/clients/__init__.py +++ b/sickbeard/clients/__init__.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. __all__ = [ 'utorrent', diff --git a/sickbeard/clients/deluge_client.py b/sickbeard/clients/deluge_client.py index 6e6a92a141c3990fa9b5ea42f32d4d246db10434..9ac79d8317883c4097ca39ac0b8d8e3d7a0f240e 100644 --- a/sickbeard/clients/deluge_client.py +++ b/sickbeard/clients/deluge_client.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import json from base64 import b64encode diff --git a/sickbeard/clients/mlnet_client.py b/sickbeard/clients/mlnet_client.py index abd4634a0bd71d1b51269c37cb5182c7665b51a2..c5fe1926d69f67406a83f6f969177bca186ed16e 100644 --- a/sickbeard/clients/mlnet_client.py +++ b/sickbeard/clients/mlnet_client.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. from sickbeard.clients.generic import GenericClient diff --git a/sickbeard/clients/qbittorrent_client.py b/sickbeard/clients/qbittorrent_client.py index d68e12f75e94843318f73c11b07e6cb282199ea6..dcd008525e3cba26ed2218a6df2556ab33463e64 100644 --- a/sickbeard/clients/qbittorrent_client.py +++ b/sickbeard/clients/qbittorrent_client.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import sickbeard from sickbeard import logger @@ -82,9 +82,9 @@ class qbittorrentAPI(GenericClient): if result.show.is_anime: label = sickbeard.TORRENT_LABEL_ANIME - if self.api > 6: + if self.api > 6 and label: self.url = self.host + 'command/setLabel' - data = {'hashes': result.hash.lower(), 'label': label} + data = {'hashes': result.hash.lower(), 'label': label.replace(' ','_')} return self._request(method='post', data=data, cookies=self.session.cookies) return None diff --git a/sickbeard/clients/rtorrent_client.py b/sickbeard/clients/rtorrent_client.py index a7bf52aaae5a53eaa6d8da590e39553a19d503a2..5fe7e4b2f4c888e223196c9d89120267c91c21fe 100644 --- a/sickbeard/clients/rtorrent_client.py +++ b/sickbeard/clients/rtorrent_client.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import traceback diff --git a/sickbeard/clients/transmission_client.py b/sickbeard/clients/transmission_client.py index c26a8391271e5bb51138e10eed47932fff194648..6a1d42c57e6449284a61ecf2b6e001c7f0c1d230 100644 --- a/sickbeard/clients/transmission_client.py +++ b/sickbeard/clients/transmission_client.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import re import json diff --git a/sickbeard/clients/utorrent_client.py b/sickbeard/clients/utorrent_client.py index 68a8a7ec63263f3ddf97c685128249481dee4298..05b26d094dca2d91e13399e4ec22fb7fce8179ff 100644 --- a/sickbeard/clients/utorrent_client.py +++ b/sickbeard/clients/utorrent_client.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import re diff --git a/sickbeard/common.py b/sickbeard/common.py index d28c3654e9a657d6726504772f674b066552462c..9f68d6fb7595dab3207c6e2a353b16978fcc4000 100644 --- a/sickbeard/common.py +++ b/sickbeard/common.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. """ Common interface for Quality and Status diff --git a/sickbeard/config.py b/sickbeard/config.py index 5b6ce6acf6eabcd7e8d0cf44aa2d1f81917f33df..caaa003fcb2887e8c28d0fa548d690feba736d7a 100644 --- a/sickbeard/config.py +++ b/sickbeard/config.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import os.path import datetime @@ -117,7 +117,7 @@ def change_LOG_DIR(log_dir, web_log): sickbeard.ACTUAL_LOG_DIR = ek(os.path.normpath, log_dir) sickbeard.LOG_DIR = abs_log_dir - logger.initLogging() + logger.init_logging() logger.log(u"Initialized new log file in " + sickbeard.LOG_DIR) log_dir_changed = True @@ -592,9 +592,9 @@ def check_setting_str(config, cfg_name, item_name, def_val, silent=True, censor_ config[cfg_name] = {} config[cfg_name][item_name] = helpers.encrypt(my_val, encryption_version) - if censor_log or (cfg_name, item_name) in logger.censoredItems.iteritems(): + if censor_log or (cfg_name, item_name) in logger.censored_items.iteritems(): if not item_name.endswith('custom_url'): - logger.censoredItems[cfg_name, item_name] = my_val + logger.censored_items[cfg_name, item_name] = my_val if not silent: logger.log(item_name + " -> " + my_val, logger.DEBUG) diff --git a/sickbeard/dailysearcher.py b/sickbeard/dailysearcher.py index 0d47acd9622e033e196c161bef54b24cf4e4ae90..64152a28deb9b8f4bbf26af48d91ceec49c22991 100644 --- a/sickbeard/dailysearcher.py +++ b/sickbeard/dailysearcher.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import datetime import threading diff --git a/sickbeard/databases/__init__.py b/sickbeard/databases/__init__.py index 2e67a9dd6a0c8ced186cafd0005d0066ebe250e3..0d2c8b8080695aac5bacd00ba41287ddb1039f64 100644 --- a/sickbeard/databases/__init__.py +++ b/sickbeard/databases/__init__.py @@ -12,10 +12,10 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. __all__ = ["mainDB", "cache", "failed"] diff --git a/sickbeard/databases/cache_db.py b/sickbeard/databases/cache_db.py index 5b4c2d1a04627f484f4ee1af4db837fa36fd5898..ea2ca13bc262d8c512c40ea2dde86d9aa7157639 100644 --- a/sickbeard/databases/cache_db.py +++ b/sickbeard/databases/cache_db.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. from sickbeard import db diff --git a/sickbeard/databases/failed_db.py b/sickbeard/databases/failed_db.py index 6a91a8af1c042300c5c5517b25742b1571f8745b..05c21c685cdab0e43f90f7d24993758c7b747db7 100644 --- a/sickbeard/databases/failed_db.py +++ b/sickbeard/databases/failed_db.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. from sickbeard import db from sickbeard.common import Quality diff --git a/sickbeard/databases/mainDB.py b/sickbeard/databases/mainDB.py index 62af35149a123ed32d3920b20ae270cb3562c0e7..59f56709d73ed4d2fcdfca7520aac824fb4314fd 100644 --- a/sickbeard/databases/mainDB.py +++ b/sickbeard/databases/mainDB.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import datetime diff --git a/sickbeard/db.py b/sickbeard/db.py index 584efe471e34b1dd8a19949d791db12687320818..781e0527b3b19056d094b4711c60e896d3dee1ff 100644 --- a/sickbeard/db.py +++ b/sickbeard/db.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import os.path diff --git a/sickbeard/failedProcessor.py b/sickbeard/failedProcessor.py index 3b794c9ac2b0ee0d4725b690dba2772d0900cb65..2b8675aaa60efa8f5c67847866e302b4f7f39174 100644 --- a/sickbeard/failedProcessor.py +++ b/sickbeard/failedProcessor.py @@ -12,7 +12,7 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License diff --git a/sickbeard/failed_history.py b/sickbeard/failed_history.py index 052e7a3fa50153a098fb16fecc5b403bfe114962..e86576ff7d03d8cda4a9b7893020de0663202bc8 100644 --- a/sickbeard/failed_history.py +++ b/sickbeard/failed_history.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import re import urllib diff --git a/sickbeard/generic_queue.py b/sickbeard/generic_queue.py index e999a57895a8ad4622675a6974426927ef35c41d..bdb917f0d404b83521628b7e69c1ccf0ffd214c2 100644 --- a/sickbeard/generic_queue.py +++ b/sickbeard/generic_queue.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import datetime import threading diff --git a/sickbeard/helpers.py b/sickbeard/helpers.py index 372fd5d50cfc6427e0887c6b81cf9f9135ef76b2..e477f4ce7dd445c21a4b01e63f082b9133653c7d 100644 --- a/sickbeard/helpers.py +++ b/sickbeard/helpers.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import os import io @@ -51,12 +51,11 @@ from socket import timeout as SocketTimeout from sickbeard import logger, classes from sickbeard.common import USER_AGENT from sickbeard import db -from sickbeard.notifiers.synoindex import notifier as synoindex_notifier +from sickbeard.notifiers import synoindex_notifier from sickrage.helper.common import http_code_description, media_extensions, pretty_file_size, subtitle_extensions from sickrage.helper.encoding import ek from sickrage.helper.exceptions import ex from sickrage.show.Show import Show -from cachecontrol import CacheControl, caches from itertools import izip, cycle import shutil @@ -127,6 +126,7 @@ def remove_non_release_groups(name): r'\.GiuseppeTnT$': 'searchre', r'\.Renc$': 'searchre', r'\.gz$': 'searchre', + r'(?<![57])\.1$': 'searchre', r'-NZBGEEK$': 'searchre', r'-Siklopentan$': 'searchre', r'-Chamele0n$': 'searchre', @@ -252,7 +252,7 @@ def makeDir(path): try: ek(os.makedirs, path) # do the library update for synoindex - synoindex_notifier().addFolder(path) + synoindex_notifier.addFolder(path) except OSError: return False return True @@ -472,7 +472,7 @@ def make_dirs(path): # use normpath to remove end separator, otherwise checks permissions against itself chmodAsParent(ek(os.path.normpath, sofar)) # do the library update for synoindex - synoindex_notifier().addFolder(sofar) + synoindex_notifier.addFolder(sofar) except (OSError, IOError) as e: logger.log(u"Failed creating %s : %r" % (sofar, ex(e)), logger.ERROR) return False @@ -552,7 +552,7 @@ def delete_empty_folders(check_empty_dir, keep_dir=None): # need shutil.rmtree when ignore_items is really implemented ek(os.rmdir, check_empty_dir) # do the library update for synoindex - synoindex_notifier().deleteFolder(check_empty_dir) + synoindex_notifier.deleteFolder(check_empty_dir) except OSError as e: logger.log(u"Unable to delete %s. Error: %r" % (check_empty_dir, repr(e)), logger.WARNING) break @@ -1771,8 +1771,8 @@ def getTVDBFromID(indexer_id, indexer): def is_ip_private(ip): - priv_lo = re.compile("^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$") - priv_24 = re.compile("^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$") - priv_20 = re.compile("^192\.168\.\d{1,3}.\d{1,3}$") - priv_16 = re.compile("^172.(1[6-9]|2[0-9]|3[0-1]).[0-9]{1,3}.[0-9]{1,3}$") + priv_lo = re.compile(r"^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$") + priv_24 = re.compile(r"^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$") + priv_20 = re.compile(r"^192\.168\.\d{1,3}.\d{1,3}$") + priv_16 = re.compile(r"^172.(1[6-9]|2[0-9]|3[0-1]).[0-9]{1,3}.[0-9]{1,3}$") return priv_lo.match(ip) or priv_24.match(ip) or priv_20.match(ip) or priv_16.match(ip) diff --git a/sickbeard/history.py b/sickbeard/history.py index ad725382fd677ec09c5bad9bacefd565ad01ac3e..7b6140b52bbd92a933ea794c32bb50e5e87437cc 100644 --- a/sickbeard/history.py +++ b/sickbeard/history.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import db import datetime diff --git a/sickbeard/image_cache.py b/sickbeard/image_cache.py index ebf03d950a63197f15b04f863b3c57335a0c09de..16516b39b1c5d90557218548eb8e395196a6f22e 100644 --- a/sickbeard/image_cache.py +++ b/sickbeard/image_cache.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import os.path diff --git a/sickbeard/indexers/__init__.py b/sickbeard/indexers/__init__.py index eb63e387f91550866b4e3a5959a0022b65c85bac..bd7e9a933423f2a347fdd621554335d223f66a5e 100644 --- a/sickbeard/indexers/__init__.py +++ b/sickbeard/indexers/__init__.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. from . import indexer_api from . import indexer_exceptions diff --git a/sickbeard/indexers/indexer_api.py b/sickbeard/indexers/indexer_api.py index a8c5e92e86a755b4bcc21a5a2125a4e175511b46..b4307ac05fe15b50bf7d2d244d9a51cdf4e68048 100644 --- a/sickbeard/indexers/indexer_api.py +++ b/sickbeard/indexers/indexer_api.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import os import sickbeard diff --git a/sickbeard/logger.py b/sickbeard/logger.py index 2c20fc430a3ae8b51ca78dbc74ba6320bd949ad6..253cb9eff65221c51aec6bec20858115f0d7e07e 100644 --- a/sickbeard/logger.py +++ b/sickbeard/logger.py @@ -12,12 +12,17 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +""" +Custom Logger for SickRage +""" + +from __future__ import unicode_literals import io import os @@ -25,12 +30,13 @@ import re import sys import logging import logging.handlers +from logging import NullHandler import threading import platform import locale import traceback -from github import Github, InputFileContent +from github import Github, InputFileContent # pylint: disable=import-error import sickbeard from sickbeard import classes @@ -40,6 +46,8 @@ from sickrage.helper.encoding import ek from sickrage.helper.exceptions import ex from sickrage.helper.common import dateTimeFormat +# pylint: disable=line-too-long + # log levels ERROR = logging.ERROR WARNING = logging.WARNING @@ -47,45 +55,50 @@ INFO = logging.INFO DEBUG = logging.DEBUG DB = 5 -reverseNames = { - u'ERROR': ERROR, - u'WARNING': WARNING, - u'INFO': INFO, - u'DEBUG': DEBUG, - u'DB': DB +LOGGING_LEVELS = { + 'ERROR': ERROR, + 'WARNING': WARNING, + 'INFO': INFO, + 'DEBUG': DEBUG, + 'DB': DB, } -censoredItems = {} - - -class NullHandler(logging.Handler): - def emit(self, record): - pass +censored_items = {} # pylint: disable=invalid-name class CensoredFormatter(logging.Formatter, object): + """ + Censor information such as API keys, user names, and passwords from the Log + """ def __init__(self, fmt=None, datefmt=None, encoding='utf-8'): super(CensoredFormatter, self).__init__(fmt, datefmt) self.encoding = encoding def format(self, record): - """Strips censored items from string""" + """ + Strips censored items from string + + :param record: to censor + """ msg = super(CensoredFormatter, self).format(record) if not isinstance(msg, unicode): msg = msg.decode(self.encoding, 'replace') # Convert to unicode - for _, v in censoredItems.iteritems(): - if not isinstance(v, unicode): - v = v.decode(self.encoding, 'replace') # Convert to unicode - msg = msg.replace(v, len(v) * u'*') + for _, value in censored_items.iteritems(): + if not isinstance(value, unicode): + value = value.decode(self.encoding, 'replace') # Convert to unicode + msg = msg.replace(value, len(value) * '*') # Needed because Newznab apikey isn't stored as key=value in a section. - msg = re.sub(ur'([&?]r|[&?]apikey|[&?]api_key)=[^&]*([&\w]?)', ur'\1=**********\2', msg) + msg = re.sub(r'([&?]r|[&?]apikey|[&?]api_key)=[^&]*([&\w]?)', r'\1=**********\2', msg) return msg class Logger(object): # pylint: disable=too-many-instance-attributes + """ + Logger to create log entries + """ def __init__(self): self.logger = logging.getLogger('sickrage') @@ -97,26 +110,31 @@ class Logger(object): # pylint: disable=too-many-instance-attributes # logging.getLogger('tornado.access'), ] - self.consoleLogging = False - self.fileLogging = False - self.debugLogging = False - self.databaseLogging = False - self.logFile = None + self.console_logging = False + self.file_logging = False + self.debug_logging = False + self.database_logging = False + self.log_file = None self.submitter_running = False - def initLogging(self, consoleLogging=False, fileLogging=False, debugLogging=False, databaseLogging=False): - self.logFile = self.logFile or ek(os.path.join, sickbeard.LOG_DIR, 'sickrage.log') - self.debugLogging = debugLogging - self.consoleLogging = consoleLogging - self.fileLogging = fileLogging - self.databaseLogging = databaseLogging + def init_logging(self, console_logging=False, file_logging=False, debug_logging=False, database_logging=False): + """ + Initialize logging - # add a new logging level DB - logging.addLevelName(DB, 'DB') + :param console_logging: True if logging to console + :param file_logging: True if logging to file + :param debug_logging: True if debug logging is enabled + :param database_logging: True if logging database access + """ + self.log_file = self.log_file or ek(os.path.join, sickbeard.LOG_DIR, 'sickrage.log') + self.debug_logging = debug_logging + self.console_logging = console_logging + self.file_logging = file_logging + self.database_logging = database_logging - # nullify root logger - logging.getLogger().addHandler(NullHandler()) + logging.addLevelName(DB, 'DB') # add a new logging level DB + logging.getLogger().addHandler(NullHandler()) # nullify root logger # set custom root logger for logger in self.loggers: @@ -124,43 +142,54 @@ class Logger(object): # pylint: disable=too-many-instance-attributes logger.root = self.logger logger.parent = self.logger - loglevel = DB if self.databaseLogging else DEBUG if self.debugLogging else INFO + log_level = DB if self.database_logging else DEBUG if self.debug_logging else INFO # set minimum logging level allowed for loggers for logger in self.loggers: - logger.setLevel(loglevel) + logger.setLevel(log_level) # console log handler - if self.consoleLogging: + if self.console_logging: console = logging.StreamHandler() - console.setFormatter(CensoredFormatter(u'%(asctime)s %(levelname)s::%(message)s', '%H:%M:%S', encoding='utf-8')) - console.setLevel(loglevel) + console.setFormatter(CensoredFormatter('%(asctime)s %(levelname)s::%(message)s', '%H:%M:%S')) + console.setLevel(log_level) for logger in self.loggers: logger.addHandler(console) # rotating log file handler - if self.fileLogging: - rfh = logging.handlers.RotatingFileHandler(self.logFile, maxBytes=int(sickbeard.LOG_SIZE * 1048576), backupCount=sickbeard.LOG_NR, encoding='utf-8') - rfh.setFormatter(CensoredFormatter(u'%(asctime)s %(levelname)-8s %(message)s', dateTimeFormat, encoding='utf-8')) - rfh.setLevel(loglevel) + if self.file_logging: + rfh = logging.handlers.RotatingFileHandler(self.log_file, maxBytes=int(sickbeard.LOG_SIZE * 1048576), backupCount=sickbeard.LOG_NR, encoding='utf-8') + rfh.setFormatter(CensoredFormatter('%(asctime)s %(levelname)-8s %(message)s', dateTimeFormat)) + rfh.setLevel(log_level) for logger in self.loggers: logger.addHandler(rfh) @staticmethod def shutdown(): + """ + Shut down the logger + """ logging.shutdown() def log(self, msg, level=INFO, *args, **kwargs): - meThread = threading.currentThread().getName() + """ + Create log entry + + :param msg: to log + :param level: of log, e.g. DEBUG, INFO, etc. + :param args: to pass to logger + :param kwargs: to pass to logger + """ + cur_thread = threading.currentThread().getName() if sickbeard.CUR_COMMIT_HASH and len(sickbeard.CUR_COMMIT_HASH) > 6 and level in [ERROR, WARNING]: msg += ' [%s]' % sickbeard.CUR_COMMIT_HASH[:7] - message = meThread + u" :: " + msg + message = '%s :: %s' % (cur_thread, msg) # Change the SSL error to a warning with a link to information about how to fix it. - check = re.sub(ur'error \[Errno 1\] _ssl.c:\d{3}: error:\d{8}:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert internal error', 'See: http://git.io/vuU5V', message) + check = re.sub(r'error \[Errno 1\] _ssl.c:\d{3}: error:\d{8}:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert internal error', 'See: http://git.io/vuU5V', message) if check != message: message = check level = WARNING @@ -178,18 +207,18 @@ class Logger(object): # pylint: disable=too-many-instance-attributes def log_error_and_exit(self, error_msg, *args, **kwargs): self.log(error_msg, ERROR, *args, **kwargs) - if not self.consoleLogging: + if not self.console_logging: sys.exit(error_msg.encode(sickbeard.SYS_ENCODING, 'xmlcharrefreplace')) else: sys.exit(1) def submit_errors(self): # pylint: disable=too-many-branches,too-many-locals - submitter_result = u'' + submitter_result = '' issue_id = None if not (sickbeard.GIT_USERNAME and sickbeard.GIT_PASSWORD and sickbeard.DEBUG and len(classes.ErrorViewer.errors) > 0): - submitter_result = u'Please set your GitHub username and password in the config and enable debug. Unable to submit issue ticket to GitHub!' + submitter_result = 'Please set your GitHub username and password in the config and enable debug. Unable to submit issue ticket to GitHub!' return submitter_result, issue_id try: @@ -197,16 +226,16 @@ class Logger(object): # pylint: disable=too-many-instance-attributes checkversion = CheckVersion() checkversion.check_for_new_version() commits_behind = checkversion.updater.get_num_commits_behind() - except Exception: - submitter_result = u'Could not check if your SickRage is updated, unable to submit issue ticket to GitHub!' + except Exception: # pylint: disable=broad-except + submitter_result = 'Could not check if your SickRage is updated, unable to submit issue ticket to GitHub!' return submitter_result, issue_id if commits_behind is None or commits_behind > 0: - submitter_result = u'Please update SickRage, unable to submit issue ticket to GitHub with an outdated version!' + submitter_result = 'Please update SickRage, unable to submit issue ticket to GitHub with an outdated version!' return submitter_result, issue_id if self.submitter_running: - submitter_result = u'Issue submitter is running, please wait for it to complete' + submitter_result = 'Issue submitter is running, please wait for it to complete' return submitter_result, issue_id self.submitter_running = True @@ -214,118 +243,125 @@ class Logger(object): # pylint: disable=too-many-instance-attributes gh_org = sickbeard.GIT_ORG or 'SickRage' gh_repo = 'sickrage-issues' - gh = Github(login_or_token=sickbeard.GIT_USERNAME, password=sickbeard.GIT_PASSWORD, user_agent="SiCKRAGE") + git = Github(login_or_token=sickbeard.GIT_USERNAME, password=sickbeard.GIT_PASSWORD, user_agent='SickRage') - try: + try: # pylint: disable=too-many-nested-blocks # read log file log_data = None - if ek(os.path.isfile, self.logFile): - with io.open(self.logFile, 'r', encoding='utf-8') as f: - log_data = f.readlines() + if ek(os.path.isfile, self.log_file): + with io.open(self.log_file, encoding='utf-8') as log_f: + log_data = log_f.readlines() for i in range(1, int(sickbeard.LOG_NR)): - if ek(os.path.isfile, self.logFile + ".%i" % i) and (len(log_data) <= 500): - with io.open(self.logFile + ".%i" % i, 'r', encoding='utf-8') as f: - log_data += f.readlines() + f_name = '%s.%i' % (self.log_file, i) + if ek(os.path.isfile, f_name) and (len(log_data) <= 500): + with io.open(f_name, encoding='utf-8') as log_f: + log_data += log_f.readlines() log_data = [line for line in reversed(log_data)] # parse and submit errors to issue tracker - for curError in sorted(classes.ErrorViewer.errors, key=lambda error: error.time, reverse=True)[:500]: - + for cur_error in sorted(classes.ErrorViewer.errors, key=lambda error: error.time, reverse=True)[:500]: try: - title_Error = ss(str(curError.title)) - if not len(title_Error) or title_Error == 'None': - title_Error = re.match(r"^[A-Z0-9\-\[\] :]+::\s*(.*)(?: \[[\w]{7}\])$", ss(curError.message)).group(1) + title_error = ss(str(cur_error.title)) + if not len(title_error) or title_error == 'None': + title_error = re.match(r'^[A-Z0-9\-\[\] :]+::\s*(.*)(?: \[[\w]{7}\])$', ss(cur_error.message)).group(1) + + if len(title_error) > 1000: + title_error = title_error[0:1000] - if len(title_Error) > 1000: - title_Error = title_Error[0:1000] - except Exception as e: - self.log("Unable to get error title : " + ex(e), ERROR) + except Exception as err_msg: # pylint: disable=broad-except + self.log('Unable to get error title : %s' % ex(err_msg), ERROR) gist = None - regex = ur"^(%s)\s+([A-Z]+)\s+([0-9A-Z\-]+)\s*(.*)(?: \[[\w]{7}\])$" % curError.time - for i, x in enumerate(log_data): - match = re.match(regex, x) + regex = r'^(%s)\s+([A-Z]+)\s+([0-9A-Z\-]+)\s*(.*)(?: \[[\w]{7}\])$' % cur_error.time + for i, data in enumerate(log_data): + match = re.match(regex, data) if match: level = match.group(2) - if reverseNames[level] == ERROR: - paste_data = u"".join(log_data[i:i + 50]) + if LOGGING_LEVELS[level] == ERROR: + paste_data = ''.join(log_data[i:i + 50]) if paste_data: - gist = gh.get_user().create_gist(True, {"sickrage.log": InputFileContent(paste_data)}) + gist = git.get_user().create_gist(True, {'sickrage.log': InputFileContent(paste_data)}) break else: gist = 'No ERROR found' - message = u"### INFO\n" - message += u"Python Version: **" + sys.version[:120].replace('\n', '') + "**\n" - message += u"Operating System: **" + platform.platform() + "**\n" try: - message += u"Locale: " + locale.getdefaultlocale()[1] + "\n" - except Exception: - message += u"Locale: unknown" + "\n" - message += u"Branch: **" + sickbeard.BRANCH + "**\n" - message += u"Commit: SickRage/SickRage@" + sickbeard.CUR_COMMIT_HASH + "\n" + locale_name = locale.getdefaultlocale()[1] + except Exception: # pylint: disable=broad-except + locale_name = 'unknown' + if gist and gist != 'No ERROR found': - message += u"Link to Log: " + gist.html_url + "\n" + log_link = 'Link to Log: %s' % gist.html_url else: - message += u"No Log available with ERRORS: " + "\n" - message += u"### ERROR\n" - message += u"```\n" - message += curError.message + "\n" - message += u"```\n" - message += u"---\n" - message += u"_STAFF NOTIFIED_: @SickRage/owners @SickRage/moderators" - - title_Error = u"[APP SUBMITTED]: " + title_Error - reports = gh.get_organization(gh_org).get_repo(gh_repo).get_issues(state="all") + log_link = 'No Log available with ERRORS:' + + msg = [ + '### INFO', + 'Python Version: **%s**' % sys.version[:120].replace('\n', ''), + 'Operating System: **%s**' % platform.platform(), + 'Locale: %s' % locale_name, + 'Branch: **%s**' % sickbeard.BRANCH, + 'Commit: SickRage/SickRage@%s' % sickbeard.CUR_COMMIT_HASH, + log_link, + '### ERROR', + '```', + cur_error.message, + '```', + '---', + '_STAFF NOTIFIED_: @SickRage/owners @SickRage/moderators', + ] + + message = '\n'.join(msg) + title_error = '[APP SUBMITTED]: %s' % title_error + reports = git.get_organization(gh_org).get_repo(gh_repo).get_issues(state='all') def is_ascii_error(title): # [APP SUBMITTED]: 'ascii' codec can't encode characters in position 00-00: ordinal not in range(128) # [APP SUBMITTED]: 'charmap' codec can't decode byte 0x00 in position 00: character maps to <undefined> - return re.search(ur".* codec can't .*code .* in position .*:", title) is not None + return re.search(r'.* codec can\'t .*code .* in position .*:', title) is not None def is_malformed_error(title): # [APP SUBMITTED]: not well-formed (invalid token): line 0, column 0 - return re.search(ur".* not well-formed \(invalid token\): line .* column .*", title) is not None + return re.search(r'.* not well-formed \(invalid token\): line .* column .*', title) is not None - ascii_error = is_ascii_error(title_Error) - malformed_error = is_malformed_error(title_Error) + ascii_error = is_ascii_error(title_error) + malformed_error = is_malformed_error(title_error) issue_found = False for report in reports: - if title_Error.rsplit(' :: ')[-1] in report.title or \ + if title_error.rsplit(' :: ')[-1] in report.title or \ (malformed_error and is_malformed_error(report.title)) or \ (ascii_error and is_ascii_error(report.title)): issue_id = report.number if not report.raw_data['locked']: if report.create_comment(message): - submitter_result = u'Commented on existing issue #%s successfully!' % issue_id + submitter_result = 'Commented on existing issue #%s successfully!' % issue_id else: - submitter_result = u'Failed to comment on found issue #%s!' % issue_id + submitter_result = 'Failed to comment on found issue #%s!' % issue_id else: - submitter_result = u'Issue #%s is locked, check github to find info about the error.' % issue_id + submitter_result = 'Issue #%s is locked, check GitHub to find info about the error.' % issue_id issue_found = True break if not issue_found: - issue = gh.get_organization(gh_org).get_repo(gh_repo).create_issue(title_Error, message) + issue = git.get_organization(gh_org).get_repo(gh_repo).create_issue(title_error, message) if issue: issue_id = issue.number - submitter_result = u'Your issue ticket #%s was submitted successfully!' % issue_id + submitter_result = 'Your issue ticket #%s was submitted successfully!' % issue_id else: - submitter_result = u'Failed to create a new issue!' + submitter_result = 'Failed to create a new issue!' - if issue_id and curError in classes.ErrorViewer.errors: + if issue_id and cur_error in classes.ErrorViewer.errors: # clear error from error list - classes.ErrorViewer.errors.remove(curError) - - except Exception as e: + classes.ErrorViewer.errors.remove(cur_error) + except Exception: # pylint: disable=broad-except self.log(traceback.format_exc(), ERROR) - submitter_result = u'Exception generated in issue submitter, please check the log' + submitter_result = 'Exception generated in issue submitter, please check the log' issue_id = None finally: self.submitter_running = False @@ -347,4 +383,4 @@ class Wrapper(object): return getattr(self.instance, name) -_globals = sys.modules[__name__] = Wrapper(sys.modules[__name__]) +_globals = sys.modules[__name__] = Wrapper(sys.modules[__name__]) # pylint: disable=invalid-name diff --git a/sickbeard/metadata/__init__.py b/sickbeard/metadata/__init__.py index aa6862f5e8bc3a20f7961fea9949e4f8bff5902a..4c37192e418615e45d043a63e17aae53015bea37 100644 --- a/sickbeard/metadata/__init__.py +++ b/sickbeard/metadata/__init__.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import sys from sickbeard.metadata import kodi, kodi_12plus, mediabrowser, ps3, wdtv, tivo, mede8er, generic, helpers diff --git a/sickbeard/metadata/generic.py b/sickbeard/metadata/generic.py index 72e54aefd5d40897ab7644764382a8cb58743c3a..e9decefa7f36f96f4ab25342ac8bc1cc5831a77c 100644 --- a/sickbeard/metadata/generic.py +++ b/sickbeard/metadata/generic.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import os import io diff --git a/sickbeard/metadata/helpers.py b/sickbeard/metadata/helpers.py index 31ad1d509ebceea3467d85a4f12482315429da20..fe20a17ac497e1957e66afc436fc92bb29e51e1e 100644 --- a/sickbeard/metadata/helpers.py +++ b/sickbeard/metadata/helpers.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. from sickbeard import helpers from sickbeard import logger diff --git a/sickbeard/metadata/kodi.py b/sickbeard/metadata/kodi.py index b7445073ee1d7389bfb5641e52336c05c51112f8..f95e4661b9eeba9379d190ab071b74dd63d30c0a 100644 --- a/sickbeard/metadata/kodi.py +++ b/sickbeard/metadata/kodi.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import os diff --git a/sickbeard/metadata/kodi_12plus.py b/sickbeard/metadata/kodi_12plus.py index 8693e5e780eac68c6d7c973b8e263a8ca52ecccd..57f65461f7cdff19c50f6ea3e849835fa6fce6ed 100644 --- a/sickbeard/metadata/kodi_12plus.py +++ b/sickbeard/metadata/kodi_12plus.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import re import datetime diff --git a/sickbeard/metadata/mede8er.py b/sickbeard/metadata/mede8er.py index 2f924f982f5628c569a1d346ab3a13af31d834ca..19aaa1cf30da075ef817aa81c04d1b6cb4508322 100644 --- a/sickbeard/metadata/mede8er.py +++ b/sickbeard/metadata/mede8er.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import io import os diff --git a/sickbeard/metadata/mediabrowser.py b/sickbeard/metadata/mediabrowser.py index f053d00e51ce559c6409ef5c30b9f8cd90074f8a..d98dad2cc8a3a3fdc9b4c03cee4275dba5dc9f9e 100644 --- a/sickbeard/metadata/mediabrowser.py +++ b/sickbeard/metadata/mediabrowser.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import datetime import os diff --git a/sickbeard/metadata/ps3.py b/sickbeard/metadata/ps3.py index e11da1389730c8a43f62755c921ea48ab2f5ad36..4ecf99a9652c71c6d90fa60ae322e4f0d5680e74 100644 --- a/sickbeard/metadata/ps3.py +++ b/sickbeard/metadata/ps3.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import os diff --git a/sickbeard/metadata/tivo.py b/sickbeard/metadata/tivo.py index 0fe70790143f6645a8c5129df5f1eaf19b08bdbb..436ec20e7acaeab540fbbeaa9f31580b2f9250b4 100644 --- a/sickbeard/metadata/tivo.py +++ b/sickbeard/metadata/tivo.py @@ -13,11 +13,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import io diff --git a/sickbeard/metadata/wdtv.py b/sickbeard/metadata/wdtv.py index 1fea81914b1b6507f352416c5c4df85aa70ca7a8..f003726bb0a5b4885a6356e71dba59347b376a88 100644 --- a/sickbeard/metadata/wdtv.py +++ b/sickbeard/metadata/wdtv.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import datetime import os diff --git a/sickbeard/name_cache.py b/sickbeard/name_cache.py index 515743cc5ab0af2331d8489e2a461495b2584dab..71f5b9473594eecbcd5b6af1e85c11205ffba4c6 100644 --- a/sickbeard/name_cache.py +++ b/sickbeard/name_cache.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import threading import sickbeard from sickbeard import db diff --git a/sickbeard/name_parser/parser.py b/sickbeard/name_parser/parser.py index 584650beaeedd1b9b505576ef46f3ae9cacf1e25..c608d5f0b03cebb51d8859377102f4ca0fcae0c0 100644 --- a/sickbeard/name_parser/parser.py +++ b/sickbeard/name_parser/parser.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import os import time diff --git a/sickbeard/name_parser/regexes.py b/sickbeard/name_parser/regexes.py index d164ee8b82758cb8f1b38b70c5c0d7501295ac34..650dcd0865ddf3c4f5f8d0188b2fa0835f989c94 100644 --- a/sickbeard/name_parser/regexes.py +++ b/sickbeard/name_parser/regexes.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. # all regexes are case insensitive diff --git a/sickbeard/naming.py b/sickbeard/naming.py index 1cf1b1e2ca7321ca858b2a909d0c8d7787c91ab4..250b51e889fbeeb41a98f5716c4e191b68f3cc8a 100644 --- a/sickbeard/naming.py +++ b/sickbeard/naming.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import datetime import os diff --git a/sickbeard/network_timezones.py b/sickbeard/network_timezones.py index 8db68734247fc9456405a7f89bfc4bae72c06cc5..c8236ab252cd88154e816b7936f37c73a94a383f 100644 --- a/sickbeard/network_timezones.py +++ b/sickbeard/network_timezones.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import re import datetime diff --git a/sickbeard/notifiers/__init__.py b/sickbeard/notifiers/__init__.py index ab4472ec4acc4b3d791af006584f468f8dbb7867..7936a8974fb056b874bcd2653888fe4e8d650201 100644 --- a/sickbeard/notifiers/__init__.py +++ b/sickbeard/notifiers/__init__.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import kodi @@ -37,6 +37,8 @@ import nma import pushalot import pushbullet import freemobile +import telegram + import tweet import trakt @@ -61,6 +63,7 @@ nma_notifier = nma.NMA_Notifier() pushalot_notifier = pushalot.PushalotNotifier() pushbullet_notifier = pushbullet.PushbulletNotifier() freemobile_notifier = freemobile.FreeMobileNotifier() +telegram_notifier = telegram.TelegramNotifier() # social twitter_notifier = tweet.TwitterNotifier() trakt_notifier = trakt.TraktNotifier() @@ -77,6 +80,7 @@ notifiers = [ pytivo_notifier, growl_notifier, freemobile_notifier, + telegram_notifier, prowl_notifier, pushover_notifier, boxcar2_notifier, diff --git a/sickbeard/notifiers/boxcar2.py b/sickbeard/notifiers/boxcar2.py index f99ce0088ed5bf4f40b0c334d71821ed622e38ee..230d7c1c47d14a94e323dcb1f95a396ecbbf0050 100644 --- a/sickbeard/notifiers/boxcar2.py +++ b/sickbeard/notifiers/boxcar2.py @@ -14,11 +14,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import urllib import urllib2 diff --git a/sickbeard/notifiers/emailnotify.py b/sickbeard/notifiers/emailnotify.py index ce8d4ead9d3e3935946f8e9dcbb20cf63b963be1..4b5bcf6ffafe8b4ebe589edeaa60909cec235987 100644 --- a/sickbeard/notifiers/emailnotify.py +++ b/sickbeard/notifiers/emailnotify.py @@ -15,11 +15,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. # ############################################################################## diff --git a/sickbeard/notifiers/emby.py b/sickbeard/notifiers/emby.py index acfbcc300a187bee6a49d061d30c9772c2b6b7d6..dc1a1a98f7a2e9b7eab0edda630ecf63d885c217 100644 --- a/sickbeard/notifiers/emby.py +++ b/sickbeard/notifiers/emby.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import urllib import urllib2 diff --git a/sickbeard/notifiers/freemobile.py b/sickbeard/notifiers/freemobile.py index 75c42df2e4d4d506833c497f5ca6e38d8293ed3f..84760b05177a912f8c4b195415ef5bc0d85bf2f8 100644 --- a/sickbeard/notifiers/freemobile.py +++ b/sickbeard/notifiers/freemobile.py @@ -14,11 +14,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import urllib2 import sickbeard diff --git a/sickbeard/notifiers/growl.py b/sickbeard/notifiers/growl.py index 94742af06023ea780ab7d448e93f45b071646ff7..dd2d22b99cd812dc04af1c54791be32654640ce0 100644 --- a/sickbeard/notifiers/growl.py +++ b/sickbeard/notifiers/growl.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import socket diff --git a/sickbeard/notifiers/kodi.py b/sickbeard/notifiers/kodi.py index 9ff6c6dbc1c17ca2704a86ee9e47463bc93338bb..7db920504b2cb17e1bdeb0981a3af61ec273715f 100644 --- a/sickbeard/notifiers/kodi.py +++ b/sickbeard/notifiers/kodi.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import httplib import urllib diff --git a/sickbeard/notifiers/libnotify.py b/sickbeard/notifiers/libnotify.py index 644701570e2f182fc3960d1db07dd772fc41e8a9..3051ed58d119edbfb48a9ee558cfcf6f586ee12e 100644 --- a/sickbeard/notifiers/libnotify.py +++ b/sickbeard/notifiers/libnotify.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import os import cgi diff --git a/sickbeard/notifiers/nmj.py b/sickbeard/notifiers/nmj.py index c9ddbda4a096e2bbf387f2d8d45097aefdb42576..dc047a3a7cb4b96963f175a85ab19df52ccddd82 100644 --- a/sickbeard/notifiers/nmj.py +++ b/sickbeard/notifiers/nmj.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import urllib import urllib2 diff --git a/sickbeard/notifiers/nmjv2.py b/sickbeard/notifiers/nmjv2.py index 5ac7bfc3b9ee3039296ac8916909646769f9134d..881432c2f3aa48b9ff842941174210e6c48c7692 100644 --- a/sickbeard/notifiers/nmjv2.py +++ b/sickbeard/notifiers/nmjv2.py @@ -13,11 +13,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import urllib2 from xml.dom.minidom import parseString diff --git a/sickbeard/notifiers/plex.py b/sickbeard/notifiers/plex.py index 217485ad7a921c8f5ba35a23d15c786949566654..4709e920efda867e760aedd6a6aad97d4fbdd728 100644 --- a/sickbeard/notifiers/plex.py +++ b/sickbeard/notifiers/plex.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import urllib import urllib2 diff --git a/sickbeard/notifiers/prowl.py b/sickbeard/notifiers/prowl.py index 24b0f145e6d783546ce4c98bd3caa6caecd614c7..22cc114416455d67a8b85cb08b979ac9f40a7a6f 100644 --- a/sickbeard/notifiers/prowl.py +++ b/sickbeard/notifiers/prowl.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. # ############################################################################## diff --git a/sickbeard/notifiers/pushalot.py b/sickbeard/notifiers/pushalot.py index 6a3c82b02a0c457172d6ea3cbee246f93e18c0f6..3efb5aa5fa1671ee41574da4bde045cbdda62300 100644 --- a/sickbeard/notifiers/pushalot.py +++ b/sickbeard/notifiers/pushalot.py @@ -13,11 +13,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import socket from httplib import HTTPSConnection, HTTPException diff --git a/sickbeard/notifiers/pushover.py b/sickbeard/notifiers/pushover.py index e2e2b89f5bae5152e1708ca7d2bb693fdebae716..4bd0fb27c8d19bb24c62f0ba4fb599c0f80f83c8 100644 --- a/sickbeard/notifiers/pushover.py +++ b/sickbeard/notifiers/pushover.py @@ -13,11 +13,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import httplib import urllib diff --git a/sickbeard/notifiers/pytivo.py b/sickbeard/notifiers/pytivo.py index 1c28eccfc322c8392c412e8004a5a5c85b235f1f..fe4a98788aa27bf59f52b9afb65a6c1d19df5e1a 100644 --- a/sickbeard/notifiers/pytivo.py +++ b/sickbeard/notifiers/pytivo.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import os import sickbeard diff --git a/sickbeard/notifiers/synoindex.py b/sickbeard/notifiers/synoindex.py index abdee673a4f06c4137a84fc93443c8274c49ed04..42f9b44d4d31f209a90a6b0428ddb938a45b1edd 100644 --- a/sickbeard/notifiers/synoindex.py +++ b/sickbeard/notifiers/synoindex.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import os import subprocess diff --git a/sickbeard/notifiers/synologynotifier.py b/sickbeard/notifiers/synologynotifier.py index bbdcfbb82cd238da5d0d5691419d76c35c4f51a4..f67d95578aae8fe4bd3adc9a8912d57d2d2ce9d0 100644 --- a/sickbeard/notifiers/synologynotifier.py +++ b/sickbeard/notifiers/synologynotifier.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import os import subprocess diff --git a/sickbeard/notifiers/telegram.py b/sickbeard/notifiers/telegram.py new file mode 100644 index 0000000000000000000000000000000000000000..cf4a663aba4017a420fb646fd6896085ba49be85 --- /dev/null +++ b/sickbeard/notifiers/telegram.py @@ -0,0 +1,133 @@ +# coding=utf-8 + +# Author: Marvin Pinto <me@marvinp.ca> +# Author: Dennis Lutter <lad1337@gmail.com> +# Author: Aaron Bieber <deftly@gmail.com> +# URL: http://code.google.com/p/sickbeard/ +# +# This file is part of SickRage. +# +# SickRage is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SickRage is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +import urllib +import urllib2 + +import sickbeard +from sickbeard import logger +from sickbeard.common import notifyStrings, NOTIFY_SNATCH, NOTIFY_DOWNLOAD, NOTIFY_SUBTITLE_DOWNLOAD, NOTIFY_GIT_UPDATE, NOTIFY_GIT_UPDATE_TEXT, NOTIFY_GIT_UPDATE_TEXT + + +class TelegramNotifier(object): + + def test_notify(self, id=None, apiKey=None): + return self._notifyTelegram('Test', "This is a test notification from SickRage", id, apiKey, force=True) + + def _sendTelegramMsg(self, title, msg, id=None, apiKey=None): + """ + Sends a Telegram notification + + title: The title of the notification to send + msg: The message string to send + id: The Telegram user/group id to send the message to + apikey: Your Telegram bot API token + + returns: True if the message succeeded, False otherwise + """ + + if id is None: + id = sickbeard.TELEGRAM_ID + if apiKey is None: + apiKey = sickbeard.TELEGRAM_APIKEY + + logger.log(u"Telegram in use with API KEY: " + apiKey, logger.DEBUG) + + message = title.encode('utf-8') + ": " + msg.encode('utf-8') + payload = urllib.urlencode({'chat_id': id, 'text': message}) + TELEGRAM_API = "https://api.telegram.org/bot%s/%s" + + req = urllib2.Request(TELEGRAM_API % (apiKey, "sendMessage"), payload) + + try: + urllib2.urlopen(req) + except IOError as e: + if hasattr(e, 'code'): + if e.code == 400: + message = "Missing parameter(s)." + logger.log(message, logger.ERROR) + return False, message + if e.code == 401: + message = "Authentication failed." + logger.log(message, logger.ERROR) + return False, message + if e.code == 420: + message = "Too many messages." + logger.log(message, logger.ERROR) + return False, message + if e.code == 500: + message = "Server error. Please retry in few moment." + logger.log(message, logger.ERROR) + return False, message + except Exception as e: + message = u"Error while sending Telegram message: {0}".format(e) + logger.log(message, logger.ERROR) + return False, message + + message = "Telegram message sent successfully." + logger.log(message, logger.INFO) + return True, message + + def notify_snatch(self, ep_name, title=notifyStrings[NOTIFY_SNATCH]): + if sickbeard.TELEGRAM_NOTIFY_ONSNATCH: + self._notifyTelegram(title, ep_name) + + def notify_download(self, ep_name, title=notifyStrings[NOTIFY_DOWNLOAD]): + if sickbeard.TELEGRAM_NOTIFY_ONDOWNLOAD: + self._notifyTelegram(title, ep_name) + + def notify_subtitle_download(self, ep_name, lang, title=notifyStrings[NOTIFY_SUBTITLE_DOWNLOAD]): + if sickbeard.TELEGRAM_NOTIFY_ONSUBTITLEDOWNLOAD: + self._notifyTelegram(title, ep_name + ": " + lang) + + def notify_git_update(self, new_version="??"): + if sickbeard.USE_TELEGRAM: + update_text = notifyStrings[NOTIFY_GIT_UPDATE_TEXT] + title = notifyStrings[NOTIFY_GIT_UPDATE] + self._notifyTelegram(title, update_text + new_version) + + def notify_login(self, ipaddress=""): + if sickbeard.USE_TELEGRAM: + update_text = common.notifyStrings[common.NOTIFY_LOGIN_TEXT] + title = common.notifyStrings[common.NOTIFY_LOGIN] + self._notifyTelegram(title, update_text.format(ipaddress)) + + def _notifyTelegram(self, title, message, id=None, apiKey=None, force=False): + """ + Sends a Telegram notification + + title: The title of the notification to send + message: The message string to send + id: The Telegram user/group id to send the message to + apikey: Your Telegram bot API token + force: Enforce sending, for instance for testing + """ + + if not sickbeard.USE_TELEGRAM and not force: + logger.log("Notification for Telegram not enabled, skipping this notification", logger.DEBUG) + return False, "Disabled" + + logger.log("Sending a Telegram message for " + message, logger.DEBUG) + + return self._sendTelegramMsg(title, message, id, apiKey) + + +notifier = TelegramNotifier diff --git a/sickbeard/notifiers/trakt.py b/sickbeard/notifiers/trakt.py index 33c4a2873bdebb2bc3a5328ab56ae5dc8e04ed31..8036ecd730c1066d4ef513394ba2df24991ae834 100644 --- a/sickbeard/notifiers/trakt.py +++ b/sickbeard/notifiers/trakt.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import sickbeard from sickbeard import logger diff --git a/sickbeard/notifiers/tweet.py b/sickbeard/notifiers/tweet.py index 9005a66411aee1e91356572bfdb094d6d30c657c..7e747b960c173fe83956884737c0522ae95f9b13 100644 --- a/sickbeard/notifiers/tweet.py +++ b/sickbeard/notifiers/tweet.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import sickbeard diff --git a/sickbeard/nzbSplitter.py b/sickbeard/nzbSplitter.py index 9528d44716a66fe089b759f9742e74d35962d158..9309dedafffd785e211ca1e9dc87736820b3d85e 100644 --- a/sickbeard/nzbSplitter.py +++ b/sickbeard/nzbSplitter.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. # pylint: disable=line-too-long diff --git a/sickbeard/nzbget.py b/sickbeard/nzbget.py index 0d054fca8532c89d7c59fedb26f2919475660c63..9a8e3e983c0e73485298414dcef7f20a261f47d4 100644 --- a/sickbeard/nzbget.py +++ b/sickbeard/nzbget.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import httplib import datetime diff --git a/sickbeard/postProcessor.py b/sickbeard/postProcessor.py index a1e8275b8ea73c3e8ea03173aecef23013aae45e..237b689a91e907832e06d978a961d1fc31517ca0 100644 --- a/sickbeard/postProcessor.py +++ b/sickbeard/postProcessor.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import glob import fnmatch diff --git a/sickbeard/processTV.py b/sickbeard/processTV.py index 5970a8e15528e7f3b573f793e44c167b8fcbdeb9..5858b449f73c14d514604af6ccae50ee1a135c64 100644 --- a/sickbeard/processTV.py +++ b/sickbeard/processTV.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import os import stat @@ -267,14 +267,16 @@ def processDir(dirName, nzbName=None, process_method=None, force=False, is_prior if process_method != u"move" or not result.result or (proc_type == u"manual" and not delete_on): continue - delete_files(processPath, notwantedFiles, result) + # These need done regardless what part of the above `if` is executed. + delete_files(processPath, notwantedFiles, result) + delete_folder(ek(os.path.join, processPath, u'@eaDir')) + if all([not sickbeard.NO_DELETE or proc_type == u"manual", + process_method == u"move", + ek(os.path.normpath, processPath) != ek(os.path.normpath, sickbeard.TV_DOWNLOAD_DIR)] + ): + if delete_folder(processPath, check_empty=True): + result.output += logHelper(u"Deleted folder: %s" % processPath, logger.DEBUG) - if all([not sickbeard.NO_DELETE or proc_type == u"manual", - process_method == u"move", - ek(os.path.normpath, processPath) != ek(os.path.normpath, sickbeard.TV_DOWNLOAD_DIR)] - ): - if delete_folder(processPath, check_empty=True): - result.output += logHelper(u"Deleted folder: %s" % processPath, logger.DEBUG) else: result.output += logHelper(u"Found temporary sync files: %s in path: %s" % (SyncFiles, processPath)) result.output += logHelper(u"Skipping post processing for folder: %s" % processPath) diff --git a/sickbeard/properFinder.py b/sickbeard/properFinder.py index bb28758ae7007ae095e48e91b7800eee571393a0..b1e254beb341ea0e8a43d915689032a342a4a615 100644 --- a/sickbeard/properFinder.py +++ b/sickbeard/properFinder.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import re import time diff --git a/sickbeard/providers/__init__.py b/sickbeard/providers/__init__.py index 55c1993661f3501a1b403bfe59ef86ff583acf9e..a32b9ef8c9fda8834ca2e8453ac024c2876ca66a 100644 --- a/sickbeard/providers/__init__.py +++ b/sickbeard/providers/__init__.py @@ -12,28 +12,27 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. from os import sys from random import shuffle import sickbeard -from sickbeard import logger from sickbeard.providers import btn, newznab, rsstorrent, womble, thepiratebay, torrentleech, kat, iptorrents, torrentz, \ - omgwtfnzbs, scc, hdtorrents, torrentday, hdbits, hounddawgs, speedcd, nyaatorrents, animenzb, bluetigers, cpasbien, fnt, xthor, torrentbytes, \ - freshontv, titansoftv, morethantv, bitsoup, t411, tokyotoshokan, shazbat, rarbg, alpharatio, tntvillage, binsearch, torrentproject, extratorrent, \ + omgwtfnzbs, scc, hdtorrents, torrentday, hdbits, hounddawgs, speedcd, nyaatorrents, bluetigers, fnt, xthor, torrentbytes, \ + freshontv, morethantv, bitsoup, t411, tokyotoshokan, shazbat, rarbg, alpharatio, tntvillage, binsearch, torrentproject, extratorrent, \ scenetime, btdigg, transmitthenet, tvchaosuk, bitcannon, pretome, gftracker, hdspace, newpct, elitetorrent, bitsnoop, danishbits, hd4free, limetorrents __all__ = [ 'womble', 'btn', 'thepiratebay', 'kat', 'torrentleech', 'scc', 'hdtorrents', 'torrentday', 'hdbits', 'hounddawgs', 'iptorrents', 'omgwtfnzbs', - 'speedcd', 'nyaatorrents', 'animenzb', 'torrentbytes', 'freshontv', 'titansoftv', + 'speedcd', 'nyaatorrents', 'torrentbytes', 'freshontv', 'morethantv', 'bitsoup', 't411', 'tokyotoshokan', 'alpharatio', - 'shazbat', 'rarbg', 'tntvillage', 'binsearch', 'bluetigers', 'cpasbien', + 'shazbat', 'rarbg', 'tntvillage', 'binsearch', 'bluetigers', 'fnt', 'xthor', 'scenetime', 'btdigg', 'transmitthenet', 'tvchaosuk', 'torrentproject', 'extratorrent', 'bitcannon', 'torrentz', 'pretome', 'gftracker', 'hdspace', 'newpct', 'elitetorrent', 'bitsnoop', 'danishbits', 'hd4free', 'limetorrents' diff --git a/sickbeard/providers/alpharatio.py b/sickbeard/providers/alpharatio.py index 39dd4d8ba6306856628f072d89dc3df4cd71a276..b080b4e20632498cdf62b94121c869028689050e 100644 --- a/sickbeard/providers/alpharatio.py +++ b/sickbeard/providers/alpharatio.py @@ -1,7 +1,6 @@ # coding=utf-8 - -# Author: Bill Nasty -# URL: https://github.com/SickRage/SickRage +# Author: Dustyn Gibson <miigotu@gmail.com> +# URL: https://sickrage.github.io # # This file is part of SickRage. # @@ -19,15 +18,17 @@ # along with SickRage. If not, see <http://www.gnu.org/licenses/>. import re -import traceback +from urllib import urlencode +from requests.utils import dict_from_cookiejar from sickbeard import logger from sickbeard import tvcache from sickbeard.bs4_parser import BS4Parser +from sickrage.helper.common import try_int, convert_size from sickrage.providers.torrent.TorrentProvider import TorrentProvider -class AlphaRatioProvider(TorrentProvider): +class AlphaRatioProvider(TorrentProvider): # pylint: disable=too-many-instance-attributes def __init__(self): @@ -39,25 +40,26 @@ class AlphaRatioProvider(TorrentProvider): self.minseed = None self.minleech = None - self.urls = {'base_url': 'http://alpharatio.cc/', - 'login': 'http://alpharatio.cc/login.php', - 'detail': 'http://alpharatio.cc/torrents.php?torrentid=%s', - 'search': 'http://alpharatio.cc/torrents.php?searchstr=%s%s', - 'download': 'http://alpharatio.cc/%s'} - - self.url = self.urls['base_url'] - - self.categories = "&filter_cat[1]=1&filter_cat[2]=1&filter_cat[3]=1&filter_cat[4]=1&filter_cat[5]=1" + self.url = 'http://alpharatio.cc/' + self.urls = { + 'login': self.url + 'login.php', + 'search': self.url + 'torrents.php', + } self.proper_strings = ['PROPER', 'REPACK'] self.cache = AlphaRatioCache(self) def login(self): - login_params = {'username': self.username, - 'password': self.password, - 'remember_me': 'on', - 'login': 'submit'} + if any(dict_from_cookiejar(self.session.cookies).values()): + return True + + login_params = { + 'username': self.username, + 'password': self.password, + 'remember_me': 'on', + 'login': 'submit' + } response = self.get_url(self.urls['login'], post_data=login_params, timeout=30) if not response: @@ -71,75 +73,89 @@ class AlphaRatioProvider(TorrentProvider): return True - def search(self, search_strings, age=0, ep_obj=None): - + def search(self, search_strings, age=0, ep_obj=None): # pylint: disable=too-many-locals, too-many-branches results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} - if not self.login(): return results - for mode in search_strings.keys(): + search_params = { + 'searchstr': '', + 'filter_cat[1]': 1, + 'filter_cat[2]': 1, + 'filter_cat[3]': 1, + 'filter_cat[4]': 1, + 'filter_cat[5]': 1 + } + + for mode in search_strings: + items = [] logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_strings[mode]: if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) - searchURL = self.urls['search'] % (search_string, self.categories) - logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) + search_params['searchstr'] = search_string + search_url = self.urls['search'] + '?' + urlencode(search_params) + logger.log(u"Search URL: %s" % search_url, logger.DEBUG) - data = self.get_url(searchURL) + data = self.get_url(search_url) if not data: continue - try: - with BS4Parser(data, 'html5lib') as html: - torrent_table = html.find('table', attrs={'id': 'torrent_table'}) - torrent_rows = torrent_table.find_all('tr') if torrent_table else [] - - # Continue only if one Release is found - if len(torrent_rows) < 2: - logger.log(u"Data returned from provider does not contain any torrents", logger.DEBUG) + with BS4Parser(data, 'html5lib') as html: + torrent_table = html.find('table', id='torrent_table') + torrent_rows = torrent_table.find_all('tr') if torrent_table else [] + + # Continue only if one Release is found + if len(torrent_rows) < 2: + logger.log(u"Data returned from provider does not contain any torrents", logger.DEBUG) + continue + + def process_column_header(td): + result = '' + if td.a and td.a.img: + result = td.a.img.get('title', td.a.get_text(strip=True)) + if not result: + result = td.get_text(strip=True) + return result + + # '', '', 'Name /Year', 'Files', 'Time', 'Size', 'Snatches', 'Seeders', 'Leechers' + labels = [process_column_header(label) for label in torrent_rows[0].find_all('td')] + + # Skip column headers + for result in torrent_rows[1:]: + cells = result.find_all('td') + if len(cells) < len(labels): continue - for result in torrent_rows[1:]: - cells = result.find_all('td') - link = result.find('a', attrs={'dir': 'ltr'}) - url = result.find('a', attrs={'title': 'Download'}) - - try: - title = link.contents[0] - download_url = self.urls['download'] % (url['href']) - seeders = cells[len(cells) - 2].contents[0] - leechers = cells[len(cells) - 1].contents[0] - # FIXME - size = -1 - except (AttributeError, TypeError): - continue - + try: + title = cells[labels.index('Name /Year')].find('a', dir='ltr').get_text(strip=True) + download_url = self.url + cells[labels.index('Name /Year')].find('a', title='Download')['href'] if not all([title, download_url]): continue - # Filter unseeded torrent + seeders = try_int(cells[labels.index('Seeders')].get_text(strip=True)) + leechers = try_int(cells[labels.index('Leechers')].get_text(strip=True)) if seeders < self.minseed or leechers < self.minleech: if mode != 'RSS': logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) continue + torrent_size = cells[labels.index('Size')].get_text(strip=True) + size = convert_size(torrent_size) or -1 + item = title, download_url, size, seeders, leechers if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) - items[mode].append(item) - - except Exception: - logger.log(u"Failed parsing provider. Traceback: %s" % traceback.format_exc(), logger.WARNING) + items.append(item) + except StandardError: + continue # For each search mode sort all the items by seeders if available - items[mode].sort(key=lambda tup: tup[3], reverse=True) - - results += items[mode] + items.sort(key=lambda tup: tup[3], reverse=True) + results += items return results diff --git a/sickbeard/providers/animenzb.py b/sickbeard/providers/animenzb.py deleted file mode 100644 index 77a5bffb92c6a6f640e6060d1a6a4d9745c823ee..0000000000000000000000000000000000000000 --- a/sickbeard/providers/animenzb.py +++ /dev/null @@ -1,127 +0,0 @@ -# coding=utf-8 - -# Author: Nic Wolfe <nic@wolfeden.ca> -# URL: http://code.google.com/p/sickbeard/ -# -# This file is part of Sick Beard. -# -# Sick Beard is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Sick Beard is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Sick Beard. If not, see <http://www.gnu.org/licenses/>. - -import urllib -import datetime - - -from sickbeard import classes -from sickbeard import show_name_helpers - -from sickbeard import logger - -from sickbeard import tvcache -from sickrage.providers.nzb.NZBProvider import NZBProvider - - -class animenzb(NZBProvider): - - def __init__(self): - - NZBProvider.__init__(self, "AnimeNZB") - - self.supports_backlog = False - self.public = True - self.supports_absolute_numbering = True - self.anime_only = True - - self.urls = {'base_url': 'http://animenzb.com/'} - self.url = self.urls['base_url'] - - self.cache = animenzbCache(self) - - def _get_season_search_strings(self, ep_obj): - return [x for x in show_name_helpers.makeSceneSeasonSearchString(self.show, ep_obj)] - - def _get_episode_search_strings(self, ep_obj, add_string=''): - return [x for x in show_name_helpers.makeSceneSearchString(self.show, ep_obj)] - - def search(self, search_string, age=0, ep_obj=None): - - logger.log(u"Search string: %s " % search_string, logger.DEBUG) - - if self.show and not self.show.is_anime: - return [] - - params = { - "cat": "anime", - "q": search_string.encode('utf-8'), - "max": "100" - } - - searchURL = self.url + "rss?" + urllib.urlencode(params) - logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) - results = [] - if 'entries' in self.cache.getRSSFeed(searchURL): - for curItem in self.cache.getRSSFeed(searchURL)['entries']: - (title, url) = self._get_title_and_url(curItem) - - if title and url: - results.append(curItem) - logger.log(u"Found result: %s " % title, logger.DEBUG) - - # For each search mode sort all the items by seeders if available if available - results.sort(key=lambda tup: tup[0], reverse=True) - - return results - - def find_propers(self, search_date=None): - - results = [] - - for item in self.search("v2|v3|v4|v5"): - - (title, url) = self._get_title_and_url(item) - - if 'published_parsed' in item and item['published_parsed']: - result_date = item.published_parsed - if result_date: - result_date = datetime.datetime(*result_date[0:6]) - else: - continue - - if not search_date or result_date > search_date: - search_result = classes.Proper(title, url, result_date, self.show) - results.append(search_result) - - return results - - -class animenzbCache(tvcache.TVCache): - - def __init__(self, provider_obj): - - tvcache.TVCache.__init__(self, provider_obj) - - # only poll animenzb every 20 minutes max - self.minTime = 20 - - def _getRSSData(self): - - params = { - "cat": "anime".encode('utf-8'), - "max": "100".encode('utf-8') - } - - rss_url = self.provider.url + 'rss?' + urllib.urlencode(params) - - return self.getRSSFeed(rss_url) - -provider = animenzb() diff --git a/sickbeard/providers/binsearch.py b/sickbeard/providers/binsearch.py index c0ea97c706d4e496143ed8235f6596441f768d0e..2be047ce6d0adee3cbaffbe984ac1eb264b1b32e 100644 --- a/sickbeard/providers/binsearch.py +++ b/sickbeard/providers/binsearch.py @@ -10,7 +10,7 @@ # # Sick Beard is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License diff --git a/sickbeard/providers/bitcannon.py b/sickbeard/providers/bitcannon.py index 12f4cdaf2d66c251b730af57956c0905962fb254..1c169e8b030548087be5787db1c923934846622c 100644 --- a/sickbeard/providers/bitcannon.py +++ b/sickbeard/providers/bitcannon.py @@ -11,21 +11,22 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import traceback from urllib import urlencode from sickbeard import logger from sickbeard import tvcache +from sickrage.helper.common import convert_size from sickrage.providers.torrent.TorrentProvider import TorrentProvider -class BitCannonProvider(TorrentProvider): +class BitCannonProvider(TorrentProvider): # pylint: disable=too-many-instance-attributes def __init__(self): TorrentProvider.__init__(self, "BitCannon") @@ -46,13 +47,12 @@ class BitCannonProvider(TorrentProvider): 'apiKey': '' } - def search(self, search_strings, age=0, ep_obj=None): + def search(self, search_strings, age=0, ep_obj=None): # pylint: disable=too-many-branches, too-many-statements, too-many-locals # search_strings comes in one of these formats: # {'Episode': ['Italian Works S05E10']} # {'Season': ['Italian Works S05']} # {'RSS': ['tv', 'anime']} results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} # select the correct category (TODO: Add more categories?) anime = (self.show and self.show.anime) or (ep_obj and ep_obj.show and ep_obj.show.anime) or False @@ -62,7 +62,8 @@ class BitCannonProvider(TorrentProvider): if self.api_key: self.search_params['apiKey'] = self.api_key - for mode in search_strings.keys(): + for mode in search_strings: + items = [] logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_strings[mode]: @@ -95,10 +96,11 @@ class BitCannonProvider(TorrentProvider): title = result.get('title', '') info_hash = result.get('infoHash', '') swarm = result.get('swarm', None) - size = int(result.get('size', 0)) if swarm is not None: seeders = int(swarm.get('seeders', 0)) leechers = int(swarm.get('leechers', 0)) + torrent_size = result.get('size', 0) + size = convert_size(torrent_size) or -1 download_url = "magnet:?xt=urn:btih:" + info_hash except (AttributeError, TypeError, KeyError, ValueError): @@ -113,14 +115,14 @@ class BitCannonProvider(TorrentProvider): if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) - items[mode].append(item) + items.append(item) except Exception: logger.log(u"Failed parsing provider. Traceback: %r" % traceback.format_exc(), logger.ERROR) # For each search mode sort all the items by seeders if available - items[mode].sort(key=lambda tup: tup[3], reverse=True) - results += items[mode] + items.sort(key=lambda tup: tup[3], reverse=True) + results += items return results diff --git a/sickbeard/providers/bitsnoop.py b/sickbeard/providers/bitsnoop.py index 5e89e174debd1c478522c4f23be6a953f3d850f0..545ca65c194eb4ceebea2ff88c5abba1b03060f8 100644 --- a/sickbeard/providers/bitsnoop.py +++ b/sickbeard/providers/bitsnoop.py @@ -10,11 +10,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import traceback from bs4 import BeautifulSoup @@ -22,7 +22,7 @@ from bs4 import BeautifulSoup import sickbeard from sickbeard import logger from sickbeard import tvcache -from sickrage.helper.common import try_int +from sickrage.helper.common import try_int, convert_size from sickrage.providers.torrent.TorrentProvider import TorrentProvider @@ -48,11 +48,9 @@ class BitSnoopProvider(TorrentProvider): # pylint: disable=too-many-instance-at self.cache = BitSnoopCache(self) def search(self, search_strings, age=0, ep_obj=None): # pylint: disable=too-many-branches,too-many-locals - results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} - - for mode in search_strings.keys(): + for mode in search_strings: + items = [] logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_strings[mode]: @@ -71,10 +69,7 @@ class BitSnoopProvider(TorrentProvider): # pylint: disable=too-many-instance-at continue data = BeautifulSoup(data, 'html5lib') - - entries = entries = data.findAll('item') - - for item in entries: + for item in data.findAll('item'): try: if not item.category.text.endswith(('TV', 'Anime')): continue @@ -92,9 +87,10 @@ class BitSnoopProvider(TorrentProvider): # pylint: disable=too-many-instance-at if not (title and download_url): continue - seeders = try_int(item.find('numseeders').text, 0) - leechers = try_int(item.find('numleechers').text, 0) - size = try_int(item.find('size').text, -1) + seeders = try_int(item.find('numseeders').text) + leechers = try_int(item.find('numleechers').text) + torrent_size = item.find('size').text + size = convert_size(torrent_size) or -1 info_hash = item.find('infohash').text @@ -111,15 +107,14 @@ class BitSnoopProvider(TorrentProvider): # pylint: disable=too-many-instance-at if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) - items[mode].append(item) + items.append(item) except (AttributeError, TypeError, KeyError, ValueError): logger.log(u"Failed parsing provider. Traceback: %r" % traceback.format_exc(), logger.ERROR) # For each search mode sort all the items by seeders if available - items[mode].sort(key=lambda tup: tup[3], reverse=True) - - results += items[mode] + items.sort(key=lambda tup: tup[3], reverse=True) + results += items return results diff --git a/sickbeard/providers/bitsoup.py b/sickbeard/providers/bitsoup.py index 639bc49bf2eb64e2547e9d89bf67b878d2c4c0ae..92bc89c22d7f1cdb4f5824772738d224e79e784d 100644 --- a/sickbeard/providers/bitsoup.py +++ b/sickbeard/providers/bitsoup.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import re import traceback @@ -23,10 +23,11 @@ import traceback from sickbeard import logger from sickbeard import tvcache from sickbeard.bs4_parser import BS4Parser +from sickrage.helper.common import convert_size from sickrage.providers.torrent.TorrentProvider import TorrentProvider -class BitSoupProvider(TorrentProvider): +class BitSoupProvider(TorrentProvider): # pylint: disable=too-many-instance-attributes def __init__(self): TorrentProvider.__init__(self, "BitSoup") @@ -77,18 +78,15 @@ class BitSoupProvider(TorrentProvider): return True - def search(self, search_strings, age=0, ep_obj=None): - + def search(self, search_strings, age=0, ep_obj=None): # pylint: disable=too-many-locals results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} - if not self.login(): return results - for mode in search_strings.keys(): + for mode in search_strings: + items = [] logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_strings[mode]: - if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) @@ -119,9 +117,7 @@ class BitSoupProvider(TorrentProvider): seeders = int(cells[10].getText().replace(',', '')) leechers = int(cells[11].getText().replace(',', '')) torrent_size = cells[8].getText() - size = -1 - if re.match(r"\d+([,\.]\d+)?\s*[KkMmGgTt]?[Bb]", torrent_size): - size = self._convertSize(torrent_size.rstrip()) + size = convert_size(torrent_size) or -1 except (AttributeError, TypeError): continue @@ -141,38 +137,21 @@ class BitSoupProvider(TorrentProvider): if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) - items[mode].append(item) + items.append(item) except Exception: logger.log(u"Failed parsing provider. Traceback: %s" % traceback.format_exc(), logger.WARNING) # For each search mode sort all the items by seeders if available - items[mode].sort(key=lambda tup: tup[3], reverse=True) + items.sort(key=lambda tup: tup[3], reverse=True) - results += items[mode] + results += items return results def seed_ratio(self): return self.ratio - def _convertSize(self, sizeString): - size = sizeString[:-2].strip() - modifier = sizeString[-2:].upper() - try: - size = float(size) - if modifier in 'KB': - size *= 1024 ** 1 - elif modifier in 'MB': - size *= 1024 ** 2 - elif modifier in 'GB': - size *= 1024 ** 3 - elif modifier in 'TB': - size *= 1024 ** 4 - except Exception: - size = -1 - return long(size) - class BitSoupCache(tvcache.TVCache): def __init__(self, provider_obj): diff --git a/sickbeard/providers/bluetigers.py b/sickbeard/providers/bluetigers.py index 5a7da1f3d660e6febd64706d862cce78a5a8836b..0ff1201fb7a44511fa3ab3a6a8e1ba51df55cac2 100644 --- a/sickbeard/providers/bluetigers.py +++ b/sickbeard/providers/bluetigers.py @@ -11,7 +11,7 @@ # # Sick Beard is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License @@ -21,7 +21,6 @@ import traceback import requests import re -from requests.auth import AuthBase from sickbeard.bs4_parser import BS4Parser from sickbeard import logger @@ -29,7 +28,7 @@ from sickbeard import tvcache from sickrage.providers.torrent.TorrentProvider import TorrentProvider -class BLUETIGERSProvider(TorrentProvider): +class BlueTigersProvider(TorrentProvider): # pylint: disable=too-many-instance-attributes def __init__(self): TorrentProvider.__init__(self, "BLUETIGERS") @@ -38,7 +37,7 @@ class BLUETIGERSProvider(TorrentProvider): self.ratio = None self.token = None - self.cache = BLUETIGERSCache(self) + self.cache = BlueTigersCache(self) self.urls = { 'base_url': 'https://www.bluetigers.ca/', @@ -79,15 +78,13 @@ class BLUETIGERSProvider(TorrentProvider): return True - def search(self, search_strings, age=0, ep_obj=None): - + def search(self, search_strings, age=0, ep_obj=None): # pylint: disable=too-many-locals results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} - if not self.login(): return results - for mode in search_strings.keys(): + for mode in search_strings: + items = [] logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_strings[mode]: @@ -131,15 +128,15 @@ class BLUETIGERSProvider(TorrentProvider): if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) - items[mode].append(item) + items.append(item) - except Exception as e: + except Exception: logger.log(u"Failed parsing provider. Traceback: %s" % traceback.format_exc(), logger.ERROR) # For each search mode sort all the items by seeders if available - items[mode].sort(key=lambda tup: tup[3], reverse=True) + items.sort(key=lambda tup: tup[3], reverse=True) - results += items[mode] + results += items return results @@ -147,17 +144,7 @@ class BLUETIGERSProvider(TorrentProvider): return self.ratio -class BLUETIGERSAuth(AuthBase): - """Attaches HTTP Authentication to the given Request object.""" - def __init__(self, token): - self.token = token - - def __call__(self, r): - r.headers['Authorization'] = self.token - return r - - -class BLUETIGERSCache(tvcache.TVCache): +class BlueTigersCache(tvcache.TVCache): def __init__(self, provider_obj): tvcache.TVCache.__init__(self, provider_obj) @@ -169,4 +156,4 @@ class BLUETIGERSCache(tvcache.TVCache): return {'entries': self.provider.search(search_strings)} -provider = BLUETIGERSProvider() +provider = BlueTigersProvider() diff --git a/sickbeard/providers/btdigg.py b/sickbeard/providers/btdigg.py index 099b7116b2f3467e8a36ba51c16b7668813240b0..2b3db677319d07e4f0df5ca0a5dcb291530732cb 100644 --- a/sickbeard/providers/btdigg.py +++ b/sickbeard/providers/btdigg.py @@ -13,19 +13,21 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +import traceback from urllib import urlencode from sickbeard import logger from sickbeard import tvcache +from sickrage.helper.common import convert_size from sickrage.providers.torrent.TorrentProvider import TorrentProvider -class BTDIGGProvider(TorrentProvider): +class BTDiggProvider(TorrentProvider): def __init__(self): TorrentProvider.__init__(self, "BTDigg") @@ -45,13 +47,12 @@ class BTDIGGProvider(TorrentProvider): self.cache = BTDiggCache(self) - def search(self, search_strings, age=0, ep_obj=None): - + def search(self, search_strings, age=0, ep_obj=None): # pylint: disable=too-many-locals results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} search_params = {'p': 0} for mode in search_strings: + items = [] logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_strings[mode]: search_params['q'] = search_string.encode('utf-8') @@ -70,45 +71,52 @@ class BTDIGGProvider(TorrentProvider): logger.log(u"No data returned to be parsed!!!", logger.DEBUG) continue - for torrent in jdata: - if not torrent['name']: - logger.log(u"Ignoring result since it has no name", logger.DEBUG) - continue + try: + + for torrent in jdata: + if not torrent['name']: + logger.log(u"Ignoring result since it has no name", logger.DEBUG) + continue + + if torrent['ff']: + logger.log(u"Ignoring result for %s since it's a fake (level = %s)" % (torrent['name'], torrent['ff']), logger.DEBUG) + continue - if torrent['ff']: - logger.log(u"Ignoring result for %s since it's a fake (level = %s)" % (torrent['name'], torrent['ff']), logger.DEBUG) - continue + if not torrent['files']: + logger.log(u"Ignoring result for %s without files" % torrent['name'], logger.DEBUG) + continue - if not torrent['files']: - logger.log(u"Ignoring result for %s without files" % torrent['name'], logger.DEBUG) - continue + download_url = torrent['magnet'] + self._custom_trackers if torrent['magnet'] else None - download_url = torrent['magnet'] + self._custom_trackers + # Provider doesn't provide seeders/leechers + seeders = 1 + leechers = 0 + title = torrent['name'] + torrent_size = torrent['size'] + size = convert_size(torrent_size) or -1 - if not download_url: - logger.log(u"Ignoring result for %s without a url" % torrent['name'], logger.DEBUG) - continue + if not all([title, download_url]): + continue - # FIXME - seeders = 1 - leechers = 0 + # Filter unseeded torrent (Unsupported) + #if seeders < self.minseed or leechers < self.minleech: + # if mode != 'RSS': + # logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) + # continue - # # Filter unseeded torrent (Unsupported) - # if seeders < self.minseed or leechers < self.minleech: - # if mode != 'RSS': - # logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) - # continue + item = title, download_url, size, seeders, leechers + if mode != 'RSS': + logger.log(u"Found result: %s " % title, logger.DEBUG) - if mode != 'RSS': - logger.log(u"Found result: %s" % torrent['name'], logger.DEBUG) + items.append(item) - item = torrent['name'], download_url, torrent['size'], seeders, leechers - items[mode].append(item) + except Exception: + logger.log(u"Failed parsing provider. Traceback: %s" % traceback.format_exc(), logger.WARNING) - # # For each search mode sort all the items by seeders if available (Unsupported) - # items[mode].sort(key=lambda tup: tup[3], reverse=True) + # For each search mode sort all the items by seeders if available + #items.sort(key=lambda tup: tup[3], reverse=True) - results += items[mode] + results += items return results @@ -130,4 +138,4 @@ class BTDiggCache(tvcache.TVCache): search_params = {'RSS': ['x264', 'x264.HDTV', '720.HDTV.x264']} return {'entries': self.provider.search(search_params)} -provider = BTDIGGProvider() +provider = BTDiggProvider() diff --git a/sickbeard/providers/btn.py b/sickbeard/providers/btn.py index f648fefc8019acd2f8bc887239c0a459b37b54cb..6384558798dfaee6d5d4bf5b8535dfebbe774588 100644 --- a/sickbeard/providers/btn.py +++ b/sickbeard/providers/btn.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import time import socket diff --git a/sickbeard/providers/cpasbien.py b/sickbeard/providers/cpasbien.py index 83ed1e9349a8fb4cdef3740266ff228e2316b337..b4f509d356a2b02ff91832078c6132685de1b860 100644 --- a/sickbeard/providers/cpasbien.py +++ b/sickbeard/providers/cpasbien.py @@ -11,17 +11,16 @@ # # Sick Beard is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Sick Beard. If not, see <http://www.gnu.org/licenses/>. - -import traceback +import re from sickbeard import logger from sickbeard import tvcache -from sickrage.helper.common import try_int +from sickrage.helper.common import try_int, convert_size from sickbeard.bs4_parser import BS4Parser from sickrage.providers.torrent.TorrentProvider import TorrentProvider @@ -39,102 +38,65 @@ class CpasbienProvider(TorrentProvider): self.url = "http://www.cpasbien.io" self.proper_strings = ['PROPER', 'REPACK'] - self.cache = CpasbienCache(self) - def search(self, search_params, age=0, ep_obj=None): # pylint: disable=too-many-locals, too-many-statements, too-many-branches - + def search(self, search_strings, age=0, ep_obj=None): # pylint: disable=too-many-locals results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} - - for mode in search_params.keys(): + for mode in search_strings: + items = [] logger.log(u"Search Mode: %s" % mode, logger.DEBUG) - for search_string in search_params[mode]: + for search_string in search_strings[mode]: if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) - searchURL = self.url + '/recherche/' + search_string.replace('.', '-').replace(' ', '-') + '.html' + search_url = self.url + '/recherche/' + search_string.replace('.', '-').replace(' ', '-') + '.html,trie-seeds-d' else: - searchURL = self.url + '/view_cat.php?categorie=series&trie=date-d' - - logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) - data = self.get_url(searchURL) + search_url = self.url + '/view_cat.php?categorie=series&trie=date-d' + logger.log(u"Search URL: %s" % search_url, logger.DEBUG) + data = self.get_url(search_url) if not data: continue - try: - with BS4Parser(data, 'html5lib') as html: - line = 0 - torrents = [] - while True: - resultlin = html.findAll(class_='ligne%i' % line) - if not resultlin: - break - - torrents += resultlin - line += 1 - - for torrent in torrents: - try: - title = torrent.find(class_="titre").get_text(strip=True).replace("HDTV", "HDTV x264-CPasBien") - tmp = torrent.find("a")['href'].split('/')[-1].replace('.html', '.torrent').strip() - download_url = (self.url + '/telechargement/%s' % tmp) - size = self._convertSize(torrent.find(class_="poid").get_text(strip=True)) - seeders = try_int(torrent.find(class_="up").get_text(strip=True)) - leechers = try_int(torrent.find(class_="down").get_text(strip=True)) - except (AttributeError, TypeError, KeyError, IndexError): - continue - + with BS4Parser(data, 'html5lib') as html: + torrent_rows = html.find_all(class_=re.compile('ligne[01]')) + for result in torrent_rows: + try: + title = result.find(class_="titre").get_text(strip=True).replace("HDTV", "HDTV x264-CPasBien") + tmp = result.find("a")['href'].split('/')[-1].replace('.html', '.torrent').strip() + download_url = (self.url + '/telechargement/%s' % tmp) if not all([title, download_url]): continue - # Filter unseeded torrent + seeders = try_int(result.find(class_="up").get_text(strip=True)) + leechers = try_int(result.find(class_="down").get_text(strip=True)) if seeders < self.minseed or leechers < self.minleech: if mode != 'RSS': logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) continue + torrent_size = result.find(class_="poid").get_text(strip=True) + + units = ['o', 'Ko', 'Mo', 'Go', 'To', 'Po'] + size = convert_size(torrent_size, units=units) or -1 + item = title, download_url, size, seeders, leechers if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) - items[mode].append(item) - - except Exception: - logger.log(u"Failed parsing provider. Traceback: %s" % traceback.format_exc(), logger.ERROR) + items.append(item) + except StandardError: + continue # For each search mode sort all the items by seeders if available - items[mode].sort(key=lambda tup: tup[3], reverse=True) - - results += items[mode] + items.sort(key=lambda tup: tup[3], reverse=True) + results += items return results def seed_ratio(self): return self.ratio - @staticmethod - def _convertSize(sizeString): - size = sizeString[:-2].strip() - modifier = sizeString[-2:].upper() - try: - size = float(size) - if modifier in 'KO': - size *= 1024 ** 1 - elif modifier in 'MO': - size *= 1024 ** 2 - elif modifier in 'GO': - size *= 1024 ** 3 - elif modifier in 'TO': - size *= 1024 ** 4 - else: - raise - except Exception: - size = -1 - - return long(size) - class CpasbienCache(tvcache.TVCache): def __init__(self, provider_obj): diff --git a/sickbeard/providers/danishbits.py b/sickbeard/providers/danishbits.py index b531cbb4d5992375783aef8c11aadeb6cbe26f1c..63573ae6053c1f3fbc8d6c03744f4077de298383 100644 --- a/sickbeard/providers/danishbits.py +++ b/sickbeard/providers/danishbits.py @@ -1,6 +1,6 @@ # coding=utf-8 -# Author: seedboy -# URL: https://github.com/seedboy +# Author: Dustyn Gibson <miigotu@gmail.com> +# URL: https://sickrage.github.io # # This file is part of SickRage. # @@ -11,19 +11,18 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. -import traceback -import urllib -import time -import re +from urllib import urlencode +from requests.utils import dict_from_cookiejar from sickbeard import logger from sickbeard import tvcache +from sickrage.helper.common import try_int, convert_size from sickrage.providers.torrent.TorrentProvider import TorrentProvider from sickbeard.bs4_parser import BS4Parser @@ -41,165 +40,129 @@ class DanishbitsProvider(TorrentProvider): # pylint: disable=too-many-instance- self.cache = DanishbitsCache(self) - self.urls = {'base_url': 'https://danishbits.org/', - 'search': 'https://danishbits.org/torrents.php?action=newbrowse&search=%s%s', - 'login_page': 'https://danishbits.org/login.php'} - - self.url = self.urls['base_url'] - - self.categories = '&group=3' - - self.last_login_check = None - - self.login_opener = None + self.url = 'https://danishbits.org/' + self.urls = { + 'login': self.url + 'login.php', + 'search': self.url + 'torrents.php', + } self.minseed = 0 self.minleech = 0 self.freeleech = True - @staticmethod - def loginSuccess(output): - if not output or "<title>Login :: Danishbits.org</title>" in output: - return False - else: - return True - def login(self): - - now = time.time() - if self.login_opener and self.last_login_check < (now - 3600): - try: - output = self.get_url(self.urls['test']) - if self.loginSuccess(output): - self.last_login_check = now - return True - else: - self.login_opener = None - except Exception: - self.login_opener = None - - if self.login_opener: + if any(dict_from_cookiejar(self.session.cookies).values()): return True - try: - data = self.get_url(self.urls['login_page']) - if not data: - return False - - login_params = { - 'username': self.username, - 'password': self.password, - } - output = self.get_url(self.urls['login_page'], post_data=login_params) - if self.loginSuccess(output): - self.last_login_check = now - self.login_opener = self.session - return True - - error = 'unknown' - except Exception: - error = traceback.format_exc() - self.login_opener = None + login_params = { + 'langlang': '', + 'username': self.username.encode('utf-8'), + 'password': self.password.encode('utf-8'), + 'keeplogged': 1, + 'login': 'Login' + } + + response = self.get_url(self.urls['login'], post_data=login_params, timeout=30) + if not response: + logger.log(u"Unable to connect to provider", logger.WARNING) + self.session.cookies.clear() + return False - self.login_opener = None - logger.log(u"Failed to login: %s" % error, logger.ERROR) - return False + if '<title>Login :: Danishbits.org</title>' in response: + logger.log(u"Invalid username or password. Check your settings", logger.WARNING) + self.session.cookies.clear() + return False - def search(self, search_params, age=0, ep_obj=None): # pylint: disable=too-many-branches,too-many-locals + return True + def search(self, search_strings, age=0, ep_obj=None): # pylint: disable=too-many-branches,too-many-locals results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} - if not self.login(): return results - for mode in search_params.keys(): - logger.log(u"Search Mode: %s" % mode, logger.DEBUG) - for search_string in search_params[mode]: - if mode == 'RSS': - continue + search_params = { + 'action': 'newbrowse', + 'group': 3, + 'search': '', + } + for mode in search_strings: + items = [] + logger.log(u"Search Mode: %s" % mode, logger.DEBUG) + for search_string in search_strings[mode]: if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) - searchURL = self.urls['search'] % (urllib.quote(search_string.encode('utf-8')), self.categories) - logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) - data = self.get_url(searchURL) - if not data: - continue - - try: - with BS4Parser(data.decode('iso-8859-1'), features=["html5lib", "permissive"]) as html: - # Collecting entries - entries = html.find_all('tr', attrs={'class': 'torrent'}) - - # Xirg STANDARD TORRENTS - # Continue only if one Release is found - if not entries: - logger.log(u"Data returned from provider does not contain any torrents", logger.DEBUG) - continue + search_params['search'] = search_string - for result in entries: + search_url = "%s?%s" % (self.urls['search'], urlencode(search_params)) + logger.log(u"Search URL: %s" % search_url, logger.DEBUG) - # try: - title = result.find('div', attrs={'class': 'croptorrenttext'}).find('b').text - download_url = self.urls['base_url'] + result.find('span', attrs={'class': 'right'}).find('a')['href'] - seeders = int(result.find_all('td')[6].text) - leechers = int(result.find_all('td')[7].text) - size = self._convertSize(result.find_all('td')[2].text) - freeleech = result.find('span', class_='freeleech') - # except (AttributeError, TypeError, KeyError): - # logger.log(u"attrErr: {0}, tErr: {1}, kErr: {2}".format(AttributeError, TypeError, KeyError), logger.DEBUG) - # continue - - if self.freeleech and not freeleech: - continue + # returns top 15 results by default, expandable in user profile to 100 + data = self.get_url(search_url) + if not data: + continue + with BS4Parser(data, 'html5lib') as html: + torrent_table = html.find('table', class_='torrent_table') + torrent_rows = torrent_table.find_all('tr') if torrent_table else [] + + # Continue only if at least one Release is found + if len(torrent_rows) < 2: + logger.log(u"Data returned from provider does not contain any torrents", logger.DEBUG) + continue + + def process_column_header(td): + result = '' + if td.img: + result = td.img.get('title') + if not result: + result = td.get_text(strip=True) + return result.encode('utf-8') + + # Literal: Navn, Størrelse, Kommentarer, Tilføjet, Snatches, Seeders, Leechers + # Translation: Name, Size, Comments, Added, Snatches, Seeders, Leechers + labels = [process_column_header(label) for label in torrent_rows[0].find_all('td')] + + for result in torrent_rows[1:]: + try: + title = result.find(class_='croptorrenttext').get_text(strip=True) + download_url = self.url + result.find(title="Direkte download link")['href'] if not all([title, download_url]): continue - # Filter unseeded torrent + cells = result.find_all('td') + + seeders = try_int(cells[labels.index('Seeders')].get_text(strip=True)) + leechers = try_int(cells[labels.index('Leechers')].get_text(strip=True)) if seeders < self.minseed or leechers < self.minleech: if mode != 'RSS': logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) continue + freeleech = result.find(class_='freeleech') + if self.freeleech and not freeleech: + continue + + torrent_size = cells[labels.index('Størrelse')].contents[0] + size = convert_size(torrent_size) or -1 + item = title, download_url, size, seeders, leechers if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) - items[mode].append(item) + items.append(item) - except Exception: - logger.log(u"Failed parsing provider. Traceback: %s" % traceback.format_exc(), logger.ERROR) + except StandardError: + continue # For each search mode sort all the items by seeders if available - items[mode].sort(key=lambda tup: tup[3], reverse=True) + items.sort(key=lambda tup: tup[3], reverse=True) - results += items[mode] + results += items return results - @staticmethod - def _convertSize(size): - regex = re.compile(r'(.+?\w{2})\d+ file\w') - m = regex.match(size) - size = m.group(1) - - size, modifier = size[:-2], size[-2:] - size = size.replace(',', '') # strip commas from comma separated values - - size = float(size) - if modifier in 'KB': - size *= 1024 ** 1 - elif modifier in 'MB': - size *= 1024 ** 2 - elif modifier in 'GB': - size *= 1024 ** 3 - elif modifier in 'TB': - size *= 1024 ** 4 - return long(size) - def seedRatio(self): return self.ratio @@ -213,8 +176,8 @@ class DanishbitsCache(tvcache.TVCache): self.minTime = 10 def _getRSSData(self): - search_params = {'RSS': ['']} - return {'entries': self.provider.search(search_params)} + search_strings = {'RSS': ['']} + return {'entries': self.provider.search(search_strings)} provider = DanishbitsProvider() diff --git a/sickbeard/providers/elitetorrent.py b/sickbeard/providers/elitetorrent.py index 984e84a24781c3522ef8a384204a4a1233215f6d..a267c8dc5d281204582e8750048a618244d9132c 100644 --- a/sickbeard/providers/elitetorrent.py +++ b/sickbeard/providers/elitetorrent.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import traceback import re @@ -27,6 +27,8 @@ from sickbeard import tvcache from sickbeard.bs4_parser import BS4Parser from sickrage.providers.torrent.TorrentProvider import TorrentProvider +from sickrage.helper.common import try_int + class elitetorrentProvider(TorrentProvider): def __init__(self): @@ -65,14 +67,12 @@ class elitetorrentProvider(TorrentProvider): } - def search(self, search_strings, age=0, ep_obj=None): - + def search(self, search_strings, age=0, ep_obj=None): # pylint: disable=too-many-locals, too-many-branches results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} - lang_info = '' if not ep_obj or not ep_obj.show else ep_obj.show.lang - for mode in search_strings.keys(): + for mode in search_strings: + items = [] logger.log(u"Search Mode: %s" % mode, logger.DEBUG) # Only search if user conditions are true @@ -87,41 +87,36 @@ class elitetorrentProvider(TorrentProvider): search_string = re.sub(r'S0*(\d*)E(\d*)', r'\1x\2', search_string) self.search_params['buscar'] = search_string.strip() if mode != 'RSS' else '' - searchURL = self.urls['search'] + '?' + urllib.parse.urlencode(self.search_params) - logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) - - data = self.get_url(searchURL, timeout=30) + search_url = self.urls['search'] + '?' + urllib.parse.urlencode(self.search_params) + logger.log(u"Search URL: %s" % search_url, logger.DEBUG) + data = self.get_url(search_url, timeout=30) if not data: continue try: with BS4Parser(data, 'html5lib') as html: torrent_table = html.find('table', class_='fichas-listado') + torrent_rows = [] - if torrent_table is None: - logger.log(u"Data returned from provider does not contain any torrents", logger.DEBUG) - continue + if torrent_table is not None: + torrent_rows = torrent_table.findAll('tr') - torrent_rows = torrent_table.findAll('tr') - if torrent_rows is None: - logger.log(u"Torrent table does not have any rows", logger.DEBUG) + if not torrent_rows: + logger.log(u"Data returned from provider does not contain any torrents", logger.DEBUG) continue for row in torrent_rows[1:]: try: - seeders_raw = row.find('td', class_='semillas').text - leechers_raw = row.find('td', class_='clientes').text - - download_url = self.urls['base_url'] + row.findAll('a')[0].get('href', '') - title = self._processTitle(row.findAll('a')[1].text) - seeders = seeders_raw if seeders_raw.isnumeric() else 0 - leechers = leechers_raw if leechers_raw.isnumeric() else 0 + download_url = self.urls['base_url'] + row.find('a')['href'] + title = self._processTitle(row.find('a', class_='nombre')['title']) + seeders = try_int(row.find('td', class_='semillas').get_text(strip=True)) + leechers = try_int(row.find('td', class_='clientes').get_text(strip=True)) - # FIXME: Provider does not provide size - size = 0 + # Provider does not provide size + size = -1 - except (AttributeError, TypeError): + except (AttributeError, TypeError, KeyError, ValueError): continue if not all([title, download_url]): @@ -137,15 +132,15 @@ class elitetorrentProvider(TorrentProvider): if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) - items[mode].append(item) + items.append(item) except Exception: logger.log(u"Failed parsing provider. Traceback: %s" % traceback.format_exc(), logger.WARNING) # For each search mode sort all the items by seeders if available - items[mode].sort(key=lambda tup: tup[3], reverse=True) + items.sort(key=lambda tup: tup[3], reverse=True) - results += items[mode] + results += items return results diff --git a/sickbeard/providers/extratorrent.py b/sickbeard/providers/extratorrent.py index 73bade79050e843b7be96989e2b5ed81a846e2fc..868c684834216503a8c40a183006115c1eb87030 100644 --- a/sickbeard/providers/extratorrent.py +++ b/sickbeard/providers/extratorrent.py @@ -11,19 +11,18 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import re -import traceback import sickbeard from sickbeard import logger from sickbeard import tvcache from sickbeard.common import USER_AGENT -from sickrage.helper.common import try_int +from sickrage.helper.common import try_int, convert_size from sickbeard.bs4_parser import BS4Parser from sickrage.providers.torrent.TorrentProvider import TorrentProvider @@ -50,11 +49,9 @@ class ExtraTorrentProvider(TorrentProvider): # pylint: disable=too-many-instanc self.search_params = {'cid': 8} def search(self, search_strings, age=0, ep_obj=None): # pylint: disable=too-many-locals, too-many-branches - results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} - - for mode in search_strings.keys(): + for mode in search_strings: + items = [] logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_strings[mode]: if mode != 'RSS': @@ -75,9 +72,10 @@ class ExtraTorrentProvider(TorrentProvider): # pylint: disable=too-many-instanc for item in parser.findAll('item'): try: title = re.sub(r'^<!\[CDATA\[|\]\]>$', '', item.find('title').get_text(strip=True)) - size = try_int(item.find('size').get_text(strip=True), -1) if item.find('size') else -1 - seeders = try_int(item.find('seeders').get_text(strip=True)) if item.find('seeders') else 0 - leechers = try_int(item.find('leechers').get_text(strip=True)) if item.find('leechers') else 0 + seeders = try_int(item.find('seeders').get_text(strip=True)) + leechers = try_int(item.find('leechers').get_text(strip=True)) + torrent_size = item.find('size').get_text() + size = convert_size(torrent_size) or -1 if sickbeard.TORRENT_METHOD == 'blackhole': enclosure = item.find('enclosure') # Backlog doesnt have enclosure @@ -87,28 +85,27 @@ class ExtraTorrentProvider(TorrentProvider): # pylint: disable=too-many-instanc info_hash = item.find('info_hash').get_text(strip=True) download_url = "magnet:?xt=urn:btih:" + info_hash + "&dn=" + title + self._custom_trackers - if not all([title, download_url]): - continue + except (AttributeError, TypeError, KeyError, ValueError): + continue - # Filter unseeded torrent - if seeders < self.minseed or leechers < self.minleech: - if mode != 'RSS': - logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) - continue + if not all([title, download_url]): + continue - item = title, download_url, size, seeders, leechers + # Filter unseeded torrent + if seeders < self.minseed or leechers < self.minleech: if mode != 'RSS': - logger.log(u"Found result: %s " % title, logger.DEBUG) + logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) + continue - items[mode].append(item) + item = title, download_url, size, seeders, leechers + if mode != 'RSS': + logger.log(u"Found result: %s " % title, logger.DEBUG) - except (AttributeError, TypeError, KeyError, ValueError): - logger.log(u"Failed parsing provider. Traceback: %r" % traceback.format_exc(), logger.WARNING) + items.append(item) # For each search mode sort all the items by seeders if available - items[mode].sort(key=lambda tup: tup[3], reverse=True) - - results += items[mode] + items.sort(key=lambda tup: tup[3], reverse=True) + results += items return results diff --git a/sickbeard/providers/fnt.py b/sickbeard/providers/fnt.py index 21cda3b43ca2d12c5348d9eca3455fd548777a25..338afbb69221efa93227a5b09e47f167484ddcb1 100644 --- a/sickbeard/providers/fnt.py +++ b/sickbeard/providers/fnt.py @@ -11,7 +11,7 @@ # # Sick Beard is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License @@ -27,7 +27,7 @@ from sickbeard.bs4_parser import BS4Parser from sickrage.providers.torrent.TorrentProvider import TorrentProvider -class FNTProvider(TorrentProvider): +class FNTProvider(TorrentProvider): # pylint: disable=too-many-instance-attributes def __init__(self): TorrentProvider.__init__(self, "FNT") @@ -75,16 +75,13 @@ class FNTProvider(TorrentProvider): logger.log(u"Invalid username or password. Check your settings", logger.WARNING) return False - def search(self, search_strings, age=0, ep_obj=None): - + def search(self, search_strings, age=0, ep_obj=None): # pylint: disable=too-many-locals, too-many-branches results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} - - # check for auth if not self.login(): return results - for mode in search_strings.keys(): + for mode in search_strings: + items = [] logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_strings[mode]: @@ -141,15 +138,14 @@ class FNTProvider(TorrentProvider): if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) - items[mode].append(item) + items.append(item) - except Exception as e: + except Exception: logger.log(u"Failed parsing provider. Traceback: %s" % traceback.format_exc(), logger.ERROR) # For each search mode sort all the items by seeders if available - items[mode].sort(key=lambda tup: tup[3], reverse=True) - - results += items[mode] + items.sort(key=lambda tup: tup[3], reverse=True) + results += items return results diff --git a/sickbeard/providers/freshontv.py b/sickbeard/providers/freshontv.py index 4ff0277dc7773a01eb854bf1a074631b8415902c..3efefdf0f1655ced937eed5a4c7bb8b95a10048d 100644 --- a/sickbeard/providers/freshontv.py +++ b/sickbeard/providers/freshontv.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import re import time @@ -25,11 +25,11 @@ import traceback from sickbeard import logger from sickbeard import tvcache from sickbeard.bs4_parser import BS4Parser -from sickrage.helper.common import try_int +from sickrage.helper.common import try_int, convert_size from sickrage.providers.torrent.TorrentProvider import TorrentProvider -class FreshOnTVProvider(TorrentProvider): +class FreshOnTVProvider(TorrentProvider): # pylint: disable=too-many-instance-attributes def __init__(self): TorrentProvider.__init__(self, "FreshOnTV") @@ -101,26 +101,24 @@ class FreshOnTVProvider(TorrentProvider): return False - def search(self, search_params, age=0, ep_obj=None): - + def search(self, search_params, age=0, ep_obj=None): # pylint: disable=too-many-locals, too-many-branches, too-many-statements results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} - - freeleech = '3' if self.freeleech else '0' - if not self.login(): return results - for mode in search_params.keys(): + freeleech = '3' if self.freeleech else '0' + + for mode in search_params: + items = [] logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_params[mode]: if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) - searchURL = self.urls['search'] % (freeleech, search_string) - logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) - init_html = self.get_url(searchURL) + search_url = self.urls['search'] % (freeleech, search_string) + logger.log(u"Search URL: %s" % search_url, logger.DEBUG) + init_html = self.get_url(search_url) max_page_number = 0 if not init_html: @@ -162,9 +160,9 @@ class FreshOnTVProvider(TorrentProvider): for i in range(1, max_page_number): time.sleep(1) - page_searchURL = searchURL + '&page=' + str(i) - # '.log(u"Search string: " + page_searchURL, logger.DEBUG) - page_html = self.get_url(page_searchURL) + page_search_url = search_url + '&page=' + str(i) + # '.log(u"Search string: " + page_search_url, logger.DEBUG) + page_html = self.get_url(page_search_url) if not page_html: continue @@ -202,8 +200,8 @@ class FreshOnTVProvider(TorrentProvider): download_url = self.urls['download'] % (str(torrent_id)) seeders = try_int(individual_torrent.find('td', {'class': 'table_seeders'}).find('span').text.strip(), 1) leechers = try_int(individual_torrent.find('td', {'class': 'table_leechers'}).find('a').text.strip(), 0) - # FIXME - size = -1 + torrent_size = individual_torrent.find('td', {'class': 'table_size'}).get_text() + size = convert_size(torrent_size) or -1 except Exception: continue @@ -220,15 +218,14 @@ class FreshOnTVProvider(TorrentProvider): if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) - items[mode].append(item) + items.append(item) except Exception: logger.log(u"Failed parsing provider. Traceback: %s" % traceback.format_exc(), logger.ERROR) # For each search mode sort all the items by seeders if available - items[mode].sort(key=lambda tup: tup[3], reverse=True) - - results += items[mode] + items.sort(key=lambda tup: tup[3], reverse=True) + results += items return results diff --git a/sickbeard/providers/gftracker.py b/sickbeard/providers/gftracker.py index 79da127b9418f064b86bf7bda3e0b8cb42f4e6f4..6b381325eb02c3285d1519f449d413b1dcdeb19c 100644 --- a/sickbeard/providers/gftracker.py +++ b/sickbeard/providers/gftracker.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import re import requests @@ -24,11 +24,12 @@ import traceback from sickbeard import logger from sickbeard import tvcache from sickbeard.bs4_parser import BS4Parser +from sickrage.helper.common import convert_size from sickrage.helper.exceptions import AuthException from sickrage.providers.torrent.TorrentProvider import TorrentProvider -class GFTrackerProvider(TorrentProvider): +class GFTrackerProvider(TorrentProvider): # pylint: disable=too-many-instance-attributes def __init__(self): @@ -83,26 +84,24 @@ class GFTrackerProvider(TorrentProvider): return True - def search(self, search_params, age=0, ep_obj=None): - + def search(self, search_params, age=0, ep_obj=None): # pylint: disable=too-many-locals, too-many-branches results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} - if not self.login(): return results - for mode in search_params.keys(): + for mode in search_params: + items = [] logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_params[mode]: if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) - searchURL = self.urls['search'] % (self.categories, search_string) - logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) + search_url = self.urls['search'] % (self.categories, search_string) + logger.log(u"Search URL: %s" % search_url, logger.DEBUG) # Returns top 30 results by default, expandable in user profile - data = self.get_url(searchURL) + data = self.get_url(search_url) if not data: continue @@ -126,10 +125,7 @@ class GFTrackerProvider(TorrentProvider): leechers = int(shares[1]) torrent_size = cells[7].get_text().split("/", 1)[0] - if re.match(r"\d+([,\.]\d+)?\s*[KkMmGgTt]?[Bb]", torrent_size): - size = self._convertSize(torrent_size.strip()) - else: - size = -1 + size = convert_size(torrent_size) or -1 except (AttributeError, TypeError): continue @@ -147,38 +143,21 @@ class GFTrackerProvider(TorrentProvider): if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) - items[mode].append(item) + items.append(item) except Exception: logger.log(u"Failed parsing provider. Traceback: %s" % traceback.format_exc(), logger.ERROR) # For each search mode sort all the items by seeders if available - items[mode].sort(key=lambda tup: tup[3], reverse=True) + items.sort(key=lambda tup: tup[3], reverse=True) - results += items[mode] + results += items return results def seed_ratio(self): return self.ratio - def _convertSize(self, sizeString): - size = sizeString[:-2].strip() - modifier = sizeString[-2:].upper() - try: - size = float(size) - if modifier in 'KB': - size *= 1024 ** 1 - elif modifier in 'MB': - size *= 1024 ** 2 - elif modifier in 'GB': - size *= 1024 ** 3 - elif modifier in 'TB': - size *= 1024 ** 4 - except Exception: - size = -1 - return long(size) - class GFTrackerCache(tvcache.TVCache): def __init__(self, provider_obj): diff --git a/sickbeard/providers/hd4free.py b/sickbeard/providers/hd4free.py index 4cf1526a486c0a09a7c3d601c39f5efcb0699fd3..8cfe55a8baea806b99fb8e58f6f0821e884fec48 100644 --- a/sickbeard/providers/hd4free.py +++ b/sickbeard/providers/hd4free.py @@ -11,31 +11,35 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. from urllib import urlencode from sickbeard import logger from sickbeard import tvcache +from sickrage.helper.common import convert_size from sickrage.providers.torrent.TorrentProvider import TorrentProvider -class HD4FREEProvider(TorrentProvider): # pylint: disable=too-many-instance-attributes +class HD4FreeProvider(TorrentProvider): # pylint: disable=too-many-instance-attributes def __init__(self): TorrentProvider.__init__(self, "HD4Free") - self.public = True self.url = 'https://hd4free.xyz' - self.ratio = 0 - self.cache = HD4FREECache(self) - self.minseed, self.minleech = 2 * [None] + self.urls = {'search': self.url + '/searchapi.php'} + + self.freeleech = None self.username = None self.api_key = None - self.freeleech = None + self.minseed = None + self.minleech = None + self.ratio = 0 + + self.cache = HD4FreeCache(self) def _check_auth(self): if self.username and self.api_key: @@ -44,34 +48,36 @@ class HD4FREEProvider(TorrentProvider): # pylint: disable=too-many-instance-att logger.log('Your authentication credentials for %s are missing, check your config.' % self.name, logger.WARNING) return False - def search(self, search_strings, age=0, ep_obj=None): # pylint: disable=too-many-locals - + def search(self, search_strings, age=0, ep_obj=None): # pylint: disable=too-many-locals, too-many-branches results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} - if not self._check_auth: return results - + search_params = { 'tv': 'true', 'username': self.username, 'apikey': self.api_key } - for mode in search_strings.keys(): # Mode = RSS, Season, Episode + for mode in search_strings: + items = [] logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_strings[mode]: - if mode != 'RSS': - search_params['search'] = search_string.encode('utf-8') - - search_params['fl'] = 'true' if self.freeleech else 'false' + if self.freeleech: + search_params['fl'] = 'true' + else: + search_params.pop('fl', '') if mode != 'RSS': logger.log(u"Search string: " + search_string.strip(), logger.DEBUG) + search_params['search'] = search_string.encode('utf-8') + else: + search_params.pop('search', '') - searchURL = self.url + "/searchapi.php?" + urlencode(search_params) - logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) - jdata = self.get_url(searchURL, json=True) + search_url = self.urls['search'] + "?" + urlencode(search_params) + logger.log(u"Search URL: %s" % search_url, logger.DEBUG) + + jdata = self.get_url(search_url, json=True) if not jdata: logger.log(u"No data returned from provider", logger.DEBUG) continue @@ -80,35 +86,38 @@ class HD4FREEProvider(TorrentProvider): # pylint: disable=too-many-instance-att if jdata['0']['total_results'] == 0: logger.log(u"Provider has no results for this search", logger.DEBUG) continue - except (ValueError, KeyError): - pass + except StandardError: + continue for i in jdata: - seeders = jdata[i]["seeders"] - leechers = jdata[i]["leechers"] - title = jdata[i]["release_name"] - size = jdata[i]["size"] - download_url = jdata[i]["download_url"] + try: + title = jdata[i]["release_name"] + download_url = jdata[i]["download_url"] + if not all([title, download_url]): + continue + + seeders = jdata[i]["seeders"] + leechers = jdata[i]["leechers"] + if seeders < self.minseed or leechers < self.minleech: + if mode != 'RSS': + logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) + continue + + torrent_size = str(jdata[i]["size"]) + ' MB' + size = convert_size(torrent_size) or -1 + item = title, download_url, size, seeders, leechers - if not all([title, download_url]): - continue - - # Filter unseeded torrent - if seeders < self.minseed or leechers < self.minleech: if mode != 'RSS': - logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) - continue + logger.log(u"Found result: %s " % title, logger.DEBUG) - if mode != 'RSS': - logger.log(u"Found result: %s " % title, logger.DEBUG) - - item = title, download_url, size, seeders, leechers - items[mode].append(item) + items.append(item) + except StandardError: + continue # For each search mode sort all the items by seeders if available - items[mode].sort(key=lambda tup: tup[3], reverse=True) + items.sort(key=lambda tup: tup[3], reverse=True) - results += items[mode] + results += items return results @@ -116,7 +125,7 @@ class HD4FREEProvider(TorrentProvider): # pylint: disable=too-many-instance-att return self.ratio -class HD4FREECache(tvcache.TVCache): +class HD4FreeCache(tvcache.TVCache): def __init__(self, provider_obj): tvcache.TVCache.__init__(self, provider_obj) @@ -129,4 +138,4 @@ class HD4FREECache(tvcache.TVCache): search_params = {'RSS': ['']} return {'entries': self.provider.search(search_params)} -provider = HD4FREEProvider() +provider = HD4FreeProvider() diff --git a/sickbeard/providers/hdbits.py b/sickbeard/providers/hdbits.py index bcd271ccc6dcef8d7d374262bd519de2a3864382..24a0b2e2bf543559b3461e97c2fa3097a266214b 100644 --- a/sickbeard/providers/hdbits.py +++ b/sickbeard/providers/hdbits.py @@ -8,11 +8,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import datetime import urllib diff --git a/sickbeard/providers/hdspace.py b/sickbeard/providers/hdspace.py index addac0b45e58dea7e03dfe028294a2fe40f6e9bc..a5a543e6327f1a3cfdc98c20427da74bce2a3f4b 100644 --- a/sickbeard/providers/hdspace.py +++ b/sickbeard/providers/hdspace.py @@ -13,11 +13,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import re import urllib @@ -26,10 +26,11 @@ from bs4 import BeautifulSoup from sickbeard import logger from sickbeard import tvcache +from sickrage.helper.common import convert_size from sickrage.providers.torrent.TorrentProvider import TorrentProvider -class HDSpaceProvider(TorrentProvider): +class HDSpaceProvider(TorrentProvider): # pylint: disable=too-many-instance-attributes def __init__(self): TorrentProvider.__init__(self, "HDSpace") @@ -81,28 +82,25 @@ class HDSpaceProvider(TorrentProvider): return True - def search(self, search_strings, age=0, ep_obj=None): - + def search(self, search_strings, age=0, ep_obj=None): # pylint: disable=too-many-branches, too-many-locals, too-many-statements results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} - if not self.login(): return results - for mode in search_strings.keys(): + for mode in search_strings: + items = [] logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_strings[mode]: - if mode != 'RSS': - searchURL = self.urls['search'] % (urllib.quote_plus(search_string.replace('.', ' ')),) + search_url = self.urls['search'] % (urllib.quote_plus(search_string.replace('.', ' ')),) else: - searchURL = self.urls['search'] % '' + search_url = self.urls['search'] % '' - logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) + logger.log(u"Search URL: %s" % search_url, logger.DEBUG) if mode != 'RSS': logger.log(u"Search string: %s" % search_string, logger.DEBUG) - data = self.get_url(searchURL) + data = self.get_url(search_url) if not data or 'please try later' in data: logger.log(u"No data returned from provider", logger.DEBUG) continue @@ -138,7 +136,8 @@ class HDSpaceProvider(TorrentProvider): download_url = self.urls['base_url'] + dl_href seeders = int(result.find('span', attrs={'class': 'seedy'}).find('a').text) leechers = int(result.find('span', attrs={'class': 'leechy'}).find('a').text) - size = re.match(r'.*?([0-9]+,?\.?[0-9]* [KkMmGg]+[Bb]+).*', str(result), re.DOTALL).group(1) + torrent_size = re.match(r'.*?([0-9]+,?\.?[0-9]* [KkMmGg]+[Bb]+).*', str(result), re.DOTALL).group(1) + size = convert_size(torrent_size) or -1 if not all([title, download_url]): continue @@ -153,34 +152,20 @@ class HDSpaceProvider(TorrentProvider): if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) - items[mode].append(item) + items.append(item) except (AttributeError, TypeError, KeyError, ValueError): continue # For each search mode sort all the items by seeders if available - items[mode].sort(key=lambda tup: tup[3], reverse=True) - - results += items[mode] + items.sort(key=lambda tup: tup[3], reverse=True) + results += items return results def seed_ratio(self): return self.ratio - def _convertSize(self, size): - size, modifier = size.split(' ') - size = float(size) - if modifier in 'KB': - size *= 1024 ** 1 - elif modifier in 'MB': - size *= 1024 ** 2 - elif modifier in 'GB': - size *= 1024 ** 3 - elif modifier in 'TB': - size *= 1024 ** 4 - return long(size) - class HDSpaceCache(tvcache.TVCache): def __init__(self, provider_obj): diff --git a/sickbeard/providers/hdtorrents.py b/sickbeard/providers/hdtorrents.py index eaa632f75b7f1df3f34afaf21cf6bd552c95acf7..8754a1b5150b585f32bd7e3821af872212740243 100644 --- a/sickbeard/providers/hdtorrents.py +++ b/sickbeard/providers/hdtorrents.py @@ -1,7 +1,6 @@ # coding=utf-8 -# Author: Idan Gutman -# Modified by jkaberg, https://github.com/jkaberg for SceneAccess -# URL: http://code.google.com/p/sickbeard/ +# Author: miigotu (Dustyn Gibson) <miigotu@gmail.com> +# URL: https://sickrage.github.io # # This file is part of SickRage. # @@ -12,24 +11,25 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import re import urllib import requests -import traceback -from sickbeard.bs4_parser import BS4Parser from sickbeard import logger from sickbeard import tvcache +from sickbeard.bs4_parser import BS4Parser + +from sickrage.helper.common import try_int, convert_size from sickrage.providers.torrent.TorrentProvider import TorrentProvider -class HDTorrentsProvider(TorrentProvider): +class HDTorrentsProvider(TorrentProvider): # pylint: disable=too-many-instance-attributes def __init__(self): TorrentProvider.__init__(self, "HDTorrents") @@ -39,6 +39,7 @@ class HDTorrentsProvider(TorrentProvider): self.ratio = None self.minseed = None self.minleech = None + self.freeleech = None self.urls = {'base_url': 'https://hd-torrents.org', 'login': 'https://hd-torrents.org/login.php', @@ -80,147 +81,115 @@ class HDTorrentsProvider(TorrentProvider): return True - def search(self, search_strings, age=0, ep_obj=None): - + def search(self, search_strings, age=0, ep_obj=None): # pylint: disable=too-many-locals, too-many-branches, too-many-statements results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} - if not self.login(): return results - for mode in search_strings.keys(): + for mode in search_strings: + items = [] logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_strings[mode]: if mode != 'RSS': - searchURL = self.urls['search'] % (urllib.quote_plus(search_string), self.categories) + search_url = self.urls['search'] % (urllib.quote_plus(search_string), self.categories) + logger.log(u"Search string: %s" % search_string, logger.DEBUG) else: - searchURL = self.urls['rss'] % self.categories + search_url = self.urls['rss'] % self.categories - logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) - if mode != 'RSS': - logger.log(u"Search string: %s" % search_string, logger.DEBUG) + if self.freeleech: + search_url = search_url.replace('active=1', 'active=5') + + logger.log(u"Search URL: %s" % search_url, logger.DEBUG) - data = self.get_url(searchURL) + data = self.get_url(search_url) if not data or 'please try later' in data: logger.log(u"No data returned from provider", logger.DEBUG) continue + if data.find('No torrents here') != -1: + logger.log(u"Data returned from provider does not contain any torrents", logger.DEBUG) + continue + # Search result page contains some invalid html that prevents html parser from returning all data. # We cut everything before the table that contains the data we are interested in thus eliminating # the invalid html portions try: index = data.lower().index('<table class="mainblockcontenttt"') except ValueError: - logger.log(u"Could not find table of torrents mainblockcontenttt", logger.ERROR) + logger.log(u"Could not find table of torrents mainblockcontenttt", logger.DEBUG) continue - data = urllib.unquote(data[index:].encode('utf-8')).decode('utf-8').replace('\t', '') + # data = urllib.unquote(data[index:].encode('utf-8')).decode('utf-8').replace('\t', '') + data = data[index:] with BS4Parser(data, 'html5lib') as html: if not html: logger.log(u"No html data parsed from provider", logger.DEBUG) continue - empty = html.find('No torrents here') - if empty: - logger.log(u"Data returned from provider does not contain any torrents", logger.DEBUG) - continue + torrent_rows = [] + torrent_table = html.find('table', class_='mainblockcontenttt') + if torrent_table: + torrent_rows = torrent_table.find_all('tr') - tables = html.find('table', attrs={'class': 'mainblockcontenttt'}) - if not tables: - logger.log(u"Could not find table of torrents mainblockcontenttt", logger.ERROR) + if not torrent_rows: + logger.log(u"Could not find results in returned data", logger.DEBUG) continue - torrents = tables.findChildren('tr') - if not torrents: - continue + # Cat., Active, Filename, Dl, Wl, Added, Size, Uploader, S, L, C + labels = [label.a.get_text(strip=True) if label.a else label.get_text(strip=True) for label in torrent_rows[0].find_all('td')] # Skip column headers - for result in torrents[1:]: + for result in torrent_rows[1:]: try: - cells = result.findChildren('td', attrs={'class': re.compile(r'(green|yellow|red|mainblockcontent)')}) - if not cells: + cells = result.findChildren('td')[:len(labels)] + if len(cells) < len(labels): continue - title = download_url = seeders = leechers = size = None - for cell in cells: - try: - if None is title and cell.get('title') and cell.get('title') in 'Download': - title = re.search('f=(.*).torrent', cell.a['href']).group(1).replace('+', '.') - title = title.decode('utf-8') - download_url = self.urls['home'] % cell.a['href'] - continue - if None is seeders and cell.get('class')[0] and cell.get('class')[0] in 'green' 'yellow' 'red': - seeders = int(cell.text) - if not seeders: - seeders = 1 - continue - elif None is leechers and cell.get('class')[0] and cell.get('class')[0] in 'green' 'yellow' 'red': - leechers = int(cell.text) - if not leechers: - leechers = 0 - continue - - # Need size for failed downloads handling - if size is None: - if re.match(r'[0-9]+,?\.?[0-9]* [KkMmGg]+[Bb]+', cell.text): - size = self._convertSize(cell.text) - if not size: - size = -1 - - except Exception: - logger.log(u"Failed parsing provider. Traceback: %s" % traceback.format_exc(), logger.ERROR) - - if not all([title, download_url]): - continue + title = cells[labels.index(u'Filename')].a.get_text(strip=True) + seeders = try_int(cells[labels.index(u'S')].get_text(strip=True)) + leechers = try_int(cells[labels.index(u'L')].get_text(strip=True)) + torrent_size = cells[labels.index(u'Size')].get_text() - # Filter unseeded torrent - if seeders < self.minseed or leechers < self.minleech: - if mode != 'RSS': - logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) - continue + size = convert_size(torrent_size) or -1 + download_url = self.url + '/' + cells[labels.index(u'Dl')].a['href'] + except (AttributeError, TypeError, KeyError, ValueError, IndexError): + continue - item = title, download_url, size, seeders, leechers + if not all([title, download_url]): + continue + + # Filter unseeded torrent + if seeders < self.minseed or leechers < self.minleech: if mode != 'RSS': - logger.log(u"Found result: %s " % title, logger.DEBUG) + logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) + continue - items[mode].append(item) + item = title, download_url, size, seeders, leechers + if mode != 'RSS': + logger.log(u"Found result: %s " % title, logger.DEBUG) - except (AttributeError, TypeError, KeyError, ValueError): - continue + items.append(item) # For each search mode sort all the items by seeders if available - items[mode].sort(key=lambda tup: tup[3], reverse=True) + items.sort(key=lambda tup: tup[3], reverse=True) - results += items[mode] + results += items return results def seed_ratio(self): return self.ratio - def _convertSize(self, size): - size, modifier = size.split(' ') - size = float(size) - if modifier in 'KB': - size *= 1024 ** 1 - elif modifier in 'MB': - size *= 1024 ** 2 - elif modifier in 'GB': - size *= 1024 ** 3 - elif modifier in 'TB': - size *= 1024 ** 4 - return long(size) - class HDTorrentsCache(tvcache.TVCache): def __init__(self, provider_obj): tvcache.TVCache.__init__(self, provider_obj) - # only poll HDTorrents every 10 minutes max - self.minTime = 10 + # only poll HDTorrents every 30 minutes max + self.minTime = 30 def _getRSSData(self): search_strings = {'RSS': ['']} diff --git a/sickbeard/providers/hounddawgs.py b/sickbeard/providers/hounddawgs.py index f6fa855453df7e21e9dc62d5396efa155c2e9013..05963cff520798c0278e25cfb91916affa8ba147 100644 --- a/sickbeard/providers/hounddawgs.py +++ b/sickbeard/providers/hounddawgs.py @@ -11,18 +11,18 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import re import traceback from sickbeard import logger from sickbeard import tvcache from sickbeard.bs4_parser import BS4Parser -from sickrage.helper.common import try_int +from sickrage.helper.common import try_int, convert_size from sickrage.providers.torrent.TorrentProvider import TorrentProvider @@ -90,14 +90,12 @@ class HoundDawgsProvider(TorrentProvider): # pylint: disable=too-many-instance- return True def search(self, search_strings, age=0, ep_obj=None): # pylint: disable=too-many-locals,too-many-branches,too-many-statements - results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} - if not self.login(): return results - for mode in search_strings.keys(): + for mode in search_strings: + items = [] logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_strings[mode]: @@ -149,7 +147,7 @@ class HoundDawgsProvider(TorrentProvider): # pylint: disable=too-many-instance- download_url = self.urls['base_url'] + allAs[0].attrs['href'] torrent_size = result.find("td", class_="nobr").find_next_sibling("td").string if torrent_size: - size = self._convertSize(torrent_size) + size = convert_size(torrent_size) or -1 seeders = try_int((result.findAll('td')[6]).text) leechers = try_int((result.findAll('td')[7]).text) @@ -169,31 +167,18 @@ class HoundDawgsProvider(TorrentProvider): # pylint: disable=too-many-instance- if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) - items[mode].append(item) + items.append(item) except Exception: logger.log(u"Failed parsing provider. Traceback: %s" % traceback.format_exc(), logger.ERROR) # For each search mode sort all the items by seeders if available - items[mode].sort(key=lambda tup: tup[3], reverse=True) + items.sort(key=lambda tup: tup[3], reverse=True) - results += items[mode] + results += items return results - @staticmethod - def _convertSize(size): - size = re.sub(r'[i, ]+', '', size) - matches = re.match(r'([\d.]+)([TGMK])', size.strip().upper()) - if not matches: - return -1 - - size = matches.group(1) - modifier = matches.group(2) - - mod = {'K': 1, 'M': 2, 'G': 3, 'T': 4} - return long(float(size) * 1024 ** mod[modifier]) - def seed_ratio(self): return self.ratio diff --git a/sickbeard/providers/iptorrents.py b/sickbeard/providers/iptorrents.py index 778d4342bcc5d0717631af90c7514231c813a371..3e36754a83ffc42bcc27fc4bcc588cab02d1a213 100644 --- a/sickbeard/providers/iptorrents.py +++ b/sickbeard/providers/iptorrents.py @@ -11,21 +11,22 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import re from sickbeard import logger from sickbeard import tvcache from sickbeard.bs4_parser import BS4Parser +from sickrage.helper.common import convert_size from sickrage.helper.exceptions import AuthException, ex from sickrage.providers.torrent.TorrentProvider import TorrentProvider -class IPTorrentsProvider(TorrentProvider): +class IPTorrentsProvider(TorrentProvider): # pylint: disable=too-many-instance-attributes def __init__(self): TorrentProvider.__init__(self, "IPTorrents") @@ -66,26 +67,27 @@ class IPTorrentsProvider(TorrentProvider): logger.log(u"Unable to connect to provider", logger.WARNING) return False - if re.search('tries left', response): - logger.log(u"You tried too often, please try again after 1 hour! Disable IPTorrents for at least 1 hour", logger.WARNING) - return False - if re.search('Password not correct', response): + # Invalid username and password combination + if re.search('Invalid username and password combination', response): logger.log(u"Invalid username or password. Check your settings", logger.WARNING) return False - return True + # You tried too often, please try again after 2 hours! + if re.search('You tried too often', response): + logger.log(u"You tried too often, please try again after 2 hours! Disable IPTorrents for at least 2 hours", logger.WARNING) + return False - def search(self, search_params, age=0, ep_obj=None): + return True + def search(self, search_params, age=0, ep_obj=None): # pylint: disable=too-many-locals, too-many-branches, too-many-statements results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} - - freeleech = '&free=on' if self.freeleech else '' - if not self.login(): return results - for mode in search_params.keys(): + freeleech = '&free=on' if self.freeleech else '' + + for mode in search_params: + items = [] logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_params[mode]: @@ -93,11 +95,11 @@ class IPTorrentsProvider(TorrentProvider): logger.log(u"Search string: %s " % search_string, logger.DEBUG) # URL with 50 tv-show results, or max 150 if adjusted in IPTorrents profile - searchURL = self.urls['search'] % (self.categories, freeleech, search_string) - searchURL += ';o=seeders' if mode != 'RSS' else '' - logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) + search_url = self.urls['search'] % (self.categories, freeleech, search_string) + search_url += ';o=seeders' if mode != 'RSS' else '' + logger.log(u"Search URL: %s" % search_url, logger.DEBUG) - data = self.get_url(searchURL) + data = self.get_url(search_url) if not data: continue @@ -124,9 +126,10 @@ class IPTorrentsProvider(TorrentProvider): try: title = result.find_all('td')[1].find('a').text download_url = self.urls['base_url'] + result.find_all('td')[3].find('a')['href'] - size = self._convertSize(result.find_all('td')[5].text) seeders = int(result.find('td', attrs={'class': 'ac t_seeders'}).text) leechers = int(result.find('td', attrs={'class': 'ac t_leechers'}).text) + torrent_size = result.find_all('td')[5].text + size = convert_size(torrent_size) or -1 except (AttributeError, TypeError, KeyError): continue @@ -143,35 +146,21 @@ class IPTorrentsProvider(TorrentProvider): if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) - items[mode].append(item) + items.append(item) except Exception as e: logger.log(u"Failed parsing provider. Error: %r" % ex(e), logger.ERROR) # For each search mode sort all the items by seeders if available - items[mode].sort(key=lambda tup: tup[3], reverse=True) + items.sort(key=lambda tup: tup[3], reverse=True) - results += items[mode] + results += items return results def seed_ratio(self): return self.ratio - @staticmethod - def _convertSize(size): - size, modifier = size.split(' ') - size = float(size) - if modifier in 'KB': - size *= 1024 ** 1 - elif modifier in 'MB': - size *= 1024 ** 2 - elif modifier in 'GB': - size *= 1024 ** 3 - elif modifier in 'TB': - size *= 1024 ** 4 - return long(size) - class IPTorrentsCache(tvcache.TVCache): def __init__(self, provider_obj): diff --git a/sickbeard/providers/kat.py b/sickbeard/providers/kat.py old mode 100755 new mode 100644 index 2472449e29d23dd636b212b45d15e9357ccb73af..8a6a71092dfa2bd449fa6cb0078d18951b48b8fe --- a/sickbeard/providers/kat.py +++ b/sickbeard/providers/kat.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import posixpath # Must use posixpath import traceback @@ -26,11 +26,11 @@ import sickbeard from sickbeard import logger from sickbeard import tvcache from sickbeard.common import USER_AGENT -from sickrage.helper.common import try_int +from sickrage.helper.common import try_int, convert_size from sickrage.providers.torrent.TorrentProvider import TorrentProvider -class KATProvider(TorrentProvider): +class KatProvider(TorrentProvider): # pylint: disable=too-many-instance-attributes def __init__(self): TorrentProvider.__init__(self, "KickAssTorrents") @@ -60,17 +60,17 @@ class KATProvider(TorrentProvider): 'category': 'tv' } - self.cache = KATCache(self) + self.cache = KatCache(self) - def search(self, search_strings, age=0, ep_obj=None): + def search(self, search_strings, age=0, ep_obj=None): # pylint: disable=too-many-branches, too-many-locals, too-many-statements results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} # select the correct category anime = (self.show and self.show.anime) or (ep_obj and ep_obj.show and ep_obj.show.anime) or False self.search_params['category'] = ('tv', 'anime')[anime] - for mode in search_strings.keys(): + for mode in search_strings: + items = [] logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_strings[mode]: @@ -82,12 +82,12 @@ class KATProvider(TorrentProvider): url_fmt_string = 'usearch' if mode != 'RSS' else search_string try: - searchURL = self.urls['search'] % url_fmt_string + '?' + urlencode(self.search_params) + search_url = self.urls['search'] % url_fmt_string + '?' + urlencode(self.search_params) if self.custom_url: - searchURL = posixpath.join(self.custom_url, searchURL.split(self.url)[1].lstrip('/')) # Must use posixpath + search_url = posixpath.join(self.custom_url, search_url.split(self.url)[1].lstrip('/')) # Must use posixpath - logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) - data = self.get_url(searchURL) + logger.log(u"Search URL: %s" % search_url, logger.DEBUG) + data = self.get_url(search_url) if not data: logger.log(u'URL did not return data, maybe try a custom url, or a different one', logger.DEBUG) continue @@ -114,10 +114,11 @@ class KATProvider(TorrentProvider): if not (title and download_url): continue - seeders = try_int(item.find('torrent:seeds').text, 0) - leechers = try_int(item.find('torrent:peers').text, 0) - verified = bool(try_int(item.find('torrent:verified').text, 0)) - size = try_int(item.find('torrent:contentlength').text) + seeders = try_int(item.find('torrent:seeds').text) + leechers = try_int(item.find('torrent:peers').text) + verified = bool(try_int(item.find('torrent:verified').text)) + torrent_size = item.find('torrent:contentlength').text + size = convert_size(torrent_size) or -1 info_hash = item.find('torrent:infohash').text # link = item['link'] @@ -140,15 +141,15 @@ class KATProvider(TorrentProvider): if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) - items[mode].append(item) + items.append(item) except Exception: logger.log(u"Failed parsing provider. Traceback: %r" % traceback.format_exc(), logger.ERROR) # For each search mode sort all the items by seeders if available - items[mode].sort(key=lambda tup: tup[3], reverse=True) + items.sort(key=lambda tup: tup[3], reverse=True) - results += items[mode] + results += items return results @@ -156,7 +157,7 @@ class KATProvider(TorrentProvider): return self.ratio -class KATCache(tvcache.TVCache): +class KatCache(tvcache.TVCache): def __init__(self, provider_obj): tvcache.TVCache.__init__(self, provider_obj) @@ -168,4 +169,4 @@ class KATCache(tvcache.TVCache): search_params = {'RSS': ['tv', 'anime']} return {'entries': self.provider.search(search_params)} -provider = KATProvider() +provider = KatProvider() diff --git a/sickbeard/providers/limetorrents.py b/sickbeard/providers/limetorrents.py index 6a73e2d846a531f689098b9b59a8e9c465088dcf..83fa9bfd1a8f4aa6b4b5811d9c1a8089baa17612 100644 --- a/sickbeard/providers/limetorrents.py +++ b/sickbeard/providers/limetorrents.py @@ -10,18 +10,18 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import traceback from bs4 import BeautifulSoup from sickbeard import logger from sickbeard import tvcache from sickbeard.common import USER_AGENT -from sickrage.helper.common import try_int +from sickrage.helper.common import try_int, convert_size from sickrage.providers.torrent.TorrentProvider import TorrentProvider @@ -47,11 +47,9 @@ class LimeTorrentsProvider(TorrentProvider): # pylint: disable=too-many-instance self.cache = LimeTorrentsCache(self) def search(self, search_strings, age=0, ep_obj=None): # pylint: disable=too-many-branches,too-many-locals - results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} - - for mode in search_strings.keys(): + for mode in search_strings: + items = [] logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_strings[mode]: @@ -97,7 +95,10 @@ class LimeTorrentsProvider(TorrentProvider): # pylint: disable=too-many-instance description = item.find('description').text.partition(',') seeders = try_int(description[0].lstrip('Seeds: ').strip()) leechers = try_int(description[2].lstrip('Leechers ').strip()) - size = try_int(item.find('size').text, -1) + + torrent_size = item.find('size').text + + size = convert_size(torrent_size) or -1 except (AttributeError, TypeError, KeyError, ValueError): continue @@ -112,15 +113,15 @@ class LimeTorrentsProvider(TorrentProvider): # pylint: disable=too-many-instance if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) - items[mode].append(item) + items.append(item) except (AttributeError, TypeError, KeyError, ValueError): logger.log(u"Failed parsing provider. Traceback: %r" % traceback.format_exc(), logger.ERROR) # For each search mode sort all the items by seeders if available - items[mode].sort(key=lambda tup: tup[3], reverse=True) + items.sort(key=lambda tup: tup[3], reverse=True) - results += items[mode] + results += items return results diff --git a/sickbeard/providers/morethantv.py b/sickbeard/providers/morethantv.py index 71892f1e7766fad006478162a3a625a596b2ab2d..e5dfac46d5b36b29347ec1838c07a572904d854d 100644 --- a/sickbeard/providers/morethantv.py +++ b/sickbeard/providers/morethantv.py @@ -1,6 +1,6 @@ # coding=utf-8 -# Author: Seamus Wassman -# URL: http://code.google.com/p/sickbeard/ +# Author: Dustyn Gibson <miigotu@gmail.com> +# URL: https://sickrage.github.io # # This file is part of SickRage. # @@ -11,28 +11,25 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. - -# This file was adapted for MoreThanTV from the freshontv scraper by -# Sparhawk76, this is my first foray into python, so there most likely -# are some mistakes or things I could have done better. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import re -import requests -import traceback +from requests.utils import dict_from_cookiejar +from urllib import urlencode from sickbeard import logger from sickbeard import tvcache from sickbeard.bs4_parser import BS4Parser +from sickrage.helper.common import try_int, convert_size from sickrage.helper.exceptions import AuthException from sickrage.providers.torrent.TorrentProvider import TorrentProvider -class MoreThanTVProvider(TorrentProvider): +class MoreThanTVProvider(TorrentProvider): # pylint: disable=too-many-instance-attributes def __init__(self): @@ -45,17 +42,12 @@ class MoreThanTVProvider(TorrentProvider): self.ratio = None self.minseed = None self.minleech = None - # self.freeleech = False - - self.urls = {'base_url': 'https://www.morethan.tv/', - 'login': 'https://www.morethan.tv/login.php', - 'detail': 'https://www.morethan.tv/torrents.php?id=%s', - 'search': 'https://www.morethan.tv/torrents.php?tags_type=1&order_by=time&order_way=desc&action=basic&searchsubmit=1&searchstr=%s', - 'download': 'https://www.morethan.tv/torrents.php?action=download&id=%s'} - - self.url = self.urls['base_url'] - self.cookies = None + self.url = 'https://www.morethan.tv/' + self.urls = { + 'login': self.url + 'login.php', + 'search': self.url + 'torrents.php', + } self.proper_strings = ['PROPER', 'REPACK'] @@ -69,137 +61,118 @@ class MoreThanTVProvider(TorrentProvider): return True def login(self): - if any(requests.utils.dict_from_cookiejar(self.session.cookies).values()): + if any(dict_from_cookiejar(self.session.cookies).values()): return True - if self._uid and self._hash: - requests.utils.add_dict_to_cookiejar(self.session.cookies, self.cookies) - else: - login_params = {'username': self.username, - 'password': self.password, - 'login': 'Log in', - 'keeplogged': '1'} + login_params = { + 'username': self.username, + 'password': self.password, + 'login': 'Log in', + 'keeplogged': '1' + } - response = self.get_url(self.urls['login'], post_data=login_params, timeout=30) - if not response: - logger.log(u"Unable to connect to provider", logger.WARNING) - return False + response = self.get_url(self.urls['login'], post_data=login_params, timeout=30) + if not response: + logger.log(u"Unable to connect to provider", logger.WARNING) + return False - if re.search('Your username or password was incorrect.', response): - logger.log(u"Invalid username or password. Check your settings", logger.WARNING) - return False + if re.search('Your username or password was incorrect.', response): + logger.log(u"Invalid username or password. Check your settings", logger.WARNING) + return False - return True - - def search(self, search_params, age=0, ep_obj=None): + return True + def search(self, search_strings, age=0, ep_obj=None): # pylint: disable=too-many-branches, too-many-locals results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} - - # freeleech = '3' if self.freeleech else '0' - if not self.login(): return results - for mode in search_params.keys(): + search_params = { + 'tags_type': 1, + 'order_by': 'time', + 'order_way': 'desc', + 'action': 'basic', + 'searchsubmit': 1, + 'searchstr': '' + } + + for mode in search_strings: + items = [] logger.log(u"Search Mode: %s" % mode, logger.DEBUG) - for search_string in search_params[mode]: - + for search_string in search_strings[mode]: if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) - searchURL = self.urls['search'] % (search_string.replace('(', '').replace(')', '')) - logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) + search_params['searchstr'] = search_string + + search_url = "%s?%s" % (self.urls['search'], urlencode(search_params)) + logger.log(u"Search URL: %s" % search_url, logger.DEBUG) # returns top 15 results by default, expandable in user profile to 100 - data = self.get_url(searchURL) + data = self.get_url(search_url) if not data: continue - try: - with BS4Parser(data, 'html5lib') as html: - torrent_table = html.find('table', attrs={'class': 'torrent_table'}) - torrent_rows = torrent_table.findChildren('tr') if torrent_table else [] + with BS4Parser(data, 'html5lib') as html: + torrent_table = html.find('table', class_='torrent_table') + torrent_rows = torrent_table.find_all('tr') if torrent_table else [] - # Continue only if one Release is found - if len(torrent_rows) < 2: - logger.log(u"Data returned from provider does not contain any torrents", logger.DEBUG) - continue + # Continue only if one Release is found + if len(torrent_rows) < 2: + logger.log(u"Data returned from provider does not contain any torrents", logger.DEBUG) + continue - # skip colheader - for result in torrent_rows[1:]: - cells = result.findChildren('td') - link = cells[1].find('a', attrs={'title': 'Download'}) + def process_column_header(td): + result = '' + if td.a and td.a.img: + result = td.a.img.get('title', td.a.get_text(strip=True)) + if not result: + result = td.get_text(strip=True) + return result - # skip if torrent has been nuked due to poor quality - if cells[1].find('img', alt='Nuked') is not None: - continue - - torrent_id_long = link['href'].replace('torrents.php?action=download&id=', '') - - try: - if link.get('title', ''): - title = cells[1].find('a', {'title': 'View torrent'}).contents[0].strip() - else: - title = link.contents[0] - download_url = self.urls['download'] % torrent_id_long - - seeders = cells[6].contents[0] - - leechers = cells[7].contents[0] - - size = -1 - if re.match(r'\d+([,\.]\d+)?\s*[KkMmGgTt]?[Bb]', cells[4].contents[0]): - size = self._convertSize(cells[4].text.strip()) + labels = [process_column_header(label) for label in torrent_rows[0].find_all('td')] - except (AttributeError, TypeError): + # skip colheader + for result in torrent_rows[1:]: + try: + # skip if torrent has been nuked due to poor quality + if result.find('img', alt='Nuked'): continue + title = result.find('a', title='View torrent').get_text(strip=True) + download_url = self.url + result.find('span', title='Download').parent['href'] if not all([title, download_url]): continue - # Filter unseeded torrent + cells = result.find_all('td') + seeders = try_int(cells[labels.index('Seeders')].get_text(strip=True)) + leechers = try_int(cells[labels.index('Leechers')].get_text(strip=True)) if seeders < self.minseed or leechers < self.minleech: if mode != 'RSS': logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) continue + torrent_size = cells[labels.index('Size')].get_text(strip=True) + size = convert_size(torrent_size) or -1 + item = title, download_url, size, seeders, leechers if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) - items[mode].append(item) - - except Exception as e: - logger.log(u"Failed parsing provider. Traceback: %s" % traceback.format_exc(), logger.ERROR) + items.append(item) + except StandardError: + continue # For each search mode sort all the items by seeders if available - items[mode].sort(key=lambda tup: tup[3], reverse=True) + items.sort(key=lambda tup: tup[3], reverse=True) - results += items[mode] + results += items return results def seed_ratio(self): return self.ratio - def _convertSize(self, sizeString): - size = sizeString[:-2].strip() - modifier = sizeString[-2:].upper() - try: - size = float(size) - if modifier in 'KB': - size *= 1024 ** 1 - elif modifier in 'MB': - size *= 1024 ** 2 - elif modifier in 'GB': - size *= 1024 ** 3 - elif modifier in 'TB': - size *= 1024 ** 4 - except Exception: - size = -1 - return long(size) - class MoreThanTVCache(tvcache.TVCache): def __init__(self, provider_obj): @@ -210,7 +183,7 @@ class MoreThanTVCache(tvcache.TVCache): self.minTime = 20 def _getRSSData(self): - search_params = {'RSS': ['']} - return {'entries': self.provider.search(search_params)} + search_strings = {'RSS': ['']} + return {'entries': self.provider.search(search_strings)} provider = MoreThanTVProvider() diff --git a/sickbeard/providers/newpct.py b/sickbeard/providers/newpct.py index 2a33d293bb3372678bd57126664ef103362a4970..3a9153b5d48031ff918a1dc0c9abea2112048bdf 100644 --- a/sickbeard/providers/newpct.py +++ b/sickbeard/providers/newpct.py @@ -13,11 +13,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import traceback import re @@ -27,6 +27,7 @@ from sickbeard import helpers from sickbeard import logger from sickbeard import tvcache from sickbeard.bs4_parser import BS4Parser +from sickrage.helper.common import convert_size from sickrage.providers.torrent.TorrentProvider import TorrentProvider @@ -68,15 +69,15 @@ class newpctProvider(TorrentProvider): 'bus_de_': 'All' } - def search(self, search_strings, age=0, ep_obj=None): + def search(self, search_strings, age=0, ep_obj=None): # pylint: disable=too-many-locals results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} # Only search if user conditions are true lang_info = '' if not ep_obj or not ep_obj.show else ep_obj.show.lang - for mode in search_strings.keys(): + for mode in search_strings: + items = [] logger.log(u"Search Mode: %s" % mode, logger.DEBUG) # Only search if user conditions are true @@ -91,10 +92,10 @@ class newpctProvider(TorrentProvider): self.search_params['q'] = search_string.strip() if mode != 'RSS' else '' self.search_params['bus_de_'] = 'All' if mode != 'RSS' else 'hoy' - searchURL = self.urls['search'] + '?' + urllib.parse.urlencode(self.search_params) - logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) + search_url = self.urls['search'] + '?' + urllib.parse.urlencode(self.search_params) + logger.log(u"Search URL: %s" % search_url, logger.DEBUG) - data = self.get_url(searchURL, timeout=30) + data = self.get_url(search_url, timeout=30) if not data: continue @@ -113,17 +114,18 @@ class newpctProvider(TorrentProvider): for row in torrent_table[:-1]: try: - torrent_size = row.findAll('td')[2] torrent_row = row.findAll('a')[0] download_url = torrent_row.get('href', '') - size = self._convertSize(torrent_size.text) + title = self._processTitle(torrent_row.get('title', '')) - # FIXME: Provider does not provide seeders/leechers + # Provider does not provide seeders/leechers seeders = 1 leechers = 0 + torrent_size = row.findAll('td')[2].text + size = convert_size(torrent_size) or -1 except (AttributeError, TypeError): continue @@ -140,19 +142,19 @@ class newpctProvider(TorrentProvider): if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) - items[mode].append(item) + items.append(item) except Exception: logger.log(u"Failed parsing provider. Traceback: %s" % traceback.format_exc(), logger.WARNING) # For each search mode sort all the items by seeders if available (Unsupported) - # items[mode].sort(key=lambda tup: tup[3], reverse=True) + # items.sort(key=lambda tup: tup[3], reverse=True) - results += items[mode] + results += items return results - def get_url(self, url, post_data=None, params=None, timeout=30, json=False, need_bytes=False): + def get_url(self, url, post_data=None, params=None, timeout=30, json=False, need_bytes=False): # pylint: disable=too-many-arguments """ need_bytes=True when trying access to torrent info (For calling torrent client). Previously we must parse the URL to get torrent file @@ -199,19 +201,6 @@ class newpctProvider(TorrentProvider): return False - @staticmethod - def _convertSize(size): - size, modifier = size.split(' ') - size = float(size) - if modifier in 'KB': - size *= 1024 ** 1 - elif modifier in 'MB': - size *= 1024 ** 2 - elif modifier in 'GB': - size *= 1024 ** 3 - elif modifier in 'TB': - size *= 1024 ** 4 - return long(size) @staticmethod def _processTitle(title): @@ -219,23 +208,23 @@ class newpctProvider(TorrentProvider): title = title[22:] # Quality - Use re module to avoid case sensitive problems with replace - title = re.sub('\[HDTV 1080p[^\[]*]', '1080p HDTV x264', title, flags=re.IGNORECASE) - title = re.sub('\[HDTV 720p[^\[]*]', '720p HDTV x264', title, flags=re.IGNORECASE) - title = re.sub('\[ALTA DEFINICION 720p[^\[]*]', '720p HDTV x264', title, flags=re.IGNORECASE) - title = re.sub('\[HDTV]', 'HDTV x264', title, flags=re.IGNORECASE) - title = re.sub('\[DVD[^\[]*]', 'DVDrip x264', title, flags=re.IGNORECASE) - title = re.sub('\[BluRay 1080p[^\[]*]', '1080p BlueRay x264', title, flags=re.IGNORECASE) - title = re.sub('\[BluRay MicroHD[^\[]*]', '1080p BlueRay x264', title, flags=re.IGNORECASE) - title = re.sub('\[MicroHD 1080p[^\[]*]', '1080p BlueRay x264', title, flags=re.IGNORECASE) - title = re.sub('\[BLuRay[^\[]*]', '720p BlueRay x264', title, flags=re.IGNORECASE) - title = re.sub('\[BRrip[^\[]*]', '720p BlueRay x264', title, flags=re.IGNORECASE) - title = re.sub('\[BDrip[^\[]*]', '720p BlueRay x264', title, flags=re.IGNORECASE) + title = re.sub(r'\[HDTV 1080p[^\[]*]', '1080p HDTV x264', title, flags=re.IGNORECASE) + title = re.sub(r'\[HDTV 720p[^\[]*]', '720p HDTV x264', title, flags=re.IGNORECASE) + title = re.sub(r'\[ALTA DEFINICION 720p[^\[]*]', '720p HDTV x264', title, flags=re.IGNORECASE) + title = re.sub(r'\[HDTV]', 'HDTV x264', title, flags=re.IGNORECASE) + title = re.sub(r'\[DVD[^\[]*]', 'DVDrip x264', title, flags=re.IGNORECASE) + title = re.sub(r'\[BluRay 1080p[^\[]*]', '1080p BlueRay x264', title, flags=re.IGNORECASE) + title = re.sub(r'\[BluRay MicroHD[^\[]*]', '1080p BlueRay x264', title, flags=re.IGNORECASE) + title = re.sub(r'\[MicroHD 1080p[^\[]*]', '1080p BlueRay x264', title, flags=re.IGNORECASE) + title = re.sub(r'\[BLuRay[^\[]*]', '720p BlueRay x264', title, flags=re.IGNORECASE) + title = re.sub(r'\[BRrip[^\[]*]', '720p BlueRay x264', title, flags=re.IGNORECASE) + title = re.sub(r'\[BDrip[^\[]*]', '720p BlueRay x264', title, flags=re.IGNORECASE) # Language - title = re.sub('\[Spanish[^\[]*]', 'SPANISH AUDIO', title, flags=re.IGNORECASE) - title = re.sub('\[Castellano[^\[]*]', 'SPANISH AUDIO', title, flags=re.IGNORECASE) + title = re.sub(r'\[Spanish[^\[]*]', 'SPANISH AUDIO', title, flags=re.IGNORECASE) + title = re.sub(r'\[Castellano[^\[]*]', 'SPANISH AUDIO', title, flags=re.IGNORECASE) title = re.sub(ur'\[Español[^\[]*]', 'SPANISH AUDIO', title, flags=re.IGNORECASE) - title = re.sub(u'\[AC3 5\.1 Español[^\[]*]', 'SPANISH AUDIO', title, flags=re.IGNORECASE) + title = re.sub(ur'\[AC3 5\.1 Español[^\[]*]', 'SPANISH AUDIO', title, flags=re.IGNORECASE) title += '-NEWPCT' diff --git a/sickbeard/providers/newznab.py b/sickbeard/providers/newznab.py index 9c458647dfa999ed6a58f5543b19cb66ed3297a8..9ca950d1a47a4870db88b35ebaa647dea347ca24 100644 --- a/sickbeard/providers/newznab.py +++ b/sickbeard/providers/newznab.py @@ -14,17 +14,18 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. # pylint: disable=too-many-instance-attributes,too-many-arguments import os import re import urllib import datetime +import time from bs4 import BeautifulSoup import sickbeard @@ -37,9 +38,10 @@ from sickbeard import db from sickbeard.common import Quality from sickrage.helper.encoding import ek, ss from sickrage.show.Show import Show -from sickrage.helper.common import try_int +from sickrage.helper.common import try_int, convert_size # from sickbeard.common import USER_AGENT from sickrage.providers.nzb.NZBProvider import NZBProvider +from sickbeard.common import cpu_presets class NewznabProvider(NZBProvider): @@ -332,6 +334,7 @@ class NewznabProvider(NZBProvider): search_url = ek(os.path.join, self.url, 'api?') + urllib.urlencode(params) logger.log(u"Search url: %s" % search_url, logger.DEBUG) data = self.get_url(search_url) + time.sleep(cpu_presets[sickbeard.CPU_PRESET]) if not data: return results @@ -357,15 +360,17 @@ class NewznabProvider(NZBProvider): continue seeders = leechers = None - size = try_int(item.size, -1) + torrent_size = item.size for attr in item.findAll('newznab:attr') + item.findAll('torznab:attr'): - size = try_int(attr['value'], -1) if attr['name'] == 'size' else size + torrent_size = attr['value'] if attr['name'] == 'size' else torrent_size seeders = try_int(attr['value'], 1) if attr['name'] == 'seeders' else seeders - leechers = try_int(attr['value'], 0) if attr['name'] == 'peers' else leechers + leechers = try_int(attr['value']) if attr['name'] == 'peers' else leechers - if not size or (torznab and (seeders is None or leechers is None)): + if not torrent_size or (torznab and (seeders is None or leechers is None)): continue + size = convert_size(torrent_size) or -1 + result = {'title': title, 'link': download_url, 'size': size, 'seeders': seeders, 'leechers': leechers} results.append(result) diff --git a/sickbeard/providers/nyaatorrents.py b/sickbeard/providers/nyaatorrents.py index fddc7b923db01d930122eabd6fa73dfdf8cacf40..e50ba7bf8dc98332e7bbfa077914ab5c030ef0f7 100644 --- a/sickbeard/providers/nyaatorrents.py +++ b/sickbeard/providers/nyaatorrents.py @@ -11,21 +11,22 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import urllib import re from sickbeard import logger from sickbeard import tvcache +from sickrage.helper.common import convert_size from sickrage.providers.torrent.TorrentProvider import TorrentProvider -class NyaaProvider(TorrentProvider): +class NyaaProvider(TorrentProvider): # pylint: disable=too-many-instance-attributes def __init__(self): TorrentProvider.__init__(self, "NyaaTorrents") @@ -45,14 +46,13 @@ class NyaaProvider(TorrentProvider): self.minleech = 0 self.confirmed = False - def search(self, search_strings, age=0, ep_obj=None): - if self.show and not self.show.is_anime: - return [] - + def search(self, search_strings, age=0, ep_obj=None): # pylint: disable=too-many-locals results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} + if self.show and not self.show.is_anime: + return results - for mode in search_strings.keys(): + for mode in search_strings: + items = [] logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_strings[mode]: if mode != 'RSS': @@ -67,21 +67,21 @@ class NyaaProvider(TorrentProvider): if mode != 'RSS': params["term"] = search_string.encode('utf-8') - searchURL = self.url + '?' + urllib.urlencode(params) - logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) + search_url = self.url + '?' + urllib.urlencode(params) + logger.log(u"Search URL: %s" % search_url, logger.DEBUG) summary_regex = ur"(\d+) seeder\(s\), (\d+) leecher\(s\), \d+ download\(s\) - (\d+.?\d* [KMGT]iB)(.*)" s = re.compile(summary_regex, re.DOTALL) results = [] - for curItem in self.cache.getRSSFeed(searchURL)['entries'] or []: + for curItem in self.cache.getRSSFeed(search_url)['entries'] or []: title = curItem['title'] download_url = curItem['link'] if not all([title, download_url]): continue - seeders, leechers, size, verified = s.findall(curItem['summary'])[0] - size = self._convertSize(size) + seeders, leechers, torrent_size, verified = s.findall(curItem['summary'])[0] + size = convert_size(torrent_size) or -1 # Filter unseeded torrent if seeders < self.minseed or leechers < self.minleech: @@ -97,29 +97,14 @@ class NyaaProvider(TorrentProvider): if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) - items[mode].append(item) + items.append(item) # For each search mode sort all the items by seeders if available - items[mode].sort(key=lambda tup: tup[3], reverse=True) - - results += items[mode] + items.sort(key=lambda tup: tup[3], reverse=True) + results += items return results - @staticmethod - def _convertSize(size): - size, modifier = size.split(' ') - size = float(size) - if modifier in 'KiB': - size *= 1024 ** 1 - elif modifier in 'MiB': - size *= 1024 ** 2 - elif modifier in 'GiB': - size *= 1024 ** 3 - elif modifier in 'TiB': - size *= 1024 ** 4 - return long(size) - def seed_ratio(self): return self.ratio diff --git a/sickbeard/providers/omgwtfnzbs.py b/sickbeard/providers/omgwtfnzbs.py index cabb18cc61df0b058e0145b1e462e75fe0e1ea45..8b1dbda3a24ae5244a6d73b8e35e5d0c0c4551e1 100644 --- a/sickbeard/providers/omgwtfnzbs.py +++ b/sickbeard/providers/omgwtfnzbs.py @@ -24,7 +24,6 @@ import sickbeard from sickbeard import tvcache from sickbeard import classes from sickbeard import logger -from sickbeard import show_name_helpers from sickrage.helper.common import try_int from sickrage.providers.nzb.NZBProvider import NZBProvider @@ -37,13 +36,17 @@ class OmgwtfnzbsProvider(NZBProvider): self.api_key = None self.cache = OmgwtfnzbsCache(self) - self.urls = {'base_url': 'https://omgwtfnzbs.org/'} - self.url = self.urls['base_url'] + self.url = 'https://omgwtfnzbs.org/' + self.urls = { + 'rss': 'https://rss.omgwtfnzbs.org/rss-download.php', + 'api': 'https://api.omgwtfnzbs.org/json/' + } def _check_auth(self): if not self.username or not self.api_key: logger.log(u"Invalid api key. Check your settings", logger.WARNING) + return False return True @@ -56,15 +59,13 @@ class OmgwtfnzbsProvider(NZBProvider): # provider doesn't return xml on error return True else: - parsedJSON = parsed_data + if 'notice' in parsed_data: + description_text = parsed_data.get('notice') - if 'notice' in parsedJSON: - description_text = parsedJSON.get('notice') - - if 'information is incorrect' in parsedJSON.get('notice'): + if 'information is incorrect' in parsed_data.get('notice'): logger.log(u"Invalid api key. Check your settings", logger.WARNING) - elif '0 results matched your terms' in parsedJSON.get('notice'): + elif '0 results matched your terms' in parsed_data.get('notice'): return True else: @@ -73,51 +74,54 @@ class OmgwtfnzbsProvider(NZBProvider): return True - def _get_season_search_strings(self, ep_obj): - return [x for x in show_name_helpers.makeSceneSeasonSearchString(self.show, ep_obj)] - - def _get_episode_search_strings(self, ep_obj, add_string=''): - return [x for x in show_name_helpers.makeSceneSearchString(self.show, ep_obj)] - def _get_title_and_url(self, item): return item['release'], item['getnzb'] def _get_size(self, item): return try_int(item['sizebytes'], -1) - def search(self, search, age=0, ep_obj=None): + def search(self, search_strings, age=0, ep_obj=None): + results = [] + if not self._check_auth(): + return results + + search_params = { + 'user': self.username, + 'api': self.api_key, + 'eng': 1, + 'catid': '19,20', # SD,HD + 'retention': sickbeard.USENET_RETENTION, + } + if age or not search_params['retention']: + search_params['retention'] = age - self._check_auth() + for mode in search_strings: + items = [] + logger.log(u"Search Mode: %s" % mode, logger.DEBUG) + for search_string in search_strings[mode]: - params = {'user': self.username, - 'api': self.api_key, - 'eng': 1, - 'catid': '19,20', # SD,HD - 'retention': sickbeard.USENET_RETENTION, - 'search': search} + search_params['search'] = search_string - if age or not params['retention']: - params['retention'] = age + if mode != 'RSS': + logger.log(u"Search string: %s " % search_string, logger.DEBUG) - searchURL = 'https://api.omgwtfnzbs.org/json/?' + urllib.urlencode(params) - logger.log(u"Search string: %s" % params, logger.DEBUG) - logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) + logger.log(u"Search URL: %s" % self.urls['api'] + '?' + urllib.urlencode(search_params), logger.DEBUG) - parsedJSON = self.get_url(searchURL, json=True) - if not parsedJSON: - return [] + data = self.get_url(self.urls['api'], params=search_params, json=True) + if not data: + continue - if self._checkAuthFromData(parsedJSON, is_XML=False): - results = [] + if self._checkAuthFromData(data, is_XML=False): + continue - for item in parsedJSON: - if 'release' in item and 'getnzb' in item: - logger.log(u"Found result: %s " % item.get('title'), logger.DEBUG) - results.append(item) + for item in data: + if 'release' in item and 'getnzb' in item: + logger.log(u"Found result: %s " % item.get('title'), logger.DEBUG) + items.append(item) - return results + results += items - return [] + return results def find_propers(self, search_date=None): search_terms = ['.PROPER.', '.REPACK.'] @@ -126,7 +130,6 @@ class OmgwtfnzbsProvider(NZBProvider): for term in search_terms: for item in self.search(term, age=4): if 'usenetage' in item: - title, url = self._get_title_and_url(item) try: result_date = datetime.fromtimestamp(int(item['usenetage'])) @@ -165,12 +168,14 @@ class OmgwtfnzbsCache(tvcache.TVCache): return title, url def _getRSSData(self): - params = {'user': provider.username, - 'api': provider.api_key, - 'eng': 1, - 'catid': '19,20'} # SD,HD - - rss_url = 'https://rss.omgwtfnzbs.org/rss-download.php?' + urllib.urlencode(params) + search_params = { + 'user': provider.username, + 'api': provider.api_key, + 'eng': 1, + 'catid': '19,20' # SD,HD + } + + rss_url = self.provider.urls['rss'] + '?' + urllib.urlencode(search_params) logger.log(u"Cache update URL: %s" % rss_url, logger.DEBUG) diff --git a/sickbeard/providers/pretome.py b/sickbeard/providers/pretome.py index 96fcf5ede873ffd568e784f00bf6a6a74b1b5a98..121ea1538a178242e1068f03e2638cfd013d1cb0 100644 --- a/sickbeard/providers/pretome.py +++ b/sickbeard/providers/pretome.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import re import urllib @@ -24,11 +24,11 @@ import traceback from sickbeard import logger from sickbeard import tvcache from sickbeard.bs4_parser import BS4Parser +from sickrage.helper.common import convert_size from sickrage.providers.torrent.TorrentProvider import TorrentProvider -class PretomeProvider(TorrentProvider): - +class PretomeProvider(TorrentProvider): # pylint: disable=too-many-instance-attributes def __init__(self): TorrentProvider.__init__(self, "Pretome") @@ -78,25 +78,23 @@ class PretomeProvider(TorrentProvider): return True - def search(self, search_params, age=0, ep_obj=None): - + def search(self, search_params, age=0, ep_obj=None): # pylint: disable=too-many-branches, too-many-statements, too-many-locals results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} - if not self.login(): return results - for mode in search_params.keys(): + for mode in search_params: + items = [] logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_params[mode]: if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) - searchURL = self.urls['search'] % (urllib.quote(search_string.encode('utf-8')), self.categories) - logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) + search_url = self.urls['search'] % (urllib.quote(search_string.encode('utf-8')), self.categories) + logger.log(u"Search URL: %s" % search_url, logger.DEBUG) - data = self.get_url(searchURL) + data = self.get_url(search_url) if not data: continue @@ -134,10 +132,8 @@ class PretomeProvider(TorrentProvider): # Need size for failed downloads handling if size is None: - if re.match(r'[0-9]+,?\.?[0-9]*[KkMmGg]+[Bb]+', cells[7].text): - size = self._convertSize(cells[7].text) - if not size: - size = -1 + torrent_size = cells[7].text + size = convert_size(torrent_size) or -1 except (AttributeError, TypeError): continue @@ -155,35 +151,21 @@ class PretomeProvider(TorrentProvider): if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) - items[mode].append(item) + items.append(item) - except Exception as e: + except Exception: logger.log(u"Failed parsing provider. Traceback: %s" % traceback.format_exc(), logger.ERROR) # For each search mode sort all the items by seeders if available - items[mode].sort(key=lambda tup: tup[3], reverse=True) + items.sort(key=lambda tup: tup[3], reverse=True) - results += items[mode] + results += items return results def seed_ratio(self): return self.ratio - def _convertSize(self, sizeString): - size = sizeString[:-2] - modifier = sizeString[-2:] - size = float(size) - if modifier in 'KB': - size *= 1024 ** 1 - elif modifier in 'MB': - size *= 1024 ** 2 - elif modifier in 'GB': - size *= 1024 ** 3 - elif modifier in 'TB': - size *= 1024 ** 4 - return long(size) - class PretomeCache(tvcache.TVCache): def __init__(self, provider_obj): diff --git a/sickbeard/providers/rarbg.py b/sickbeard/providers/rarbg.py index b13b62597709ebf13ce143ee834ac65588ef64d5..440a8f0c0adb4f8cfaefe800b51cde58d53564c2 100644 --- a/sickbeard/providers/rarbg.py +++ b/sickbeard/providers/rarbg.py @@ -1,6 +1,6 @@ # coding=utf-8 -# Author: Nic Wolfe <nic@wolfeden.ca> -# URL: http://code.google.com/p/sickbeard/ +# Author: Dustyn Gibson <miigotu@gmail.com> +# URL: https://sickrage.github.io # # This file is part of SickRage. # @@ -11,30 +11,24 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. -import traceback -import re -import datetime -import json import time +import datetime +from urllib import urlencode -from sickbeard import logger -from sickbeard import tvcache -from sickbeard.common import USER_AGENT +from sickbeard import logger, tvcache from sickbeard.indexers.indexer_config import INDEXER_TVDB +from sickrage.helper.common import try_int +from sickrage.helper.common import convert_size from sickrage.providers.torrent.TorrentProvider import TorrentProvider -class GetOutOfLoop(Exception): - pass - - -class RarbgProvider(TorrentProvider): +class RarbgProvider(TorrentProvider): # pylint: disable=too-many-instance-attributes def __init__(self): TorrentProvider.__init__(self, "Rarbg") @@ -46,68 +40,51 @@ class RarbgProvider(TorrentProvider): self.sorting = None self.minleech = None self.token = None - self.tokenExpireDate = None - - self.urls = {'url': u'https://rarbg.com', - 'token': u'http://torrentapi.org/pubapi_v2.php?get_token=get_token&format=json&app_id=sickrage2', - 'listing': u'http://torrentapi.org/pubapi_v2.php?mode=list&app_id=sickrage2', - 'search': u'http://torrentapi.org/pubapi_v2.php?mode=search&app_id=sickrage2&search_string={search_string}', - 'search_tvdb': u'http://torrentapi.org/pubapi_v2.php?mode=search&app_id=sickrage2&search_tvdb={tvdb}&search_string={search_string}', - 'api_spec': u'https://rarbg.com/pubapi/apidocs.txt'} - - self.url = self.urls['listing'] - - self.urlOptions = { - 'categories': '&category={categories}', - 'seeders': '&min_seeders={min_seeders}', - 'leechers': '&min_leechers={min_leechers}', - 'sorting': '&sort={sorting}', - 'limit': '&limit={limit}', - 'format': '&format={format}', - 'ranked': '&ranked={ranked}', - 'token': '&token={token}' - } + self.token_expires = None - self.defaultOptions = self.urlOptions['categories'].format(categories='tv') + \ - self.urlOptions['limit'].format(limit='100') + \ - self.urlOptions['format'].format(format='json_extended') + # Spec: https://torrentapi.org/apidocs_v2.txt + self.url = u'https://rarbg.com' + self.url_api = u'http://torrentapi.org/pubapi_v2.php' self.proper_strings = ['{{PROPER|REPACK}}'] - self.next_request = datetime.datetime.now() - - self.headers.update({'User-Agent': USER_AGENT}) - self.cache = RarbgCache(self) def login(self): - if self.token and self.tokenExpireDate and datetime.datetime.now() < self.tokenExpireDate: + if self.token and self.token_expires and datetime.datetime.now() < self.token_expires: return True - response = self.get_url(self.urls['token'], timeout=30, json=True) + login_params = { + 'get_token': 'get_token', + 'format': 'json', + 'app_id': 'sickrage2' + } + + response = self.get_url(self.url_api, params=login_params, timeout=30, json=True) if not response: logger.log(u"Unable to connect to provider", logger.WARNING) return False - try: - if response['token']: - self.token = response['token'] - self.tokenExpireDate = datetime.datetime.now() + datetime.timedelta(minutes=14) - return True - except Exception as e: - logger.log(u"No token found", logger.WARNING) - logger.log(u"No token found: %s" % repr(e), logger.DEBUG) - - return False - - def search(self, search_params, age=0, ep_obj=None): + self.token = response.get('token') + self.token_expires = datetime.datetime.now() + datetime.timedelta(minutes=14) if self.token else None + return self.token is not None + def search(self, search_strings, age=0, ep_obj=None): # pylint: disable=too-many-branches, too-many-locals results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} - if not self.login(): return results + search_params = { + 'app_id': 'sickrage2', + 'categories': 'tv', + 'seeders': try_int(self.minseed), + 'leechers': try_int(self.minleech), + 'limit': 100, + 'format': 'json_extended', + 'ranked': try_int(self.ranked), + 'token': self.token, + } + if ep_obj is not None: ep_indexerid = ep_obj.show.indexerid ep_indexer = ep_obj.show.indexer @@ -115,130 +92,59 @@ class RarbgProvider(TorrentProvider): ep_indexerid = None ep_indexer = None - for mode in search_params.keys(): # Mode = RSS, Season, Episode + for mode in search_strings: + items = [] logger.log(u"Search Mode: %s" % mode, logger.DEBUG) - for search_string in search_params[mode]: + if mode == 'RSS': + search_params['sorting'] = 'last' + search_params['mode'] = 'list' + search_params.pop('search_string', None) + search_params.pop('search_tvdb', None) + else: + + search_params['sorting'] = self.sorting if self.sorting else 'seeders' + search_params['mode'] = 'search' + + if ep_indexer == INDEXER_TVDB and ep_indexerid: + search_params['search_tvdb'] = ep_indexerid + else: + search_params.pop('search_tvdb', None) + for search_string in search_strings[mode]: if mode != 'RSS': + search_params['search_string'] = search_string logger.log(u"Search string: %s " % search_string, logger.DEBUG) - if mode == 'RSS': - searchURL = self.urls['listing'] + self.defaultOptions - elif mode == 'Season': - if ep_indexer == INDEXER_TVDB: - searchURL = self.urls['search_tvdb'].format(search_string=search_string, tvdb=ep_indexerid) + self.defaultOptions - else: - searchURL = self.urls['search'].format(search_string=search_string) + self.defaultOptions - elif mode == 'Episode': - if ep_indexer == INDEXER_TVDB: - searchURL = self.urls['search_tvdb'].format(search_string=search_string, tvdb=ep_indexerid) + self.defaultOptions - else: - searchURL = self.urls['search'].format(search_string=search_string) + self.defaultOptions - else: - logger.log(u"Invalid search mode: %s " % mode, logger.ERROR) - - if self.minleech: - searchURL += self.urlOptions['leechers'].format(min_leechers=int(self.minleech)) - - if self.minseed: - searchURL += self.urlOptions['seeders'].format(min_seeders=int(self.minseed)) - - searchURL += self.urlOptions['sorting'].format(sorting=(self.sorting if self.sorting else 'seeders', 'last')[mode == 'RSS']) - - if self.ranked: - searchURL += self.urlOptions['ranked'].format(ranked=int(self.ranked)) - - logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) - - try: - retry = 3 - while retry > 0: - time_out = 0 - while (datetime.datetime.now() < self.next_request) and time_out <= 15: - time_out += 1 - time.sleep(1) - - data = self.get_url(searchURL + self.urlOptions['token'].format(token=self.token)) - - self.next_request = datetime.datetime.now() + datetime.timedelta(seconds=10) - - if not data: - logger.log(u"No data returned from provider", logger.DEBUG) - raise GetOutOfLoop - if re.search('ERROR', data): - logger.log(u"Error returned from provider", logger.DEBUG) - raise GetOutOfLoop - if re.search('No results found', data): - logger.log(u"No results found", logger.DEBUG) - raise GetOutOfLoop - if re.search('Invalid token set!', data): - logger.log(u"Invalid token!", logger.WARNING) - return results - if re.search('Too many requests per minute. Please try again later!', data): - logger.log(u"Too many requests per minute", logger.WARNING) - retry -= 1 - time.sleep(10) - continue - if re.search('Cant find search_tvdb in database. Are you sure this imdb exists?', data): - logger.log(u"No results found. The tvdb id: %s do not exist on provider" % ep_indexerid, logger.WARNING) - raise GetOutOfLoop - if re.search('Invalid token. Use get_token for a new one!', data): - logger.log(u"Invalid token, retrieving new token", logger.DEBUG) - retry -= 1 - self.token = None - self.tokenExpireDate = None - if not self.login(): - logger.log(u"Failed retrieving new token", logger.DEBUG) - return results - logger.log(u"Using new token", logger.DEBUG) - continue - - # No error found break - break - else: - logger.log(u"Retried 3 times without getting results", logger.DEBUG) - continue - except GetOutOfLoop: + logger.log(u"Search URL: %s" % self.url_api + '?' + urlencode(search_params), logger.DEBUG) + data = self.get_url(self.url_api, params=search_params, json=True) + if not all([isinstance(data, dict), data.get('torrent_results')]): + logger.log(u"No data returned from provider", logger.DEBUG) continue - try: - data = re.search(r'\[\{\"title\".*\}\]', data) - if data is not None: - data_json = json.loads(data.group()) - else: - data_json = {} - except Exception: - logger.log(u"JSON load failed: %s" % traceback.format_exc(), logger.ERROR) - logger.log(u"JSON load failed. Data dump: %s" % data, logger.DEBUG) - continue - - try: - for item in data_json: - try: - title = item['title'] - download_url = item['download'] - size = item['size'] - seeders = item['seeders'] - leechers = item['leechers'] - # pubdate = item['pubdate'] - - if not all([title, download_url]): - continue + for item in data.get('torrent_results', []): + try: + title = item.get('title') + download_url = item.get('download') + seeders = item.get('seeders', 0) + leechers = item.get('leechers', 0) + size = convert_size(item.get('size', -1)) or -1 + except Exception: + logger.log(u"Skipping invalid result. JSON item: %s" % item, logger.DEBUG) + continue - item = title, download_url, size, seeders, leechers - if mode != 'RSS': - logger.log(u"Found result: %s " % title, logger.DEBUG) - items[mode].append(item) + if not all([title, download_url]): + continue - except Exception: - logger.log(u"Skipping invalid result. JSON item: %s" % item, logger.DEBUG) + item = title, download_url, size, seeders, leechers + if mode != 'RSS': + logger.log(u"Found result: %s " % title, logger.DEBUG) + items.append(item) - except Exception: - logger.log(u"Failed parsing provider. Traceback: %s" % traceback.format_exc(), logger.ERROR) + time.sleep(10) # For each search mode sort all the items by seeders - items[mode].sort(key=lambda tup: tup[3], reverse=True) - results += items[mode] + items.sort(key=lambda tup: tup[3], reverse=True) + results += items return results @@ -255,8 +161,8 @@ class RarbgCache(tvcache.TVCache): self.minTime = 10 def _getRSSData(self): - search_params = {'RSS': ['']} - return {'entries': self.provider.search(search_params)} + search_strings = {'RSS': ['']} + return {'entries': self.provider.search(search_strings)} provider = RarbgProvider() diff --git a/sickbeard/providers/rsstorrent.py b/sickbeard/providers/rsstorrent.py index 411bc2a4c0ea26fc99807b23c63ff2de602c51cb..eb2cde31cb036fb1cd275f0ca6c4675c2cc65a36 100644 --- a/sickbeard/providers/rsstorrent.py +++ b/sickbeard/providers/rsstorrent.py @@ -10,11 +10,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import io import os diff --git a/sickbeard/providers/scc.py b/sickbeard/providers/scc.py index 0aac6ce591f12b9fcd8d9e56e8119a0a1a3af58b..250b2e1ec12b69cea4c8e0b10953d51f0c6a6e3e 100644 --- a/sickbeard/providers/scc.py +++ b/sickbeard/providers/scc.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import re import time @@ -26,6 +26,7 @@ import sickbeard from sickbeard.common import cpu_presets from sickbeard import logger from sickbeard import tvcache +from sickrage.helper.common import convert_size from sickbeard.bs4_parser import BS4Parser from sickrage.providers.torrent.TorrentProvider import TorrentProvider @@ -85,26 +86,24 @@ class SCCProvider(TorrentProvider): # pylint: disable=too-many-instance-attribu title = r'<title>.+? \| %s</title>' % section return re.search(title, text, re.IGNORECASE) - def search(self, search_strings, age=0, ep_obj=None): # pylint: disable=too-many-locals,too-many-branches - - items = {'Season': [], 'Episode': [], 'RSS': []} + def search(self, search_strings, age=0, ep_obj=None): # pylint: disable=too-many-locals,too-many-branches, too-many-statements results = [] - if not self.login(): return results - for mode in search_strings.keys(): + for mode in search_strings: + items = [] if mode != 'RSS': logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_strings[mode]: if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) - searchURL = self.urls['search'] % (urllib.quote(search_string), self.categories[mode]) + search_url = self.urls['search'] % (urllib.quote(search_string), self.categories[mode]) try: - logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) - data = self.get_url(searchURL) + logger.log(u"Search URL: %s" % search_url, logger.DEBUG) + data = self.get_url(search_url) time.sleep(cpu_presets[sickbeard.CPU_PRESET]) except Exception as e: logger.log(u"Unable to fetch data. Error: %s" % repr(e), logger.WARNING) @@ -136,7 +135,9 @@ class SCCProvider(TorrentProvider): # pylint: disable=too-many-instance-attribu download_url = self.urls['download'] % url['href'] seeders = int(result.find('td', attrs={'class': 'ttr_seeders'}).string) leechers = int(result.find('td', attrs={'class': 'ttr_leechers'}).string) - size = self._convertSize(result.find('td', attrs={'class': 'ttr_size'}).contents[0]) + torrent_size = result.find('td', attrs={'class': 'ttr_size'}).contents[0] + + size = convert_size(torrent_size) or -1 except (AttributeError, TypeError): continue @@ -153,32 +154,18 @@ class SCCProvider(TorrentProvider): # pylint: disable=too-many-instance-attribu if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) - items[mode].append(item) + items.append(item) # For each search mode sort all the items by seeders if available - items[mode].sort(key=lambda tup: tup[3], reverse=True) + items.sort(key=lambda tup: tup[3], reverse=True) - results += items[mode] + results += items return results def seed_ratio(self): return self.ratio - @staticmethod - def _convertSize(size): - size, base = size.split() - size = float(size) - if base in 'KB': - size *= 1024 ** 1 - elif base in 'MB': - size *= 1024 ** 2 - elif base in 'GB': - size *= 1024 ** 3 - elif base in 'TB': - size *= 1024 ** 4 - return long(size) - class SCCCache(tvcache.TVCache): def __init__(self, provider_obj): diff --git a/sickbeard/providers/scenetime.py b/sickbeard/providers/scenetime.py index 351761c2c91f3031597636a1900219d5de2d40f5..8e3eba84fb7153e1586852eb50dbbe9ec73d0c17 100644 --- a/sickbeard/providers/scenetime.py +++ b/sickbeard/providers/scenetime.py @@ -11,23 +11,25 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import re import urllib -import traceback from sickbeard import logger from sickbeard import tvcache from sickbeard.bs4_parser import BS4Parser +from sickrage.helper.common import convert_size from sickrage.providers.torrent.TorrentProvider import TorrentProvider +from sickrage.helper.common import try_int -class SceneTimeProvider(TorrentProvider): + +class SceneTimeProvider(TorrentProvider): # pylint: disable=too-many-instance-attributes def __init__(self): @@ -67,86 +69,80 @@ class SceneTimeProvider(TorrentProvider): return True - def search(self, search_params, age=0, ep_obj=None): - + def search(self, search_params, age=0, ep_obj=None): # pylint: disable=too-many-branches, too-many-locals results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} - if not self.login(): return results - for mode in search_params.keys(): + for mode in search_params: + items = [] logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_params[mode]: if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) - searchURL = self.urls['search'] % (urllib.quote(search_string), self.categories) - logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) + search_url = self.urls['search'] % (urllib.quote(search_string), self.categories) + logger.log(u"Search URL: %s" % search_url, logger.DEBUG) - data = self.get_url(searchURL) + data = self.get_url(search_url) if not data: continue - try: - with BS4Parser(data, 'html5lib') as html: - torrent_table = html.select("#torrenttable table") - torrent_rows = torrent_table[0].select("tr") if torrent_table else [] + with BS4Parser(data, 'html5lib') as html: + torrent_table = html.find('div', id="torrenttable") + torrent_rows = [] + if torrent_table: + torrent_rows = torrent_table.select("tr") - # Continue only if one Release is found - if len(torrent_rows) < 2: - logger.log(u"Data returned from provider does not contain any torrents", logger.DEBUG) - continue + # Continue only if one Release is found + if len(torrent_rows) < 2: + logger.log(u"Data returned from provider does not contain any torrents", logger.DEBUG) + continue - # Scenetime apparently uses different number of cells in #torrenttable based - # on who you are. This works around that by extracting labels from the first - # <tr> and using their index to find the correct download/seeders/leechers td. - labels = [label.get_text() for label in torrent_rows[0].find_all('td')] + # Scenetime apparently uses different number of cells in #torrenttable based + # on who you are. This works around that by extracting labels from the first + # <tr> and using their index to find the correct download/seeders/leechers td. + labels = [label.get_text(strip=True) for label in torrent_rows[0].find_all('td')] - for result in torrent_rows[1:]: + for result in torrent_rows[1:]: + try: cells = result.find_all('td') link = cells[labels.index('Name')].find('a') + torrent_id = link['href'].replace('details.php?id=', '').split("&")[0] - full_id = link['href'].replace('details.php?id=', '') - torrent_id = full_id.split("&")[0] + title = link.get_text(strip=True) + download_url = self.urls['download'] % (torrent_id, "%s.torrent" % title.replace(" ", ".")) - try: - title = link.contents[0].get_text() - filename = "%s.torrent" % title.replace(" ", ".") - download_url = self.urls['download'] % (torrent_id, filename) + seeders = try_int(cells[labels.index('Seeders')].get_text(strip=True)) + leechers = try_int(cells[labels.index('Leechers')].get_text(strip=True)) + torrent_size = cells[labels.index('Size')].get_text() - seeders = int(cells[labels.index('Seeders')].get_text()) - leechers = int(cells[labels.index('Leechers')].get_text()) - # FIXME - size = -1 + size = convert_size(torrent_size) or -1 - except (AttributeError, TypeError): - continue - - if not all([title, download_url]): - continue + except (AttributeError, TypeError, KeyError, ValueError): + continue - # Filter unseeded torrent - if seeders < self.minseed or leechers < self.minleech: - if mode != 'RSS': - logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) - continue + if not all([title, download_url]): + continue - item = title, download_url, size, seeders, leechers + # Filter unseeded torrent + if seeders < self.minseed or leechers < self.minleech: if mode != 'RSS': - logger.log(u"Found result: %s " % title, logger.DEBUG) + logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) + continue - items[mode].append(item) + item = title, download_url, size, seeders, leechers + if mode != 'RSS': + logger.log(u"Found result: %s " % title, logger.DEBUG) - except Exception as e: - logger.log(u"Failed parsing provider. Traceback: %s" % traceback.format_exc(), logger.ERROR) + items.append(item) # For each search mode sort all the items by seeders if available - items[mode].sort(key=lambda tup: tup[3], reverse=True) + items.sort(key=lambda tup: tup[3], reverse=True) - results += items[mode] + results += items return results diff --git a/sickbeard/providers/shazbat.py b/sickbeard/providers/shazbat.py index 959d9e20e7faf8111db86d124c57ac37e5653542..940a35eef6fb70d2c05b4ea58d3945e226b45c22 100644 --- a/sickbeard/providers/shazbat.py +++ b/sickbeard/providers/shazbat.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. from sickbeard import logger from sickbeard import tvcache diff --git a/sickbeard/providers/speedcd.py b/sickbeard/providers/speedcd.py index d231611da1b1fa3896696c512fb76ec33e08e1a2..924dbcc0d326b8f1b347ba1a89ea88a5a3be07c3 100644 --- a/sickbeard/providers/speedcd.py +++ b/sickbeard/providers/speedcd.py @@ -1,6 +1,6 @@ # coding=utf-8 -# Author: Mr_Orange -# URL: https://github.com/mr-orange/Sick-Beard +# Author: Dustyn Gibson <miigotu@gmail.com> +# URL: https://sickrage.github.io # # This file is part of SickRage. # @@ -11,20 +11,24 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import re +from urllib import urlencode +from requests.utils import dict_from_cookiejar from sickbeard import logger from sickbeard import tvcache +from sickbeard.bs4_parser import BS4Parser +from sickrage.helper.common import try_int, convert_size from sickrage.providers.torrent.TorrentProvider import TorrentProvider -class SpeedCDProvider(TorrentProvider): +class SpeedCDProvider(TorrentProvider): # pylint: disable=too-many-instance-attributes def __init__(self): @@ -37,24 +41,24 @@ class SpeedCDProvider(TorrentProvider): self.minseed = None self.minleech = None - self.urls = {'base_url': 'http://speed.cd/', - 'login': 'http://speed.cd/take.login.php', - 'detail': 'http://speed.cd/t/%s', - 'search': 'http://speed.cd/V3/API/API.php', - 'download': 'http://speed.cd/download.php?torrent=%s'} - - self.url = self.urls['base_url'] - - self.categories = {'Season': {'c14': 1}, 'Episode': {'c2': 1, 'c49': 1}, 'RSS': {'c14': 1, 'c2': 1, 'c49': 1}} + self.url = 'http://speed.cd/' + self.urls = { + 'login': self.url + 'take.login.php', + 'search': self.url + 'browse.php', + } self.proper_strings = ['PROPER', 'REPACK'] self.cache = SpeedCDCache(self) def login(self): + if any(dict_from_cookiejar(self.session.cookies).values()): + return True - login_params = {'username': self.username, - 'password': self.password} + login_params = { + 'username': self.username, + 'password': self.password + } response = self.get_url(self.urls['login'], post_data=login_params, timeout=30) if not response: @@ -67,66 +71,96 @@ class SpeedCDProvider(TorrentProvider): return True - def search(self, search_params, age=0, ep_obj=None): - + def search(self, search_strings, age=0, ep_obj=None): # pylint: disable=too-many-locals, too-many-branches results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} - if not self.login(): return results - for mode in search_params.keys(): + # http://speed.cd/browse.php?c49=1&c50=1&c52=1&c41=1&c55=1&c2=1&c30=1&freeleech=on&search=arrow&d=on + search_params = { + 'c2': 1, # TV/Episodes + 'c30': 1, # Anime + 'c41': 1, # TV/Packs + 'c49': 1, # TV/HD + 'c50': 1, # TV/Sports + 'c52': 1, # TV/B-Ray + 'c55': 1, # TV/Kids + 'search': '', + } + if self.freeleech: + search_params['freeleech'] = 'on' + + for mode in search_strings: + items = [] logger.log(u"Search Mode: %s" % mode, logger.DEBUG) - for search_string in search_params[mode]: - + for search_string in search_strings[mode]: if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) - search_string = '+'.join(search_string.split()) - - post_data = dict({'/browse.php?': None, 'cata': 'yes', 'jxt': 4, 'jxw': 'b', 'search': search_string}, - **self.categories[mode]) + search_params['search'] = search_string - parsedJSON = self.get_url(self.urls['search'], post_data=post_data, json=True) - if not parsedJSON: - continue + search_url = "%s?%s" % (self.urls['search'], urlencode(search_params)) + logger.log(u"Search URL: %s" % search_url, logger.DEBUG) - try: - torrents = parsedJSON.get('Fs', [])[0].get('Cn', {}).get('torrents', []) - except Exception: + # returns top 15 results by default, expandable in user profile to 100 + data = self.get_url(search_url) + if not data: continue - for torrent in torrents: + with BS4Parser(data, 'html5lib') as html: + torrent_table = html.find('div', class_='boxContent').find('table') + torrent_rows = torrent_table.find_all('tr') if torrent_table else [] - if self.freeleech and not torrent['free']: + # Continue only if one Release is found + if len(torrent_rows) < 2: + logger.log(u"Data returned from provider does not contain any torrents", logger.DEBUG) continue - title = re.sub('<[^>]*>', '', torrent['name']) - download_url = self.urls['download'] % (torrent['id']) - seeders = int(torrent['seed']) - leechers = int(torrent['leech']) - # FIXME - size = -1 - - if not all([title, download_url]): - continue - - # Filter unseeded torrent - if seeders < self.minseed or leechers < self.minleech: - if mode != 'RSS': - logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) - continue - - item = title, download_url, size, seeders, leechers - if mode != 'RSS': - logger.log(u"Found result: %s " % title, logger.DEBUG) - - items[mode].append(item) + def process_column_header(td): + result = '' + if td.a and td.a.img: + result = td.a.img.get('alt', td.a.get_text(strip=True)) + if td.img and not result: + result = td.img.get('alt', '') + if not result: + result = td.get_text(strip=True) + return result + + labels = [process_column_header(label) for label in torrent_rows[0].find_all('th')] + + # skip colheader + for result in torrent_rows[1:]: + try: + cells = result.find_all('td') + + title = cells[labels.index('Title')].find('a', class_='torrent').get_text() + download_url = self.url + cells[labels.index('Download')].find(title='Download').parent['href'] + if not all([title, download_url]): + continue + + seeders = try_int(cells[labels.index('Seeders')].get_text(strip=True)) + leechers = try_int(cells[labels.index('Leechers')].get_text(strip=True)) + if seeders < self.minseed or leechers < self.minleech: + if mode != 'RSS': + logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) + continue + + torrent_size = cells[labels.index('Size')].get_text() + # TODO: Make convert_size work with 123.12GB + torrent_size = torrent_size[:-2] + ' ' + torrent_size[-2:] + size = convert_size(torrent_size) or -1 + + item = title, download_url, size, seeders, leechers + if mode != 'RSS': + logger.log(u"Found result: %s " % title, logger.DEBUG) + + items.append(item) + except StandardError: + continue # For each search mode sort all the items by seeders if available - items[mode].sort(key=lambda tup: tup[3], reverse=True) - - results += items[mode] + items.sort(key=lambda tup: tup[3], reverse=True) + results += items return results @@ -143,7 +177,8 @@ class SpeedCDCache(tvcache.TVCache): self.minTime = 20 def _getRSSData(self): - search_params = {'RSS': ['']} - return {'entries': self.provider.search(search_params)} + search_strings = {'RSS': ['']} + return {'entries': self.provider.search(search_strings)} + provider = SpeedCDProvider() diff --git a/sickbeard/providers/strike.py b/sickbeard/providers/strike.py index d48b0f9ea96ea9ff15ceaeab364d700c23d146db..c3955871da1a408fb8bd4d9677f626939d43dfd8 100644 --- a/sickbeard/providers/strike.py +++ b/sickbeard/providers/strike.py @@ -11,18 +11,19 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. from sickbeard import logger from sickbeard import tvcache +from sickrage.helper.common import convert_size from sickrage.providers.torrent.TorrentProvider import TorrentProvider -class STRIKEProvider(TorrentProvider): +class StrikeProvider(TorrentProvider): def __init__(self): TorrentProvider.__init__(self, "Strike") @@ -34,20 +35,18 @@ class STRIKEProvider(TorrentProvider): self.minseed, self.minleech = 2 * [None] def search(self, search_strings, age=0, ep_obj=None): - results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} - - for mode in search_strings.keys(): # Mode = RSS, Season, Episode + for mode in search_strings: # Mode = RSS, Season, Episode + items = [] logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_strings[mode]: if mode != 'RSS': logger.log(u"Search string: " + search_string.strip(), logger.DEBUG) - searchURL = self.url + "api/v2/torrents/search/?category=TV&phrase=" + search_string - logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) - jdata = self.get_url(searchURL, json=True) + search_url = self.url + "api/v2/torrents/search/?category=TV&phrase=" + search_string + logger.log(u"Search URL: %s" % search_url, logger.DEBUG) + jdata = self.get_url(search_url, json=True) if not jdata: logger.log(u"No data returned from provider", logger.DEBUG) return [] @@ -58,7 +57,8 @@ class STRIKEProvider(TorrentProvider): seeders = ('seeds' in item and item['seeds']) or 0 leechers = ('leeches' in item and item['leeches']) or 0 title = ('torrent_title' in item and item['torrent_title']) or '' - size = ('size' in item and item['size']) or 0 + torrent_size = ('size' in item and item['size']) + size = convert_size(torrent_size) or -1 download_url = ('magnet_uri' in item and item['magnet_uri']) or '' if not all([title, download_url]): @@ -74,12 +74,12 @@ class STRIKEProvider(TorrentProvider): logger.log(u"Found result: %s " % title, logger.DEBUG) item = title, download_url, size, seeders, leechers - items[mode].append(item) + items.append(item) # For each search mode sort all the items by seeders if available - items[mode].sort(key=lambda tup: tup[3], reverse=True) + items.sort(key=lambda tup: tup[3], reverse=True) - results += items[mode] + results += items return results @@ -101,4 +101,4 @@ class StrikeCache(tvcache.TVCache): search_params = {'RSS': ['x264']} return {'entries': self.provider.search(search_params)} -provider = STRIKEProvider() +provider = StrikeProvider() diff --git a/sickbeard/providers/t411.py b/sickbeard/providers/t411.py index 9ef558461bbd259749e1500bffd3ecb908d0d6ea..4775e25bf6f119c4f0691ccb61ba6b5197b9476f 100644 --- a/sickbeard/providers/t411.py +++ b/sickbeard/providers/t411.py @@ -11,7 +11,7 @@ # # Sick Beard is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License @@ -24,10 +24,11 @@ from requests.auth import AuthBase from sickbeard import logger from sickbeard import tvcache from sickbeard.common import USER_AGENT +from sickrage.helper.common import convert_size from sickrage.providers.torrent.TorrentProvider import TorrentProvider -class T411Provider(TorrentProvider): +class T411Provider(TorrentProvider): # pylint: disable=too-many-instance-attributes def __init__(self): TorrentProvider.__init__(self, "T411") @@ -72,32 +73,30 @@ class T411Provider(TorrentProvider): if response and 'token' in response: self.token = response['token'] self.tokenLastUpdate = time.time() - self.uid = response['uid'].encode('ascii', 'ignore') + # self.uid = response['uid'].encode('ascii', 'ignore') self.session.auth = T411Auth(self.token) return True else: logger.log(u"Token not found in authentication response", logger.WARNING) return False - def search(self, search_params, age=0, ep_obj=None): - + def search(self, search_params, age=0, ep_obj=None): # pylint: disable=too-many-branches, too-many-locals, too-many-statements results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} - if not self.login(): return results - for mode in search_params.keys(): + for mode in search_params: + items = [] logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_params[mode]: if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) - searchURLS = ([self.urls['search'] % (search_string, u) for u in self.subcategories], [self.urls['rss']])[mode == 'RSS'] - for searchURL in searchURLS: - logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) - data = self.get_url(searchURL, json=True) + search_urlS = ([self.urls['search'] % (search_string, u) for u in self.subcategories], [self.urls['rss']])[mode == 'RSS'] + for search_url in search_urlS: + logger.log(u"Search URL: %s" % search_url, logger.DEBUG) + data = self.get_url(search_url, json=True) if not data: continue @@ -123,10 +122,11 @@ class T411Provider(TorrentProvider): if not all([title, download_url]): continue - size = int(torrent['size']) seeders = int(torrent['seeders']) leechers = int(torrent['leechers']) verified = bool(torrent['isVerified']) + torrent_size = torrent['size'] + size = convert_size(torrent_size) or -1 # Filter unseeded torrent if seeders < self.minseed or leechers < self.minleech: @@ -142,7 +142,7 @@ class T411Provider(TorrentProvider): if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) - items[mode].append(item) + items.append(item) except Exception: logger.log(u"Invalid torrent data, skipping result: %s" % torrent, logger.DEBUG) @@ -153,9 +153,9 @@ class T411Provider(TorrentProvider): logger.log(u"Failed parsing provider. Traceback: %s" % traceback.format_exc(), logger.ERROR) # For each search mode sort all the items by seeders if available if available - items[mode].sort(key=lambda tup: tup[3], reverse=True) + items.sort(key=lambda tup: tup[3], reverse=True) - results += items[mode] + results += items return results @@ -163,7 +163,7 @@ class T411Provider(TorrentProvider): return self.ratio -class T411Auth(AuthBase): +class T411Auth(AuthBase): # pylint: disable=too-few-public-methods """Attaches HTTP Authentication to the given Request object.""" def __init__(self, token): self.token = token diff --git a/sickbeard/providers/thepiratebay.py b/sickbeard/providers/thepiratebay.py index 56b05cf07403c77ac593fb6e0f17094bb81bf77a..f0c680e6108ac8e614dd821e7a09f9d5148a01c0 100644 --- a/sickbeard/providers/thepiratebay.py +++ b/sickbeard/providers/thepiratebay.py @@ -1,6 +1,6 @@ # coding=utf-8 -# Author: Mr_Orange <mr_orange@hotmail.it> -# URL: http://code.google.com/p/sickbeard/ +# Author: Dustyn Gibson <miigotu@gmail.com> +# URL: https://sickrage.github.io # # This file is part of SickRage. # @@ -11,22 +11,23 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import re import posixpath # Must use posixpath from urllib import urlencode from sickbeard import logger from sickbeard import tvcache -from sickbeard.common import USER_AGENT +from sickbeard.bs4_parser import BS4Parser +from sickrage.helper.common import try_int, convert_size from sickrage.providers.torrent.TorrentProvider import TorrentProvider -class ThePirateBayProvider(TorrentProvider): +class ThePirateBayProvider(TorrentProvider): # pylint: disable=too-many-instance-attributes def __init__(self): TorrentProvider.__init__(self, "ThePirateBay") @@ -40,22 +41,21 @@ class ThePirateBayProvider(TorrentProvider): self.cache = ThePirateBayCache(self) + self.url = 'https://thepiratebay.ms/' self.urls = { - 'base_url': 'https://thepiratebay.ms/', - 'search': 'https://thepiratebay.ms/s/', - 'rss': 'https://thepiratebay.ms/tv/latest' + 'search': self.url + 's/', + 'rss': self.url + 'tv/latest' } - self.url = self.urls['base_url'] self.custom_url = None - self.headers.update({'User-Agent': USER_AGENT}) - + def search(self, search_strings, age=0, ep_obj=None): # pylint: disable=too-many-locals, too-many-branches + results = [] """ 205 = SD, 208 = HD, 200 = All Videos https://pirateproxy.pl/s/?q=Game of Thrones&type=search&orderby=7&page=0&category=200 """ - self.search_params = { + search_params = { 'q': '', 'type': 'search', 'orderby': 7, @@ -63,82 +63,86 @@ class ThePirateBayProvider(TorrentProvider): 'category': 200 } - self.re_title_url = r'/torrent/(?P<id>\d+)/(?P<title>.*?)".+?(?P<url>magnet.*?)".+?Size (?P<size>[\d\.]* [TGKMiB]{2,3}).+?(?P<seeders>\d+)</td>.+?(?P<leechers>\d+)</td>' - - def search(self, search_strings, age=0, ep_obj=None): - - results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} - - for mode in search_strings.keys(): + for mode in search_strings: + items = [] logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_strings[mode]: - - self.search_params.update({'q': search_string.strip()}) - if mode != 'RSS': - logger.log(u"Search string: " + search_string, logger.DEBUG) + logger.log(u"Search string: %s " % search_string, logger.DEBUG) - searchURL = self.urls[('search', 'rss')[mode == 'RSS']] + '?' + urlencode(self.search_params) + search_params['q'] = search_string.strip() + + search_url = self.urls[('search', 'rss')[mode == 'RSS']] + '?' + urlencode(search_params) if self.custom_url: - searchURL = posixpath.join(self.custom_url, searchURL.split(self.url)[1].lstrip('/')) # Must use posixpath + search_url = posixpath.join(self.custom_url, search_url.split(self.url)[1].lstrip('/')) # Must use posixpath + + logger.log(u"Search URL: %s" % search_url, logger.DEBUG) - logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) - data = self.get_url(searchURL) + data = self.get_url(search_url) if not data: logger.log(u'URL did not return data, maybe try a custom url, or a different one', logger.DEBUG) continue - matches = re.compile(self.re_title_url, re.DOTALL).finditer(data) - for torrent in matches: - title = torrent.group('title') - download_url = torrent.group('url') - # id = int(torrent.group('id')) - size = self._convertSize(torrent.group('size')) - seeders = int(torrent.group('seeders')) - leechers = int(torrent.group('leechers')) - - if not all([title, download_url]): - continue - - # Filter unseeded torrent - if seeders < self.minseed or leechers < self.minleech: - if mode != 'RSS': - logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) - continue + with BS4Parser(data, 'html5lib') as html: + torrent_table = html.find('table', id='searchResult') + torrent_rows = torrent_table.find_all('tr') if torrent_table else [] - # Accept Torrent only from Good People for every Episode Search - if self.confirmed and re.search(r'(VIP|Trusted|Helper|Moderator)', torrent.group(0)) is None: - if mode != 'RSS': - logger.log(u"Found result %s but that doesn't seem like a trusted result so I'm ignoring it" % title, logger.DEBUG) + # Continue only if one Release is found + if len(torrent_rows) < 2: + logger.log(u"Data returned from provider does not contain any torrents", logger.DEBUG) continue - item = title, download_url, size, seeders, leechers - if mode != 'RSS': - logger.log(u"Found result: %s " % title, logger.DEBUG) - - items[mode].append(item) + def process_column_header(th): + result = '' + if th.a: + result = th.a.get_text(strip=True) + if not result: + result = th.get_text(strip=True) + return result + + labels = [process_column_header(label) for label in torrent_rows[0].find_all('th')] + for result in torrent_rows[1:]: + try: + cells = result.find_all('td') + + title = result.find(class_='detName').get_text(strip=True) + download_url = result.find(title="Download this torrent using magnet")['href'] + if not all([title, download_url]): + continue + + seeders = try_int(cells[labels.index('SE')].get_text(strip=True)) + leechers = try_int(cells[labels.index('LE')].get_text(strip=True)) + if seeders < self.minseed or leechers < self.minleech: + if mode != 'RSS': + logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) + continue + + # Accept Torrent only from Good People for every Episode Search + if self.confirmed and result.find(alt=re.compile(r'(VIP|Trusted|Helper|Moderator)')): + if mode != 'RSS': + logger.log(u"Found result %s but that doesn't seem like a trusted result so I'm ignoring it" % title, logger.DEBUG) + continue + + # Convert size after all possible skip scenarios + torrent_size = cells[labels.index('Name')].find(class_='detDesc').get_text(strip=True).split(', ')[1] + torrent_size = re.sub(r'Size ([\d.]+).+([KMGT]iB)', r'\1 \2', torrent_size) + size = convert_size(torrent_size) or -1 + + item = title, download_url, size, seeders, leechers + if mode != 'RSS': + logger.log(u"Found result: %s " % title, logger.DEBUG) + + items.append(item) + except StandardError: + continue # For each search mode sort all the items by seeders if available - items[mode].sort(key=lambda tup: tup[3], reverse=True) + items.sort(key=lambda tup: tup[3], reverse=True) - results += items[mode] + results += items return results - def _convertSize(self, size): - size, modifier = size.split(' ') - size = float(size) - if modifier in 'KiB': - size *= 1024 ** 1 - elif modifier in 'MiB': - size *= 1024 ** 2 - elif modifier in 'GiB': - size *= 1024 ** 3 - elif modifier in 'TiB': - size *= 1024 ** 4 - return long(size) - def seed_ratio(self): return self.ratio @@ -152,7 +156,7 @@ class ThePirateBayCache(tvcache.TVCache): self.minTime = 30 def _getRSSData(self): - search_params = {'RSS': ['']} - return {'entries': self.provider.search(search_params)} + search_strings = {'RSS': ['']} + return {'entries': self.provider.search(search_strings)} provider = ThePirateBayProvider() diff --git a/sickbeard/providers/titansoftv.py b/sickbeard/providers/titansoftv.py index 87820802af9a3542ad10fac7f2cbb92feb27c589..7cb72144bdb47f318b19041ef6ac827343614cf4 100644 --- a/sickbeard/providers/titansoftv.py +++ b/sickbeard/providers/titansoftv.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import urllib @@ -64,11 +64,11 @@ class TitansOfTVProvider(TorrentProvider): if search_params: params.update(search_params) - searchURL = self.url + '?' + urllib.urlencode(params) + search_url = self.url + '?' + urllib.urlencode(params) logger.log(u"Search string: %s " % search_params, logger.DEBUG) - logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) + logger.log(u"Search URL: %s" % search_url, logger.DEBUG) - parsedJSON = self.get_url(searchURL, json=True) # do search + parsedJSON = self.get_url(search_url, json=True) # do search if not parsedJSON: logger.log(u"No data returned from provider", logger.DEBUG) diff --git a/sickbeard/providers/tntvillage.py b/sickbeard/providers/tntvillage.py index 430467a283a93cefd7809c77fe45a1350ed5d8ca..e79044502998071b6fbe73567167a36fd14288bb 100644 --- a/sickbeard/providers/tntvillage.py +++ b/sickbeard/providers/tntvillage.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import re import traceback @@ -26,6 +26,7 @@ from sickbeard import db from sickbeard.bs4_parser import BS4Parser from sickbeard.name_parser.parser import NameParser, InvalidNameException, InvalidShowException +from sickrage.helper.common import convert_size from sickrage.helper.exceptions import AuthException from sickrage.providers.torrent.TorrentProvider import TorrentProvider @@ -56,7 +57,7 @@ category_excluded = {'Sport': 22, 'Mobile': 37} -class TNTVillageProvider(TorrentProvider): +class TNTVillageProvider(TorrentProvider): # pylint: disable=too-many-instance-attributes def __init__(self): TorrentProvider.__init__(self, "TNTVillage") @@ -136,7 +137,8 @@ class TNTVillageProvider(TorrentProvider): return True - def _reverseQuality(self, quality): + @staticmethod + def _reverseQuality(quality): quality_string = '' @@ -161,7 +163,8 @@ class TNTVillageProvider(TorrentProvider): return quality_string - def _episodeQuality(self, torrent_rows): + @staticmethod + def _episodeQuality(torrent_rows): # pylint: disable=too-many-return-statements, too-many-branches """ Return The quality from the scene episode HTML row. """ @@ -237,7 +240,8 @@ class TNTVillageProvider(TorrentProvider): return italian - def _is_english(self, torrent_rows): + @staticmethod + def _is_english(torrent_rows): name = str(torrent_rows.find_all('td')[1].find('b').find('span')) if not name or name == 'None': @@ -250,7 +254,8 @@ class TNTVillageProvider(TorrentProvider): return english - def _is_season_pack(self, name): + @staticmethod + def _is_season_pack(name): try: myParser = NameParser(tryIndexers=True) @@ -268,17 +273,15 @@ class TNTVillageProvider(TorrentProvider): if int(episodes[0]['count']) == len(parse_result.episode_numbers): return True - def search(self, search_params, age=0, ep_obj=None): - + def search(self, search_params, age=0, ep_obj=None): # pylint: disable=too-many-locals, too-many-branches, too-many-statements results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} - - self.categories = "cat=" + str(self.cat) - if not self.login(): return results - for mode in search_params.keys(): + self.categories = "cat=" + str(self.cat) + + for mode in search_params: + items = [] logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_params[mode]: @@ -299,15 +302,15 @@ class TNTVillageProvider(TorrentProvider): break if mode != 'RSS': - searchURL = (self.urls['search_page'] + '&filter={2}').format(z, self.categories, search_string) + search_url = (self.urls['search_page'] + '&filter={2}').format(z, self.categories, search_string) else: - searchURL = self.urls['search_page'].format(z, self.categories) + search_url = self.urls['search_page'].format(z, self.categories) if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) - logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) - data = self.get_url(searchURL) + logger.log(u"Search URL: %s" % search_url, logger.DEBUG) + data = self.get_url(search_url) if not data: logger.log(u"No data returned from provider", logger.DEBUG) continue @@ -336,8 +339,8 @@ class TNTVillageProvider(TorrentProvider): leechers = int(leechers.strip('[]')) seeders = result.find_all('td')[3].find_all('td')[2].text seeders = int(seeders.strip('[]')) - # FIXME - size = -1 + torrent_size = result.find_all('td')[3].find_all('td')[3].text.strip('[]') + " GB" + size = convert_size(torrent_size) or -1 except (AttributeError, TypeError): continue @@ -385,15 +388,15 @@ class TNTVillageProvider(TorrentProvider): if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) - items[mode].append(item) + items.append(item) except Exception: logger.log(u"Failed parsing provider. Traceback: %s" % traceback.format_exc(), logger.ERROR) # For each search mode sort all the items by seeders if available if available - items[mode].sort(key=lambda tup: tup[3], reverse=True) + items.sort(key=lambda tup: tup[3], reverse=True) - results += items[mode] + results += items return results diff --git a/sickbeard/providers/tokyotoshokan.py b/sickbeard/providers/tokyotoshokan.py index 9c8c06fba80cb6ad56e804d4dd31544735ec3e58..49de713ce5e8b132d753abd4658dca9b59721630 100644 --- a/sickbeard/providers/tokyotoshokan.py +++ b/sickbeard/providers/tokyotoshokan.py @@ -11,23 +11,24 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +import re import urllib -import traceback from sickbeard import logger from sickbeard import tvcache -from sickbeard import show_name_helpers from sickbeard.bs4_parser import BS4Parser + +from sickrage.helper.common import try_int, convert_size from sickrage.providers.torrent.TorrentProvider import TorrentProvider -class TokyoToshokanProvider(TorrentProvider): +class TokyoToshokanProvider(TorrentProvider): # pylint: disable=too-many-instance-attributes def __init__(self): TorrentProvider.__init__(self, "TokyoToshokan") @@ -37,76 +38,87 @@ class TokyoToshokanProvider(TorrentProvider): self.anime_only = True self.ratio = None - self.cache = TokyoToshokanCache(self) + self.minseed = None + self.minleech = None - self.urls = {'base_url': 'http://tokyotosho.info/'} - self.url = self.urls['base_url'] + self.url = 'http://tokyotosho.info/' + self.urls = { + 'search': self.url + 'search.php', + 'rss': self.url + 'rss.php' + } + self.cache = TokyoToshokanCache(self) def seed_ratio(self): return self.ratio - def _get_season_search_strings(self, ep_obj): - return [x.replace('.', ' ') for x in show_name_helpers.makeSceneSeasonSearchString(self.show, ep_obj)] - - def _get_episode_search_strings(self, ep_obj, add_string=''): - return [x.replace('.', ' ') for x in show_name_helpers.makeSceneSearchString(self.show, ep_obj)] - - def search(self, search_string, age=0, ep_obj=None): - # FIXME ADD MODE - if self.show and not self.show.is_anime: - return [] - - logger.log(u"Search string: %s " % search_string, logger.DEBUG) - - params = { - "terms": search_string.encode('utf-8'), - "type": 1, # get anime types - } - - searchURL = self.url + 'search.php?' + urllib.urlencode(params) - logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) - data = self.get_url(searchURL) - - if not data: - return [] - + def search(self, search_strings, age=0, ep_obj=None): # pylint: disable=too-many-locals results = [] - try: - with BS4Parser(data, 'html5lib') as soup: - torrent_table = soup.find('table', attrs={'class': 'listing'}) - torrent_rows = torrent_table.find_all('tr') if torrent_table else [] - if torrent_rows: - if torrent_rows[0].find('td', attrs={'class': 'centertext'}): - a = 1 - else: - a = 0 - - for top, bottom in zip(torrent_rows[a::2], torrent_rows[a::2]): - title = top.find('td', attrs={'class': 'desc-top'}).text - title.lstrip() - download_url = top.find('td', attrs={'class': 'desc-top'}).find('a')['href'] - # FIXME - size = -1 - seeders = 1 - leechers = 0 + if not self.show or not self.show.is_anime: + return results + + for mode in search_strings: + items = [] + logger.log(u"Search Mode: %s" % mode, logger.DEBUG) + for search_string in search_strings[mode]: + if mode != 'RSS': + logger.log(u"Search string: %s " % search_string, logger.DEBUG) + + search_params = { + "terms": search_string.encode('utf-8'), + "type": 1, # get anime types + } + + logger.log(u"Search URL: %s" % self.urls['search'] + '?' + urllib.urlencode(search_params), logger.DEBUG) + data = self.get_url(self.urls['search'], params=search_params) + if not data: + continue + + with BS4Parser(data, 'html5lib') as soup: + torrent_table = soup.find('table', class_='listing') + torrent_rows = torrent_table.find_all('tr') if torrent_table else [] + + # Continue only if one Release is found + if len(torrent_rows) < 2: + logger.log(u"Data returned from provider does not contain any torrents", logger.DEBUG) + continue + + a = 1 if len(torrent_rows[0].find_all('td')) < 2 else 0 + + for top, bot in zip(torrent_rows[a::2], torrent_rows[a+1::2]): + try: + desc_top = top.find('td', class_='desc-top') + title = desc_top.get_text(strip=True) + download_url = desc_top.find('a')['href'] + + desc_bottom = bot.find('td', class_='desc-bot').get_text(strip=True) + size = convert_size(desc_bottom.split('|')[1].strip('Size: ')) or -1 + + stats = bot.find('td', class_='stats').get_text(strip=True) + sl = re.match(r'S:(?P<seeders>\d+)L:(?P<leechers>\d+)C:(?:\d+)ID:(?:\d+)', stats.replace(' ', '')) + seeders = try_int(sl.group('seeders')) if sl else 0 + leechers = try_int(sl.group('leechers')) if sl else 0 + except StandardError: + continue if not all([title, download_url]): continue # Filter unseeded torrent - # if seeders < self.minseed or leechers < self.minleech: - # if mode != 'RSS': - # logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) - # continue + if seeders < self.minseed or leechers < self.minleech: + if mode != 'RSS': + logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) + continue item = title, download_url, size, seeders, leechers + if mode != 'RSS': + logger.log(u"Found result: %s " % title, logger.DEBUG) - results.append(item) + items.append(item) - except Exception as e: - logger.log(u"Failed parsing provider. Traceback: %s" % traceback.format_exc(), logger.ERROR) + # For each search mode sort all the items by seeders if available + items.sort(key=lambda tup: tup[3], reverse=True) + results += items - # FIXME SORTING return results @@ -114,19 +126,22 @@ class TokyoToshokanCache(tvcache.TVCache): def __init__(self, provider_obj): tvcache.TVCache.__init__(self, provider_obj) - # only poll NyaaTorrents every 15 minutes max + # only poll TokyoToshokan every 15 minutes max self.minTime = 15 - def _getRSSData(self): - params = { - "filter": '1', - } - - url = self.provider.url + 'rss.php?' + urllib.urlencode(params) - - logger.log(u"Cache update URL: %s" % url, logger.DEBUG) - - return self.getRSSFeed(url) + # def _getRSSData(self): + # params = { + # "filter": '1', + # } + # + # url = self.provider.urls['rss'] + '?' + urllib.urlencode(params) + # + # logger.log(u"Cache update URL: %s" % url, logger.DEBUG) + # + # return self.getRSSFeed(url) + def _getRSSData(self): + search_strings = {'RSS': ['']} + return {'entries': self.provider.search(search_strings)} provider = TokyoToshokanProvider() diff --git a/sickbeard/providers/torrentbytes.py b/sickbeard/providers/torrentbytes.py index e22c5eb300f10783ee3bbd232426596813a0e9b9..08e9cf3ecca9f3da7791c8a42fd9a28172da96b2 100644 --- a/sickbeard/providers/torrentbytes.py +++ b/sickbeard/providers/torrentbytes.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import re import urllib @@ -24,10 +24,11 @@ import traceback from sickbeard import logger from sickbeard import tvcache from sickbeard.bs4_parser import BS4Parser +from sickrage.helper.common import convert_size from sickrage.providers.torrent.TorrentProvider import TorrentProvider -class TorrentBytesProvider(TorrentProvider): +class TorrentBytesProvider(TorrentProvider): # pylint: disable=too-many-instance-attributes def __init__(self): @@ -40,11 +41,13 @@ class TorrentBytesProvider(TorrentProvider): self.minleech = None self.freeleech = False - self.urls = {'base_url': 'https://www.torrentbytes.net', - 'login': 'https://www.torrentbytes.net/takelogin.php', - 'detail': 'https://www.torrentbytes.net/details.php?id=%s', - 'search': 'https://www.torrentbytes.net/browse.php?search=%s%s', - 'download': 'https://www.torrentbytes.net/download.php?id=%s&name=%s'} + self.urls = { + 'base_url': 'https://www.torrentbytes.net', + 'login': 'https://www.torrentbytes.net/takelogin.php', + 'detail': 'https://www.torrentbytes.net/details.php?id=%s', + 'search': 'https://www.torrentbytes.net/browse.php?search=%s%s', + 'download': 'https://www.torrentbytes.net/download.php?id=%s&name=%s' + } self.url = self.urls['base_url'] @@ -71,25 +74,23 @@ class TorrentBytesProvider(TorrentProvider): return True - def search(self, search_params, age=0, ep_obj=None): - + def search(self, search_params, age=0, ep_obj=None): # pylint: disable=too-many-locals, too-many-branches, too-many-statements results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} - if not self.login(): return results - for mode in search_params.keys(): + for mode in search_params: + items = [] logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_params[mode]: if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) - searchURL = self.urls['search'] % (urllib.quote(search_string.encode('utf-8')), self.categories) - logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) + search_url = self.urls['search'] % (urllib.quote(search_string.encode('utf-8')), self.categories) + logger.log(u"Search URL: %s" % search_url, logger.DEBUG) - data = self.get_url(searchURL) + data = self.get_url(search_url) if not data: continue @@ -106,7 +107,7 @@ class TorrentBytesProvider(TorrentProvider): for result in torrent_rows[1:]: cells = result.find_all('td') - size = None + torrent_size = None link = cells[1].find('a', attrs={'class': 'index'}) full_id = link['href'].replace('details.php?id=', '') @@ -132,11 +133,7 @@ class TorrentBytesProvider(TorrentProvider): leechers = int(cells[9].find('span').contents[0]) # Need size for failed downloads handling - if size is None: - if re.match(r'[0-9]+,?\.?[0-9]*[KkMmGg]+[Bb]+', cells[6].text): - size = self._convertSize(cells[6].text) - if not size: - size = -1 + torrent_size = cells[6].text if torrent_size is None else torrent_size except (AttributeError, TypeError): continue @@ -150,39 +147,27 @@ class TorrentBytesProvider(TorrentProvider): logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) continue + size = convert_size(torrent_size) or -1 + item = title, download_url, size, seeders, leechers if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) - items[mode].append(item) + items.append(item) - except Exception as e: + except Exception: logger.log(u"Failed parsing provider. Traceback: %s" % traceback.format_exc(), logger.ERROR) # For each search mode sort all the items by seeders if available - items[mode].sort(key=lambda tup: tup[3], reverse=True) + items.sort(key=lambda tup: tup[3], reverse=True) - results += items[mode] + results += items return results def seed_ratio(self): return self.ratio - def _convertSize(self, sizeString): - size = sizeString[:-2] - modifier = sizeString[-2:] - size = float(size) - if modifier in 'KB': - size *= 1024 ** 1 - elif modifier in 'MB': - size *= 1024 ** 2 - elif modifier in 'GB': - size *= 1024 ** 3 - elif modifier in 'TB': - size *= 1024 ** 4 - return long(size) - class TorrentBytesCache(tvcache.TVCache): def __init__(self, provider_obj): diff --git a/sickbeard/providers/torrentday.py b/sickbeard/providers/torrentday.py index 9c79bbd6b0f9f2eb435452af6603dc67ab9d2e0f..0e9e2e6113fe7a3444bb35e4a99d32e436fa9a34 100644 --- a/sickbeard/providers/torrentday.py +++ b/sickbeard/providers/torrentday.py @@ -10,20 +10,21 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import re import requests from sickbeard import logger from sickbeard import tvcache +from sickrage.helper.common import convert_size from sickrage.providers.torrent.TorrentProvider import TorrentProvider -class TorrentDayProvider(TorrentProvider): +class TorrentDayProvider(TorrentProvider): # pylint: disable=too-many-instance-attributes def __init__(self): @@ -93,15 +94,13 @@ class TorrentDayProvider(TorrentProvider): logger.log(u"Unable to obtain cookie", logger.WARNING) return False - def search(self, search_params, age=0, ep_obj=None): - + def search(self, search_params, age=0, ep_obj=None): # pylint: disable=too-many-locals results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} - if not self.login(): return results - for mode in search_params.keys(): + for mode in search_params: + items = [] logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_params[mode]: @@ -129,12 +128,12 @@ class TorrentDayProvider(TorrentProvider): for torrent in torrents: - title = re.sub(r"\[.*\=.*\].*\[/.*\]", "", torrent['name']) - download_url = self.urls['download'] % (torrent['id'], torrent['fname']) - seeders = int(torrent['seed']) - leechers = int(torrent['leech']) - # FIXME - size = -1 + title = re.sub(r"\[.*\=.*\].*\[/.*\]", "", torrent['name']) if torrent['name'] else None + download_url = self.urls['download'] % (torrent['id'], torrent['fname']) if torrent['id'] and torrent['fname'] else None + seeders = int(torrent['seed']) if torrent['seed'] else 1 + leechers = int(torrent['leech']) if torrent['leech'] else 0 + torrent_size = torrent['size'] + size = convert_size(torrent_size) or -1 if not all([title, download_url]): continue @@ -149,12 +148,12 @@ class TorrentDayProvider(TorrentProvider): if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) - items[mode].append(item) + items.append(item) # For each search mode sort all the items by seeders if available if available - items[mode].sort(key=lambda tup: tup[3], reverse=True) + items.sort(key=lambda tup: tup[3], reverse=True) - results += items[mode] + results += items return results diff --git a/sickbeard/providers/torrentleech.py b/sickbeard/providers/torrentleech.py index 5b05cd8b760906ee455c19313edb966b5bd8265d..3e1fdf902bffe1e1857fe8acdb56c1184d0d4f45 100644 --- a/sickbeard/providers/torrentleech.py +++ b/sickbeard/providers/torrentleech.py @@ -1,6 +1,6 @@ # coding=utf-8 -# Author: Idan Gutman -# URL: http://code.google.com/p/sickbeard/ +# Author: Dustyn Gibson <miigotu@gmail.com> +# URL: https://sickrage.github.io # # This file is part of SickRage. # @@ -11,23 +11,24 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import re -import traceback -import urllib +from urllib import urlencode +from requests.utils import dict_from_cookiejar from sickbeard import logger from sickbeard import tvcache from sickbeard.bs4_parser import BS4Parser +from sickrage.helper.common import try_int, convert_size from sickrage.providers.torrent.TorrentProvider import TorrentProvider -class TorrentLeechProvider(TorrentProvider): +class TorrentLeechProvider(TorrentProvider): # pylint: disable=too-many-instance-attributes def __init__(self): @@ -39,27 +40,26 @@ class TorrentLeechProvider(TorrentProvider): self.minseed = None self.minleech = None - self.urls = {'base_url': 'https://torrentleech.org/', - 'login': 'https://torrentleech.org/user/account/login/', - 'detail': 'https://torrentleech.org/torrent/%s', - 'search': 'https://torrentleech.org/torrents/browse/index/query/%s/categories/%s', - 'download': 'https://torrentleech.org%s', - 'index': 'https://torrentleech.org/torrents/browse/index/categories/%s'} - - self.url = self.urls['base_url'] - - self.categories = "2,7,26,27,32,34,35" + self.url = 'https://torrentleech.org' + self.urls = { + 'login': self.url + '/user/account/login/', + 'search': self.url + '/torrents/browse', + } self.proper_strings = ['PROPER', 'REPACK'] self.cache = TorrentLeechCache(self) def login(self): + if any(dict_from_cookiejar(self.session.cookies).values()): + return True - login_params = {'username': self.username, - 'password': self.password, - 'remember_me': 'on', - 'login': 'submit'} + login_params = { + 'username': self.username.encode('utf-8'), + 'password': self.password.encode('utf-8'), + 'remember_me': 'on', + 'login': 'submit' + } response = self.get_url(self.urls['login'], post_data=login_params, timeout=30) if not response: @@ -72,75 +72,89 @@ class TorrentLeechProvider(TorrentProvider): return True - def search(self, search_params, age=0, ep_obj=None): - + def search(self, search_strings, age=0, ep_obj=None): # pylint: disable=too-many-locals, too-many-branches results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} - if not self.login(): return results - for mode in search_params.keys(): - logger.log(u"Search Mode: %s" % mode, logger.DEBUG) - for search_string in search_params[mode]: + # TV, Episodes, BoxSets, Episodes HD, Animation, Anime, Cartoons + # 2,26,27,32,7,34,35 - if mode == 'RSS': - searchURL = self.urls['index'] % self.categories - else: - searchURL = self.urls['search'] % (urllib.quote_plus(search_string.encode('utf-8')), self.categories) + for mode in search_strings: + items = [] + logger.log(u"Search Mode: %s" % mode, logger.DEBUG) + for search_string in search_strings[mode]: + if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) - data = self.get_url(searchURL) - logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) - if not data: - continue + categories = ['2', '7', '35'] + categories += ['26', '32'] if mode == 'Episode' else ['27'] + if self.show and self.show.is_anime: + categories += ['34'] + else: + categories = ['2', '26', '27', '32', '7', '34', '35'] - try: - with BS4Parser(data, 'html5lib') as html: - torrent_table = html.find('table', attrs={'id': 'torrenttable'}) - torrent_rows = torrent_table.find_all('tr') if torrent_table else [] + search_params = { + 'categories': ','.join(categories), + 'query': search_string + } - # Continue only if one Release is found - if len(torrent_rows) < 2: - logger.log(u"Data returned from provider does not contain any torrents", logger.DEBUG) - continue + search_url = "%s?%s" % (self.urls['search'], urlencode(search_params)) + logger.log(u"Search URL: %s" % search_url, logger.DEBUG) - for result in torrent_table.find_all('tr')[1:]: - - try: - link = result.find('td', attrs={'class': 'name'}).find('a') - url = result.find('td', attrs={'class': 'quickdownload'}).find('a') - title = link.string - download_url = self.urls['download'] % url['href'] - seeders = int(result.find('td', attrs={'class': 'seeders'}).string) - leechers = int(result.find('td', attrs={'class': 'leechers'}).string) - # FIXME - size = -1 - except (AttributeError, TypeError): - continue + # returns top 15 results by default, expandable in user profile to 100 + data = self.get_url(search_url) + if not data: + continue + with BS4Parser(data, 'html5lib') as html: + torrent_table = html.find('table', id='torrenttable') + torrent_rows = torrent_table.find_all('tr') if torrent_table else [] + + # Continue only if at least one Release is found + if len(torrent_rows) < 2: + logger.log(u"Data returned from provider does not contain any torrents", logger.DEBUG) + continue + + def process_column_header(td): + result = '' + if td.a: + result = td.a.get('title') + if not result: + result = td.get_text(strip=True) + return result + + labels = [process_column_header(label) for label in torrent_rows[0].find_all('th')] + + for result in torrent_rows[1:]: + try: + title = result.find('td', class_='name').find('a').get_text(strip=True) + download_url = self.url + result.find('td', class_='quickdownload').find('a')['href'] if not all([title, download_url]): continue - # Filter unseeded torrent + seeders = try_int(result.find('td', class_='seeders').get_text(strip=True)) + leechers = try_int(result.find('td', class_='leechers').get_text(strip=True)) if seeders < self.minseed or leechers < self.minleech: if mode != 'RSS': logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) continue + torrent_size = result.find_all('td')[labels.index('Size')].get_text() + size = convert_size(torrent_size) or -1 + item = title, download_url, size, seeders, leechers if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) - items[mode].append(item) - - except Exception as e: - logger.log(u"Failed parsing provider. Traceback: %s" % traceback.format_exc(), logger.ERROR) + items.append(item) + except StandardError: + continue # For each search mode sort all the items by seeders if available - items[mode].sort(key=lambda tup: tup[3], reverse=True) + items.sort(key=lambda tup: tup[3], reverse=True) - results += items[mode] + results += items return results @@ -157,8 +171,8 @@ class TorrentLeechCache(tvcache.TVCache): self.minTime = 20 def _getRSSData(self): - search_params = {'RSS': ['']} - return {'entries': self.provider.search(search_params)} + search_strings = {'RSS': ['']} + return {'entries': self.provider.search(search_strings)} provider = TorrentLeechProvider() diff --git a/sickbeard/providers/torrentproject.py b/sickbeard/providers/torrentproject.py index eedb52d6d7a23ef456417ed65d54aeba1d9f2488..b5b15844762d80f47ec971c1b7d78a11581270fe 100644 --- a/sickbeard/providers/torrentproject.py +++ b/sickbeard/providers/torrentproject.py @@ -11,22 +11,22 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import posixpath # Must use posixpath from urllib import quote_plus from sickbeard import logger from sickbeard import tvcache from sickbeard.common import USER_AGENT -from sickrage.helper.common import try_int +from sickrage.helper.common import try_int, convert_size from sickrage.providers.torrent.TorrentProvider import TorrentProvider -class TORRENTPROJECTProvider(TorrentProvider): +class TorrentProjectProvider(TorrentProvider): def __init__(self): TorrentProvider.__init__(self, "TorrentProject") @@ -38,25 +38,23 @@ class TORRENTPROJECTProvider(TorrentProvider): self.headers.update({'User-Agent': USER_AGENT}) self.minseed = None self.minleech = None - self.cache = TORRENTPROJECTCache(self) + self.cache = TorrentProjectCache(self) def search(self, search_strings, age=0, ep_obj=None): - results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} - - for mode in search_strings.keys(): # Mode = RSS, Season, Episode + for mode in search_strings: # Mode = RSS, Season, Episode + items = [] logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_strings[mode]: if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) - searchURL = self.urls['api'] + "?s=%s&out=json&filter=2101&num=150" % quote_plus(search_string.encode('utf-8')) + search_url = self.urls['api'] + "?s=%s&out=json&filter=2101&num=150" % quote_plus(search_string.encode('utf-8')) if self.custom_url: - searchURL = posixpath.join(self.custom_url, searchURL.split(self.url)[1].lstrip('/')) # Must use posixpath + search_url = posixpath.join(self.custom_url, search_url.split(self.url)[1].lstrip('/')) # Must use posixpath - logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) - torrents = self.get_url(searchURL, json=True) + logger.log(u"Search URL: %s" % search_url, logger.DEBUG) + torrents = self.get_url(search_url, json=True) if not (torrents and "total_found" in torrents and int(torrents["total_found"]) > 0): logger.log(u"Data returned from provider does not contain any torrents", logger.DEBUG) continue @@ -74,7 +72,8 @@ class TORRENTPROJECTProvider(TorrentProvider): continue t_hash = torrents[i]["torrent_hash"] - size = int(torrents[i]["torrent_size"]) + torrent_size = torrents[i]["torrent_size"] + size = convert_size(torrent_size) or -1 try: assert seeders < 10 @@ -82,7 +81,7 @@ class TORRENTPROJECTProvider(TorrentProvider): logger.log(u"Torrent has less than 10 seeds getting dyn trackers: " + title, logger.DEBUG) trackerUrl = self.urls['api'] + "" + t_hash + "/trackers_json" if self.custom_url: - searchURL = posixpath.join(self.custom_url, searchURL.split(self.url)[1].lstrip('/')) # Must use posixpath + search_url = posixpath.join(self.custom_url, search_url.split(self.url)[1].lstrip('/')) # Must use posixpath jdata = self.get_url(trackerUrl, json=True) assert jdata != "maintenance" download_url = "magnet:?xt=urn:btih:" + t_hash + "&dn=" + title + "".join(["&tr=" + s for s in jdata]) @@ -97,12 +96,12 @@ class TORRENTPROJECTProvider(TorrentProvider): if mode != 'RSS': logger.log(u"Found result: %s" % title, logger.DEBUG) - items[mode].append(item) + items.append(item) # For each search mode sort all the items by seeders - items[mode].sort(key=lambda tup: tup[3], reverse=True) + items.sort(key=lambda tup: tup[3], reverse=True) - results += items[mode] + results += items return results @@ -110,7 +109,7 @@ class TORRENTPROJECTProvider(TorrentProvider): return self.ratio -class TORRENTPROJECTCache(tvcache.TVCache): +class TorrentProjectCache(tvcache.TVCache): def __init__(self, provider_obj): tvcache.TVCache.__init__(self, provider_obj) @@ -122,4 +121,4 @@ class TORRENTPROJECTCache(tvcache.TVCache): search_params = {'RSS': ['0day']} return {'entries': self.provider.search(search_params)} -provider = TORRENTPROJECTProvider() +provider = TorrentProjectProvider() diff --git a/sickbeard/providers/torrentz.py b/sickbeard/providers/torrentz.py index 3916ccdc0a4ddc8febdb45df5b5f78c8caca946f..02060f299a983b9921538a5e38614de114793b16 100644 --- a/sickbeard/providers/torrentz.py +++ b/sickbeard/providers/torrentz.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import re import traceback @@ -24,10 +24,11 @@ from sickbeard import logger from sickbeard import tvcache from sickbeard.common import USER_AGENT from sickbeard.bs4_parser import BS4Parser +from sickrage.helper.common import convert_size from sickrage.providers.torrent.TorrentProvider import TorrentProvider -class TORRENTZProvider(TorrentProvider): +class TorrentzProvider(TorrentProvider): # pylint: disable=too-many-instance-attributes def __init__(self): @@ -37,7 +38,7 @@ class TORRENTZProvider(TorrentProvider): self.ratio = None self.minseed = None self.minleech = None - self.cache = TORRENTZCache(self) + self.cache = TorrentzCache(self) self.headers.update({'User-Agent': USER_AGENT}) self.urls = {'verified': 'https://torrentz.eu/feed_verified', 'feed': 'https://torrentz.eu/feed', @@ -52,11 +53,11 @@ class TORRENTZProvider(TorrentProvider): match = re.findall(r'[0-9]+', description) return int(match[0]) * 1024 ** 2, int(match[1]), int(match[2]) - def search(self, search_strings, age=0, ep_obj=None): + def search(self, search_strings, age=0, ep_obj=None): # pylint: disable=too-many-locals results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} for mode in search_strings: + items = [] for search_string in search_strings[mode]: search_url = self.urls['verified'] if self.confirmed else self.urls['feed'] if mode != 'RSS': @@ -83,9 +84,9 @@ class TORRENTZProvider(TorrentProvider): if not all([title, t_hash]): continue - # TODO: Add method to generic provider for building magnet from hash. download_url = "magnet:?xt=urn:btih:" + t_hash + "&dn=" + title + self._custom_trackers - size, seeders, leechers = self._split_description(item.find('description').text) + torrent_size, seeders, leechers = self._split_description(item.find('description').text) + size = convert_size(torrent_size) or -1 # Filter unseeded torrent if seeders < self.minseed or leechers < self.minleech: @@ -93,19 +94,19 @@ class TORRENTZProvider(TorrentProvider): logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) continue - items[mode].append((title, download_url, size, seeders, leechers)) + items.append((title, download_url, size, seeders, leechers)) except (AttributeError, TypeError, KeyError, ValueError): logger.log(u"Failed parsing provider. Traceback: %r" % traceback.format_exc(), logger.ERROR) # For each search mode sort all the items by seeders if available - items[mode].sort(key=lambda tup: tup[3], reverse=True) - results += items[mode] + items.sort(key=lambda tup: tup[3], reverse=True) + results += items return results -class TORRENTZCache(tvcache.TVCache): +class TorrentzCache(tvcache.TVCache): def __init__(self, provider_obj): @@ -117,4 +118,4 @@ class TORRENTZCache(tvcache.TVCache): def _getRSSData(self): return {'entries': self.provider.search({'RSS': ['']})} -provider = TORRENTZProvider() +provider = TorrentzProvider() diff --git a/sickbeard/providers/transmitthenet.py b/sickbeard/providers/transmitthenet.py index 928ed49db5e625cce5b71c4c50fa9153efa8d49a..55c0845c2fddee727113b0177ab72330d40706b2 100644 --- a/sickbeard/providers/transmitthenet.py +++ b/sickbeard/providers/transmitthenet.py @@ -8,11 +8,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import re import traceback @@ -22,11 +22,11 @@ from sickbeard import logger from sickbeard import tvcache from sickbeard.bs4_parser import BS4Parser from sickrage.helper.exceptions import AuthException -from sickrage.helper.common import try_int +from sickrage.helper.common import try_int, convert_size from sickrage.providers.torrent.TorrentProvider import TorrentProvider -class TransmitTheNetProvider(TorrentProvider): +class TransmitTheNetProvider(TorrentProvider): # pylint: disable=too-many-instance-attributes def __init__(self): TorrentProvider.__init__(self, "TransmitTheNet") @@ -75,15 +75,13 @@ class TransmitTheNetProvider(TorrentProvider): return True - def search(self, search_strings, age=0, ep_obj=None): - + def search(self, search_strings, age=0, ep_obj=None): # pylint: disable=too-many-branches, too-many-locals, too-many-statements results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} - if not self.login(): return results - for mode in search_strings.keys(): + for mode in search_strings: + items = [] for search_string in search_strings[mode]: if mode != 'RSS': @@ -136,7 +134,9 @@ class TransmitTheNetProvider(TorrentProvider): if not title: title = torrent_row.find('a', onmouseout='return nd();').string title = title.replace("[", "").replace("]", "").replace("/ ", "") - size = try_int(temp_anchor['data-filesize']) + + torrent_size = temp_anchor['data-filesize'] + size = convert_size(torrent_size) or -1 temp_anchor = torrent_row.find('span', class_='time').parent.find_next_sibling() seeders = try_int(temp_anchor.text.strip()) @@ -155,15 +155,15 @@ class TransmitTheNetProvider(TorrentProvider): if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) - items[mode].append(item) + items.append(item) except Exception: logger.log(u"Failed parsing provider. Traceback: %s" % traceback.format_exc(), logger.ERROR) # For each search mode sort all the items by seeders - items[mode].sort(key=lambda tup: tup[3], reverse=True) + items.sort(key=lambda tup: tup[3], reverse=True) - results += items[mode] + results += items return results diff --git a/sickbeard/providers/tvchaosuk.py b/sickbeard/providers/tvchaosuk.py index 874f4761a1df8bef587e1a31602c71053968d306..65d18be459bfd03c27ec7dc7e17facc0afe04f51 100644 --- a/sickbeard/providers/tvchaosuk.py +++ b/sickbeard/providers/tvchaosuk.py @@ -8,11 +8,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import re # from urllib import urlencode @@ -23,11 +23,12 @@ from sickbeard import tvcache from sickbeard import show_name_helpers from sickbeard.helpers import sanitizeSceneName from sickbeard.bs4_parser import BS4Parser +from sickrage.helper.common import convert_size from sickrage.helper.exceptions import AuthException from sickrage.providers.torrent.TorrentProvider import TorrentProvider -class TVChaosUKProvider(TorrentProvider): +class TVChaosUKProvider(TorrentProvider): # pylint: disable=too-many-instance-attributes def __init__(self): TorrentProvider.__init__(self, 'TvChaosUK') @@ -119,15 +120,13 @@ class TVChaosUKProvider(TorrentProvider): return True - def search(self, search_strings, age=0, ep_obj=None): - + def search(self, search_strings, age=0, ep_obj=None): # pylint: disable=too-many-locals, too-many-branches results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} - if not self.login(): return results - for mode in search_strings.keys(): + for mode in search_strings: + items = [] logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_strings[mode]: @@ -180,46 +179,27 @@ class TVChaosUKProvider(TorrentProvider): # Strip year from the end or we can't parse it! title = re.sub(r'[\. ]?\(\d{4}\)', '', title) - torrent_size = cells[4].getText().strip() - size = -1 - if re.match(r"\d+([,\.]\d+)?\s*[KkMmGgTt]?[Bb]", torrent_size): - size = self._convertSize(torrent_size.rstrip()) + torrent_size = cells[4].getText() + size = convert_size(torrent_size) or -1 item = title, download_url, size, seeders, leechers if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) - items[mode].append(item) + items.append(item) except Exception: continue # For each search mode sort all the items by seeders if available - items[mode].sort(key=lambda tup: tup[3], reverse=True) + items.sort(key=lambda tup: tup[3], reverse=True) - results += items[mode] + results += items return results def seed_ratio(self): return self.ratio - def _convertSize(self, sizeString): - size = sizeString[:-2].strip() - modifier = sizeString[-2:].upper() - try: - size = float(size) - if modifier in 'KB': - size *= 1024 ** 1 - elif modifier in 'MB': - size *= 1024 ** 2 - elif modifier in 'GB': - size *= 1024 ** 3 - elif modifier in 'TB': - size *= 1024 ** 4 - except Exception: - size = -1 - return long(size) - class TVChaosUKCache(tvcache.TVCache): def __init__(self, provider_obj): diff --git a/sickbeard/providers/womble.py b/sickbeard/providers/womble.py index 5a003863467a81064d75c801f2b589d1aafe2754..b21d3fbd3045fe75210aeda7639bc3392c60a0bc 100644 --- a/sickbeard/providers/womble.py +++ b/sickbeard/providers/womble.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. from sickbeard import logger from sickbeard import tvcache diff --git a/sickbeard/providers/xthor.py b/sickbeard/providers/xthor.py index 83b596a7e5e8109aca2e259e702fd8f81314e449..c5b479fa0b4ea6de4b2e28c9cf201118431c690e 100644 --- a/sickbeard/providers/xthor.py +++ b/sickbeard/providers/xthor.py @@ -1,6 +1,7 @@ # -*- coding: latin-1 -*- # Author: adaur <adaur.underground@gmail.com> -# URL: http://code.google.com/p/sickbeard/ +# Rewrite: Dustyn Gibson (miigotu) <miigotu@gmail.com> +# URL: https://sickrage.github.io # # This file is part of SickRage. # @@ -11,23 +12,26 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import re -import cookielib -import urllib import requests +import cookielib +from urllib import urlencode from sickbeard import logger +from sickbeard import tvcache from sickbeard.bs4_parser import BS4Parser from sickrage.providers.torrent.TorrentProvider import TorrentProvider +from sickrage.helper.common import try_int, convert_size + -class XthorProvider(TorrentProvider): +class XthorProvider(TorrentProvider): # pylint: disable=too-many-instance-attributes def __init__(self): @@ -35,13 +39,20 @@ class XthorProvider(TorrentProvider): self.cj = cookielib.CookieJar() - self.url = "https://xthor.bz" - self.urlsearch = "https://xthor.bz/browse.php?search=\"%s\"%s" - self.categories = "&searchin=title&incldead=0" + self.url = 'https://xthor.bz' + self.urls = { + 'login': self.url + '/takelogin.php', + 'search': self.url + '/browse.php?' + } + self.ratio = None + self.minseed = None + self.minleech = None self.username = None self.password = None - self.ratio = None + self.freeleech = None + self.proper_strings = ['PROPER'] + self.cache = XthorCache(self) def login(self): @@ -52,7 +63,7 @@ class XthorProvider(TorrentProvider): 'password': self.password, 'submitme': 'X'} - response = self.get_url(self.url + '/takelogin.php', post_data=login_params, timeout=30) + response = self.get_url(self.urls['login'], post_data=login_params, timeout=30) if not response: logger.log(u"Unable to connect to provider", logger.WARNING) return False @@ -63,75 +74,107 @@ class XthorProvider(TorrentProvider): logger.log(u"Invalid username or password. Check your settings", logger.WARNING) return False - def search(self, search_params, age=0, ep_obj=None): - + def search(self, search_strings, age=0, ep_obj=None): # pylint: disable=too-many-locals, too-many-branches results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} - - # check for auth if not self.login(): return results - for mode in search_params.keys(): + """ + Séries / Pack TV 13 + Séries / TV FR 14 + Séries / HD FR 15 + Séries / TV VOSTFR 16 + Séries / HD VOSTFR 17 + Mangas (Anime) 32 + Sport 34 + """ + search_params = { + 'only_free': try_int(self.freeleech), + 'searchin': 'title', + 'incldead': 0, + 'type': 'desc', + 'c13': 1, 'c14': 1, 'c15': 1, + 'c16': 1, 'c17': 1, 'c32': 1 + } + + for mode in search_strings: + items = [] logger.log(u"Search Mode: %s" % mode, logger.DEBUG) - for search_string in search_params[mode]: + # Sorting: 1: Name, 3: Comments, 5: Size, 6: Completed, 7: Seeders, 8: Leechers (4: Time ?) + search_params['sort'] = (7, 4)[mode == 'RSS'] + for search_string in search_strings[mode]: if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) - searchURL = self.urlsearch % (urllib.quote(search_string), self.categories) - logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) - data = self.get_url(searchURL) - + search_params['search'] = search_string + search_url = self.urls['search'] + urlencode(search_params) + logger.log(u"Search URL: %s" % search_url, logger.DEBUG) + data = self.get_url(search_url) if not data: continue with BS4Parser(data, 'html5lib') as html: - resultsTable = html.find("table", {"class": "table2 table-bordered2"}) - if not resultsTable: + torrent_table = html.find("table", class_="table2 table-bordered2") + torrent_rows = [] + if torrent_table: + torrent_rows = torrent_table.find_all("tr") + + # Continue only if at least one Release is found + if len(torrent_rows) < 2: + logger.log(u"Data returned from provider does not contain any torrents", logger.DEBUG) continue - rows = resultsTable.findAll("tr") - for row in rows: - link = row.find("a", href=re.compile("details.php")) - if not link: - continue - - title = link.text - download_item = row.find("a", href=re.compile("download.php")) - if not download_item: + # Catégorie, Nom du Torrent, (Download), (Bookmark), Com., Taille, Complété, Seeders, Leechers + labels = [label.get_text(strip=True) for label in torrent_rows[0].find_all('td')] + + for row in torrent_rows[1:]: + try: + cells = row.find_all('td') + title = cells[labels.index('Nom du Torrent')].get_text(strip=True) + download_url = self.url + '/' + row.find("a", href=re.compile("download.php"))['href'] + size = convert_size(cells[labels.index('Taille')].get_text(strip=True)) + seeders = try_int(cells[labels.index('Seeders')].get_text(strip=True)) + leechers = try_int(cells[labels.index('Leechers')].get_text(strip=True)) + except (AttributeError, TypeError, KeyError, ValueError): continue - download_url = self.url + '/' + download_item['href'] - - # FIXME - size = -1 - seeders = 1 - leechers = 0 - if not all([title, download_url]): continue # Filter unseeded torrent - # if seeders < self.minseed or leechers < self.minleech: - # if mode != 'RSS': - # logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) - # continue + if seeders < self.minseed or leechers < self.minleech: + if mode != 'RSS': + logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) + continue item = title, download_url, size, seeders, leechers if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) - items[mode].append(item) + items.append(item) # For each search mode sort all the items by seeders if available if available - items[mode].sort(key=lambda tup: tup[3], reverse=True) + items.sort(key=lambda tup: tup[3], reverse=True) - results += items[mode] + results += items return results def seed_ratio(self): return self.ratio + +class XthorCache(tvcache.TVCache): + def __init__(self, provider_obj): + + tvcache.TVCache.__init__(self, provider_obj) + + self.minTime = 30 + + def _getRSSData(self): + search_strings = {'RSS': ['']} + return {'entries': self.provider.search(search_strings)} + + provider = XthorProvider() diff --git a/sickbeard/sab.py b/sickbeard/sab.py index bf4a0583ca45206fdcab7c8f2507367bc141ff88..ee053533401aa8233e749d10ae9e022a19237e58 100644 --- a/sickbeard/sab.py +++ b/sickbeard/sab.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import urllib import httplib diff --git a/sickbeard/sbdatetime.py b/sickbeard/sbdatetime.py index f61d5ecd3b75444a78cf433b185a1f1dc26ae623..03bb4b325912c336e76a4f7f01e89087a5ec8573 100644 --- a/sickbeard/sbdatetime.py +++ b/sickbeard/sbdatetime.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import datetime import locale diff --git a/sickbeard/scene_exceptions.py b/sickbeard/scene_exceptions.py index 0598ee4fbf8ab5b83c830edbef8576e6dd66ee94..ceb15b3ccaf69f3e73ba4afd34bfce746fb74bce 100644 --- a/sickbeard/scene_exceptions.py +++ b/sickbeard/scene_exceptions.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import re import time diff --git a/sickbeard/scene_numbering.py b/sickbeard/scene_numbering.py index b1a19387be1d2c65e5b8f1dc7ac9180e94f8e706..4316347063269796f459c1c7615c993ea8404873 100644 --- a/sickbeard/scene_numbering.py +++ b/sickbeard/scene_numbering.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. # # Created on Sep 20, 2012 # @author: Dermot Buckley <dermot@buckley.ie> diff --git a/sickbeard/scheduler.py b/sickbeard/scheduler.py index a4690cf5dda6c7727261dd6376b0622a27fd876b..cdf3171c895ef53083d3c9be7b95f68501b3be49 100644 --- a/sickbeard/scheduler.py +++ b/sickbeard/scheduler.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import datetime import time diff --git a/sickbeard/search.py b/sickbeard/search.py index 58145a60e973d7dbcd4b0cdaebd7ffd3078d30f0..541bff2cdf8c68d601404b5ee13777da054103b5 100644 --- a/sickbeard/search.py +++ b/sickbeard/search.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import os import re diff --git a/sickbeard/searchBacklog.py b/sickbeard/searchBacklog.py index efd21b5d517742b8c39b1e99905b30be70698eab..a16552e597407af300a28f509d0ecfb9f675ca5d 100644 --- a/sickbeard/searchBacklog.py +++ b/sickbeard/searchBacklog.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import datetime diff --git a/sickbeard/search_queue.py b/sickbeard/search_queue.py index 4b8fccb4dca3208ad96b1fb67ecc11e5dd8f5a99..b70c9278b342b4d3f2934048a72c6131e987de09 100644 --- a/sickbeard/search_queue.py +++ b/sickbeard/search_queue.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import time diff --git a/sickbeard/showUpdater.py b/sickbeard/showUpdater.py index f995d45174593692b573230c8b9a773bb8852450..929e276fe61d88fc327c2eb598df9b074eb9f20e 100644 --- a/sickbeard/showUpdater.py +++ b/sickbeard/showUpdater.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import xml.etree.ElementTree as ET import requests @@ -35,14 +35,14 @@ from sickbeard.indexers.indexer_config import INDEXER_TVRAGE from sickbeard.indexers.indexer_config import INDEXER_TVDB -class ShowUpdater(object): +class ShowUpdater(object): # pylint: disable=too-few-public-methods def __init__(self): self.lock = threading.Lock() self.amActive = False self.session = requests.Session() - def run(self, force=False): # pylint: disable=unused-parameter + def run(self, force=False): # pylint: disable=unused-argument, too-many-locals, too-many-branches, too-many-statements self.amActive = True @@ -79,13 +79,16 @@ class ShowUpdater(object): # url = 'http://thetvdb.com/api/Updates.php?type=series&time=%s' % last_update url = 'http://thetvdb.com/api/%s/updates/%s' % (sickbeard.indexerApi(INDEXER_TVDB).api_params['apikey'], update_file) data = helpers.getURL(url, session=self.session) + if not data: + logger.log(u"Could not get the recently updated show data from %s. Retrying later. Url was: %s" % (sickbeard.indexerApi(INDEXER_TVDB).name, url)) + self.amActive = False + return updated_shows = [] try: tree = ET.fromstring(data) for show in tree.findall("Series"): updated_shows.append(int(show.find('id').text)) - except SyntaxError: pass diff --git a/sickbeard/show_name_helpers.py b/sickbeard/show_name_helpers.py index f161b0569c67dcd64e910395a8dce8d969b716b9..299d7a7a7990118ebc638f83f4b46b56e08668f5 100644 --- a/sickbeard/show_name_helpers.py +++ b/sickbeard/show_name_helpers.py @@ -11,27 +11,23 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import fnmatch import os import re -import datetime -from functools import partial import sickbeard from sickbeard import common -from sickbeard.helpers import sanitizeSceneName from sickbeard.scene_exceptions import get_scene_exceptions from sickbeard import logger -from sickbeard import db -from sickrage.helper.encoding import ek, ss -from name_parser.parser import NameParser, InvalidNameException, InvalidShowException +from sickrage.helper.encoding import ek +from sickbeard.name_parser.parser import NameParser, InvalidNameException, InvalidShowException resultFilters = [ "sub(bed|ed|pack|s)", @@ -56,7 +52,7 @@ def containsAtLeastOneWord(name, words): """ if isinstance(words, basestring): words = words.split(',') - items = [(re.compile('(^|[\W_])%s($|[\W_])' % re.escape(word.strip()), re.I), word.strip()) for word in words] + items = [(re.compile(r'(^|[\W_])%s($|[\W_])' % re.escape(word.strip()), re.I), word.strip()) for word in words] for regexp, word in items: if regexp.search(name): return word @@ -104,195 +100,6 @@ def filterBadReleases(name, parse=True): return True -def sceneToNormalShowNames(name): - """ - Takes a show name from a scene dirname and converts it to a more "human-readable" format. - - name: The show name to convert - - Returns: a list of all the possible "normal" names - """ - - if not name: - return [] - - name_list = [name] - - # use both and and & - new_name = re.sub('(?i)([\. ])and([\. ])', '\\1&\\2', name, re.I) - if new_name not in name_list: - name_list.append(new_name) - - results = [] - - for cur_name in name_list: - # add brackets around the year - results.append(re.sub('(\D)(\d{4})$', '\\1(\\2)', cur_name)) - - # add brackets around the country - country_match_str = '|'.join(common.countryList.values()) - results.append(re.sub('(?i)([. _-])(' + country_match_str + ')$', '\\1(\\2)', cur_name)) - - results += name_list - - return list(set(results)) - - -def makeSceneShowSearchStrings(show, season=-1, anime=False): - showNames = allPossibleShowNames(show, season=season) - - # scenify the names - if anime: - sanitizeSceneNameAnime = partial(sanitizeSceneName, anime=True) - return map(sanitizeSceneNameAnime, showNames) - else: - return map(sanitizeSceneName, showNames) - - -def makeSceneSeasonSearchString(show, ep_obj, extraSearchType=None): - - if show.air_by_date or show.sports: - numseasons = 0 - - # the search string for air by date shows is just - seasonStrings = [str(ep_obj.airdate).split('-')[0]] - elif show.is_anime: - numseasons = 0 - seasonEps = show.getAllEpisodes(ep_obj.season) - - # get show qualities - anyQualities, bestQualities = common.Quality.splitQuality(show.quality) - - # compile a list of all the episode numbers we need in this 'season' - seasonStrings = [] - for episode in seasonEps: - - # get quality of the episode - curCompositeStatus = episode.status - curStatus, curQuality = common.Quality.splitCompositeStatus(curCompositeStatus) - - if bestQualities: - highestBestQuality = max(bestQualities) - else: - highestBestQuality = 0 - - # if we need a better one then add it to the list of episodes to fetch - if (curStatus in ( - common.DOWNLOADED, - common.SNATCHED) and curQuality < highestBestQuality) or curStatus == common.WANTED: - ab_number = episode.scene_absolute_number - if ab_number > 0: - seasonStrings.append("%02d" % ab_number) - - else: - myDB = db.DBConnection() - numseasonsSQlResult = myDB.select( - "SELECT COUNT(DISTINCT season) as numseasons FROM tv_episodes WHERE showid = ? and season != 0", - [show.indexerid]) - - numseasons = int(numseasonsSQlResult[0][0]) - seasonStrings = ["S%02d" % int(ep_obj.scene_season)] - - showNames = set(makeSceneShowSearchStrings(show, ep_obj.scene_season)) - - toReturn = [] - - # search each show name - for curShow in showNames: - # most providers all work the same way - if not extraSearchType: - # if there's only one season then we can just use the show name straight up - if numseasons == 1: - toReturn.append(curShow) - # for providers that don't allow multiple searches in one request we only search for Sxx style stuff - else: - for cur_season in seasonStrings: - if ep_obj.show.is_anime: - if ep_obj.show.release_groups is not None: - if len(show.release_groups.whitelist) > 0: - for keyword in show.release_groups.whitelist: - toReturn.append(keyword + '.' + curShow + "." + cur_season) - else: - toReturn.append(curShow + "." + cur_season) - - return toReturn - - -def makeSceneSearchString(show, ep_obj): - myDB = db.DBConnection() - numseasonsSQlResult = myDB.select( - "SELECT COUNT(DISTINCT season) as numseasons FROM tv_episodes WHERE showid = ? and season != 0", - [show.indexerid]) - numseasons = int(numseasonsSQlResult[0][0]) - - # see if we should use dates instead of episodes - if (show.air_by_date or show.sports) and ep_obj.airdate != datetime.date.fromordinal(1): - epStrings = [str(ep_obj.airdate)] - elif show.is_anime: - epStrings = ["%02i" % int(ep_obj.scene_absolute_number if ep_obj.scene_absolute_number > 0 else ep_obj.scene_episode)] - else: - epStrings = ["S%02iE%02i" % (int(ep_obj.scene_season), int(ep_obj.scene_episode)), - "%ix%02i" % (int(ep_obj.scene_season), int(ep_obj.scene_episode))] - - # for single-season shows just search for the show name -- if total ep count (exclude s0) is less than 11 - # due to the amount of qualities and releases, it is easy to go over the 50 result limit on rss feeds otherwise - if numseasons == 1 and not ep_obj.show.is_anime: - epStrings = [''] - - showNames = set(makeSceneShowSearchStrings(show, ep_obj.scene_season)) - - toReturn = [] - - for curShow in showNames: - for curEpString in epStrings: - if ep_obj.show.is_anime: - if ep_obj.show.release_groups is not None: - if len(ep_obj.show.release_groups.whitelist) > 0: - for keyword in ep_obj.show.release_groups.whitelist: - toReturn.append(keyword + '.' + curShow + '.' + curEpString) - elif len(ep_obj.show.release_groups.blacklist) == 0: - # If we have neither whitelist or blacklist we just append what we have - toReturn.append(curShow + '.' + curEpString) - else: - toReturn.append(curShow + '.' + curEpString) - - return toReturn - - -def isGoodResult(name, show, log=True, season=-1): - """ - Use an automatically-created regex to make sure the result actually is the show it claims to be - """ - - all_show_names = allPossibleShowNames(show, season=season) - showNames = map(sanitizeSceneName, all_show_names) + all_show_names - showNames += map(ss, all_show_names) - - for curName in set(showNames): - if not show.is_anime: - escaped_name = re.sub('\\\\[\\s.-]', '\W+', re.escape(curName)) - if show.startyear: - escaped_name += "(?:\W+" + str(show.startyear) + ")?" - curRegex = '^' + escaped_name + '\W+(?:(?:S\d[\dE._ -])|(?:\d\d?x)|(?:\d{4}\W\d\d\W\d\d)|(?:(?:part|pt)[\._ -]?(\d|[ivx]))|Season\W+\d+\W+|E\d+\W+|(?:\d{1,3}.+\d{1,}[a-zA-Z]{2}\W+[a-zA-Z]{3,}\W+\d{4}.+))' - else: - escaped_name = re.sub('\\\\[\\s.-]', '[\W_]+', re.escape(curName)) - # FIXME: find a "automatically-created" regex for anime releases # test at http://regexr.com?2uon3 - curRegex = '^((\[.*?\])|(\d+[\.-]))*[ _\.]*' + escaped_name + '(([ ._-]+\d+)|([ ._-]+s\d{2})).*' - - if log: - logger.log(u"Checking if show " + name + " matches " + curRegex, logger.DEBUG) - - match = re.search(curRegex, name, re.I) - if match: - logger.log(u"Matched " + curRegex + " to " + name, logger.DEBUG) - return True - - if log: - logger.log( - u"Provider gave result " + name + " but that doesn't seem like a valid result for " + show.name + " so I'm ignoring it") - return False - - def allPossibleShowNames(show, season=-1): """ Figures out every possible variation of the name for a particular show. Includes TVDB name, TVRage name, @@ -354,7 +161,8 @@ def determineReleaseName(dir_name=None, nzb_name=None): reg_expr = re.compile(fnmatch.translate(search), re.IGNORECASE) files = [file_name for file_name in ek(os.listdir, dir_name) if ek(os.path.isfile, ek(os.path.join, dir_name, file_name))] - results = filter(reg_expr.search, files) + + results = [f for f in files if reg_expr.search(f)] if len(results) == 1: found_file = ek(os.path.basename, results[0]) diff --git a/sickbeard/show_queue.py b/sickbeard/show_queue.py index e9bfcf309325d8a71cf3949aedaa7a785e39ec2c..4d868108cbaf08ec5e704aec54aa3e6c40004f59 100644 --- a/sickbeard/show_queue.py +++ b/sickbeard/show_queue.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import traceback @@ -432,7 +432,7 @@ class QueueItemAdd(ShowQueueItem): logger.log(traceback.format_exc(), logger.DEBUG) # update internal name cache - name_cache.buildNameCache() + name_cache.buildNameCache(self.show) try: self.show.loadEpisodesFromDir() diff --git a/sickbeard/subtitles.py b/sickbeard/subtitles.py index 7b20b57330223bc0ed47829f0ca785c6e0539bc9..15f1a56e00c17218033461978e05d9328a6e673c 100644 --- a/sickbeard/subtitles.py +++ b/sickbeard/subtitles.py @@ -13,11 +13,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import os import re @@ -25,10 +25,9 @@ import datetime import traceback import subliminal import subprocess -import pkg_resources import sickbeard -from subliminal.api import provider_manager from babelfish import Language, language_converters +from subliminal import ProviderPool, provider_manager from sickbeard import logger from sickbeard import history from sickbeard import db @@ -40,32 +39,8 @@ from sickrage.helper.encoding import ek from sickrage.helper.exceptions import ex from sickrage.show.Show import Show -DISTRIBUTION = pkg_resources.Distribution(location=os.path.dirname(os.path.dirname(__file__)), - project_name='fake_entry_points', version='1.0.0') - -ENTRY_POINTS = { - 'subliminal.providers': [ - 'addic7ed = subliminal.providers.addic7ed:Addic7edProvider', - 'legendastv = subliminal.providers.legendastv:LegendasTvProvider', - 'napiprojekt = subliminal.providers.napiprojekt:NapiProjektProvider', - 'opensubtitles = subliminal.providers.opensubtitles:OpenSubtitlesProvider', - 'podnapisi = subliminal.providers.podnapisi:PodnapisiProvider', - 'subscenter = subliminal.providers.subscenter:SubsCenterProvider', - 'thesubdb = subliminal.providers.thesubdb:TheSubDBProvider', - 'tvsubtitles = subliminal.providers.tvsubtitles:TVsubtitlesProvider' - ], - 'babelfish.language_converters': [ - 'addic7ed = subliminal.converters.addic7ed:Addic7edConverter', - 'legendastv = subliminal.converters.legendastv:LegendasTvConverter', - 'thesubdb = subliminal.converters.thesubdb:TheSubDBConverter', - 'tvsubtitles = subliminal.converters.tvsubtitles:TVsubtitlesConverter' - ] -} - -DISTRIBUTION._ep_map = pkg_resources.EntryPoint.parse_map(ENTRY_POINTS, DISTRIBUTION) # pylint: disable=protected-access -pkg_resources.working_set.add(DISTRIBUTION) - -provider_manager.ENTRY_POINT_CACHE.pop('subliminal.providers') +provider_manager.register('legendastv = subliminal.providers.legendastv:LegendasTvProvider') +provider_manager.register('napiprojekt = subliminal.providers.napiprojekt:NapiProjektProvider') subliminal.region.configure('dogpile.cache.memory') @@ -87,14 +62,14 @@ def sorted_service_list(): current_index = 0 for current_service in sickbeard.SUBTITLES_SERVICES_LIST: - if current_service in subliminal.provider_manager.names(): + if current_service in provider_manager.names(): new_list.append({'name': current_service, 'url': PROVIDER_URLS[current_service] if current_service in PROVIDER_URLS else lmgtfy % current_service, 'image': current_service + '.png', 'enabled': sickbeard.SUBTITLES_SERVICES_ENABLED[current_index] == 1}) current_index += 1 - for current_service in subliminal.provider_manager.names(): + for current_service in provider_manager.names(): if current_service not in [service['name'] for service in new_list]: new_list.append({'name': current_service, 'url': PROVIDER_URLS[current_service] if current_service in PROVIDER_URLS else lmgtfy % current_service, @@ -187,7 +162,7 @@ def download_subtitles(subtitles_info): # pylint: disable=too-many-locals, too- 'opensubtitles': {'username': sickbeard.OPENSUBTITLES_USER, 'password': sickbeard.OPENSUBTITLES_PASS}} - pool = subliminal.api.ProviderPool(providers=providers, provider_configs=provider_configs) + pool = ProviderPool(providers=providers, provider_configs=provider_configs) try: subtitles_list = pool.list_subtitles(video, languages) @@ -333,7 +308,7 @@ class SubtitlesFinder(object): 'opensubtitles': {'username': sickbeard.OPENSUBTITLES_USER, 'password': sickbeard.OPENSUBTITLES_PASS}} - pool = subliminal.api.ProviderPool(providers=providers, provider_configs=provider_configs) + pool = ProviderPool(providers=providers, provider_configs=provider_configs) # Search for all wanted languages languages = {from_code(language) for language in wanted_languages()} diff --git a/sickbeard/traktChecker.py b/sickbeard/traktChecker.py index 14d3b92e7aa00fe97f5c7a058005c7609f74db26..07c4f5a69f22062b0b320b13c40e38e71e25262a 100644 --- a/sickbeard/traktChecker.py +++ b/sickbeard/traktChecker.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import os import traceback diff --git a/sickbeard/tv.py b/sickbeard/tv.py index f9d0a51bb2bc2f702023c258a807bc48b41d53b6..640511e4b8fe1c9fc9c2e85ac7fee540bf02c24e 100644 --- a/sickbeard/tv.py +++ b/sickbeard/tv.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. # pylint: disable=too-many-lines import os.path @@ -629,30 +629,46 @@ class TVShow(object): # pylint: disable=too-many-instance-attributes, too-many- logger.log(u"Unable to parse the filename " + file + " into a valid show", logger.DEBUG) return None - if not len(parse_result.episode_numbers): + # for now lets assume that any episode in the show dir belongs to that show + season = parse_result.season_number + episodes = [ep for ep in parse_result.episode_numbers if ep is not None] + absolute_numbers = [ep for ep in parse_result.ab_episode_numbers if ep is not None] + + is_absolute = len(absolute_numbers) > 0 + + if season is None and not is_absolute: + season = 1 + + if not episodes + absolute_numbers: logger.log(u"parse_result: " + str(parse_result)) logger.log(u"No episode number found in " + file + ", ignoring it", logger.WARNING) return None - # for now lets assume that any episode in the show dir belongs to that show - season = parse_result.season_number if parse_result.season_number is not None else 1 - episodes = parse_result.episode_numbers rootEp = None sql_l = [] - for curEpNum in episodes: - - episode = int(curEpNum) + for current_ep in episodes if not is_absolute else absolute_numbers: - logger.log(u"%s: %s parsed to %s S%02dE%02d" % (self.indexerid, file, self.name, season or 0, episode or 0), logger.DEBUG) + if is_absolute: + logger.log(u"%s: %s parsed to %s with absolute number %d" % (self.indexerid, file, self.name, current_ep), logger.DEBUG) + else: + logger.log(u"%s: %s parsed to %s S%02dE%02d" % (self.indexerid, file, self.name, season, current_ep), logger.DEBUG) checkQualityAgain = False same_file = False - curEp = self.getEpisode(season, episode) + if is_absolute: + curEp = self.getEpisode(absolute_number=current_ep) + else: + curEp = self.getEpisode(season, current_ep) + if not curEp: try: - curEp = self.getEpisode(season, episode, file) + if is_absolute: + curEp = self.getEpisode(absolute_number=current_ep, file=file) + else: + curEp = self.getEpisode(season, current_ep, file) + if not curEp: raise EpisodeNotFoundException except EpisodeNotFoundException: @@ -2187,7 +2203,7 @@ class TVEpisode(object): # pylint: disable=too-many-instance-attributes, too-ma result_name = pattern # if there's no release group in the db, let the user know we replaced it - if replace_map['%RG'] and replace_map['%RG'] != 'SiCKRAGE': + if replace_map['%RG'] and replace_map['%RG'] != 'SickRage': if not hasattr(self, '_release_group'): logger.log(u"Episode has no release group, replacing it with '" + replace_map['%RG'] + "'", logger.DEBUG) self._release_group = replace_map['%RG'] # if release_group is not in the db, put it there diff --git a/sickbeard/tvcache.py b/sickbeard/tvcache.py index 17b8c6e799d889ab24a2e01aa0c72923fbd4d8ae..417e64fae259969019057161cebd7723cb31a9af 100644 --- a/sickbeard/tvcache.py +++ b/sickbeard/tvcache.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import time import datetime diff --git a/sickbeard/ui.py b/sickbeard/ui.py index 60c9273df0698ab3918f15fb63d3c7420634e88c..4946f71b267993ae24934cd4638531c26f231d27 100644 --- a/sickbeard/ui.py +++ b/sickbeard/ui.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import datetime import sickbeard diff --git a/sickbeard/versionChecker.py b/sickbeard/versionChecker.py index dfa6815474ed94e3a3997aeeed879282a39d69c7..82598358f0a0bcf8414497cbb30cf12538387fb0 100644 --- a/sickbeard/versionChecker.py +++ b/sickbeard/versionChecker.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import os import platform diff --git a/sickbeard/webapi.py b/sickbeard/webapi.py index be38f1b8eba78fed7b918daef3300e3d43be4aaa..8954fdf5c91713e8f32a35e86b3038b7220589a1 100644 --- a/sickbeard/webapi.py +++ b/sickbeard/webapi.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. # TODO: break this up into separate files # pylint: disable=line-too-long,too-many-lines,abstract-method @@ -1197,11 +1197,11 @@ class CMD_Logs(ApiCall): def run(self): """ Get the logs """ # 10 = Debug / 20 = Info / 30 = Warning / 40 = Error - min_level = logger.reverseNames[str(self.min_level).upper()] + min_level = logger.LOGGING_LEVELS[str(self.min_level).upper()] data = [] - if ek(os.path.isfile, logger.logFile): - with io.open(logger.logFile, 'r', encoding='utf-8') as f: + if ek(os.path.isfile, logger.log_file): + with io.open(logger.log_file, 'r', encoding='utf-8') as f: data = f.readlines() regex = r"^(\d\d\d\d)\-(\d\d)\-(\d\d)\s*(\d\d)\:(\d\d):(\d\d)\s*([A-Z]+)\s*(.+?)\s*\:\:\s*(.*)$" @@ -1218,11 +1218,11 @@ class CMD_Logs(ApiCall): if match: level = match.group(7) - if level not in logger.reverseNames: + if level not in logger.LOGGING_LEVELS: last_line = False continue - if logger.reverseNames[level] >= min_level: + if logger.LOGGING_LEVELS[level] >= min_level: last_line = True final_data.append(x.rstrip("\n")) else: diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py index fd4f08c47685fd1906dfdf8813777fb82efa25f0..986e7b2a84c8cc6ba635831b0d4b878b3c628ebb 100644 --- a/sickbeard/webserve.py +++ b/sickbeard/webserve.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. # pylint: disable=abstract-method,too-many-lines import io @@ -81,6 +81,7 @@ except ImportError: from mako.template import Template as MakoTemplate from mako.lookup import TemplateLookup +from mako.exceptions import RichTraceback from tornado.routes import route from tornado.web import RequestHandler, HTTPError, authenticated @@ -106,7 +107,11 @@ def get_lookup(): mako_cache = ek(os.path.join, sickbeard.CACHE_DIR, 'mako') if mako_lookup is None: use_strict = sickbeard.BRANCH and sickbeard.BRANCH != 'master' - mako_lookup = TemplateLookup(directories=[mako_path], module_directory=mako_cache, format_exceptions=True, strict_undefined=use_strict) + mako_lookup = TemplateLookup(directories=[mako_path], + module_directory=mako_cache, + # format_exceptions=True, + strict_undefined=use_strict, + filesystem_checks=True) return mako_lookup @@ -158,9 +163,13 @@ class PageTemplate(MakoTemplate): kwargs[key] = self.arguments[key] kwargs['makoStartTime'] = time.time() - - return self.template.render_unicode(*args, **kwargs) - + try: + return self.template.render_unicode(*args, **kwargs) + except Exception: + kwargs['title'] = '500' + kwargs['header'] = 'Mako Error' + kwargs['backtrace'] = RichTraceback() + return get_lookup().get_template('500.mako').render_unicode(*args, **kwargs) class BaseHandler(RequestHandler): startTime = 0. @@ -181,7 +190,8 @@ class BaseHandler(RequestHandler): url = url[len(sickbeard.WEB_ROOT) + 1:] if url[:3] != 'api': - return self.redirect('/') + t = PageTemplate(rh=self, filename="404.mako") + return self.finish(t.render(title='404', header='Oops')) else: self.finish('Wrong API key used') @@ -802,6 +812,15 @@ class Home(WebRoot): else: return "Problem sending SMS: " + message + @staticmethod + def testTelegram(telegram_id=None, telegram_apikey=None): + + result, message = notifiers.telegram_notifier.test_notify(telegram_id, telegram_apikey) + if result: + return "Telegram notification succeeded. Check your Telegram clients to make sure it worked" + else: + return "Error sending Telegram notification: " + message + @staticmethod def testGrowl(host=None, password=None): # self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') @@ -1211,9 +1230,11 @@ class Home(WebRoot): [showObj.indexerid] ) + min_season = 0 if sickbeard.DISPLAY_SHOW_SPECIALS else 1 + sqlResults = myDB.select( - "SELECT * FROM tv_episodes WHERE showid = ? ORDER BY season DESC, episode DESC", - [showObj.indexerid] + "SELECT * FROM tv_episodes WHERE showid = ? and season >= ? ORDER BY season DESC, episode DESC", + [showObj.indexerid, min_season] ) t = PageTemplate(rh=self, filename="displayShow.mako") @@ -4750,6 +4771,8 @@ class ConfigNotifications(Config): growl_notify_onsubtitledownload=None, growl_host=None, growl_password=None, use_freemobile=None, freemobile_notify_onsnatch=None, freemobile_notify_ondownload=None, freemobile_notify_onsubtitledownload=None, freemobile_id=None, freemobile_apikey=None, + use_telegram=None, telegram_notify_onsnatch=None, telegram_notify_ondownload=None, + telegram_notify_onsubtitledownload=None, telegram_id=None, telegram_apikey=None, use_prowl=None, prowl_notify_onsnatch=None, prowl_notify_ondownload=None, prowl_notify_onsubtitledownload=None, prowl_api=None, prowl_priority=0, prowl_show_list=None, prowl_show=None, prowl_message_title=None, @@ -4831,6 +4854,13 @@ class ConfigNotifications(Config): sickbeard.FREEMOBILE_ID = freemobile_id sickbeard.FREEMOBILE_APIKEY = freemobile_apikey + sickbeard.USE_TELEGRAM = config.checkbox_to_value(use_telegram) + sickbeard.TELEGRAM_NOTIFY_ONSNATCH = config.checkbox_to_value(telegram_notify_onsnatch) + sickbeard.TELEGRAM_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(telegram_notify_ondownload) + sickbeard.TELEGRAM_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(telegram_notify_onsubtitledownload) + sickbeard.TELEGRAM_ID = telegram_id + sickbeard.TELEGRAM_APIKEY = telegram_apikey + sickbeard.USE_PROWL = config.checkbox_to_value(use_prowl) sickbeard.PROWL_NOTIFY_ONSNATCH = config.checkbox_to_value(prowl_notify_onsnatch) sickbeard.PROWL_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(prowl_notify_ondownload) @@ -5115,7 +5145,7 @@ class ErrorLogs(WebRoot): logName = match.group(8) if not sickbeard.DEBUG and (level == 'DEBUG' or level == 'DB'): continue - if level not in logger.reverseNames: + if level not in logger.LOGGING_LEVELS: lastLine = False continue @@ -5123,7 +5153,7 @@ class ErrorLogs(WebRoot): lastLine = True finalData.append(x) numLines += 1 - elif not logSearch and logger.reverseNames[level] >= minLevel and (logFilter == '<NONE>' or logName.startswith(logFilter)): + elif not logSearch and logger.LOGGING_LEVELS[level] >= minLevel and (logFilter == '<NONE>' or logName.startswith(logFilter)): lastLine = True finalData.append(x) numLines += 1 @@ -5175,13 +5205,13 @@ class ErrorLogs(WebRoot): data = [] - if ek(os.path.isfile, logger.logFile): - with io.open(logger.logFile, 'r', encoding='utf-8') as f: + if ek(os.path.isfile, logger.log_file): + with io.open(logger.log_file, 'r', encoding='utf-8') as f: data = Get_Data(minLevel, f.readlines(), 0, regex, logFilter, logSearch, maxLines) for i in range(1, int(sickbeard.LOG_NR)): - if ek(os.path.isfile, logger.logFile + "." + str(i)) and (len(data) <= maxLines): - with io.open(logger.logFile + "." + str(i), 'r', encoding='utf-8') as f: + if ek(os.path.isfile, logger.log_file + "." + str(i)) and (len(data) <= maxLines): + with io.open(logger.log_file + "." + str(i), 'r', encoding='utf-8') as f: data += Get_Data(minLevel, f.readlines(), len(data), regex, logFilter, logSearch, maxLines) return t.render( diff --git a/sickbeard/webserveInit.py b/sickbeard/webserveInit.py index fcfa1ff7ff515c209c13cab3793e54dda54b2aeb..433fac267e5014f7783431f708116a9ec970cfb6 100644 --- a/sickbeard/webserveInit.py +++ b/sickbeard/webserveInit.py @@ -15,15 +15,15 @@ from tornado.ioloop import IOLoop from tornado.routes import route -class SRWebServer(threading.Thread): - def __init__(self, options={}, io_loop=None): +class SRWebServer(threading.Thread): # pylint: disable=too-many-instance-attributes + def __init__(self, options=None, io_loop=None): threading.Thread.__init__(self) self.daemon = True self.alive = True self.name = "TORNADO" self.io_loop = io_loop or IOLoop.current() - self.options = options + self.options = options or {} self.options.setdefault('port', 8081) self.options.setdefault('host', '0.0.0.0') self.options.setdefault('log_dir', None) @@ -33,6 +33,8 @@ class SRWebServer(threading.Thread): assert isinstance(self.options['port'], int) assert 'data_root' in self.options + self.server = None + # video root if sickbeard.ROOT_DIRS: root_dirs = sickbeard.ROOT_DIRS.split('|') @@ -94,14 +96,12 @@ class SRWebServer(threading.Thread): (r'%s/login(/?)' % self.options['web_root'], LoginHandler), (r'%s/logout(/?)' % self.options['web_root'], LogoutHandler), + # Web calendar handler (Needed because option Unprotected calendar) + (r'%s/calendar' % self.options['web_root'], CalendarHandler), + # webui handlers ] + route.get_routes(self.options['web_root'])) - # Web calendar handler (Needed because option Unprotected calendar) - self.app.add_handlers('.*$', [ - (r'%s/calendar' % self.options['web_root'], CalendarHandler), - ]) - # Static File Handlers self.app.add_handlers(".*$", [ # favicon @@ -124,9 +124,14 @@ class SRWebServer(threading.Thread): (r'%s/js/(.*)' % self.options['web_root'], StaticFileHandler, {"path": ek(os.path.join, self.options['data_root'], 'js')}), + # fonts + (r'%s/fonts/(.*)' % self.options['web_root'], StaticFileHandler, + {"path": ek(os.path.join, self.options['data_root'], 'fonts')}), + # videos - ] + [(r'%s/videos/(.*)' % self.options['web_root'], StaticFileHandler, - {"path": self.video_root})]) + (r'%s/videos/(.*)' % self.options['web_root'], StaticFileHandler, + {"path": self.video_root}) + ]) def run(self): if self.enable_https: @@ -141,12 +146,12 @@ class SRWebServer(threading.Thread): try: self.server.listen(self.options['port'], self.options['host']) - except: + except Exception: if sickbeard.LAUNCH_BROWSER and not self.daemon: sickbeard.launchBrowser('https' if sickbeard.ENABLE_HTTPS else 'http', self.options['port'], sickbeard.WEB_ROOT) logger.log(u"Launching browser and exiting") logger.log(u"Could not start webserver on port %s, already in use!" % self.options['port']) - os._exit(1) + os._exit(1) # pylint: disable=protected-access try: self.io_loop.start() diff --git a/sickrage/helper/common.py b/sickrage/helper/common.py index 5fad42bfd4a434c039076ea04a5d31e225c1d0a0..5def83b5184e37189827b7446f9899b0892b48ff 100644 --- a/sickrage/helper/common.py +++ b/sickrage/helper/common.py @@ -11,11 +11,13 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. + +from __future__ import unicode_literals import re import sickbeard @@ -90,13 +92,13 @@ http_status_code = { 509: 'Bandwidth Limit Exceeded', 510: 'Not Extended', 511: 'Network Authentication Required', - 520: 'Cloudfare - Web server is returning an unknown error', - 521: 'Cloudfare - Web server is down', - 522: 'Cloudfare - Connection timed out', - 523: 'Cloudfare - Origin is unreachable', - 524: 'Cloudfare - A timeout occurred', - 525: 'Cloudfare - SSL handshake failed', - 526: 'Cloudfare - Invalid SSL certificate', + 520: 'CloudFlare - Web server is returning an unknown error', + 521: 'CloudFlare - Web server is down', + 522: 'CloudFlare - Connection timed out', + 523: 'CloudFlare - Origin is unreachable', + 524: 'CloudFlare - A timeout occurred', + 525: 'CloudFlare - SSL handshake failed', + 526: 'CloudFlare - Invalid SSL certificate', 598: 'Network read timeout error', 599: 'Network connect timeout error', } @@ -124,7 +126,7 @@ def http_code_description(http_code): return description # TODO Restore logger import - # logger.log(u'Unknown HTTP status code %s. Please submit an issue' % http_code, logger.ERROR) + # logger.log('Unknown HTTP status code %s. Please submit an issue' % http_code, logger.ERROR) return None @@ -157,10 +159,15 @@ def is_torrent_or_nzb_file(filename): return filename.rpartition('.')[2].lower() in ['nzb', 'torrent'] -def pretty_file_size(size): +def pretty_file_size(size, use_decimal=False, **kwargs): """ Return a human readable representation of the provided ``size``. + :param size: The size to convert + :param use_decimal: use decimal instead of binary prefixes (e.g. kilo = 1000 instead of 1024) + + :keyword units: A list of unit names in ascending order. Default units: ['B', 'KB', 'MB', 'GB', 'TB', 'PB'] + :return: The converted size """ try: @@ -169,13 +176,68 @@ def pretty_file_size(size): size = 0. remaining_size = size - for unit in ['B', 'KB', 'MB', 'GB', 'TB', 'PB']: - if remaining_size < 1024.: + units = kwargs.pop('units', ['B', 'KB', 'MB', 'GB', 'TB', 'PB']) + block = 1024. if not use_decimal else 1000. + for unit in units: + if remaining_size < block: return '%3.2f %s' % (remaining_size, unit) - remaining_size /= 1024. + remaining_size /= block return size +def convert_size(size, default=None, use_decimal=False, **kwargs): + """ + Convert a file size into the number of bytes + + :param size: to be converted + :param default: value to return if conversion fails + :param use_decimal: use decimal instead of binary prefixes (e.g. kilo = 1000 instead of 1024) + + :keyword sep: Separator between size and units, default is space + :keyword units: A list of (uppercase) unit names in ascending order. Default units: ['B', 'KB', 'MB', 'GB', 'TB', 'PB'] + :keyword default_units: Default unit if none is given, default is lowest unit in the scale, e.g. bytes + + :returns: the number of bytes, the default value, or 0 + """ + result = None + + try: + sep = kwargs.pop('sep', ' ') + scale = kwargs.pop('units', ['B', 'KB', 'MB', 'GB', 'TB', 'PB']) + default_units = kwargs.pop('default_units', scale[0]) + + if sep: + size_tuple = size.strip().split(sep) + scalar, units = size_tuple[0], size_tuple[1:] + units = units[0].upper() if units else default_units + else: + regex_units = re.search(r'(\w+)', size, re.IGNORECASE) + units = regex_units.group() if regex_units else default_units + scalar = size.strip(units) + + scalar = float(scalar) + scalar *= (1024 if not use_decimal else 1000) ** scale.index(units) + + result = scalar + + # TODO: Make sure fallback methods obey default units + except AttributeError: + result = size if size is not None else default + + except ValueError: + result = default + + finally: + try: + if result != default: + result = long(result) + result = max(result, 0) + except (TypeError, ValueError): + pass + + return result + + def remove_extension(filename): """ Remove the extension of the provided ``filename``. @@ -220,7 +282,7 @@ def sanitize_filename(filename): if isinstance(filename, (str, unicode)): filename = re.sub(r'[\\/\*]', '-', filename) filename = re.sub(r'[:"<>|?]', '', filename) - filename = re.sub(ur'\u2122', '', filename) # Trade Mark Sign + filename = re.sub(r'™', '', filename) # Trade Mark Sign unicode: \u2122 filename = filename.strip(' .') return filename @@ -238,5 +300,5 @@ def try_int(candidate, default_value=0): try: return int(candidate) - except Exception: + except (ValueError, TypeError): return default_value diff --git a/sickrage/helper/encoding.py b/sickrage/helper/encoding.py index e199b3d61157ff9873c4e5b77643c73bad34ee52..97dbde1d56df43cd663cfbeb79242f9b2378d3f9 100644 --- a/sickrage/helper/encoding.py +++ b/sickrage/helper/encoding.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import sickbeard diff --git a/sickrage/helper/exceptions.py b/sickrage/helper/exceptions.py index 560e27af7ad1c8a4e425bcf7d2e559bf3e08928b..a6b1026c9b68fe3a89ad73bcb05fd4886372b244 100644 --- a/sickrage/helper/exceptions.py +++ b/sickrage/helper/exceptions.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. from sickrage.helper.encoding import ss diff --git a/sickrage/helper/quality.py b/sickrage/helper/quality.py index 85002db566f4370486d61ab821da6faa6c4bac08..49b1fce186801b7babf2632988162bd93c10835e 100644 --- a/sickrage/helper/quality.py +++ b/sickrage/helper/quality.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. from sickbeard.common import Quality, qualityPresetStrings diff --git a/sickrage/media/GenericMedia.py b/sickrage/media/GenericMedia.py index b3d56e2ac7fb11a70ae4a8c0563f59843c74dbf1..955c30c2d0b950a51cb371bb6eaabe8581a27f61 100644 --- a/sickrage/media/GenericMedia.py +++ b/sickrage/media/GenericMedia.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import sickbeard diff --git a/sickrage/media/ShowBanner.py b/sickrage/media/ShowBanner.py index ac0427514819bc73046a1d8b3cb281065f91a80e..fccf7d888e454dec12d314401b94adc4c0bb0115 100644 --- a/sickrage/media/ShowBanner.py +++ b/sickrage/media/ShowBanner.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. from sickbeard.image_cache import ImageCache from sickrage.media.GenericMedia import GenericMedia diff --git a/sickrage/media/ShowFanArt.py b/sickrage/media/ShowFanArt.py index 36e0b1cc5ab68db5304b42f1b9850e4908c3ca17..4376f1ddd49e3fb59965f2196b5e223739fd798d 100644 --- a/sickrage/media/ShowFanArt.py +++ b/sickrage/media/ShowFanArt.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. from sickbeard.image_cache import ImageCache from sickrage.media.GenericMedia import GenericMedia diff --git a/sickrage/media/ShowNetworkLogo.py b/sickrage/media/ShowNetworkLogo.py index 59910b32051f5ec8a02ee24e686a4e460fd7d7ac..58034e2dd1251cefc8fd50da44981b99bbaf3891 100644 --- a/sickrage/media/ShowNetworkLogo.py +++ b/sickrage/media/ShowNetworkLogo.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. from os.path import join from sickrage.helper.encoding import ek diff --git a/sickrage/media/ShowPoster.py b/sickrage/media/ShowPoster.py index 86629649140b0760616a47e83577cecffc61da07..2b6b01c9e0ae9fe6a3ad7c085d29a8f81c527de6 100644 --- a/sickrage/media/ShowPoster.py +++ b/sickrage/media/ShowPoster.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. from sickbeard.image_cache import ImageCache from sickrage.media.GenericMedia import GenericMedia diff --git a/sickrage/providers/GenericProvider.py b/sickrage/providers/GenericProvider.py index 669b51398c1e4bab159945f6948469e559f9e148..f8bba141cb3caf84501fb737b32e2fc0df2c82b6 100644 --- a/sickrage/providers/GenericProvider.py +++ b/sickrage/providers/GenericProvider.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import re import sickbeard diff --git a/sickrage/providers/nzb/NZBProvider.py b/sickrage/providers/nzb/NZBProvider.py index 89d3f15d0942bd08df69eab1345f4d9300428290..3a7a6dcf2251b4fae2c71804432743cf7f46c1c8 100644 --- a/sickrage/providers/nzb/NZBProvider.py +++ b/sickrage/providers/nzb/NZBProvider.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import sickbeard diff --git a/sickrage/providers/torrent/TorrentProvider.py b/sickrage/providers/torrent/TorrentProvider.py index e0b91945b7076427d7c201d006713306c57ea699..51b03fd2c69eb901d0e52ae2d2271d842e68ec50 100644 --- a/sickrage/providers/torrent/TorrentProvider.py +++ b/sickrage/providers/torrent/TorrentProvider.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import sickbeard diff --git a/sickrage/show/ComingEpisodes.py b/sickrage/show/ComingEpisodes.py index 976fc780467324b361c9d79230b4ef89213546f6..e19511f8c265a5b90eed4e49b222d990d6030a36 100644 --- a/sickrage/show/ComingEpisodes.py +++ b/sickrage/show/ComingEpisodes.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import sickbeard diff --git a/sickrage/show/History.py b/sickrage/show/History.py index 35a372f848dc3fecd27d77ba8d54eb9f597b0264..4d9099a2497ccc582cfc827854faf114354bcb2c 100644 --- a/sickrage/show/History.py +++ b/sickrage/show/History.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. from datetime import datetime from datetime import timedelta diff --git a/sickrage/show/Show.py b/sickrage/show/Show.py index 034269475e28baad0a74c26e098bca4c1d74ca22..2e66067d4967931a537f50189b15e17c4b55f5cd 100644 --- a/sickrage/show/Show.py +++ b/sickrage/show/Show.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import sickbeard diff --git a/sickrage/system/Restart.py b/sickrage/system/Restart.py index 48056638b228c68dc1f696590accf150afff7e13..399e909535fd64f79dc4533ffef34d2b2ccbc6a7 100644 --- a/sickrage/system/Restart.py +++ b/sickrage/system/Restart.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import sickbeard diff --git a/sickrage/system/Shutdown.py b/sickrage/system/Shutdown.py index ca4501eb3d244e6cfe6a75ab30a2e8cdddf3bcb1..813bea5a582daf4b494948baa3b29aa124fa9b81 100644 --- a/sickrage/system/Shutdown.py +++ b/sickrage/system/Shutdown.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. import sickbeard diff --git a/tests/all_tests.py b/tests/all_tests.py index 47aea896f0f49e69e44dfb3cf51770a417364551..b0d4312dda2aaa0eaa2df78ea64e327d2bd7917c 100755 --- a/tests/all_tests.py +++ b/tests/all_tests.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. """ Perform all tests in tests/ diff --git a/tests/db_tests.py b/tests/db_tests.py index 6088dc342ed987a1c75f344b353a5267c80aa02c..0c0ce59bd7731235de925cd82c336cde3d3b41ba 100644 --- a/tests/db_tests.py +++ b/tests/db_tests.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. """ Test show database functionality. diff --git a/tests/helpers_tests.py b/tests/helpers_tests.py index 1ec1cb4605e726317f0c03b068c1dc6b1846f8a0..bc997e2e029dcbd415dd4a64f11ead79028b422a 100755 --- a/tests/helpers_tests.py +++ b/tests/helpers_tests.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. """ Test sickbeard.helpers diff --git a/tests/issue_submitter_tests.py b/tests/issue_submitter_tests.py index c929ee284587eae0e1d75a51c9cfacbeff794631..283584d8c8ed44c799bdc5a068160702e1258d05 100644 --- a/tests/issue_submitter_tests.py +++ b/tests/issue_submitter_tests.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. """ Test exception logging diff --git a/tests/notifier_tests.py b/tests/notifier_tests.py index 6b4decb3b3f80340575fe39e8d8125f83d94cc66..5a176f13fbb7bf564c7cdec6e8cc8a192b362349 100644 --- a/tests/notifier_tests.py +++ b/tests/notifier_tests.py @@ -10,11 +10,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. # ############################################################################## diff --git a/tests/pp_tests.py b/tests/pp_tests.py index 30ecdc4750928f9d253f526fca0e0708a17aac96..f5cadaca0505797b94af536698486e093835e0bd 100644 --- a/tests/pp_tests.py +++ b/tests/pp_tests.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. """ Test post processing diff --git a/tests/scene_helpers_tests.py b/tests/scene_helpers_tests.py index 9241c69fa22de4eb087c89b0370173a40decd4bd..4a357fa7fad86c0cbdfd8739b8347ef8fbe4665d 100644 --- a/tests/scene_helpers_tests.py +++ b/tests/scene_helpers_tests.py @@ -21,21 +21,6 @@ class SceneTests(test.SickbeardTestDBCase): """ Test Scene """ - def _test_scene_to_norm_show_name(self, name, expected): - """ - Test scene to normal show names - - :param name: - :param expected: - :return: - """ - result = show_name_helpers.sceneToNormalShowNames(name) - self.assertTrue(len(set(expected).intersection(set(result))) == len(expected)) - - dot_result = show_name_helpers.sceneToNormalShowNames(name.replace(' ', '.')) - dot_expected = [x.replace(' ', '.') for x in expected] - self.assertTrue(len(set(dot_expected).intersection(set(dot_result))) == len(dot_expected)) - def _test_all_possible_show_names(self, name, indexerid=0, expected=None): """ Test all possible show names @@ -63,51 +48,6 @@ class SceneTests(test.SickbeardTestDBCase): result = show_name_helpers.filterBadReleases(name) self.assertEqual(result, expected) - def _test_is_good_name(self, name, show): - """ - Test if name is good - - :param name: - :param show: - :return: - """ - self.assertTrue(show_name_helpers.isGoodResult(name, show)) - - def test_is_good_name(self): - """ - Perform good name tests - """ - list_of_cases = [('Show.Name.S01E02.Test-Test', 'Show/Name'), - ('Show.Name.S01E02.Test-Test', 'Show. Name'), - ('Show.Name.S01E02.Test-Test', 'Show- Name'), - ('Show.Name.Part.IV.Test-Test', 'Show Name'), - ('Show.Name.S01.Test-Test', 'Show Name'), - ('Show.Name.E02.Test-Test', 'Show: Name'), - ('Show Name Season 2 Test', 'Show: Name'), ] - - for test_case in list_of_cases: - scene_name, show_name = test_case - show = Show(1, 0) - show.name = show_name - self._test_is_good_name(scene_name, show) - - def test_scene_to_norm_show_names(self): - """ - Test scene to normal show names - """ - self._test_scene_to_norm_show_name('Show Name 2010', ['Show Name 2010', 'Show Name (2010)']) - self._test_scene_to_norm_show_name('Show Name US', ['Show Name US', 'Show Name (US)']) - self._test_scene_to_norm_show_name('Show Name AU', ['Show Name AU', 'Show Name (AU)']) - self._test_scene_to_norm_show_name('Show Name CA', ['Show Name CA', 'Show Name (CA)']) - self._test_scene_to_norm_show_name('Show and Name', ['Show and Name', 'Show & Name']) - self._test_scene_to_norm_show_name('Show and Name 2010', ['Show and Name 2010', 'Show & Name 2010', 'Show and Name (2010)', 'Show & Name (2010)']) - self._test_scene_to_norm_show_name('show name us', ['show name us', 'show name (us)']) - self._test_scene_to_norm_show_name('Show And Name', ['Show And Name', 'Show & Name']) - - # failure cases - self._test_scene_to_norm_show_name('Show Name 90210', ['Show Name 90210']) - self._test_scene_to_norm_show_name('Show Name YA', ['Show Name YA']) - def test_all_possible_show_names(self): """ Test all possible show names diff --git a/tests/search_tests.py b/tests/search_tests.py index 326a4f2f2388c010eeba5bbbf16b26c17c2d5003..3a50d95d209589130fe88526e14f731bb4dc4ca9 100644 --- a/tests/search_tests.py +++ b/tests/search_tests.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. # pylint: disable=line-too-long @@ -64,7 +64,7 @@ def test_generator(cur_data, cur_name, cur_provider): :return: """ - def do_test(): + def do_test(self): """ Test to perform """ diff --git a/tests/sickrage_tests/__init__.py b/tests/sickrage_tests/__init__.py index a504b8b1db037eb7bf86e88cc8aa2ef29110a251..b873eb3c1037554b77c80aaeacad9cf84b10a041 100644 --- a/tests/sickrage_tests/__init__.py +++ b/tests/sickrage_tests/__init__.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. """ Tests for SickRage diff --git a/tests/sickrage_tests/helper/common_tests.py b/tests/sickrage_tests/helper/common_tests.py index 766084ea5404d297b8d1bd9a4ca5d30112cdb0db..ffdd05bb873746af82379ae09b703a7143cfdcb9 100644 --- a/tests/sickrage_tests/helper/common_tests.py +++ b/tests/sickrage_tests/helper/common_tests.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. # pylint: disable=line-too-long @@ -34,7 +34,7 @@ sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '../. import sickbeard from sickrage.helper.common import http_code_description, is_sync_file, is_torrent_or_nzb_file, pretty_file_size -from sickrage.helper.common import remove_extension, replace_extension, sanitize_filename, try_int +from sickrage.helper.common import remove_extension, replace_extension, sanitize_filename, try_int, convert_size class CommonTests(unittest.TestCase): @@ -397,6 +397,47 @@ class CommonTests(unittest.TestCase): for (candidate, result) in test.iteritems(): self.assertEqual(try_int(candidate, default_value), result) + def test_convert_size(self): + # converts pretty file sizes to integers + self.assertEqual(convert_size('1 B'), 1) + self.assertEqual(convert_size('1 KB'), 1024) + self.assertEqual(convert_size('1 kb', use_decimal=True), 1000) # can use decimal units (e.g. KB = 1000 bytes instead of 1024) + + # returns integer sizes for integers + self.assertEqual(convert_size(0, -1), 0) + self.assertEqual(convert_size(100, -1), 100) + self.assertEqual(convert_size(1.312, -1), 1) # returns integer sizes for floats too + + # without a default value, failures return None + self.assertEqual(convert_size('pancakes'), None) + + # default value can be anything + self.assertEqual(convert_size(None, -1), -1) + self.assertEqual(convert_size('', 3.14), 3.14) + self.assertEqual(convert_size('elephant', 'frog'), 'frog') + + # negative sizes return 0 + self.assertEqual(convert_size(-1024, -1), 0) + self.assertEqual(convert_size('-1 GB', -1), 0) + + # can also use `or` for a default value + self.assertEqual(convert_size(None) or 100, 100) + self.assertEqual(convert_size(None) or 1.61803, 1.61803) # default doesn't have to be integer + self.assertEqual(convert_size(None) or '100', '100') # default doesn't have to be numeric either + self.assertEqual(convert_size('-1 GB') or -1, -1) # can use `or` to provide a default when size evaluates to 0 + + # can use custom dictionary to support internationalization + french = ['O', 'KO', 'MO', 'GO', 'TO', 'PO'] + self.assertEqual(convert_size('1 o', units=french), 1) + self.assertEqual(convert_size('1 go', use_decimal=True, units=french), 1000000000) + self.assertEqual(convert_size('1 o'), None) # Wrong units so result is None + + # custom units need to be uppercase or they won't match + oops = ['b', 'kb', 'Mb', 'Gb', 'tB', 'Pb'] + self.assertEqual(convert_size('1 b', units=oops), None) + self.assertEqual(convert_size('1 B', units=oops), None) + self.assertEqual(convert_size('1 Mb', units=oops), None) + self.assertEqual(convert_size('1 MB', units=oops), None) if __name__ == '__main__': print('=====> Testing %s' % __file__) diff --git a/tests/sickrage_tests/helper/quality_tests.py b/tests/sickrage_tests/helper/quality_tests.py index be0021fadabd078fac6f21b8a46a0a1af93a5278..9c802fa4a4196e2d1fa6617802ace55468fd2961 100644 --- a/tests/sickrage_tests/helper/quality_tests.py +++ b/tests/sickrage_tests/helper/quality_tests.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. """ Test qualities diff --git a/tests/sickrage_tests/media/generic_media_tests.py b/tests/sickrage_tests/media/generic_media_tests.py index b7079bd67a40223ff19565d22dc11c548c50350b..124691bc40b55955af59e5beb835faeeb1b2b955 100644 --- a/tests/sickrage_tests/media/generic_media_tests.py +++ b/tests/sickrage_tests/media/generic_media_tests.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. """ Test GenericMedia diff --git a/tests/sickrage_tests/media/show_banner_tests.py b/tests/sickrage_tests/media/show_banner_tests.py index 68233b51e015edd236746bfbfba8f2d920739f70..f61379eb1dc9323e753f48c9512feda5e3b225c5 100644 --- a/tests/sickrage_tests/media/show_banner_tests.py +++ b/tests/sickrage_tests/media/show_banner_tests.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. """ Test ShowBanner diff --git a/tests/sickrage_tests/media/show_fan_art_tests.py b/tests/sickrage_tests/media/show_fan_art_tests.py index 6e5be0582097ab9f9a9711894ceedd86d0e2053c..e82e9250a2fac5d28804b0a995451c9f73b467ba 100644 --- a/tests/sickrage_tests/media/show_fan_art_tests.py +++ b/tests/sickrage_tests/media/show_fan_art_tests.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. """ Test ShowFanArt diff --git a/tests/sickrage_tests/media/show_network_logo_tests.py b/tests/sickrage_tests/media/show_network_logo_tests.py index 3bce111d5b77f3c18c1e3b0595512f68bcfe712f..a98b7e5fa57871f073ae1e4b6b24b16631fd4de5 100644 --- a/tests/sickrage_tests/media/show_network_logo_tests.py +++ b/tests/sickrage_tests/media/show_network_logo_tests.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. """ Test ShowNetworkLogo diff --git a/tests/sickrage_tests/media/show_poster_tests.py b/tests/sickrage_tests/media/show_poster_tests.py index 16a8a8bc21eb7e2d24760c5d7a1603d9496f56d7..248b5e135b8af88279cee0f38101786cf59d191b 100644 --- a/tests/sickrage_tests/media/show_poster_tests.py +++ b/tests/sickrage_tests/media/show_poster_tests.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. """ Test ShowPoster diff --git a/tests/sickrage_tests/providers/generic_provider_tests.py b/tests/sickrage_tests/providers/generic_provider_tests.py index 335491dad6e396b753e2c53868921124318dac42..cfb078a21930e6d11d850827419e4c9965322656 100644 --- a/tests/sickrage_tests/providers/generic_provider_tests.py +++ b/tests/sickrage_tests/providers/generic_provider_tests.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. """ Test GenericProvider diff --git a/tests/sickrage_tests/providers/nzb_provider_tests.py b/tests/sickrage_tests/providers/nzb_provider_tests.py index 2cafcdb54e953595d2aade8dba02633fdc3a31cc..d11064459f95c20eefb8b7754fa64c426d208512 100644 --- a/tests/sickrage_tests/providers/nzb_provider_tests.py +++ b/tests/sickrage_tests/providers/nzb_provider_tests.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. """ Test NZBProvider diff --git a/tests/sickrage_tests/providers/torrent_provider_tests.py b/tests/sickrage_tests/providers/torrent_provider_tests.py index 48e772144d168a288cf9f2e7e3ea4eb543845bf1..83e77c7fbafacd6d59638730d4617fa98ae57457 100644 --- a/tests/sickrage_tests/providers/torrent_provider_tests.py +++ b/tests/sickrage_tests/providers/torrent_provider_tests.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. """ Test TorrentProvider diff --git a/tests/sickrage_tests/show/coming_episodes_tests.py b/tests/sickrage_tests/show/coming_episodes_tests.py index 387f90839a1c9b6b65ee36a5594a8ced99ea5a3b..b24a33f3eaa0ac109fc3af297e7ab4f82e377ab2 100644 --- a/tests/sickrage_tests/show/coming_episodes_tests.py +++ b/tests/sickrage_tests/show/coming_episodes_tests.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. """ Test coming episodes diff --git a/tests/sickrage_tests/show/history_tests.py b/tests/sickrage_tests/show/history_tests.py index 74aac381b2ffbfe2398e934325d66549d383bb41..a671a5f11006bbc3d8b3f6dc7e75eca741b0a9e6 100644 --- a/tests/sickrage_tests/show/history_tests.py +++ b/tests/sickrage_tests/show/history_tests.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. """ Test history diff --git a/tests/sickrage_tests/show/show_tests.py b/tests/sickrage_tests/show/show_tests.py index f28ec0c6b71a3d9297166abb8dcee4bfcd571b81..ff353a3f639068fbfb50ea0edd6e2a53a9ad8062 100644 --- a/tests/sickrage_tests/show/show_tests.py +++ b/tests/sickrage_tests/show/show_tests.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. """ Test shows diff --git a/tests/sickrage_tests/system/restart_tests.py b/tests/sickrage_tests/system/restart_tests.py index e0b6b6c47cf4e80a0c1ebf8824d7d73d250dfe29..fdfe6f17523fc9b84b90d20b22cb14caede90c45 100644 --- a/tests/sickrage_tests/system/restart_tests.py +++ b/tests/sickrage_tests/system/restart_tests.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. """ Test restart diff --git a/tests/sickrage_tests/system/shutdown_tests.py b/tests/sickrage_tests/system/shutdown_tests.py index 7b425c0d4669104fe86c66de23018064f4b721d7..7ee0b294d57a78099cac27ea5c3c8429b3461615 100644 --- a/tests/sickrage_tests/system/shutdown_tests.py +++ b/tests/sickrage_tests/system/shutdown_tests.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. """ Test shutdown diff --git a/tests/snatch_tests.py b/tests/snatch_tests.py index c629f5f66bbf614b432a13d273a1c44d4ae0dd0e..fdab792bd99ce56c5320f41e4903c34f299d0a52 100644 --- a/tests/snatch_tests.py +++ b/tests/snatch_tests.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. # pylint: disable=line-too-long diff --git a/tests/ssl_sni_tests.py b/tests/ssl_sni_tests.py index f69618501ac8d39b9ade1ba47a5e8b34e879be11..d60ff29f0c17edbc75d5667ecb5fc0e786678a29 100644 --- a/tests/ssl_sni_tests.py +++ b/tests/ssl_sni_tests.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. # pylint: disable=line-too-long diff --git a/tests/test_lib.py b/tests/test_lib.py index a3381ae84e23f85e5f5b1767d8df1868d8f4b49d..7e8a7545726bc3b1d8b72bebdfc90295ad2ef92b 100644 --- a/tests/test_lib.py +++ b/tests/test_lib.py @@ -12,11 +12,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. # pylint: disable=line-too-long @@ -126,14 +126,14 @@ sickbeard.GIT_USERNAME = sickbeard.config.check_setting_str(sickbeard.CFG, 'Gene sickbeard.GIT_PASSWORD = sickbeard.config.check_setting_str(sickbeard.CFG, 'General', 'git_password', '', censor_log=True) sickbeard.LOG_DIR = os.path.join(TEST_DIR, 'Logs') -sickbeard.logger.logFile = os.path.join(sickbeard.LOG_DIR, 'test_sickbeard.log') +sickbeard.logger.log_file = os.path.join(sickbeard.LOG_DIR, 'test_sickbeard.log') create_test_log_folder() sickbeard.CACHE_DIR = os.path.join(TEST_DIR, 'cache') create_test_cache_folder() # pylint: disable=no-member -sickbeard.logger.initLogging(False, True) +sickbeard.logger.init_logging(False, True) # ================= diff --git a/tests/torrent_tests.py b/tests/torrent_tests.py index 665be92ed4083a3243507f76984a4f4a31475804..3f3974205ae0cc91de0c8ae4fe086deb2fe1ae4a 100644 --- a/tests/torrent_tests.py +++ b/tests/torrent_tests.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. """ Test torrents diff --git a/tests/tv_tests.py b/tests/tv_tests.py index 1b02e70940040affdf6db4977df94336d4a78a01..c76cb96af429dc59b96d7b5d7f5e04abfeebb80e 100644 --- a/tests/tv_tests.py +++ b/tests/tv_tests.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. """ Test tv diff --git a/tests/xem_tests.py b/tests/xem_tests.py index 4341c6625bf49cbedf285643a8e0028e03c13b9c..a9e5596c669bf53c5c841d2d8bee8e104e238d99 100644 --- a/tests/xem_tests.py +++ b/tests/xem_tests.py @@ -11,11 +11,11 @@ # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. """ Test XEM