diff --git a/gui/slick/interfaces/default/config_providers.tmpl b/gui/slick/interfaces/default/config_providers.tmpl index b0aae4eb..97081ee6 100644 --- a/gui/slick/interfaces/default/config_providers.tmpl +++ b/gui/slick/interfaces/default/config_providers.tmpl @@ -22,7 +22,7 @@ \$(document).ready(function(){ var show_nzb_providers = #if $sickbeard.USE_NZBS then "true" else "false"#; #for $curNewznabProvider in $sickbeard.newznabProviderList: -\$(this).addProvider('$curNewznabProvider.getID()', '$curNewznabProvider.name', '$curNewznabProvider.url', '$curNewznabProvider.key', $int($curNewznabProvider.default), show_nzb_providers); +\$(this).addProvider('$curNewznabProvider.getID()', '$curNewznabProvider.name', '$curNewznabProvider.url', '$curNewznabProvider.key', '$curNewznabProvider.catIDs', $int($curNewznabProvider.default), show_nzb_providers); #end for }); //--> @@ -572,6 +572,24 @@ var show_nzb_providers = #if $sickbeard.USE_NZBS then "true" else "false"#; (if not required, type 0) + +
+ + + +
+
diff --git a/gui/slick/js/configProviders.js b/gui/slick/js/configProviders.js index 52eb6a39..56fd8c93 100644 --- a/gui/slick/js/configProviders.js +++ b/gui/slick/js/configProviders.js @@ -13,7 +13,41 @@ $(document).ready(function(){ }); } - $.fn.addProvider = function (id, name, url, key, isDefault, showProvider) { + + $.fn.getCategories = function (isDefault, name, url, key) { + + if (!name) + return; + + if (!url) + return; + + if (!key) + return; + + var params = {url: url, name: name, key: key}; + var returnData; + + $.ajaxSetup( { "async": false } ); + $.getJSON(sbRoot + '/config/providers/getNewznabCategories', params, + function(data){ + if (data.error != "") { + alert(data.error); + return false; + } + + if (data.success == false) { + return false; + } + + console.debug(data.tv_categories); + returnData = data; + }); + $.ajaxSetup( { "async": true } ); + return returnData; + } + + $.fn.addProvider = function (id, name, url, key, cat, isDefault, showProvider) { url = $.trim(url); if (!url) @@ -25,7 +59,7 @@ $(document).ready(function(){ if (url.match('/$') == null) url = url + '/'; - var newData = [isDefault, [name, url, key]]; + var newData = [isDefault, [name, url, key, cat]]; newznabProviders[id] = newData; if (!isDefault){ @@ -63,10 +97,11 @@ $(document).ready(function(){ } - $.fn.updateProvider = function (id, url, key) { + $.fn.updateProvider = function (id, url, key, cat) { newznabProviders[id][1][1] = url; newznabProviders[id][1][2] = key; + newznabProviders[id][1][3] = cat; $(this).populateNewznabSection(); @@ -108,17 +143,49 @@ $(document).ready(function(){ var isDefault = 0; $('#newznab_add_div').show(); $('#newznab_update_div').hide(); + $('#newznab_cat').attr('disabled','disabled'); + $('#newznab_cap').attr('disabled','disabled'); + + $("#newznab_cat option").each(function() { + $(this).remove(); + return; + }); + + $("#newznab_cap option").each(function() { + $(this).remove(); + return; + }); + } else { var data = newznabProviders[selectedProvider][1]; var isDefault = newznabProviders[selectedProvider][0]; $('#newznab_add_div').hide(); $('#newznab_update_div').show(); + $('#newznab_cat').removeAttr("disabled"); + $('#newznab_cap').removeAttr("disabled"); } $('#newznab_name').val(data[0]); $('#newznab_url').val(data[1]); $('#newznab_key').val(data[2]); - + + //Check if not already array + if (typeof data[3] === 'string') { + rrcat = data[3].split(",") + } + else { + rrcat = data[3]; + } + + // Update the category select box (on the right) + var newCatOptions = []; + if (rrcat) { + rrcat.forEach(function (cat) { + newCatOptions.push({text : cat, value : cat}); + }); + $("#newznab_cat").replaceOptions(newCatOptions); + }; + if (selectedProvider == 'addNewznab') { $('#newznab_name').removeAttr("disabled"); $('#newznab_url').removeAttr("disabled"); @@ -132,11 +199,51 @@ $(document).ready(function(){ } else { $('#newznab_url').removeAttr("disabled"); $('#newznab_delete').removeAttr("disabled"); + + //Get Categories Capabilities + if (data[0] && data[1] && data[2] && !ifExists($.fn.newznabProvidersCapabilities, data[0])) { + var categoryresult = $(this).getCategories(isDefault, data[0], data[1], data[2]); + if (categoryresult && categoryresult.success && categoryresult.tv_categories) { + $.fn.newznabProvidersCapabilities.push({'name' : data[0], 'categories' : categoryresult.tv_categories}); + } + + } + + //Loop through the array and if currently selected newznab provider name matches one in the array, use it to + //update the capabilities select box (on the left). + if (data[0]) { + $.fn.newznabProvidersCapabilities.forEach(function(newzNabCap) { + + if (newzNabCap.name && newzNabCap.name == data[0] && newzNabCap.categories instanceof Array) { + var newCapOptions = []; + newzNabCap.categories.forEach(function(category_set) { + if (category_set.id && category_set.name) { + newCapOptions.push({value : category_set.id, text : category_set.name + "(" + category_set.id + ")"}); + }; + }); + $("#newznab_cap").replaceOptions(newCapOptions); + } + + }); + }; + } } } + ifExists = function(loopThroughArray, searchFor) { + var found = false; + + loopThroughArray.forEach(function(rootObject) { + if (rootObject.name == searchFor) { + found = true; + } + console.log(rootObject.name + " while searching for: "+ searchFor); + }); + return found; + }; + $.fn.makeNewznabProviderString = function() { var provStrings = new Array(); @@ -294,9 +401,10 @@ $(document).ready(function(){ provider_id = provider_id.substring(0, provider_id.length-'_hash'.length); var url = $('#'+provider_id+'_url').val(); + var cat = $('#'+provider_id+'_cat').val(); var key = $(this).val(); - $(this).updateProvider(provider_id, url, key); + $(this).updateProvider(provider_id, url, key, cat); }); @@ -310,7 +418,11 @@ $(document).ready(function(){ var url = $('#newznab_url').val(); var key = $('#newznab_key').val(); - $(this).updateProvider(selectedProvider, url, key); + var cat = $('#newznab_cat option').map(function(i, opt) { + return $(opt).text(); + }).toArray().join(','); + + $(this).updateProvider(selectedProvider, url, key, cat); }); @@ -344,6 +456,48 @@ $(document).ready(function(){ $(this).refreshProviderList(); }); + $(this).on('click', '#newznab_cat_update', function(){ + console.debug('Clicked Button'); + + //Maybe check if there is anything selected? + $("#newznab_cat option").each(function() { + $(this).remove(); + return; + }); + + var newOptions = []; + + // When the update botton is clicked, loop through the capabilities list + // and copy the selected category id's to the category list on the right. + $("#newznab_cap option").each(function(){ + if($(this).attr('selected') == 'selected') + { + var selected_cat = $(this).val(); + console.debug(selected_cat); + newOptions.push({text: selected_cat, value: selected_cat}) + }; + }); + + $("#newznab_cat").replaceOptions(newOptions); + + var selectedProvider = $('#editANewznabProvider :selected').val(); + if (selectedProvider == "addNewznab") + return; + + var url = $('#newznab_url').val(); + var key = $('#newznab_key').val(); + + var cat = $('#newznab_cat option').map(function(i, opt) { + return $(opt).text(); + }).toArray().join(','); + + $("#newznab_cat option:not([value])").remove(); + + $(this).updateProvider(selectedProvider, url, key, cat); + + }); + + $('#newznab_add').click(function(){ var selectedProvider = $('#editANewznabProvider :selected').val(); @@ -351,6 +505,11 @@ $(document).ready(function(){ var name = $.trim($('#newznab_name').val()); var url = $.trim($('#newznab_url').val()); var key = $.trim($('#newznab_key').val()); + //var cat = $.trim($('#newznab_cat').val()); + + var cat = $.trim($('#newznab_cat option').map(function(i, opt) { + return $(opt).text();}).toArray().join(',')); + if (!name) return; @@ -371,7 +530,7 @@ $(document).ready(function(){ return; } - $(this).addProvider(data.success, name, url, key, 0); + $(this).addProvider(data.success, name, url, key, cat, 0); }); }); @@ -465,9 +624,27 @@ $(document).ready(function(){ $(this).makeTorrentOptionString(provider_id); }); + + + $.fn.replaceOptions = function(options) { + var self, $option; + + this.empty(); + self = this; + + $.each(options, function(index, option) { + $option = $("") + .attr("value", option.value) + .text(option.text); + self.append($option); + }); + }; // initialization stuff + + $.fn.newznabProvidersCapabilities = []; + $(this).hideConfigTab(); $(this).showHideProviders(); diff --git a/sickbeard/providers/newznab.py b/sickbeard/providers/newznab.py index d4686b52..10f57251 100755 --- a/sickbeard/providers/newznab.py +++ b/sickbeard/providers/newznab.py @@ -37,6 +37,9 @@ from sickbeard import logger from sickbeard import tvcache from sickbeard.exceptions import ex, AuthException +from lib import requests +from lib.requests import exceptions +from lib.bencode import bdecode class NewznabProvider(generic.NZBProvider): def __init__(self, name, url, key='', catIDs='5030,5040', search_mode='eponly', search_fallback=False, @@ -52,8 +55,6 @@ class NewznabProvider(generic.NZBProvider): self.search_mode = search_mode self.search_fallback = search_fallback - self.enable_daily = enable_daily - self.enable_backlog = enable_backlog # a 0 in the key spot indicates that no key is needed if self.key == '0': @@ -86,6 +87,52 @@ class NewznabProvider(generic.NZBProvider): def isEnabled(self): return self.enabled + def _getURL(self, url, post_data=None, params=None, timeout=30, json=False): + """ + By default this is just a simple urlopen call but this method should be overridden + for providers with special URL requirements (like cookies) + Not really changed much from the superclass, can be used in future. + """ + + # check for auth + if not self._doLogin(): + return + + return helpers.getURL(url, post_data=post_data, params=params, headers=self.headers, timeout=timeout, + session=self.session, json=json) + + def get_newznab_categories(self): + """ + Uses the newznab provider url and apikey to get the capabilities. + Makes use of the default newznab caps param. e.a. http://yournewznab/api?t=caps&apikey=skdfiw7823sdkdsfjsfk + Returns a tuple with (succes or not, array with dicts [{"id": "5070", "name": "Anime"}, + {"id": "5080", "name": "Documentary"}, {"id": "5020", "name": "Foreign"}...etc}], error message) + """ + return_categories = [] + + self._checkAuth() + + params = {"t": "caps"} + if self.needs_auth and self.key: + params['apikey'] = self.key + + categories = self.getURL("%s/api" % (self.url), params=params) + + xml_categories = helpers.parse_xml(categories) + + if not xml_categories: + return (False, return_categories, "Error parsing xml for [%s]" % (self.name)) + + try: + for category in xml_categories.iter('category'): + if category.get('name') == 'TV': + for subcat in category.findall('subcat'): + return_categories.append(subcat.attrib) + except: + return (False, return_categories, "Error parsing result for [%s]" % (self.name)) + + return (True, return_categories, "") + def _get_season_search_strings(self, ep_obj): to_return = [] diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py index f05bf557..9b15db9a 100644 --- a/sickbeard/webserve.py +++ b/sickbeard/webserve.py @@ -1909,6 +1909,34 @@ class ConfigProviders(MainHandler): sickbeard.newznabProviderList.append(newProvider) return newProvider.getID() + '|' + newProvider.configStr() + def getNewznabCategories(self, name, url, key): + ''' + Retrieves a list of possible categories with category id's + Using the default url/api?cat + http://yournewznaburl.com/api?t=caps&apikey=yourapikey + ''' + error = "" + success = False + + if not name: + error += "\nNo Provider Name specified" + if not url: + error += "\nNo Provider Url specified" + if not key: + error += "\nNo Provider Api key specified" + + if error <> "": + return json.dumps({'success' : False, 'error': error}) + + #Get list with Newznabproviders + #providerDict = dict(zip([x.getID() for x in sickbeard.newznabProviderList], sickbeard.newznabProviderList)) + + #Get newznabprovider obj with provided name + tempProvider= newznab.NewznabProvider(name, url, key) + + success, tv_categories, error = tempProvider.get_newznab_categories() + + return json.dumps({'success' : success,'tv_categories' : tv_categories, 'error' : error}) def deleteNewznabProvider(self, nnid): @@ -2002,46 +2030,24 @@ class ConfigProviders(MainHandler): if not curNewznabProviderStr: continue - cur_name, cur_url, cur_key = curNewznabProviderStr.split('|') + cur_name, cur_url, cur_key, cur_cat = curNewznabProviderStr.split('|') cur_url = config.clean_url(cur_url) newProvider = newznab.NewznabProvider(cur_name, cur_url, key=cur_key) + cur_id = newProvider.getID() # if it already exists then update it if cur_id in newznabProviderDict: newznabProviderDict[cur_id].name = cur_name newznabProviderDict[cur_id].url = cur_url - newznabProviderDict[cur_id].key = cur_key + newznabProviderDict[cur_id].catIDs = cur_cat # a 0 in the key spot indicates that no key is needed if cur_key == '0': newznabProviderDict[cur_id].needs_auth = False else: newznabProviderDict[cur_id].needs_auth = True - - try: - newznabProviderDict[cur_id].search_mode = str(kwargs[cur_id + '_search_mode']).strip() - except: - pass - - try: - newznabProviderDict[cur_id].search_fallback = config.checkbox_to_value( - kwargs[cur_id + '_search_fallback']) - except: - pass - - try: - newznabProviderDict[cur_id].enable_daily = config.checkbox_to_value( - kwargs[cur_id + '_enable_daily']) - except: - pass - - try: - newznabProviderDict[cur_id].enable_backlog = config.checkbox_to_value( - kwargs[cur_id + '_enable_backlog']) - except: - pass else: sickbeard.newznabProviderList.append(newProvider)