13 changed files with 853 additions and 778 deletions
@ -0,0 +1,207 @@ |
|||||
|
from couchpotato import get_session |
||||
|
from couchpotato.core.event import addEvent, fireEvent |
||||
|
from couchpotato.core.helpers.encoding import simplifyString, toUnicode |
||||
|
from couchpotato.core.helpers.variable import md5, getTitle |
||||
|
from couchpotato.core.logger import CPLog |
||||
|
from couchpotato.core.plugins.base import Plugin |
||||
|
from couchpotato.core.settings.model import Movie, Release, ReleaseInfo |
||||
|
from couchpotato.environment import Env |
||||
|
from inspect import ismethod, isfunction |
||||
|
import datetime |
||||
|
import re |
||||
|
import time |
||||
|
import traceback |
||||
|
|
||||
|
log = CPLog(__name__) |
||||
|
|
||||
|
|
||||
|
class Searcher(Plugin): |
||||
|
|
||||
|
def __init__(self): |
||||
|
addEvent('searcher.get_types', self.getSearchTypes) |
||||
|
addEvent('searcher.contains_other_quality', self.containsOtherQuality) |
||||
|
addEvent('searcher.correct_year', self.correctYear) |
||||
|
addEvent('searcher.correct_name', self.correctName) |
||||
|
addEvent('searcher.download', self.download) |
||||
|
|
||||
|
def download(self, data, movie, manual = False): |
||||
|
|
||||
|
# Test to see if any downloaders are enabled for this type |
||||
|
downloader_enabled = fireEvent('download.enabled', manual, data, single = True) |
||||
|
|
||||
|
if downloader_enabled: |
||||
|
|
||||
|
snatched_status = fireEvent('status.get', 'snatched', single = True) |
||||
|
|
||||
|
# Download movie to temp |
||||
|
filedata = None |
||||
|
if data.get('download') and (ismethod(data.get('download')) or isfunction(data.get('download'))): |
||||
|
filedata = data.get('download')(url = data.get('url'), nzb_id = data.get('id')) |
||||
|
if filedata == 'try_next': |
||||
|
return filedata |
||||
|
|
||||
|
download_result = fireEvent('download', data = data, movie = movie, manual = manual, filedata = filedata, single = True) |
||||
|
log.debug('Downloader result: %s', download_result) |
||||
|
|
||||
|
if download_result: |
||||
|
try: |
||||
|
# Mark release as snatched |
||||
|
db = get_session() |
||||
|
rls = db.query(Release).filter_by(identifier = md5(data['url'])).first() |
||||
|
if rls: |
||||
|
renamer_enabled = Env.setting('enabled', 'renamer') |
||||
|
|
||||
|
done_status = fireEvent('status.get', 'done', single = True) |
||||
|
rls.status_id = done_status.get('id') if not renamer_enabled else snatched_status.get('id') |
||||
|
|
||||
|
# Save download-id info if returned |
||||
|
if isinstance(download_result, dict): |
||||
|
for key in download_result: |
||||
|
rls_info = ReleaseInfo( |
||||
|
identifier = 'download_%s' % key, |
||||
|
value = toUnicode(download_result.get(key)) |
||||
|
) |
||||
|
rls.info.append(rls_info) |
||||
|
db.commit() |
||||
|
|
||||
|
log_movie = '%s (%s) in %s' % (getTitle(movie['library']), movie['library']['year'], rls.quality.label) |
||||
|
snatch_message = 'Snatched "%s": %s' % (data.get('name'), log_movie) |
||||
|
log.info(snatch_message) |
||||
|
fireEvent('movie.snatched', message = snatch_message, data = rls.to_dict()) |
||||
|
|
||||
|
# If renamer isn't used, mark movie done |
||||
|
if not renamer_enabled: |
||||
|
active_status = fireEvent('status.get', 'active', single = True) |
||||
|
done_status = fireEvent('status.get', 'done', single = True) |
||||
|
try: |
||||
|
if movie['status_id'] == active_status.get('id'): |
||||
|
for profile_type in movie['profile']['types']: |
||||
|
if profile_type['quality_id'] == rls.quality.id and profile_type['finish']: |
||||
|
log.info('Renamer disabled, marking movie as finished: %s', log_movie) |
||||
|
|
||||
|
# Mark release done |
||||
|
rls.status_id = done_status.get('id') |
||||
|
rls.last_edit = int(time.time()) |
||||
|
db.commit() |
||||
|
|
||||
|
# Mark movie done |
||||
|
mvie = db.query(Movie).filter_by(id = movie['id']).first() |
||||
|
mvie.status_id = done_status.get('id') |
||||
|
mvie.last_edit = int(time.time()) |
||||
|
db.commit() |
||||
|
except: |
||||
|
log.error('Failed marking movie finished, renamer disabled: %s', traceback.format_exc()) |
||||
|
|
||||
|
except: |
||||
|
log.error('Failed marking movie finished: %s', traceback.format_exc()) |
||||
|
|
||||
|
return True |
||||
|
|
||||
|
log.info('Tried to download, but none of the "%s" downloaders are enabled or gave an error', (data.get('type', ''))) |
||||
|
|
||||
|
return False |
||||
|
|
||||
|
def getSearchTypes(self): |
||||
|
|
||||
|
download_types = fireEvent('download.enabled_types', merge = True) |
||||
|
provider_types = fireEvent('provider.enabled_types', merge = True) |
||||
|
|
||||
|
if download_types and len(list(set(provider_types) & set(download_types))) == 0: |
||||
|
log.error('There aren\'t any providers enabled for your downloader (%s). Check your settings.', ','.join(download_types)) |
||||
|
return [] |
||||
|
|
||||
|
for useless_provider in list(set(provider_types) - set(download_types)): |
||||
|
log.debug('Provider for "%s" enabled, but no downloader.', useless_provider) |
||||
|
|
||||
|
search_types = download_types |
||||
|
|
||||
|
if len(search_types) == 0: |
||||
|
log.error('There aren\'t any downloaders enabled. Please pick one in settings.') |
||||
|
return [] |
||||
|
|
||||
|
return search_types |
||||
|
|
||||
|
def containsOtherQuality(self, nzb, movie_year = None, preferred_quality = {}): |
||||
|
|
||||
|
name = nzb['name'] |
||||
|
size = nzb.get('size', 0) |
||||
|
nzb_words = re.split('\W+', simplifyString(name)) |
||||
|
|
||||
|
qualities = fireEvent('quality.all', single = True) |
||||
|
|
||||
|
found = {} |
||||
|
for quality in qualities: |
||||
|
# Main in words |
||||
|
if quality['identifier'] in nzb_words: |
||||
|
found[quality['identifier']] = True |
||||
|
|
||||
|
# Alt in words |
||||
|
if list(set(nzb_words) & set(quality['alternative'])): |
||||
|
found[quality['identifier']] = True |
||||
|
|
||||
|
# Try guessing via quality tags |
||||
|
guess = fireEvent('quality.guess', [nzb.get('name')], single = True) |
||||
|
if guess: |
||||
|
found[guess['identifier']] = True |
||||
|
|
||||
|
# Hack for older movies that don't contain quality tag |
||||
|
year_name = fireEvent('scanner.name_year', name, single = True) |
||||
|
if len(found) == 0 and movie_year < datetime.datetime.now().year - 3 and not year_name.get('year', None): |
||||
|
if size > 3000: # Assume dvdr |
||||
|
log.info('Quality was missing in name, assuming it\'s a DVD-R based on the size: %s', (size)) |
||||
|
found['dvdr'] = True |
||||
|
else: # Assume dvdrip |
||||
|
log.info('Quality was missing in name, assuming it\'s a DVD-Rip based on the size: %s', (size)) |
||||
|
found['dvdrip'] = True |
||||
|
|
||||
|
# Allow other qualities |
||||
|
for allowed in preferred_quality.get('allow'): |
||||
|
if found.get(allowed): |
||||
|
del found[allowed] |
||||
|
|
||||
|
return not (found.get(preferred_quality['identifier']) and len(found) == 1) |
||||
|
|
||||
|
def correctYear(self, haystack, year, year_range): |
||||
|
|
||||
|
if not isinstance(haystack, (list, tuple, set)): |
||||
|
haystack = [haystack] |
||||
|
|
||||
|
for string in haystack: |
||||
|
|
||||
|
year_name = fireEvent('scanner.name_year', string, single = True) |
||||
|
|
||||
|
if year_name and ((year - year_range) <= year_name.get('year') <= (year + year_range)): |
||||
|
log.debug('Movie year matches range: %s looking for %s', (year_name.get('year'), year)) |
||||
|
return True |
||||
|
|
||||
|
log.debug('Movie year doesn\'t matche range: %s looking for %s', (year_name.get('year'), year)) |
||||
|
return False |
||||
|
|
||||
|
def correctName(self, check_name, movie_name): |
||||
|
|
||||
|
check_names = [check_name] |
||||
|
|
||||
|
# Match names between " |
||||
|
try: check_names.append(re.search(r'([\'"])[^\1]*\1', check_name).group(0)) |
||||
|
except: pass |
||||
|
|
||||
|
# Match longest name between [] |
||||
|
try: check_names.append(max(check_name.split('['), key = len)) |
||||
|
except: pass |
||||
|
|
||||
|
for check_name in list(set(check_names)): |
||||
|
check_movie = fireEvent('scanner.name_year', check_name, single = True) |
||||
|
|
||||
|
try: |
||||
|
check_words = filter(None, re.split('\W+', check_movie.get('name', ''))) |
||||
|
movie_words = filter(None, re.split('\W+', simplifyString(movie_name))) |
||||
|
|
||||
|
if len(check_words) > 0 and len(movie_words) > 0 and len(list(set(check_words) - set(movie_words))) == 0: |
||||
|
return True |
||||
|
except: |
||||
|
pass |
||||
|
|
||||
|
return False |
||||
|
|
||||
|
class SearchSetupError(Exception): |
||||
|
pass |
@ -0,0 +1,60 @@ |
|||||
|
from .main import Searcher |
||||
|
import random |
||||
|
|
||||
|
def start(): |
||||
|
return Searcher() |
||||
|
|
||||
|
config = [{ |
||||
|
'name': 'searcher', |
||||
|
'order': 20, |
||||
|
'groups': [ |
||||
|
{ |
||||
|
'tab': 'searcher', |
||||
|
'name': 'movie_searcher', |
||||
|
'label': 'Movie search', |
||||
|
'description': 'Search options for movies', |
||||
|
'advanced': True, |
||||
|
'options': [ |
||||
|
{ |
||||
|
'name': 'always_search', |
||||
|
'default': False, |
||||
|
'type': 'bool', |
||||
|
'label': 'Always search', |
||||
|
'description': 'Search for movies even before there is a ETA. Enabling this will probably get you a lot of fakes.', |
||||
|
}, |
||||
|
{ |
||||
|
'name': 'run_on_launch', |
||||
|
'label': 'Run on launch', |
||||
|
'advanced': True, |
||||
|
'default': 0, |
||||
|
'type': 'bool', |
||||
|
'description': 'Force run the searcher after (re)start.', |
||||
|
}, |
||||
|
{ |
||||
|
'name': 'cron_day', |
||||
|
'label': 'Day', |
||||
|
'advanced': True, |
||||
|
'default': '*', |
||||
|
'type': 'string', |
||||
|
'description': '<strong>*</strong>: Every day, <strong>*/2</strong>: Every 2 days, <strong>1</strong>: Every first of the month. See <a href="http://packages.python.org/APScheduler/cronschedule.html">APScheduler</a> for details.', |
||||
|
}, |
||||
|
{ |
||||
|
'name': 'cron_hour', |
||||
|
'label': 'Hour', |
||||
|
'advanced': True, |
||||
|
'default': random.randint(0, 23), |
||||
|
'type': 'string', |
||||
|
'description': '<strong>*</strong>: Every hour, <strong>*/8</strong>: Every 8 hours, <strong>3</strong>: At 3, midnight.', |
||||
|
}, |
||||
|
{ |
||||
|
'name': 'cron_minute', |
||||
|
'label': 'Minute', |
||||
|
'advanced': True, |
||||
|
'default': random.randint(0, 59), |
||||
|
'type': 'string', |
||||
|
'description': "Just keep it random, so the providers don't get DDOSed by every CP user on a 'full' hour." |
||||
|
}, |
||||
|
], |
||||
|
}, |
||||
|
], |
||||
|
}] |
Loading…
Reference in new issue