@@ -372,9 +596,10 @@ addOption("show.pause-opt", "Pause", "&pause=1");
+
-
+
diff --git a/gui/slick/interfaces/default/config_general.tmpl b/gui/slick/interfaces/default/config_general.tmpl
index 9425f48..20fb069 100644
--- a/gui/slick/interfaces/default/config_general.tmpl
+++ b/gui/slick/interfaces/default/config_general.tmpl
@@ -482,6 +482,7 @@
permit the use of the SickGear (SickBeard) API
+ Old Sickbeard API calls are limited to shows from thetvdb.com. Use new API LINK for full access
diff --git a/gui/slick/js/apibuilder.js b/gui/slick/js/apibuilder.js
index 8f7afdc..527fb26 100644
--- a/gui/slick/js/apibuilder.js
+++ b/gui/slick/js/apibuilder.js
@@ -10,7 +10,7 @@
var _disable_empty_list=false;
var _hide_empty_list=false;
-function goListGroup(apikey, L7, L6, L5, L4, L3, L2, L1){
+function goListGroup(apikey, L8, L7, L6, L5, L4, L3, L2, L1){
var GlobalOptions = "";
$('.global').each(function(){
var checked = $(this).prop('checked');
@@ -26,7 +26,7 @@ function goListGroup(apikey, L7, L6, L5, L4, L3, L2, L1){
});
// handle the show.getposter / show.getbanner differently as they return an image and not json
- if (L1 == "?cmd=show.getposter" || L1 == "?cmd=show.getbanner") {
+ if (L1 == "?cmd=sg.getnetworkicon" || L1 == "?cmd=sg.show.getposter" || L1 == "?cmd=sg.show.getbanner" || L1 == "?cmd=show.getposter" || L1 == "?cmd=show.getbanner" || L1 == "?cmd=sg.getindexericon") {
var imgcache = sbRoot + "/api/" + apikey + "/" + L1 + L2 + GlobalOptions;
var html = imgcache + '
';
$('#apiResponse').html(html);
@@ -36,14 +36,24 @@ function goListGroup(apikey, L7, L6, L5, L4, L3, L2, L1){
cache: false,
dataType: "html",
success: function (img) {
- $('#imgcache').attr('src', imgcache);
+ $('#imgcache').attr('src', imgcache + "&random=" + Math.random() * 100000000000000000000);
}
})
}
+ else if (L1 == "?cmd=listcommands")
+ {
+ var html = $.ajax({
+ url: sbRoot + "/api/" + apikey + "/" + L1 + L2 + L3 + L4 + L5 + L6 + L7 + L8 + GlobalOptions,
+ async: false,
+ dataType: "html",
+ }).responseText;
+
+ $('#apiResponse').html(html);
+ }
else {
- var html = sbRoot + "/api/" + apikey + "/" + L1 + L2 + L3 + L4 + L5 + L6 + L7 + GlobalOptions + "
";
+ var html = sbRoot + "/api/" + apikey + "/" + L1 + L2 + L3 + L4 + L5 + L6 + L7 + L8 + GlobalOptions + "
";
html += $.ajax({
- url: sbRoot + "/api/" + apikey + "/" + L1 + L2 + L3 + L4 + L5 + L6 + L7 + GlobalOptions,
+ url: sbRoot + "/api/" + apikey + "/" + L1 + L2 + L3 + L4 + L5 + L6 + L7 + L8 + GlobalOptions,
async: false,
dataType: "html",
}).responseText;
@@ -167,7 +177,7 @@ function cs_addL(dis,link,label,css) { this.items[this.items.length]=new cs_link
function cs_addG(label,css) { this.items[this.items.length]=new cs_groupOBJ(label,css); }
function cs_endG() { this.items[this.items.length]=new cs_groupOBJ2(); }
-function cs_showMsg(msg) { window.status=msg; }
+function cs_showMsg(msg) { console.error(msg); window.status=msg; }
function cs_badContent(n) { cs_goodContent=false; cs_showMsg("["+n+"] Not Found."); }
function _setCookie(name, value) {
@@ -636,6 +646,6 @@ function selectOptions(n,opts,mode) {
}
}
}
- }
+ }
}
// ------
diff --git a/sickbeard/indexers/indexer_api.py b/sickbeard/indexers/indexer_api.py
index b105751..8db5af1 100644
--- a/sickbeard/indexers/indexer_api.py
+++ b/sickbeard/indexers/indexer_api.py
@@ -126,6 +126,11 @@ class indexerApi(object):
return dict((int(x['id']), x['name']) for x in indexerConfig.values() if not x['mapped_only'])
@property
+ def search_indexers(self):
+ return dict((int(x['id']), x['name']) for x in indexerConfig.values() if not x['mapped_only'] and
+ x.get('active') and not x.get('defunct'))
+
+ @property
def all_indexers(self):
"""
return all indexers including mapped only indexers
diff --git a/sickbeard/network_timezones.py b/sickbeard/network_timezones.py
index a5076eb..0972b31 100644
--- a/sickbeard/network_timezones.py
+++ b/sickbeard/network_timezones.py
@@ -295,31 +295,35 @@ def load_network_dict(load=True):
# get timezone of a network or return default timezone
-def get_network_timezone(network):
+def get_network_timezone(network, return_name=False):
if network is None:
return sb_timezone
timezone = None
+ timezone_name = None
try:
if zoneinfo.ZONEFILENAME is not None:
if not network_dict:
load_network_dict()
try:
- timezone = tz.gettz(network_dupes.get(network) or network_dict.get(network.replace(' ', '').lower()),
- zoneinfo_priority=True)
- except:
+ timezone_name = network_dupes.get(network) or network_dict.get(network.replace(' ', '').lower())
+ timezone = tz.gettz(timezone_name, zoneinfo_priority=True)
+ except (StandardError, Exception):
pass
if timezone is None:
cc = re.search(r'\(([a-z]+)\)$', network, flags=re.I)
try:
- timezone = tz.gettz(country_timezones.get(cc.group(1).upper()), zoneinfo_priority=True)
- except:
+ timezone_name = country_timezones.get(cc.group(1).upper())
+ timezone = tz.gettz(timezone_name, zoneinfo_priority=True)
+ except (StandardError, Exception):
pass
- except:
+ except (StandardError, Exception):
pass
+ if return_name:
+ return timezone if isinstance(timezone, datetime.tzinfo) else sb_timezone, timezone_name
return timezone if isinstance(timezone, datetime.tzinfo) else sb_timezone
diff --git a/sickbeard/sbdatetime.py b/sickbeard/sbdatetime.py
index 34bdab1..3566e64 100644
--- a/sickbeard/sbdatetime.py
+++ b/sickbeard/sbdatetime.py
@@ -113,10 +113,10 @@ class sbdatetime(datetime.datetime):
'july', 'august', 'september', 'october', 'november', 'december'])
@static_or_instance
- def convert_to_setting(self, dt=None):
+ def convert_to_setting(self, dt=None, force_local=False):
obj = (dt, self)[self is not None]
try:
- if 'local' == sickbeard.TIMEZONE_DISPLAY:
+ if force_local or 'local' == sickbeard.TIMEZONE_DISPLAY:
return obj.astimezone(sb_timezone)
except (StandardError, Exception):
pass
diff --git a/sickbeard/scene_numbering.py b/sickbeard/scene_numbering.py
index f4a8988..4a2da32 100644
--- a/sickbeard/scene_numbering.py
+++ b/sickbeard/scene_numbering.py
@@ -694,3 +694,66 @@ def fix_xem_numbering(indexer_id, indexer):
if 0 < len(cl):
my_db = db.DBConnection()
my_db.mass_action(cl)
+
+
+def set_scene_numbering_helper(indexerid, indexer, forSeason=None, forEpisode=None, forAbsolute=None,
+ sceneSeason=None, sceneEpisode=None, sceneAbsolute=None):
+ # sanitize:
+ indexerid = None if indexerid in [None, 'null', ''] else int(indexerid)
+ indexer = None if indexer in [None, 'null', ''] else int(indexer)
+
+ show_obj = sickbeard.helpers.find_show_by_id(sickbeard.showList, {indexer: indexerid}, no_mapped_ids=True)
+
+ if not show_obj:
+ result = {'success': False}
+ return result
+
+ if not show_obj.is_anime:
+ for_season = None if forSeason in [None, 'null', ''] else int(forSeason)
+ for_episode = None if forEpisode in [None, 'null', ''] else int(forEpisode)
+ scene_season = None if sceneSeason in [None, 'null', ''] else int(sceneSeason)
+ scene_episode = None if sceneEpisode in [None, 'null', ''] else int(sceneEpisode)
+ action_log = u'Set episode scene numbering to %sx%s for episode %sx%s of "%s"' \
+ % (scene_season, scene_episode, for_season, for_episode, show_obj.name)
+ ep_args = {'show': indexerid, 'season': for_season, 'episode': for_episode}
+ scene_args = {'indexer_id': indexerid, 'indexer': indexer, 'season': for_season, 'episode': for_episode,
+ 'sceneSeason': scene_season, 'sceneEpisode': scene_episode}
+ result = {'forSeason': for_season, 'forEpisode': for_episode, 'sceneSeason': None, 'sceneEpisode': None}
+ else:
+ for_absolute = None if forAbsolute in [None, 'null', ''] else int(forAbsolute)
+ scene_absolute = None if sceneAbsolute in [None, 'null', ''] else int(sceneAbsolute)
+ action_log = u'Set absolute scene numbering to %s for episode %s of "%s"' \
+ % (scene_absolute, for_absolute, show_obj.name)
+ ep_args = {'show': indexerid, 'absolute': for_absolute}
+ scene_args = {'indexer_id': indexerid, 'indexer': indexer, 'absolute_number': for_absolute,
+ 'sceneAbsolute': scene_absolute}
+ result = {'forAbsolute': for_absolute, 'sceneAbsolute': None}
+
+ if ep_args.get('absolute'):
+ ep_obj = show_obj.getEpisode(absolute_number=int(ep_args['absolute']))
+ elif None is not ep_args['season'] and None is not ep_args['episode']:
+ ep_obj = show_obj.getEpisode(int(ep_args['season']), int(ep_args['episode']))
+ else:
+ ep_obj = 'Invalid paramaters'
+
+ if ep_obj is None:
+ ep_obj = "Episode couldn't be retrieved"
+
+ result['success'] = not isinstance(ep_obj, str)
+ if result['success']:
+ logger.log(action_log, logger.DEBUG)
+ set_scene_numbering(**scene_args)
+ show_obj.flushEpisodes()
+ else:
+ result['errorMessage'] = ep_obj
+
+ if not show_obj.is_anime:
+ scene_numbering = get_scene_numbering(indexerid, indexer, for_season, for_episode)
+ if scene_numbering:
+ (result['sceneSeason'], result['sceneEpisode']) = scene_numbering
+ else:
+ scene_numbering = get_scene_absolute_numbering(indexerid, indexer, for_absolute)
+ if scene_numbering:
+ result['sceneAbsolute'] = scene_numbering
+
+ return result
\ No newline at end of file
diff --git a/sickbeard/show_queue.py b/sickbeard/show_queue.py
index 4110a3d..d7500ba 100644
--- a/sickbeard/show_queue.py
+++ b/sickbeard/show_queue.py
@@ -177,10 +177,10 @@ class ShowQueue(generic_queue.GenericQueue):
def addShow(self, indexer, indexer_id, showDir, default_status=None, quality=None, flatten_folders=None,
lang='en', subtitles=None, anime=None, scene=None, paused=None, blacklist=None, whitelist=None,
- wanted_begin=None, wanted_latest=None, tag=None, new_show=False, show_name=None):
+ wanted_begin=None, wanted_latest=None, tag=None, new_show=False, show_name=None, upgrade_once=False):
queueItemObj = QueueItemAdd(indexer, indexer_id, showDir, default_status, quality, flatten_folders, lang,
subtitles, anime, scene, paused, blacklist, whitelist,
- wanted_begin, wanted_latest, tag, new_show=new_show, show_name=show_name)
+ wanted_begin, wanted_latest, tag, new_show=new_show, show_name=show_name, upgrade_once=upgrade_once)
self.add_item(queueItemObj)
@@ -238,7 +238,7 @@ class ShowQueueItem(generic_queue.QueueItem):
class QueueItemAdd(ShowQueueItem):
def __init__(self, indexer, indexer_id, showDir, default_status, quality, flatten_folders, lang, subtitles, anime,
scene, paused, blacklist, whitelist, default_wanted_begin, default_wanted_latest, tag,
- scheduled_update=False, new_show=False, show_name=None):
+ scheduled_update=False, new_show=False, show_name=None, upgrade_once=False):
self.indexer = indexer
self.indexer_id = indexer_id
@@ -247,6 +247,7 @@ class QueueItemAdd(ShowQueueItem):
self.default_wanted_begin = default_wanted_begin
self.default_wanted_latest = default_wanted_latest
self.quality = quality
+ self.upgrade_once = upgrade_once
self.flatten_folders = flatten_folders
self.lang = lang
self.subtitles = subtitles
@@ -341,6 +342,7 @@ class QueueItemAdd(ShowQueueItem):
self.show.location = self.showDir
self.show.subtitles = self.subtitles if None is not self.subtitles else sickbeard.SUBTITLES_DEFAULT
self.show.quality = self.quality if self.quality else sickbeard.QUALITY_DEFAULT
+ self.show.archive_firstmatch = self.upgrade_once
self.show.flatten_folders = self.flatten_folders if None is not self.flatten_folders else sickbeard.FLATTEN_FOLDERS_DEFAULT
self.show.anime = self.anime if None is not self.anime else sickbeard.ANIME_DEFAULT
self.show.scene = self.scene if None is not self.scene else sickbeard.SCENE_DEFAULT
diff --git a/sickbeard/webapi.py b/sickbeard/webapi.py
index b7c5ffa..82de861 100644
--- a/sickbeard/webapi.py
+++ b/sickbeard/webapi.py
@@ -27,6 +27,10 @@ import re
import traceback
import sickbeard
import webserve
+import glob
+
+from mimetypes import MimeTypes
+from random import randint
from sickbeard import db, logger, exceptions, history, ui, helpers
from sickbeard import encodingKludge as ek
@@ -35,12 +39,16 @@ from sickbeard import image_cache
from sickbeard import classes
from sickbeard import processTV
from sickbeard import network_timezones, sbdatetime
-from sickbeard.exceptions import ex
+from sickbeard.exceptions import ex, MultipleShowObjectsException
from sickbeard.common import SNATCHED, SNATCHED_ANY, SNATCHED_PROPER, SNATCHED_BEST, DOWNLOADED, SKIPPED, UNAIRED, IGNORED, ARCHIVED, WANTED, UNKNOWN
from sickbeard.helpers import remove_article
+from sickbeard.scene_numbering import set_scene_numbering_helper
from common import Quality, qualityPresetStrings, statusStrings
from sickbeard.indexers.indexer_config import *
-from sickbeard.webserve import MainHandler
+from sickbeard.indexers import indexer_config, indexer_api
+from tornado import gen
+from sickbeard.search_backlog import FORCED_BACKLOG
+from sickbeard.webserve import NewHomeAddShows
try:
import json
@@ -49,6 +57,7 @@ except ImportError:
from lib import subliminal
+
dateFormat = "%Y-%m-%d"
dateTimeFormat = "%Y-%m-%d %H:%M"
timeFormat = '%A %I:%M %p'
@@ -68,10 +77,24 @@ result_type_map = {RESULT_SUCCESS: "success",
}
# basically everything except RESULT_SUCCESS / success is bad
+quality_map = {'sdtv': Quality.SDTV,
+ 'sddvd': Quality.SDDVD,
+ 'hdtv': Quality.HDTV,
+ 'rawhdtv': Quality.RAWHDTV,
+ 'fullhdtv': Quality.FULLHDTV,
+ 'hdwebdl': Quality.HDWEBDL,
+ 'fullhdwebdl': Quality.FULLHDWEBDL,
+ 'hdbluray': Quality.HDBLURAY,
+ 'fullhdbluray': Quality.FULLHDBLURAY,
+ 'uhd4kweb': Quality.UHD4KWEB,
+ 'unknown': Quality.UNKNOWN}
+
+quality_map_inversed = {v: k for k, v in quality_map.iteritems()}
+
class Api(webserve.BaseHandler):
""" api class that returns json results """
- version = 4 # use an int since float-point is unpredictible
+ version = 10 # use an int since float-point is unpredictible
intent = 4
def set_default_headers(self):
@@ -79,7 +102,10 @@ class Api(webserve.BaseHandler):
self.set_header('X-Robots-Tag', 'noindex, nofollow, noarchive, nocache, noodp, noydir, noimageindex, nosnippet')
if sickbeard.SEND_SECURITY_HEADERS:
self.set_header('X-Frame-Options', 'SAMEORIGIN')
+ self.set_header('X-Application', 'SickGear')
+ self.set_header('X-API-Version', Api.version)
+ @gen.coroutine
def get(self, route, *args, **kwargs):
route = route.strip('/') or 'index'
@@ -287,6 +313,19 @@ class ApiCall(object):
# RequestHandler
self.handler = handler
+ # old sickbeard call
+ self._sickbeard_call = getattr(self, '_sickbeard_call', False)
+
+ @property
+ def sickbeard_call(self):
+ if hasattr(self, '_sickbeard_call'):
+ return self._sickbeard_call
+ return False
+
+ @sickbeard_call.setter
+ def sickbeard_call(self, v):
+ self._sickbeard_call = v
+
def run(self):
# override with real output function in subclass
return {}
@@ -335,13 +374,13 @@ class ApiCall(object):
msg = "The required parameters: '" + "','".join(self._missing) + "' where not set"
return _responds(RESULT_ERROR, msg=msg)
- def check_params(self, args, kwargs, key, default, required, type, allowedValues):
+ def check_params(self, args, kwargs, key, default, required, type, allowedValues, sub_type=None):
# TODO: explain this
""" function to check passed params for the shorthand wrapper
and to detect missing/required param
"""
# Fix for applications that send tvdbid instead of indexerid
- if key == "indexerid" and "indexerid" not in kwargs:
+ if self.sickbeard_call and key == "indexerid" and "indexerid" not in kwargs:
key = "tvdbid"
missing = True
@@ -360,7 +399,8 @@ class ApiCall(object):
if required:
try:
self._missing
- self._requiredParams.append(key)
+ self._requiredParams[key] = {"allowedValues": allowedValues,
+ "defaultValue": orgDefault}
except AttributeError:
self._missing = []
self._requiredParams = {}
@@ -378,14 +418,14 @@ class ApiCall(object):
"defaultValue": orgDefault}
if default:
- default = self._check_param_type(default, key, type)
+ default = self._check_param_type(default, key, type, sub_type)
if type == "bool":
type = []
self._check_param_value(default, key, allowedValues)
return default, args
- def _check_param_type(self, value, name, type):
+ def _check_param_type(self, value, name, type, sub_type):
""" checks if value can be converted / parsed to type
will raise an error on failure
or will convert it to type and return new converted value
@@ -412,7 +452,29 @@ class ApiCall(object):
else:
error = True
elif type == "list":
- value = value.split("|")
+ if None is not sub_type:
+ if sub_type in (int, long):
+ if isinstance(value, (int, long)):
+ value = [value]
+ elif isinstance(value, basestring):
+ if '|' in value:
+ li = [int(v) for v in value.split('|')]
+ if any([not isinstance(v, (int, long)) for v in li]):
+ error = True
+ else:
+ value = li
+ else:
+ value = [int(value)]
+ else:
+ error = True
+ else:
+ li = value.split('|')
+ if any([sub_type is not type(v) for v in li]):
+ error = True
+ else:
+ value = li
+ else:
+ value = value.split("|")
elif type == "string":
pass
elif type == "ignore":
@@ -589,17 +651,7 @@ def _mapQuality(showObj):
def _getQualityMap():
- return {Quality.SDTV: 'sdtv',
- Quality.SDDVD: 'sddvd',
- Quality.HDTV: 'hdtv',
- Quality.RAWHDTV: 'rawhdtv',
- Quality.FULLHDTV: 'fullhdtv',
- Quality.HDWEBDL: 'hdwebdl',
- Quality.FULLHDWEBDL: 'fullhdwebdl',
- Quality.HDBLURAY: 'hdbluray',
- Quality.FULLHDBLURAY: 'fullhdbluray',
- Quality.UNKNOWN: 'unknown'}
-
+ return quality_map_inversed
def _getRootDirs():
if sickbeard.ROOT_DIRS == "":
@@ -651,6 +703,83 @@ class IntParseError(Exception):
# -------------------------------------------------------------------------------------#
+class CMD_ListCommands(ApiCall):
+ _help = {"desc": "list help of all commands",
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ ApiCall.__init__(self, handler, args, kwargs)
+
+ def run(self):
+ """ display help information for all commands """
+ out = ''
+ table_sickgear_commands = ''
+ table_sickbeard_commands = ''
+ for f, v in sorted(_functionMaper.iteritems(), key=lambda x: (re.sub(r'^s[bg]\.', '', x[0], flags=re.I), re.sub(r'^sg\.', '1', x[0], flags=re.I))):
+ if 'listcommands' == f:
+ continue
+ help = getattr(v, '_help', None)
+ is_old_command = isinstance(help, dict) and "SickGearCommand" in help
+ if is_old_command:
+ table_sickbeard_commands += '%s | ' % f
+ else:
+ table_sickgear_commands += '
%s | ' % f
+ color = ("", " style='color: grey !important;'")[is_old_command]
+ out += '
%s%s
' % (color, f, ("", " (Sickbeard compatibility command)")[is_old_command])
+ if isinstance(help, dict):
+ sg_c = ''
+ if "SickGearCommand" in help:
+ sg_c += '%s | ' % help['SickGearCommand']
+ out += "for all features use SickGear API Command: %s
" % help['SickGearCommand']
+ if "desc" in help:
+ if is_old_command:
+ table_sickbeard_commands += '%s | %s' % (help['desc'], sg_c)
+ else:
+ table_sickgear_commands += '%s | ' % help['desc']
+ out += help['desc']
+
+ table = ''
+
+ if "requiredParameters" in help and isinstance(help['requiredParameters'], dict):
+ for p, d in help['requiredParameters'].iteritems():
+ des = ''
+ if isinstance(d, dict) and 'desc' in d:
+ des = d.get('desc')
+ table += "
%s required | %s |
" % (p, des)
+
+ if "optionalParameters" in help and isinstance(help['optionalParameters'], dict):
+ for p, d in help['optionalParameters'].iteritems():
+ des = ''
+ if isinstance(d, dict) and 'desc' in d:
+ des = d.get('desc')
+ table += "%s optional | %s |
" % (p, des)
+ if table:
+ out += "
Parameter | Description |
"
+ out += table
+ out += '
'
+ else:
+ if is_old_command:
+ table_sickbeard_commands += '%s | | ' % 'no description'
+ else:
+ table_sickgear_commands += '%s | ' % 'no description'
+
+ if is_old_command:
+ table_sickbeard_commands += ''
+ else:
+ table_sickgear_commands += ''
+
+ if table_sickbeard_commands:
+ out = "SickBeard Commands (compatibility):
Command | Description | Replacement SickGear Command |
" + table_sickbeard_commands + '
' + out
+
+ if table_sickgear_commands:
+ out = "SickGear Commands:
Command | Description |
" + table_sickgear_commands + '
' + out
+
+ return out
+
class CMD_Help(ApiCall):
_help = {"desc": "display help information for a given subject/command",
@@ -673,7 +802,7 @@ class CMD_Help(ApiCall):
return out
-class CMD_ComingEpisodes(ApiCall):
+class CMD_SickGearComingEpisodes(ApiCall):
_help = {"desc": "display the coming episodes",
"optionalParameters": {"sort": {"desc": "change the sort order"},
"type": {"desc": "one or more of allowedValues separated by |"},
@@ -709,28 +838,51 @@ class CMD_ComingEpisodes(ApiCall):
myDB = db.DBConnection()
sql_results = myDB.select(
- "SELECT airdate, airs, episode, name AS 'ep_name', description AS 'ep_plot', network, season, showid AS 'indexerid', show_name, tv_shows.quality AS quality, tv_shows.status AS 'show_status', tv_shows.paused AS 'paused' FROM tv_episodes, tv_shows WHERE season != 0 AND airdate >= ? AND airdate <= ? AND tv_shows.indexer_id = tv_episodes.showid AND tv_episodes.status NOT IN (" + ','.join(
- ['?'] * len(qualList)) + ")", [yesterday, next_week] + qualList)
+ "SELECT airdate, airs, runtime, tv_shows.indexer AS 'indexer', episode, name AS 'ep_name', "
+ "tv_episodes.status as 'status', description AS 'ep_plot', network, season, showid AS 'indexerid', "
+ "show_name, tv_shows.quality AS quality, tv_shows.status AS 'show_status', "
+ "tv_shows.paused AS 'paused' FROM tv_episodes, tv_shows WHERE " +
+ ("", "tv_shows.indexer = %s AND " % INDEXER_TVDB)[self.sickbeard_call] +
+ "season != 0 AND airdate >= ? AND "
+ "airdate <= ? AND tv_shows.indexer_id = tv_episodes.showid AND tv_shows.indexer == tv_episodes.indexer AND "
+ "tv_episodes.status NOT IN (" + ','.join(['?'] * len(qualList)) + ")", [yesterday, next_week] + qualList)
for cur_result in sql_results:
- done_show_list.append(int(cur_result["indexerid"]))
-
- more_sql_results = myDB.select(
- "SELECT airdate, airs, episode, name AS 'ep_name', description AS 'ep_plot', network, season, showid AS 'indexerid', show_name, tv_shows.quality AS quality, tv_shows.status AS 'show_status', tv_shows.paused AS 'paused' FROM tv_episodes outer_eps, tv_shows WHERE season != 0 AND showid NOT IN (" + ','.join(
- ['?'] * len(
- done_show_list)) + ") AND tv_shows.indexer_id = outer_eps.showid AND airdate = (SELECT airdate FROM tv_episodes inner_eps WHERE inner_eps.season != 0 AND inner_eps.showid = outer_eps.showid AND inner_eps.airdate >= ? ORDER BY inner_eps.airdate ASC LIMIT 1) AND outer_eps.status NOT IN (" + ','.join(
- ['?'] * len(Quality.DOWNLOADED + Quality.SNATCHED)) + ")",
- done_show_list + [next_week] + Quality.DOWNLOADED + Quality.SNATCHED)
+ done_show_list.append((int(cur_result["indexerid"]), int(cur_result["indexer"])))
+
+ more_sql_results = [m for m in myDB.select(
+ "SELECT airdate, airs, runtime, tv_shows.indexer AS 'indexer', episode, name AS 'ep_name', "
+ "outer_eps.status as 'status', description AS 'ep_plot', network, season, showid AS 'indexerid', "
+ "show_name, tv_shows.quality AS quality, tv_shows.status AS 'show_status', "
+ "tv_shows.paused AS 'paused' FROM tv_episodes outer_eps, tv_shows WHERE " +
+ ("", "tv_shows.indexer = %s AND " % INDEXER_TVDB)[self.sickbeard_call] +
+ "season != 0 AND "
+ "tv_shows.indexer_id = outer_eps.showid AND tv_shows.indexer == outer_eps.indexer AND "
+ "airdate = (SELECT airdate FROM tv_episodes inner_eps WHERE inner_eps.season != 0 AND "
+ "inner_eps.showid = outer_eps.showid AND inner_eps.indexer == outer_eps.indexer AND "
+ "inner_eps.airdate >= ? ORDER BY inner_eps.airdate ASC LIMIT 1) AND "
+ "outer_eps.status NOT IN (" + ','.join(['?'] * len(Quality.DOWNLOADED + Quality.SNATCHED)) + ")",
+ [next_week] + Quality.DOWNLOADED + Quality.SNATCHED) if (int(m['indexerid']), int(m['indexer']))
+ not in done_show_list]
sql_results += more_sql_results
more_sql_results = myDB.select(
- "SELECT airdate, airs, episode, name AS 'ep_name', description AS 'ep_plot', network, season, showid AS 'indexerid', show_name, tv_shows.quality AS quality, tv_shows.status AS 'show_status', tv_shows.paused AS 'paused' FROM tv_episodes, tv_shows WHERE season != 0 AND tv_shows.indexer_id = tv_episodes.showid AND airdate <= ? AND airdate >= ? AND tv_episodes.status = ? AND tv_episodes.status NOT IN (" + ','.join(
+ "SELECT airdate, airs, runtime, tv_shows.indexer AS 'indexer', episode, name AS 'ep_name', "
+ "tv_episodes.status as 'status', description AS 'ep_plot', network, season, showid AS 'indexerid', "
+ "show_name, tv_shows.quality AS quality, tv_shows.status AS 'show_status', "
+ "tv_shows.paused AS 'paused' FROM tv_episodes, tv_shows WHERE " +
+ ("", "tv_shows.indexer = %s AND " % INDEXER_TVDB)[self.sickbeard_call] +
+ "season != 0 AND tv_shows.indexer_id = tv_episodes.showid AND tv_shows.indexer == tv_episodes.indexer AND "
+ "airdate <= ? AND airdate >= ? AND "
+ "tv_episodes.status = ? AND tv_episodes.status NOT IN (" + ','.join(
['?'] * len(qualList)) + ")", [tomorrow, recently, WANTED] + qualList)
sql_results += more_sql_results
sql_results = list(set(sql_results))
# make a dict out of the sql results
- sql_results = [dict(row) for row in sql_results]
+ sql_results = [dict(row) for row in sql_results
+ if Quality.splitCompositeStatus(helpers.tryInt(row['status']))[0] not in
+ [DOWNLOADED, SNATCHED, SNATCHED_PROPER, SNATCHED_BEST, ARCHIVED, IGNORED, SKIPPED]]
# multi dimension sort
sorts = {
@@ -752,9 +904,15 @@ class CMD_ComingEpisodes(ApiCall):
# add parsed_datetime to the dict
for index, item in enumerate(sql_results):
- sql_results[index]['parsed_datetime'] = network_timezones.parse_date_time(item['airdate'], item['airs'], item['network'])
+ timezone, sql_results[index]['timezone'] = network_timezones.get_network_timezone(item['network'],
+ return_name=True)
+ p_t = network_timezones.parse_date_time(item['airdate'], item['airs'], timezone)
+ sql_results[index]['parsed_datetime'] = p_t
+ sql_results[index]['local_datetime'] = sbdatetime.sbdatetime.sbstrftime(
+ sbdatetime.sbdatetime.convert_to_setting(p_t, force_local=True), dateTimeFormat)
sql_results[index]['data_show_name'] = value_maybe_article(item['show_name'])
sql_results[index]['data_network'] = value_maybe_article(item['network'])
+ sql_results[index]['status_str'] = statusStrings[item['status']]
sql_results.sort(sorts[self.sort])
@@ -801,7 +959,14 @@ class CMD_ComingEpisodes(ApiCall):
# start day of the week on 1 (monday)
ep['weekday'] = 1 + datetime.date.fromordinal(ep['airdate']).weekday()
# Add tvdbid for backward compability
- ep["tvdbid"] = ep['indexerid']
+ try:
+ showObj = helpers.find_show_by_id(sickbeard.showList, {ep['indexer']: ep['indexerid']})
+ ep['tvdbid'] = showObj.ids.get(INDEXER_TVDB, {'id': 0})['id']
+ ep['ids'] = {k: v.get('id') for k, v in showObj.ids.iteritems()}
+ except (StandardError, Exception):
+ ep['tvdbid'] = (None, ep['indexerid'])[INDEXER_TVDB == ep['indexer']]
+ ep['ids'] = None
+
ep['airdate'] = sbdatetime.sbdatetime.sbfdate(datetime.date.fromordinal(ep['airdate']), d_preset=dateFormat)
ep['parsed_datetime'] = sbdatetime.sbdatetime.sbfdatetime(ep['parsed_datetime'], d_preset=dateFormat, t_preset='%H:%M %z')
@@ -814,19 +979,41 @@ class CMD_ComingEpisodes(ApiCall):
return _responds(RESULT_SUCCESS, finalEpResults)
-class CMD_Episode(ApiCall):
+class CMD_ComingEpisodes(CMD_SickGearComingEpisodes):
+ _help = {"desc": "display the coming episodes",
+ "optionalParameters": {"sort": {"desc": "change the sort order"},
+ "type": {"desc": "one or more of allowedValues separated by |"},
+ "paused": {
+ "desc": "0 to exclude paused shows, 1 to include them, or omitted to use the SB default"},
+ },
+ "SickGearCommand": "sg.future",
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ self.sickbeard_call = True
+ CMD_SickGearComingEpisodes.__init__(self, handler, args, kwargs)
+
+
+class CMD_SickGearEpisode(ApiCall):
_help = {"desc": "display detailed info about an episode",
- "requiredParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"},
+ "requiredParameters": {"indexer": {"desc": "indexer of a show"},
+ "indexerid": {"desc": "unique id of a show"},
"season": {"desc": "the season number"},
"episode": {"desc": "the episode number"}
- },
+ },
"optionalParameters": {"full_path": {
- "desc": "show the full absolute path (if valid) instead of a relative path for the episode location"}
- }
- }
+ "desc": "show the full absolute path (if valid) instead of a relative path for the episode location"},
+
+ },
+ }
def __init__(self, handler, args, kwargs):
# required
+ self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int",
+ [i for i in indexer_api.indexerApi().indexers])
self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
self.s, args = self.check_params(args, kwargs, "season", None, True, "int", [])
self.e, args = self.check_params(args, kwargs, "episode", None, True, "int", [])
@@ -837,14 +1024,16 @@ class CMD_Episode(ApiCall):
def run(self):
""" display detailed info about an episode """
- showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid))
+ showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid})
if not showObj:
return _responds(RESULT_FAILURE, msg="Show not found")
myDB = db.DBConnection(row_type="dict")
sqlResults = myDB.select(
- "SELECT name, description, airdate, status, location, file_size, release_name, subtitles FROM tv_episodes WHERE showid = ? AND episode = ? AND season = ?",
- [self.indexerid, self.e, self.s])
+ "SELECT name, description, airdate, status, location, file_size, release_name, subtitles, absolute_number, "
+ "scene_season, scene_episode, scene_absolute_number FROM tv_episodes WHERE indexer = ? AND showid = ? "
+ "AND episode = ? AND season = ?",
+ [self.indexer, self.indexerid, self.e, self.s])
if not len(sqlResults) == 1:
raise ApiError("Episode not found")
episode = sqlResults[0]
@@ -865,7 +1054,8 @@ class CMD_Episode(ApiCall):
elif not showPath: # show dir is broken ... episode path will be empty
episode["location"] = ""
# convert stuff to human form
- episode['airdate'] = sbdatetime.sbdatetime.sbfdate(sbdatetime.sbdatetime.convert_to_setting(network_timezones.parse_date_time(int(episode['airdate']), showObj.airs, showObj.network)), d_preset=dateFormat)
+ timezone, episode['timezone'] = network_timezones.get_network_timezone(showObj.network, return_name=True)
+ episode['airdate'] = sbdatetime.sbdatetime.sbfdate(sbdatetime.sbdatetime.convert_to_setting(network_timezones.parse_date_time(int(episode['airdate']), showObj.airs, timezone)), d_preset=dateFormat)
status, quality = Quality.splitCompositeStatus(int(episode["status"]))
episode["status"] = _get_status_Strings(status)
episode["quality"] = _get_quality_string(quality)
@@ -874,16 +1064,39 @@ class CMD_Episode(ApiCall):
return _responds(RESULT_SUCCESS, episode)
-class CMD_EpisodeSearch(ApiCall):
+class CMD_Episode(CMD_SickGearEpisode):
+ _help = {"desc": "display detailed info about an episode",
+ "requiredParameters": {"indexerid": {"desc": "unique id of a show"},
+ "season": {"desc": "the season number"},
+ "episode": {"desc": "the episode number"}
+ },
+ "optionalParameters": {"full_path": {
+ "desc": "show the full absolute path (if valid) instead of a relative path for the episode location"},
+ },
+ "SickGearCommand": "sg.episode",
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # super, missing, help
+ kwargs['indexer'] = INDEXER_TVDB
+ self.sickbeard_call = True
+ CMD_SickGearEpisode.__init__(self, handler, args, kwargs)
+
+
+class CMD_SickGearEpisodeSearch(ApiCall):
_help = {"desc": "search for an episode. the response might take some time",
- "requiredParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"},
+ "requiredParameters": {"indexer": {"desc": "indexer of a show"},
+ "indexerid": {"desc": "unique id of a show"},
"season": {"desc": "the season number"},
"episode": {"desc": "the episode number"}
- }
+ },
}
def __init__(self, handler, args, kwargs):
# required
+ self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int",
+ [i for i in indexer_api.indexerApi().indexers])
self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
self.s, args = self.check_params(args, kwargs, "season", None, True, "int", [])
self.e, args = self.check_params(args, kwargs, "episode", None, True, "int", [])
@@ -893,7 +1106,7 @@ class CMD_EpisodeSearch(ApiCall):
def run(self):
""" search for an episode """
- showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid))
+ showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid})
if not showObj:
return _responds(RESULT_FAILURE, msg="Show not found")
@@ -920,32 +1133,55 @@ class CMD_EpisodeSearch(ApiCall):
return _responds(RESULT_FAILURE, msg='Unable to find episode')
-class CMD_EpisodeSetStatus(ApiCall):
+class CMD_EpisodeSearch(CMD_SickGearEpisodeSearch):
+ _help = {"desc": "search for an episode. the response might take some time",
+ "requiredParameters": {"tvdbid": {"desc": "thetvdb.com id of a show"},
+ "season": {"desc": "the season number"},
+ "episode": {"desc": "the episode number"}
+ },
+ "SickGearCommand": "episode.search",
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ kwargs['indexer'] = INDEXER_TVDB
+ self.sickbeard_call = True
+ CMD_SickGearEpisodeSearch.__init__(self, handler, args, kwargs)
+
+
+class CMD_SickGearEpisodeSetStatus(ApiCall):
_help = {"desc": "set status of an episode or season (when no ep is provided)",
- "requiredParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"},
+ "requiredParameters": {"indexer": {"desc": "indexer of a show"},
+ "indexerid": {"desc": "unique id of a show"},
"season": {"desc": "the season number"},
- "status": {"desc": "the status values: wanted, skipped, archived, ignored, failed"}
- },
+ "status": {"desc": "the status values: wanted, skipped, archived, ignored, failed, snatched, downloaded"}
+ },
"optionalParameters": {"episode": {"desc": "the episode number"},
- "force": {"desc": "should we replace existing (downloaded) episodes or not"}
- }
- }
+ "force": {"desc": "should we replace existing (downloaded) episodes or not"},
+ "quality": {"desc": "set quality of episode(s), only for statuses: snatched, downloaded, archived"},
+ }
+ }
def __init__(self, handler, args, kwargs):
# required
+ self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int",
+ [i for i in indexer_api.indexerApi().indexers])
self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
self.s, args = self.check_params(args, kwargs, "season", None, True, "int", [])
self.status, args = self.check_params(args, kwargs, "status", None, True, "string",
- ["wanted", "skipped", "archived", "ignored", "failed"])
+ ["wanted", "skipped", "archived", "ignored", "failed", "snatched", "downloaded"])
# optional
self.e, args = self.check_params(args, kwargs, "episode", None, False, "int", [])
self.force, args = self.check_params(args, kwargs, "force", 0, False, "bool", [])
+ self.quality, args = self.check_params(args, kwargs, "quality", None, False, "string", [q for q in quality_map])
# super, missing, help
ApiCall.__init__(self, handler, args, kwargs)
def run(self):
""" set status of an episode or a season (when no ep is provided) """
- showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid))
+ showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid})
if not showObj:
return _responds(RESULT_FAILURE, msg="Show not found")
@@ -958,6 +1194,12 @@ class CMD_EpisodeSetStatus(ApiCall):
# the allowed values has at least one item that could not be matched against the internal status strings
raise ApiError("The status string could not be matched to a status. Report to Devs!")
+ if None is not self.quality:
+ if self.status not in (SNATCHED, SNATCHED_BEST, SNATCHED_PROPER, DOWNLOADED, ARCHIVED):
+ return _responds(RESULT_FAILURE, msg="Can't set status %s together with quailty: %s" %
+ (statusStrings[self.status], self.quality))
+ self.quality = quality_map[self.quality]
+
ep_list = []
if self.e:
epObj = showObj.getEpisode(self.s, self.e)
@@ -996,13 +1238,16 @@ class CMD_EpisodeSetStatus(ApiCall):
continue
# allow the user to force setting the status for an already downloaded episode
- if epObj.status in Quality.DOWNLOADED and not self.force:
+ if epObj.status in Quality.DOWNLOADED and not self.force and None is self.quality:
ep_results.append(_epResult(RESULT_FAILURE, epObj,
"Refusing to change status because it is already marked as DOWNLOADED"))
failure = True
continue
- epObj.status = self.status
+ if None is not self.quality:
+ epObj.status = Quality.compositeStatus(self.status, self.quality)
+ else:
+ epObj.status = self.status
result = epObj.get_sql()
if None is not result:
sql_l.append(result)
@@ -1032,16 +1277,40 @@ class CMD_EpisodeSetStatus(ApiCall):
return _responds(RESULT_SUCCESS, msg='All status set successfully.' + extra_msg)
-class CMD_SubtitleSearch(ApiCall):
- _help = {"desc": "search episode subtitles. the response might take some time",
+class CMD_EpisodeSetStatus(CMD_SickGearEpisodeSetStatus):
+ _help = {"desc": "set status of an episode or season (when no ep is provided)",
"requiredParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"},
"season": {"desc": "the season number"},
+ "status": {"desc": "the status values: wanted, skipped, archived, ignored, failed"}
+ },
+ "optionalParameters": {"episode": {"desc": "the episode number"},
+ "force": {"desc": "should we replace existing (downloaded) episodes or not"}
+ },
+ "SickGearCommand": "sg.episode.setstatus",
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ self.sickbeard_call = True
+ kwargs['indexer'] = INDEXER_TVDB
+ CMD_SickGearEpisodeSetStatus.__init__(self, handler, args, kwargs)
+
+
+class CMD_SickGearSubtitleSearch(ApiCall):
+ _help = {"desc": "search episode subtitles. the response might take some time",
+ "requiredParameters": {"indexer": {"desc": "indexer of a show"},
+ "indexerid": {"desc": "unique id of a show"},
+ "season": {"desc": "the season number"},
"episode": {"desc": "the episode number"}
- }
+ },
}
def __init__(self, handler, args, kwargs):
# required
+ self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int",
+ [i for i in indexer_api.indexerApi().indexers])
self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
self.s, args = self.check_params(args, kwargs, "season", None, True, "int", [])
self.e, args = self.check_params(args, kwargs, "episode", None, True, "int", [])
@@ -1051,7 +1320,7 @@ class CMD_SubtitleSearch(ApiCall):
def run(self):
""" search episode subtitles """
- showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid))
+ showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid})
if not showObj:
return _responds(RESULT_FAILURE, msg="Show not found")
@@ -1084,16 +1353,37 @@ class CMD_SubtitleSearch(ApiCall):
return response
-class CMD_Exceptions(ApiCall):
+class CMD_SubtitleSearch(ApiCall):
+ _help = {"desc": "search episode subtitles. the response might take some time",
+ "requiredParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"},
+ "season": {"desc": "the season number"},
+ "episode": {"desc": "the episode number"}
+ },
+ "SickGearCommand": "sg.episode.subtitlesearch",
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ kwargs['indexer'] = INDEXER_TVDB
+ self.sickbeard_call = True
+ ApiCall.__init__(self, handler, args, kwargs)
+
+
+class CMD_SickGearExceptions(ApiCall):
_help = {"desc": "display scene exceptions for all or a given show",
- "optionalParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"},
- }
- }
+ "optionalParameters": {"indexerid": {"desc": "unique id of a show"},
+ "indexer": {"desc": "indexer of a show"},
+ }
+ }
def __init__(self, handler, args, kwargs):
# required
# optional
self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, False, "int", [])
+ self.indexer, args = self.check_params(args, kwargs, "indexer", None, False, "int",
+ [i for i in indexer_api.indexerApi().indexers])
# super, missing, help
ApiCall.__init__(self, handler, args, kwargs)
@@ -1111,7 +1401,7 @@ class CMD_Exceptions(ApiCall):
scene_exceptions[indexerid].append(row["show_name"])
else:
- showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid))
+ showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid})
if not showObj:
return _responds(RESULT_FAILURE, msg="Show not found")
@@ -1125,12 +1415,95 @@ class CMD_Exceptions(ApiCall):
return _responds(RESULT_SUCCESS, scene_exceptions)
-class CMD_History(ApiCall):
- _help = {"desc": "display sickbeard downloaded/snatched history",
+class CMD_Exceptions(CMD_SickGearExceptions):
+ _help = {"desc": "display scene exceptions for all or a given show",
+ "optionalParameters": {"indexerid": {"desc": "thetvdb.com id of a show"},
+ },
+ "SickGearCommand": "sg.exceptions",
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ kwargs['indexer'] = INDEXER_TVDB
+ self.sickbeard_call = True
+ CMD_SickGearExceptions.__init__(self, handler, args, kwargs)
+
+
+class CMD_SetExceptions(ApiCall):
+ _help = {"desc": "set scene exceptions for a given show",
+ "requiredParameters": {"indexerid": {"desc": "unique id of a show"},
+ "indexer": {"desc": "indexer of a show"},
+ "forseason": {"desc": "exception for season, -1 for all seasons"},
+ },
+ "optionalParameters": {"add": {"desc": "list of exceptions to add"},
+ "remove": {"desc": "list of exceptions to remove"},
+ },
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
+ self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int",
+ [i for i in indexer_api.indexerApi().indexers])
+ self.forseason, args = self.check_params(args, kwargs, "forseason", None, True, "int", [])
+ # optional
+ self.add, args = self.check_params(args, kwargs, "add", None, False, "list", [])
+ self.remove, args = self.check_params(args, kwargs, "remove", None, False, "list", [])
+ # super, missing, help
+ ApiCall.__init__(self, handler, args, kwargs)
+
+ def run(self):
+ if not self.add and not self.remove:
+ return _responds(RESULT_FAILURE, 'No Exceptions provided to be add or removed.')
+
+ showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid})
+ if not showObj:
+ return _responds(RESULT_FAILURE, 'Could not find any show in db from indexer: %s with id: %s' %
+ (self.indexer, self.indexerid))
+
+ myDB = db.DBConnection(row_type="dict")
+ sqlResults = myDB.select("SELECT show_name, season, indexer_id AS 'indexerid' FROM scene_exceptions WHERE "
+ "indexer_id = ? and season = ?", [self.indexerid, self.forseason])
+
+ cl = []
+ curexep = [(s['show_name'], s['season']) for s in sqlResults]
+ add_list = []
+ remove_list = []
+ if self.remove:
+ for r in self.remove:
+ if (r, self.forseason) in curexep:
+ cl.append(['DELETE FROM scene_exceptions WHERE indexer_id = ? AND season = ? AND show_name = ?',
+ [self.indexerid, self.forseason, r]])
+ try:
+ curexep.remove((r, self.forseason))
+ except ValueError:
+ pass
+ remove_list.append(r)
+
+ if self.add:
+ for a in self.add:
+ if (a, self.forseason) not in curexep:
+ cl.append(['INSERT INTO scene_exceptions (show_name, indexer_id, season) VALUES (?,?,?)',
+ [a, self.indexerid, self.forseason]])
+ curexep.append((a, self.forseason))
+ add_list.append(a)
+
+ if cl:
+ myDB.mass_action(cl)
+ return _responds(RESULT_SUCCESS, data={'added': add_list, 'removed': remove_list, 'for season': self.forseason,
+ 'current': [c[0] for c in curexep], 'indexer': self.indexer,
+ 'indexerid': self.indexerid},
+ msg='Exceptions changed.')
+
+
+class CMD_SickGearHistory(ApiCall):
+ _help = {"desc": "display sickgear downloaded/snatched history",
"optionalParameters": {"limit": {"desc": "limit returned results"},
"type": {"desc": "only show a specific type of results"},
- }
- }
+ }
+ }
def __init__(self, handler, args, kwargs):
# required
@@ -1141,7 +1514,7 @@ class CMD_History(ApiCall):
ApiCall.__init__(self, handler, args, kwargs)
def run(self):
- """ display sickbeard downloaded/snatched history """
+ """ display sickgear downloaded/snatched history """
typeCodes = []
if self.type == "downloaded":
@@ -1158,12 +1531,15 @@ class CMD_History(ApiCall):
ulimit = min(int(self.limit), 100)
if ulimit == 0:
sqlResults = myDB.select(
- "SELECT h.*, show_name FROM history h, tv_shows s WHERE h.showid=s.indexer_id AND action in (" + ','.join(
- ['?'] * len(typeCodes)) + ") ORDER BY date DESC", typeCodes)
+ "SELECT h.*, show_name, s.indexer FROM history h, tv_shows s WHERE h.showid=s.indexer_id" +
+ ("", " AND s.indexer=%s" % INDEXER_TVDB)[self.sickbeard_call] +
+ " AND action in (" + ','.join(['?'] * len(typeCodes)) + ") ORDER BY date DESC", typeCodes)
else:
sqlResults = myDB.select(
- "SELECT h.*, show_name FROM history h, tv_shows s WHERE h.showid=s.indexer_id AND action in (" + ','.join(
- ['?'] * len(typeCodes)) + ") ORDER BY date DESC LIMIT ?", typeCodes + [ulimit])
+ "SELECT h.*, show_name, s.indexer FROM history h, tv_shows s WHERE h.showid=s.indexer_id" +
+ ("", " AND s.indexer=%s" % INDEXER_TVDB)[self.sickbeard_call] +
+ " AND action in (" + ','.join(['?'] * len(typeCodes)) + ") ORDER BY date DESC LIMIT ?",
+ typeCodes + [ulimit])
results = []
for row in sqlResults:
@@ -1179,15 +1555,31 @@ class CMD_History(ApiCall):
row["resource_path"] = os.path.dirname(row["resource"])
row["resource"] = os.path.basename(row["resource"])
# Add tvdbid for backward compability
- row['tvdbid'] = row['indexerid']
+ row['tvdbid'] = (None, row['indexerid'])[INDEXER_TVDB == row['indexer']]
results.append(row)
return _responds(RESULT_SUCCESS, results)
-class CMD_HistoryClear(ApiCall):
- _help = {"desc": "clear sickbeard's history",
- }
+class CMD_History(CMD_SickGearHistory):
+ _help = {"desc": "display sickgear downloaded/snatched history",
+ "optionalParameters": {"limit": {"desc": "limit returned results"},
+ "type": {"desc": "only show a specific type of results"},
+ },
+ "SickGearCommand": "sg.history",
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ self.sickbeard_call = True
+ CMD_SickGearHistory.__init__(self, handler, args, kwargs)
+
+
+class CMD_SickGearHistoryClear(ApiCall):
+ _help = {"desc": "clear sickgear's history",
+ }
def __init__(self, handler, args, kwargs):
# required
@@ -1196,16 +1588,29 @@ class CMD_HistoryClear(ApiCall):
ApiCall.__init__(self, handler, args, kwargs)
def run(self):
- """ clear sickbeard's history """
+ """ clear sickgear's history """
myDB = db.DBConnection()
myDB.action("DELETE FROM history WHERE 1=1")
return _responds(RESULT_SUCCESS, msg="History cleared")
-class CMD_HistoryTrim(ApiCall):
- _help = {"desc": "trim sickbeard's history by removing entries greater than 30 days old"
- }
+class CMD_HistoryClear(CMD_SickGearHistoryClear):
+ _help = {"desc": "clear sickgear's history",
+ "SickGearCommand": "sg.history.clear",
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ self.sickbeard_call = True
+ CMD_SickGearHistoryClear.__init__(self, handler, args, kwargs)
+
+
+class CMD_SickGearHistoryTrim(ApiCall):
+ _help = {"desc": "trim sickgear's history by removing entries greater than 30 days old"
+ }
def __init__(self, handler, args, kwargs):
# required
@@ -1214,7 +1619,7 @@ class CMD_HistoryTrim(ApiCall):
ApiCall.__init__(self, handler, args, kwargs)
def run(self):
- """ trim sickbeard's history """
+ """ trim sickgear's history """
myDB = db.DBConnection()
myDB.action("DELETE FROM history WHERE date < " + str(
(datetime.datetime.today() - datetime.timedelta(days=30)).strftime(history.dateFormat)))
@@ -1222,11 +1627,24 @@ class CMD_HistoryTrim(ApiCall):
return _responds(RESULT_SUCCESS, msg="Removed history entries greater than 30 days old")
-class CMD_Logs(ApiCall):
- _help = {"desc": "view sickbeard's log",
+class CMD_HistoryTrim(CMD_SickGearHistoryTrim):
+ _help = {"desc": "trim sickgear's history by removing entries greater than 30 days old",
+ "SickGearCommand": "sg.history.trim",
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ self.sickbeard_call = True
+ CMD_SickGearHistoryTrim.__init__(self, handler, args, kwargs)
+
+
+class CMD_SickGearLogs(ApiCall):
+ _help = {"desc": "view sickgear's log",
"optionalParameters": {"min_level ": {
"desc": "the minimum level classification of log entries to show, with each level inherting its above level"}}
- }
+ }
def __init__(self, handler, args, kwargs):
# required
@@ -1297,7 +1715,22 @@ class CMD_Logs(ApiCall):
return _responds(RESULT_SUCCESS, final_data)
-class CMD_PostProcess(ApiCall):
+class CMD_Logs(CMD_SickGearLogs):
+ _help = {"desc": "view sickgear's log",
+ "optionalParameters": {"min_level ": {
+ "desc": "the minimum level classification of log entries to show, with each level inherting its above level"}},
+ "SickGearCommand": "sg.logs",
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ self.sickbeard_call = True
+ CMD_SickGearLogs.__init__(self, handler, args, kwargs)
+
+
+class CMD_SickGearPostProcess(ApiCall):
_help = {"desc": "Manual postprocess TV Download Dir",
"optionalParameters": {"path": {"desc": "Post process this folder"},
"force_replace": {"desc": "Force already Post Processed Dir/Files"},
@@ -1339,8 +1772,28 @@ class CMD_PostProcess(ApiCall):
return _responds(RESULT_SUCCESS, data=data, msg="Started postprocess for %s" % self.path)
-class CMD_SickBeard(ApiCall):
- _help = {"desc": "display misc sickbeard related information"}
+class CMD_PostProcess(CMD_SickGearPostProcess):
+ _help = {"desc": "Manual postprocess TV Download Dir",
+ "optionalParameters": {"path": {"desc": "Post process this folder"},
+ "force_replace": {"desc": "Force already Post Processed Dir/Files"},
+ "return_data": {"desc": "Returns result for the process"},
+ "process_method": {"desc": "Symlink, hardlink, move or copy the file"},
+ "is_priority": {"desc": "Replace the file even if it exists in a higher quality)"},
+ "type": {"desc": "What type of postprocess request is this, auto of manual"}
+ },
+ "SickGearCommand": "sg.postprocess",
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ self.sickbeard_call = True
+ CMD_SickGearPostProcess.__init__(self, handler, args, kwargs)
+
+
+class CMD_SickGear(ApiCall):
+ _help = {"desc": "display misc sickgear related information"}
def __init__(self, handler, args, kwargs):
# required
@@ -1349,19 +1802,31 @@ class CMD_SickBeard(ApiCall):
ApiCall.__init__(self, handler, args, kwargs)
def run(self):
- """ display misc sickbeard related information """
- data = {"sb_version": sickbeard.BRANCH, "api_version": Api.version,
- "api_commands": sorted(_functionMaper.keys())}
+ """ display misc sickgear related information """
+ data = {"sb_version": sickbeard.BRANCH, "api_version": Api.version, "fork": "SickGear",
+ "api_commands": sorted(x for x in _functionMaper.keys() if 'listcommands' != x)}
return _responds(RESULT_SUCCESS, data)
-class CMD_SickBeardAddRootDir(ApiCall):
- _help = {"desc": "add a sickbeard user's parent directory",
+class CMD_SickBeard(CMD_SickGear):
+ _help = {"desc": "display misc sickgear related information",
+ "SickGearCommand": "sg",}
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ self.sickbeard_call = True
+ CMD_SickGear.__init__(self, handler, args, kwargs)
+
+
+class CMD_SickGearAddRootDir(ApiCall):
+ _help = {"desc": "add a sickgear user's parent directory",
"requiredParameters": {"location": {"desc": "the full path to root (parent) directory"}
- },
+ },
"optionalParameters": {"default": {"desc": "make the location passed the default root (parent) directory"}
- }
- }
+ }
+ }
def __init__(self, handler, args, kwargs):
# required
@@ -1372,7 +1837,7 @@ class CMD_SickBeardAddRootDir(ApiCall):
ApiCall.__init__(self, handler, args, kwargs)
def run(self):
- """ add a parent directory to sickbeard's config """
+ """ add a parent directory to sickgear's config """
self.location = urllib.unquote_plus(self.location)
location_matched = 0
@@ -1413,7 +1878,24 @@ class CMD_SickBeardAddRootDir(ApiCall):
return _responds(RESULT_SUCCESS, _getRootDirs(), msg="Root directories updated")
-class CMD_SickBeardCheckScheduler(ApiCall):
+class CMD_SickBeardAddRootDir(CMD_SickGearAddRootDir):
+ _help = {"desc": "add a sickgear user's parent directory",
+ "requiredParameters": {"location": {"desc": "the full path to root (parent) directory"}
+ },
+ "optionalParameters": {"default": {"desc": "make the location passed the default root (parent) directory"}
+ },
+ "SickGearCommand": "sg.addrootdir",
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ self.sickbeard_call = True
+ CMD_SickGearAddRootDir.__init__(self, handler, args, kwargs)
+
+
+class CMD_SickGearCheckScheduler(ApiCall):
_help = {"desc": "query the scheduler"}
def __init__(self, handler, args, kwargs):
@@ -1437,10 +1919,22 @@ class CMD_SickBeardCheckScheduler(ApiCall):
return _responds(RESULT_SUCCESS, data)
-class CMD_SickBeardDeleteRootDir(ApiCall):
- _help = {"desc": "delete a sickbeard user's parent directory",
+class CMD_SickBeardCheckScheduler(CMD_SickGearCheckScheduler):
+ _help = {"desc": "query the scheduler",
+ "SickGearCommand": "sg.checkscheduler"}
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ self.sickbeard_call = True
+ CMD_SickGearCheckScheduler.__init__(self, handler, args, kwargs)
+
+
+class CMD_SickGearDeleteRootDir(ApiCall):
+ _help = {"desc": "delete a sickgear user's parent directory",
"requiredParameters": {"location": {"desc": "the full path to root (parent) directory"}}
- }
+ }
def __init__(self, handler, args, kwargs):
# required
@@ -1450,7 +1944,7 @@ class CMD_SickBeardDeleteRootDir(ApiCall):
ApiCall.__init__(self, handler, args, kwargs)
def run(self):
- """ delete a parent directory from sickbeard's config """
+ """ delete a parent directory from sickgear's config """
if sickbeard.ROOT_DIRS == "":
return _responds(RESULT_FAILURE, _getRootDirs(), msg="No root directories detected")
@@ -1483,45 +1977,108 @@ class CMD_SickBeardDeleteRootDir(ApiCall):
return _responds(RESULT_SUCCESS, _getRootDirs(), msg="Root directory deleted")
-class CMD_SickBeardForceSearch(ApiCall):
- _help = {'desc': 'force the episode recent search early'}
+class CMD_SickBeardDeleteRootDir(CMD_SickGearDeleteRootDir):
+ _help = {"desc": "delete a sickgear user's parent directory",
+ "requiredParameters": {"location": {"desc": "the full path to root (parent) directory"}},
+ "SickGearCommand": "sg.deleterootdir"
+ }
def __init__(self, handler, args, kwargs):
# required
# optional
# super, missing, help
- ApiCall.__init__(self, handler, args, kwargs)
-
- def run(self):
- """ force the episode search early """
- # Searching all providers for any needed episodes
- result = sickbeard.recentSearchScheduler.forceRun()
- if result:
- return _responds(RESULT_SUCCESS, msg='Episode recent search successfully forced')
- return _responds(RESULT_FAILURE, msg='Can not force the episode recent search because it\'s already active')
+ self.sickbeard_call = True
+ CMD_SickGearDeleteRootDir.__init__(self, handler, args, kwargs)
-class CMD_SickBeardGetDefaults(ApiCall):
- _help = {"desc": "get sickbeard user defaults"}
+class CMD_SickGearForceSearch(ApiCall):
+ _help = {'desc': 'force the given search type searches',
+ "requiredParameters": {"searchtype": {"desc": "type of search to be forced: recent, backlog, proper"}}
+ }
def __init__(self, handler, args, kwargs):
# required
+ self.searchtype, args = self.check_params(args, kwargs, "searchtype", "recent", True, "string",
+ ["recent", "backlog", "proper"])
# optional
# super, missing, help
ApiCall.__init__(self, handler, args, kwargs)
def run(self):
- """ get sickbeard user defaults """
+ """ force the given search type search """
+ result = None
+ if 'recent' == self.searchtype:
+ result = sickbeard.recentSearchScheduler.forceRun()
+ elif 'backlog' == self.searchtype:
+ result = sickbeard.backlogSearchScheduler.force_search(force_type=FORCED_BACKLOG)
+ elif 'proper' == self.searchtype:
+ result = sickbeard.properFinderScheduler.forceRun()
+ if result:
+ return _responds(RESULT_SUCCESS, msg='%s search successfully forced' % self.searchtype)
+ return _responds(RESULT_FAILURE,
+ msg='Can not force the %s search because it\'s already active' % self.searchtype)
- anyQualities, bestQualities = _mapQuality(sickbeard.QUALITY_DEFAULT)
- data = {"status": statusStrings[sickbeard.STATUS_DEFAULT].lower(),
+class CMD_SickBeardForceSearch(CMD_SickGearForceSearch):
+ _help = {'desc': 'force the episode recent search early',
+ "SickGearCommand": "sg.forcesearch",}
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ kwargs['searchtype'] = 'recent'
+ self.sickbeard_call = True
+ CMD_SickGearForceSearch.__init__(self, handler, args, kwargs)
+
+
+class CMD_SickGearSearchQueue(ApiCall):
+ _help = {'desc': 'list sickgear\'s search queue'}
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ ApiCall.__init__(self, handler, args, kwargs)
+
+ def run(self):
+ """ list sickgear's search queue """
+ return _responds(RESULT_SUCCESS, sickbeard.searchQueueScheduler.action.queue_length())
+
+
+class CMD_SickGearGetDefaults(ApiCall):
+ _help = {"desc": "get sickgear user defaults"}
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ ApiCall.__init__(self, handler, args, kwargs)
+
+ def run(self):
+ """ get sickgear user defaults """
+
+ anyQualities, bestQualities = _mapQuality(sickbeard.QUALITY_DEFAULT)
+
+ data = {"status": statusStrings[sickbeard.STATUS_DEFAULT].lower(),
"flatten_folders": int(sickbeard.FLATTEN_FOLDERS_DEFAULT), "initial": anyQualities,
- "archive": bestQualities, "future_show_paused": int(sickbeard.EPISODE_VIEW_DISPLAY_PAUSED)}
+ "archive": bestQualities, "future_show_paused": int(sickgear.EPISODE_VIEW_DISPLAY_PAUSED)}
return _responds(RESULT_SUCCESS, data)
-class CMD_SickBeardGetMessages(ApiCall):
+class CMD_SickBeardGetDefaults(CMD_SickGearGetDefaults):
+ _help = {"desc": "get sickgear user defaults",
+ "SickGearCommand": "sg.getdefaults"}
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ self.sickbeard_call = True
+ CMD_SickGearGetDefaults.__init__(self, handler, args, kwargs)
+
+
+class CMD_SickGearGetMessages(ApiCall):
_help = {"desc": "get all messages"}
def __init__(self, handler, args, kwargs):
@@ -1539,8 +2096,115 @@ class CMD_SickBeardGetMessages(ApiCall):
return _responds(RESULT_SUCCESS, messages)
-class CMD_SickBeardGetRootDirs(ApiCall):
- _help = {"desc": "get sickbeard user parent directories"}
+class CMD_SickBeardGetMessages(CMD_SickGearGetMessages):
+ _help = {"desc": "get all messages",
+ "SickGearCommand": "sg.getmessages"}
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ self.sickbeard_call = True
+ CMD_SickGearGetMessages.__init__(self, handler, args, kwargs)
+
+
+class CMD_SickGearGetQualities(ApiCall):
+ _help = {"desc": "get all qualities"}
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ ApiCall.__init__(self, handler, args, kwargs)
+
+ def run(self):
+ return _responds(RESULT_SUCCESS, quality_map)
+
+
+class CMD_SickGearGetIndexers(ApiCall):
+ _help = {"desc": "get indexer list",
+ "optionalParameters": {"searchable-only ": {"desc": "searchable indexers only"}}}
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ self.searchable_only, args = self.check_params(args, kwargs, "searchable-only", False, False, "bool", [])
+ # super, missing, help
+ ApiCall.__init__(self, handler, args, kwargs)
+
+ def run(self):
+ result = {}
+ for i in indexer_config.indexerConfig:
+ for d, v in indexer_config.indexerConfig[i].iteritems():
+ if self.searchable_only and (indexer_config.indexerConfig[i].get('mapped_only') or
+ not indexer_config.indexerConfig[i].get('active') or
+ indexer_config.indexerConfig[i].get('defunct')):
+ continue
+ if d in ['id', 'name', 'show_url', 'mapped_only', 'main_url'] and \
+ isinstance(v, (basestring, tuple, dict, list, int, long, float, bool)):
+ if 'mapped_only' == d:
+ key = 'searchable'
+ val = not v and indexer_config.indexerConfig[i].get('active') and \
+ not indexer_config.indexerConfig[i].get('defunct')
+ else:
+ key = d
+ val = (v, '%s{INDEXER-ID}' % v)['show_url' == d]
+ result.setdefault(i, {}).update({key: val})
+ return _responds(RESULT_SUCCESS, result)
+
+
+class CMD_SickGearGetIndexerIcon(ApiCall):
+ _help = {"desc": "get indexer icon",
+ "requiredParameters": {"indexer": {"desc": "indexer"},
+ },
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int",
+ [i for i in indexer_api.indexerApi().all_indexers])
+ # optional
+ # super, missing, help
+ ApiCall.__init__(self, handler, args, kwargs)
+
+ def run(self):
+ # doesn't work
+ i = indexer_config.indexerConfig.get(self.indexer)
+ if not i:
+ self.handler.set_status(404)
+ return _responds(RESULT_FAILURE, 'Icon not found')
+ img = i['icon']
+ image = ek.ek(os.path.join, sickbeard.PROG_DIR, 'gui', 'slick', 'images', img)
+ if not ek.ek(os.path.isfile, image):
+ self.handler.set_status(404)
+ return _responds(RESULT_FAILURE, 'Icon not found')
+ return {'outputType': 'image', 'image': self.handler.getImage(image)}
+
+
+class CMD_SickGearGetNetworkIcon(ApiCall):
+ _help = {"desc": "get network icon",
+ "requiredParameters": {"network": {"desc": "name of network"},
+ },
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ self.network, args = self.check_params(args, kwargs, "network", None, True, "string", [])
+ # optional
+ # super, missing, help
+ ApiCall.__init__(self, handler, args, kwargs)
+
+ def run(self):
+ image = ek.ek(os.path.join, sickbeard.PROG_DIR, 'gui', 'slick', 'images', 'network',
+ '%s.png' % self.network.lower())
+ if not ek.ek(os.path.isfile, image):
+ self.handler.set_status(404)
+ return _responds(RESULT_FAILURE, 'Icon not found')
+ return {'outputType': 'image', 'image': self.handler.getImage(image)}
+
+
+class CMD_SickGearGetqualityStrings(ApiCall):
+ _help = {"desc": "get human readable quality strings"}
def __init__(self, handler, args, kwargs):
# required
@@ -1549,15 +2213,40 @@ class CMD_SickBeardGetRootDirs(ApiCall):
ApiCall.__init__(self, handler, args, kwargs)
def run(self):
- """ get the parent directories defined in sickbeard's config """
+ return _responds(RESULT_SUCCESS, Quality.qualityStrings)
+
+
+class CMD_SickGearGetRootDirs(ApiCall):
+ _help = {"desc": "get sickgear user parent directories"}
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ ApiCall.__init__(self, handler, args, kwargs)
+
+ def run(self):
+ """ get the parent directories defined in sickgear's config """
return _responds(RESULT_SUCCESS, _getRootDirs())
-class CMD_SickBeardPauseBacklog(ApiCall):
+class CMD_SickBeardGetRootDirs(CMD_SickGearGetRootDirs):
+ _help = {"desc": "get sickgear user parent directories",
+ "SickGearCommand": "sg.getrootdirs"}
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ self.sickbeard_call = True
+ CMD_SickGearGetRootDirs.__init__(self, handler, args, kwargs)
+
+
+class CMD_SickGearPauseBacklog(ApiCall):
_help = {"desc": "pause the backlog search",
"optionalParameters": {"pause ": {"desc": "pause or unpause the global backlog"}}
- }
+ }
def __init__(self, handler, args, kwargs):
# required
@@ -1576,8 +2265,22 @@ class CMD_SickBeardPauseBacklog(ApiCall):
return _responds(RESULT_SUCCESS, msg="Backlog unpaused")
-class CMD_SickBeardPing(ApiCall):
- _help = {"desc": "check to see if sickbeard is running"}
+class CMD_SickBeardPauseBacklog(CMD_SickGearPauseBacklog):
+ _help = {"desc": "pause the backlog search",
+ "optionalParameters": {"pause ": {"desc": "pause or unpause the global backlog"}},
+ "SickGearCommand": "sg.pausebacklog"
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ self.sickbeard_call = True
+ CMD_SickGearPauseBacklog.__init__(self, handler, args, kwargs)
+
+
+class CMD_SickGearPing(ApiCall):
+ _help = {"desc": "check to see if sickgear is running",}
def __init__(self, handler, args, kwargs):
# required
@@ -1586,7 +2289,7 @@ class CMD_SickBeardPing(ApiCall):
ApiCall.__init__(self, handler, args, kwargs)
def run(self):
- """ check to see if sickbeard is running """
+ """ check to see if sickgear is running """
self.handler.set_header('Cache-Control', "max-age=0,no-cache,no-store")
if sickbeard.started:
return _responds(RESULT_SUCCESS, {"pid": sickbeard.PID}, "Pong")
@@ -1594,8 +2297,20 @@ class CMD_SickBeardPing(ApiCall):
return _responds(RESULT_SUCCESS, msg="Pong")
-class CMD_SickBeardRestart(ApiCall):
- _help = {"desc": "restart sickbeard"}
+class CMD_SickBeardPing(CMD_SickGearPing):
+ _help = {"desc": "check to see if sickgear is running",
+ "SickGearCommand": "sg.ping"}
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ self.sickbeard_call = True
+ CMD_SickGearPing.__init__(self, handler, args, kwargs)
+
+
+class CMD_SickGearRestart(ApiCall):
+ _help = {"desc": "restart sickgear"}
def __init__(self, handler, args, kwargs):
# required
@@ -1604,16 +2319,29 @@ class CMD_SickBeardRestart(ApiCall):
ApiCall.__init__(self, handler, args, kwargs)
def run(self):
- """ restart sickbeard """
+ """ restart sickgear """
sickbeard.events.put(sickbeard.events.SystemEvent.RESTART)
return _responds(RESULT_SUCCESS, msg="SickGear is restarting...")
-class CMD_SickBeardSearchIndexers(ApiCall):
+class CMD_SickBeardRestart(CMD_SickGearRestart):
+ _help = {"desc": "restart sickgear",
+ "SickGearCommand": "sg.restart"}
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ self.sickbeard_call = True
+ CMD_SickGearRestart.__init__(self, handler, args, kwargs)
+
+
+class CMD_SickGearSearchIndexers(ApiCall):
_help = {"desc": "search for show on the indexers with a given string and language",
"optionalParameters": {"name": {"desc": "name of the show you want to search for"},
"indexerid": {"desc": "thetvdb.com or tvrage.com unique id of a show"},
- "lang": {"desc": "the 2 letter abbreviation lang id"}
+ "lang": {"desc": "the 2 letter abbreviation lang id"},
+ "indexer": {"desc": "indexer to search, use -1 to search all indexers"}
}
}
@@ -1628,54 +2356,74 @@ class CMD_SickBeardSearchIndexers(ApiCall):
# optional
self.name, args = self.check_params(args, kwargs, "name", None, False, "string", [])
self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, False, "int", [])
- self.lang, args = self.check_params(args, kwargs, "lang", "en", False, "string", self.valid_languages.keys())
- self.indexer, args = self.check_params(args, kwargs, "indexer", 1, False, "int", [])
+ # self.lang, args = self.check_params(args, kwargs, "lang", "en", False, "string", self.valid_languages.keys())
+ self.indexers, args = self.check_params(args, kwargs, "indexers", -1, False, "list",
+ [-1] + [i for i in indexer_api.indexerApi().search_indexers], int)
# super, missing, help
ApiCall.__init__(self, handler, args, kwargs)
def run(self):
- """ search for show at tvdb with a given string and language """
- if self.name and not self.indexerid: # only name was given
- lINDEXER_API_PARMS = sickbeard.indexerApi(self.indexer).api_params.copy()
- lINDEXER_API_PARMS['language'] = self.lang
- lINDEXER_API_PARMS['custom_ui'] = classes.AllShowsListUI
- t = sickbeard.indexerApi(self.indexer).indexer(**lINDEXER_API_PARMS)
+ """ search for show at indexers with a given string and language """
+ if 1 > len(self.indexers) and -1 in self.indexers:
+ raise ApiError('Mix of -1 (all Indexer) and specific Indexer not allowed')
- apiData = None
+ all_indexer = 1 == len(self.indexers) and -1 == self.indexers[0]
- try:
- apiData = t[str(self.name).encode()]
- except Exception as e:
- pass
+ if self.name and not self.indexerid: # only name was given
+ results = []
+ indexertosearch = (self.indexers, [i for i in indexer_api.indexerApi().indexers if
+ indexer_api.indexerApi(i).config.get('active') and
+ not indexer_api.indexerApi(i).config.get('mapped_only') and
+ not indexer_api.indexerApi(i).config.get('defunct')])[all_indexer]
+ for i in indexertosearch:
+ lINDEXER_API_PARMS = sickbeard.indexerApi(i).api_params.copy()
+ lINDEXER_API_PARMS['language'] = 'en'
+ lINDEXER_API_PARMS['custom_ui'] = classes.AllShowsNoFilterListUI
+ t = sickbeard.indexerApi(i).indexer(**lINDEXER_API_PARMS)
+
+ apiData = None
- if not apiData:
- return _responds(RESULT_FAILURE, msg="Did not get result from tvdb")
+ try:
+ apiData = t[str(self.name).encode(), False]
+ except (StandardError, Exception):
+ pass
+
+ for curSeries in apiData:
+ s = {"indexerid": int(curSeries['id']),
+ "name": curSeries['seriesname'],
+ "first_aired": curSeries['firstaired'],
+ "indexer": i,
+ "aliases": curSeries.get('aliases', None),
+ "relevance": NewHomeAddShows.get_UWRatio(self.name, curSeries['seriesname'],
+ curSeries.get('aliases', None))}
+ if INDEXER_TVDB == i:
+ s["tvdbid"] = int(curSeries['id'])
+ else:
+ s["tvdbid"] = None
+ results.append(s)
- results = []
- for curSeries in apiData:
- results.append({"indexerid": int(curSeries['id']),
- "tvdbid": int(curSeries['id']),
- "name": curSeries['seriesname'],
- "first_aired": curSeries['firstaired'],
- "indexer": self.indexer})
+ if not results:
+ return _responds(RESULT_FAILURE, msg="Did not get result from %s" %
+ ', '.join([sickbeard.indexerApi(i).name for i in indexertosearch]))
+
+ results = sorted(results, key=lambda x: x['relevance'], reverse=True)
- lang_id = self.valid_languages[self.lang]
- return _responds(RESULT_SUCCESS, {"results": results, "langid": lang_id})
+ return _responds(RESULT_SUCCESS, {"results": results, "langid": 'en'})
- elif self.indexerid:
- lINDEXER_API_PARMS = sickbeard.indexerApi(self.indexer).api_params.copy()
+ elif self.indexerid and not all_indexer and 1 == len(self.indexers):
+ lINDEXER_API_PARMS = sickbeard.indexerApi(self.indexers[0]).api_params.copy()
- lang_id = self.valid_languages[self.lang]
- if self.lang and not self.lang == 'en':
- lINDEXER_API_PARMS['language'] = self.lang
+ lang_id = 'en'
+ lINDEXER_API_PARMS['language'] = 'en'
+ lINDEXER_API_PARMS['custom_ui'] = classes.AllShowsNoFilterListUI
lINDEXER_API_PARMS['actors'] = False
- t = sickbeard.indexerApi(self.indexer).indexer(**lINDEXER_API_PARMS)
+ t = sickbeard.indexerApi(self.indexers[0]).indexer(**lINDEXER_API_PARMS)
try:
- myShow = t[int(self.indexerid)]
+ myShow = t[int(self.indexerid), False]
except (sickbeard.indexer_shownotfound, sickbeard.indexer_error):
logger.log(u"API :: Unable to find show with id " + str(self.indexerid), logger.WARNING)
return _responds(RESULT_SUCCESS, {"results": [], "langid": lang_id})
@@ -1687,17 +2435,42 @@ class CMD_SickBeardSearchIndexers(ApiCall):
return _responds(RESULT_FAILURE, msg="Show contains no name, invalid result")
showOut = [{"indexerid": self.indexerid,
- "tvdbid": self.indexerid,
+ "indexer": self.indexers[0],
"name": unicode(myShow.data['seriesname']),
- "first_aired": myShow.data['firstaired']}]
+ "first_aired": myShow.data['firstaired'],
+ "aliases": myShow.data.get('aliases', None),
+ "relevance": NewHomeAddShows.get_UWRatio(self.name, myShow.data['seriesname'],
+ myShow.data.get('aliases', None))}]
+
+ if INDEXER_TVDB == self.indexers[0]:
+ showOut[0]["tvdbid"] = int(myShow.data['id'])
+ else:
+ showOut[0]["tvdbid"] = None
+ showOut = sorted(showOut, key=lambda x: x['relevance'], reverse=True)
return _responds(RESULT_SUCCESS, {"results": showOut, "langid": lang_id})
else:
- return _responds(RESULT_FAILURE, msg="Either indexerid or name is required")
+ return _responds(RESULT_FAILURE, msg="Either indexer + indexerid or name is required")
+
+
+class CMD_SickBeardSearchIndexers(CMD_SickGearSearchIndexers):
+ _help = {"desc": "search for show on the tvdb with a given string and language",
+ "optionalParameters": {"name": {"desc": "name of the show you want to search for"},
+ "indexerid": {"desc": "thetvdb.com unique id of a show"},
+ "lang": {"desc": "the 2 letter abbreviation lang id"},
+ },
+ "SickGearCommand": "sg.searchtv",
+ }
+
+ def __init__(self, handler, args, kwargs):
+ kwargs['indexers'] = INDEXER_TVDB
+ # super, missing, help
+ self.sickbeard_call = True
+ CMD_SickGearSearchIndexers.__init__(self, handler, args, kwargs)
class CMD_SickBeardSetDefaults(ApiCall):
- _help = {"desc": "set sickbeard user defaults",
+ _help = {"desc": "set sickgear user defaults",
"optionalParameters": {"initial": {"desc": "initial quality for the show"},
"archive": {"desc": "archive quality for the show"},
"flatten_folders": {"desc": "flatten subfolders within the show directory"},
@@ -1708,12 +2481,8 @@ class CMD_SickBeardSetDefaults(ApiCall):
def __init__(self, handler, args, kwargs):
# required
# optional
- self.initial, args = self.check_params(args, kwargs, "initial", None, False, "list",
- ["sdtv", "sddvd", "hdtv", "rawhdtv", "fullhdtv", "hdwebdl",
- "fullhdwebdl", "hdbluray", "fullhdbluray", "unknown"])
- self.archive, args = self.check_params(args, kwargs, "archive", None, False, "list",
- ["sddvd", "hdtv", "rawhdtv", "fullhdtv", "hdwebdl", "fullhdwebdl",
- "hdbluray", "fullhdbluray"])
+ self.initial, args = self.check_params(args, kwargs, "initial", None, False, "list", [q for q in quality_map])
+ self.archive, args = self.check_params(args, kwargs, "archive", None, False, "list", [q for q in quality_map])
self.future_show_paused, args = self.check_params(args, kwargs, "future_show_paused", None, False, "bool", [])
self.flatten_folders, args = self.check_params(args, kwargs, "flatten_folders", None, False, "bool", [])
self.status, args = self.check_params(args, kwargs, "status", None, False, "string",
@@ -1722,18 +2491,7 @@ class CMD_SickBeardSetDefaults(ApiCall):
ApiCall.__init__(self, handler, args, kwargs)
def run(self):
- """ set sickbeard user defaults """
-
- quality_map = {'sdtv': Quality.SDTV,
- 'sddvd': Quality.SDDVD,
- 'hdtv': Quality.HDTV,
- 'rawhdtv': Quality.RAWHDTV,
- 'fullhdtv': Quality.FULLHDTV,
- 'hdwebdl': Quality.HDWEBDL,
- 'fullhdwebdl': Quality.FULLHDWEBDL,
- 'hdbluray': Quality.HDBLURAY,
- 'fullhdbluray': Quality.FULLHDBLURAY,
- 'unknown': Quality.UNKNOWN}
+ """ set sickgear user defaults """
iqualityID = []
aqualityID = []
@@ -1771,8 +2529,81 @@ class CMD_SickBeardSetDefaults(ApiCall):
return _responds(RESULT_SUCCESS, msg="Saved defaults")
+class CMD_SickGearSetSceneNumber(ApiCall):
+ _help = {"desc": "set Scene Numbers",
+ "requiredParameters": {"indexerid": {"desc": "unique id of a show"},
+ "indexer": {"desc": "indexer of a show"},
+ },
+ "optionalParameters": {"forSeason": {"desc": "season number of a show"},
+ "forEpisode": {"desc": "episode number of a show"},
+ "forAbsolute": {"desc": "absolute episode number of a show"},
+ "sceneSeason": {"desc": "scene season number of a show to set"},
+ "sceneEpisode": {"desc": "scene episode number of a show to set"},
+ "sceneAbsolute": {"desc": "scene absolute episode number of a show to set"},
+ }
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
+ self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int",
+ [i for i in indexer_api.indexerApi().indexers])
+ # optional
+ self.forSeason, args = self.check_params(args, kwargs, "forSeason", None, False, "int", [])
+ self.forEpisode, args = self.check_params(args, kwargs, "forEpisode", None, False, "int", [])
+ self.forAbsolute, args = self.check_params(args, kwargs, "forAbsolute", None, False, "int", [])
+ self.sceneSeason, args = self.check_params(args, kwargs, "sceneSeason", None, False, "int", [])
+ self.sceneEpisode, args = self.check_params(args, kwargs, "sceneEpisode", None, False, "int", [])
+ self.sceneAbsolute, args = self.check_params(args, kwargs, "sceneAbsolute", None, False, "int", [])
+ # super, missing, help
+ ApiCall.__init__(self, handler, args, kwargs)
+
+ def run(self):
+ """ saving scene numbers """
+
+ result = set_scene_numbering_helper(self.indexerid, self.indexer, self.forSeason, self.forEpisode,
+ self.forAbsolute, self.sceneSeason, self.sceneEpisode, self.sceneEpisode)
+
+ if not result['success']:
+ return _responds(RESULT_FAILURE, result)
+
+ return _responds(RESULT_SUCCESS, result)
+
+
+class CMD_SickGearActivateSceneNumber(ApiCall):
+ _help = {"desc": "De-/Activate Scene Numbers",
+ "requiredParameters": {"indexerid": {"desc": "unique id of a show"},
+ "indexer": {"desc": "indexer of a show"},
+ "activate": {"desc": "de-/activate scene numbering"}},
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
+ self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int",
+ [i for i in indexer_api.indexerApi().indexers])
+ self.activate, args = self.check_params(args, kwargs, "activate", None, True, "bool", [])
+ # optional
+ # super, missing, help
+ ApiCall.__init__(self, handler, args, kwargs)
+
+ def run(self):
+ """ de-/activate scene numbers """
+
+ showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid})
+ if not showObj:
+ return _responds(RESULT_FAILURE, msg="Can't find show")
+
+ showObj.scene = int(self.activate)
+ showObj.saveToDB()
+
+ return _responds(RESULT_SUCCESS, data={'indexer': self.indexer, 'indexerid': self.indexerid,
+ 'show_name': showObj.name, 'scenenumbering': showObj.is_scene},
+ msg="Scene Numbering %sactivated" % ('de', '')[self.activate])
+
+
class CMD_SickBeardShutdown(ApiCall):
- _help = {"desc": "shutdown sickbeard"}
+ _help = {"desc": "shutdown sickgear"}
def __init__(self, handler, args, kwargs):
# required
@@ -1781,27 +2612,273 @@ class CMD_SickBeardShutdown(ApiCall):
ApiCall.__init__(self, handler, args, kwargs)
def run(self):
- """ shutdown sickbeard """
+ """ shutdown sickgear """
sickbeard.events.put(sickbeard.events.SystemEvent.SHUTDOWN)
return _responds(RESULT_SUCCESS, msg="SickGear is shutting down...")
-class CMD_Show(ApiCall):
+class CMD_SickGearListIgnoreWords(ApiCall):
+ _help = {"desc": "list ignore words",
+ "optionalParameters": {"indexerid": {"desc": "unique id of a show"},
+ "indexer": {"desc": "indexer of a show"},
+ }
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, False, "int", [])
+ self.indexer, args = self.check_params(args, kwargs, "indexer", None, False, "int",
+ [i for i in indexer_api.indexerApi().indexers])
+ # super, missing, help
+ ApiCall.__init__(self, handler, args, kwargs)
+
+ def run(self):
+ """ list ignore words """
+ if self.indexer and self.indexerid:
+ myDB = db.DBConnection()
+ sqlResults = myDB.select('SELECT show_name, rls_ignore_words FROM tv_shows WHERE indexer = ? AND '
+ 'indexer_id = ?', [self.indexer, self.indexerid])
+ if sqlResults:
+ ignore_words = sqlResults[0]['rls_ignore_words']
+ return_data = {'type': 'show', 'indexer': self.indexer, 'indexerid': self.indexerid,
+ 'show name': sqlResults[0]['show_name']}
+ return_type = '%s:' % sqlResults[0]['show_name']
+ else:
+ return _responds(RESULT_FAILURE, msg='Show not found.')
+ elif (None is self.indexer) != (None is self.indexerid):
+ return _responds(RESULT_FAILURE, msg='You must supply indexer + indexerid.')
+ else:
+ ignore_words = sickbeard.IGNORE_WORDS
+ return_data = {'type': 'global'}
+ return_type = 'Global'
+
+ return_data['use regex'] = ignore_words.startswith('regex:')
+ return_data['ignore words'] = [w.strip() for w in ignore_words.replace('regex:', '').split(',')]
+ return _responds(RESULT_SUCCESS, data=return_data, msg="%s ignore words" % return_type)
+
+
+class CMD_SickGearSetIgnoreWords(ApiCall):
+ _help = {"desc": "set ignore words",
+ "optionalParameters": {"indexerid": {"desc": "unique id of a show"},
+ "indexer": {"desc": "indexer of a show"},
+ "add": {"desc": "add words to list"},
+ "remove": {"desc": "remove words from list"},
+ "regex": {"desc": "interpret ALL (including existing) ignore words as regex"},
+ }
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, False, "int", [])
+ self.indexer, args = self.check_params(args, kwargs, "indexer", None, False, "int",
+ [i for i in indexer_api.indexerApi().indexers])
+ self.add, args = self.check_params(args, kwargs, "add", None, False, "list", [])
+ self.remove, args = self.check_params(args, kwargs, "remove", None, False, "list", [])
+ self.regex, args = self.check_params(args, kwargs, "regex", None, False, "bool", [])
+ # super, missing, help
+ ApiCall.__init__(self, handler, args, kwargs)
+
+ def run(self):
+ """ set ignore words """
+ if not self.add and not self.remove:
+ return _responds(RESULT_FAILURE, msg="No words to add/remove provided")
+
+ def _create_ignore_words():
+ use_regex = ignore_words.startswith('regex:')
+ ignore_list = [w.strip() for w in ignore_words.replace('regex:', '').split(',')]
+
+ if None is not self.regex:
+ use_regex = self.regex
+ if self.add:
+ for a in self.add:
+ ignore_list.append(a)
+ if self.remove:
+ for r in self.remove:
+ try:
+ ignore_list.remove(r)
+ except ValueError:
+ pass
+ return use_regex, ignore_list, '%s%s' % (('', 'regex:')[use_regex], ', '.join(ignore_list))
+
+ if self.indexer and self.indexerid:
+ showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid})
+ if not showObj:
+ return _responds(RESULT_FAILURE, msg="Show not found")
+
+ myDB = db.DBConnection()
+ sqlResults = myDB.select('SELECT show_name, rls_ignore_words FROM tv_shows WHERE indexer = ? AND '
+ 'indexer_id = ?', [self.indexer, self.indexerid])
+
+ ignore_words = ''
+ if sqlResults:
+ ignore_words = sqlResults[0]['rls_ignore_words']
+
+ return_data = {'type': 'show', 'indexer': self.indexer, 'indexerid': self.indexerid,
+ 'show name': sqlResults[0]['show_name']}
+ return_type = '%s:' % sqlResults[0]['show_name']
+
+ use_regex, ignore_list, new_ignore_words = _create_ignore_words()
+ myDB.action('UPDATE tv_shows SET rls_ignore_words = ? WHERE indexer = ? AND indexer_id = ?',
+ [new_ignore_words, self.indexer, self.indexerid])
+ elif (None is self.indexer) != (None is self.indexerid):
+ return _responds(RESULT_FAILURE, msg='You must supply indexer + indexerid.')
+ else:
+ ignore_words = sickbeard.IGNORE_WORDS
+ use_regex, ignore_list, new_ignore_words = _create_ignore_words()
+ sickbeard.IGNORE_WORDS = new_ignore_words
+ sickbeard.save_config()
+ return_data = {'type': 'global'}
+ return_type = 'Global'
+
+ return_data['use regex'] = use_regex
+ return_data['ignore words'] = ignore_list
+ return _responds(RESULT_SUCCESS, data=return_data, msg="%s set ignore words" % return_type)
+
+
+class CMD_SickGearListRequireWords(ApiCall):
+ _help = {"desc": "list requried words",
+ "optionalParameters": {"indexerid": {"desc": "unique id of a show"},
+ "indexer": {"desc": "indexer of a show"},
+ }
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, False, "int", [])
+ self.indexer, args = self.check_params(args, kwargs, "indexer", None, False, "int",
+ [i for i in indexer_api.indexerApi().indexers])
+ # super, missing, help
+ ApiCall.__init__(self, handler, args, kwargs)
+
+ def run(self):
+ """ list required words """
+ if self.indexer and self.indexerid:
+ myDB = db.DBConnection()
+ sqlResults = myDB.select('SELECT show_name, rls_require_words FROM tv_shows WHERE indexer = ? '
+ 'AND indexer_id = ?', [self.indexer, self.indexerid])
+ if sqlResults:
+ required_words = sqlResults[0]['rls_require_words']
+ return_data = {'type': 'show', 'indexer': self.indexer, 'indexerid': self.indexerid,
+ 'show name': sqlResults[0]['show_name']}
+ return_type = '%s:' % sqlResults[0]['show_name']
+ else:
+ return _responds(RESULT_FAILURE, msg='Show not found.')
+ elif (None is self.indexer) != (None is self.indexerid):
+ return _responds(RESULT_FAILURE, msg='You must supply indexer + indexerid.')
+ else:
+ required_words = sickbeard.REQUIRE_WORDS
+ return_data = {'type': 'global'}
+ return_type = 'Global'
+
+ return_data['use regex'] = required_words.startswith('regex:')
+ return_data['required words'] = [w.strip() for w in required_words.replace('regex:', '').split(',')]
+ return _responds(RESULT_SUCCESS, data=return_data, msg="%s required words" % return_type)
+
+
+class CMD_SickGearSetRequrieWords(ApiCall):
+ _help = {"desc": "set required words",
+ "optionalParameters": {"indexerid": {"desc": "unique id of a show"},
+ "indexer": {"desc": "indexer of a show"},
+ "add": {"desc": "add words to list"},
+ "remove": {"desc": "remove words from list"},
+ "regex": {"desc": "interpret ALL (including existing) ignore words as regex"},
+ }
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, False, "int", [])
+ self.indexer, args = self.check_params(args, kwargs, "indexer", None, False, "int",
+ [i for i in indexer_api.indexerApi().indexers])
+ self.add, args = self.check_params(args, kwargs, "add", None, False, "list", [])
+ self.remove, args = self.check_params(args, kwargs, "remove", None, False, "list", [])
+ self.regex, args = self.check_params(args, kwargs, "regex", None, False, "bool", [])
+ # super, missing, help
+ ApiCall.__init__(self, handler, args, kwargs)
+
+ def run(self):
+ """ set ignore words """
+ if not self.add and not self.remove:
+ return _responds(RESULT_FAILURE, msg="No words to add/remove provided")
+
+ def _create_required_words():
+ use_regex = requried_words.startswith('regex:')
+ require_list = [w.strip() for w in requried_words.replace('regex:', '').split(',')]
+
+ if None is not self.regex:
+ use_regex = self.regex
+ if self.add:
+ for a in self.add:
+ require_list.append(a)
+ if self.remove:
+ for r in self.remove:
+ try:
+ require_list.remove(r)
+ except ValueError:
+ pass
+ return use_regex, require_list, '%s%s' % (('', 'regex:')[use_regex], ', '.join(require_list))
+
+ if self.indexer and self.indexerid:
+ showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid})
+ if not showObj:
+ return _responds(RESULT_FAILURE, msg="Show not found")
+
+ myDB = db.DBConnection()
+ sqlResults = myDB.select('SELECT show_name, rls_require_words FROM tv_shows WHERE indexer = ? AND '
+ 'indexer_id = ?', [self.indexer, self.indexerid])
+
+ requried_words = ''
+ if sqlResults:
+ requried_words = sqlResults[0]['rls_require_words']
+
+ return_data = {'type': 'show', 'indexer': self.indexer, 'indexerid': self.indexerid,
+ 'show name': sqlResults[0]['show_name']}
+ return_type = '%s:' % sqlResults[0]['show_name']
+
+ use_regex, required_list, new_required_words = _create_required_words()
+ myDB.action('UPDATE tv_shows SET rls_require_words = ? WHERE indexer = ? AND indexer_id = ?',
+ [new_required_words, self.indexer, self.indexerid])
+ elif (None is self.indexer) != (None is self.indexerid):
+ return _responds(RESULT_FAILURE, msg='You must supply indexer + indexerid.')
+ else:
+ requried_words = sickbeard.REQUIRE_WORDS
+ use_regex, required_list, new_required_words = _create_required_words()
+ sickbeard.REQUIRE_WORDS = new_required_words
+ sickbeard.save_config()
+ return_data = {'type': 'global'}
+ return_type = 'Global'
+
+ return_data['use regex'] = use_regex
+ return_data['required words'] = required_list
+ return _responds(RESULT_SUCCESS, data=return_data, msg="%s set requried words" % return_type)
+
+
+class CMD_SickGearShow(ApiCall):
_help = {"desc": "display information for a given show",
- "requiredParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"},
+ "requiredParameters": {"indexer": {"desc": "indexer of a show"},
+ "indexerid": {"desc": "unique id of a show"},
+ },
+ "optionalParameters": {"overview": {"desc": "include overview"},
+ },
}
- }
def __init__(self, handler, args, kwargs):
# required
+ self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int",
+ [i for i in indexer_api.indexerApi().indexers])
self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
# optional
+ self.overview, args = self.check_params(args, kwargs, "overview", False, False, "bool", [])
# super, missing, help
ApiCall.__init__(self, handler, args, kwargs)
def run(self):
""" display information for a given show """
- showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid))
+ showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid})
if not showObj:
return _responds(RESULT_FAILURE, msg="Show not found")
@@ -1838,15 +2915,31 @@ class CMD_Show(ApiCall):
showDict["airs"] = str(showObj.airs).replace('am', ' AM').replace('pm', ' PM').replace(' ', ' ')
showDict["indexerid"] = self.indexerid
showDict["tvrage_id"] = showObj.ids.get(INDEXER_TVRAGE, {'id': 0})['id']
+ showDict['ids'] = {k: v.get('id') for k, v in showObj.ids.iteritems()}
showDict["tvrage_name"] = showObj.name
showDict["network"] = showObj.network
if not showDict["network"]:
showDict["network"] = ""
showDict["status"] = showObj.status
+ showDict["scenenumbering"] = showObj.is_scene
+ showDict["archivefirstmatch"] = showObj.upgrade_once
+ showDict["irgnorewords"] = showObj.rls_ignore_words
+ showDict["requirewords"] = showObj.rls_require_words
+ if self.overview:
+ showDict["overview"] = showObj.overview
+ showDict["tag"] = showObj.tag
+ showDict["imdb_id"] = showObj.imdbid
+ showDict["classification"] = showObj.classification
+ showDict["runtime"] = showObj.runtime
+ showDict["startyear"] = showObj.startyear
+ showDict["indexer"] = showObj.indexer
+ timezone, showDict['timezone'] = network_timezones.get_network_timezone(showDict['network'], return_name=True)
if showObj.nextaired:
- dtEpisodeAirs = sbdatetime.sbdatetime.convert_to_setting(network_timezones.parse_date_time(showObj.nextaired, showDict['airs'], showDict['network']))
- showDict['airs'] = sbdatetime.sbdatetime.sbftime(dtEpisodeAirs, t_preset=timeFormat).lstrip('0').replace(' 0', ' ')
+ dtEpisodeAirs = sbdatetime.sbdatetime.convert_to_setting(
+ network_timezones.parse_date_time(showObj.nextaired, showDict['airs'], timezone))
+ showDict['airs'] = sbdatetime.sbdatetime.sbftime(dtEpisodeAirs,
+ t_preset=timeFormat).lstrip('0').replace(' 0', ' ')
showDict['next_ep_airdate'] = sbdatetime.sbdatetime.sbfdate(dtEpisodeAirs, d_preset=dateFormat)
else:
showDict['next_ep_airdate'] = ''
@@ -1854,39 +2947,49 @@ class CMD_Show(ApiCall):
return _responds(RESULT_SUCCESS, showDict)
-class CMD_ShowAddExisting(ApiCall):
- _help = {"desc": "add a show in sickbeard with an existing folder",
- "requiredParameters": {"tvdbid or tvrageid": {"desc": "thetvdb.com or tvrage.com id"},
+class CMD_Show(CMD_SickGearShow):
+ _help = {"desc": "display information for a given thetvdb.com show",
+ "requiredParameters": {"indexerid": {"desc": "thetvdb.com id of a show"},
+ },
+ "optionalParameters": {"overview": {"desc": "include overview"},
+ },
+ "SickGearCommand": "sg.show",
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ kwargs['indexer'] = INDEXER_TVDB
+ self.sickbeard_call = True
+ CMD_SickGearShow.__init__(self, handler, args, kwargs)
+
+
+class CMD_SickGearShowAddExisting(ApiCall):
+ _help = {"desc": "add a show in SickGear with an existing folder",
+ "requiredParameters": {"indexer": {"desc": "indexer of a show"},
+ "indexerid": {"desc": "indexer id of a show"},
"location": {"desc": "full path to the existing folder for the show"}
},
"optionalParameters": {"initial": {"desc": "initial quality for the show"},
"archive": {"desc": "archive quality for the show"},
+ "upgrade_once": {"desc": "upgrade only once"},
"flatten_folders": {"desc": "flatten subfolders for the show"},
"subtitles": {"desc": "allow search episode subtitle"}
}
}
def __init__(self, handler, args, kwargs):
- if "tvdbid" in args or "tvdbid" in kwargs:
- _INDEXER_INT = 1
- _INDEXER = "tvdbid"
- elif "tvrageid" in args or "tvrageid" in kwargs:
- _INDEXER_INT = 2
- _INDEXER = "tvrageid"
- else:
- _INDEXER_INT = None
- _INDEXER = None
# required
- self.indexerid, args = self.check_params(args, kwargs, _INDEXER, None, True, "int", [])
- self.indexer = _INDEXER_INT
+ self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
+ self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int",
+ [i for i in indexer_api.indexerApi().search_indexers])
self.location, args = self.check_params(args, kwargs, "location", None, True, "string", [])
# optional
- self.initial, args = self.check_params(args, kwargs, "initial", None, False, "list",
- ["sdtv", "sddvd", "hdtv", "rawhdtv", "fullhdtv", "hdwebdl",
- "fullhdwebdl", "hdbluray", "fullhdbluray", "unknown"])
- self.archive, args = self.check_params(args, kwargs, "archive", None, False, "list",
- ["sddvd", "hdtv", "rawhdtv", "fullhdtv", "hdwebdl", "fullhdwebdl",
- "hdbluray", "fullhdbluray"])
+ self.initial, args = self.check_params(args, kwargs, "initial", None, False, "list", [q for q in quality_map])
+ self.archive, args = self.check_params(args, kwargs, "archive", None, False, "list", [q for q in quality_map])
+ self.upgradeonce, args = self.check_params(args, kwargs, "upgrade_once", False, False, "bool", [])
+
self.flatten_folders, args = self.check_params(args, kwargs, "flatten_folders",
str(sickbeard.FLATTEN_FOLDERS_DEFAULT), False, "bool", [])
self.subtitles, args = self.check_params(args, kwargs, "subtitles", int(sickbeard.USE_SUBTITLES), False, "int",
@@ -1895,8 +2998,8 @@ class CMD_ShowAddExisting(ApiCall):
ApiCall.__init__(self, handler, args, kwargs)
def run(self):
- """ add a show in sickbeard with an existing folder """
- showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid))
+ """ add a show in sickgear with an existing folder """
+ showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid})
if showObj:
return _responds(RESULT_FAILURE, msg="An existing indexerid already exists in the database")
@@ -1916,17 +3019,6 @@ class CMD_ShowAddExisting(ApiCall):
if not indexerName:
return _responds(RESULT_FAILURE, msg="Unable to retrieve information from indexer")
- quality_map = {'sdtv': Quality.SDTV,
- 'sddvd': Quality.SDDVD,
- 'hdtv': Quality.HDTV,
- 'rawhdtv': Quality.RAWHDTV,
- 'fullhdtv': Quality.FULLHDTV,
- 'hdwebdl': Quality.HDWEBDL,
- 'fullhdwebdl': Quality.FULLHDWEBDL,
- 'hdbluray': Quality.HDBLURAY,
- 'fullhdbluray': Quality.FULLHDBLURAY,
- 'unknown': Quality.UNKNOWN}
-
#use default quality as a failsafe
newQuality = int(sickbeard.QUALITY_DEFAULT)
iqualityID = []
@@ -1943,72 +3035,79 @@ class CMD_ShowAddExisting(ApiCall):
newQuality = Quality.combineQualities(iqualityID, aqualityID)
sickbeard.showQueueScheduler.action.addShow(int(self.indexer), int(self.indexerid), self.location, SKIPPED,
- newQuality, int(self.flatten_folders))
+ newQuality, int(self.flatten_folders),
+ upgrade_once=self.upgradeonce)
return _responds(RESULT_SUCCESS, {"name": indexerName}, indexerName + " has been queued to be added")
-class CMD_ShowAddNew(ApiCall):
- _help = {"desc": "add a new show to sickbeard",
- "requiredParameters": {"tvdbid or tvrageid": {"desc": "thetvdb.com or tvrage.com id"}
- },
+class CMD_ShowAddExisting(CMD_SickGearShowAddExisting):
+ _help = {"desc": "add a show in sickgear with an existing folder",
+ "requiredParameters": {"tvdbid": {"desc": "thetvdb.com id"},
+ "location": {"desc": "full path to the existing folder for the show"}
+ },
+ "optionalParameters": {"initial": {"desc": "initial quality for the show"},
+ "archive": {"desc": "archive quality for the show"},
+ "flatten_folders": {"desc": "flatten subfolders for the show"},
+ "subtitles": {"desc": "allow search episode subtitle"}
+ },
+ "SickGearCommand": "sg.show.addexisting",
+ }
+
+ def __init__(self, handler, args, kwargs):
+ kwargs['indexer'] = INDEXER_TVDB
+ # required
+ kwargs['indexerid'], args = self.check_params(args, kwargs, "tvdbid", None, True, "int", [])
+ # super, missing, help
+ self.sickbeard_call = True
+ CMD_SickGearShowAddExisting.__init__(self, handler, args, kwargs)
+
+
+class CMD_SickGearShowAddNew(ApiCall):
+ _help = {"desc": "add a new show to sickgear",
+ "requiredParameters": {"indexer": {"desc": "indexer of show"},
+ "indexerid": {"desc": "id of show"},
+ },
"optionalParameters": {"initial": {"desc": "initial quality for the show"},
"location": {"desc": "base path for where the show folder is to be created"},
"archive": {"desc": "archive quality for the show"},
+ "upgrade_once": {"desc": "upgrade only once"},
"flatten_folders": {"desc": "flatten subfolders for the show"},
"status": {"desc": "status of missing episodes"},
- "lang": {"desc": "the 2 letter lang abbreviation id"},
"subtitles": {"desc": "allow search episode subtitle"},
"anime": {"desc": "set show to anime"},
"scene": {"desc": "show searches episodes by scene numbering"}
- }
- }
-
- valid_languages = {
- 'el': 20, 'en': 7, 'zh': 27, 'it': 15, 'cs': 28, 'es': 16, 'ru': 22,
- 'nl': 13, 'pt': 26, 'no': 9, 'tr': 21, 'pl': 18, 'fr': 17, 'hr': 31,
- 'de': 14, 'da': 10, 'fi': 11, 'hu': 19, 'ja': 25, 'he': 24, 'ko': 32,
- 'sv': 8, 'sl': 30}
+ }
+ }
def __init__(self, handler, args, kwargs):
- if "tvdbid" in args or "tvdbid" in kwargs:
- _INDEXER_INT = 1
- _INDEXER = "tvdbid"
- elif "tvrageid" in args or "tvrageid" in kwargs:
- _INDEXER_INT = 2
- _INDEXER = "tvrageid"
- else:
- _INDEXER_INT = None
- _INDEXER = None
# required
- self.indexerid, args = self.check_params(args, kwargs, _INDEXER, None, True, "int", [])
- self.indexer = _INDEXER_INT
+ self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
+ self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int",
+ [i for i in indexer_api.indexerApi().search_indexers])
# optional
self.location, args = self.check_params(args, kwargs, "location", None, False, "string", [])
- self.initial, args = self.check_params(args, kwargs, "initial", None, False, "list",
- ["sdtv", "sddvd", "hdtv", "rawhdtv", "fullhdtv", "hdwebdl",
- "fullhdwebdl", "hdbluray", "fullhdbluray", "unknown"])
- self.archive, args = self.check_params(args, kwargs, "archive", None, False, "list",
- ["sddvd", "hdtv", "rawhdtv", "fullhdtv", "hdwebdl", "fullhdwebdl",
- "hdbluray", "fullhdbluray"])
+ self.initial, args = self.check_params(args, kwargs, "initial", None, False, "list", [q for q in quality_map])
+ self.archive, args = self.check_params(args, kwargs, "archive", None, False, "list", [q for q in quality_map])
+ self.upgradeonce, args = self.check_params(args, kwargs, "upgrade_once", False, False, "bool", [])
self.flatten_folders, args = self.check_params(args, kwargs, "flatten_folders",
str(sickbeard.FLATTEN_FOLDERS_DEFAULT), False, "bool", [])
self.status, args = self.check_params(args, kwargs, "status", None, False, "string",
["wanted", "skipped", "archived", "ignored"])
- self.lang, args = self.check_params(args, kwargs, "lang", "en", False, "string", self.valid_languages.keys())
self.subtitles, args = self.check_params(args, kwargs, "subtitles", int(sickbeard.USE_SUBTITLES), False, "int",
[])
self.anime, args = self.check_params(args, kwargs, "anime", int(sickbeard.ANIME_DEFAULT), False, "int",
[])
self.scene, args = self.check_params(args, kwargs, "scene", int(sickbeard.SCENE_DEFAULT), False, "int",
[])
+ self.lang = 'en'
# super, missing, help
ApiCall.__init__(self, handler, args, kwargs)
def run(self):
- """ add a show in sickbeard with an existing folder """
- showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid))
+ """ add a show in sickgear with an existing folder """
+ showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid})
if showObj:
return _responds(RESULT_FAILURE, msg="An existing indexerid already exists in database")
@@ -2024,17 +3123,6 @@ class CMD_ShowAddNew(ApiCall):
if not ek.ek(os.path.isdir, self.location):
return _responds(RESULT_FAILURE, msg="'" + self.location + "' is not a valid location")
- quality_map = {'sdtv': Quality.SDTV,
- 'sddvd': Quality.SDDVD,
- 'hdtv': Quality.HDTV,
- 'rawhdtv': Quality.RAWHDTV,
- 'fullhdtv': Quality.FULLHDTV,
- 'hdwebdl': Quality.HDWEBDL,
- 'fullhdwebdl': Quality.FULLHDWEBDL,
- 'hdbluray': Quality.HDBLURAY,
- 'fullhdbluray': Quality.FULLHDBLURAY,
- 'unknown': Quality.UNKNOWN}
-
# use default quality as a failsafe
newQuality = int(sickbeard.QUALITY_DEFAULT)
iqualityID = []
@@ -2094,146 +3182,410 @@ class CMD_ShowAddNew(ApiCall):
else:
helpers.chmodAsParent(showPath)
- sickbeard.showQueueScheduler.action.addShow(int(self.indexer), int(self.indexerid), showPath, newStatus,
- newQuality,
- int(self.flatten_folders), self.lang, self.subtitles, self.anime,
- self.scene, new_show=True) # @UndefinedVariable
+ sickbeard.showQueueScheduler.action.addShow(int(self.indexer), int(self.indexerid), showPath, newStatus,
+ newQuality,
+ int(self.flatten_folders), self.lang, self.subtitles, self.anime,
+ self.scene, new_show=True, upgrade_once=self.upgradeonce) # @UndefinedVariable
+
+ return _responds(RESULT_SUCCESS, {"name": indexerName}, indexerName + " has been queued to be added")
+
+
+class CMD_ShowAddNew(CMD_SickGearShowAddNew):
+ _help = {"desc": "add a new show to sickgear",
+ "requiredParameters": {"tvdbid": {"desc": "thetvdb.com id"}
+ },
+ "optionalParameters": {"initial": {"desc": "initial quality for the show"},
+ "location": {"desc": "base path for where the show folder is to be created"},
+ "archive": {"desc": "archive quality for the show"},
+ "flatten_folders": {"desc": "flatten subfolders for the show"},
+ "status": {"desc": "status of missing episodes"},
+ "lang": {"desc": "the 2 letter lang abbreviation id"},
+ "subtitles": {"desc": "allow search episode subtitle"},
+ "anime": {"desc": "set show to anime"},
+ "scene": {"desc": "show searches episodes by scene numbering"}
+ },
+ "SickGearCommand": "sg.show.addnew",
+ }
+
+ valid_languages = {
+ 'el': 20, 'en': 7, 'zh': 27, 'it': 15, 'cs': 28, 'es': 16, 'ru': 22,
+ 'nl': 13, 'pt': 26, 'no': 9, 'tr': 21, 'pl': 18, 'fr': 17, 'hr': 31,
+ 'de': 14, 'da': 10, 'fi': 11, 'hu': 19, 'ja': 25, 'he': 24, 'ko': 32,
+ 'sv': 8, 'sl': 30}
+
+ def __init__(self, handler, args, kwargs):
+ kwargs['indexer'] = INDEXER_TVDB
+ kwargs['indexerid'], args = self.check_params(args, kwargs, "tvdbid", None, True, "int", [])
+ # required
+ # optional
+ # super, missing, help
+ self.sickbeard_call = True
+ CMD_SickGearShowAddNew.__init__(self, handler, args, kwargs)
+
+
+class CMD_SickGearShowCache(ApiCall):
+ _help = {"desc": "check sickgear's cache to see if the banner or poster image for a show is valid",
+ "requiredParameters": {"indexer": {"desc": "indexer of a show"},
+ "indexerid": {"desc": "unique id of a show"},
+ },
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int",
+ [i for i in indexer_api.indexerApi().indexers])
+ self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
+ # optional
+ # super, missing, help
+ ApiCall.__init__(self, handler, args, kwargs)
+
+ def run(self):
+ """ check sickgear's cache to see if the banner or poster image for a show is valid """
+ showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid})
+ if not showObj:
+ return _responds(RESULT_FAILURE, msg="Show not found")
+
+ #TODO: catch if cache dir is missing/invalid.. so it doesn't break show/show.cache
+ #return {"poster": 0, "banner": 0}
+
+ cache_obj = image_cache.ImageCache()
+
+ has_poster = 0
+ has_banner = 0
+
+ if ek.ek(os.path.isfile, cache_obj.poster_path(showObj.indexerid)):
+ has_poster = 1
+ if ek.ek(os.path.isfile, cache_obj.banner_path(showObj.indexerid)):
+ has_banner = 1
+
+ return _responds(RESULT_SUCCESS, {"poster": has_poster, "banner": has_banner})
+
+
+class CMD_ShowCache(CMD_SickGearShowCache):
+ _help = {"desc": "check sickgear's cache to see if the banner or poster image for a show is valid",
+ "requiredParameters": {"indexerid": {"desc": "thetvdb.com id of a show"},
+ },
+ "SickGearCommand": "sg.show.cache",
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ kwargs['indexer'] = INDEXER_TVDB
+ self.sickbeard_call = True
+ CMD_SickGearShowCache.__init__(self, handler, args, kwargs)
+
+
+class CMD_SickGearShowDelete(ApiCall):
+ _help = {"desc": "delete a show in sickgear",
+ "requiredParameters": {"indexer": {"desc": "indexer of a show"},
+ "indexerid": {"desc": "unique id of a show"},
+ },
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int",
+ [i for i in indexer_api.indexerApi().indexers])
+ self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
+ # optional
+ # super, missing, help
+ ApiCall.__init__(self, handler, args, kwargs)
+
+ def run(self):
+ """ delete a show in sickgear """
+ showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid})
+ if not showObj:
+ return _responds(RESULT_FAILURE, msg="Show not found")
+
+ if sickbeard.showQueueScheduler.action.isBeingAdded(
+ showObj) or sickbeard.showQueueScheduler.action.isBeingUpdated(showObj): #@UndefinedVariable
+ return _responds(RESULT_FAILURE, msg="Show can not be deleted while being added or updated")
+
+ showObj.deleteShow()
+ return _responds(RESULT_SUCCESS, msg=str(showObj.name) + " has been deleted")
- return _responds(RESULT_SUCCESS, {"name": indexerName}, indexerName + " has been queued to be added")
+class CMD_ShowDelete(CMD_SickGearShowDelete):
+ _help = {"desc": "delete a show in sickgear",
+ "requiredParameters": {"indexerid": {"desc": "thetvdb.com id of a show"},
+ },
+ "SickGearCommand": "sg.show.delete",
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ kwargs['indexer'] = INDEXER_TVDB
+ self.sickbeard_call = True
+ CMD_SickGearShowDelete.__init__(self, handler, args, kwargs)
-class CMD_ShowCache(ApiCall):
- _help = {"desc": "check sickbeard's cache to see if the banner or poster image for a show is valid",
- "requiredParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"}
+
+class CMD_SickGearShowGetQuality(ApiCall):
+ _help = {"desc": "get quality setting for a show in sickgear",
+ "requiredParameters": {"indexer": {"desc": "indexer of a show"},
+ "indexerid": {"desc": "unique id of a show"},
+ },
}
- }
def __init__(self, handler, args, kwargs):
# required
+ self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int",
+ [i for i in indexer_api.indexerApi().indexers])
self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
# optional
# super, missing, help
ApiCall.__init__(self, handler, args, kwargs)
def run(self):
- """ check sickbeard's cache to see if the banner or poster image for a show is valid """
- showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid))
+ """ get quality setting for a show in sickgear """
+ showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid})
if not showObj:
return _responds(RESULT_FAILURE, msg="Show not found")
- #TODO: catch if cache dir is missing/invalid.. so it doesn't break show/show.cache
- #return {"poster": 0, "banner": 0}
+ anyQualities, bestQualities = _mapQuality(showObj.quality)
- cache_obj = image_cache.ImageCache()
+ data = {"initial": anyQualities, "archive": bestQualities}
- has_poster = 0
- has_banner = 0
+ if not self.sickbeard_call:
+ data['upgrade_once'] = showObj.upgrade_once
- if ek.ek(os.path.isfile, cache_obj.poster_path(showObj.indexerid)):
- has_poster = 1
- if ek.ek(os.path.isfile, cache_obj.banner_path(showObj.indexerid)):
- has_banner = 1
+ return _responds(RESULT_SUCCESS, data)
- return _responds(RESULT_SUCCESS, {"poster": has_poster, "banner": has_banner})
+class CMD_ShowGetQuality(CMD_SickGearShowGetQuality):
+ _help = {"desc": "get quality setting for a show in sickgear",
+ "requiredParameters": {"indexerid": {"desc": "thetvdb.com id of a show"},
+ },
+ "SickGearCommand": "sg.show.getquality",
+ }
-class CMD_ShowDelete(ApiCall):
- _help = {"desc": "delete a show in sickbeard",
- "requiredParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"},
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ kwargs['indexer'] = INDEXER_TVDB
+ self.sickbeard_call = True
+ CMD_SickGearShowGetQuality.__init__(self, handler, args, kwargs)
+
+
+class CMD_SickGearShowGetPoster(ApiCall):
+ _help = {"desc": "get the poster stored for a show in sickgear",
+ "requiredParameters": {"indexer": {"desc": "indexer of a show"},
+ "indexerid": {"desc": "unique id of a show"},
+ },
}
- }
def __init__(self, handler, args, kwargs):
# required
+ self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int",
+ [i for i in indexer_api.indexerApi().indexers])
self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
# optional
# super, missing, help
ApiCall.__init__(self, handler, args, kwargs)
def run(self):
- """ delete a show in sickbeard """
- showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid))
- if not showObj:
- return _responds(RESULT_FAILURE, msg="Show not found")
+ """ get the poster for a show in sickgear """
+ return {'outputType': 'image', 'image': self.handler.showPoster(self.indexerid, 'poster', True)}
- if sickbeard.showQueueScheduler.action.isBeingAdded(
- showObj) or sickbeard.showQueueScheduler.action.isBeingUpdated(showObj): #@UndefinedVariable
- return _responds(RESULT_FAILURE, msg="Show can not be deleted while being added or updated")
- showObj.deleteShow()
- return _responds(RESULT_SUCCESS, msg=str(showObj.name) + " has been deleted")
+class CMD_ShowGetPoster(CMD_SickGearShowGetPoster):
+ _help = {"desc": "get the poster stored for a show in sickgear",
+ "requiredParameters": {"indexerid": {"desc": "thetvdb.com id of a show"},
+ },
+ "SickGearCommand": "sg.show.getposter",
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ kwargs['indexer'] = INDEXER_TVDB
+ self.sickbeard_call = True
+ CMD_SickGearShowGetPoster.__init__(self, handler, args, kwargs)
-class CMD_ShowGetQuality(ApiCall):
- _help = {"desc": "get quality setting for a show in sickbeard",
- "requiredParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"}
+class CMD_SickGearShowGetBanner(ApiCall):
+ _help = {"desc": "get the banner stored for a show in sickgear",
+ "requiredParameters": {"indexer": {"desc": "indexer of a show"},
+ "indexerid": {"desc": "unique id of a show"},
+ },
}
- }
def __init__(self, handler, args, kwargs):
# required
+ self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int",
+ [i for i in indexer_api.indexerApi().indexers])
self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
# optional
# super, missing, help
ApiCall.__init__(self, handler, args, kwargs)
def run(self):
- """ get quality setting for a show in sickbeard """
- showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid))
- if not showObj:
- return _responds(RESULT_FAILURE, msg="Show not found")
+ """ get the banner for a show in sickgear """
+ return {'outputType': 'image', 'image': self.handler.showPoster(self.indexerid, 'banner', True)}
- anyQualities, bestQualities = _mapQuality(showObj.quality)
- return _responds(RESULT_SUCCESS, {"initial": anyQualities, "archive": bestQualities})
+class CMD_ShowGetBanner(CMD_SickGearShowGetBanner):
+ _help = {"desc": "get the banner stored for a show in sickgear",
+ "requiredParameters": {"indexerid": {"desc": "thetvdb.com id of a show"},
+ },
+ "SickGearCommand": "sg.show.getbanner",
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ kwargs['indexer'] = INDEXER_TVDB
+ self.sickbeard_call = True
+ CMD_SickGearShowGetBanner.__init__(self, handler, args, kwargs)
-class CMD_ShowGetPoster(ApiCall):
- _help = {"desc": "get the poster stored for a show in sickbeard",
- "requiredParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"}
+class CMD_SickGearShowListFanart(ApiCall):
+ _help = {"desc": "list the fanart's stored for a show in sickgear",
+ "requiredParameters": {"indexer": {"desc": "indexer of a show"},
+ "indexerid": {"desc": "unique id of a show"},
+ },
}
- }
def __init__(self, handler, args, kwargs):
# required
+ self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int",
+ [i for i in indexer_api.indexerApi().indexers])
self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
# optional
# super, missing, help
ApiCall.__init__(self, handler, args, kwargs)
def run(self):
- """ get the poster for a show in sickbeard """
- return {'outputType': 'image', 'image': self.handler.showPoster(self.indexerid, 'poster', True)}
+ """ list the fanart's for a show in sickgear """
+ showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid})
+ if not showObj:
+ return _responds(RESULT_FAILURE, msg="Show not found")
+ fanart = []
+ rating_names = {10: 'group', 20: 'favorite', 30: 'avoid'}
+ cache_obj = image_cache.ImageCache()
+ for img in ek.ek(glob.glob, cache_obj.fanart_path(showObj.indexerid).replace('fanart.jpg', '*')) or []:
+ match = re.search(r'\.(\d+(?:\.(\w*?(\d*)))?\.(?:\w{5,8}))\.fanart\.', img, re.I)
+ if match and match.group(1):
+ fanart += [(match.group(1), rating_names.get(sickbeard.FANART_RATINGS.get(str(self.indexerid), {}).get(match.group(1), ''), ''))]
+
+ return _responds(RESULT_SUCCESS, fanart)
-class CMD_ShowGetBanner(ApiCall):
- _help = {"desc": "get the banner stored for a show in sickbeard",
- "requiredParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"}
+
+class CMD_SickGearShowRateFanart(ApiCall):
+ _help = {"desc": "rate the fanart's stored for a show in sickgear",
+ "requiredParameters": {"indexer": {"desc": "indexer of a show"},
+ "indexerid": {"desc": "unique id of a show"},
+ "fanartname": {"desc": "fanart name form sg.show.listfanart"},
+ "rating": {"desc": "rate: unrate, group, favorite, avoid"},
+ },
}
- }
def __init__(self, handler, args, kwargs):
# required
+ self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int",
+ [i for i in indexer_api.indexerApi().indexers])
self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
+ self.fanartname, args = self.check_params(args, kwargs, "fanartname", None, True, "string", [])
+ self.rating, args = self.check_params(args, kwargs, "rating", None, True, "string",
+ ['unrate', 'group', 'favorite', 'avoid'])
# optional
# super, missing, help
ApiCall.__init__(self, handler, args, kwargs)
def run(self):
- """ get the banner for a show in sickbeard """
- return {'outputType': 'image', 'image': self.handler.showPoster(self.indexerid, 'banner', True)}
+ """ rate the fanart's for a show in sickgear """
+ cache_obj = image_cache.ImageCache()
+ fanartfile = cache_obj.fanart_path(self.indexerid).replace('fanart.jpg', '%s.fanart.jpg' % self.fanartname)
+ if not ek.ek(os.path.isfile, fanartfile):
+ return _responds(RESULT_FAILURE, msg='Unknown Fanart')
+ fan_ratings = {'unrate': 0, 'group': 10, 'favorite': 20, 'avoid': 30}
+ if 'unrate' == self.rating and str(self.indexerid) in sickbeard.FANART_RATINGS \
+ and self.fanartname in sickbeard.FANART_RATINGS[str(self.indexerid)]:
+ del sickbeard.FANART_RATINGS[str(self.indexerid)][self.fanartname]
+ else:
+ sickbeard.FANART_RATINGS[str(self.indexerid)][self.fanartname] = fan_ratings[self.rating]
+ sickbeard.save_config()
+ return _responds(RESULT_SUCCESS, msg='Rated Fanart: %s = %s' % (self.fanartname, self.rating))
+
+
+class CMD_SickGearShowGetFanart(ApiCall):
+ _help = {"desc": "get the fanart stored for a show in sickgear. X-Fanartname response header resturns Fanart name or default for not found",
+ "requiredParameters": {"indexer": {"desc": "indexer of a show"},
+ "indexerid": {"desc": "unique id of a show"},
+ },
+ "optionalParameters": {"fanartname": {"desc": "fanart name form sg.show.listfanart"},
+ },
+ }
+ def __init__(self, handler, args, kwargs):
+ # required
+ self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int",
+ [i for i in indexer_api.indexerApi().indexers])
+ self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
+ # optional
+ self.fanartname, args = self.check_params(args, kwargs, "fanartname", None, False, "string", [])
+ # super, missing, help
+ ApiCall.__init__(self, handler, args, kwargs)
-class CMD_ShowPause(ApiCall):
- _help = {"desc": "set a show's paused state in sickbeard",
- "requiredParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"},
- },
+ def run(self):
+ """ get the fanart for a show in sickgear """
+ cache_obj = image_cache.ImageCache()
+ default_fanartfile = ek.ek(os.path.join, sickbeard.PROG_DIR, 'gui', 'slick', 'images', 'trans.png')
+ fanartfile = default_fanartfile
+ used_fanart = 'default'
+ if self.fanartname:
+ fanartfile = cache_obj.fanart_path(self.indexerid).replace('fanart.jpg', '%s.fanart.jpg' % self.fanartname)
+ if not ek.ek(os.path.isfile, fanartfile):
+ fanartfile = default_fanartfile
+ used_fanart = self.fanartname
+ else:
+ fanart = []
+ for img in ek.ek(glob.glob, cache_obj.fanart_path(self.indexerid).replace('fanart.jpg', '*')) or []:
+ if not ek.ek(os.path.isfile, img):
+ continue
+ match = re.search(r'\.(\d+(?:\.(\w*?(\d*)))?\.(?:\w{5,8}))\.fanart\.', img, re.I)
+ if match and match.group(1):
+ fanart += [(img, match.group(1), sickbeard.FANART_RATINGS.get(str(self.indexerid), {}).get(match.group(1), 0))]
+ if fanart:
+ fanartsorted = sorted([f for f in fanart if f[2] != 30], key=lambda x: x[2], reverse=True)
+ max_fa = max([f[2] for f in fanartsorted])
+ fanartsorted = [f for f in fanartsorted if f[2] == max_fa]
+ if fanartsorted:
+ random_fanart = randint(0, len(fanartsorted) - 1)
+ fanartfile = fanartsorted[random_fanart][0]
+ used_fanart = fanartsorted[random_fanart][1]
+
+ if fanartfile and ek.ek(os.path.isfile, fanartfile):
+ with ek.ek(open, fanartfile, 'rb') as f:
+ mime_type, encoding = MimeTypes().guess_type(fanartfile)
+ self.handler.set_header('X-Fanartname', used_fanart)
+ self.handler.set_header('Content-Type', mime_type)
+ return {'outputType': 'image', 'image': f.read()}
+
+ # we should never get here
+ return _responds(RESULT_FAILURE, msg='No Fanart found')
+
+
+class CMD_SickGearShowPause(ApiCall):
+ _help = {"desc": "set a show's paused state in sickgear",
+ "requiredParameters": {"indexer": {"desc": "indexer of a show"},
+ "indexerid": {"desc": "unique id of a show"},
+ },
"optionalParameters": {"pause": {"desc": "set the pause state of the show"}
- }
- }
+ }
+ }
def __init__(self, handler, args, kwargs):
# required
+ self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int",
+ [i for i in indexer_api.indexerApi().indexers])
self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
# optional
self.pause, args = self.check_params(args, kwargs, "pause", 0, False, "bool", [])
@@ -2241,8 +3593,8 @@ class CMD_ShowPause(ApiCall):
ApiCall.__init__(self, handler, args, kwargs)
def run(self):
- """ set a show's paused state in sickbeard """
- showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid))
+ """ set a show's paused state in sickgear """
+ showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid})
if not showObj:
return _responds(RESULT_FAILURE, msg="Show not found")
@@ -2256,22 +3608,42 @@ class CMD_ShowPause(ApiCall):
return _responds(RESULT_FAILURE, msg=str(showObj.name) + " was unable to be paused")
-class CMD_ShowRefresh(ApiCall):
- _help = {"desc": "refresh a show in sickbeard",
- "requiredParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"},
- }
- }
+class CMD_ShowPause(CMD_SickGearShowPause):
+ _help = {"desc": "set a show's paused state in sickgear",
+ "requiredParameters": {"indexerid": {"desc": "thetvdb.com id of a show"},
+ },
+ "optionalParameters": {"pause": {"desc": "set the pause state of the show"}
+ },
+ "SickGearCommand": "sg.show.pause",
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ kwargs['indexer'] = INDEXER_TVDB
+ self.sickbeard_call = True
+ CMD_SickGearShowPause.__init__(self, handler, args, kwargs)
+
+
+class CMD_SickGearShowRefresh(ApiCall):
+ _help = {"desc": "refresh a show in sickgear",
+ "requiredParameters": {"indexer": {"desc": "indexer of a show"},
+ "indexerid": {"desc": "unique id of a show"},},
+ }
def __init__(self, handler, args, kwargs):
# required
+ self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int",
+ [i for i in indexer_api.indexerApi().indexers])
self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
# optional
# super, missing, help
ApiCall.__init__(self, handler, args, kwargs)
def run(self):
- """ refresh a show in sickbeard """
- showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid))
+ """ refresh a show in sickgear """
+ showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid})
if not showObj:
return _responds(RESULT_FAILURE, msg="Show not found")
@@ -2283,16 +3655,35 @@ class CMD_ShowRefresh(ApiCall):
return _responds(RESULT_FAILURE, msg="Unable to refresh " + str(showObj.name))
-class CMD_ShowSeasonList(ApiCall):
+class CMD_ShowRefresh(CMD_SickGearShowRefresh):
+ _help = {"desc": "refresh a show in sickgear",
+ "requiredParameters": {"indexerid": {"desc": "thetvdb.com id of a show"},
+ },
+ "SickGearCommand": "sg.show.refresh",
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ kwargs['indexer'] = INDEXER_TVDB
+ self.sickbeard_call = True
+ CMD_SickGearShowRefresh.__init__(self, handler, args, kwargs)
+
+
+class CMD_SickGearShowSeasonList(ApiCall):
_help = {"desc": "display the season list for a given show",
- "requiredParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"},
- },
+ "requiredParameters": {"indexer": {"desc": "indexer of a show"},
+ "indexerid": {"desc": "unique id of a show"},
+ },
"optionalParameters": {"sort": {"desc": "change the sort order from descending to ascending"}
- }
- }
+ }
+ }
def __init__(self, handler, args, kwargs):
# required
+ self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int",
+ [i for i in indexer_api.indexerApi().indexers])
self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
# optional
self.sort, args = self.check_params(args, kwargs, "sort", "desc", False, "string",
@@ -2302,17 +3693,19 @@ class CMD_ShowSeasonList(ApiCall):
def run(self):
""" display the season list for a given show """
- showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid))
+ showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid})
if not showObj:
return _responds(RESULT_FAILURE, msg="Show not found")
myDB = db.DBConnection(row_type="dict")
if self.sort == "asc":
- sqlResults = myDB.select("SELECT DISTINCT season FROM tv_episodes WHERE showid = ? ORDER BY season ASC",
- [self.indexerid])
+ sqlResults = myDB.select("SELECT DISTINCT season FROM tv_episodes WHERE showid = ? AND indexer = ? "
+ "ORDER BY season ASC",
+ [self.indexerid, self.indexer])
else:
- sqlResults = myDB.select("SELECT DISTINCT season FROM tv_episodes WHERE showid = ? ORDER BY season DESC",
- [self.indexerid])
+ sqlResults = myDB.select("SELECT DISTINCT season FROM tv_episodes WHERE showid = ? AND indexer = ? "
+ "ORDER BY season DESC",
+ [self.indexerid, self.indexer])
seasonList = [] # a list with all season numbers
for row in sqlResults:
seasonList.append(int(row["season"]))
@@ -2320,16 +3713,37 @@ class CMD_ShowSeasonList(ApiCall):
return _responds(RESULT_SUCCESS, seasonList)
-class CMD_ShowSeasons(ApiCall):
+class CMD_ShowSeasonList(CMD_SickGearShowSeasonList):
+ _help = {"desc": "display the season list for a given show",
+ "requiredParameters": {"indexerid": {"desc": "thetvdb.com id of a show"},
+ },
+ "optionalParameters": {"sort": {"desc": "change the sort order from descending to ascending"}
+ },
+ "SickGearCommand": "sg.show.seasonlist",
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ kwargs['indexer'] = INDEXER_TVDB
+ self.sickbeard_call = True
+ CMD_SickGearShowSeasonList.__init__(self, handler, args, kwargs)
+
+
+class CMD_SickGearShowSeasons(ApiCall):
_help = {"desc": "display a listing of episodes for all or a given season",
- "requiredParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"},
- },
+ "requiredParameters": {"indexer": {"desc": "indexer of a show"},
+ "indexerid": {"desc": "unique id of a show"},
+ },
"optionalParameters": {"season": {"desc": "the season number"},
- }
- }
+ }
+ }
def __init__(self, handler, args, kwargs):
# required
+ self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int",
+ [i for i in indexer_api.indexerApi().indexers])
self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
# optional
self.season, args = self.check_params(args, kwargs, "season", None, False, "int", [])
@@ -2338,22 +3752,30 @@ class CMD_ShowSeasons(ApiCall):
def run(self):
""" display a listing of episodes for all or a given show """
- showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid))
+ showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid})
if not showObj:
return _responds(RESULT_FAILURE, msg="Show not found")
myDB = db.DBConnection(row_type="dict")
if self.season == None:
- sqlResults = myDB.select("SELECT name, episode, airdate, status, season FROM tv_episodes WHERE showid = ?",
- [self.indexerid])
+ sqlResults = myDB.select("SELECT name, description, absolute_number, scene_absolute_number, episode, "
+ "scene_episode, scene_season, airdate, status, season FROM tv_episodes "
+ "WHERE showid = ? AND indexer = ?",
+ [self.indexerid, self.indexer])
seasons = {}
for row in sqlResults:
status, quality = Quality.splitCompositeStatus(int(row["status"]))
row["status"] = _get_status_Strings(status)
row["quality"] = _get_quality_string(quality)
- dtEpisodeAirs = sbdatetime.sbdatetime.convert_to_setting(network_timezones.parse_date_time(row['airdate'],showObj.airs,showObj.network))
+ timezone, row['timezone'] = network_timezones.get_network_timezone(showObj.network, return_name=True)
+ dtEpisodeAirs = sbdatetime.sbdatetime.convert_to_setting(network_timezones.parse_date_time(
+ row['airdate'], showObj.airs, timezone))
row['airdate'] = sbdatetime.sbdatetime.sbfdate(dtEpisodeAirs, d_preset=dateFormat)
+ row['scene_episode'] = helpers.tryInt(row['scene_episode'])
+ row['scene_season'] = helpers.tryInt(row['scene_season'])
+ row['absolute_number'] = helpers.tryInt(row['absolute_number'])
+ row['scene_absolute_number'] = helpers.tryInt(row['scene_absolute_number'])
curSeason = int(row["season"])
curEpisode = int(row["episode"])
del row["season"]
@@ -2364,8 +3786,9 @@ class CMD_ShowSeasons(ApiCall):
else:
sqlResults = myDB.select(
- "SELECT name, episode, airdate, status FROM tv_episodes WHERE showid = ? AND season = ?",
- [self.indexerid, self.season])
+ "SELECT name, description, absolute_number, scene_absolute_number, episode, scene_episode, "
+ "scene_season, airdate, status FROM tv_episodes WHERE showid = ? AND indexer = ? AND season = ?",
+ [self.indexerid, self.indexer, self.season])
if len(sqlResults) is 0:
return _responds(RESULT_FAILURE, msg="Season not found")
seasons = {}
@@ -2375,8 +3798,14 @@ class CMD_ShowSeasons(ApiCall):
status, quality = Quality.splitCompositeStatus(int(row["status"]))
row["status"] = _get_status_Strings(status)
row["quality"] = _get_quality_string(quality)
- dtEpisodeAirs = sbdatetime.sbdatetime.convert_to_setting(network_timezones.parse_date_time(row['airdate'], showObj.airs, showObj.network))
+ timezone, row['timezone'] = network_timezones.get_network_timezone(showObj.network, return_name=True)
+ dtEpisodeAirs = sbdatetime.sbdatetime.convert_to_setting(network_timezones.parse_date_time(
+ row['airdate'], showObj.airs, timezone))
row['airdate'] = sbdatetime.sbdatetime.sbfdate(dtEpisodeAirs, d_preset=dateFormat)
+ row['scene_episode'] = helpers.tryInt(row['scene_episode'])
+ row['scene_season'] = helpers.tryInt(row['scene_season'])
+ row['absolute_number'] = helpers.tryInt(row['absolute_number'])
+ row['scene_absolute_number'] = helpers.tryInt(row['scene_absolute_number'])
if not curEpisode in seasons:
seasons[curEpisode] = {}
seasons[curEpisode] = row
@@ -2384,50 +3813,58 @@ class CMD_ShowSeasons(ApiCall):
return _responds(RESULT_SUCCESS, seasons)
-class CMD_ShowSetQuality(ApiCall):
+class CMD_ShowSeasons(CMD_SickGearShowSeasons):
+ _help = {"desc": "display a listing of episodes for all or a given season",
+ "requiredParameters": {"indexerid": {"desc": "thetvdb.com id of a show"},
+ },
+ "optionalParameters": {"season": {"desc": "the season number"},
+ },
+ "SickGearCommand": "sg.show.seasons",
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ kwargs['indexer'] = INDEXER_TVDB
+ self.sickbeard_call = True
+ CMD_SickGearShowSeasons.__init__(self, handler, args, kwargs)
+
+
+class CMD_SickGearShowSetQuality(ApiCall):
_help = {
- "desc": "set desired quality of a show in sickbeard. if neither initial or archive are provided then the config default quality will be used",
- "requiredParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"}
- },
+ "desc": "set desired quality of a show in sickgear. if neither initial or archive are provided then the config default quality will be used",
+ "requiredParameters": {"indexer": {"desc": "indexer of a show"},
+ "indexerid": {"desc": "unique id of a show"}
+ },
"optionalParameters": {"initial": {"desc": "initial quality for the show"},
- "archive": {"desc": "archive quality for the show"}
- }
- }
+ "archive": {"desc": "archive quality for the show"},
+ "upgrade_once": {"desc": "upgrade only once"}
+ }
+ }
def __init__(self, handler, args, kwargs):
# required
+ self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int",
+ [i for i in indexer_api.indexerApi().indexers])
self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
# optional
# this for whatever reason removes hdbluray not sdtv... which is just wrong. reverting to previous code.. plus we didnt use the new code everywhere.
# self.archive, args = self.check_params(args, kwargs, "archive", None, False, "list", _getQualityMap().values()[1:])
- self.initial, args = self.check_params(args, kwargs, "initial", None, False, "list",
- ["sdtv", "sddvd", "hdtv", "rawhdtv", "fullhdtv", "hdwebdl",
- "fullhdwebdl", "hdbluray", "fullhdbluray", "unknown"])
- self.archive, args = self.check_params(args, kwargs, "archive", None, False, "list",
- ["sddvd", "hdtv", "rawhdtv", "fullhdtv", "hdwebdl", "fullhdwebdl",
- "hdbluray", "fullhdbluray"])
+ self.initial, args = self.check_params(args, kwargs, "initial", None, False, "list", [q for q in quality_map])
+ self.archive, args = self.check_params(args, kwargs, "archive", None, False, "list", [q for q in quality_map])
+ self.upgradeonce, args = self.check_params(args, kwargs, "upgrade_once", False, False, "bool", [])
# super, missing, help
ApiCall.__init__(self, handler, args, kwargs)
def run(self):
- """ set the quality for a show in sickbeard by taking in a deliminated
+ """ set the quality for a show in sickgear by taking in a deliminated
string of qualities, map to their value and combine for new values
"""
- showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid))
+ showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid})
if not showObj:
return _responds(RESULT_FAILURE, msg="Show not found")
- quality_map = {'sdtv': Quality.SDTV,
- 'sddvd': Quality.SDDVD,
- 'hdtv': Quality.HDTV,
- 'rawhdtv': Quality.RAWHDTV,
- 'fullhdtv': Quality.FULLHDTV,
- 'hdwebdl': Quality.HDWEBDL,
- 'fullhdwebdl': Quality.FULLHDWEBDL,
- 'hdbluray': Quality.HDBLURAY,
- 'fullhdbluray': Quality.FULLHDBLURAY,
- 'unknown': Quality.UNKNOWN}
-
#use default quality as a failsafe
newQuality = int(sickbeard.QUALITY_DEFAULT)
iqualityID = []
@@ -2444,18 +3881,46 @@ class CMD_ShowSetQuality(ApiCall):
newQuality = Quality.combineQualities(iqualityID, aqualityID)
showObj.quality = newQuality
+ showObj.upgrade_once = self.upgradeonce
+
+ showObj.saveToDB()
+
return _responds(RESULT_SUCCESS,
msg=showObj.name + " quality has been changed to " + _get_quality_string(showObj.quality))
-class CMD_ShowStats(ApiCall):
+class CMD_ShowSetQuality(CMD_SickGearShowSetQuality):
+ _help = {
+ "desc": "set desired quality of a show in sickgear. if neither initial or archive are provided then the config default quality will be used",
+ "requiredParameters": {"indexerid": {"desc": "thetvdb.com id of a show"}
+ },
+ "optionalParameters": {"initial": {"desc": "initial quality for the show"},
+ "archive": {"desc": "archive quality for the show"}
+ },
+ "SickGearCommand": "sg.show.setquality",
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+
+ kwargs['indexer'] = INDEXER_TVDB
+ self.sickbeard_call = True
+ CMD_SickGearShowSetQuality.__init__(self, handler, args, kwargs)
+
+
+class CMD_SickGearShowStats(ApiCall):
_help = {"desc": "display episode statistics for a given show",
- "requiredParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"},
+ "requiredParameters": {"indexer": {"desc": "indexer of a show"},
+ "indexerid": {"desc": "unique id of a show"},
+ },
}
- }
def __init__(self, handler, args, kwargs):
# required
+ self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int",
+ [i for i in indexer_api.indexerApi().indexers])
self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
# optional
# super, missing, help
@@ -2463,7 +3928,7 @@ class CMD_ShowStats(ApiCall):
def run(self):
""" display episode statistics for a given show """
- showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid))
+ showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid})
if not showObj:
return _responds(RESULT_FAILURE, msg="Show not found")
@@ -2494,8 +3959,9 @@ class CMD_ShowStats(ApiCall):
episode_qualities_counts_snatch[statusCode] = 0
myDB = db.DBConnection(row_type="dict")
- sqlResults = myDB.select("SELECT status, season FROM tv_episodes WHERE season != 0 AND showid = ?",
- [self.indexerid])
+ sqlResults = myDB.select("SELECT status, season FROM tv_episodes WHERE season != 0 AND showid = ? "
+ "AND indexer = ?",
+ [self.indexerid, self.indexer])
# the main loop that goes through all episodes
for row in sqlResults:
status, quality = Quality.splitCompositeStatus(int(row["status"]))
@@ -2552,22 +4018,41 @@ class CMD_ShowStats(ApiCall):
return _responds(RESULT_SUCCESS, episodes_stats)
-class CMD_ShowUpdate(ApiCall):
- _help = {"desc": "update a show in sickbeard",
- "requiredParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"},
+class CMD_ShowStats(CMD_SickGearShowStats):
+ _help = {"desc": "display episode statistics for a given show",
+ "requiredParameters": {"indexerid": {"desc": "thetvdb.com id of a show"},
+ },
+ "SickGearCommand": "sg.show.stats",
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ kwargs['indexer'] = INDEXER_TVDB
+ self.sickbeard_call = True
+ CMD_SickGearShowStats.__init__(self, handler, args, kwargs)
+
+
+class CMD_SickGearShowUpdate(ApiCall):
+ _help = {"desc": "update a show in sickgear",
+ "requiredParameters": {"indexer": {"desc": "indexer of a show"},
+ "indexerid": {"desc": "unique id of a show"},
+ },
}
- }
def __init__(self, handler, args, kwargs):
# required
+ self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int",
+ [i for i in indexer_api.indexerApi().search_indexers])
self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
# optional
# super, missing, help
ApiCall.__init__(self, handler, args, kwargs)
def run(self):
- """ update a show in sickbeard """
- showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid))
+ """ update a show in sickgear """
+ showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid})
if not showObj:
return _responds(RESULT_FAILURE, msg="Show not found")
@@ -2579,27 +4064,49 @@ class CMD_ShowUpdate(ApiCall):
return _responds(RESULT_FAILURE, msg="Unable to update " + str(showObj.name))
-class CMD_Shows(ApiCall):
- _help = {"desc": "display all shows in sickbeard",
+class CMD_ShowUpdate(CMD_SickGearShowUpdate):
+ _help = {"desc": "update a show in sickgear",
+ "requiredParameters": {"indexerid": {"desc": "thetvdb.com id of a show"},
+ },
+ "SickGearCommand": "sg.show.update",
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ kwargs['indexer'] = INDEXER_TVDB
+ self.sickbeard_call = True
+ CMD_SickGearShowUpdate.__init__(self, handler, args, kwargs)
+
+
+class CMD_SickGearShows(ApiCall):
+ _help = {"desc": "display all shows in sickgear",
"optionalParameters": {"sort": {"desc": "sort the list of shows by show name instead of indexerid"},
"paused": {"desc": "only show the shows that are set to paused"},
- },
- }
+ "overview": {"desc": "include overview"},
+ },
+ }
def __init__(self, handler, args, kwargs):
# required
# optional
self.sort, args = self.check_params(args, kwargs, "sort", "id", False, "string", ["id", "name"])
self.paused, args = self.check_params(args, kwargs, "paused", None, False, "bool", [])
+ self.overview, args = self.check_params(args, kwargs, "overview", False, False, "bool", [])
# super, missing, help
ApiCall.__init__(self, handler, args, kwargs)
def run(self):
- """ display_is_int_multi( self.indexerid )shows in sickbeard """
+ """ display_is_int_multi( self.indexerid )shows in sickgear """
shows = {}
+
for curShow in sickbeard.showList:
- if self.paused != None and bool(self.paused) != bool(curShow.paused):
+ if self.sickbeard_call and INDEXER_TVDB != curShow.indexer:
+ continue
+
+ if self.paused is not None and bool(self.paused) != bool(curShow.paused):
continue
showDict = {
@@ -2610,17 +4117,35 @@ class CMD_Shows(ApiCall):
"sports": curShow.sports,
"anime": curShow.anime,
"indexerid": curShow.indexerid,
+ "indexer": curShow.indexer,
"tvdbid": curShow.ids.get(INDEXER_TVDB , {'id': 0})['id'],
+ 'ids': {k: v.get('id') for k, v in curShow.ids.iteritems()},
"tvrage_id": curShow.ids.get(INDEXER_TVRAGE, {'id': 0})['id'],
"tvrage_name": curShow.name,
"network": curShow.network,
"show_name": curShow.name,
"status": curShow.status,
"subtitles": curShow.subtitles,
+ "scenenumbering": curShow.is_scene,
+ "archivefirstmatch": curShow.upgrade_once,
+ "irgnorewords": curShow.rls_ignore_words,
+ "requirewords": curShow.rls_require_words,
+ "tag": curShow.tag,
+ "imdb_id": curShow.imdbid,
+ "classification": curShow.classification,
+ "runtime": curShow.runtime,
+ "startyear": curShow.startyear,
}
+ if self.overview:
+ showDict["overview"] = curShow.overview
+
+ timezone, showDict['timezone'] = network_timezones.get_network_timezone(showDict['network'],
+ return_name=True)
+
if curShow.nextaired:
- dtEpisodeAirs = sbdatetime.sbdatetime.convert_to_setting(network_timezones.parse_date_time(curShow.nextaired, curShow.airs, showDict['network']))
+ dtEpisodeAirs = sbdatetime.sbdatetime.convert_to_setting(network_timezones.parse_date_time(
+ curShow.nextaired, curShow.airs, timezone))
showDict['next_ep_airdate'] = sbdatetime.sbdatetime.sbfdate(dtEpisodeAirs, d_preset=dateFormat)
else:
showDict['next_ep_airdate'] = ''
@@ -2636,9 +4161,120 @@ class CMD_Shows(ApiCall):
return _responds(RESULT_SUCCESS, shows)
-class CMD_ShowsStats(ApiCall):
+class CMD_Shows(CMD_SickGearShows):
+ _help = {"desc": "display all thetvdb.com shows in sickgear",
+ "optionalParameters": {"sort": {"desc": "sort the list of shows by show name instead of indexerid"},
+ "paused": {"desc": "only show the shows that are set to paused"},
+ "overview": {"desc": "include overview"},
+ },
+ "SickGearCommand": "sg.shows",
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ self.sickbeard_call = True
+ CMD_SickGearShows.__init__(self, handler, args, kwargs)
+
+
+class CMD_SickGearShowsBrowseTrakt(ApiCall):
+ _help = {"desc": "browse trakt shows in sickgear",
+ "requiredParameters": {"type": {"desc": "type to browse: anticipated, newshows, newseasons, popular, "
+ "trending, recommended, watchlist"},
+ },
+ "optionalParameters": {"account_id": {"desc": "account_id for recommended, watchlist - "
+ "see sg.listtraktaccounts"},
+ },
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ self.type, args = self.check_params(args, kwargs, "type", "anticipated", True, "string",
+ ["anticipated", "newshows", "newseasons", "popular", "trending",
+ "recommended", "watchlist"])
+ # optional
+ self.account, args = self.check_params(args, kwargs, "account_id", None, False, "int",
+ [s for s in sickbeard.TRAKT_ACCOUNTS])
+ # super, missing, help
+ ApiCall.__init__(self, handler, args, kwargs)
+
+ def run(self):
+ """ browse trakt shows in sickgear """
+ urls = {'anticipated': 'shows/anticipated?limit=%s&' % 100,
+ 'newshows': '/calendars/all/shows/new/%s/%s?' % (sbdatetime.sbdatetime.sbfdate(
+ dt=datetime.datetime.now() + datetime.timedelta(days=-16), d_preset='%Y-%m-%d'), 32),
+ 'newseasons': '/calendars/all/shows/premieres/%s/%s?' % (sbdatetime.sbdatetime.sbfdate(
+ dt=datetime.datetime.now() + datetime.timedelta(days=-16), d_preset='%Y-%m-%d'), 32),
+ 'popular': 'shows/popular?limit=%s&' % 100,
+ 'trending': 'shows/trending?limit=%s&' % 100,
+ 'recommended': 'recommendations/shows?limit=%s&' % 100,
+ }
+ kwargs = {}
+ if self.type in ('recommended', 'watchlist'):
+ if not self.account:
+ return _responds(RESULT_FAILURE, msg='Need Trakt account')
+ kwargs['send_oauth'] = self.account
+ urls['watchlist'] = 'users/%s/watchlist/shows?limit=%s&' % (sickbeard.TRAKT_ACCOUNTS[self.account].slug, 100)
+ try:
+ data, oldest, newest = NewHomeAddShows.get_trakt_data(urls[self.type], **kwargs)
+ except Exception as e:
+ return _responds(RESULT_FAILURE, msg=e.message)
+ return _responds(RESULT_SUCCESS, data)
+
+
+class CMD_SickGearListTraktAccounts(ApiCall):
+ _help = {"desc": "list Trakt accounts in sickgear"}
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ ApiCall.__init__(self, handler, args, kwargs)
+
+ def run(self):
+ """ list Trakt accounts in sickgear """
+ accounts = [{'name': v.name, 'account_id': v.account_id} for a, v in sickbeard.TRAKT_ACCOUNTS.iteritems()]
+ return _responds(RESULT_SUCCESS, accounts)
+
+
+class CMD_SickGearShowsForceUpdate(ApiCall):
+ _help = {"desc": "force the daily show update now."}
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ ApiCall.__init__(self, handler, args, kwargs)
+
+ def run(self):
+ """ force the daily show update now """
+ if sickbeard.showQueueScheduler.action.isShowUpdateRunning() or sickbeard.showUpdateScheduler.action.amActive:
+ return _responds(RESULT_FAILURE, msg="show update already running.")
+
+ result = sickbeard.showUpdateScheduler.forceRun()
+ if result:
+ return _responds(RESULT_SUCCESS, msg="daily show update started")
+ return _responds(RESULT_FAILURE, msg="can't start show update currently")
+
+
+class CMD_SickGearShowsQueue(ApiCall):
+ _help = {"desc": "list the show update queue."}
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ ApiCall.__init__(self, handler, args, kwargs)
+
+ def run(self):
+ """ list the show update queue """
+ return _responds(RESULT_SUCCESS, sickbeard.showQueueScheduler.action.queue_length())
+
+
+class CMD_SickGearShowsStats(ApiCall):
_help = {"desc": "display the global shows and episode stats"
- }
+ }
def __init__(self, handler, args, kwargs):
# required
@@ -2650,68 +4286,148 @@ class CMD_ShowsStats(ApiCall):
""" display the global shows and episode stats """
stats = {}
+ indexer_limit = ('', ' AND indexer = %s' % INDEXER_TVDB)[self.sickbeard_call]
myDB = db.DBConnection()
today = str(datetime.date.today().toordinal())
- stats["shows_total"] = len(sickbeard.showList)
+ stats["shows_total"] = (len(sickbeard.showList),
+ len([x for x in sickbeard.showList if x.indexer == INDEXER_TVDB]))[self.sickbeard_call]
stats["shows_active"] = len(
- [show for show in sickbeard.showList if show.paused == 0 and show.status != "Ended"])
+ [show for show in sickbeard.showList if show.paused == 0 and show.status != "Ended"
+ and (not self.sickbeard_call or show.indexer == INDEXER_TVDB)])
stats["ep_downloaded"] = myDB.select("SELECT COUNT(*) FROM tv_episodes WHERE status IN (" + ",".join(
[str(show) for show in
- Quality.DOWNLOADED + Quality.ARCHIVED]) + ") AND season != 0 and episode != 0 AND airdate <= " + today + "")[0][
- 0]
+ Quality.DOWNLOADED + Quality.ARCHIVED]) + ") AND season != 0 and episode != 0 AND airdate <= " + today +
+ indexer_limit)[0][0]
stats["ep_total"] = myDB.select(
"SELECT COUNT(*) FROM tv_episodes WHERE season != 0 AND episode != 0 AND (airdate != 1 OR status IN (" + ",".join(
[str(show) for show in Quality.SNATCHED_ANY + Quality.DOWNLOADED + Quality.ARCHIVED]) +
- ")) AND airdate <= " + today + " AND status != " + str(IGNORED) + "")[0][0]
+ ")) AND airdate <= " + today + " AND status != " + str(IGNORED) + indexer_limit)[0][0]
return _responds(RESULT_SUCCESS, stats)
+
+class CMD_ShowsStats(CMD_SickGearShowsStats):
+ _help = {"desc": "display the global thetvdb.com shows and episode stats",
+ "SickGearCommand": "sg.shows.stats",
+ }
+
+ def __init__(self, handler, args, kwargs):
+ # required
+ # optional
+ # super, missing, help
+ self.sickbeard_call = True
+ CMD_SickGearShowsStats.__init__(self, handler, args, kwargs)
+
+
# WARNING: never define a cmd call string that contains a "_" (underscore)
# this is reserved for cmd indexes used while cmd chaining
# WARNING: never define a param name that contains a "." (dot)
# this is reserved for cmd namspaces used while cmd chaining
+
+
_functionMaper = {"help": CMD_Help,
+ "listcommands": CMD_ListCommands,
"future": CMD_ComingEpisodes,
+ "sg.future": CMD_SickGearComingEpisodes,
"episode": CMD_Episode,
+ "sg.episode": CMD_SickGearEpisode,
"episode.search": CMD_EpisodeSearch,
+ "sg.episode.search": CMD_SickGearEpisodeSearch,
"episode.setstatus": CMD_EpisodeSetStatus,
+ "sg.episode.setstatus": CMD_SickGearEpisodeSetStatus,
"episode.subtitlesearch": CMD_SubtitleSearch,
+ "sg.episode.subtitlesearch": CMD_SickGearSubtitleSearch,
"exceptions": CMD_Exceptions,
+ "sg.exceptions": CMD_SickGearExceptions,
+ 'sg.setexceptions': CMD_SetExceptions,
"history": CMD_History,
+ "sg.history": CMD_SickGearHistory,
"history.clear": CMD_HistoryClear,
+ "sg.history.clear": CMD_SickGearHistoryClear,
"history.trim": CMD_HistoryTrim,
+ "sg.history.trim": CMD_SickGearHistoryTrim,
"logs": CMD_Logs,
+ "sg.logs": CMD_Logs,
"sb": CMD_SickBeard,
+ "sg": CMD_SickGear,
"postprocess": CMD_PostProcess,
+ "sg.postprocess": CMD_SickGearPostProcess,
"sb.addrootdir": CMD_SickBeardAddRootDir,
+ "sg.addrootdir": CMD_SickGearAddRootDir,
"sb.checkscheduler": CMD_SickBeardCheckScheduler,
+ "sg.checkscheduler": CMD_SickGearCheckScheduler,
"sb.deleterootdir": CMD_SickBeardDeleteRootDir,
+ "sg.deleterootdir": CMD_SickGearDeleteRootDir,
"sb.forcesearch": CMD_SickBeardForceSearch,
+ "sg.forcesearch": CMD_SickGearForceSearch,
+ "sg.searchqueue": CMD_SickGearSearchQueue,
"sb.getdefaults": CMD_SickBeardGetDefaults,
+ "sg.getdefaults": CMD_SickGearGetDefaults,
"sb.getmessages": CMD_SickBeardGetMessages,
+ "sg.getmessages": CMD_SickGearGetMessages,
+ "sg.getqualities": CMD_SickGearGetQualities,
+ "sg.getqualitystrings": CMD_SickGearGetqualityStrings,
"sb.getrootdirs": CMD_SickBeardGetRootDirs,
+ "sg.getrootdirs": CMD_SickGearGetRootDirs,
"sb.pausebacklog": CMD_SickBeardPauseBacklog,
+ "sg.pausebacklog": CMD_SickGearPauseBacklog,
"sb.ping": CMD_SickBeardPing,
+ "sg.ping": CMD_SickGearPing,
"sb.restart": CMD_SickBeardRestart,
+ "sg.restart": CMD_SickGearRestart,
"sb.searchtvdb": CMD_SickBeardSearchIndexers,
+ "sg.searchtv": CMD_SickGearSearchIndexers,
"sb.setdefaults": CMD_SickBeardSetDefaults,
+ "sg.setscenenumber": CMD_SickGearSetSceneNumber,
+ "sg.activatescenenumbering": CMD_SickGearActivateSceneNumber,
+ "sg.getindexers": CMD_SickGearGetIndexers,
+ "sg.getindexericon": CMD_SickGearGetIndexerIcon,
+ "sg.getnetworkicon": CMD_SickGearGetNetworkIcon,
"sb.shutdown": CMD_SickBeardShutdown,
+ "sg.listignorewords": CMD_SickGearListIgnoreWords,
+ "sg.setignorewords": CMD_SickGearSetIgnoreWords,
+ "sg.listrequiredwords": CMD_SickGearListRequireWords,
+ "sg.setrequiredwords": CMD_SickGearSetRequrieWords,
"show": CMD_Show,
+ "sg.show": CMD_SickGearShow,
"show.addexisting": CMD_ShowAddExisting,
+ "sg.show.addexisting": CMD_SickGearShowAddExisting,
"show.addnew": CMD_ShowAddNew,
+ "sg.show.addnew": CMD_SickGearShowAddNew,
"show.cache": CMD_ShowCache,
+ "sg.show.cache": CMD_SickGearShowCache,
"show.delete": CMD_ShowDelete,
+ "sg.show.delete": CMD_SickGearShowDelete,
"show.getquality": CMD_ShowGetQuality,
+ "sg.show.getquality": CMD_SickGearShowGetQuality,
"show.getposter": CMD_ShowGetPoster,
+ "sg.show.getposter": CMD_SickGearShowGetPoster,
"show.getbanner": CMD_ShowGetBanner,
+ "sg.show.getbanner": CMD_SickGearShowGetBanner,
+ "sg.show.listfanart": CMD_SickGearShowListFanart,
+ "sg.show.ratefanart": CMD_SickGearShowRateFanart,
+ "sg.show.getfanart": CMD_SickGearShowGetFanart,
"show.pause": CMD_ShowPause,
+ "sg.show.pause": CMD_SickGearShowPause,
"show.refresh": CMD_ShowRefresh,
+ "sg.show.refresh": CMD_SickGearShowRefresh,
"show.seasonlist": CMD_ShowSeasonList,
+ "sg.show.seasonlist": CMD_SickGearShowSeasonList,
"show.seasons": CMD_ShowSeasons,
+ "sg.show.seasons": CMD_SickGearShowSeasons,
"show.setquality": CMD_ShowSetQuality,
+ "sg.show.setquality": CMD_SickGearShowSetQuality,
"show.stats": CMD_ShowStats,
+ "sg.show.stats": CMD_SickGearShowStats,
"show.update": CMD_ShowUpdate,
+ "sg.show.update": CMD_SickGearShowUpdate,
"shows": CMD_Shows,
- "shows.stats": CMD_ShowsStats
-}
+ "sg.shows": CMD_SickGearShows,
+ "sg.shows.browsetrakt": CMD_SickGearShowsBrowseTrakt,
+ "sg.listtraktaccounts": CMD_SickGearListTraktAccounts,
+ "shows.stats": CMD_ShowsStats,
+ "sg.shows.stats": CMD_SickGearShowsStats,
+ "sg.shows.forceupdate": CMD_SickGearShowsForceUpdate,
+ "sg.shows.queue": CMD_SickGearShowsQueue,
+ }
diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py
index 19919e6..1e37879 100644
--- a/sickbeard/webserve.py
+++ b/sickbeard/webserve.py
@@ -51,7 +51,7 @@ from sickbeard.helpers import has_image_ext, remove_article, starify
from sickbeard.indexers.indexer_config import INDEXER_TVDB, INDEXER_TVRAGE, INDEXER_TRAKT
from sickbeard.scene_numbering import get_scene_numbering, set_scene_numbering, get_scene_numbering_for_show, \
get_xem_numbering_for_show, get_scene_absolute_numbering_for_show, get_xem_absolute_numbering_for_show, \
- get_scene_absolute_numbering
+ get_scene_absolute_numbering, set_scene_numbering_helper
from sickbeard.name_cache import buildNameCache
from sickbeard.browser import foldersAtPath
from sickbeard.blackandwhitelist import BlackAndWhiteList, short_group_names
@@ -145,6 +145,13 @@ class BaseHandler(RequestHandler):
return self.get_secure_cookie('sickgear-session-%s' % helpers.md5_for_text(sickbeard.WEB_PORT))
return True
+ def getImage(self, image):
+ if ek.ek(os.path.isfile, image):
+ mime_type, encoding = MimeTypes().guess_type(image)
+ self.set_header('Content-Type', mime_type)
+ with ek.ek(open, image, 'rb') as img:
+ return img.read()
+
def showPoster(self, show=None, which=None, api=None):
# Redirect initial poster/banner thumb to default images
if 'poster' == which[0:6]:
@@ -175,9 +182,14 @@ class BaseHandler(RequestHandler):
static_image_path = image_file_name
if api:
+ used_file = ek.ek(os.path.basename, static_image_path)
+ if static_image_path.startswith('/images'):
+ used_file = 'default'
+ static_image_path = ek.ek(os.path.join, sickbeard.PROG_DIR, 'gui', 'slick', static_image_path[1:])
mime_type, encoding = MimeTypes().guess_type(static_image_path)
self.set_header('Content-Type', mime_type)
- with open(static_image_path, 'rb') as img:
+ self.set_header('X-Filename', used_file)
+ with ek.ek(open, static_image_path, 'rb') as img:
return img.read()
else:
static_image_path = os.path.normpath(static_image_path.replace(sickbeard.CACHE_DIR, '/cache'))
@@ -2458,50 +2470,8 @@ class Home(MainHandler):
def setSceneNumbering(self, show, indexer, forSeason=None, forEpisode=None, forAbsolute=None, sceneSeason=None,
sceneEpisode=None, sceneAbsolute=None):
- # sanitize:
- show = None if show in [None, 'null', ''] else int(show)
- indexer = None if indexer in [None, 'null', ''] else int(indexer)
-
- show_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, show)
-
- if not show_obj.is_anime:
- for_season = None if forSeason in [None, 'null', ''] else int(forSeason)
- for_episode = None if forEpisode in [None, 'null', ''] else int(forEpisode)
- scene_season = None if sceneSeason in [None, 'null', ''] else int(sceneSeason)
- scene_episode = None if sceneEpisode in [None, 'null', ''] else int(sceneEpisode)
- action_log = u'Set episode scene numbering to %sx%s for episode %sx%s of "%s"'\
- % (scene_season, scene_episode, for_season, for_episode, show_obj.name)
- ep_args = {'show': show, 'season': for_season, 'episode': for_episode}
- scene_args = {'indexer_id': show, 'indexer': indexer, 'season': for_season, 'episode': for_episode,
- 'sceneSeason': scene_season, 'sceneEpisode': scene_episode}
- result = {'forSeason': for_season, 'forEpisode': for_episode, 'sceneSeason': None, 'sceneEpisode': None}
- else:
- for_absolute = None if forAbsolute in [None, 'null', ''] else int(forAbsolute)
- scene_absolute = None if sceneAbsolute in [None, 'null', ''] else int(sceneAbsolute)
- action_log = u'Set absolute scene numbering to %s for episode %s of "%s"'\
- % (scene_absolute, for_absolute, show_obj.name)
- ep_args = {'show': show, 'absolute': for_absolute}
- scene_args = {'indexer_id': show, 'indexer': indexer, 'absolute_number': for_absolute,
- 'sceneAbsolute': scene_absolute}
- result = {'forAbsolute': for_absolute, 'sceneAbsolute': None}
-
- ep_obj = self._getEpisode(**ep_args)
- result['success'] = not isinstance(ep_obj, str)
- if result['success']:
- logger.log(action_log, logger.DEBUG)
- set_scene_numbering(**scene_args)
- show_obj.flushEpisodes()
- else:
- result['errorMessage'] = ep_obj
-
- if not show_obj.is_anime:
- scene_numbering = get_scene_numbering(show, indexer, for_season, for_episode)
- if scene_numbering:
- (result['sceneSeason'], result['sceneEpisode']) = scene_numbering
- else:
- scene_numbering = get_scene_absolute_numbering(show, indexer, for_absolute)
- if scene_numbering:
- result['sceneAbsolute'] = scene_numbering
+ result = set_scene_numbering_helper(show, indexer, forSeason, forEpisode, forAbsolute, sceneSeason,
+ sceneEpisode, sceneAbsolute)
return json.dumps(result)
@@ -2730,7 +2700,7 @@ class NewHomeAddShows(Home):
re.sub(r'([,.!][^,.!]*?)$', '...',
re.sub(r'([.!?])(?=\w)', r'\1 ',
self.encode_html((show.get('overview', '') or '')[:250:].strip()))),
- self._get_UWRatio(term, show['seriesname'], show.get('aliases', [])), None, None,
+ self.get_UWRatio(term, show['seriesname'], show.get('aliases', [])), None, None,
self._make_search_image_url(iid, show)
] for show in shows.itervalues()] for iid, shows in results.iteritems()))
@@ -2791,7 +2761,8 @@ class NewHomeAddShows(Home):
('%s.jpg' % show['id'], show['id'])
return img_url
- def _get_UWRatio(self, search_term, showname, aliases):
+ @classmethod
+ def get_UWRatio(cls, search_term, showname, aliases):
s = fuzz.UWRatio(search_term, showname)
# check aliases and give them a little lower score
for a in aliases:
@@ -3452,6 +3423,93 @@ class NewHomeAddShows(Home):
return self.redirect('/home/addShows/%s' % ('trakt_trending', sickbeard.TRAKT_MRU)[any(sickbeard.TRAKT_MRU)])
+ @staticmethod
+ def get_trakt_data(url_path, *args, **kwargs):
+ normalised, filtered = ([], [])
+ error_msg = None
+ try:
+ account = kwargs.get('send_oauth', None)
+ if account:
+ account = sickbeard.helpers.tryInt(account)
+ resp = TraktAPI().trakt_request('%sextended=full,images' % url_path, send_oauth=account)
+ if resp:
+ if 'show' in resp[0]:
+ if 'first_aired' in resp[0]:
+ for item in resp:
+ item['show']['first_aired'] = item['first_aired']
+ del item['first_aired']
+ normalised = resp
+ else:
+ for item in resp:
+ normalised.append({u'show': item})
+ del resp
+ except TraktAuthException as e:
+ logger.log(u'Pin authorisation needed to connect to Trakt service: %s' % ex(e), logger.WARNING)
+ error_msg = 'Unauthorized: Get another pin in the Notifications Trakt settings'
+ except TraktException as e:
+ logger.log(u'Could not connect to Trakt service: %s' % ex(e), logger.WARNING)
+ except (IndexError, KeyError):
+ pass
+
+ if not normalised:
+ error_msg = 'No items in watchlist. Use the "Add to watchlist" button at the Trakt website'
+ raise Exception(error_msg)
+
+ oldest_dt = 9999999
+ newest_dt = 0
+ oldest = None
+ newest = None
+ for item in normalised:
+ ignore = '''
+ ((bbc|channel\s*?5.*?|itv)\s*?(drama|documentaries))|bbc\s*?(comedy|music)|music\s*?specials|tedtalks
+ '''
+ if re.search(ignore, item['show']['title'].strip(), re.I | re.X):
+ continue
+ try:
+ dt = dateutil.parser.parse(item['show']['first_aired'])
+ dt_ordinal = dt.toordinal()
+ dt_string = sbdatetime.sbdatetime.sbfdate(dt)
+ if dt_ordinal < oldest_dt:
+ oldest_dt = dt_ordinal
+ oldest = dt_string
+ if dt_ordinal > newest_dt:
+ newest_dt = dt_ordinal
+ newest = dt_string
+
+ tmdbid = item.get('show', {}).get('ids', {}).get('tmdb', 0)
+ tvdbid = item.get('show', {}).get('ids', {}).get('tvdb', 0)
+ traktid = item.get('show', {}).get('ids', {}).get('trakt', 0)
+ images = dict(poster=dict(thumb='imagecache?path=browse/thumb/trakt&filename=%s&tmdbid=%s&tvdbid=%s' %
+ ('%s.jpg' % traktid, tmdbid, tvdbid)))
+
+ filtered.append(dict(
+ premiered=dt_ordinal,
+ premiered_str=dt_string,
+ when_past=dt_ordinal < datetime.datetime.now().toordinal(), # air time not yet available 16.11.2015
+ episode_number='' if 'episode' not in item else item['episode']['number'] or 1,
+ episode_overview=('' if 'episode' not in item else
+ item['episode']['overview'].strip() or ''),
+ episode_season='' if 'episode' not in item else item['episode']['season'] or 1,
+ genres=('' if 'genres' not in item['show'] else
+ ', '.join(['%s' % v for v in item['show']['genres']])),
+ ids=item['show']['ids'],
+ images=images,
+ overview=('' if 'overview' not in item['show'] or None is item['show']['overview'] else
+ item['show']['overview'].strip()),
+ rating=0 < item['show'].get('rating', 0) and
+ ('%.2f' % (item['show'].get('rating') * 10)).replace('.00', '') or 0,
+ title=item['show']['title'].strip(),
+ url_src_db='https://trakt.tv/shows/%s' % item['show']['ids']['slug'],
+ url_tvdb=('', '%s%s' % (sickbeard.indexerApi(INDEXER_TVDB).config['show_url'],
+ item['show']['ids']['tvdb']))[
+ isinstance(item['show']['ids']['tvdb'], (int, long))
+ and 0 < item['show']['ids']['tvdb']],
+ votes='0' if 'votes' not in item['show'] else item['show']['votes']))
+ except (StandardError, Exception):
+ pass
+
+ return filtered, oldest, newest
+
def browse_trakt(self, url_path, browse_title, *args, **kwargs):
browse_type = 'Trakt'
@@ -6233,6 +6291,8 @@ class ApiBuilder(MainHandler):
t.seasonSQLResults = seasonSQLResults
t.episodeSQLResults = episodeSQLResults
+ t.indexers = sickbeard.indexerApi().all_indexers
+ t.searchindexers = sickbeard.indexerApi().search_indexers
if len(sickbeard.API_KEY) == 32:
t.apikey = sickbeard.API_KEY