diff --git a/CouchPotato.py b/CouchPotato.py index c36757fc..e777f9bf 100755 --- a/CouchPotato.py +++ b/CouchPotato.py @@ -62,7 +62,6 @@ class Loader(object): self.log.logger.addHandler(hdlr) def addSignals(self): - signal.signal(signal.SIGINT, self.onExit) signal.signal(signal.SIGTERM, lambda signum, stack_frame: sys.exit(1)) @@ -74,7 +73,7 @@ class Loader(object): def onExit(self, signal, frame): from couchpotato.core.event import fireEvent - fireEvent('app.crappy_shutdown', single = True) + fireEvent('app.shutdown', single = True) def run(self): diff --git a/contributing.md b/contributing.md index 5bb77037..572dd332 100644 --- a/contributing.md +++ b/contributing.md @@ -5,9 +5,10 @@ * Search through the existing (and closed) issues first. See if you can get your answer there. * Double check the result manually, because it could be an external issue. * Post logs! Without seeing what is going on, I can't reproduce the error. - * What are you settings for the specific problem - * What providers are you using. (While your logs include these, scanning through hundred of lines of log isn't my hobby) - * Give me a short step by step of how to reproduce + * What is the movie + quality you are searching for. + * What are you settings for the specific problem. + * What providers are you using. (While your logs include these, scanning through hundred of lines of log isn't my hobby). + * Give me a short step by step of how to reproduce. * What hardware / OS are you using and what are the limits? NAS can be slow and maybe have a different python installed then when you use CP on OSX or Windows for example. * I will mark issues with the "can't reproduce" tag. Don't go asking me "why closed" if it clearly says the issue in the tag ;) diff --git a/couchpotato/core/_base/_core/__init__.py b/couchpotato/core/_base/_core/__init__.py index 8d702f18..c8c3fda6 100644 --- a/couchpotato/core/_base/_core/__init__.py +++ b/couchpotato/core/_base/_core/__init__.py @@ -23,20 +23,22 @@ config = [{ 'default': '', 'type': 'password', }, - { - 'name': 'host', - 'advanced': True, - 'default': '0.0.0.0', - 'hidden': True, - 'label': 'IP', - 'description': 'Host that I should listen to. "0.0.0.0" listens to all ips.', - }, { 'name': 'port', 'default': 5050, 'type': 'int', 'description': 'The port I should listen to.', }, + { + 'name': 'ssl_cert', + 'description': 'Path to SSL server.crt', + 'advanced': True, + }, + { + 'name': 'ssl_key', + 'description': 'Path to SSL server.key', + 'advanced': True, + }, { 'name': 'launch_browser', 'default': True, diff --git a/couchpotato/core/_base/_core/main.py b/couchpotato/core/_base/_core/main.py index c5ce39ad..c36c3fc5 100644 --- a/couchpotato/core/_base/_core/main.py +++ b/couchpotato/core/_base/_core/main.py @@ -10,6 +10,7 @@ from uuid import uuid4 import os import platform import signal +import sys import time import traceback import webbrowser @@ -178,6 +179,7 @@ class Core(Plugin): def signalHandler(self): def signal_handler(signal, frame): - fireEvent('app.do_shutdown') + fireEvent('app.shutdown') signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) diff --git a/couchpotato/core/_base/scheduler/main.py b/couchpotato/core/_base/scheduler/main.py index d442722d..4102552e 100644 --- a/couchpotato/core/_base/scheduler/main.py +++ b/couchpotato/core/_base/scheduler/main.py @@ -2,7 +2,6 @@ from apscheduler.scheduler import Scheduler as Sched from couchpotato.core.event import addEvent from couchpotato.core.logger import CPLog from couchpotato.core.plugins.base import Plugin -import logging log = CPLog(__name__) diff --git a/couchpotato/core/downloaders/__init__.py b/couchpotato/core/downloaders/__init__.py index e69de29b..5fb7125f 100644 --- a/couchpotato/core/downloaders/__init__.py +++ b/couchpotato/core/downloaders/__init__.py @@ -0,0 +1,13 @@ +config = { + 'name': 'download_providers', + 'groups': [ + { + 'label': 'Downloaders', + 'description': 'You can select different downloaders for each type (usenet / torrent)', + 'type': 'list', + 'name': 'download_providers', + 'tab': 'downloaders', + 'options': [], + }, + ], +} diff --git a/couchpotato/core/downloaders/base.py b/couchpotato/core/downloaders/base.py index 7a695e21..55a41838 100644 --- a/couchpotato/core/downloaders/base.py +++ b/couchpotato/core/downloaders/base.py @@ -33,18 +33,41 @@ class Downloader(Provider): ] def __init__(self): - addEvent('download', self.download) - addEvent('download.status', self.getAllDownloadStatus) - addEvent('download.remove_failed', self.removeFailed) + addEvent('download', self._download) + addEvent('download.enabled', self._isEnabled) + addEvent('download.enabled_types', self.getEnabledDownloadType) + addEvent('download.status', self._getAllDownloadStatus) + addEvent('download.remove_failed', self._removeFailed) - def download(self, data = {}, movie = {}, manual = False, filedata = None): - pass + def getEnabledDownloadType(self): + for download_type in self.type: + if self.isEnabled(manual = True, data = {'type': download_type}): + return self.type + + return [] + + def _download(self, data = {}, movie = {}, manual = False, filedata = None): + if self.isDisabled(manual, data): + return + return self.download(data = data, movie = movie, filedata = filedata) + + def _getAllDownloadStatus(self): + if self.isDisabled(manual = True, data = {}): + return + + return self.getAllDownloadStatus() + + def _removeFailed(self, item): + if self.isDisabled(manual = True, data = {}): + return + + if self.conf('delete_failed', default = True): + return self.removeFailed(item) - def getAllDownloadStatus(self): return False - def removeFailed(self, name = {}, nzo_id = {}): - return False + def removeFailed(self, item): + return def isCorrectType(self, item_type): is_correct = item_type in self.type @@ -77,9 +100,16 @@ class Downloader(Provider): log.error('Failed converting magnet url to torrent: %s', (torrent_hash)) return False - def isDisabled(self, manual): - return not self.isEnabled(manual) + def isDisabled(self, manual, data): + return not self.isEnabled(manual, data) - def isEnabled(self, manual): + def _isEnabled(self, manual, data = {}): + if not self.isEnabled(manual, data): + return + return True + + def isEnabled(self, manual, data = {}): d_manual = self.conf('manual', default = False) - return super(Downloader, self).isEnabled() and ((d_manual and manual) or (d_manual is False)) + return super(Downloader, self).isEnabled() and \ + ((d_manual and manual) or (d_manual is False)) and \ + (not data or self.isCorrectType(data.get('type'))) diff --git a/couchpotato/core/downloaders/blackhole/__init__.py b/couchpotato/core/downloaders/blackhole/__init__.py index 71649df1..290e8d43 100644 --- a/couchpotato/core/downloaders/blackhole/__init__.py +++ b/couchpotato/core/downloaders/blackhole/__init__.py @@ -10,6 +10,7 @@ config = [{ 'groups': [ { 'tab': 'downloaders', + 'list': 'download_providers', 'name': 'blackhole', 'label': 'Black hole', 'description': 'Download the NZB/Torrent to a specific folder.', diff --git a/couchpotato/core/downloaders/blackhole/main.py b/couchpotato/core/downloaders/blackhole/main.py index ca38fc49..402e607a 100644 --- a/couchpotato/core/downloaders/blackhole/main.py +++ b/couchpotato/core/downloaders/blackhole/main.py @@ -10,11 +10,7 @@ class Blackhole(Downloader): type = ['nzb', 'torrent', 'torrent_magnet'] - def download(self, data = {}, movie = {}, manual = False, filedata = None): - if self.isDisabled(manual) or \ - (not self.isCorrectType(data.get('type')) or \ - (not self.conf('use_for') in ['both', 'torrent' if 'torrent' in data.get('type') else data.get('type')])): - return + def download(self, data = {}, movie = {}, filedata = None): directory = self.conf('directory') if not directory or not os.path.isdir(directory): @@ -52,4 +48,17 @@ class Blackhole(Downloader): except: log.info('Failed to download file %s: %s', (data.get('name'), traceback.format_exc())) return False + return False + + def getEnabledDownloadType(self): + if self.conf('use_for') == 'both': + return super(Blackhole, self).getEnabledDownloadType() + elif self.conf('use_for') == 'torrent': + return ['torrent', 'torrent_magnet'] + else: + return ['nzb'] + + def isEnabled(self, manual, data = {}): + return super(Blackhole, self).isEnabled(manual, data) and \ + ((self.conf('use_for') in ['both', 'torrent' if 'torrent' in data.get('type') else data.get('type')])) diff --git a/couchpotato/core/downloaders/nzbget/__init__.py b/couchpotato/core/downloaders/nzbget/__init__.py index 4f5afb36..403a7e7d 100644 --- a/couchpotato/core/downloaders/nzbget/__init__.py +++ b/couchpotato/core/downloaders/nzbget/__init__.py @@ -8,6 +8,7 @@ config = [{ 'groups': [ { 'tab': 'downloaders', + 'list': 'download_providers', 'name': 'nzbget', 'label': 'NZBGet', 'description': 'Use NZBGet to download NZBs.', @@ -33,6 +34,13 @@ config = [{ 'default': 'Movies', 'description': 'The category CP places the nzb in. Like movies or couchpotato', }, + { + 'name': 'priority', + 'default': '0', + 'type': 'dropdown', + 'values': [('Very Low', -100), ('Low', -50), ('Normal', 0), ('High', 50), ('Very High', 100)], + 'description': 'Only change this if you are using NZBget 9.0 or higher', + }, { 'name': 'manual', 'default': 0, diff --git a/couchpotato/core/downloaders/nzbget/main.py b/couchpotato/core/downloaders/nzbget/main.py index 0d9c52aa..c3b9703e 100644 --- a/couchpotato/core/downloaders/nzbget/main.py +++ b/couchpotato/core/downloaders/nzbget/main.py @@ -1,7 +1,8 @@ from base64 import standard_b64encode from couchpotato.core.downloaders.base import Downloader +from couchpotato.core.helpers.variable import tryInt from couchpotato.core.logger import CPLog -from inspect import isfunction +import re import socket import traceback import xmlrpclib @@ -14,10 +15,7 @@ class NZBGet(Downloader): url = 'http://nzbget:%(password)s@%(host)s/xmlrpc' - def download(self, data = {}, movie = {}, manual = False, filedata = None): - - if self.isDisabled(manual) or not self.isCorrectType(data.get('type')): - return + def download(self, data = {}, movie = {}, filedata = None): if not filedata: log.error('Unable to get NZB file: %s', traceback.format_exc()) @@ -44,7 +42,12 @@ class NZBGet(Downloader): log.error('Protocol Error: %s', e) return False - if rpc.append(nzb_name, self.conf('category'), False, standard_b64encode(filedata.strip())): + if re.search(r"^0", rpc.version()): + xml_response = rpc.append(nzb_name, self.conf('category'), False, standard_b64encode(filedata.strip())) + else: + xml_response = rpc.append(nzb_name, self.conf('category'), tryInt(self.conf('priority')), False, standard_b64encode(filedata.strip())) + + if xml_response: log.info('NZB sent successfully to NZBGet') return True else: diff --git a/couchpotato/core/downloaders/nzbvortex/__init__.py b/couchpotato/core/downloaders/nzbvortex/__init__.py index e0e77466..f1604ea8 100644 --- a/couchpotato/core/downloaders/nzbvortex/__init__.py +++ b/couchpotato/core/downloaders/nzbvortex/__init__.py @@ -8,6 +8,7 @@ config = [{ 'groups': [ { 'tab': 'downloaders', + 'list': 'download_providers', 'name': 'nzbvortex', 'label': 'NZBVortex', 'description': 'Use NZBVortex to download NZBs.', diff --git a/couchpotato/core/downloaders/nzbvortex/main.py b/couchpotato/core/downloaders/nzbvortex/main.py index 32b1e6e8..1462c678 100644 --- a/couchpotato/core/downloaders/nzbvortex/main.py +++ b/couchpotato/core/downloaders/nzbvortex/main.py @@ -22,10 +22,7 @@ class NZBVortex(Downloader): api_level = None session_id = None - def download(self, data = {}, movie = {}, manual = False, filedata = None): - - if self.isDisabled(manual) or not self.isCorrectType(data.get('type')) or not self.getApiLevel(): - return + def download(self, data = {}, movie = {}, filedata = None): # Send the nzb try: @@ -39,9 +36,6 @@ class NZBVortex(Downloader): def getAllDownloadStatus(self): - if self.isDisabled(manual = True): - return False - raw_statuses = self.call('nzb') statuses = [] @@ -66,9 +60,6 @@ class NZBVortex(Downloader): def removeFailed(self, item): - if not self.conf('delete_failed', default = True): - return False - log.info('%s failed downloading, deleting...', item['name']) try: @@ -153,6 +144,9 @@ class NZBVortex(Downloader): return self.api_level + def isEnabled(self, manual, data): + return super(NZBVortex, self).isEnabled(manual, data) and self.getApiLevel() + class HTTPSConnection(httplib.HTTPSConnection): def __init__(self, *args, **kwargs): diff --git a/couchpotato/core/downloaders/pneumatic/__init__.py b/couchpotato/core/downloaders/pneumatic/__init__.py index f119cfc5..96574a7a 100644 --- a/couchpotato/core/downloaders/pneumatic/__init__.py +++ b/couchpotato/core/downloaders/pneumatic/__init__.py @@ -9,6 +9,7 @@ config = [{ 'groups': [ { 'tab': 'downloaders', + 'list': 'download_providers', 'name': 'pneumatic', 'label': 'Pneumatic', 'description': 'Use Pneumatic to download .strm files.', diff --git a/couchpotato/core/downloaders/pneumatic/main.py b/couchpotato/core/downloaders/pneumatic/main.py index 57e47e60..5e2b7854 100644 --- a/couchpotato/core/downloaders/pneumatic/main.py +++ b/couchpotato/core/downloaders/pneumatic/main.py @@ -11,9 +11,7 @@ class Pneumatic(Downloader): type = ['nzb'] strm_syntax = 'plugin://plugin.program.pneumatic/?mode=strm&type=add_file&nzb=%s&nzbname=%s' - def download(self, data = {}, movie = {}, manual = False, filedata = None): - if self.isDisabled(manual) or (not self.isCorrectType(data.get('type'))): - return + def download(self, data = {}, movie = {}, filedata = None): directory = self.conf('directory') if not directory or not os.path.isdir(directory): diff --git a/couchpotato/core/downloaders/sabnzbd/__init__.py b/couchpotato/core/downloaders/sabnzbd/__init__.py index e4162509..6c976f1e 100644 --- a/couchpotato/core/downloaders/sabnzbd/__init__.py +++ b/couchpotato/core/downloaders/sabnzbd/__init__.py @@ -8,6 +8,7 @@ config = [{ 'groups': [ { 'tab': 'downloaders', + 'list': 'download_providers', 'name': 'sabnzbd', 'label': 'Sabnzbd', 'description': 'Use SABnzbd to download NZBs.', diff --git a/couchpotato/core/downloaders/sabnzbd/main.py b/couchpotato/core/downloaders/sabnzbd/main.py index e3848e40..91302780 100644 --- a/couchpotato/core/downloaders/sabnzbd/main.py +++ b/couchpotato/core/downloaders/sabnzbd/main.py @@ -12,10 +12,7 @@ class Sabnzbd(Downloader): type = ['nzb'] - def download(self, data = {}, movie = {}, manual = False, filedata = None): - - if self.isDisabled(manual) or not self.isCorrectType(data.get('type')): - return + def download(self, data = {}, movie = {}, filedata = None): log.info('Sending "%s" to SABnzbd.', data.get('name')) @@ -65,8 +62,6 @@ class Sabnzbd(Downloader): return False def getAllDownloadStatus(self): - if self.isDisabled(manual = True): - return False log.debug('Checking SABnzbd download status.') @@ -122,9 +117,6 @@ class Sabnzbd(Downloader): def removeFailed(self, item): - if not self.conf('delete_failed', default = True): - return False - log.info('%s failed downloading, deleting...', item['name']) try: diff --git a/couchpotato/core/downloaders/synology/__init__.py b/couchpotato/core/downloaders/synology/__init__.py index 2b7e861d..00a135d4 100644 --- a/couchpotato/core/downloaders/synology/__init__.py +++ b/couchpotato/core/downloaders/synology/__init__.py @@ -8,6 +8,7 @@ config = [{ 'groups': [ { 'tab': 'downloaders', + 'list': 'download_providers', 'name': 'synology', 'label': 'Synology', 'description': 'Use Synology Download Station to download.', diff --git a/couchpotato/core/downloaders/synology/main.py b/couchpotato/core/downloaders/synology/main.py index 02d91d56..6e405980 100644 --- a/couchpotato/core/downloaders/synology/main.py +++ b/couchpotato/core/downloaders/synology/main.py @@ -14,10 +14,7 @@ class Synology(Downloader): type = ['torrent_magnet'] log = CPLog(__name__) - def download(self, data, movie, manual = False, filedata = None): - - if self.isDisabled(manual) or not self.isCorrectType(data.get('type')): - return + def download(self, data, movie, filedata = None): log.error('Sending "%s" (%s) to Synology.', (data.get('name'), data.get('type'))) diff --git a/couchpotato/core/downloaders/transmission/__init__.py b/couchpotato/core/downloaders/transmission/__init__.py index 0fe11845..210a0d9e 100644 --- a/couchpotato/core/downloaders/transmission/__init__.py +++ b/couchpotato/core/downloaders/transmission/__init__.py @@ -8,6 +8,7 @@ config = [{ 'groups': [ { 'tab': 'downloaders', + 'list': 'download_providers', 'name': 'transmission', 'label': 'Transmission', 'description': 'Use Transmission to download torrents.', diff --git a/couchpotato/core/downloaders/transmission/main.py b/couchpotato/core/downloaders/transmission/main.py index 22e01c2f..63c9de9d 100644 --- a/couchpotato/core/downloaders/transmission/main.py +++ b/couchpotato/core/downloaders/transmission/main.py @@ -16,10 +16,7 @@ class Transmission(Downloader): type = ['torrent', 'torrent_magnet'] log = CPLog(__name__) - def download(self, data, movie, manual = False, filedata = None): - - if self.isDisabled(manual) or not self.isCorrectType(data.get('type')): - return + def download(self, data, movie, filedata = None): log.debug('Sending "%s" (%s) to Transmission.', (data.get('name'), data.get('type'))) diff --git a/couchpotato/core/downloaders/utorrent/__init__.py b/couchpotato/core/downloaders/utorrent/__init__.py index 09a82a1e..2c494eb2 100644 --- a/couchpotato/core/downloaders/utorrent/__init__.py +++ b/couchpotato/core/downloaders/utorrent/__init__.py @@ -8,6 +8,7 @@ config = [{ 'groups': [ { 'tab': 'downloaders', + 'list': 'download_providers', 'name': 'utorrent', 'label': 'uTorrent', 'description': 'Use uTorrent to download torrents.', diff --git a/couchpotato/core/downloaders/utorrent/main.py b/couchpotato/core/downloaders/utorrent/main.py index 983afbf9..e1e67de1 100644 --- a/couchpotato/core/downloaders/utorrent/main.py +++ b/couchpotato/core/downloaders/utorrent/main.py @@ -1,6 +1,6 @@ from bencode import bencode, bdecode from couchpotato.core.downloaders.base import Downloader -from couchpotato.core.helpers.encoding import isInt +from couchpotato.core.helpers.encoding import isInt, ss from couchpotato.core.logger import CPLog from hashlib import sha1 from multipartpost import MultipartPostHandler @@ -20,10 +20,7 @@ class uTorrent(Downloader): type = ['torrent', 'torrent_magnet'] utorrent_api = None - def download(self, data, movie, manual = False, filedata = None): - - if self.isDisabled(manual) or not self.isCorrectType(data.get('type')): - return + def download(self, data, movie, filedata = None): log.debug('Sending "%s" (%s) to uTorrent.', (data.get('name'), data.get('type'))) @@ -125,7 +122,7 @@ class uTorrentAPI(object): def add_torrent_file(self, filename, filedata): action = "action=add-file" - return self._request(action, {"torrent_file": (filename, filedata)}) + return self._request(action, {"torrent_file": (ss(filename), filedata)}) def set_torrent(self, hash, params): action = "action=setprops&hash=%s" % hash diff --git a/couchpotato/core/event.py b/couchpotato/core/event.py index 8365ae57..aa05ce0f 100644 --- a/couchpotato/core/event.py +++ b/couchpotato/core/event.py @@ -115,7 +115,8 @@ def fireEvent(name, *args, **kwargs): elif isinstance(results[0], list): merged = [] for result in results: - merged += result + if result not in merged: + merged += result results = merged diff --git a/couchpotato/core/loader.py b/couchpotato/core/loader.py index 30395195..a97437a2 100644 --- a/couchpotato/core/loader.py +++ b/couchpotato/core/loader.py @@ -67,6 +67,18 @@ class Loader(object): def addFromDir(self, plugin_type, priority, module, dir_name): + # Load dir module + try: + m = __import__(module) + splitted = module.split('.') + for sub in splitted[1:]: + m = getattr(m, sub) + + if hasattr(m, 'config'): + fireEvent('settings.options', splitted[-1] + '_config', getattr(m, 'config')) + except: + raise + for cur_file in glob.glob(os.path.join(dir_name, '*')): name = os.path.basename(cur_file) if os.path.isdir(os.path.join(dir_name, name)): diff --git a/couchpotato/core/notifications/__init__.py b/couchpotato/core/notifications/__init__.py index e69de29b..8ac24dfb 100644 --- a/couchpotato/core/notifications/__init__.py +++ b/couchpotato/core/notifications/__init__.py @@ -0,0 +1,13 @@ +config = { + 'name': 'notification_providers', + 'groups': [ + { + 'label': 'Notifications', + 'description': 'Notify when movies are done or snatched', + 'type': 'list', + 'name': 'notification_providers', + 'tab': 'notifications', + 'options': [], + }, + ], +} diff --git a/couchpotato/core/notifications/boxcar/__init__.py b/couchpotato/core/notifications/boxcar/__init__.py index f83722f9..ab244c32 100644 --- a/couchpotato/core/notifications/boxcar/__init__.py +++ b/couchpotato/core/notifications/boxcar/__init__.py @@ -8,6 +8,7 @@ config = [{ 'groups': [ { 'tab': 'notifications', + 'list': 'notification_providers', 'name': 'boxcar', 'options': [ { diff --git a/couchpotato/core/notifications/email/__init__.py b/couchpotato/core/notifications/email/__init__.py new file mode 100644 index 00000000..b41cc8e6 --- /dev/null +++ b/couchpotato/core/notifications/email/__init__.py @@ -0,0 +1,56 @@ +from .main import Email + +def start(): + return Email() + +config = [{ + 'name': 'email', + 'groups': [ + { + 'tab': 'notifications', + 'list': 'notification_providers', + 'name': 'email', + 'options': [ + { + 'name': 'enabled', + 'default': 0, + 'type': 'enabler', + }, + { + 'name': 'from', + 'label': 'Send e-mail from', + }, + { + 'name': 'to', + 'label': 'Send e-mail to', + }, + { + 'name': 'smtp_server', + 'label': 'SMTP server', + }, + { + 'name': 'ssl', + 'label': 'Enable SSL', + 'default': 0, + 'type': 'bool', + }, + { + 'name': 'smtp_user', + 'label': 'SMTP user', + }, + { + 'name': 'smtp_pass', + 'label': 'SMTP password', + 'type': 'password', + }, + { + 'name': 'on_snatch', + 'default': 0, + 'type': 'bool', + 'advanced': True, + 'description': 'Also send message when movie is snatched.', + }, + ], + } + ], +}] diff --git a/couchpotato/core/notifications/email/main.py b/couchpotato/core/notifications/email/main.py new file mode 100644 index 00000000..118ed1b6 --- /dev/null +++ b/couchpotato/core/notifications/email/main.py @@ -0,0 +1,49 @@ +from couchpotato.core.helpers.encoding import toUnicode +from couchpotato.core.logger import CPLog +from couchpotato.core.notifications.base import Notification +from email.mime.text import MIMEText +import smtplib +import traceback + +log = CPLog(__name__) + + +class Email(Notification): + + def notify(self, message = '', data = {}, listener = None): + if self.isDisabled(): return + + # Extract all the settings from settings + from_address = self.conf('from') + to = self.conf('to') + smtp_server = self.conf('smtp_server') + ssl = self.conf('ssl') + smtp_user = self.conf('smtp_user') + smtp_pass = self.conf('smtp_pass') + + # Make the basic message + message = MIMEText(toUnicode(message)) + message['Subject'] = self.default_title + message['From'] = from_address + message['To'] = to + + try: + # Open the SMTP connection, via SSL if requested + mailserver = smtplib.SMTP_SSL(smtp_server) if ssl == 1 else smtplib.SMTP(smtp_server) + + # Check too see if an login attempt should be attempted + if len(smtp_user) > 0: + mailserver.login(smtp_user, smtp_pass) + + # Send the e-mail + mailserver.sendmail(from_address, to, message.as_string()) + + # Close the SMTP connection + mailserver.quit() + log.info('Email notifications sent.') + return True + except: + log.error('E-mail failed: %s', traceback.format_exc()) + return False + + return False diff --git a/couchpotato/core/notifications/growl/__init__.py b/couchpotato/core/notifications/growl/__init__.py index 82a66361..8e462236 100644 --- a/couchpotato/core/notifications/growl/__init__.py +++ b/couchpotato/core/notifications/growl/__init__.py @@ -8,6 +8,7 @@ config = [{ 'groups': [ { 'tab': 'notifications', + 'list': 'notification_providers', 'name': 'growl', 'description': 'Version 1.4+', 'options': [ diff --git a/couchpotato/core/notifications/nmj/__init__.py b/couchpotato/core/notifications/nmj/__init__.py index 6fac5ee6..08a21a3e 100644 --- a/couchpotato/core/notifications/nmj/__init__.py +++ b/couchpotato/core/notifications/nmj/__init__.py @@ -8,6 +8,7 @@ config = [{ 'groups': [ { 'tab': 'notifications', + 'list': 'notification_providers', 'name': 'nmj', 'label': 'NMJ', 'options': [ diff --git a/couchpotato/core/notifications/notifo/__init__.py b/couchpotato/core/notifications/notifo/__init__.py index 5bf035c8..941246cc 100644 --- a/couchpotato/core/notifications/notifo/__init__.py +++ b/couchpotato/core/notifications/notifo/__init__.py @@ -8,6 +8,7 @@ config = [{ 'groups': [ { 'tab': 'notifications', + 'list': 'notification_providers', 'name': 'notifo', 'description': 'Keep in mind that Notifo service will end soon.', 'options': [ diff --git a/couchpotato/core/notifications/notifymyandroid/__init__.py b/couchpotato/core/notifications/notifymyandroid/__init__.py index 58f8e62c..9ee5d90a 100644 --- a/couchpotato/core/notifications/notifymyandroid/__init__.py +++ b/couchpotato/core/notifications/notifymyandroid/__init__.py @@ -8,6 +8,7 @@ config = [{ 'groups': [ { 'tab': 'notifications', + 'list': 'notification_providers', 'name': 'notifymyandroid', 'label': 'Notify My Android', 'options': [ diff --git a/couchpotato/core/notifications/notifymywp/__init__.py b/couchpotato/core/notifications/notifymywp/__init__.py index 76228e6a..4e52761d 100644 --- a/couchpotato/core/notifications/notifymywp/__init__.py +++ b/couchpotato/core/notifications/notifymywp/__init__.py @@ -8,6 +8,7 @@ config = [{ 'groups': [ { 'tab': 'notifications', + 'list': 'notification_providers', 'name': 'notifymywp', 'label': 'Notify My Windows Phone', 'options': [ diff --git a/couchpotato/core/notifications/plex/__init__.py b/couchpotato/core/notifications/plex/__init__.py index f908cbb2..8d89a40f 100644 --- a/couchpotato/core/notifications/plex/__init__.py +++ b/couchpotato/core/notifications/plex/__init__.py @@ -8,6 +8,7 @@ config = [{ 'groups': [ { 'tab': 'notifications', + 'list': 'notification_providers', 'name': 'plex', 'options': [ { diff --git a/couchpotato/core/notifications/prowl/__init__.py b/couchpotato/core/notifications/prowl/__init__.py index 5884f748..e0564289 100644 --- a/couchpotato/core/notifications/prowl/__init__.py +++ b/couchpotato/core/notifications/prowl/__init__.py @@ -8,6 +8,7 @@ config = [{ 'groups': [ { 'tab': 'notifications', + 'list': 'notification_providers', 'name': 'prowl', 'options': [ { diff --git a/couchpotato/core/notifications/pushover/__init__.py b/couchpotato/core/notifications/pushover/__init__.py index d8d76b78..1ea1d5c0 100644 --- a/couchpotato/core/notifications/pushover/__init__.py +++ b/couchpotato/core/notifications/pushover/__init__.py @@ -8,6 +8,7 @@ config = [{ 'groups': [ { 'tab': 'notifications', + 'list': 'notification_providers', 'name': 'pushover', 'options': [ { diff --git a/couchpotato/core/notifications/synoindex/__init__.py b/couchpotato/core/notifications/synoindex/__init__.py index af476238..eb3a793f 100644 --- a/couchpotato/core/notifications/synoindex/__init__.py +++ b/couchpotato/core/notifications/synoindex/__init__.py @@ -8,6 +8,7 @@ config = [{ 'groups': [ { 'tab': 'notifications', + 'list': 'notification_providers', 'name': 'synoindex', 'description': 'Automaticly adds index to Synology Media Server.', 'options': [ diff --git a/couchpotato/core/notifications/toasty/__init__.py b/couchpotato/core/notifications/toasty/__init__.py index 25d27ecd..8e2dae76 100644 --- a/couchpotato/core/notifications/toasty/__init__.py +++ b/couchpotato/core/notifications/toasty/__init__.py @@ -8,6 +8,7 @@ config = [{ 'groups': [ { 'tab': 'notifications', + 'list': 'notification_providers', 'name': 'toasty', 'options': [ { diff --git a/couchpotato/core/notifications/twitter/__init__.py b/couchpotato/core/notifications/twitter/__init__.py index 5910b0a2..9db8dcb8 100644 --- a/couchpotato/core/notifications/twitter/__init__.py +++ b/couchpotato/core/notifications/twitter/__init__.py @@ -8,6 +8,7 @@ config = [{ 'groups': [ { 'tab': 'notifications', + 'list': 'notification_providers', 'name': 'twitter', 'options': [ { diff --git a/couchpotato/core/notifications/xbmc/__init__.py b/couchpotato/core/notifications/xbmc/__init__.py index ee2b4cc2..0753c82a 100644 --- a/couchpotato/core/notifications/xbmc/__init__.py +++ b/couchpotato/core/notifications/xbmc/__init__.py @@ -8,6 +8,7 @@ config = [{ 'groups': [ { 'tab': 'notifications', + 'list': 'notification_providers', 'name': 'xbmc', 'label': 'XBMC', 'description': 'v11 (Eden) and v12 (Frodo)', diff --git a/couchpotato/core/notifications/xbmc/main.py b/couchpotato/core/notifications/xbmc/main.py index eef94695..a1987bfa 100755 --- a/couchpotato/core/notifications/xbmc/main.py +++ b/couchpotato/core/notifications/xbmc/main.py @@ -13,6 +13,7 @@ class XBMC(Notification): listen_to = ['renamer.after'] use_json_notifications = {} + couch_logo_url = 'https://raw.github.com/RuudBurger/CouchPotatoServer/master/couchpotato/static/images/xbmc-notify.png' def notify(self, message = '', data = {}, listener = None): if self.isDisabled(): return @@ -27,7 +28,7 @@ class XBMC(Notification): if self.use_json_notifications.get(host): response = self.request(host, [ - ('GUI.ShowNotification', {'title':self.default_title, 'message':message}), + ('GUI.ShowNotification', {'title': self.default_title, 'message': message, 'image': self.couch_logo_url}), ('VideoLibrary.Scan', {}), ]) else: @@ -89,7 +90,7 @@ class XBMC(Notification): self.use_json_notifications[host] = True # send the text message - resp = self.request(host, [('GUI.ShowNotification', {'title':self.default_title, 'message':message})]) + resp = self.request(host, [('GUI.ShowNotification', {'title':self.default_title, 'message':message, 'image':self.couch_logo_url})]) for result in resp: if (result.get('result') and result['result'] == 'OK'): log.debug('Message delivered successfully!') @@ -111,8 +112,8 @@ class XBMC(Notification): server = 'http://%s/xbmcCmds/' % host - # title, message [, timeout , image #can be added!] - cmd = "xbmcHttp?command=ExecBuiltIn(Notification('%s','%s'))" % (urllib.quote(data['title']), urllib.quote(data['message'])) + # Notification(title, message [, timeout , image]) + cmd = "xbmcHttp?command=ExecBuiltIn(Notification(%s,%s,'',%s))" % (urllib.quote(data['title']), urllib.quote(data['message']), urllib.quote(self.couch_logo_url)) server += cmd # I have no idea what to set to, just tried text/plain and seems to be working :) diff --git a/couchpotato/core/plugins/automation/main.py b/couchpotato/core/plugins/automation/main.py index c216688c..f4ede40d 100644 --- a/couchpotato/core/plugins/automation/main.py +++ b/couchpotato/core/plugins/automation/main.py @@ -12,7 +12,7 @@ class Automation(Plugin): fireEvent('schedule.interval', 'automation.add_movies', self.addMovies, hours = self.conf('hour', default = 12)) - if Env.get('dev'): + if not Env.get('dev'): addEvent('app.load', self.addMovies) def addMovies(self): diff --git a/couchpotato/core/plugins/base.py b/couchpotato/core/plugins/base.py index a3d5628a..73a2c306 100644 --- a/couchpotato/core/plugins/base.py +++ b/couchpotato/core/plugins/base.py @@ -78,7 +78,7 @@ class Plugin(object): self.makeDir(os.path.dirname(path)) try: - f = open(path, 'w' if not binary else 'wb') + f = open(path, 'w+' if not binary else 'w+b') f.write(content) f.close() os.chmod(path, Env.getPermission('file')) diff --git a/couchpotato/core/plugins/manage/main.py b/couchpotato/core/plugins/manage/main.py index 40336e5f..9ef2a9c2 100644 --- a/couchpotato/core/plugins/manage/main.py +++ b/couchpotato/core/plugins/manage/main.py @@ -135,7 +135,6 @@ class Manage(Plugin): already_used = used_files.get(release_file['path']) if already_used: - print already_used, release['id'] if already_used < release['id']: fireEvent('release.delete', release['id'], single = True) # delete this one else: @@ -199,9 +198,12 @@ class Manage(Plugin): def directories(self): try: - return splitString(self.conf('library', default = ''), '::') + if self.conf('library', '').strip(): + return splitString(self.conf('library', default = ''), '::') except: - return [] + pass + + return [] def scanFilesToLibrary(self, folder = None, files = None): diff --git a/couchpotato/core/plugins/searcher/main.py b/couchpotato/core/plugins/searcher/main.py index 805affcf..8c1929af 100644 --- a/couchpotato/core/plugins/searcher/main.py +++ b/couchpotato/core/plugins/searcher/main.py @@ -12,6 +12,7 @@ from couchpotato.environment import Env from inspect import ismethod, isfunction from sqlalchemy.exc import InterfaceError import datetime +import random import re import time import traceback @@ -83,37 +84,51 @@ class Searcher(Plugin): movies = db.query(Movie).filter( Movie.status.has(identifier = 'active') ).all() + random.shuffle(movies) self.in_progress = { 'total': len(movies), 'to_go': len(movies), } - for movie in movies: - movie_dict = movie.to_dict({ - 'profile': {'types': {'quality': {}}}, - 'releases': {'status': {}, 'quality': {}}, - 'library': {'titles': {}, 'files':{}}, - 'files': {} - }) + try: + search_types = self.getSearchTypes() - try: - self.single(movie_dict) - except IndexError: - log.error('Forcing library update for %s, if you see this often, please report: %s', (movie_dict['library']['identifier'], traceback.format_exc())) - fireEvent('library.update', movie_dict['library']['identifier'], force = True) - except: - log.error('Search failed for %s: %s', (movie_dict['library']['identifier'], traceback.format_exc())) + for movie in movies: + movie_dict = movie.to_dict({ + 'profile': {'types': {'quality': {}}}, + 'releases': {'status': {}, 'quality': {}}, + 'library': {'titles': {}, 'files':{}}, + 'files': {} + }) - self.in_progress['to_go'] -= 1 + try: + self.single(movie_dict, search_types) + except IndexError: + log.error('Forcing library update for %s, if you see this often, please report: %s', (movie_dict['library']['identifier'], traceback.format_exc())) + fireEvent('library.update', movie_dict['library']['identifier'], force = True) + except: + log.error('Search failed for %s: %s', (movie_dict['library']['identifier'], traceback.format_exc())) - # Break if CP wants to shut down - if self.shuttingDown(): - break + self.in_progress['to_go'] -= 1 + + # Break if CP wants to shut down + if self.shuttingDown(): + break + + except SearchSetupError: + pass self.in_progress = False - def single(self, movie): + def single(self, movie, search_types = None): + + # Find out search type + try: + if not search_types: + search_types = self.getSearchTypes() + except SearchSetupError: + return done_status = fireEvent('status.get', 'done', single = True) @@ -128,6 +143,8 @@ class Searcher(Plugin): available_status = fireEvent('status.get', 'available', single = True) ignored_status = fireEvent('status.get', 'ignored', single = True) + found_releases = [] + default_title = getTitle(movie['library']) if not default_title: log.error('No proper info found for movie, removing it from library to cause it from having more issues.') @@ -136,6 +153,7 @@ class Searcher(Plugin): fireEvent('notify.frontend', type = 'searcher.started.%s' % movie['id'], data = True, message = 'Searching for "%s"' % default_title) + ret = False for quality_type in movie['profile']['types']: if not self.couldBeReleased(quality_type['quality']['identifier'], release_dates, pre_releases): @@ -155,7 +173,11 @@ class Searcher(Plugin): log.info('Search for %s in %s', (default_title, quality_type['quality']['label'])) quality = fireEvent('quality.single', identifier = quality_type['quality']['identifier'], single = True) - results = fireEvent('yarr.search', movie, quality, merge = True) + results = [] + for search_type in search_types: + type_results = fireEvent('%s.search' % search_type, movie, quality, merge = True) + if type_results: + results += type_results sorted_results = sorted(results, key = lambda k: k['score'], reverse = True) if len(sorted_results) == 0: @@ -172,10 +194,13 @@ class Searcher(Plugin): # Add them to this movie releases list for nzb in sorted_results: - rls = db.query(Release).filter_by(identifier = md5(nzb['url'])).first() + nzb_identifier = md5(nzb['url']) + found_releases.append(nzb_identifier) + + rls = db.query(Release).filter_by(identifier = nzb_identifier).first() if not rls: rls = Release( - identifier = md5(nzb['url']), + identifier = nzb_identifier, movie_id = movie.get('id'), quality_id = quality_type.get('quality_id'), status_id = available_status.get('id') @@ -223,6 +248,12 @@ class Searcher(Plugin): break elif downloaded != 'try_next': break + + # Remove releases that aren't found anymore + for release in movie.get('releases', []): + if release.get('status_id') == available_status.get('id') and release.get('identifier') not in found_releases: + fireEvent('release.delete', release.get('id'), single = True) + else: log.info('Better quality (%s) already available or snatched for %s', (quality_type['quality']['label'], default_title)) fireEvent('movie.restatus', movie['id']) @@ -238,61 +269,87 @@ class Searcher(Plugin): def download(self, data, movie, manual = False): - snatched_status = fireEvent('status.get', 'snatched', single = True) + # Test to see if any downloaders are enabled for this type + downloader_enabled = fireEvent('download.enabled', manual, data, 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 + if downloader_enabled: - successful = fireEvent('download', data = data, movie = movie, manual = manual, filedata = filedata, single = True) + snatched_status = fireEvent('status.get', 'snatched', single = True) - if successful: + # 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 - try: - # Mark release as snatched - db = get_session() - rls = db.query(Release).filter_by(identifier = md5(data['url'])).first() - if rls: - rls.status_id = snatched_status.get('id') - db.commit() + successful = fireEvent('download', data = data, movie = movie, manual = manual, filedata = filedata, single = True) - 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 successful: - # If renamer isn't used, mark movie done - if not Env.setting('enabled', 'renamer'): - 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 rls and profile_type['quality_id'] == rls.quality.id and profile_type['finish']: - log.info('Renamer disabled, marking movie as finished: %s', log_movie) + try: + # Mark release as snatched + db = get_session() + rls = db.query(Release).filter_by(identifier = md5(data['url'])).first() + if rls: + rls.status_id = snatched_status.get('id') + db.commit() - # Mark release done - rls.status_id = done_status.get('id') - 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()) - # Mark movie done - mvie = db.query(Movie).filter_by(id = movie['id']).first() - mvie.status_id = done_status.get('id') - db.commit() - except: - log.error('Failed marking movie finished, renamer disabled: %s', traceback.format_exc()) + # If renamer isn't used, mark movie done + if not Env.setting('enabled', 'renamer'): + 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 rls and profile_type['quality_id'] == rls.quality.id and profile_type['finish']: + log.info('Renamer disabled, marking movie as finished: %s', log_movie) - except: - log.error('Failed marking movie finished: %s', traceback.format_exc()) + # Mark release done + rls.status_id = done_status.get('id') + db.commit() - return True + # Mark movie done + mvie = db.query(Movie).filter_by(id = movie['id']).first() + mvie.status_id = done_status.get('id') + 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', (data.get('type', ''))) - log.info('Tried to download, but none of the downloaders are enabled') 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)) + raise NoProviders + + 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.') + raise NoDownloaders + + return search_types + def correctMovie(self, nzb = {}, movie = {}, quality = {}, **kwargs): imdb_results = kwargs.get('imdb_results', False) @@ -536,3 +593,12 @@ class Searcher(Plugin): except: log.error('Failed searching for next release: %s', traceback.format_exc()) return False + +class SearchSetupError(Exception): + pass + +class NoDownloaders(SearchSetupError): + pass + +class NoProviders(SearchSetupError): + pass diff --git a/couchpotato/core/plugins/subtitle/main.py b/couchpotato/core/plugins/subtitle/main.py index cde1b929..73ead087 100644 --- a/couchpotato/core/plugins/subtitle/main.py +++ b/couchpotato/core/plugins/subtitle/main.py @@ -49,6 +49,7 @@ class Subtitle(Plugin): available_languages = sum(group['subtitle_language'].itervalues(), []) downloaded = [] files = [toUnicode(x) for x in group['files']['movie']] + log.debug('Searching for subtitles for: %s', files) for lang in self.getLanguages(): if lang not in available_languages: @@ -57,6 +58,7 @@ class Subtitle(Plugin): downloaded.extend(download[subtitle]) for d_sub in downloaded: + log.info('Found subtitle (%s): %s', (d_sub.language.alpha2, files)) group['files']['subtitle'].add(d_sub.path) group['subtitle_language'][d_sub.path] = [d_sub.language.alpha2] diff --git a/couchpotato/core/plugins/wizard/static/wizard.css b/couchpotato/core/plugins/wizard/static/wizard.css index a24f2b9e..8d50d9de 100644 --- a/couchpotato/core/plugins/wizard/static/wizard.css +++ b/couchpotato/core/plugins/wizard/static/wizard.css @@ -1,8 +1,13 @@ +.page.wizard .uniForm { + width: 80%; + margin: 0 auto 30px; +} + .page.wizard h1 { padding: 10px 30px; margin: 0; display: block; - font-size: 40px; + font-size: 30px; margin-top: 80px; } diff --git a/couchpotato/core/plugins/wizard/static/wizard.js b/couchpotato/core/plugins/wizard/static/wizard.js index fd6eb14b..eb41cb59 100644 --- a/couchpotato/core/plugins/wizard/static/wizard.js +++ b/couchpotato/core/plugins/wizard/static/wizard.js @@ -41,8 +41,7 @@ Page.Wizard = new Class({ }, 'providers': { 'title': 'Are you registered at any of these sites?', - 'description': 'CP uses these sites to search for movies. A few free are enabled by default, but it\'s always better to have a few more. Check settings for the full list of available providers.', - 'include': ['nzb_providers', 'torrent_providers'] + 'description': 'CP uses these sites to search for movies. A few free are enabled by default, but it\'s always better to have a few more. Check settings for the full list of available providers.' }, 'renamer': { 'title': 'Move & rename the movies after downloading?', @@ -213,8 +212,6 @@ Page.Wizard = new Class({ // Hide retention self.el.getElement('.tab_searcher').hide(); self.el.getElement('.t_searcher').hide(); - self.el.getElement('.t_nzb_providers').hide(); - self.el.getElement('.t_torrent_providers').hide(); // Add pointer new Element('.tab_wrapper').wraps(tabs).adopt( diff --git a/couchpotato/core/providers/automation/__init__.py b/couchpotato/core/providers/automation/__init__.py index e69de29b..a217948a 100644 --- a/couchpotato/core/providers/automation/__init__.py +++ b/couchpotato/core/providers/automation/__init__.py @@ -0,0 +1,21 @@ +config = { + 'name': 'automation_providers', + 'groups': [ + { + 'label': 'Watchlists', + 'description': 'Check watchlists for new movies', + 'type': 'list', + 'name': 'watchlist_providers', + 'tab': 'automation', + 'options': [], + }, + { + 'label': 'Automated', + 'description': 'Uses minimal requirements', + 'type': 'list', + 'name': 'automation_providers', + 'tab': 'automation', + 'options': [], + }, + ], +} diff --git a/couchpotato/core/providers/automation/bluray/__init__.py b/couchpotato/core/providers/automation/bluray/__init__.py index 6e9d831d..b916b0af 100644 --- a/couchpotato/core/providers/automation/bluray/__init__.py +++ b/couchpotato/core/providers/automation/bluray/__init__.py @@ -8,6 +8,7 @@ config = [{ 'groups': [ { 'tab': 'automation', + 'list': 'automation_providers', 'name': 'bluray_automation', 'label': 'Blu-ray.com', 'description': 'Imports movies from blu-ray.com. (uses minimal requirements)', diff --git a/couchpotato/core/providers/automation/imdb/__init__.py b/couchpotato/core/providers/automation/imdb/__init__.py index 925138d0..8a91d42e 100644 --- a/couchpotato/core/providers/automation/imdb/__init__.py +++ b/couchpotato/core/providers/automation/imdb/__init__.py @@ -8,6 +8,7 @@ config = [{ 'groups': [ { 'tab': 'automation', + 'list': 'watchlist_providers', 'name': 'imdb_automation', 'label': 'IMDB', 'description': 'From any public IMDB watchlists. Url should be the RSS link.', diff --git a/couchpotato/core/providers/automation/imdb/main.py b/couchpotato/core/providers/automation/imdb/main.py index 428a8b2b..75a2d75c 100644 --- a/couchpotato/core/providers/automation/imdb/main.py +++ b/couchpotato/core/providers/automation/imdb/main.py @@ -27,7 +27,7 @@ class IMDB(Automation, RSS): try: rss_data = self.getHTMLData(url) - imdbs = getImdb(rss_data, multiple = True) + imdbs = getImdb(rss_data, multiple = True) if rss_data else [] for imdb in imdbs: movies.append(imdb) diff --git a/couchpotato/core/providers/automation/itunes/__init__.py b/couchpotato/core/providers/automation/itunes/__init__.py index 368c1e43..b5c565f6 100644 --- a/couchpotato/core/providers/automation/itunes/__init__.py +++ b/couchpotato/core/providers/automation/itunes/__init__.py @@ -8,6 +8,7 @@ config = [{ 'groups': [ { 'tab': 'automation', + 'list': 'automation_providers', 'name': 'itunes_automation', 'label': 'iTunes', 'description': 'From any iTunes Store feed. Url should be the RSS link. (uses minimal requirements)', diff --git a/couchpotato/core/providers/automation/kinepolis/__init__.py b/couchpotato/core/providers/automation/kinepolis/__init__.py index eea36016..d3b8e898 100644 --- a/couchpotato/core/providers/automation/kinepolis/__init__.py +++ b/couchpotato/core/providers/automation/kinepolis/__init__.py @@ -8,6 +8,7 @@ config = [{ 'groups': [ { 'tab': 'automation', + 'list': 'automation_providers', 'name': 'kinepolis_automation', 'label': 'Kinepolis', 'description': 'Imports movies from the current top 10 of kinepolis. (uses minimal requirements)', diff --git a/couchpotato/core/providers/automation/moviemeter/__init__.py b/couchpotato/core/providers/automation/moviemeter/__init__.py index 8ea7c06d..773bed45 100644 --- a/couchpotato/core/providers/automation/moviemeter/__init__.py +++ b/couchpotato/core/providers/automation/moviemeter/__init__.py @@ -8,6 +8,7 @@ config = [{ 'groups': [ { 'tab': 'automation', + 'list': 'automation_providers', 'name': 'moviemeter_automation', 'label': 'Moviemeter', 'description': 'Imports movies from the current top 10 of moviemeter.nl. (uses minimal requirements)', diff --git a/couchpotato/core/providers/automation/movies_io/__init__.py b/couchpotato/core/providers/automation/movies_io/__init__.py index 5d997e9a..9b280930 100644 --- a/couchpotato/core/providers/automation/movies_io/__init__.py +++ b/couchpotato/core/providers/automation/movies_io/__init__.py @@ -8,6 +8,7 @@ config = [{ 'groups': [ { 'tab': 'automation', + 'list': 'watchlist_providers', 'name': 'moviesio', 'label': 'Movies.IO', 'description': 'Imports movies from Movies.io RSS watchlists', diff --git a/couchpotato/core/providers/automation/rottentomatoes/__init__.py b/couchpotato/core/providers/automation/rottentomatoes/__init__.py new file mode 100644 index 00000000..dd96fe45 --- /dev/null +++ b/couchpotato/core/providers/automation/rottentomatoes/__init__.py @@ -0,0 +1,29 @@ +from .main import Rottentomatoes + +def start(): + return Rottentomatoes() + +config = [{ + 'name': 'rottentomatoes', + 'groups': [ + { + 'tab': 'automation', + 'list': 'automation_providers', + 'name': 'rottentomatoes_automation', + 'label': 'Rottentomatoes', + 'description': 'Imports movies from the rottentomatoes "in theaters"-feed.', + 'options': [ + { + 'name': 'automation_enabled', + 'default': False, + 'type': 'enabler', + }, + { + 'name': 'tomatometer_percent', + 'default': '80', + 'label': 'Tomatometer' + } + ], + }, + ], +}] diff --git a/couchpotato/core/providers/automation/rottentomatoes/main.py b/couchpotato/core/providers/automation/rottentomatoes/main.py new file mode 100644 index 00000000..053b79e8 --- /dev/null +++ b/couchpotato/core/providers/automation/rottentomatoes/main.py @@ -0,0 +1,48 @@ +from couchpotato.core.helpers.rss import RSS +from couchpotato.core.helpers.variable import tryInt +from couchpotato.core.logger import CPLog +from couchpotato.core.providers.automation.base import Automation +from xml.etree.ElementTree import QName +import datetime +import re + +log = CPLog(__name__) + +class Rottentomatoes(Automation, RSS): + + interval = 1800 + urls = { + 'namespace': 'http://www.rottentomatoes.com/xmlns/rtmovie/', + 'theater': 'http://www.rottentomatoes.com/syndication/rss/in_theaters.xml', + } + + def getIMDBids(self): + + movies = [] + + rss_movies = self.getRSSData(self.urls['theater']) + rating_tag = str(QName(self.urls['namespace'], 'tomatometer_percent')) + + for movie in rss_movies: + + value = self.getTextElement(movie, "title") + result = re.search('(?<=%\s).*', value) + + if result: + + log.info2('Something smells...') + rating = tryInt(self.getTextElement(movie, rating_tag)) + name = result.group(0) + + if rating < tryInt(self.conf('tomatometer_percent')): + log.info2('%s seems to be rotten...' % name) + else: + + log.info2('Found %s fresh enough movies, enqueuing: %s' % (rating, name)) + year = datetime.datetime.now().strftime("%Y") + imdb = self.search(name, year) + + if imdb: + movies.append(imdb['imdb']) + + return movies diff --git a/couchpotato/core/providers/automation/trakt/__init__.py b/couchpotato/core/providers/automation/trakt/__init__.py index fca7af35..cbaaece3 100644 --- a/couchpotato/core/providers/automation/trakt/__init__.py +++ b/couchpotato/core/providers/automation/trakt/__init__.py @@ -8,6 +8,7 @@ config = [{ 'groups': [ { 'tab': 'automation', + 'list': 'watchlist_providers', 'name': 'trakt_automation', 'label': 'Trakt', 'description': 'import movies from your own watchlist', diff --git a/couchpotato/core/providers/base.py b/couchpotato/core/providers/base.py index 9c143d81..12d77740 100644 --- a/couchpotato/core/providers/base.py +++ b/couchpotato/core/providers/base.py @@ -56,14 +56,14 @@ class Provider(Plugin): return [] - def getRSSData(self, url, **kwargs): + def getRSSData(self, url, item_path = 'channel/item', **kwargs): data = self.getCache(md5(url), url, **kwargs) if data: try: data = XMLTree.fromstring(data) - return self.getElements(data, 'channel/item') + return self.getElements(data, item_path) except: log.error('Failed to parsing %s: %s', (self.getName(), traceback.format_exc())) @@ -84,8 +84,16 @@ class YarrProvider(Provider): login_opener = None def __init__(self): + addEvent('provider.enabled_types', self.getEnabledProviderType) addEvent('provider.belongs_to', self.belongsTo) addEvent('yarr.search', self.search) + addEvent('%s.search' % self.type, self.search) + + def getEnabledProviderType(self): + if self.isEnabled(): + return self.type + else: + return [] def login(self): diff --git a/couchpotato/core/providers/movie/_modifier/main.py b/couchpotato/core/providers/movie/_modifier/main.py index 7346480e..f0f98e0e 100644 --- a/couchpotato/core/providers/movie/_modifier/main.py +++ b/couchpotato/core/providers/movie/_modifier/main.py @@ -30,11 +30,6 @@ class MovieResultModifier(Plugin): temp[imdb] = self.getLibraryTags(imdb) order.append(imdb) - if item.get('via_imdb'): - if order.count(imdb): - order.remove(imdb) - order.insert(0, imdb) - # Merge dicts temp[imdb] = mergeDicts(temp[imdb], item) diff --git a/couchpotato/core/providers/movie/couchpotatoapi/main.py b/couchpotato/core/providers/movie/couchpotatoapi/main.py index 5d6a35ba..88883d79 100644 --- a/couchpotato/core/providers/movie/couchpotatoapi/main.py +++ b/couchpotato/core/providers/movie/couchpotatoapi/main.py @@ -33,7 +33,7 @@ class CouchPotatoApi(MovieProvider): def search(self, q, limit = 12): cache_key = 'cpapi.cache.%s' % q - cached = self.getCache(cache_key, self.urls['search'] % tryUrlencode(q), timeout = 3, headers = self.getRequestHeaders()) + cached = self.getCache(cache_key, self.urls['search'] % tryUrlencode(q), headers = self.getRequestHeaders()) if cached: try: @@ -50,7 +50,7 @@ class CouchPotatoApi(MovieProvider): return cache_key = 'cpapi.cache.info.%s' % identifier - cached = self.getCache(cache_key, self.urls['info'] % identifier, timeout = 3, headers = self.getRequestHeaders()) + cached = self.getCache(cache_key, self.urls['info'] % identifier, headers = self.getRequestHeaders()) if cached: try: diff --git a/couchpotato/core/providers/nzb/__init__.py b/couchpotato/core/providers/nzb/__init__.py index e69de29b..651ae8b9 100644 --- a/couchpotato/core/providers/nzb/__init__.py +++ b/couchpotato/core/providers/nzb/__init__.py @@ -0,0 +1,15 @@ +config = { + 'name': 'nzb_providers', + 'groups': [ + { + 'label': 'Usenet', + 'description': 'Providers searching usenet for new releases', + 'wizard': True, + 'type': 'list', + 'name': 'nzb_providers', + 'tab': 'searcher', + 'subtab': 'providers', + 'options': [], + }, + ], +} diff --git a/couchpotato/core/providers/nzb/binsearch/__init__.py b/couchpotato/core/providers/nzb/binsearch/__init__.py index 42281ec0..f4288b11 100644 --- a/couchpotato/core/providers/nzb/binsearch/__init__.py +++ b/couchpotato/core/providers/nzb/binsearch/__init__.py @@ -8,9 +8,11 @@ config = [{ 'groups': [ { 'tab': 'searcher', - 'subtab': 'nzb_providers', + 'subtab': 'providers', + 'list': 'nzb_providers', 'name': 'binsearch', 'description': 'Free provider, less accurate. See BinSearch', + 'wizard': True, 'options': [ { 'name': 'enabled', diff --git a/couchpotato/core/providers/nzb/binsearch/main.py b/couchpotato/core/providers/nzb/binsearch/main.py index a7e27b56..1d863002 100644 --- a/couchpotato/core/providers/nzb/binsearch/main.py +++ b/couchpotato/core/providers/nzb/binsearch/main.py @@ -22,11 +22,10 @@ class BinSearch(NZBProvider): def _search(self, movie, quality, results): - q = '%s %s' % (movie['library']['identifier'], quality.get('identifier')) arguments = tryUrlencode({ - 'q': q, + 'q': movie['library']['identifier'], 'm': 'n', - 'max': 250, + 'max': 400, 'adv_age': Env.setting('retention', 'nzb'), 'adv_sort': 'date', 'adv_col': 'on', diff --git a/couchpotato/core/providers/nzb/ftdworld/__init__.py b/couchpotato/core/providers/nzb/ftdworld/__init__.py index e11f486a..ca60ac4d 100644 --- a/couchpotato/core/providers/nzb/ftdworld/__init__.py +++ b/couchpotato/core/providers/nzb/ftdworld/__init__.py @@ -8,9 +8,11 @@ config = [{ 'groups': [ { 'tab': 'searcher', - 'subtab': 'nzb_providers', + 'subtab': 'providers', + 'list': 'nzb_providers', 'name': 'FTDWorld', 'description': 'Free provider, less accurate. See FTDWorld', + 'wizard': True, 'options': [ { 'name': 'enabled', diff --git a/couchpotato/core/providers/nzb/ftdworld/main.py b/couchpotato/core/providers/nzb/ftdworld/main.py index c5a06652..bd2a3ee2 100644 --- a/couchpotato/core/providers/nzb/ftdworld/main.py +++ b/couchpotato/core/providers/nzb/ftdworld/main.py @@ -4,6 +4,7 @@ from couchpotato.core.logger import CPLog from couchpotato.core.providers.nzb.base import NZBProvider from couchpotato.environment import Env from dateutil.parser import parse +import json import traceback log = CPLog(__name__) @@ -15,7 +16,7 @@ class FTDWorld(NZBProvider): 'search': 'http://ftdworld.net/api/index.php?%s', 'detail': 'http://ftdworld.net/spotinfo.php?id=%s', 'download': 'http://ftdworld.net/cgi-bin/nzbdown.pl?fileID=%s', - 'login': 'http://ftdworld.net/index.php', + 'login': 'http://ftdworld.net/api/login.php', } http_time_between_calls = 3 #seconds @@ -56,6 +57,7 @@ class FTDWorld(NZBProvider): 'id': nzb_id, 'name': toUnicode(item.get('Title')), 'age': self.calculateAge(tryInt(item.get('Created'))), + 'size': item.get('Size', 0), 'url': self.urls['download'] % nzb_id, 'download': self.loginDownload, 'detail_url': self.urls['detail'] % nzb_id, @@ -73,4 +75,7 @@ class FTDWorld(NZBProvider): }) def loginSuccess(self, output): - return 'password is incorrect' not in output + try: + return json.loads(output).get('goodToGo', False) + except: + return False diff --git a/couchpotato/core/providers/nzb/newznab/__init__.py b/couchpotato/core/providers/nzb/newznab/__init__.py index 1e76d1ca..9047d200 100644 --- a/couchpotato/core/providers/nzb/newznab/__init__.py +++ b/couchpotato/core/providers/nzb/newznab/__init__.py @@ -8,7 +8,8 @@ config = [{ 'groups': [ { 'tab': 'searcher', - 'subtab': 'nzb_providers', + 'subtab': 'providers', + 'list': 'nzb_providers', 'name': 'newznab', 'order': 10, 'description': 'Enable NewzNab providers such as NZB.su, \ diff --git a/couchpotato/core/providers/nzb/newznab/main.py b/couchpotato/core/providers/nzb/newznab/main.py index b1e93bcd..f80e07cc 100644 --- a/couchpotato/core/providers/nzb/newznab/main.py +++ b/couchpotato/core/providers/nzb/newznab/main.py @@ -104,12 +104,23 @@ class Newznab(NZBProvider, RSS): return result def getUrl(self, host, type): + if '?page=newznabapi' in host: + return cleanHost(host)[:-1] + '&t=' + type + return cleanHost(host) + 'api?t=' + type - def isDisabled(self, host): + def isDisabled(self, host = None): return not self.isEnabled(host) - def isEnabled(self, host): + def isEnabled(self, host = None): + + # Return true if at least one is enabled and no host is given + if host is None: + for host in self.getHosts(): + if self.isEnabled(host): + return True + return False + return NZBProvider.isEnabled(self) and host['host'] and host['api_key'] and int(host['use']) def getApiExt(self, host): diff --git a/couchpotato/core/providers/nzb/nzbclub/__init__.py b/couchpotato/core/providers/nzb/nzbclub/__init__.py index c7cf8d94..7859fe9c 100644 --- a/couchpotato/core/providers/nzb/nzbclub/__init__.py +++ b/couchpotato/core/providers/nzb/nzbclub/__init__.py @@ -8,9 +8,11 @@ config = [{ 'groups': [ { 'tab': 'searcher', - 'subtab': 'nzb_providers', + 'subtab': 'providers', + 'list': 'nzb_providers', 'name': 'NZBClub', 'description': 'Free provider, less accurate. See NZBClub', + 'wizard': True, 'options': [ { 'name': 'enabled', diff --git a/couchpotato/core/providers/nzb/nzbclub/main.py b/couchpotato/core/providers/nzb/nzbclub/main.py index 0e66baff..59382dfd 100644 --- a/couchpotato/core/providers/nzb/nzbclub/main.py +++ b/couchpotato/core/providers/nzb/nzbclub/main.py @@ -20,13 +20,13 @@ class NZBClub(NZBProvider, RSS): def _searchOnTitle(self, title, movie, quality, results): - q = '"%s %s" %s' % (title, movie['library']['year'], quality.get('identifier')) + q = '"%s %s"' % (title, movie['library']['year']) params = tryUrlencode({ 'q': q, - 'ig': '1', + 'ig': 1, 'rpp': 200, - 'st': 1, + 'st': 5, 'sp': 1, 'ns': 1, }) diff --git a/couchpotato/core/providers/nzb/nzbindex/__init__.py b/couchpotato/core/providers/nzb/nzbindex/__init__.py index 04d5022e..29eb0d38 100644 --- a/couchpotato/core/providers/nzb/nzbindex/__init__.py +++ b/couchpotato/core/providers/nzb/nzbindex/__init__.py @@ -8,9 +8,11 @@ config = [{ 'groups': [ { 'tab': 'searcher', - 'subtab': 'nzb_providers', + 'subtab': 'providers', + 'list': 'nzb_providers', 'name': 'nzbindex', 'description': 'Free provider, less accurate. See NZBIndex', + 'wizard': True, 'options': [ { 'name': 'enabled', diff --git a/couchpotato/core/providers/nzb/nzbindex/main.py b/couchpotato/core/providers/nzb/nzbindex/main.py index 6da89fc2..3643f55b 100644 --- a/couchpotato/core/providers/nzb/nzbindex/main.py +++ b/couchpotato/core/providers/nzb/nzbindex/main.py @@ -23,7 +23,7 @@ class NzbIndex(NZBProvider, RSS): def _searchOnTitle(self, title, movie, quality, results): - q = '"%s" %s %s' % (title, movie['library']['year'], quality.get('identifier')) + q = '"%s %s"' % (title, movie['library']['year']) arguments = tryUrlencode({ 'q': q, 'age': Env.setting('retention', 'nzb'), diff --git a/couchpotato/core/providers/nzb/nzbsrus/__init__.py b/couchpotato/core/providers/nzb/nzbsrus/__init__.py index cd4d6691..3a042784 100644 --- a/couchpotato/core/providers/nzb/nzbsrus/__init__.py +++ b/couchpotato/core/providers/nzb/nzbsrus/__init__.py @@ -8,10 +8,11 @@ config = [{ 'groups': [ { 'tab': 'searcher', - 'subtab': 'nzb_providers', + 'subtab': 'providers', + 'list': 'nzb_providers', 'name': 'nzbsrus', 'label': 'Nzbsrus', - 'description': 'See NZBsRus', + 'description': 'See NZBsRus. You need a VIP account!', 'wizard': True, 'options': [ { diff --git a/couchpotato/core/providers/nzb/nzbsrus/main.py b/couchpotato/core/providers/nzb/nzbsrus/main.py index 9a20153b..d52212a7 100644 --- a/couchpotato/core/providers/nzb/nzbsrus/main.py +++ b/couchpotato/core/providers/nzb/nzbsrus/main.py @@ -37,7 +37,7 @@ class Nzbsrus(NZBProvider, RSS): arguments += '&lang0=1&lang3=1&lang1=1' url = '%s&%s&%s' % (self.urls['search'], arguments , cat_id_string) - nzbs = self.getRSSData(url, cache_timeout = 1800, headers = {'User-Agent': Env.getIdentifier()}) + nzbs = self.getRSSData(url, item_path = 'results/result', cache_timeout = 1800, headers = {'User-Agent': Env.getIdentifier()}) for nzb in nzbs: @@ -53,7 +53,7 @@ class Nzbsrus(NZBProvider, RSS): 'name': title, 'age': age, 'size': size, - 'url': self.urls['download'] % id + self.getApiExt() + self.getTextElement(nzb, 'key'), + 'url': self.urls['download'] % nzb_id + self.getApiExt() + self.getTextElement(nzb, 'key'), 'detail_url': self.urls['detail'] % nzb_id, 'description': self.getTextElement(nzb, 'addtext'), }) diff --git a/couchpotato/core/providers/nzb/nzbx/__init__.py b/couchpotato/core/providers/nzb/nzbx/__init__.py index f1c7d588..9ce9226b 100644 --- a/couchpotato/core/providers/nzb/nzbx/__init__.py +++ b/couchpotato/core/providers/nzb/nzbx/__init__.py @@ -8,9 +8,11 @@ config = [{ 'groups': [ { 'tab': 'searcher', - 'subtab': 'nzb_providers', + 'subtab': 'providers', + 'list': 'nzb_providers', 'name': 'nzbX', 'description': 'Free provider. See nzbX', + 'wizard': True, 'options': [ { 'name': 'enabled', diff --git a/couchpotato/core/providers/nzb/omgwtfnzbs/__init__.py b/couchpotato/core/providers/nzb/omgwtfnzbs/__init__.py index 8e8af499..287ced49 100644 --- a/couchpotato/core/providers/nzb/omgwtfnzbs/__init__.py +++ b/couchpotato/core/providers/nzb/omgwtfnzbs/__init__.py @@ -8,9 +8,11 @@ config = [{ 'groups': [ { 'tab': 'searcher', - 'subtab': 'nzb_providers', + 'subtab': 'providers', + 'list': 'nzb_providers', 'name': 'OMGWTFNZBs', - 'description': 'See OMGWTFNZBs', + 'description': 'See OMGWTFNZBs', + 'wizard': True, 'options': [ { 'name': 'enabled', diff --git a/couchpotato/core/providers/torrent/__init__.py b/couchpotato/core/providers/torrent/__init__.py index e69de29b..191e132e 100644 --- a/couchpotato/core/providers/torrent/__init__.py +++ b/couchpotato/core/providers/torrent/__init__.py @@ -0,0 +1,15 @@ +config = { + 'name': 'torrent_providers', + 'groups': [ + { + 'label': 'Torrent', + 'description': 'Providers searching torrent sites for new releases', + 'wizard': True, + 'type': 'list', + 'name': 'torrent_providers', + 'tab': 'searcher', + 'subtab': 'providers', + 'options': [], + }, + ], +} diff --git a/couchpotato/core/providers/torrent/kickasstorrents/__init__.py b/couchpotato/core/providers/torrent/kickasstorrents/__init__.py index d31250fb..8ddb1f4a 100644 --- a/couchpotato/core/providers/torrent/kickasstorrents/__init__.py +++ b/couchpotato/core/providers/torrent/kickasstorrents/__init__.py @@ -8,7 +8,8 @@ config = [{ 'groups': [ { 'tab': 'searcher', - 'subtab': 'torrent_providers', + 'subtab': 'providers', + 'list': 'torrent_providers', 'name': 'KickAssTorrents', 'description': 'See KickAssTorrents', 'wizard': True, diff --git a/couchpotato/core/providers/torrent/passthepopcorn/__init__.py b/couchpotato/core/providers/torrent/passthepopcorn/__init__.py index 3291c9cd..06be7a89 100644 --- a/couchpotato/core/providers/torrent/passthepopcorn/__init__.py +++ b/couchpotato/core/providers/torrent/passthepopcorn/__init__.py @@ -8,9 +8,11 @@ config = [{ 'groups': [ { 'tab': 'searcher', - 'subtab': 'torrent_providers', + 'subtab': 'providers', + 'list': 'torrent_providers', 'name': 'PassThePopcorn', 'description': 'See PassThePopcorn.me', + 'wizard': True, 'options': [ { 'name': 'enabled', diff --git a/couchpotato/core/providers/torrent/publichd/__init__.py b/couchpotato/core/providers/torrent/publichd/__init__.py index b0d3b70b..2c356e20 100644 --- a/couchpotato/core/providers/torrent/publichd/__init__.py +++ b/couchpotato/core/providers/torrent/publichd/__init__.py @@ -8,9 +8,11 @@ config = [{ 'groups': [ { 'tab': 'searcher', - 'subtab': 'torrent_providers', + 'subtab': 'providers', + 'list': 'torrent_providers', 'name': 'PublicHD', 'description': 'Public Torrent site with only HD content. See PublicHD', + 'wizard': True, 'options': [ { 'name': 'enabled', diff --git a/couchpotato/core/providers/torrent/sceneaccess/__init__.py b/couchpotato/core/providers/torrent/sceneaccess/__init__.py index e59f89b1..e12bf8bd 100644 --- a/couchpotato/core/providers/torrent/sceneaccess/__init__.py +++ b/couchpotato/core/providers/torrent/sceneaccess/__init__.py @@ -8,9 +8,11 @@ config = [{ 'groups': [ { 'tab': 'searcher', - 'subtab': 'torrent_providers', + 'subtab': 'providers', + 'list': 'torrent_providers', 'name': 'SceneAccess', 'description': 'See SceneAccess', + 'wizard': True, 'options': [ { 'name': 'enabled', diff --git a/couchpotato/core/providers/torrent/scenehd/__init__.py b/couchpotato/core/providers/torrent/scenehd/__init__.py index 69cf8a17..10c5e385 100644 --- a/couchpotato/core/providers/torrent/scenehd/__init__.py +++ b/couchpotato/core/providers/torrent/scenehd/__init__.py @@ -8,9 +8,11 @@ config = [{ 'groups': [ { 'tab': 'searcher', - 'subtab': 'torrent_providers', + 'subtab': 'providers', + 'list': 'torrent_providers', 'name': 'SceneHD', 'description': 'See SceneHD', + 'wizard': True, 'options': [ { 'name': 'enabled', diff --git a/couchpotato/core/providers/torrent/scenehd/main.py b/couchpotato/core/providers/torrent/scenehd/main.py index cc914660..93897c67 100644 --- a/couchpotato/core/providers/torrent/scenehd/main.py +++ b/couchpotato/core/providers/torrent/scenehd/main.py @@ -22,7 +22,7 @@ class SceneHD(TorrentProvider): def _searchOnTitle(self, title, movie, quality, results): - q = '"%s %s" %s' % (simplifyString(title), movie['library']['year'], quality.get('identifier')) + q = '"%s %s"' % (simplifyString(title), movie['library']['year']) arguments = tryUrlencode({ 'search': q, }) diff --git a/couchpotato/core/providers/torrent/thepiratebay/__init__.py b/couchpotato/core/providers/torrent/thepiratebay/__init__.py index f890ca0e..38169084 100644 --- a/couchpotato/core/providers/torrent/thepiratebay/__init__.py +++ b/couchpotato/core/providers/torrent/thepiratebay/__init__.py @@ -8,7 +8,8 @@ config = [{ 'groups': [ { 'tab': 'searcher', - 'subtab': 'torrent_providers', + 'subtab': 'providers', + 'list': 'torrent_providers', 'name': 'ThePirateBay', 'description': 'The world\'s largest bittorrent tracker. See ThePirateBay', 'wizard': True, diff --git a/couchpotato/core/providers/torrent/thepiratebay/main.py b/couchpotato/core/providers/torrent/thepiratebay/main.py index 41bebe38..2a2433f0 100644 --- a/couchpotato/core/providers/torrent/thepiratebay/main.py +++ b/couchpotato/core/providers/torrent/thepiratebay/main.py @@ -15,7 +15,7 @@ class ThePirateBay(TorrentMagnetProvider): urls = { 'detail': '%s/torrent/%s', - 'search': '%s/search/%s/0/7/%d' + 'search': '%s/search/%s/%s/7/%d' } cat_ids = [ @@ -45,52 +45,66 @@ class ThePirateBay(TorrentMagnetProvider): def _searchOnTitle(self, title, movie, quality, results): - search_url = self.urls['search'] % (self.getDomain(), tryUrlencode(title + ' ' + quality['identifier']), self.getCatId(quality['identifier'])[0]) + page = 0 + total_pages = 1 - data = self.getHTMLData(search_url) + while page < total_pages: - if data: - try: - soup = BeautifulSoup(data) - results_table = soup.find('table', attrs = {'id': 'searchResult'}) + search_url = self.urls['search'] % (self.getDomain(), tryUrlencode('"%s %s"' % (title, movie['library']['year'])), page, self.getCatId(quality['identifier'])[0]) + page += 1 - if not results_table: - return + data = self.getHTMLData(search_url) - entries = results_table.find_all('tr') - for result in entries[2:]: - link = result.find(href = re.compile('torrent\/\d+\/')) - download = result.find(href = re.compile('magnet:')) + if data: + try: + soup = BeautifulSoup(data) + results_table = soup.find('table', attrs = {'id': 'searchResult'}) + + if not results_table: + return try: - size = re.search('Size (?P.+),', unicode(result.select('font.detDesc')[0])).group('size') + total_pages = len(soup.find('div', attrs = {'align': 'center'}).find_all('a')) except: - continue + pass - if link and download: + print total_pages, page - def extra_score(item): - trusted = (0, 10)[result.find('img', alt = re.compile('Trusted')) != None] - vip = (0, 20)[result.find('img', alt = re.compile('VIP')) != None] - confirmed = (0, 30)[result.find('img', alt = re.compile('Helpers')) != None] - moderated = (0, 50)[result.find('img', alt = re.compile('Moderator')) != None] + entries = results_table.find_all('tr') + for result in entries[2:]: + link = result.find(href = re.compile('torrent\/\d+\/')) + download = result.find(href = re.compile('magnet:')) - return confirmed + trusted + vip + moderated + try: + size = re.search('Size (?P.+),', unicode(result.select('font.detDesc')[0])).group('size') + except: + continue - results.append({ - 'id': re.search('/(?P\d+)/', link['href']).group('id'), - 'name': link.string, - 'url': download['href'], - 'detail_url': self.getDomain(link['href']), - 'size': self.parseSize(size), - 'seeders': tryInt(result.find_all('td')[2].string), - 'leechers': tryInt(result.find_all('td')[3].string), - 'extra_score': extra_score, - 'get_more_info': self.getMoreInfo - }) + if link and download: + + def extra_score(item): + trusted = (0, 10)[result.find('img', alt = re.compile('Trusted')) != None] + vip = (0, 20)[result.find('img', alt = re.compile('VIP')) != None] + confirmed = (0, 30)[result.find('img', alt = re.compile('Helpers')) != None] + moderated = (0, 50)[result.find('img', alt = re.compile('Moderator')) != None] + + return confirmed + trusted + vip + moderated + + results.append({ + 'id': re.search('/(?P\d+)/', link['href']).group('id'), + 'name': link.string, + 'url': download['href'], + 'detail_url': self.getDomain(link['href']), + 'size': self.parseSize(size), + 'seeders': tryInt(result.find_all('td')[2].string), + 'leechers': tryInt(result.find_all('td')[3].string), + 'extra_score': extra_score, + 'get_more_info': self.getMoreInfo + }) + + except: + log.error('Failed getting results from %s: %s', (self.getName(), traceback.format_exc())) - except: - log.error('Failed getting results from %s: %s', (self.getName(), traceback.format_exc())) def isEnabled(self): return super(ThePirateBay, self).isEnabled() and self.getDomain() diff --git a/couchpotato/core/providers/torrent/torrentday/__init__.py b/couchpotato/core/providers/torrent/torrentday/__init__.py index 8ffd48cf..1a4d3c7f 100644 --- a/couchpotato/core/providers/torrent/torrentday/__init__.py +++ b/couchpotato/core/providers/torrent/torrentday/__init__.py @@ -8,9 +8,11 @@ config = [{ 'groups': [ { 'tab': 'searcher', - 'subtab': 'torrent_providers', + 'subtab': 'providers', + 'list': 'torrent_providers', 'name': 'TorrentDay', 'description': 'See TorrentDay', + 'wizard': True, 'options': [ { 'name': 'enabled', diff --git a/couchpotato/core/providers/torrent/torrentday/main.py b/couchpotato/core/providers/torrent/torrentday/main.py index 8b29f320..5e207c1e 100644 --- a/couchpotato/core/providers/torrent/torrentday/main.py +++ b/couchpotato/core/providers/torrent/torrentday/main.py @@ -59,3 +59,6 @@ class TorrentDay(TorrentProvider): 'password': self.conf('password'), 'submit': 'submit', }) + + def loginSuccess(self, output): + return 'Password not correct' not in output diff --git a/couchpotato/core/providers/torrent/torrentleech/__init__.py b/couchpotato/core/providers/torrent/torrentleech/__init__.py index b808a005..d96ac064 100644 --- a/couchpotato/core/providers/torrent/torrentleech/__init__.py +++ b/couchpotato/core/providers/torrent/torrentleech/__init__.py @@ -8,9 +8,11 @@ config = [{ 'groups': [ { 'tab': 'searcher', - 'subtab': 'torrent_providers', + 'subtab': 'providers', + 'list': 'torrent_providers', 'name': 'TorrentLeech', 'description': 'See TorrentLeech', + 'wizard': True, 'options': [ { 'name': 'enabled', diff --git a/couchpotato/core/providers/torrent/torrentleech/main.py b/couchpotato/core/providers/torrent/torrentleech/main.py index df6072d0..6de18fbd 100644 --- a/couchpotato/core/providers/torrent/torrentleech/main.py +++ b/couchpotato/core/providers/torrent/torrentleech/main.py @@ -34,7 +34,7 @@ class TorrentLeech(TorrentProvider): def _searchOnTitle(self, title, movie, quality, results): - url = self.urls['search'] % (tryUrlencode(title.replace(':', '') + ' ' + quality['identifier']), self.getCatId(quality['identifier'])[0]) + url = self.urls['search'] % (tryUrlencode('%s %s' % (title.replace(':', ''), movie['library']['year'])), self.getCatId(quality['identifier'])[0]) data = self.getHTMLData(url, opener = self.login_opener) if data: diff --git a/couchpotato/runner.py b/couchpotato/runner.py index c0b7eb86..7062c75b 100644 --- a/couchpotato/runner.py +++ b/couchpotato/runner.py @@ -4,6 +4,7 @@ from couchpotato.api import api, NonBlockHandler from couchpotato.core.event import fireEventAsync, fireEvent from couchpotato.core.helpers.variable import getDataDir, tryInt from logging import handlers +from tornado.httpserver import HTTPServer from tornado.web import Application, FallbackHandler from tornado.wsgi import WSGIContainer from werkzeug.contrib.cache import FileSystemCache @@ -210,8 +211,10 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En # app.debug = development config = { 'use_reloader': reloader, - 'host': Env.setting('host', default = '0.0.0.0'), - 'port': tryInt(Env.setting('port', default = 5000)) + 'port': tryInt(Env.setting('port', default = 5000)), + 'host': Env.setting('host', default = ''), + 'ssl_cert': Env.setting('ssl_cert', default = None), + 'ssl_key': Env.setting('ssl_key', default = None), } # Static path @@ -243,12 +246,20 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En debug = config['use_reloader'] ) + if config['ssl_cert'] and config['ssl_key']: + server = HTTPServer(application, no_keep_alive = True, ssl_options = { + "certfile": config['ssl_cert'], + "keyfile": config['ssl_key'], + }) + else: + server = HTTPServer(application, no_keep_alive = True) + try_restart = True restart_tries = 5 while try_restart: try: - application.listen(config['port'], config['host'], no_keep_alive = True) + server.listen(config['port']) loop.start() except Exception, e: try: diff --git a/couchpotato/static/images/xbmc-notify.png b/couchpotato/static/images/xbmc-notify.png new file mode 100644 index 00000000..6b7959f4 Binary files /dev/null and b/couchpotato/static/images/xbmc-notify.png differ diff --git a/couchpotato/static/scripts/page/settings.js b/couchpotato/static/scripts/page/settings.js index 44ae0f6b..ddb2abf1 100644 --- a/couchpotato/static/scripts/page/settings.js +++ b/couchpotato/static/scripts/page/settings.js @@ -7,6 +7,7 @@ Page.Settings = new Class({ wizard_only: false, tabs: {}, + lists: {}, current: 'about', has_tab: false, @@ -178,12 +179,24 @@ Page.Settings = new Class({ var content_container = self.tabs[group.tab].subtabs[group.subtab].content } + if(group.list && !self.lists[group.list]){ + self.lists[group.list] = self.createList(content_container); + } + // Create the group if(!self.tabs[group.tab].groups[group.name]){ var group_el = self.createGroup(group) - .inject(content_container) + .inject(group.list ? self.lists[group.list] : content_container) .addClass('section_'+section_name); - self.tabs[group.tab].groups[group.name] = group_el + self.tabs[group.tab].groups[group.name] = group_el; + } + + // Create list if needed + if(group.type && group.type == 'list'){ + if(!self.lists[group.name]) + self.lists[group.name] = self.createList(content_container); + else + self.lists[group.name].inject(self.tabs[group.tab].groups[group.name]); } // Add options to group @@ -283,6 +296,14 @@ Page.Settings = new Class({ ) return group_el + }, + + createList: function(content_container){ + return new Element('div.option_list').grab( + new Element('h3', { + 'text': 'Enable another' + }) + ).inject(content_container) } }); @@ -550,15 +571,21 @@ Option.Enabler = new Class({ }, checkState: function(){ - var self = this; + var self = this, + enabled = self.getValue(); + + self.parentFieldset[ enabled ? 'removeClass' : 'addClass']('disabled'); + + if(self.parentList) + self.parentFieldset.inject(self.parentList.getElement('h3'), enabled ? 'before' : 'after'); - self.parentFieldset[ self.getValue() ? 'removeClass' : 'addClass']('disabled'); }, afterInject: function(){ var self = this; - self.parentFieldset = self.el.getParent('fieldset') + self.parentFieldset = self.el.getParent('fieldset').addClass('enabler') + self.parentList = self.parentFieldset.getParent('.option_list'); self.el.inject(self.parentFieldset, 'top') self.checkState() } @@ -1311,7 +1338,7 @@ Option.Combined = new Class({ if(has_empty > 0) return; self.add_empty_timeout = setTimeout(function(){ - self.createItem(false, null); + self.createItem({'use': true}); }, 10); }, diff --git a/couchpotato/static/style/page/settings.css b/couchpotato/static/style/page/settings.css index 571e5a68..8d0b0f2e 100644 --- a/couchpotato/static/style/page/settings.css +++ b/couchpotato/static/style/page/settings.css @@ -92,6 +92,10 @@ font-size: 12px; margin-left: 10px; } + .page fieldset h2 .hint a { + margin: 0 !important; + padding: 0; + } .page fieldset.disabled .ctrlHolder { display: none; @@ -102,7 +106,7 @@ width: auto; margin: 0; position: relative; - margin-bottom: -25px; + margin-bottom: -23px; border: none; width: 20px; } @@ -148,6 +152,74 @@ } .page .xsmall { width: 20px !important; text-align: center; } + + .page .enabler { + display: block; + } + + .page .option_list { + margin-bottom: 20px; + } + + .page .option_list .enabler { + padding: 0; + margin-left: 5px !important; + } + + .page .option_list .enabler:not(.disabled) { + margin: 0 0 0 30px; + } + + .page .option_list .enabler:not(.disabled) .ctrlHolder:first-child { + margin: 10px 0 -33px 0; + } + + .page .option_list h3 { + padding: 0; + margin: 10px 0 0 0; + text-align: center; + font-weight: normal; + text-shadow: none; + text-transform: uppercase; + font-size: 12px; + background: rgba(255,255,255,0.03); + } + + .page .option_list .enabler.disabled { + display: inline-block; + margin: 3px 3px 3px 20px; + padding: 4px 0; + width: 159px; + vertical-align: top; + } + + .page .option_list .enabler.disabled h2 { + border: none; + box-shadow: none; + padding: 0 10px 0 25px; + font-size: 16px; + } + + .page .option_list .enabler:not(.disabled) h2 { + font-size: 16px; + font-weight: bold; + border: none; + border-top: 1px solid rgba(255,255,255, 0.15); + box-shadow: 0 -1px 0px #333; + margin: 0; + padding: 10px 0 5px 25px; + } + .page .option_list .enabler:not(.disabled):first-child h2 { + border: none; + box-shadow: none; + } + + .page .option_list .enabler.disabled h2 .hint { + display: none; + } + .page .option_list .enabler h2 .hint { + font-weight: normal; + } .page input[type=text], .page input[type=password] { padding: 5px 3px;