* Added saving of changed newznab categories in backend.

Added gui for selecting categories from multiselect box.
Created some helper function in js, for dynamically modifying selects/options

Made results of function for retrieving newznab capabilities more generic. In that now always a valid json is returned with success,tv_categories,error

Added gui elements for retrieving and displaying newznab capabilities

Added backend functions for calling ajax /getNewznabCategories?name=yourNewznabProvider&url=https://newznabprovURL&key=YourApiKey
Returns json.dumps() with TV category capabilities of newznab provider. Is going to be used for new gui element in adding newsnab provider.
This commit is contained in:
Woodpaker 2014-09-01 17:57:52 +02:00
parent 90356544cd
commit 000467cc73
4 changed files with 283 additions and 35 deletions

View File

@ -22,7 +22,7 @@
\$(document).ready(function(){ \$(document).ready(function(){
var show_nzb_providers = #if $sickbeard.USE_NZBS then "true" else "false"#; var show_nzb_providers = #if $sickbeard.USE_NZBS then "true" else "false"#;
#for $curNewznabProvider in $sickbeard.newznabProviderList: #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 #end for
}); });
//--> //-->
@ -572,6 +572,24 @@ var show_nzb_providers = #if $sickbeard.USE_NZBS then "true" else "false"#;
<span class="component-desc">(if not required, type 0)</span> <span class="component-desc">(if not required, type 0)</span>
</label> </label>
</div> </div>
<div class="field-pair">
<label class="nocheck clearfix">
<span class="component-title">NewzNab search categories</span>
<select id="newznab_cap" multiple="multiple" style="min-width:10em;" >
</select>
<select id="newznab_cat" multiple="multiple" style="min-width:10em;" >
</select>
</label>
<label class="clearfix">
<span class="component-title">&nbsp;</span>
<span class="component-desc">(Select your Newznab categories on the left, and click the "update categories" button to use them for searching.)
Don't forget to to save the form!</span>
</label>
<input class="btn" type="button" class="newznab_cat_update" id="newznab_cat_update" value="Update Categories" />
</div>
<div id="newznab_add_div"> <div id="newznab_add_div">
<input class="btn" type="button" class="newznab_save" id="newznab_add" value="Add" /> <input class="btn" type="button" class="newznab_save" id="newznab_add" value="Add" />
</div> </div>

View File

@ -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); url = $.trim(url);
if (!url) if (!url)
@ -25,7 +59,7 @@ $(document).ready(function(){
if (url.match('/$') == null) if (url.match('/$') == null)
url = url + '/'; url = url + '/';
var newData = [isDefault, [name, url, key]]; var newData = [isDefault, [name, url, key, cat]];
newznabProviders[id] = newData; newznabProviders[id] = newData;
if (!isDefault){ 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][1] = url;
newznabProviders[id][1][2] = key; newznabProviders[id][1][2] = key;
newznabProviders[id][1][3] = cat;
$(this).populateNewznabSection(); $(this).populateNewznabSection();
@ -108,17 +143,49 @@ $(document).ready(function(){
var isDefault = 0; var isDefault = 0;
$('#newznab_add_div').show(); $('#newznab_add_div').show();
$('#newznab_update_div').hide(); $('#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 { } else {
var data = newznabProviders[selectedProvider][1]; var data = newznabProviders[selectedProvider][1];
var isDefault = newznabProviders[selectedProvider][0]; var isDefault = newznabProviders[selectedProvider][0];
$('#newznab_add_div').hide(); $('#newznab_add_div').hide();
$('#newznab_update_div').show(); $('#newznab_update_div').show();
$('#newznab_cat').removeAttr("disabled");
$('#newznab_cap').removeAttr("disabled");
} }
$('#newznab_name').val(data[0]); $('#newznab_name').val(data[0]);
$('#newznab_url').val(data[1]); $('#newznab_url').val(data[1]);
$('#newznab_key').val(data[2]); $('#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') { if (selectedProvider == 'addNewznab') {
$('#newznab_name').removeAttr("disabled"); $('#newznab_name').removeAttr("disabled");
$('#newznab_url').removeAttr("disabled"); $('#newznab_url').removeAttr("disabled");
@ -132,11 +199,51 @@ $(document).ready(function(){
} else { } else {
$('#newznab_url').removeAttr("disabled"); $('#newznab_url').removeAttr("disabled");
$('#newznab_delete').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() { $.fn.makeNewznabProviderString = function() {
var provStrings = new Array(); var provStrings = new Array();
@ -294,9 +401,10 @@ $(document).ready(function(){
provider_id = provider_id.substring(0, provider_id.length-'_hash'.length); provider_id = provider_id.substring(0, provider_id.length-'_hash'.length);
var url = $('#'+provider_id+'_url').val(); var url = $('#'+provider_id+'_url').val();
var cat = $('#'+provider_id+'_cat').val();
var key = $(this).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 url = $('#newznab_url').val();
var key = $('#newznab_key').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).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(){ $('#newznab_add').click(function(){
var selectedProvider = $('#editANewznabProvider :selected').val(); var selectedProvider = $('#editANewznabProvider :selected').val();
@ -351,6 +505,11 @@ $(document).ready(function(){
var name = $.trim($('#newznab_name').val()); var name = $.trim($('#newznab_name').val());
var url = $.trim($('#newznab_url').val()); var url = $.trim($('#newznab_url').val());
var key = $.trim($('#newznab_key').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) if (!name)
return; return;
@ -371,7 +530,7 @@ $(document).ready(function(){
return; 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); $(this).makeTorrentOptionString(provider_id);
}); });
$.fn.replaceOptions = function(options) {
var self, $option;
this.empty();
self = this;
$.each(options, function(index, option) {
$option = $("<option></option>")
.attr("value", option.value)
.text(option.text);
self.append($option);
});
};
// initialization stuff // initialization stuff
$.fn.newznabProvidersCapabilities = [];
$(this).hideConfigTab(); $(this).hideConfigTab();
$(this).showHideProviders(); $(this).showHideProviders();

View File

@ -37,6 +37,9 @@ from sickbeard import logger
from sickbeard import tvcache from sickbeard import tvcache
from sickbeard.exceptions import ex, AuthException from sickbeard.exceptions import ex, AuthException
from lib import requests
from lib.requests import exceptions
from lib.bencode import bdecode
class NewznabProvider(generic.NZBProvider): class NewznabProvider(generic.NZBProvider):
def __init__(self, name, url, key='', catIDs='5030,5040', search_mode='eponly', search_fallback=False, 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_mode = search_mode
self.search_fallback = search_fallback 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 # a 0 in the key spot indicates that no key is needed
if self.key == '0': if self.key == '0':
@ -86,6 +87,52 @@ class NewznabProvider(generic.NZBProvider):
def isEnabled(self): def isEnabled(self):
return self.enabled 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): def _get_season_search_strings(self, ep_obj):
to_return = [] to_return = []

View File

@ -1909,6 +1909,34 @@ class ConfigProviders(MainHandler):
sickbeard.newznabProviderList.append(newProvider) sickbeard.newznabProviderList.append(newProvider)
return newProvider.getID() + '|' + newProvider.configStr() 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): def deleteNewznabProvider(self, nnid):
@ -2002,46 +2030,24 @@ class ConfigProviders(MainHandler):
if not curNewznabProviderStr: if not curNewznabProviderStr:
continue 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) cur_url = config.clean_url(cur_url)
newProvider = newznab.NewznabProvider(cur_name, cur_url, key=cur_key) newProvider = newznab.NewznabProvider(cur_name, cur_url, key=cur_key)
cur_id = newProvider.getID() cur_id = newProvider.getID()
# if it already exists then update it # if it already exists then update it
if cur_id in newznabProviderDict: if cur_id in newznabProviderDict:
newznabProviderDict[cur_id].name = cur_name newznabProviderDict[cur_id].name = cur_name
newznabProviderDict[cur_id].url = cur_url newznabProviderDict[cur_id].url = cur_url
newznabProviderDict[cur_id].key = cur_key newznabProviderDict[cur_id].key = cur_key
newznabProviderDict[cur_id].catIDs = cur_cat
# a 0 in the key spot indicates that no key is needed # a 0 in the key spot indicates that no key is needed
if cur_key == '0': if cur_key == '0':
newznabProviderDict[cur_id].needs_auth = False newznabProviderDict[cur_id].needs_auth = False
else: else:
newznabProviderDict[cur_id].needs_auth = True 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: else:
sickbeard.newznabProviderList.append(newProvider) sickbeard.newznabProviderList.append(newProvider)