@@ -2152,11 +2205,18 @@
var deletedNewznabs = 0;
- $(".remove").click(function() {
+ $(".removeNewznab").click(function() {
$(this).parent().parent().remove();
deletedNewznabs = deletedNewznabs + 1;
});
+ var deletedTorznabs = 0;
+
+ $(".removeTorznab").click(function() {
+ $(this).parent().parent().remove();
+ deletedTorznabs = deletedTorznabs + 1;
+ });
+
$("#modify_extras").click(openExtrasDialog);
$("#include_extras").click(function(){
@@ -2179,7 +2239,7 @@
$("#add_newznab").click(function() {
var intId = $("#newznab_providers > div").size() + deletedNewznabs + 1;
var formfields = $("
");
- var removeButton = $("
");
+ var removeButton = $("
");
removeButton.click(function() {
$(this).parent().remove();
deletedNewznabs = deletedNewznabs + 1;
@@ -2190,6 +2250,20 @@
$("#add_newznab").before(formfields);
});
+ $("#add_torznab").click(function() {
+ var intId = $("#torznab_providers > div").size() + deletedTorznabs + 1;
+ var formfields = $("
");
+ $("#add_torznab").before(formfields);
+ });
+
$(".hpuser").keyup(function() {
$(".hpuser").val($(this).val());
});
@@ -2206,6 +2280,7 @@
initConfigCheckbox("#use_newznab");
initConfigCheckbox("#use_nzbsorg");
initConfigCheckbox("#use_omgwtfnzbs");
+ initConfigCheckbox("#use_torznab");
initConfigCheckbox("#use_kat");
initConfigCheckbox("#use_piratebay");
initConfigCheckbox("#use_oldpiratebay");
diff --git a/headphones/config.py b/headphones/config.py
index 97ad7c62..67b7af72 100644
--- a/headphones/config.py
+++ b/headphones/config.py
@@ -81,6 +81,7 @@ _CONFIG_DEFINITIONS = {
'ENCODER_PATH': (str, 'General', ''),
'EXTRAS': (str, 'General', ''),
'EXTRA_NEWZNABS': (list, 'Newznab', ''),
+ 'EXTRA_TORZNABS': (list, 'Torznab', ''),
'FILE_FORMAT': (str, 'General', 'Track Artist - Album [Year] - Title'),
'FILE_PERMISSIONS': (str, 'General', '0644'),
'FILE_UNDERSCORES': (int, 'General', 0),
@@ -140,6 +141,10 @@ _CONFIG_DEFINITIONS = {
'NEWZNAB_APIKEY': (str, 'Newznab', ''),
'NEWZNAB_ENABLED': (int, 'Newznab', 1),
'NEWZNAB_HOST': (str, 'Newznab', ''),
+ 'TORZNAB': (int, 'Torznab', 0),
+ 'TORZNAB_APIKEY': (str, 'Torznab', ''),
+ 'TORZNAB_ENABLED': (int, 'Torznab', 1),
+ 'TORZNAB_HOST': (str, 'Torznab', ''),
'NMA_APIKEY': (str, 'NMA', ''),
'NMA_ENABLED': (int, 'NMA', 0),
'NMA_ONSNATCH': (int, 'NMA', 0),
@@ -351,6 +356,25 @@ class Config(object):
extra_newznabs.append(item)
self.EXTRA_NEWZNABS = extra_newznabs
+ def get_extra_torznabs(self):
+ """ Return the extra torznab tuples """
+ extra_torznabs = list(
+ itertools.izip(*[itertools.islice(self.EXTRA_TORZNABS, i, None, 3)
+ for i in range(3)])
+ )
+ return extra_torznabs
+
+ def clear_extra_torznabs(self):
+ """ Forget about the configured extra torznabs """
+ self.EXTRA_TORZNABS = []
+
+ def add_extra_torznab(self, torznab):
+ """ Add a new extra torznab """
+ extra_torznabs = self.EXTRA_TORZNABS
+ for item in torznab:
+ extra_torznabs.append(item)
+ self.EXTRA_TORZNABS = extra_torznabs
+
def __getattr__(self, name):
"""
Returns something from the ini unless it is a real property
diff --git a/headphones/searcher.py b/headphones/searcher.py
index a498d619..dbfab875 100644
--- a/headphones/searcher.py
+++ b/headphones/searcher.py
@@ -228,7 +228,7 @@ def do_sorted_search(album, new, losslessOnly, choose_specific_download=False):
NZB_PROVIDERS = (headphones.CONFIG.HEADPHONES_INDEXER or headphones.CONFIG.NEWZNAB or headphones.CONFIG.NZBSORG or headphones.CONFIG.OMGWTFNZBS)
NZB_DOWNLOADERS = (headphones.CONFIG.SAB_HOST or headphones.CONFIG.BLACKHOLE_DIR or headphones.CONFIG.NZBGET_HOST)
- TORRENT_PROVIDERS = (headphones.CONFIG.KAT or headphones.CONFIG.PIRATEBAY or headphones.CONFIG.OLDPIRATEBAY or headphones.CONFIG.MININOVA or headphones.CONFIG.WAFFLES or headphones.CONFIG.RUTRACKER or headphones.CONFIG.WHATCD)
+ TORRENT_PROVIDERS = (headphones.CONFIG.TORZNAB or headphones.CONFIG.KAT or headphones.CONFIG.PIRATEBAY or headphones.CONFIG.OLDPIRATEBAY or headphones.CONFIG.MININOVA or headphones.CONFIG.WAFFLES or headphones.CONFIG.RUTRACKER or headphones.CONFIG.WHATCD)
results = []
myDB = db.DBConnection()
@@ -1086,6 +1086,68 @@ def searchTorrent(album, new=False, losslessOnly=False, albumlength=None, choose
return proxy_url
+ if headphones.CONFIG.TORZNAB:
+ provider = "torznab"
+ torznab_hosts = []
+
+ if headphones.CONFIG.TORZNAB_HOST and headphones.CONFIG.TORZNAB_ENABLED:
+ torznab_hosts.append((headphones.CONFIG.TORZNAB_HOST, headphones.CONFIG.TORZNAB_APIKEY, headphones.CONFIG.TORZNAB_ENABLED))
+
+ for torznab_host in headphones.CONFIG.get_extra_torznabs():
+ if torznab_host[2] == '1' or torznab_host[2] == 1:
+ torznab_hosts.append(torznab_host)
+
+ if headphones.CONFIG.PREFERRED_QUALITY == 3 or losslessOnly:
+ categories = "3040"
+ elif headphones.CONFIG.PREFERRED_QUALITY == 1 or allow_lossless:
+ categories = "3040,3010"
+ else:
+ categories = "3010"
+
+ if album['Type'] == 'Other':
+ categories = "3030"
+ logger.info("Album type is audiobook/spokenword. Using audiobook category")
+
+ for torznab_host in torznab_hosts:
+
+ provider = torznab_host[0]
+
+ # Request results
+ logger.info('Parsing results from %s using search term: %s' % (torznab_host[0],term))
+
+ headers = {'User-Agent': USER_AGENT}
+ params = {
+ "t": "search",
+ "apikey": torznab_host[1],
+ "cat": categories,
+ "maxage": headphones.CONFIG.USENET_RETENTION,
+ "q": term
+ }
+
+ data = request.request_feed(
+ url=torznab_host[0] + '/api?',
+ params=params, headers=headers
+ )
+
+ # Process feed
+ if data:
+ if not len(data.entries):
+ logger.info(u"No results found from %s for %s", torznab_host[0], term)
+ else:
+ for item in data.entries:
+ try:
+ url = item.link
+ title = item.title
+ size = int(item.links[1]['length'])
+ if all(word.lower() in title.lower() for word in term.split()):
+ logger.info('Found %s. Size: %s' % (title, helpers.bytes_to_mb(size)))
+ resultlist.append((title, size, url, provider, 'torrent', True))
+ else:
+ logger.info('Skipping %s, not all search term words found' % title)
+
+ except Exception as e:
+ logger.exception("An unknown error occurred trying to parse the feed: %s" % e)
+
if headphones.CONFIG.KAT:
provider = "Kick Ass Torrents"
ka_term = term.replace("!", "")
diff --git a/headphones/webserve.py b/headphones/webserve.py
index bc6d4aa7..59418d28 100644
--- a/headphones/webserve.py
+++ b/headphones/webserve.py
@@ -1065,6 +1065,11 @@ class WebInterface(object):
"newznab_apikey": headphones.CONFIG.NEWZNAB_APIKEY,
"newznab_enabled": checked(headphones.CONFIG.NEWZNAB_ENABLED),
"extra_newznabs": headphones.CONFIG.get_extra_newznabs(),
+ "use_torznab": checked(headphones.CONFIG.TORZNAB),
+ "torznab_host": headphones.CONFIG.TORZNAB_HOST,
+ "torznab_apikey": headphones.CONFIG.TORZNAB_APIKEY,
+ "torznab_enabled": checked(headphones.CONFIG.TORZNAB_ENABLED),
+ "extra_torznabs": headphones.CONFIG.get_extra_torznabs(),
"use_nzbsorg": checked(headphones.CONFIG.NZBSORG),
"nzbsorg_uid": headphones.CONFIG.NZBSORG_UID,
"nzbsorg_hash": headphones.CONFIG.NZBSORG_HASH,
@@ -1278,7 +1283,7 @@ class WebInterface(object):
# Handle the variable config options. Note - keys with False values aren't getting passed
checked_configs = [
- "launch_browser", "enable_https", "api_enabled", "use_blackhole", "headphones_indexer", "use_newznab", "newznab_enabled",
+ "launch_browser", "enable_https", "api_enabled", "use_blackhole", "headphones_indexer", "use_newznab", "newznab_enabled", "use_torznab", "torznab_enabled",
"use_nzbsorg", "use_omgwtfnzbs", "use_kat", "use_piratebay", "use_oldpiratebay", "use_mininova", "use_waffles", "use_rutracker",
"use_whatcd", "preferred_bitrate_allow_lossless", "detect_bitrate", "ignore_clean_releases", "freeze_db", "cue_split", "move_files",
"rename_files", "correct_metadata", "cleanup_files", "keep_nfo", "add_album_art", "embed_album_art", "embed_lyrics",
@@ -1316,6 +1321,21 @@ class WebInterface(object):
del kwargs[key]
extra_newznabs.append((newznab_host, newznab_api, newznab_enabled))
+ extra_torznabs = []
+ for kwarg in [x for x in kwargs if x.startswith('torznab_host')]:
+ torznab_host_key = kwarg
+ torznab_number = kwarg[12:]
+ if len(torznab_number):
+ torznab_api_key = 'torznab_api' + torznab_number
+ torznab_enabled_key = 'torznab_enabled' + torznab_number
+ torznab_host = kwargs.get(torznab_host_key, '')
+ torznab_api = kwargs.get(torznab_api_key, '')
+ torznab_enabled = int(kwargs.get(torznab_enabled_key, 0))
+ for key in [torznab_host_key, torznab_api_key, torznab_enabled_key]:
+ if key in kwargs:
+ del kwargs[key]
+ extra_torznabs.append((torznab_host, torznab_api, torznab_enabled))
+
# Convert the extras to list then string. Coming in as 0 or 1 (append new extras to the end)
temp_extras_list = []
@@ -1346,6 +1366,11 @@ class WebInterface(object):
for extra_newznab in extra_newznabs:
headphones.CONFIG.add_extra_newznab(extra_newznab)
+ headphones.CONFIG.clear_extra_torznabs()
+ headphones.CONFIG.process_kwargs(kwargs)
+ for extra_torznab in extra_torznabs:
+ headphones.CONFIG.add_extra_torznab(extra_torznab)
+
# Sanity checking
if headphones.CONFIG.SEARCH_INTERVAL and headphones.CONFIG.SEARCH_INTERVAL < 360:
logger.info("Search interval too low. Resetting to 6 hour minimum")