mirror of
https://github.com/moparisthebest/SickRage
synced 2024-11-17 23:05:11 -05:00
14c354b551
Provider getURL and downloadResult functions now removed and replaced with ones from helpers.py to help slim the code down plus allow more better control over request sessions. Removed TVTumbler code. Fixed HDBits provider. Fixed config settings that were ment to be booleans but instead where set as str or int, should help resolve random html errors. XEM Refresh check re-coded. NameParser code for creating show object has been changed to only attempt at the very end once its found the bestMatch result, helps on resources and performance.
341 lines
12 KiB
Python
341 lines
12 KiB
Python
# Author: Nic Wolfe <nic@wolfeden.ca>
|
|
# URL: http://code.google.com/p/sickbeard/
|
|
#
|
|
# This file is part of SickRage.
|
|
#
|
|
# SickRage is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# SickRage is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with SickRage. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import os
|
|
import re
|
|
import sys
|
|
import time
|
|
import urllib, urlparse
|
|
|
|
from datetime import datetime, timedelta
|
|
|
|
import sickbeard
|
|
import generic
|
|
|
|
import sickbeard.encodingKludge as ek
|
|
from sickbeard import classes, logger, helpers, exceptions, show_name_helpers
|
|
from sickbeard import tvcache
|
|
from sickbeard.common import Quality
|
|
from sickbeard.exceptions import ex
|
|
from lib.dateutil.parser import parse as parseDate
|
|
|
|
|
|
class NewzbinDownloader(urllib.FancyURLopener):
|
|
def __init__(self):
|
|
urllib.FancyURLopener.__init__(self)
|
|
|
|
def http_error_default(self, url, fp, errcode, errmsg, headers):
|
|
|
|
# if newzbin is throttling us, wait seconds and try again
|
|
if errcode == 400:
|
|
|
|
newzbinErrCode = int(headers.getheader('X-DNZB-RCode'))
|
|
|
|
if newzbinErrCode == 450:
|
|
rtext = str(headers.getheader('X-DNZB-RText'))
|
|
result = re.search("wait (\d+) seconds", rtext)
|
|
logger.log("Newzbin throttled our NZB downloading, pausing for " + result.group(1) + "seconds")
|
|
time.sleep(int(result.group(1)))
|
|
raise exceptions.NewzbinAPIThrottled()
|
|
|
|
elif newzbinErrCode == 401:
|
|
raise exceptions.AuthException("Newzbin username or password incorrect")
|
|
|
|
elif newzbinErrCode == 402:
|
|
raise exceptions.AuthException("Newzbin account not premium status, can't download NZBs")
|
|
|
|
class NewzbinProvider(generic.NZBProvider):
|
|
def __init__(self):
|
|
|
|
generic.NZBProvider.__init__(self, "Newzbin")
|
|
|
|
self.supportsBacklog = True
|
|
|
|
self.cache = NewzbinCache(self)
|
|
|
|
self.url = 'https://www.newzbin2.es/'
|
|
|
|
self.NEWZBIN_DATE_FORMAT = '%a, %d %b %Y %H:%M:%S %Z'
|
|
|
|
def isEnabled(self):
|
|
return sickbeard.NEWZBIN
|
|
|
|
def getQuality(self, item, anime=False):
|
|
attributes = item.report[0]
|
|
attr_dict = {}
|
|
|
|
for attribute in attributes.getElementsByTagName('report:attribute'):
|
|
cur_attr = attribute.getAttribute('type')
|
|
cur_attr_value = helpers.get_xml_text(attribute)
|
|
if cur_attr not in attr_dict:
|
|
attr_dict[cur_attr] = [cur_attr_value]
|
|
else:
|
|
attr_dict[cur_attr].append(cur_attr_value)
|
|
|
|
logger.log("Finding quality of item based on attributes " + str(attr_dict), logger.DEBUG)
|
|
|
|
if self._is_SDTV(attr_dict):
|
|
quality = Quality.SDTV
|
|
elif self._is_SDDVD(attr_dict):
|
|
quality = Quality.SDDVD
|
|
elif self._is_HDTV(attr_dict):
|
|
quality = Quality.HDTV
|
|
elif self._is_WEBDL(attr_dict):
|
|
quality = Quality.HDWEBDL
|
|
elif self._is_720pBluRay(attr_dict):
|
|
quality = Quality.HDBLURAY
|
|
elif self._is_1080pBluRay(attr_dict):
|
|
quality = Quality.FULLHDBLURAY
|
|
else:
|
|
quality = Quality.UNKNOWN
|
|
|
|
logger.log("Resulting quality: " + str(quality), logger.DEBUG)
|
|
|
|
return quality
|
|
|
|
def _is_SDTV(self, attrs):
|
|
|
|
# Video Fmt: (XviD, DivX, H.264/x264), NOT 720p, NOT 1080p, NOT 1080i
|
|
video_fmt = 'Video Fmt' in attrs and (
|
|
'XviD' in attrs['Video Fmt'] or 'DivX' in attrs['Video Fmt'] or 'H.264/x264' in attrs['Video Fmt']) \
|
|
and ('720p' not in attrs['Video Fmt']) \
|
|
and ('1080p' not in attrs['Video Fmt']) \
|
|
and ('1080i' not in attrs['Video Fmt'])
|
|
|
|
# Source: TV Cap or HDTV or (None)
|
|
source = 'Source' not in attrs or 'TV Cap' in attrs['Source'] or 'HDTV' in attrs['Source']
|
|
|
|
# Subtitles: (None)
|
|
subs = 'Subtitles' not in attrs
|
|
|
|
return video_fmt and source and subs
|
|
|
|
def _is_SDDVD(self, attrs):
|
|
|
|
# Video Fmt: (XviD, DivX, H.264/x264), NOT 720p, NOT 1080p, NOT 1080i
|
|
video_fmt = 'Video Fmt' in attrs and (
|
|
'XviD' in attrs['Video Fmt'] or 'DivX' in attrs['Video Fmt'] or 'H.264/x264' in attrs['Video Fmt']) \
|
|
and ('720p' not in attrs['Video Fmt']) \
|
|
and ('1080p' not in attrs['Video Fmt']) \
|
|
and ('1080i' not in attrs['Video Fmt'])
|
|
|
|
# Source: DVD
|
|
source = 'Source' in attrs and 'DVD' in attrs['Source']
|
|
|
|
# Subtitles: (None)
|
|
subs = 'Subtitles' not in attrs
|
|
|
|
return video_fmt and source and subs
|
|
|
|
def _is_HDTV(self, attrs):
|
|
# Video Fmt: H.264/x264, 720p
|
|
video_fmt = 'Video Fmt' in attrs and ('H.264/x264' in attrs['Video Fmt']) \
|
|
and ('720p' in attrs['Video Fmt'])
|
|
|
|
# Source: TV Cap or HDTV or (None)
|
|
source = 'Source' not in attrs or 'TV Cap' in attrs['Source'] or 'HDTV' in attrs['Source']
|
|
|
|
# Subtitles: (None)
|
|
subs = 'Subtitles' not in attrs
|
|
|
|
return video_fmt and source and subs
|
|
|
|
def _is_WEBDL(self, attrs):
|
|
|
|
# Video Fmt: H.264/x264, 720p
|
|
video_fmt = 'Video Fmt' in attrs and ('H.264/x264' in attrs['Video Fmt']) \
|
|
and ('720p' in attrs['Video Fmt'])
|
|
|
|
# Source: WEB-DL
|
|
source = 'Source' in attrs and 'WEB-DL' in attrs['Source']
|
|
|
|
# Subtitles: (None)
|
|
subs = 'Subtitles' not in attrs
|
|
|
|
return video_fmt and source and subs
|
|
|
|
def _is_720pBluRay(self, attrs):
|
|
|
|
# Video Fmt: H.264/x264, 720p
|
|
video_fmt = 'Video Fmt' in attrs and ('H.264/x264' in attrs['Video Fmt']) \
|
|
and ('720p' in attrs['Video Fmt'])
|
|
|
|
# Source: Blu-ray or HD-DVD
|
|
source = 'Source' in attrs and ('Blu-ray' in attrs['Source'] or 'HD-DVD' in attrs['Source'])
|
|
|
|
return video_fmt and source
|
|
|
|
def _is_1080pBluRay(self, attrs):
|
|
|
|
# Video Fmt: H.264/x264, 1080p
|
|
video_fmt = 'Video Fmt' in attrs and ('H.264/x264' in attrs['Video Fmt']) \
|
|
and ('1080p' in attrs['Video Fmt'])
|
|
|
|
# Source: Blu-ray or HD-DVD
|
|
source = 'Source' in attrs and ('Blu-ray' in attrs['Source'] or 'HD-DVD' in attrs['Source'])
|
|
|
|
return video_fmt and source
|
|
|
|
|
|
def getIDFromURL(self, url):
|
|
id_regex = re.escape(self.url) + 'browse/post/(\d+)/'
|
|
id_match = re.match(id_regex, url)
|
|
if not id_match:
|
|
return None
|
|
else:
|
|
return id_match.group(1)
|
|
|
|
def downloadResult(self, nzb):
|
|
|
|
id = self.getIDFromURL(nzb.url)
|
|
if not id:
|
|
logger.log("Unable to get an ID from " + str(nzb.url) + ", can't download from Newzbin's API", logger.ERROR)
|
|
return False
|
|
|
|
logger.log("Downloading an NZB from newzbin with id " + id)
|
|
|
|
fileName = ek.ek(os.path.join, sickbeard.NZB_DIR, helpers.sanitizeFileName(nzb.name) + '.nzb')
|
|
logger.log("Saving to " + fileName)
|
|
|
|
urllib._urlopener = NewzbinDownloader()
|
|
|
|
params = urllib.urlencode(
|
|
{"username": sickbeard.NEWZBIN_USERNAME, "password": sickbeard.NEWZBIN_PASSWORD, "reportid": id})
|
|
try:
|
|
urllib.urlretrieve(self.url + "api/dnzb/", fileName, data=params)
|
|
except exceptions.NewzbinAPIThrottled:
|
|
logger.log("Done waiting for Newzbin API throttle limit, starting downloads again")
|
|
self.downloadResult(nzb)
|
|
except (urllib.ContentTooShortError, IOError), e:
|
|
logger.log("Error downloading NZB: " + str(sys.exc_info()) + " - " + ex(e), logger.ERROR)
|
|
return False
|
|
|
|
return True
|
|
|
|
def _get_season_search_strings(self, ep_obj):
|
|
return ['^' + x for x in show_name_helpers.makeSceneSeasonSearchString(self.show, ep_obj)]
|
|
|
|
def _get_episode_search_strings(self, ep_obj, add_string=''):
|
|
return ['^' + x for x in show_name_helpers.makeSceneSearchString(self.show, ep_obj)]
|
|
|
|
def _doSearch(self, searchStr, show=None, age=None):
|
|
|
|
data = self._getRSSData(searchStr.encode('utf-8'))
|
|
|
|
item_list = []
|
|
|
|
try:
|
|
items = data.entries
|
|
except Exception, e:
|
|
logger.log("Error trying to load Newzbin RSS feed: " + ex(e), logger.ERROR)
|
|
return []
|
|
|
|
for cur_item in items:
|
|
title = cur_item.title
|
|
if title == 'Feeds Error':
|
|
raise exceptions.AuthException("The feed wouldn't load, probably because of invalid auth info")
|
|
if sickbeard.USENET_RETENTION is not None:
|
|
try:
|
|
dateString = helpers.get_xml_text(cur_item.getElementsByTagName('report:postdate')[0])
|
|
# use the parse (imported as parseDate) function from the dateutil lib
|
|
# and we have to remove the timezone info from it because the retention_date will not have one
|
|
# and a comparison of them is not possible
|
|
post_date = parseDate(dateString).replace(tzinfo=None)
|
|
retention_date = datetime.now() - timedelta(days=sickbeard.USENET_RETENTION)
|
|
if post_date < retention_date:
|
|
logger.log(u"Date " + str(post_date) + " is out of retention range, skipping", logger.DEBUG)
|
|
continue
|
|
except Exception, e:
|
|
logger.log("Error parsing date from Newzbin RSS feed: " + str(e), logger.ERROR)
|
|
continue
|
|
|
|
item_list.append(cur_item)
|
|
|
|
return item_list
|
|
|
|
|
|
def _getRSSData(self, search=None):
|
|
|
|
params = {
|
|
'searchaction': 'Search',
|
|
'fpn': 'p',
|
|
'category': 8,
|
|
'u_nfo_posts_only': 0,
|
|
'u_url_posts_only': 0,
|
|
'u_comment_posts_only': 0,
|
|
'u_show_passworded': 0,
|
|
'u_v3_retention': 0,
|
|
'ps_rb_video_format': 3082257,
|
|
'ps_rb_language': 4096,
|
|
'sort': 'date',
|
|
'order': 'desc',
|
|
'u_post_results_amt': 50,
|
|
'feed': 'rss',
|
|
'hauth': 1,
|
|
}
|
|
|
|
if search:
|
|
params['q'] = search + " AND "
|
|
else:
|
|
params['q'] = ''
|
|
|
|
params['q'] += 'Attr:Lang~Eng AND NOT Attr:VideoF=DVD'
|
|
|
|
url = self.url + "search/?%s" % urllib.urlencode(params)
|
|
logger.log("Newzbin search URL: " + url, logger.DEBUG)
|
|
|
|
return self.cache.getRSSFeed(url)
|
|
|
|
def _checkAuth(self):
|
|
if sickbeard.NEWZBIN_USERNAME in (None, "") or sickbeard.NEWZBIN_PASSWORD in (None, ""):
|
|
raise exceptions.AuthException("Newzbin authentication details are empty, check your config")
|
|
|
|
|
|
class NewzbinCache(tvcache.TVCache):
|
|
def __init__(self, provider):
|
|
|
|
tvcache.TVCache.__init__(self, provider)
|
|
|
|
# only poll Newzbin every 10 mins max
|
|
self.minTime = 1
|
|
|
|
def _getRSSData(self):
|
|
|
|
return self.provider._getRSSData()
|
|
|
|
def _parseItem(self, item):
|
|
|
|
(title, url) = self.provider._get_title_and_url(item)
|
|
|
|
if title == 'Feeds Error':
|
|
logger.log("There's an error in the feed, probably bad auth info", logger.DEBUG)
|
|
raise exceptions.AuthException("Invalid Newzbin username/password")
|
|
|
|
if not title or not url:
|
|
logger.log(
|
|
"The XML returned from the " + self.provider.name + " feed is incomplete, this result is unusable",
|
|
logger.ERROR)
|
|
return
|
|
|
|
logger.log(u"RSS Feed provider: [" + self.provider.name + "] Attempting to add item to cache: " + title, logger.DEBUG)
|
|
|
|
self._addCacheEntry(title, url)
|
|
|
|
|
|
provider = NewzbinProvider()
|