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