diff --git a/autoProcessTV/autoProcessTV.py b/autoProcessTV/autoProcessTV.py index 5d2c817c8a58ec8c616c9b7b8ab7ed824a49717e..25630b213c2e4fa89689a438254fc9892a0eb15b 100755 --- a/autoProcessTV/autoProcessTV.py +++ b/autoProcessTV/autoProcessTV.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python2.7 # Author: Nic Wolfe <nic@wolfeden.ca> # URL: http://code.google.com/p/sickbeard/ @@ -7,7 +7,7 @@ # DEPRECATION NOTICE: autoProcessTV is deprecated and will be removed # from SickRage at 31-10-2015. # -# Please switch to nzbToMedia from Clinton Hall, which is included in +# Please switch to nzbToMedia from Clinton Hall, which is included in # the contrib folder # # SickRage is free software: you can redistribute it and/or modify diff --git a/autoProcessTV/hellaToSickBeard.py b/autoProcessTV/hellaToSickBeard.py index b57ddef1cdaedc1c1ca45399b9d7995d61f34d98..333ff63cd7397635a1299c6e7ef23730ec6aa54a 100755 --- a/autoProcessTV/hellaToSickBeard.py +++ b/autoProcessTV/hellaToSickBeard.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python2.7 # Author: Nic Wolfe <nic@wolfeden.ca> # URL: http://code.google.com/p/sickbeard/ @@ -19,7 +19,7 @@ # 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/>. diff --git a/autoProcessTV/mediaToSickbeard.py b/autoProcessTV/mediaToSickbeard.py index a24ffaf657a2561b0c20aa1c4627dc8d4b866e8c..21ff9b931251efb5d4b598a526b539cc741b8784 100755 --- a/autoProcessTV/mediaToSickbeard.py +++ b/autoProcessTV/mediaToSickbeard.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python2.7 # DEPRECATION NOTICE: autoProcessTV is deprecated and will be removed # from SickRage at 31-10-2015. # @@ -61,16 +61,16 @@ def utorrent(): dirName = sys.argv[1] nzbName = sys.argv[2] - + return (dirName, nzbName) - + def transmission(): - + dirName = os.getenv('TR_TORRENT_DIR') nzbName = os.getenv('TR_TORRENT_NAME') - + return (dirName, nzbName) - + def deluge(): if len(sys.argv) < 4: @@ -78,10 +78,10 @@ def deluge(): print "No folder supplied - is this being called from Deluge?" time.sleep(3) sys.exit() - + dirName = sys.argv[3] nzbName = sys.argv[2] - + return (dirName, nzbName) def deluged() : @@ -128,10 +128,10 @@ def blackhole(): # nzbName = sys.argv[2] # else: # dirName = sys.argv[1] -# -# return (dirName, nzbName) # -#def hella(): +# return (dirName, nzbName) +# +#def hella(): # if len(sys.argv) < 4: # scriptlogger.error('No folder supplied - is this being called from HellaVCR?') # print "No folder supplied - is this being called from HellaVCR?" @@ -139,8 +139,8 @@ def blackhole(): # else: # dirName = sys.argv[3] # nzbName = sys.argv[2] -# -# return (dirName, nzbName) +# +# return (dirName, nzbName) def main(): scriptlogger.info(u'Starting external PostProcess script ' + __file__) @@ -156,28 +156,28 @@ def main(): ssl = int(config.get("General", "enable_https")) except (ConfigParser.NoOptionError, ValueError): ssl = 0 - + try: web_root = config.get("General", "web_root") except ConfigParser.NoOptionError: web_root = "" - + tv_dir = config.get("General", "tv_download_dir") use_torrents = int(config.get("General", "use_torrents")) torrent_method = config.get("General", "torrent_method") - + if not use_torrents: scriptlogger.error(u'Enable Use Torrent on Sickbeard to use this Script. Aborting!') print u'Enable Use Torrent on Sickbeard to use this Script. Aborting!' time.sleep(3) sys.exit() - + if not torrent_method in ['utorrent', 'transmission', 'deluge', 'deluged', 'blackhole']: scriptlogger.error(u'Unknown Torrent Method. Aborting!') print u'Unknown Torrent Method. Aborting!' time.sleep(3) sys.exit() - + dirName, nzbName = eval(locals()['torrent_method'])() if dirName is None: @@ -194,29 +194,29 @@ def main(): if nzbName and os.path.isdir(os.path.join(dirName, nzbName)): dirName = os.path.join(dirName, nzbName) - + params = {} - + params['quiet'] = 1 - + params['dir'] = dirName if nzbName != None: params['nzbName'] = nzbName - + if ssl: protocol = "https://" else: protocol = "http://" - + if host == '0.0.0.0': host = 'localhost' - + url = protocol + host + ":" + port + web_root + "/home/postprocess/processEpisode" login_url = protocol + host + ":" + port + web_root + "/login" - - scriptlogger.debug("Opening URL: " + url + ' with params=' + str(params)) + + scriptlogger.debug("Opening URL: " + url + ' with params=' + str(params)) print "Opening URL: " + url + ' with params=' + str(params) - + try: sess = requests.Session() sess.post(login_url, data={'username': username, 'password': password}, stream=True, verify=False) @@ -225,18 +225,18 @@ def main(): scriptlogger.error(u': Unknown exception raised when opening url: ' + str(e)) time.sleep(3) sys.exit() - + if response.status_code == 302: scriptlogger.error(u'Invalid Sickbeard Username or Password, check your config') print 'Invalid Sickbeard Username or Password, check your config' time.sleep(3) sys.exit() - + if response.status_code == 200: scriptlogger.info(u'Script ' + __file__ + ' Succesfull') print 'Script ' + __file__ + ' Succesfull' time.sleep(3) sys.exit() - + if __name__ == '__main__': main() diff --git a/autoProcessTV/sabToSickBeard.py b/autoProcessTV/sabToSickBeard.py index 6419bb9a56050b2e281bc0cc0c0382c36b8894bb..2fe414d8ba3fa11a0b29e517ec62aa8245d074cc 100755 --- a/autoProcessTV/sabToSickBeard.py +++ b/autoProcessTV/sabToSickBeard.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python2.7 # Author: Nic Wolfe <nic@wolfeden.ca> # URL: http://code.google.com/p/sickbeard/ @@ -19,7 +19,7 @@ # 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/>. diff --git a/contributing.md b/contributing.md index 4ca2dc5ed4e75b5f418b9f03b94ff12c3a687a42..90ede8c6f461f6d9d144f1936275b05e5fb4c0af 100644 --- a/contributing.md +++ b/contributing.md @@ -51,14 +51,14 @@ Please follow these guidelines before reporting a bug: 1. **Update to the latest version** — Check if you can reproduce the issue with the latest version from the `develop` branch. -2. **Use the SickRage Forums search** — check if the issue has already been reported. If it has been, please comment on the existing issue. +2. **Use the search on sickrage-issues** — check if the issue has already been reported. If it has been, please comment on the existing issue. 3. **Provide a means to reproduce the problem** — Please provide as much details as possible, e.g. SickRage log files (obfuscate apikey/passwords), browser and operating system versions, how you started SickRage, and of course the steps to reproduce the problem. Bugs are always reported in the forums. ### Feature requests - -Please follow the bug guidelines above for feature requests, i.e. update to the latest version and search for existing issues before posting a new request. You can submit Feature Requests in the [SickRage Forum](http://sickrage.tv/) as well. +Please follow the bug guidelines above for feature requests, i.e. update to the latest version and search for existing requests on [FeatHub](http://feathub.com/SiCKRAGETV/SickRage) before posting a new request.. +[](http://feathub.com/SiCKRAGETV/SickRage) ### Pull requests @@ -102,7 +102,7 @@ Please follow this process; it's the best way to get your work included in the p # merge upstream changes git merge upstream/master ``` - + - Make sure that your develop branch is up to date: ```bash @@ -131,4 +131,3 @@ Please follow this process; it's the best way to get your work included in the p - [Open a Pull Request](https://help.github.com/articles/using-pull-requests) with a clear title and description. - diff --git a/gui/slick/images/providers/extratorrent.png b/gui/slick/images/providers/extratorrent.png new file mode 100644 index 0000000000000000000000000000000000000000..f0d5f822f8e178a031f3ebc1485d970a77e21199 Binary files /dev/null and b/gui/slick/images/providers/extratorrent.png differ diff --git a/gui/slick/js/config.js b/gui/slick/js/config.js deleted file mode 100644 index 7c852360b89e1f1ea408996f73d007bc5d69b81d..0000000000000000000000000000000000000000 --- a/gui/slick/js/config.js +++ /dev/null @@ -1,115 +0,0 @@ -$(document).ready(function(){ - $(".enabler").each(function(){ - if (!$(this).prop('checked')) $('#content_'+$(this).attr('id')).hide(); - }); - - $(".enabler").click(function() { - if ($(this).prop('checked')){ - $('#content_'+$(this).attr('id')).fadeIn("fast", "linear"); - } else { - $('#content_'+$(this).attr('id')).fadeOut("fast", "linear"); - } - }); - - $(".viewIf").click(function() { - if ($(this).prop('checked')) { - $('.hide_if_'+$(this).attr('id')).css('display','none'); - $('.show_if_'+$(this).attr('id')).fadeIn("fast", "linear"); - } else { - $('.show_if_'+$(this).attr('id')).css('display','none'); - $('.hide_if_'+$(this).attr('id')).fadeIn("fast", "linear"); - } - }); - - $(".datePresets").click(function() { - var def = $('#date_presets').val(); - if ($(this).prop('checked') && '%x' == def) { - def = '%a, %b %d, %Y'; - $('#date_use_system_default').html('1'); - } else if (!$(this).prop('checked') && '1' == $('#date_use_system_default').html()){ - def = '%x'; - } - - $('#date_presets').attr('name', 'date_preset_old'); - $('#date_presets').attr('id', 'date_presets_old'); - - $('#date_presets_na').attr('name', 'date_preset'); - $('#date_presets_na').attr('id', 'date_presets'); - - $('#date_presets_old').attr('name', 'date_preset_na'); - $('#date_presets_old').attr('id', 'date_presets_na'); - - if (def) $('#date_presets').val(def); - }); - - // bind 'myForm' and provide a simple callback function - $('#configForm').ajaxForm({ - beforeSubmit: function(){ - $('.config_submitter .config_submitter_refresh').each(function(){ - $(this).attr("disabled", "disabled"); - $(this).after('<span><img src="' + srRoot + '/images/loading16' + themeSpinner + '.gif"> Saving...</span>'); - $(this).hide(); - }); - }, - success: function(){ - setTimeout(function () { - "use strict"; - config_success(); - }, 2000); - } - }); - - $('#api_key').click(function(){ - $('#api_key').select(); - }); - - $("#generate_new_apikey").click(function(){ - $.get(srRoot + '/config/general/generateApiKey', - function(data){ - if (data.error !== undefined) { - alert(data.error); - return; - } - $('#api_key').val(data); - }); - }); - - $('#branchCheckout').click(function() { - var url = srRoot+'/home/branchCheckout?branch='+$("#branchVersion").val(); - var checkDBversion = srRoot + "/home/getDBcompare"; - $.getJSON(checkDBversion, function(data){ - if (data.status == "success") { - if (data.message == "equal") { - //Checkout Branch - window.location.href = url; - } - if (data.message == "upgrade") { - if ( confirm("Changing branch will upgrade your database.\nYou won't be able to downgrade afterward.\nDo you want to continue?") ) { - //Checkout Branch - window.location.href = url; - } - } - if (data.message == "downgrade") { - alert("Can't switch branch as this will result in a database downgrade."); - } - } - }); - }); - -}); - -function config_success(){ - $('.config_submitter').each(function(){ - $(this).removeAttr("disabled"); - $(this).next().remove(); - $(this).show(); - }); - $('.config_submitter_refresh').each(function(){ - $(this).removeAttr("disabled"); - $(this).next().remove(); - $(this).show(); - url = srRoot+'/config/providers/'; - window.location.href = url; - }); - $('#email_show').trigger('notify'); -} diff --git a/gui/slick/js/new/config_backuprestore.js b/gui/slick/js/new/config_backuprestore.js index 774e7e39dda07a91c29487ea9768e0c3037fb740..143464c6165e53740b51b7875fa9510a5ba5590f 100644 --- a/gui/slick/js/new/config_backuprestore.js +++ b/gui/slick/js/new/config_backuprestore.js @@ -1,5 +1,119 @@ -$(document).load(function(){ +$(document).ready(function(){ $('#backupDir').fileBrowser({ title: 'Select backup folder to save to', key: 'backupPath' }); $('#backupFile').fileBrowser({ title: 'Select backup files to restore', key: 'backupFile', includeFiles: 1 }); $('#config-components').tabs(); + + $(".enabler").each(function(){ + if (!$(this).prop('checked')) $('#content_'+$(this).attr('id')).hide(); + }); + + $(".enabler").click(function() { + if ($(this).prop('checked')){ + $('#content_'+$(this).attr('id')).fadeIn("fast", "linear"); + } else { + $('#content_'+$(this).attr('id')).fadeOut("fast", "linear"); + } + }); + + $(".viewIf").click(function() { + if ($(this).prop('checked')) { + $('.hide_if_'+$(this).attr('id')).css('display','none'); + $('.show_if_'+$(this).attr('id')).fadeIn("fast", "linear"); + } else { + $('.show_if_'+$(this).attr('id')).css('display','none'); + $('.hide_if_'+$(this).attr('id')).fadeIn("fast", "linear"); + } + }); + + $(".datePresets").click(function() { + var def = $('#date_presets').val(); + if ($(this).prop('checked') && '%x' == def) { + def = '%a, %b %d, %Y'; + $('#date_use_system_default').html('1'); + } else if (!$(this).prop('checked') && '1' == $('#date_use_system_default').html()){ + def = '%x'; + } + + $('#date_presets').attr('name', 'date_preset_old'); + $('#date_presets').attr('id', 'date_presets_old'); + + $('#date_presets_na').attr('name', 'date_preset'); + $('#date_presets_na').attr('id', 'date_presets'); + + $('#date_presets_old').attr('name', 'date_preset_na'); + $('#date_presets_old').attr('id', 'date_presets_na'); + + if (def) $('#date_presets').val(def); + }); + + // bind 'myForm' and provide a simple callback function + $('#configForm').ajaxForm({ + beforeSubmit: function(){ + $('.config_submitter .config_submitter_refresh').each(function(){ + $(this).attr("disabled", "disabled"); + $(this).after('<span><img src="' + srRoot + '/images/loading16' + themeSpinner + '.gif"> Saving...</span>'); + $(this).hide(); + }); + }, + success: function(){ + setTimeout(function () { + "use strict"; + config_success(); + }, 2000); + } + }); + + $('#api_key').click(function(){ + $('#api_key').select(); + }); + + $("#generate_new_apikey").click(function(){ + $.get(srRoot + '/config/general/generateApiKey', + function(data){ + if (data.error !== undefined) { + alert(data.error); + return; + } + $('#api_key').val(data); + }); + }); + + $('#branchCheckout').click(function() { + var url = srRoot+'/home/branchCheckout?branch='+$("#branchVersion").val(); + var checkDBversion = srRoot + "/home/getDBcompare"; + $.getJSON(checkDBversion, function(data){ + if (data.status == "success") { + if (data.message == "equal") { + //Checkout Branch + window.location.href = url; + } + if (data.message == "upgrade") { + if ( confirm("Changing branch will upgrade your database.\nYou won't be able to downgrade afterward.\nDo you want to continue?") ) { + //Checkout Branch + window.location.href = url; + } + } + if (data.message == "downgrade") { + alert("Can't switch branch as this will result in a database downgrade."); + } + } + }); + }); + }); + +function config_success(){ + $('.config_submitter').each(function(){ + $(this).removeAttr("disabled"); + $(this).next().remove(); + $(this).show(); + }); + $('.config_submitter_refresh').each(function(){ + $(this).removeAttr("disabled"); + $(this).next().remove(); + $(this).show(); + url = srRoot+'/config/providers/'; + window.location.href = url; + }); + $('#email_show').trigger('notify'); +} diff --git a/gui/slick/views/config_backuprestore.mako b/gui/slick/views/config_backuprestore.mako index 3f7cffa618816a038f5ae67b93dff2a0c0dd97d6..51293caf1ab33a839a27d10255c9ebb49b76ec9c 100644 --- a/gui/slick/views/config_backuprestore.mako +++ b/gui/slick/views/config_backuprestore.mako @@ -25,9 +25,6 @@ % if sickbeard.INDEXER_DEFAULT: <% indexer = sickbeard.INDEXER_DEFAULT %> % endif - -<script type="text/javascript" src="${srRoot}/js/config.js?${sbPID}"></script> - <div id="config"> <div id="config-content"> diff --git a/gui/slick/views/displayShow.mako b/gui/slick/views/displayShow.mako index b1914a82307c2e9b22b04462189397a350ead244..fc25ef1c33104794cc78263cd3fd15ce384b8341 100644 --- a/gui/slick/views/displayShow.mako +++ b/gui/slick/views/displayShow.mako @@ -494,7 +494,7 @@ <% date = sbdatetime.sbdatetime.convert_to_setting(network_timezones.parse_date_time(epResult['airdate'], show.airs, show.network)) %> <td class="col-airdate"> % if int(epResult['airdate']) != 1: - <time datetime="${date.isoformat('T')}" class="date">${date}</time> + <time datetime="${date.isoformat('T')}" class="date">${sbdatetime.sbdatetime.sbfdate(date)}</time> % else: Never % endif diff --git a/readme.md b/readme.md index 3c6d1fdb3f2de833a16e9905cc14aa407d32652f..bf321572f0471ce0a4adefbf54fdaa0868803a20 100644 --- a/readme.md +++ b/readme.md @@ -32,6 +32,9 @@ Automatic Video Library Manager for TV Shows. It watches for new episodes of you ## Dependencies To run SickRage from source you will need Python 2.7.x, Mako, and PyOpenSSL +## Feature Requests +[](http://feathub.com/SiCKRAGETV/SickRage) + ## Forums Any questions or setup info your looking for can be found at out forums https://www.sickrage.tv diff --git a/runscripts/init.debian b/runscripts/init.debian index 6d4b2e97a494f62d1d653c1eee204957e5d2a56c..1682dc0bfaf217eb6af035ebdff9f50484d29113 100755 --- a/runscripts/init.debian +++ b/runscripts/init.debian @@ -35,7 +35,7 @@ fi ## SR_HOME= #$APP_PATH, the location of SickBeard.py, the default is /opt/sickrage ## SR_DATA= #$DATA_DIR, the location of sickbeard.db, cache, logs, the default is /opt/sickrage ## SR_PIDFILE= #$PID_FILE, the location of sickrage.pid, the default is /var/run/sickrage/sickrage.pid -## PYTHON_BIN= #$DAEMON, the location of the python binary, the default is /usr/bin/python +## PYTHON_BIN= #$DAEMON, the location of the python binary, the default is /usr/bin/python2.7 ## SR_OPTS= #$EXTRA_DAEMON_OPTS, extra cli option for sickrage, i.e. " --config=/home/sickrage/config.ini" ## SSD_OPTS= #$EXTRA_SSD_OPTS, extra start-stop-daemon option like " --group=users" ## @@ -50,7 +50,7 @@ NAME=$(basename "$0") DESC=SickRage ## The defaults -# Run as username +# Run as username RUN_AS=${SR_USER-sickrage} # Run as group @@ -66,7 +66,7 @@ DATA_DIR=${SR_DATA-/opt/sickrage} PID_FILE=${SR_PIDFILE-/var/run/sickrage/sickrage.pid} # path to python bin -DAEMON=${PYTHON_BIN-/usr/bin/python} +DAEMON=${PYTHON_BIN-/usr/bin/python2.7} # Extra daemon option like: SR_OPTS=" --config=/home/sickrage/config.ini" EXTRA_DAEMON_OPTS=${SR_OPTS-} @@ -103,7 +103,7 @@ fi start_sickrage() { [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" - start-stop-daemon -d $APP_PATH -c $RUN_AS --group=${RUN_GROUP} $EXTRA_SSD_OPTS --start --quiet --pidfile $PID_FILE --exec $DAEMON -- $DAEMON_OPTS + start-stop-daemon -d $APP_PATH -c $RUN_AS --group=${RUN_GROUP} $EXTRA_SSD_OPTS --start --quiet --pidfile $PID_FILE --exec $DAEMON -- $DAEMON_OPTS RETVAL="$?" case "${RETVAL}" in # Service was started or was running already @@ -127,7 +127,7 @@ stop_sickrage() { esac [ "${RETVAL}" = 2 ] && return 2 [ -f "${PID_FILE}" ] && rm -f ${PID_FILE} - return 0 + return 0 } case "$1" in diff --git a/runscripts/init.fedora b/runscripts/init.fedora index 792f9d5c8ade1c338148126d31edb3d489c2f507..ecb40d23c4b05890b7ccbc3f9228f04b56fcf6ca 100755 --- a/runscripts/init.fedora +++ b/runscripts/init.fedora @@ -28,7 +28,7 @@ homedir=${SR_HOME-/opt/sickrage} datadir=${SR_DATA-/opt/sickrage} pidfile=${SR_PIDFILE-/var/run/sickrage/sickrage.pid} nice=${SR_NICE-} -python_bin=${PYTHON_BIN-/usr/bin/python} +python_bin=${PYTHON_BIN-/usr/bin/python2.7} ## pidpath=`dirname ${pidfile}` diff --git a/runscripts/init.gentoo b/runscripts/init.gentoo index 43147d9b8a28642d9314abe02980f161b3049aa5..2a0a3f5f2c3084f61d702892199a6fa3ec09d163 100755 --- a/runscripts/init.gentoo +++ b/runscripts/init.gentoo @@ -12,7 +12,7 @@ # SICKRAGE_USER=<user you want sickrage to run under> # SICKRAGE_GROUP=<group you want sickrage to run under> # SICKRAGE_DIR=<path to Sickbeard.py> -# PATH_TO_PYTHON_2=/usr/bin/python2 +# PATH_TO_PYTHON_2=/usr/bin/python2.7 # SICKRAGE_DATADIR=<directory that contains sickbeard.db file> # SICKRAGE_CONFDIR=<directory that contains Sickrage's config.ini file> # diff --git a/runscripts/init.systemd b/runscripts/init.systemd index 44d4990eaa4aa25892bb6d92f56c83563b9852f0..c73087c41712685fc702d64f31dbb3ffad6f8a44 100755 --- a/runscripts/init.systemd +++ b/runscripts/init.systemd @@ -24,21 +24,21 @@ ### Example Using SickRage as daemon with pid file # Type=forking # PIDFile=/var/run/sickrage/sickrage.pid -# ExecStart=/usr/bin/python /opt/sickrage/SickBeard.py -q --daemon --nolaunch --pidfile=/var/run/sickrage/sickrage.pid --datadir=/opt/sickrage +# ExecStart=/usr/bin/python2.7 /opt/sickrage/SickBeard.py -q --daemon --nolaunch --pidfile=/var/run/sickrage/sickrage.pid --datadir=/opt/sickrage ## Example Using SickRage as daemon without pid file # Type=forking # GuessMainPID=no -# ExecStart=/usr/bin/python /opt/sickrage/SickBeard.py -q --daemon --nolaunch --datadir=/opt/sickrage +# ExecStart=/usr/bin/python2.7 /opt/sickrage/SickBeard.py -q --daemon --nolaunch --datadir=/opt/sickrage ### Example Using simple # Type=simple -# ExecStart=/usr/bin/python /opt/sickrage/SickBeard.py -q --nolaunch +# ExecStart=/usr/bin/python2.7 /opt/sickrage/SickBeard.py -q --nolaunch ### Example Using simple with EnvironmentFile where SR_DATA=/home/sickrage/.sickrage in /etc/sickrage.conf # Type=simple # EnvironmentFile=/etc/sickrage.conf -# ExecStart=/usr/bin/python /opt/sickrage/SickBeard.py -q --nolaunch --datadir=${SR_DATA} +# ExecStart=/usr/bin/python2.7 /opt/sickrage/SickBeard.py -q --nolaunch --datadir=${SR_DATA} ### Configuration @@ -51,7 +51,7 @@ Group=sickrage Type=forking GuessMainPID=no -ExecStart=/usr/bin/python /opt/sickrage/SickBeard.py -q --daemon --nolaunch --datadir=/opt/sickrage +ExecStart=/usr/bin/python2.7 /opt/sickrage/SickBeard.py -q --daemon --nolaunch --datadir=/opt/sickrage [Install] WantedBy=multi-user.target diff --git a/runscripts/init.ubuntu b/runscripts/init.ubuntu index fd50edee5e0345ca467ac91406ab46d1d1be03e7..97543d77cc748bb8094248a6c1024e575e6fdda4 100755 --- a/runscripts/init.ubuntu +++ b/runscripts/init.ubuntu @@ -35,7 +35,7 @@ DESC=SickRage ## SR_HOME= #$APP_PATH, the location of SickBeard.py, the default is /opt/sickrage ## SR_DATA= #$DATA_DIR, the location of sickbeard.db, cache, logs, the default is /opt/sickrage ## SR_PIDFILE= #$PID_FILE, the location of sickrage.pid, the default is /var/run/sickrage/sickrage.pid -## PYTHON_BIN= #$DAEMON, the location of the python binary, the default is /usr/bin/python +## PYTHON_BIN= #$DAEMON, the location of the python binary, the default is /usr/bin/python2.7 ## SR_OPTS= #$EXTRA_DAEMON_OPTS, extra cli option for sickrage, i.e. " --config=/home/sickrage/config.ini" ## SSD_OPTS= #$EXTRA_SSD_OPTS, extra start-stop-daemon option like " --group=users" ## @@ -44,7 +44,7 @@ DESC=SickRage ## otherwise default sickrage is used ## The defaults -# Run as username +# Run as username RUN_AS=${SR_USER-sickrage} # Path to app SR_HOME=path_to_app_SickBeard.py @@ -57,7 +57,7 @@ DATA_DIR=${SR_DATA-/opt/sickrage} PID_FILE=${SR_PIDFILE-/var/run/sickrage/sickrage.pid} # path to python bin -DAEMON=${PYTHON_BIN-/usr/bin/python} +DAEMON=${PYTHON_BIN-/usr/bin/python2.7} # Extra daemon option like: SR_OPTS=" --config=/home/sickrage/config.ini" EXTRA_DAEMON_OPTS=${SR_OPTS-} diff --git a/sickbeard/__init__.py b/sickbeard/__init__.py index 4124b784b1cf139484292645af7dd01af07824be..52ac11ecfc209ad6431e5b6c526c6816c125f0a4 100644 --- a/sickbeard/__init__.py +++ b/sickbeard/__init__.py @@ -39,7 +39,7 @@ from sickbeard import providers from sickbeard.providers.generic import GenericProvider from sickbeard.providers import btn, newznab, womble, thepiratebay, torrentleech, kat, iptorrents, \ omgwtfnzbs, scc, hdtorrents, torrentday, hdbits, hounddawgs, nextgen, speedcd, nyaatorrents, animenzb, bluetigers, cpasbien, fnt, xthor, torrentbytes, \ - frenchtorrentdb, freshontv, titansoftv, libertalia, morethantv, bitsoup, t411, tokyotoshokan, shazbat, rarbg, alpharatio, tntvillage, binsearch, torrentproject, \ + frenchtorrentdb, freshontv, titansoftv, libertalia, morethantv, bitsoup, t411, tokyotoshokan, shazbat, rarbg, alpharatio, tntvillage, binsearch, torrentproject, extratorrent, \ scenetime, btdigg, strike, transmitthenet, tvchaosuk from sickbeard.config import CheckSection, check_setting_int, check_setting_str, check_setting_float, ConfigMigrator, \ naming_ep_type diff --git a/sickbeard/indexers/indexer_exceptions.py b/sickbeard/indexers/indexer_exceptions.py index 221f529661f32dcadc798725956de5bf23873b97..f8bd2221808289df3fd1eccae37c14081d6e947f 100644 --- a/sickbeard/indexers/indexer_exceptions.py +++ b/sickbeard/indexers/indexer_exceptions.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python2.7 # encoding:utf-8 #author:echel0n #project:indexer_api diff --git a/sickbeard/processTV.py b/sickbeard/processTV.py index 1c41988a9a3b8d9fe334c55aecd599905c9e117b..9058b0275668363f2ad97fa7eb7a825901ea6e5c 100644 --- a/sickbeard/processTV.py +++ b/sickbeard/processTV.py @@ -311,16 +311,21 @@ def validateDir(path, dirName, nzbNameOriginal, failed, result): :return: True if dir is valid for processing, False if not """ + IGNORED_FOLDERS = ['.@__thumb', '@eaDir'] + folder_name = ek(os.path.basename, dirName) + if folder_name in IGNORED_FOLDERS: + return False + result.output += logHelper(u"Processing folder " + dirName, logger.DEBUG) - if ek(os.path.basename, dirName).startswith('_FAILED_'): + if folder_name.startswith('_FAILED_'): result.output += logHelper(u"The directory name indicates it failed to extract.", logger.DEBUG) failed = True - elif ek(os.path.basename, dirName).startswith('_UNDERSIZED_'): + elif folder_name.startswith('_UNDERSIZED_'): result.output += logHelper(u"The directory name indicates that it was previously rejected for being undersized.", logger.DEBUG) failed = True - elif ek(os.path.basename, dirName).upper().startswith('_UNPACK'): + elif folder_name.upper().startswith('_UNPACK'): result.output += logHelper(u"The directory name indicates that this release is in the process of being unpacked.", logger.DEBUG) result.missedfiles.append(dirName + " : Being unpacked") diff --git a/sickbeard/providers/__init__.py b/sickbeard/providers/__init__.py index 40bc790c8b38d1e6c0509edbc6a6a6dbee330ba7..86588c6cca6cbe5532365954400ef89251bb2c60 100644 --- a/sickbeard/providers/__init__.py +++ b/sickbeard/providers/__init__.py @@ -55,7 +55,8 @@ __all__ = ['womble', 'strike', 'transmitthenet', 'tvchaosuk', - 'torrentproject' + 'torrentproject', + 'extratorrent' ] import sickbeard diff --git a/sickbeard/providers/extratorrent.py b/sickbeard/providers/extratorrent.py new file mode 100644 index 0000000000000000000000000000000000000000..585a8e13f04e1b81b6bc9c2b2229322b020c4bf6 --- /dev/null +++ b/sickbeard/providers/extratorrent.py @@ -0,0 +1,197 @@ +# Author: duramato <matigonkas@outlook.com> +# Author: miigotu +# URL: https://github.com/SiCKRAGETV/sickrage +# 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 re +import traceback +import datetime +import sickbeard +import xmltodict + +from sickbeard.providers import generic +from sickbeard.common import Quality +from sickbeard import logger +from sickbeard import tvcache +from sickbeard import db +from sickbeard import classes +from sickbeard import helpers +from sickbeard import show_name_helpers +from sickbeard.helpers import sanitizeSceneName + + +class ExtraTorrentProvider(generic.TorrentProvider): + def __init__(self): + generic.TorrentProvider.__init__(self, "ExtraTorrent") + + self.urls = { + 'index': 'http://extratorrent.cc', + 'rss': 'http://extratorrent.cc/rss.xml', + } + + self.url = self.urls['index'] + + self.supportsBacklog = True + self.public = True + self.enabled = False + self.ratio = None + self.minseed = None + self.minleech = None + + self.cache = ExtraTorrentCache(self) + + self.search_params = {'cid': 8} + + def isEnabled(self): + return self.enabled + + def _get_season_search_strings(self, ep_obj): + + search_string = {'Season': []} + for show_name in set(show_name_helpers.allPossibleShowNames(ep_obj.show)): + if ep_obj.show.air_by_date or ep_obj.show.sports: + ep_string = show_name + ' ' + str(ep_obj.airdate).split('-')[0] + elif ep_obj.show.anime: + ep_string = show_name + ' ' + "%d" % ep_obj.scene_absolute_number + else: + ep_string = show_name + ' S%02d' % int(ep_obj.scene_season) #1) showName SXX + + search_string['Season'].append(ep_string.strip()) + + return [search_string] + + def _get_episode_search_strings(self, ep_obj, add_string=''): + + search_strings = {'Episode': []} + + if not ep_obj: + return [] + + for show_name in set(show_name_helpers.allPossibleShowNames(ep_obj.show)): + if ep_obj.show.air_by_date: + ep_string = sanitizeSceneName(show_name) + ' ' + \ + str(ep_obj.airdate).replace('-', '|') + elif ep_obj.show.sports: + ep_string = sanitizeSceneName(show_name) + ' ' + \ + str(ep_obj.airdate).replace('-', '|') + '|' + \ + ep_obj.airdate.strftime('%b') + elif ep_obj.show.anime: + ep_string = sanitizeSceneName(show_name) + ' ' + \ + "%i" % int(ep_obj.scene_absolute_number) + else: + ep_string = sanitizeSceneName(show_name) + ' ' + \ + sickbeard.config.naming_ep_type[2] % {'seasonnumber': ep_obj.scene_season, + 'episodenumber': ep_obj.scene_episode} + + if add_string: + ep_string += ' %s' % add_string + + search_strings['Episode'].append(re.sub(r'\s+', ' ', ep_string)) + + return [search_strings] + + + def _doSearch(self, search_strings, search_mode='eponly', epcount=0, age=0, epObj=None): + + results = [] + items = {'Season': [], 'Episode': [], 'RSS': []} + + for mode in search_strings.keys(): + for search_string in search_strings[mode]: + try: + self.search_params.update({'type': ('search', 'rss')[mode == 'RSS'], 'search': search_string.strip()}) + data = self.getURL(self.urls['rss'], params=self.search_params) + if not data: + continue + + data = xmltodict.parse(data) + for item in data['rss']['channel']['item']: + title = item['title'] + info_hash = item['info_hash'] + url = item['enclosure']['@url'] + size = int(item['enclosure']['@length'] or item['size']) + seeders = int(item['seeders']) + leechers = int(item['leechers']) + + if not seeders or seeders < self.minseed or leechers < self.minleech: + continue + + items[mode].append((title, url, seeders, leechers, size, info_hash)) + + except Exception: + logger.log(u"Failed parsing " + self.name + " Traceback: " + traceback.format_exc(), logger.ERROR) + + results += items[mode] + + return results + + def _get_title_and_url(self, item): + #pylint: disable=W0612 + title, url, seeders, leechers, size, info_hash = item + + if title: + title = self._clean_title_from_provider(title) + + if url: + url = url.replace('&', '&') + + return (title, url) + + + def _get_size(self, item): + #pylint: disable=W0612 + title, url, seeders, leechers, size, info_hash = item + return size + + def findPropers(self, search_date=datetime.datetime.today()-datetime.timedelta(days=1)): + results = [] + myDB = db.DBConnection() + sqlResults = myDB.select( + 'SELECT s.show_name, e.showid, e.season, e.episode, e.status, e.airdate FROM tv_episodes AS e' + + ' INNER JOIN tv_shows AS s ON (e.showid = s.indexer_id)' + + ' WHERE e.airdate >= ' + str(search_date.toordinal()) + + ' AND (e.status IN (' + ','.join([str(x) for x in Quality.DOWNLOADED]) + ')' + + ' OR (e.status IN (' + ','.join([str(x) for x in Quality.SNATCHED]) + ')))' + ) + + for sqlshow in sqlResults or []: + show = helpers.findCertainShow(sickbeard.showList, int(sqlshow["showid"])) + if show: + curEp = show.getEpisode(int(sqlshow["season"]), int(sqlshow["episode"])) + searchStrings = self._get_episode_search_strings(curEp, add_string='PROPER|REPACK') + for item in self._doSearch(searchStrings): + title, url = self._get_title_and_url(item) + results.append(classes.Proper(title, url, datetime.datetime.today(), show)) + + return results + + def seedRatio(self): + return self.ratio + + +class ExtraTorrentCache(tvcache.TVCache): + def __init__(self, _provider): + + tvcache.TVCache.__init__(self, _provider) + + self.minTime = 30 + + def _getRSSData(self): + search_strings = {'RSS': ['']} + return {'entries': self.provider._doSearch(search_strings)} + + +provider = ExtraTorrentProvider() diff --git a/sickbeard/providers/fnt.py b/sickbeard/providers/fnt.py index b94e2b82398ec77a8d6dc54b03a1f78270c08468..ecb9a54ee94d095d2af43485bac74f32f874857f 100644 --- a/sickbeard/providers/fnt.py +++ b/sickbeard/providers/fnt.py @@ -152,6 +152,10 @@ class FNTProvider(generic.TorrentProvider): results = [] items = {'Season': [], 'Episode': [], 'RSS': []} + # check for auth + if not self._doLogin(): + return results + for mode in search_strings.keys(): for search_string in search_strings[mode]: logger.log(u"Search string: " + search_string, logger.DEBUG) diff --git a/sickbeard/providers/generic.py b/sickbeard/providers/generic.py index f8ee86b45edf0ae5085f90195466a65acb60c566..10009610fc83db8b413f92b7ff7dcdfe69ce0246 100644 --- a/sickbeard/providers/generic.py +++ b/sickbeard/providers/generic.py @@ -92,7 +92,7 @@ class GenericProvider: @staticmethod def makeID(name): - return re.sub("[^\w\d_]", "_", name.strip().lower()) + return re.sub(r"[^\w\d_]", "_", name.strip().lower()) def imageName(self): return self.getID() + '.png' @@ -156,7 +156,7 @@ class GenericProvider: filename = u'' if result.url.startswith('magnet'): try: - torrent_hash = re.findall('urn:btih:([\w]{32,40})', result.url)[0].upper() + torrent_hash = re.findall(r'urn:btih:([\w]{32,40})', result.url)[0].upper() try: torrent_name = re.findall('dn=([^&]+)', result.url)[0] diff --git a/sickbeard/providers/kat.py b/sickbeard/providers/kat.py index 0f142b056037a45255b05ccc1395b59ae5d55f6a..b82c8d1e09c83970c8e751e5926781b6aea1a664 100644 --- a/sickbeard/providers/kat.py +++ b/sickbeard/providers/kat.py @@ -20,13 +20,12 @@ from __future__ import with_statement import traceback -import urllib import re import datetime +import xmltodict import sickbeard -import generic - +from sickbeard.providers import generic from sickbeard.common import Quality from sickbeard import logger from sickbeard import tvcache @@ -34,7 +33,6 @@ from sickbeard import helpers from sickbeard import db from sickbeard import classes from sickbeard.show_name_helpers import allPossibleShowNames, sanitizeSceneName -from unidecode import unidecode class KATProvider(generic.TorrentProvider): @@ -46,17 +44,29 @@ class KATProvider(generic.TorrentProvider): self.public = True self.enabled = False - self.confirmed = False + self.confirmed = True self.ratio = None self.minseed = None self.minleech = None self.cache = KATCache(self) - self.urls = {'base_url': 'https://kat.cr/'} + self.urls = { + 'base_url': 'https://kat.cr/', + 'search': 'https://kat.cr/usearch/', + 'rss': 'https://kat.cr/tv/', + } self.url = self.urls['base_url'] + self.search_params = { + 'q': '', + 'field': 'seeders', + 'sorder': 'desc', + 'rss': 1, + 'category': 'tv' + } + def isEnabled(self): return self.enabled @@ -66,81 +76,92 @@ class KATProvider(generic.TorrentProvider): def _get_season_search_strings(self, ep_obj): search_string = {'Season': []} - for show_name in set(allPossibleShowNames(self.show)): + for show_name in set(allPossibleShowNames(ep_obj.show)): ep_string = sanitizeSceneName(show_name) + ' ' if ep_obj.show.air_by_date or ep_obj.show.sports: ep_string += str(ep_obj.airdate).split('-')[0] - search_string['Season'].append(ep_string) elif ep_obj.show.anime: ep_string += "%02d" % ep_obj.scene_absolute_number - search_string['Season'].append(ep_string) else: ep_string = '%s S%02d -S%02dE category:tv' % (sanitizeSceneName(show_name), ep_obj.scene_season, ep_obj.scene_season) #1) showName SXX -SXXE search_string['Season'].append(ep_string) ep_string = '%s "Season %d" -Ep* category:tv' % (sanitizeSceneName(show_name), ep_obj.scene_season) # 2) showName "Season X" - search_string['Season'].append(ep_string) + + search_string['Season'].append(ep_string) return [search_string] def _get_episode_search_strings(self, ep_obj, add_string=''): search_string = {'Episode': []} - for show_name in set(allPossibleShowNames(self.show)): + for show_name in set(allPossibleShowNames(ep_obj.show)): ep_string = sanitizeSceneName(show_name) + ' ' - if self.show.air_by_date: + if ep_obj.show.air_by_date: ep_string += str(ep_obj.airdate).replace('-', ' ') - elif self.show.sports: + elif ep_obj.show.sports: ep_string += str(ep_obj.airdate).replace('-', ' ') + '|' + ep_obj.airdate.strftime('%b') - elif self.show.anime: + elif ep_obj.show.anime: ep_string += "%02d" % ep_obj.scene_absolute_number else: ep_string += sickbeard.config.naming_ep_type[2] % {'seasonnumber': ep_obj.scene_season, 'episodenumber': ep_obj.scene_episode} + '|' + \ sickbeard.config.naming_ep_type[0] % {'seasonnumber': ep_obj.scene_season, - 'episodenumber': ep_obj.scene_episode} + ' category:tv' + 'episodenumber': ep_obj.scene_episode} if add_string: ep_string += ' ' + add_string - search_string['Episode'].append(re.sub('\s+', ' ', ep_string)) + search_string['Episode'].append(re.sub(r'\s+', ' ', ep_string.strip())) return [search_string] def _get_size(self, item): - title, url, id, seeders, leechers, size, pubdate = item + #pylint: disable=W0612 + title, url, info_hash, seeders, leechers, size, pubdate = item return size or -1 - def _doSearch(self, search_params, search_mode='eponly', epcount=0, age=0, epObj=None): + def _doSearch(self, search_strings, search_mode='eponly', epcount=0, age=0, epObj=None): results = [] items = {'Season': [], 'Episode': [], 'RSS': []} - for mode in search_params.keys(): - for search_string in search_params[mode]: - if isinstance(search_string, unicode): - search_string = unidecode(search_string) + for mode in search_strings.keys(): + for search_string in search_strings[mode]: + self.search_params.update({'q': search_string, 'field': ('seeders', 'time_add')[mode == 'RSS']}) + logger.log(u"Search string: %s" % unicode(self.search_params), logger.DEBUG) - if mode != 'RSS': - searchURL = self.url + 'usearch/%s/?field=seeders&sorder=desc&rss=1' % urllib.quote_plus(search_string) - else: - searchURL = self.url + 'tv/?field=time_add&sorder=desc&rss=1' + try: + data = self.getURL(self.urls[('search', 'rss')[mode == 'RSS']], params=self.search_params) + if not data: + continue - logger.log(u"Search string: " + searchURL, logger.DEBUG) + entries = xmltodict.parse(data) + if not all([entries, 'rss' in entries, 'channel' in entries['rss'], 'item' in entries['rss']['channel']]): + continue - try: - entries = self.cache.getRSSFeed(searchURL)['entries'] - for item in entries or []: + for item in entries['rss']['channel']['item']: try: - link = item['link'] - id = item['guid'] title = item['title'] - url = item['torrent_magneturi'] - verified = bool(int(item['torrent_verified']) or 0) - seeders = int(item['torrent_seeds']) - leechers = int(item['torrent_peers']) - size = int(item['torrent_contentlength']) + + # Use the torcache link kat provides, + # unless it is not torcache or we are not using blackhole + # because we want to use magnets if connecting direct to client + # so that proxies work. + url = item['enclosure']['@url'] + if sickbeard.TORRENT_METHOD != "blackhole" or 'torcache' not in url: + url = item['torrent:magnetURI'] + + seeders = int(item['torrent:seeds']) + leechers = int(item['torrent:peers']) + verified = bool(int(item['torrent:verified']) or 0) + size = int(item['torrent:contentLength']) + + info_hash = item['torrent:infoHash'] + #link = item['link'] + except (AttributeError, TypeError, KeyError): continue - if mode != 'RSS' and (seeders < self.minseed or leechers < self.minleech): + # Dont let RSS add items with no seeders either -.- + if not seeders or seeders < self.minseed or leechers < self.minleech: 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 @@ -152,26 +173,17 @@ class KATProvider(generic.TorrentProvider): continue try: - pubdate = datetime.datetime(*item['published_parsed'][0:6]) - except AttributeError: - try: - pubdate = datetime.datetime(*item['updated_parsed'][0:6]) - except AttributeError: - try: - pubdate = datetime.datetime(*item['created_parsed'][0:6]) - except AttributeError: - try: - pubdate = datetime.datetime(*item['date'][0:6]) - except AttributeError: - pubdate = datetime.datetime.today() - - item = title, url, id, seeders, leechers, size, pubdate + pubdate = datetime.datetime.strptime(item['pubDate'], '%a, %d %b %Y %H:%M:%S +0000') + except Exception: + pubdate = datetime.datetime.today() + + item = title, url, info_hash, seeders, leechers, size, pubdate items[mode].append(item) - except Exception, e: + except Exception: logger.log(u"Failed to parsing " + self.name + " Traceback: " + traceback.format_exc(), - logger.ERROR) + logger.WARNING) #For each search mode sort all the items by seeders items[mode].sort(key=lambda tup: tup[3], reverse=True) @@ -181,7 +193,8 @@ class KATProvider(generic.TorrentProvider): return results def _get_title_and_url(self, item): - title, url, id, seeders, leechers, size, pubdate = item + #pylint: disable=W0612 + title, url, info_hash, seeders, leechers, size, pubdate = item if title: title = self._clean_title_from_provider(title) @@ -191,7 +204,7 @@ class KATProvider(generic.TorrentProvider): return (title, url) - def findPropers(self, search_date=datetime.datetime.today()): + def findPropers(self, search_date=datetime.datetime.today()-datetime.timedelta(days=1)): results = [] myDB = db.DBConnection() @@ -203,21 +216,18 @@ class KATProvider(generic.TorrentProvider): ' OR (e.status IN (' + ','.join([str(x) for x in Quality.SNATCHED]) + ')))' ) - if not sqlResults: - return [] - - for sqlshow in sqlResults: - self.show = helpers.findCertainShow(sickbeard.showList, int(sqlshow["showid"])) - if self.show: - curEp = self.show.getEpisode(int(sqlshow["season"]), int(sqlshow["episode"])) + for sqlshow in sqlResults or []: + show = helpers.findCertainShow(sickbeard.showList, int(sqlshow["showid"])) + if show: + curEp = show.getEpisode(int(sqlshow["season"]), int(sqlshow["episode"])) - searchString = self._get_episode_search_strings(curEp, add_string='PROPER|REPACK') + searchStrings = self._get_episode_search_strings(curEp, add_string='PROPER|REPACK') - for item in self._doSearch(searchString[0]): + for item in self._doSearch(searchStrings): title, url = self._get_title_and_url(item) pubdate = item[6] - results.append(classes.Proper(title, url, pubdate, self.show)) + results.append(classes.Proper(title, url, pubdate, show)) return results @@ -226,15 +236,15 @@ class KATProvider(generic.TorrentProvider): class KATCache(tvcache.TVCache): - def __init__(self, provider): + def __init__(self, provider_obj): - tvcache.TVCache.__init__(self, provider) + tvcache.TVCache.__init__(self, provider_obj) # only poll KickAss every 10 minutes max self.minTime = 20 def _getRSSData(self): - search_params = {'RSS': ['rss']} + search_params = {'RSS': ['']} return {'entries': self.provider._doSearch(search_params)} provider = KATProvider() diff --git a/sickbeard/providers/thepiratebay.py b/sickbeard/providers/thepiratebay.py index 682994d3061237df87f5a65cd232121b7a9d2714..a4f14bcd7c0815307c99e6f0b17d99dfce2299bf 100644 --- a/sickbeard/providers/thepiratebay.py +++ b/sickbeard/providers/thepiratebay.py @@ -19,19 +19,18 @@ from __future__ import with_statement import re -import urllib import datetime import sickbeard -import generic +from sickbeard.providers import generic from sickbeard.common import Quality +from sickbeard.common import USER_AGENT from sickbeard import db from sickbeard import classes from sickbeard import logger from sickbeard import tvcache from sickbeard import helpers from sickbeard.show_name_helpers import allPossibleShowNames, sanitizeSceneName -from unidecode import unidecode class ThePirateBayProvider(generic.TorrentProvider): @@ -44,124 +43,121 @@ class ThePirateBayProvider(generic.TorrentProvider): self.enabled = False self.ratio = None - self.confirmed = False + self.confirmed = True self.minseed = None self.minleech = None self.cache = ThePirateBayCache(self) - self.urls = {'base_url': 'https://thepiratebay.gd/'} + self.urls = { + 'base_url': 'https://thepiratebay.gd/', + 'search': 'https://thepiratebay.gd/s/', + 'rss': 'https://thepiratebay.gd/tv/latest' + } self.url = self.urls['base_url'] - - self.searchurl = self.url + 'search/%s/0/7/200' # order by seed - - self.re_title_url = '/torrent/(?P<id>\d+)/(?P<title>.*?)//1".+?(?P<url>magnet.*?)//1".+?(?P<seeders>\d+)</td>.+?(?P<leechers>\d+)</td>' + self.headers.update({'User-Agent': USER_AGENT}) + + """ + 205 = SD, 208 = HD, 200 = All Videos + https://thepiratebay.gd/s/?q=Game of Thrones&type=search&orderby=7&page=0&category=200 + """ + self.search_params = { + 'q': '', + 'type': 'search', + 'orderby': 7, + 'page': 0, + 'category': 200 + } + + self.re_title_url = r'/torrent/(?P<id>\d+)/(?P<title>.*?)//1".+?(?P<url>magnet.*?)//1".+?Size (?P<size>[\d\.]* [TGKMiB]{2,3}).+?(?P<seeders>\d+)</td>.+?(?P<leechers>\d+)</td>' def isEnabled(self): return self.enabled - def imageName(self): - return 'thepiratebay.png' - def _get_season_search_strings(self, ep_obj): - search_string = {'Season': []} - for show_name in set(allPossibleShowNames(self.show)): + search_strings = {'Season': []} + for show_name in set(allPossibleShowNames(ep_obj.show)): if ep_obj.show.air_by_date or ep_obj.show.sports: ep_string = show_name + ' ' + str(ep_obj.airdate).split('-')[0] - search_string['Season'].append(ep_string) + search_strings['Season'].append(ep_string) ep_string = show_name + ' Season ' + str(ep_obj.airdate).split('-')[0] - search_string['Season'].append(ep_string) elif ep_obj.show.anime: - ep_string = show_name + ' ' + "%02d" % ep_obj.scene_absolute_number - search_string['Season'].append(ep_string) + ep_string = show_name + ' %02d' % ep_obj.scene_absolute_number else: ep_string = show_name + ' S%02d' % int(ep_obj.scene_season) - search_string['Season'].append(ep_string) + search_strings['Season'].append(ep_string) ep_string = show_name + ' Season ' + str(ep_obj.scene_season) + ' -Ep*' - search_string['Season'].append(ep_string) - search_string['Season'].append(ep_string) + search_strings['Season'].append(ep_string) - return [search_string] + return [search_strings] def _get_episode_search_strings(self, ep_obj, add_string=''): - search_string = {'Episode': []} - - if self.show.air_by_date: - for show_name in set(allPossibleShowNames(self.show)): - ep_string = sanitizeSceneName(show_name) + ' ' + \ - str(ep_obj.airdate).replace('-', ' ') - search_string['Episode'].append(ep_string) - elif self.show.sports: - for show_name in set(allPossibleShowNames(self.show)): - ep_string = sanitizeSceneName(show_name) + ' ' + \ - str(ep_obj.airdate).replace('-', '|') + '|' + \ - ep_obj.airdate.strftime('%b') - search_string['Episode'].append(ep_string) - elif self.show.anime: - for show_name in set(allPossibleShowNames(self.show)): - ep_string = sanitizeSceneName(show_name) + ' ' + \ - "%02i" % int(ep_obj.scene_absolute_number) - search_string['Episode'].append(ep_string) - else: - for show_name in set(allPossibleShowNames(self.show)): - ep_string = sanitizeSceneName(show_name) + ' ' + \ - sickbeard.config.naming_ep_type[2] % {'seasonnumber': ep_obj.scene_season, - 'episodenumber': ep_obj.scene_episode} + '|' + \ - sickbeard.config.naming_ep_type[0] % {'seasonnumber': ep_obj.scene_season, - 'episodenumber': ep_obj.scene_episode} + ' %s' % add_string - search_string['Episode'].append(re.sub('\s+', ' ', ep_string)) - - return [search_string] - - def _doSearch(self, search_params, search_mode='eponly', epcount=0, age=0, epObj=None): + search_strings = {'Episode': []} + for show_name in set(allPossibleShowNames(ep_obj.show)): + ep_string = sanitizeSceneName(show_name) + ' ' + if ep_obj.show.air_by_date: + ep_string += str(ep_obj.airdate).replace('-', ' ') + elif ep_obj.show.sports: + ep_string += str(ep_obj.airdate).replace('-', '|') + '|' + ep_obj.airdate.strftime('%b') + elif ep_obj.show.anime: + ep_string += "%02i" % int(ep_obj.scene_absolute_number) + else: + ep_string += sickbeard.config.naming_ep_type[2] % {'seasonnumber': ep_obj.scene_season, + 'episodenumber': ep_obj.scene_episode} + '|' + \ + sickbeard.config.naming_ep_type[0] % {'seasonnumber': ep_obj.scene_season, + 'episodenumber': ep_obj.scene_episode} - results = [] - items = {'Season': [], 'Episode': [], 'RSS': []} + if add_string: + ep_string += ' %s' % add_string + + search_strings['Episode'].append(re.sub(r'\s+', ' ', ep_string).strip()) + + return [search_strings] - for mode in search_params.keys(): - for search_string in search_params[mode]: - if isinstance(search_string, unicode): - search_string = unidecode(search_string) + def _doSearch(self, search_strings, search_mode='eponly', epcount=0, age=0, epObj=None): - if mode != 'RSS': - searchURL = self.searchurl % (urllib.quote(search_string)) - else: - searchURL = self.url + 'tv/latest/' + results = [] + items = {'Season': [], 'Episode': [], 'RSS': []} - logger.log(u"Search string: " + searchURL, logger.DEBUG) + for mode in search_strings.keys(): + for search_string in search_strings[mode]: + self.search_params.update({'q': search_string.strip()}) + logger.log(u"Search string: " + search_string.strip(), logger.DEBUG) - data = self.getURL(searchURL) + data = self.getURL(self.urls[('search', 'rss')[mode == 'RSS']], params=self.search_params) if not data: continue re_title_url = self.proxy._buildRE(self.re_title_url).replace('&f=norefer', '') - matches = re.compile(re_title_url, re.DOTALL).finditer(urllib.unquote(data)) + matches = re.compile(re_title_url, re.DOTALL).finditer(data) for torrent in matches: title = torrent.group('title') url = torrent.group('url') - id = int(torrent.group('id')) + #id = int(torrent.group('id')) + size = self._convertSize(torrent.group('size')) seeders = int(torrent.group('seeders')) leechers = int(torrent.group('leechers')) - #Filter unseeded torrent - if mode != 'RSS' and (seeders < self.minseed or leechers < self.minleech): - logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(name, seeders, leechers), logger.DEBUG) + # Continue before we check if we need to log anything, + # if there is no url or title. + if not title or not url: continue - #Accept Torrent only from Good People for every Episode Search - if self.confirmed and re.search('(VIP|Trusted|Helper|Moderator)', torrent.group(0)) is None: - logger.log(u"ThePirateBay Provider found result " + torrent.group( - 'title') + " but that doesn't seem like a trusted result so I'm ignoring it", logger.DEBUG) + #Filter unseeded torrent + if not seeders or seeders < self.minseed or leechers < self.minleech: + 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 title or not url: + #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: + logger.log(u"ThePirateBay Provider found result " + title + " but that doesn't seem like a trusted result so I'm ignoring it", logger.DEBUG) continue - item = title, url, id, seeders, leechers + item = title, url, size, seeders, leechers items[mode].append(item) @@ -172,9 +168,27 @@ class ThePirateBayProvider(generic.TorrentProvider): return results - def _get_title_and_url(self, item): + def _convertSize(self, size): + size, modifier = size.split(' ') + size = float(size) + if modifier in 'KiB': + size = size * 1024 + elif modifier in 'MiB': + size = size * 1024**2 + elif modifier in 'GiB': + size = size * 1024**3 + elif modifier in 'TiB': + size = size * 1024**4 + return size + + def _get_size(self, item): + # pylint: disable=W0612 + title, url, size, seeders, leechers = item + return size - title, url, id, seeders, leechers = item + def _get_title_and_url(self, item): + # pylint: disable=W0612 + title, url, size, seeders, leechers = item if title: title = self._clean_title_from_provider(title) @@ -184,7 +198,7 @@ class ThePirateBayProvider(generic.TorrentProvider): return (title, url) - def findPropers(self, search_date=datetime.datetime.today()): + def findPropers(self, search_date=datetime.datetime.today()-datetime.timedelta(days=1)): results = [] @@ -197,20 +211,14 @@ class ThePirateBayProvider(generic.TorrentProvider): ' OR (e.status IN (' + ','.join([str(x) for x in Quality.SNATCHED]) + ')))' ) - if not sqlResults: - return [] - - for sqlshow in sqlResults: - self.show = helpers.findCertainShow(sickbeard.showList, int(sqlshow["showid"])) - - if self.show: - curEp = self.show.getEpisode(int(sqlshow["season"]), int(sqlshow["episode"])) - - searchString = self._get_episode_search_strings(curEp, add_string='PROPER|REPACK') - - for item in self._doSearch(searchString[0]): + for sqlshow in sqlResults or []: + show = helpers.findCertainShow(sickbeard.showList, int(sqlshow["showid"])) + if show: + curEp = show.getEpisode(int(sqlshow["season"]), int(sqlshow["episode"])) + searchStrings = self._get_episode_search_strings(curEp, add_string='PROPER|REPACK') + for item in self._doSearch(searchStrings): title, url = self._get_title_and_url(item) - results.append(classes.Proper(title, url, search_date, self.show)) + results.append(classes.Proper(title, url, search_date, show)) return results @@ -219,15 +227,15 @@ class ThePirateBayProvider(generic.TorrentProvider): class ThePirateBayCache(tvcache.TVCache): - def __init__(self, provider): + def __init__(self, provider_obj): - tvcache.TVCache.__init__(self, provider) + tvcache.TVCache.__init__(self, provider_obj) - # only poll ThePirateBay every 10 minutes max - self.minTime = 20 + # only poll ThePirateBay every 30 minutes max + self.minTime = 30 def _getRSSData(self): - search_params = {'RSS': ['rss']} + search_params = {'RSS': ['']} return {'entries': self.provider._doSearch(search_params)} provider = ThePirateBayProvider() diff --git a/sickbeard/providers/torrentproject.py b/sickbeard/providers/torrentproject.py index e1a6958040f6a144074c4074ab9472039408da49..dc81feeb531b5eac3cb4aacbbcd3863da9d4fd1b 100644 --- a/sickbeard/providers/torrentproject.py +++ b/sickbeard/providers/torrentproject.py @@ -148,9 +148,11 @@ class TORRENTPROJECTProvider(generic.TorrentProvider): continue hash = torrents[i]["torrent_hash"] size = torrents[i]["torrent_size"] - - magnet = "magnet:?xt=urn:btih:" + hash + "&tr=udp://open.demonii.com:1337/announce&tr=udp://tracker.openbittorrent.com:80/announce&tr=udp://tracker.leechers-paradise.org:6969/announce&tr=http://tracker.dler.org:6969/announce&tr=http://bt.careland.com.cn:6969/announce&tr=http://tracker.tfile.me/announce&tr=http://mgtracker.org:2710/announce&tr=http://tracker1.wasabii.com.tw:6969/announce" - #logger.log(u'magnet : ' + magnet, logger.DEBUG) + trackerUrl = self.urls['api'] + "" + hash + "/trackers_json" + logger.log(u'The tracker list is: ' + trackerUrl, logger.DEBUG) + jdata = self.getURL(trackerUrl, json=True) + magnet = "magnet:?xt=urn:btih:" + hash + "&dn=" + name + "".join(["&tr=" + s for s in jdata]) + logger.log(u'Magnet URL is: ' + magnet, logger.DEBUG) results.append((name, magnet, size)) logger.log("URL to be parsed: " + searchUrl, logger.DEBUG) diff --git a/sickbeard/providers/xthor.py b/sickbeard/providers/xthor.py index 350b5c98011086636be3df6e21fb46b480882d6f..bdb587eece5cc5759162f44f540805e8e09d1f8e 100644 --- a/sickbeard/providers/xthor.py +++ b/sickbeard/providers/xthor.py @@ -47,7 +47,7 @@ class XthorProvider(generic.TorrentProvider): self.cj = cookielib.CookieJar() self.url = "https://xthor.bz" - self.urlsearch = "https://xthor.bz/browse.php?search=\"%s%s\"" + self.urlsearch = "https://xthor.bz/browse.php?search=\"%s\"%s" self.categories = "&searchin=title&incldead=0" self.enabled = False diff --git a/sickbeard/webapi.py b/sickbeard/webapi.py index 5b867723d914f575e2b644954fa5d835b8fc6df8..b1200db7ae98f5e39fb930749998799fb8b62b4f 100644 --- a/sickbeard/webapi.py +++ b/sickbeard/webapi.py @@ -2706,7 +2706,7 @@ class CMD_ShowStats(ApiCall): # the outgoing container episodes_stats = {} episodes_stats["downloaded"] = {} - # truning codes into strings + # turning codes into strings for statusCode in episode_qualities_counts_download: if statusCode == "total": episodes_stats["downloaded"]["total"] = episode_qualities_counts_download[statusCode] diff --git a/tests/all_tests.py b/tests/all_tests.py index 4f0c9e46008ff1057b77eaab7b20a4f8525738f8..5351032c1ca3b7a25adf9b4461e56bd61863dab2 100755 --- a/tests/all_tests.py +++ b/tests/all_tests.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python2.7 # coding=UTF-8 # Author: Dennis Lutter <lad1337@gmail.com> # URL: http://code.google.com/p/sickbeard/