Compare commits

...
51 Commits
Author SHA1 Message Date
Ruud 506871b506 One up 2013-01-23 23:10:55 +01:00
Ruud 6115917660 Merge branch 'refs/heads/develop' into desktop
Conflicts:
	version.py
2013-01-23 22:57:07 +01:00
Ruud 44b78f8d2f Version up 2013-01-23 22:56:23 +01:00
Ruud 21df8819d3 Merge branch 'refs/heads/develop' into desktop 2013-01-23 22:55:09 +01:00
Ruud cad9bfae9f Transmission: Don't use ratio when not filled in. 2013-01-23 22:54:02 +01:00
Ruud 749075b4cb Setting cleanup 2013-01-23 22:50:31 +01:00
Ruud 0456a1e820 Typo 2013-01-23 22:29:02 +01:00
ikkemaniac 35a9739ec5 Improve debugging for email notifications 2013-01-23 22:26:09 +01:00
ikkemaniac 2a451c255e Rename var for naming consistency 2013-01-23 22:26:09 +01:00
Ruud 7c38ad1c00 Remove non-int backup folders. closes #1298 2013-01-23 22:24:09 +01:00
Ruud 647159e549 Fix and cleanup wizard. fix #1324 2013-01-23 22:16:04 +01:00
Ruud 7cc55c21b6 Shutdown cleanly on quit process 2013-01-22 23:43:15 +01:00
Ruud 89c38f5aa4 Use host from config again. fix #1278 2013-01-22 23:24:09 +01:00
Ruud 5f428649c3 Writing larger files fails on Windows. fix #1281 2013-01-22 23:20:55 +01:00
Ruud 8ed2a99830 Don't try to parse None on IMDB watchlist 2013-01-22 22:55:06 +01:00
Ruud 1a89d551dc Contribute update 2013-01-22 22:41:17 +01:00
Ruud 9d633910f6 NZBsRus fixes 2013-01-22 22:35:44 +01:00
Ruud 54ea22e9b6 Only remove available status releases when refreshing 2013-01-22 22:02:23 +01:00
Ruud fb3f3e11f6 Merge branch 'refs/heads/develop' into desktop 2013-01-22 21:40:40 +01:00
Ruud f84b23eecc Only fire enabled downloader failed event 2013-01-22 21:35:11 +01:00
Ruud 6ea045ddd3 Remove failed wrong parameters 2013-01-21 22:37:58 +01:00
Ruud f8b4e75b74 If data is empty, assume correct type for downloaders 2013-01-20 21:11:36 +01:00
Ruud faaf351662 Group providers together 2013-01-20 20:50:50 +01:00
Ruud f41fc794c1 Don't search full disk when no manage folders are filled. fix #1304 2013-01-19 01:30:56 +01:00
Ruud 0f789b5b40 NZBsRus rss different item path. fix #1301 2013-01-19 01:17:57 +01:00
Ruud d2496d768d Don't reorder based on omdb 2013-01-19 01:04:24 +01:00
Ruud b93488f025 Use default timeout for CP calls 2013-01-19 00:56:03 +01:00
Ruud d4de68ef86 Add page nr after 2013-01-19 00:51:20 +01:00
Ruud 61a0bb8ec6 Don't use quality identifier in title searches 2013-01-19 00:45:08 +01:00
Ruud fe52ac7203 Use default title as email subject 2013-01-16 19:45:39 +01:00
Cybertinus 4447b7611e Added the e-mail notifier 2013-01-15 21:15:17 +01:00
Ruud 178c8942c3 Merge branch 'refs/heads/develop' into desktop 2013-01-14 19:54:22 +01:00
Ruud 4fe9f9e42f Log subtitle search 2013-01-13 10:08:59 +01:00
Ruud 71b22345bc Make sure downloaders and providers match
Remove releases not found anymore on new searches
2013-01-12 16:07:11 +01:00
Ruud a0dc5c075a Remove print 2013-01-12 16:05:54 +01:00
Ruud a264c75f8c Remove releases that aren't found in latest search 2013-01-11 22:31:19 +01:00
Ruud fcc8a71eae NZBget version check 2013-01-11 21:45:25 +01:00
Ruud cdd681ad48 XBMC icon image 2013-01-11 21:02:22 +01:00
Ruud 36e5c49147 TorrentDay: decoding error. fix #1260 2013-01-11 20:50:38 +01:00
Ruud 300f4738a0 Randomize wanted search. fix #1261 2013-01-11 20:12:12 +01:00
Ruud 9447833653 RT cleanup 2013-01-10 07:42:15 +01:00
Ruud df53d0c578 Rename RT folder 2013-01-10 07:29:48 +01:00
Kris Kater 17eaba3e2a Added rottentomatoes automation 2013-01-10 07:28:39 +01:00
Ruud 0f389f18cb NZBGet: Don't use priority when set to normal. 2013-01-09 23:41:47 +01:00
Prinz23 28ce083f48 nzbget priority support 2013-01-09 23:34:03 +01:00
Ruud cfaffe2bcb SSL support 2013-01-09 23:29:54 +01:00
Ruud 432852cf5d Enable added combined list by default 2013-01-09 21:20:27 +01:00
Ruud 3c728608e9 FTDWorld use simple login
Add size
2013-01-09 20:42:33 +01:00
Ruud 8892ace3c2 OMGWTF proper link in settings 2013-01-09 20:26:02 +01:00
Ruud 87574a1810 Allow spotweb url without rewriterule. fix #1248 2013-01-08 20:28:59 +01:00
Ruud 14e0219e62 Urlencode spotweb id. fix #1213 2013-01-07 23:11:45 +01:00
97 changed files with 830 additions and 251 deletions
+1 -2
View File
@@ -62,7 +62,6 @@ class Loader(object):
self.log.logger.addHandler(hdlr) self.log.logger.addHandler(hdlr)
def addSignals(self): def addSignals(self):
signal.signal(signal.SIGINT, self.onExit) signal.signal(signal.SIGINT, self.onExit)
signal.signal(signal.SIGTERM, lambda signum, stack_frame: sys.exit(1)) signal.signal(signal.SIGTERM, lambda signum, stack_frame: sys.exit(1))
@@ -74,7 +73,7 @@ class Loader(object):
def onExit(self, signal, frame): def onExit(self, signal, frame):
from couchpotato.core.event import fireEvent from couchpotato.core.event import fireEvent
fireEvent('app.crappy_shutdown', single = True) fireEvent('app.shutdown', single = True)
def run(self): def run(self):
+4 -3
View File
@@ -5,9 +5,10 @@
* Search through the existing (and closed) issues first. See if you can get your answer there. * 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. * 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. * Post logs! Without seeing what is going on, I can't reproduce the error.
* What are you settings for the specific problem * What is the movie + quality you are searching for.
* What providers are you using. (While your logs include these, scanning through hundred of lines of log isn't my hobby) * What are you settings for the specific problem.
* Give me a short step by step of how to reproduce * 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. * 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 ;) * 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 ;)
+10 -8
View File
@@ -23,20 +23,22 @@ config = [{
'default': '', 'default': '',
'type': 'password', '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', 'name': 'port',
'default': 5050, 'default': 5050,
'type': 'int', 'type': 'int',
'description': 'The port I should listen to.', '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', 'name': 'launch_browser',
'default': True, 'default': True,
+3 -1
View File
@@ -10,6 +10,7 @@ from uuid import uuid4
import os import os
import platform import platform
import signal import signal
import sys
import time import time
import traceback import traceback
import webbrowser import webbrowser
@@ -178,6 +179,7 @@ class Core(Plugin):
def signalHandler(self): def signalHandler(self):
def signal_handler(signal, frame): def signal_handler(signal, frame):
fireEvent('app.do_shutdown') fireEvent('app.shutdown')
signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
-1
View File
@@ -2,7 +2,6 @@ from apscheduler.scheduler import Scheduler as Sched
from couchpotato.core.event import addEvent from couchpotato.core.event import addEvent
from couchpotato.core.logger import CPLog from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin from couchpotato.core.plugins.base import Plugin
import logging
log = CPLog(__name__) log = CPLog(__name__)
+13
View File
@@ -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': [],
},
],
}
+42 -12
View File
@@ -33,18 +33,41 @@ class Downloader(Provider):
] ]
def __init__(self): def __init__(self):
addEvent('download', self.download) addEvent('download', self._download)
addEvent('download.status', self.getAllDownloadStatus) addEvent('download.enabled', self._isEnabled)
addEvent('download.remove_failed', self.removeFailed) 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): def getEnabledDownloadType(self):
pass 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 return False
def removeFailed(self, name = {}, nzo_id = {}): def removeFailed(self, item):
return False return
def isCorrectType(self, item_type): def isCorrectType(self, item_type):
is_correct = item_type in self.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)) log.error('Failed converting magnet url to torrent: %s', (torrent_hash))
return False return False
def isDisabled(self, manual): def isDisabled(self, manual, data):
return not self.isEnabled(manual) 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) 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')))
@@ -10,6 +10,7 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'downloaders', 'tab': 'downloaders',
'list': 'download_providers',
'name': 'blackhole', 'name': 'blackhole',
'label': 'Black hole', 'label': 'Black hole',
'description': 'Download the NZB/Torrent to a specific folder.', 'description': 'Download the NZB/Torrent to a specific folder.',
+14 -5
View File
@@ -10,11 +10,7 @@ class Blackhole(Downloader):
type = ['nzb', 'torrent', 'torrent_magnet'] type = ['nzb', 'torrent', 'torrent_magnet']
def download(self, data = {}, movie = {}, manual = False, filedata = None): def download(self, data = {}, movie = {}, 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
directory = self.conf('directory') directory = self.conf('directory')
if not directory or not os.path.isdir(directory): if not directory or not os.path.isdir(directory):
@@ -52,4 +48,17 @@ class Blackhole(Downloader):
except: except:
log.info('Failed to download file %s: %s', (data.get('name'), traceback.format_exc())) log.info('Failed to download file %s: %s', (data.get('name'), traceback.format_exc()))
return False return False
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')]))
@@ -8,6 +8,7 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'downloaders', 'tab': 'downloaders',
'list': 'download_providers',
'name': 'nzbget', 'name': 'nzbget',
'label': 'NZBGet', 'label': 'NZBGet',
'description': 'Use <a href="http://nzbget.sourceforge.net/Main_Page" target="_blank">NZBGet</a> to download NZBs.', 'description': 'Use <a href="http://nzbget.sourceforge.net/Main_Page" target="_blank">NZBGet</a> to download NZBs.',
@@ -33,6 +34,13 @@ config = [{
'default': 'Movies', 'default': 'Movies',
'description': 'The category CP places the nzb in. Like <strong>movies</strong> or <strong>couchpotato</strong>', 'description': 'The category CP places the nzb in. Like <strong>movies</strong> or <strong>couchpotato</strong>',
}, },
{
'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', 'name': 'manual',
'default': 0, 'default': 0,
+9 -6
View File
@@ -1,7 +1,8 @@
from base64 import standard_b64encode from base64 import standard_b64encode
from couchpotato.core.downloaders.base import Downloader from couchpotato.core.downloaders.base import Downloader
from couchpotato.core.helpers.variable import tryInt
from couchpotato.core.logger import CPLog from couchpotato.core.logger import CPLog
from inspect import isfunction import re
import socket import socket
import traceback import traceback
import xmlrpclib import xmlrpclib
@@ -14,10 +15,7 @@ class NZBGet(Downloader):
url = 'http://nzbget:%(password)s@%(host)s/xmlrpc' url = 'http://nzbget:%(password)s@%(host)s/xmlrpc'
def download(self, data = {}, movie = {}, manual = False, filedata = None): def download(self, data = {}, movie = {}, filedata = None):
if self.isDisabled(manual) or not self.isCorrectType(data.get('type')):
return
if not filedata: if not filedata:
log.error('Unable to get NZB file: %s', traceback.format_exc()) log.error('Unable to get NZB file: %s', traceback.format_exc())
@@ -44,7 +42,12 @@ class NZBGet(Downloader):
log.error('Protocol Error: %s', e) log.error('Protocol Error: %s', e)
return False 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') log.info('NZB sent successfully to NZBGet')
return True return True
else: else:
@@ -8,6 +8,7 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'downloaders', 'tab': 'downloaders',
'list': 'download_providers',
'name': 'nzbvortex', 'name': 'nzbvortex',
'label': 'NZBVortex', 'label': 'NZBVortex',
'description': 'Use <a href="http://www.nzbvortex.com/landing/" target="_blank">NZBVortex</a> to download NZBs.', 'description': 'Use <a href="http://www.nzbvortex.com/landing/" target="_blank">NZBVortex</a> to download NZBs.',
+4 -10
View File
@@ -22,10 +22,7 @@ class NZBVortex(Downloader):
api_level = None api_level = None
session_id = None session_id = None
def download(self, data = {}, movie = {}, manual = False, filedata = None): def download(self, data = {}, movie = {}, filedata = None):
if self.isDisabled(manual) or not self.isCorrectType(data.get('type')) or not self.getApiLevel():
return
# Send the nzb # Send the nzb
try: try:
@@ -39,9 +36,6 @@ class NZBVortex(Downloader):
def getAllDownloadStatus(self): def getAllDownloadStatus(self):
if self.isDisabled(manual = True):
return False
raw_statuses = self.call('nzb') raw_statuses = self.call('nzb')
statuses = [] statuses = []
@@ -66,9 +60,6 @@ class NZBVortex(Downloader):
def removeFailed(self, item): def removeFailed(self, item):
if not self.conf('delete_failed', default = True):
return False
log.info('%s failed downloading, deleting...', item['name']) log.info('%s failed downloading, deleting...', item['name'])
try: try:
@@ -153,6 +144,9 @@ class NZBVortex(Downloader):
return self.api_level return self.api_level
def isEnabled(self, manual, data):
return super(NZBVortex, self).isEnabled(manual, data) and self.getApiLevel()
class HTTPSConnection(httplib.HTTPSConnection): class HTTPSConnection(httplib.HTTPSConnection):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@@ -9,6 +9,7 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'downloaders', 'tab': 'downloaders',
'list': 'download_providers',
'name': 'pneumatic', 'name': 'pneumatic',
'label': 'Pneumatic', 'label': 'Pneumatic',
'description': 'Use <a href="http://forum.xbmc.org/showthread.php?tid=97657" target="_blank">Pneumatic</a> to download .strm files.', 'description': 'Use <a href="http://forum.xbmc.org/showthread.php?tid=97657" target="_blank">Pneumatic</a> to download .strm files.',
@@ -11,9 +11,7 @@ class Pneumatic(Downloader):
type = ['nzb'] type = ['nzb']
strm_syntax = 'plugin://plugin.program.pneumatic/?mode=strm&type=add_file&nzb=%s&nzbname=%s' strm_syntax = 'plugin://plugin.program.pneumatic/?mode=strm&type=add_file&nzb=%s&nzbname=%s'
def download(self, data = {}, movie = {}, manual = False, filedata = None): def download(self, data = {}, movie = {}, filedata = None):
if self.isDisabled(manual) or (not self.isCorrectType(data.get('type'))):
return
directory = self.conf('directory') directory = self.conf('directory')
if not directory or not os.path.isdir(directory): if not directory or not os.path.isdir(directory):
@@ -8,6 +8,7 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'downloaders', 'tab': 'downloaders',
'list': 'download_providers',
'name': 'sabnzbd', 'name': 'sabnzbd',
'label': 'Sabnzbd', 'label': 'Sabnzbd',
'description': 'Use <a href="http://sabnzbd.org/" target="_blank">SABnzbd</a> to download NZBs.', 'description': 'Use <a href="http://sabnzbd.org/" target="_blank">SABnzbd</a> to download NZBs.',
+1 -9
View File
@@ -12,10 +12,7 @@ class Sabnzbd(Downloader):
type = ['nzb'] type = ['nzb']
def download(self, data = {}, movie = {}, manual = False, filedata = None): def download(self, data = {}, movie = {}, filedata = None):
if self.isDisabled(manual) or not self.isCorrectType(data.get('type')):
return
log.info('Sending "%s" to SABnzbd.', data.get('name')) log.info('Sending "%s" to SABnzbd.', data.get('name'))
@@ -65,8 +62,6 @@ class Sabnzbd(Downloader):
return False return False
def getAllDownloadStatus(self): def getAllDownloadStatus(self):
if self.isDisabled(manual = True):
return False
log.debug('Checking SABnzbd download status.') log.debug('Checking SABnzbd download status.')
@@ -122,9 +117,6 @@ class Sabnzbd(Downloader):
def removeFailed(self, item): def removeFailed(self, item):
if not self.conf('delete_failed', default = True):
return False
log.info('%s failed downloading, deleting...', item['name']) log.info('%s failed downloading, deleting...', item['name'])
try: try:
@@ -8,6 +8,7 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'downloaders', 'tab': 'downloaders',
'list': 'download_providers',
'name': 'synology', 'name': 'synology',
'label': 'Synology', 'label': 'Synology',
'description': 'Use <a href="http://www.synology.com/dsm/home_home_applications_download_station.php" target="_blank">Synology Download Station</a> to download.', 'description': 'Use <a href="http://www.synology.com/dsm/home_home_applications_download_station.php" target="_blank">Synology Download Station</a> to download.',
@@ -14,10 +14,7 @@ class Synology(Downloader):
type = ['torrent_magnet'] type = ['torrent_magnet']
log = CPLog(__name__) log = CPLog(__name__)
def download(self, data, movie, manual = False, filedata = None): def download(self, data, movie, filedata = None):
if self.isDisabled(manual) or not self.isCorrectType(data.get('type')):
return
log.error('Sending "%s" (%s) to Synology.', (data.get('name'), data.get('type'))) log.error('Sending "%s" (%s) to Synology.', (data.get('name'), data.get('type')))
@@ -8,6 +8,7 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'downloaders', 'tab': 'downloaders',
'list': 'download_providers',
'name': 'transmission', 'name': 'transmission',
'label': 'Transmission', 'label': 'Transmission',
'description': 'Use <a href="http://www.transmissionbt.com/" target="_blank">Transmission</a> to download torrents.', 'description': 'Use <a href="http://www.transmissionbt.com/" target="_blank">Transmission</a> to download torrents.',
@@ -16,10 +16,7 @@ class Transmission(Downloader):
type = ['torrent', 'torrent_magnet'] type = ['torrent', 'torrent_magnet']
log = CPLog(__name__) log = CPLog(__name__)
def download(self, data, movie, manual = False, filedata = None): def download(self, data, movie, filedata = None):
if self.isDisabled(manual) or not self.isCorrectType(data.get('type')):
return
log.debug('Sending "%s" (%s) to Transmission.', (data.get('name'), data.get('type'))) log.debug('Sending "%s" (%s) to Transmission.', (data.get('name'), data.get('type')))
@@ -41,10 +38,11 @@ class Transmission(Downloader):
'download-dir': folder_path 'download-dir': folder_path
} }
torrent_params = { if self.conf('ratio'):
'seedRatioLimit': self.conf('ratio'), torrent_params = {
'seedRatioMode': (0 if self.conf('ratio') else 1) 'seedRatioLimit': self.conf('ratio'),
} 'seedRatioMode': self.conf('ratio')
}
if not filedata and data.get('type') == 'torrent': if not filedata and data.get('type') == 'torrent':
log.error('Failed sending torrent, no data') log.error('Failed sending torrent, no data')
@@ -8,6 +8,7 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'downloaders', 'tab': 'downloaders',
'list': 'download_providers',
'name': 'utorrent', 'name': 'utorrent',
'label': 'uTorrent', 'label': 'uTorrent',
'description': 'Use <a href="http://www.utorrent.com/" target="_blank">uTorrent</a> to download torrents.', 'description': 'Use <a href="http://www.utorrent.com/" target="_blank">uTorrent</a> to download torrents.',
@@ -1,6 +1,6 @@
from bencode import bencode, bdecode from bencode import bencode, bdecode
from couchpotato.core.downloaders.base import Downloader 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 couchpotato.core.logger import CPLog
from hashlib import sha1 from hashlib import sha1
from multipartpost import MultipartPostHandler from multipartpost import MultipartPostHandler
@@ -20,10 +20,7 @@ class uTorrent(Downloader):
type = ['torrent', 'torrent_magnet'] type = ['torrent', 'torrent_magnet']
utorrent_api = None utorrent_api = None
def download(self, data, movie, manual = False, filedata = None): def download(self, data, movie, filedata = None):
if self.isDisabled(manual) or not self.isCorrectType(data.get('type')):
return
log.debug('Sending "%s" (%s) to uTorrent.', (data.get('name'), data.get('type'))) 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): def add_torrent_file(self, filename, filedata):
action = "action=add-file" 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): def set_torrent(self, hash, params):
action = "action=setprops&hash=%s" % hash action = "action=setprops&hash=%s" % hash
+2 -1
View File
@@ -115,7 +115,8 @@ def fireEvent(name, *args, **kwargs):
elif isinstance(results[0], list): elif isinstance(results[0], list):
merged = [] merged = []
for result in results: for result in results:
merged += result if result not in merged:
merged += result
results = merged results = merged
+12
View File
@@ -67,6 +67,18 @@ class Loader(object):
def addFromDir(self, plugin_type, priority, module, dir_name): 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, '*')): for cur_file in glob.glob(os.path.join(dir_name, '*')):
name = os.path.basename(cur_file) name = os.path.basename(cur_file)
if os.path.isdir(os.path.join(dir_name, name)): if os.path.isdir(os.path.join(dir_name, name)):
@@ -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': [],
},
],
}
@@ -8,6 +8,7 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'notifications', 'tab': 'notifications',
'list': 'notification_providers',
'name': 'boxcar', 'name': 'boxcar',
'options': [ 'options': [
{ {
@@ -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.',
},
],
}
],
}]
@@ -0,0 +1,54 @@
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_address = self.conf('to')
ssl = self.conf('ssl')
smtp_server = self.conf('smtp_server')
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_address
try:
# Open the SMTP connection, via SSL if requested
log.debug("SMTP over SSL %s", ("enabled" if ssl == 1 else "disabled"))
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:
log.debug("Logging on to SMTP server using username \'%s\'%s", (smtp_user, " and a password" if len(smtp_pass) > 0 else ""))
mailserver.login(smtp_user, smtp_pass)
# Send the e-mail
log.debug("Sending the email")
mailserver.sendmail(from_address, to_address, message.as_string())
# Close the SMTP connection
mailserver.quit()
log.info('Email notification sent')
return True
except:
log.error('E-mail failed: %s', traceback.format_exc())
return False
return False
@@ -8,6 +8,7 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'notifications', 'tab': 'notifications',
'list': 'notification_providers',
'name': 'growl', 'name': 'growl',
'description': 'Version 1.4+', 'description': 'Version 1.4+',
'options': [ 'options': [
@@ -8,6 +8,7 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'notifications', 'tab': 'notifications',
'list': 'notification_providers',
'name': 'nmj', 'name': 'nmj',
'label': 'NMJ', 'label': 'NMJ',
'options': [ 'options': [
@@ -8,6 +8,7 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'notifications', 'tab': 'notifications',
'list': 'notification_providers',
'name': 'notifo', 'name': 'notifo',
'description': 'Keep in mind that Notifo service will end soon.', 'description': 'Keep in mind that Notifo service will end soon.',
'options': [ 'options': [
@@ -8,6 +8,7 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'notifications', 'tab': 'notifications',
'list': 'notification_providers',
'name': 'notifymyandroid', 'name': 'notifymyandroid',
'label': 'Notify My Android', 'label': 'Notify My Android',
'options': [ 'options': [
@@ -8,8 +8,9 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'notifications', 'tab': 'notifications',
'list': 'notification_providers',
'name': 'notifymywp', 'name': 'notifymywp',
'label': 'Notify My Windows Phone', 'label': 'Windows Phone',
'options': [ 'options': [
{ {
'name': 'enabled', 'name': 'enabled',
@@ -8,6 +8,7 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'notifications', 'tab': 'notifications',
'list': 'notification_providers',
'name': 'plex', 'name': 'plex',
'options': [ 'options': [
{ {
@@ -8,6 +8,7 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'notifications', 'tab': 'notifications',
'list': 'notification_providers',
'name': 'prowl', 'name': 'prowl',
'options': [ 'options': [
{ {
@@ -8,6 +8,7 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'notifications', 'tab': 'notifications',
'list': 'notification_providers',
'name': 'pushover', 'name': 'pushover',
'options': [ 'options': [
{ {
@@ -8,6 +8,7 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'notifications', 'tab': 'notifications',
'list': 'notification_providers',
'name': 'synoindex', 'name': 'synoindex',
'description': 'Automaticly adds index to Synology Media Server.', 'description': 'Automaticly adds index to Synology Media Server.',
'options': [ 'options': [
@@ -8,6 +8,7 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'notifications', 'tab': 'notifications',
'list': 'notification_providers',
'name': 'toasty', 'name': 'toasty',
'options': [ 'options': [
{ {
@@ -8,6 +8,7 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'notifications', 'tab': 'notifications',
'list': 'notification_providers',
'name': 'twitter', 'name': 'twitter',
'options': [ 'options': [
{ {
@@ -8,6 +8,7 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'notifications', 'tab': 'notifications',
'list': 'notification_providers',
'name': 'xbmc', 'name': 'xbmc',
'label': 'XBMC', 'label': 'XBMC',
'description': 'v11 (Eden) and v12 (Frodo)', 'description': 'v11 (Eden) and v12 (Frodo)',
+5 -4
View File
@@ -13,6 +13,7 @@ class XBMC(Notification):
listen_to = ['renamer.after'] listen_to = ['renamer.after']
use_json_notifications = {} 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): def notify(self, message = '', data = {}, listener = None):
if self.isDisabled(): return if self.isDisabled(): return
@@ -27,7 +28,7 @@ class XBMC(Notification):
if self.use_json_notifications.get(host): if self.use_json_notifications.get(host):
response = self.request(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', {}), ('VideoLibrary.Scan', {}),
]) ])
else: else:
@@ -89,7 +90,7 @@ class XBMC(Notification):
self.use_json_notifications[host] = True self.use_json_notifications[host] = True
# send the text message # 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: for result in resp:
if (result.get('result') and result['result'] == 'OK'): if (result.get('result') and result['result'] == 'OK'):
log.debug('Message delivered successfully!') log.debug('Message delivered successfully!')
@@ -111,8 +112,8 @@ class XBMC(Notification):
server = 'http://%s/xbmcCmds/' % host server = 'http://%s/xbmcCmds/' % host
# title, message [, timeout , image #can be added!] # Notification(title, message [, timeout , image])
cmd = "xbmcHttp?command=ExecBuiltIn(Notification('%s','%s'))" % (urllib.quote(data['title']), urllib.quote(data['message'])) cmd = "xbmcHttp?command=ExecBuiltIn(Notification(%s,%s,'',%s))" % (urllib.quote(data['title']), urllib.quote(data['message']), urllib.quote(self.couch_logo_url))
server += cmd server += cmd
# I have no idea what to set to, just tried text/plain and seems to be working :) # I have no idea what to set to, just tried text/plain and seems to be working :)
+1 -1
View File
@@ -12,7 +12,7 @@ class Automation(Plugin):
fireEvent('schedule.interval', 'automation.add_movies', self.addMovies, hours = self.conf('hour', default = 12)) 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) addEvent('app.load', self.addMovies)
def addMovies(self): def addMovies(self):
+1 -1
View File
@@ -78,7 +78,7 @@ class Plugin(object):
self.makeDir(os.path.dirname(path)) self.makeDir(os.path.dirname(path))
try: try:
f = open(path, 'w' if not binary else 'wb') f = open(path, 'w+' if not binary else 'w+b')
f.write(content) f.write(content)
f.close() f.close()
os.chmod(path, Env.getPermission('file')) os.chmod(path, Env.getPermission('file'))
+5 -3
View File
@@ -135,7 +135,6 @@ class Manage(Plugin):
already_used = used_files.get(release_file['path']) already_used = used_files.get(release_file['path'])
if already_used: if already_used:
print already_used, release['id']
if already_used < release['id']: if already_used < release['id']:
fireEvent('release.delete', release['id'], single = True) # delete this one fireEvent('release.delete', release['id'], single = True) # delete this one
else: else:
@@ -199,9 +198,12 @@ class Manage(Plugin):
def directories(self): def directories(self):
try: try:
return splitString(self.conf('library', default = ''), '::') if self.conf('library', '').strip():
return splitString(self.conf('library', default = ''), '::')
except: except:
return [] pass
return []
def scanFilesToLibrary(self, folder = None, files = None): def scanFilesToLibrary(self, folder = None, files = None):
+130 -64
View File
@@ -12,6 +12,7 @@ from couchpotato.environment import Env
from inspect import ismethod, isfunction from inspect import ismethod, isfunction
from sqlalchemy.exc import InterfaceError from sqlalchemy.exc import InterfaceError
import datetime import datetime
import random
import re import re
import time import time
import traceback import traceback
@@ -83,37 +84,51 @@ class Searcher(Plugin):
movies = db.query(Movie).filter( movies = db.query(Movie).filter(
Movie.status.has(identifier = 'active') Movie.status.has(identifier = 'active')
).all() ).all()
random.shuffle(movies)
self.in_progress = { self.in_progress = {
'total': len(movies), 'total': len(movies),
'to_go': len(movies), 'to_go': len(movies),
} }
for movie in movies: try:
movie_dict = movie.to_dict({ search_types = self.getSearchTypes()
'profile': {'types': {'quality': {}}},
'releases': {'status': {}, 'quality': {}},
'library': {'titles': {}, 'files':{}},
'files': {}
})
try: for movie in movies:
self.single(movie_dict) movie_dict = movie.to_dict({
except IndexError: 'profile': {'types': {'quality': {}}},
log.error('Forcing library update for %s, if you see this often, please report: %s', (movie_dict['library']['identifier'], traceback.format_exc())) 'releases': {'status': {}, 'quality': {}},
fireEvent('library.update', movie_dict['library']['identifier'], force = True) 'library': {'titles': {}, 'files':{}},
except: 'files': {}
log.error('Search failed for %s: %s', (movie_dict['library']['identifier'], traceback.format_exc())) })
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 self.in_progress['to_go'] -= 1
if self.shuttingDown():
break # Break if CP wants to shut down
if self.shuttingDown():
break
except SearchSetupError:
pass
self.in_progress = False 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) done_status = fireEvent('status.get', 'done', single = True)
@@ -128,6 +143,8 @@ class Searcher(Plugin):
available_status = fireEvent('status.get', 'available', single = True) available_status = fireEvent('status.get', 'available', single = True)
ignored_status = fireEvent('status.get', 'ignored', single = True) ignored_status = fireEvent('status.get', 'ignored', single = True)
found_releases = []
default_title = getTitle(movie['library']) default_title = getTitle(movie['library'])
if not default_title: if not default_title:
log.error('No proper info found for movie, removing it from library to cause it from having more issues.') 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) fireEvent('notify.frontend', type = 'searcher.started.%s' % movie['id'], data = True, message = 'Searching for "%s"' % default_title)
ret = False ret = False
for quality_type in movie['profile']['types']: for quality_type in movie['profile']['types']:
if not self.couldBeReleased(quality_type['quality']['identifier'], release_dates, pre_releases): 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'])) log.info('Search for %s in %s', (default_title, quality_type['quality']['label']))
quality = fireEvent('quality.single', identifier = quality_type['quality']['identifier'], single = True) 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) sorted_results = sorted(results, key = lambda k: k['score'], reverse = True)
if len(sorted_results) == 0: if len(sorted_results) == 0:
@@ -172,10 +194,13 @@ class Searcher(Plugin):
# Add them to this movie releases list # Add them to this movie releases list
for nzb in sorted_results: 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: if not rls:
rls = Release( rls = Release(
identifier = md5(nzb['url']), identifier = nzb_identifier,
movie_id = movie.get('id'), movie_id = movie.get('id'),
quality_id = quality_type.get('quality_id'), quality_id = quality_type.get('quality_id'),
status_id = available_status.get('id') status_id = available_status.get('id')
@@ -223,6 +248,12 @@ class Searcher(Plugin):
break break
elif downloaded != 'try_next': elif downloaded != 'try_next':
break 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: else:
log.info('Better quality (%s) already available or snatched for %s', (quality_type['quality']['label'], default_title)) log.info('Better quality (%s) already available or snatched for %s', (quality_type['quality']['label'], default_title))
fireEvent('movie.restatus', movie['id']) fireEvent('movie.restatus', movie['id'])
@@ -238,61 +269,87 @@ class Searcher(Plugin):
def download(self, data, movie, manual = False): 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 if downloader_enabled:
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
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: successful = fireEvent('download', data = data, movie = movie, manual = manual, filedata = filedata, single = True)
# 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()
log_movie = '%s (%s) in %s' % (getTitle(movie['library']), movie['library']['year'], rls.quality.label) if successful:
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 try:
if not Env.setting('enabled', 'renamer'): # Mark release as snatched
active_status = fireEvent('status.get', 'active', single = True) db = get_session()
done_status = fireEvent('status.get', 'done', single = True) rls = db.query(Release).filter_by(identifier = md5(data['url'])).first()
try: if rls:
if movie['status_id'] == active_status.get('id'): rls.status_id = snatched_status.get('id')
for profile_type in movie['profile']['types']: db.commit()
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)
# Mark release done log_movie = '%s (%s) in %s' % (getTitle(movie['library']), movie['library']['year'], rls.quality.label)
rls.status_id = done_status.get('id') snatch_message = 'Snatched "%s": %s' % (data.get('name'), log_movie)
db.commit() log.info(snatch_message)
fireEvent('movie.snatched', message = snatch_message, data = rls.to_dict())
# Mark movie done # If renamer isn't used, mark movie done
mvie = db.query(Movie).filter_by(id = movie['id']).first() if not Env.setting('enabled', 'renamer'):
mvie.status_id = done_status.get('id') active_status = fireEvent('status.get', 'active', single = True)
db.commit() done_status = fireEvent('status.get', 'done', single = True)
except: try:
log.error('Failed marking movie finished, renamer disabled: %s', traceback.format_exc()) 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: # Mark release done
log.error('Failed marking movie finished: %s', traceback.format_exc()) 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 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): def correctMovie(self, nzb = {}, movie = {}, quality = {}, **kwargs):
imdb_results = kwargs.get('imdb_results', False) imdb_results = kwargs.get('imdb_results', False)
@@ -536,3 +593,12 @@ class Searcher(Plugin):
except: except:
log.error('Failed searching for next release: %s', traceback.format_exc()) log.error('Failed searching for next release: %s', traceback.format_exc())
return False return False
class SearchSetupError(Exception):
pass
class NoDownloaders(SearchSetupError):
pass
class NoProviders(SearchSetupError):
pass
@@ -49,6 +49,7 @@ class Subtitle(Plugin):
available_languages = sum(group['subtitle_language'].itervalues(), []) available_languages = sum(group['subtitle_language'].itervalues(), [])
downloaded = [] downloaded = []
files = [toUnicode(x) for x in group['files']['movie']] files = [toUnicode(x) for x in group['files']['movie']]
log.debug('Searching for subtitles for: %s', files)
for lang in self.getLanguages(): for lang in self.getLanguages():
if lang not in available_languages: if lang not in available_languages:
@@ -57,6 +58,7 @@ class Subtitle(Plugin):
downloaded.extend(download[subtitle]) downloaded.extend(download[subtitle])
for d_sub in downloaded: for d_sub in downloaded:
log.info('Found subtitle (%s): %s', (d_sub.language.alpha2, files))
group['files']['subtitle'].add(d_sub.path) group['files']['subtitle'].add(d_sub.path)
group['subtitle_language'][d_sub.path] = [d_sub.language.alpha2] group['subtitle_language'][d_sub.path] = [d_sub.language.alpha2]
@@ -1,8 +1,13 @@
.page.wizard .uniForm {
width: 80%;
margin: 0 auto 30px;
}
.page.wizard h1 { .page.wizard h1 {
padding: 10px 30px; padding: 10px 30px;
margin: 0; margin: 0;
display: block; display: block;
font-size: 40px; font-size: 30px;
margin-top: 80px; margin-top: 80px;
} }
@@ -41,8 +41,7 @@ Page.Wizard = new Class({
}, },
'providers': { 'providers': {
'title': 'Are you registered at any of these sites?', '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.', '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']
}, },
'renamer': { 'renamer': {
'title': 'Move & rename the movies after downloading?', 'title': 'Move & rename the movies after downloading?',
@@ -213,8 +212,6 @@ Page.Wizard = new Class({
// Hide retention // Hide retention
self.el.getElement('.tab_searcher').hide(); self.el.getElement('.tab_searcher').hide();
self.el.getElement('.t_searcher').hide(); self.el.getElement('.t_searcher').hide();
self.el.getElement('.t_nzb_providers').hide();
self.el.getElement('.t_torrent_providers').hide();
// Add pointer // Add pointer
new Element('.tab_wrapper').wraps(tabs).adopt( new Element('.tab_wrapper').wraps(tabs).adopt(
@@ -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': [],
},
],
}
@@ -8,6 +8,7 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'automation', 'tab': 'automation',
'list': 'automation_providers',
'name': 'bluray_automation', 'name': 'bluray_automation',
'label': 'Blu-ray.com', 'label': 'Blu-ray.com',
'description': 'Imports movies from blu-ray.com. (uses minimal requirements)', 'description': 'Imports movies from blu-ray.com. (uses minimal requirements)',
@@ -8,6 +8,7 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'automation', 'tab': 'automation',
'list': 'watchlist_providers',
'name': 'imdb_automation', 'name': 'imdb_automation',
'label': 'IMDB', 'label': 'IMDB',
'description': 'From any <strong>public</strong> IMDB watchlists. Url should be the RSS link.', 'description': 'From any <strong>public</strong> IMDB watchlists. Url should be the RSS link.',
@@ -27,7 +27,7 @@ class IMDB(Automation, RSS):
try: try:
rss_data = self.getHTMLData(url) 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: for imdb in imdbs:
movies.append(imdb) movies.append(imdb)
@@ -8,6 +8,7 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'automation', 'tab': 'automation',
'list': 'automation_providers',
'name': 'itunes_automation', 'name': 'itunes_automation',
'label': 'iTunes', 'label': 'iTunes',
'description': 'From any <a href="http://itunes.apple.com/rss">iTunes</a> Store feed. Url should be the RSS link. (uses minimal requirements)', 'description': 'From any <a href="http://itunes.apple.com/rss">iTunes</a> Store feed. Url should be the RSS link. (uses minimal requirements)',
@@ -8,6 +8,7 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'automation', 'tab': 'automation',
'list': 'automation_providers',
'name': 'kinepolis_automation', 'name': 'kinepolis_automation',
'label': 'Kinepolis', 'label': 'Kinepolis',
'description': 'Imports movies from the current top 10 of kinepolis. (uses minimal requirements)', 'description': 'Imports movies from the current top 10 of kinepolis. (uses minimal requirements)',
@@ -8,6 +8,7 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'automation', 'tab': 'automation',
'list': 'automation_providers',
'name': 'moviemeter_automation', 'name': 'moviemeter_automation',
'label': 'Moviemeter', 'label': 'Moviemeter',
'description': 'Imports movies from the current top 10 of moviemeter.nl. (uses minimal requirements)', 'description': 'Imports movies from the current top 10 of moviemeter.nl. (uses minimal requirements)',
@@ -8,6 +8,7 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'automation', 'tab': 'automation',
'list': 'watchlist_providers',
'name': 'moviesio', 'name': 'moviesio',
'label': 'Movies.IO', 'label': 'Movies.IO',
'description': 'Imports movies from <a href="http://movies.io">Movies.io</a> RSS watchlists', 'description': 'Imports movies from <a href="http://movies.io">Movies.io</a> RSS watchlists',
@@ -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'
}
],
},
],
}]
@@ -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
@@ -8,6 +8,7 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'automation', 'tab': 'automation',
'list': 'watchlist_providers',
'name': 'trakt_automation', 'name': 'trakt_automation',
'label': 'Trakt', 'label': 'Trakt',
'description': 'import movies from your own watchlist', 'description': 'import movies from your own watchlist',
+10 -2
View File
@@ -56,14 +56,14 @@ class Provider(Plugin):
return [] return []
def getRSSData(self, url, **kwargs): def getRSSData(self, url, item_path = 'channel/item', **kwargs):
data = self.getCache(md5(url), url, **kwargs) data = self.getCache(md5(url), url, **kwargs)
if data: if data:
try: try:
data = XMLTree.fromstring(data) data = XMLTree.fromstring(data)
return self.getElements(data, 'channel/item') return self.getElements(data, item_path)
except: except:
log.error('Failed to parsing %s: %s', (self.getName(), traceback.format_exc())) log.error('Failed to parsing %s: %s', (self.getName(), traceback.format_exc()))
@@ -84,8 +84,16 @@ class YarrProvider(Provider):
login_opener = None login_opener = None
def __init__(self): def __init__(self):
addEvent('provider.enabled_types', self.getEnabledProviderType)
addEvent('provider.belongs_to', self.belongsTo) addEvent('provider.belongs_to', self.belongsTo)
addEvent('yarr.search', self.search) 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): def login(self):
@@ -30,11 +30,6 @@ class MovieResultModifier(Plugin):
temp[imdb] = self.getLibraryTags(imdb) temp[imdb] = self.getLibraryTags(imdb)
order.append(imdb) order.append(imdb)
if item.get('via_imdb'):
if order.count(imdb):
order.remove(imdb)
order.insert(0, imdb)
# Merge dicts # Merge dicts
temp[imdb] = mergeDicts(temp[imdb], item) temp[imdb] = mergeDicts(temp[imdb], item)
@@ -33,7 +33,7 @@ class CouchPotatoApi(MovieProvider):
def search(self, q, limit = 12): def search(self, q, limit = 12):
cache_key = 'cpapi.cache.%s' % q 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: if cached:
try: try:
@@ -50,7 +50,7 @@ class CouchPotatoApi(MovieProvider):
return return
cache_key = 'cpapi.cache.info.%s' % identifier 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: if cached:
try: try:
@@ -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': [],
},
],
}
@@ -8,9 +8,11 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'searcher', 'tab': 'searcher',
'subtab': 'nzb_providers', 'subtab': 'providers',
'list': 'nzb_providers',
'name': 'binsearch', 'name': 'binsearch',
'description': 'Free provider, less accurate. See <a href="https://www.binsearch.info/">BinSearch</a>', 'description': 'Free provider, less accurate. See <a href="https://www.binsearch.info/">BinSearch</a>',
'wizard': True,
'options': [ 'options': [
{ {
'name': 'enabled', 'name': 'enabled',
@@ -22,11 +22,10 @@ class BinSearch(NZBProvider):
def _search(self, movie, quality, results): def _search(self, movie, quality, results):
q = '%s %s' % (movie['library']['identifier'], quality.get('identifier'))
arguments = tryUrlencode({ arguments = tryUrlencode({
'q': q, 'q': movie['library']['identifier'],
'm': 'n', 'm': 'n',
'max': 250, 'max': 400,
'adv_age': Env.setting('retention', 'nzb'), 'adv_age': Env.setting('retention', 'nzb'),
'adv_sort': 'date', 'adv_sort': 'date',
'adv_col': 'on', 'adv_col': 'on',
@@ -8,9 +8,11 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'searcher', 'tab': 'searcher',
'subtab': 'nzb_providers', 'subtab': 'providers',
'list': 'nzb_providers',
'name': 'FTDWorld', 'name': 'FTDWorld',
'description': 'Free provider, less accurate. See <a href="http://ftdworld.net">FTDWorld</a>', 'description': 'Free provider, less accurate. See <a href="http://ftdworld.net">FTDWorld</a>',
'wizard': True,
'options': [ 'options': [
{ {
'name': 'enabled', 'name': 'enabled',
@@ -4,6 +4,7 @@ from couchpotato.core.logger import CPLog
from couchpotato.core.providers.nzb.base import NZBProvider from couchpotato.core.providers.nzb.base import NZBProvider
from couchpotato.environment import Env from couchpotato.environment import Env
from dateutil.parser import parse from dateutil.parser import parse
import json
import traceback import traceback
log = CPLog(__name__) log = CPLog(__name__)
@@ -15,7 +16,7 @@ class FTDWorld(NZBProvider):
'search': 'http://ftdworld.net/api/index.php?%s', 'search': 'http://ftdworld.net/api/index.php?%s',
'detail': 'http://ftdworld.net/spotinfo.php?id=%s', 'detail': 'http://ftdworld.net/spotinfo.php?id=%s',
'download': 'http://ftdworld.net/cgi-bin/nzbdown.pl?fileID=%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 http_time_between_calls = 3 #seconds
@@ -56,6 +57,7 @@ class FTDWorld(NZBProvider):
'id': nzb_id, 'id': nzb_id,
'name': toUnicode(item.get('Title')), 'name': toUnicode(item.get('Title')),
'age': self.calculateAge(tryInt(item.get('Created'))), 'age': self.calculateAge(tryInt(item.get('Created'))),
'size': item.get('Size', 0),
'url': self.urls['download'] % nzb_id, 'url': self.urls['download'] % nzb_id,
'download': self.loginDownload, 'download': self.loginDownload,
'detail_url': self.urls['detail'] % nzb_id, 'detail_url': self.urls['detail'] % nzb_id,
@@ -73,4 +75,7 @@ class FTDWorld(NZBProvider):
}) })
def loginSuccess(self, output): def loginSuccess(self, output):
return 'password is incorrect' not in output try:
return json.loads(output).get('goodToGo', False)
except:
return False
@@ -8,7 +8,8 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'searcher', 'tab': 'searcher',
'subtab': 'nzb_providers', 'subtab': 'providers',
'list': 'nzb_providers',
'name': 'newznab', 'name': 'newznab',
'order': 10, 'order': 10,
'description': 'Enable <a href="http://newznab.com/" target="_blank">NewzNab providers</a> such as <a href="https://nzb.su" target="_blank">NZB.su</a>, \ 'description': 'Enable <a href="http://newznab.com/" target="_blank">NewzNab providers</a> such as <a href="https://nzb.su" target="_blank">NZB.su</a>, \
+13 -2
View File
@@ -104,12 +104,23 @@ class Newznab(NZBProvider, RSS):
return result return result
def getUrl(self, host, type): def getUrl(self, host, type):
if '?page=newznabapi' in host:
return cleanHost(host)[:-1] + '&t=' + type
return cleanHost(host) + 'api?t=' + type return cleanHost(host) + 'api?t=' + type
def isDisabled(self, host): def isDisabled(self, host = None):
return not self.isEnabled(host) 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']) return NZBProvider.isEnabled(self) and host['host'] and host['api_key'] and int(host['use'])
def getApiExt(self, host): def getApiExt(self, host):
@@ -8,9 +8,11 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'searcher', 'tab': 'searcher',
'subtab': 'nzb_providers', 'subtab': 'providers',
'list': 'nzb_providers',
'name': 'NZBClub', 'name': 'NZBClub',
'description': 'Free provider, less accurate. See <a href="https://www.nzbclub.com/">NZBClub</a>', 'description': 'Free provider, less accurate. See <a href="https://www.nzbclub.com/">NZBClub</a>',
'wizard': True,
'options': [ 'options': [
{ {
'name': 'enabled', 'name': 'enabled',
@@ -20,13 +20,13 @@ class NZBClub(NZBProvider, RSS):
def _searchOnTitle(self, title, movie, quality, results): 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({ params = tryUrlencode({
'q': q, 'q': q,
'ig': '1', 'ig': 1,
'rpp': 200, 'rpp': 200,
'st': 1, 'st': 5,
'sp': 1, 'sp': 1,
'ns': 1, 'ns': 1,
}) })
@@ -8,9 +8,11 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'searcher', 'tab': 'searcher',
'subtab': 'nzb_providers', 'subtab': 'providers',
'list': 'nzb_providers',
'name': 'nzbindex', 'name': 'nzbindex',
'description': 'Free provider, less accurate. See <a href="https://www.nzbindex.com/">NZBIndex</a>', 'description': 'Free provider, less accurate. See <a href="https://www.nzbindex.com/">NZBIndex</a>',
'wizard': True,
'options': [ 'options': [
{ {
'name': 'enabled', 'name': 'enabled',
@@ -23,7 +23,7 @@ class NzbIndex(NZBProvider, RSS):
def _searchOnTitle(self, title, movie, quality, results): 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({ arguments = tryUrlencode({
'q': q, 'q': q,
'age': Env.setting('retention', 'nzb'), 'age': Env.setting('retention', 'nzb'),
@@ -8,10 +8,11 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'searcher', 'tab': 'searcher',
'subtab': 'nzb_providers', 'subtab': 'providers',
'list': 'nzb_providers',
'name': 'nzbsrus', 'name': 'nzbsrus',
'label': 'Nzbsrus', 'label': 'Nzbsrus',
'description': 'See <a href="https://www.nzbsrus.com/">NZBsRus</a>', 'description': 'See <a href="https://www.nzbsrus.com/">NZBsRus</a>. <strong>You need a VIP account!</strong>',
'wizard': True, 'wizard': True,
'options': [ 'options': [
{ {
@@ -37,7 +37,7 @@ class Nzbsrus(NZBProvider, RSS):
arguments += '&lang0=1&lang3=1&lang1=1' arguments += '&lang0=1&lang3=1&lang1=1'
url = '%s&%s&%s' % (self.urls['search'], arguments , cat_id_string) 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: for nzb in nzbs:
@@ -53,7 +53,7 @@ class Nzbsrus(NZBProvider, RSS):
'name': title, 'name': title,
'age': age, 'age': age,
'size': size, '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, 'detail_url': self.urls['detail'] % nzb_id,
'description': self.getTextElement(nzb, 'addtext'), 'description': self.getTextElement(nzb, 'addtext'),
}) })
@@ -8,9 +8,11 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'searcher', 'tab': 'searcher',
'subtab': 'nzb_providers', 'subtab': 'providers',
'list': 'nzb_providers',
'name': 'nzbX', 'name': 'nzbX',
'description': 'Free provider. See <a href="https://www.nzbx.co/">nzbX</a>', 'description': 'Free provider. See <a href="https://www.nzbx.co/">nzbX</a>',
'wizard': True,
'options': [ 'options': [
{ {
'name': 'enabled', 'name': 'enabled',
@@ -8,9 +8,11 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'searcher', 'tab': 'searcher',
'subtab': 'nzb_providers', 'subtab': 'providers',
'list': 'nzb_providers',
'name': 'OMGWTFNZBs', 'name': 'OMGWTFNZBs',
'description': 'See <a href="http://www.omgwtfnzbs.com/">OMGWTFNZBs</a>', 'description': 'See <a href="http://omgwtfnzbs.org/">OMGWTFNZBs</a>',
'wizard': True,
'options': [ 'options': [
{ {
'name': 'enabled', 'name': 'enabled',
@@ -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': [],
},
],
}
@@ -8,7 +8,8 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'searcher', 'tab': 'searcher',
'subtab': 'torrent_providers', 'subtab': 'providers',
'list': 'torrent_providers',
'name': 'KickAssTorrents', 'name': 'KickAssTorrents',
'description': 'See <a href="https://kat.ph/">KickAssTorrents</a>', 'description': 'See <a href="https://kat.ph/">KickAssTorrents</a>',
'wizard': True, 'wizard': True,
@@ -8,9 +8,11 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'searcher', 'tab': 'searcher',
'subtab': 'torrent_providers', 'subtab': 'providers',
'list': 'torrent_providers',
'name': 'PassThePopcorn', 'name': 'PassThePopcorn',
'description': 'See <a href="https://passthepopcorn.me">PassThePopcorn.me</a>', 'description': 'See <a href="https://passthepopcorn.me">PassThePopcorn.me</a>',
'wizard': True,
'options': [ 'options': [
{ {
'name': 'enabled', 'name': 'enabled',
@@ -8,9 +8,11 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'searcher', 'tab': 'searcher',
'subtab': 'torrent_providers', 'subtab': 'providers',
'list': 'torrent_providers',
'name': 'PublicHD', 'name': 'PublicHD',
'description': 'Public Torrent site with only HD content. See <a href="https://publichd.se/">PublicHD</a>', 'description': 'Public Torrent site with only HD content. See <a href="https://publichd.se/">PublicHD</a>',
'wizard': True,
'options': [ 'options': [
{ {
'name': 'enabled', 'name': 'enabled',
@@ -8,9 +8,11 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'searcher', 'tab': 'searcher',
'subtab': 'torrent_providers', 'subtab': 'providers',
'list': 'torrent_providers',
'name': 'SceneAccess', 'name': 'SceneAccess',
'description': 'See <a href="https://sceneaccess.eu/">SceneAccess</a>', 'description': 'See <a href="https://sceneaccess.eu/">SceneAccess</a>',
'wizard': True,
'options': [ 'options': [
{ {
'name': 'enabled', 'name': 'enabled',
@@ -8,9 +8,11 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'searcher', 'tab': 'searcher',
'subtab': 'torrent_providers', 'subtab': 'providers',
'list': 'torrent_providers',
'name': 'SceneHD', 'name': 'SceneHD',
'description': 'See <a href="https://scenehd.org">SceneHD</a>', 'description': 'See <a href="https://scenehd.org">SceneHD</a>',
'wizard': True,
'options': [ 'options': [
{ {
'name': 'enabled', 'name': 'enabled',
@@ -22,7 +22,7 @@ class SceneHD(TorrentProvider):
def _searchOnTitle(self, title, movie, quality, results): 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({ arguments = tryUrlencode({
'search': q, 'search': q,
}) })
@@ -8,7 +8,8 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'searcher', 'tab': 'searcher',
'subtab': 'torrent_providers', 'subtab': 'providers',
'list': 'torrent_providers',
'name': 'ThePirateBay', 'name': 'ThePirateBay',
'description': 'The world\'s largest bittorrent tracker. See <a href="http://fucktimkuik.org/">ThePirateBay</a>', 'description': 'The world\'s largest bittorrent tracker. See <a href="http://fucktimkuik.org/">ThePirateBay</a>',
'wizard': True, 'wizard': True,
@@ -15,7 +15,7 @@ class ThePirateBay(TorrentMagnetProvider):
urls = { urls = {
'detail': '%s/torrent/%s', 'detail': '%s/torrent/%s',
'search': '%s/search/%s/0/7/%d' 'search': '%s/search/%s/%s/7/%d'
} }
cat_ids = [ cat_ids = [
@@ -45,52 +45,66 @@ class ThePirateBay(TorrentMagnetProvider):
def _searchOnTitle(self, title, movie, quality, results): 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: search_url = self.urls['search'] % (self.getDomain(), tryUrlencode('"%s %s"' % (title, movie['library']['year'])), page, self.getCatId(quality['identifier'])[0])
try: page += 1
soup = BeautifulSoup(data)
results_table = soup.find('table', attrs = {'id': 'searchResult'})
if not results_table: data = self.getHTMLData(search_url)
return
entries = results_table.find_all('tr') if data:
for result in entries[2:]: try:
link = result.find(href = re.compile('torrent\/\d+\/')) soup = BeautifulSoup(data)
download = result.find(href = re.compile('magnet:')) results_table = soup.find('table', attrs = {'id': 'searchResult'})
if not results_table:
return
try: try:
size = re.search('Size (?P<size>.+),', unicode(result.select('font.detDesc')[0])).group('size') total_pages = len(soup.find('div', attrs = {'align': 'center'}).find_all('a'))
except: except:
continue pass
if link and download: print total_pages, page
def extra_score(item): entries = results_table.find_all('tr')
trusted = (0, 10)[result.find('img', alt = re.compile('Trusted')) != None] for result in entries[2:]:
vip = (0, 20)[result.find('img', alt = re.compile('VIP')) != None] link = result.find(href = re.compile('torrent\/\d+\/'))
confirmed = (0, 30)[result.find('img', alt = re.compile('Helpers')) != None] download = result.find(href = re.compile('magnet:'))
moderated = (0, 50)[result.find('img', alt = re.compile('Moderator')) != None]
return confirmed + trusted + vip + moderated try:
size = re.search('Size (?P<size>.+),', unicode(result.select('font.detDesc')[0])).group('size')
except:
continue
results.append({ if link and download:
'id': re.search('/(?P<id>\d+)/', link['href']).group('id'),
'name': link.string, def extra_score(item):
'url': download['href'], trusted = (0, 10)[result.find('img', alt = re.compile('Trusted')) != None]
'detail_url': self.getDomain(link['href']), vip = (0, 20)[result.find('img', alt = re.compile('VIP')) != None]
'size': self.parseSize(size), confirmed = (0, 30)[result.find('img', alt = re.compile('Helpers')) != None]
'seeders': tryInt(result.find_all('td')[2].string), moderated = (0, 50)[result.find('img', alt = re.compile('Moderator')) != None]
'leechers': tryInt(result.find_all('td')[3].string),
'extra_score': extra_score, return confirmed + trusted + vip + moderated
'get_more_info': self.getMoreInfo
}) results.append({
'id': re.search('/(?P<id>\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): def isEnabled(self):
return super(ThePirateBay, self).isEnabled() and self.getDomain() return super(ThePirateBay, self).isEnabled() and self.getDomain()
@@ -8,9 +8,11 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'searcher', 'tab': 'searcher',
'subtab': 'torrent_providers', 'subtab': 'providers',
'list': 'torrent_providers',
'name': 'TorrentDay', 'name': 'TorrentDay',
'description': 'See <a href="http://www.td.af/">TorrentDay</a>', 'description': 'See <a href="http://www.td.af/">TorrentDay</a>',
'wizard': True,
'options': [ 'options': [
{ {
'name': 'enabled', 'name': 'enabled',
@@ -59,3 +59,6 @@ class TorrentDay(TorrentProvider):
'password': self.conf('password'), 'password': self.conf('password'),
'submit': 'submit', 'submit': 'submit',
}) })
def loginSuccess(self, output):
return 'Password not correct' not in output
@@ -8,9 +8,11 @@ config = [{
'groups': [ 'groups': [
{ {
'tab': 'searcher', 'tab': 'searcher',
'subtab': 'torrent_providers', 'subtab': 'providers',
'list': 'torrent_providers',
'name': 'TorrentLeech', 'name': 'TorrentLeech',
'description': 'See <a href="http://torrentleech.org">TorrentLeech</a>', 'description': 'See <a href="http://torrentleech.org">TorrentLeech</a>',
'wizard': True,
'options': [ 'options': [
{ {
'name': 'enabled', 'name': 'enabled',
@@ -34,7 +34,7 @@ class TorrentLeech(TorrentProvider):
def _searchOnTitle(self, title, movie, quality, results): 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) data = self.getHTMLData(url, opener = self.login_opener)
if data: if data:
+15 -4
View File
@@ -4,6 +4,7 @@ from couchpotato.api import api, NonBlockHandler
from couchpotato.core.event import fireEventAsync, fireEvent from couchpotato.core.event import fireEventAsync, fireEvent
from couchpotato.core.helpers.variable import getDataDir, tryInt from couchpotato.core.helpers.variable import getDataDir, tryInt
from logging import handlers from logging import handlers
from tornado.httpserver import HTTPServer
from tornado.web import Application, FallbackHandler from tornado.web import Application, FallbackHandler
from tornado.wsgi import WSGIContainer from tornado.wsgi import WSGIContainer
from werkzeug.contrib.cache import FileSystemCache from werkzeug.contrib.cache import FileSystemCache
@@ -97,7 +98,7 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
total_backups = len(backups) total_backups = len(backups)
for backup in backups: for backup in backups:
if total_backups > 3: if total_backups > 3:
if int(os.path.basename(backup)) < time.time() - 259200: if tryInt(os.path.basename(backup)) < time.time() - 259200:
for src_file in src_files: for src_file in src_files:
b_file = os.path.join(backup, os.path.basename(src_file)) b_file = os.path.join(backup, os.path.basename(src_file))
if os.path.isfile(b_file): if os.path.isfile(b_file):
@@ -210,8 +211,10 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
# app.debug = development # app.debug = development
config = { config = {
'use_reloader': reloader, '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 # Static path
@@ -243,12 +246,20 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
debug = config['use_reloader'] 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 try_restart = True
restart_tries = 5 restart_tries = 5
while try_restart: while try_restart:
try: try:
application.listen(config['port'], config['host'], no_keep_alive = True) server.listen(config['port'])
loop.start() loop.start()
except Exception, e: except Exception, e:
try: try:
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

+33 -6
View File
@@ -7,6 +7,7 @@ Page.Settings = new Class({
wizard_only: false, wizard_only: false,
tabs: {}, tabs: {},
lists: {},
current: 'about', current: 'about',
has_tab: false, has_tab: false,
@@ -178,12 +179,24 @@ Page.Settings = new Class({
var content_container = self.tabs[group.tab].subtabs[group.subtab].content 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 // Create the group
if(!self.tabs[group.tab].groups[group.name]){ if(!self.tabs[group.tab].groups[group.name]){
var group_el = self.createGroup(group) var group_el = self.createGroup(group)
.inject(content_container) .inject(group.list ? self.lists[group.list] : content_container)
.addClass('section_'+section_name); .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 // Add options to group
@@ -283,6 +296,14 @@ Page.Settings = new Class({
) )
return group_el 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(){ 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(){ afterInject: function(){
var self = this; 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.el.inject(self.parentFieldset, 'top')
self.checkState() self.checkState()
} }
@@ -1311,7 +1338,7 @@ Option.Combined = new Class({
if(has_empty > 0) return; if(has_empty > 0) return;
self.add_empty_timeout = setTimeout(function(){ self.add_empty_timeout = setTimeout(function(){
self.createItem(false, null); self.createItem({'use': true});
}, 10); }, 10);
}, },
+75 -3
View File
@@ -92,6 +92,10 @@
font-size: 12px; font-size: 12px;
margin-left: 10px; margin-left: 10px;
} }
.page fieldset h2 .hint a {
margin: 0 !important;
padding: 0;
}
.page fieldset.disabled .ctrlHolder { .page fieldset.disabled .ctrlHolder {
display: none; display: none;
@@ -102,7 +106,7 @@
width: auto; width: auto;
margin: 0; margin: 0;
position: relative; position: relative;
margin-bottom: -25px; margin-bottom: -23px;
border: none; border: none;
width: 20px; width: 20px;
} }
@@ -148,6 +152,74 @@
} }
.page .xsmall { width: 20px !important; text-align: center; } .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 5px 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: 173px;
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] { .page input[type=text], .page input[type=password] {
padding: 5px 3px; padding: 5px 3px;
@@ -557,14 +629,14 @@
.group_userscript .or { .group_userscript .or {
float: left; float: left;
margin: 20px 10px; margin: 20px -10px 0 10px;
} }
.group_userscript .bookmarklet { .group_userscript .bookmarklet {
display: block; display: block;
display: block; display: block;
float: left; float: left;
padding: 20px 15px 0 0 ; padding: 20px 15px 0 25px;
border-radius: 5px; border-radius: 5px;
} }
+1 -1
View File
@@ -1,5 +1,5 @@
#define MyAppName "CouchPotato" #define MyAppName "CouchPotato"
#define MyAppVer "2.0.5" #define MyAppVer "2.0.6"
[Setup] [Setup]
AppName={#MyAppName} AppName={#MyAppName}
+1 -1
View File
@@ -1,2 +1,2 @@
VERSION = '2.0.5' VERSION = '2.0.6'
BRANCH = 'desktop' BRANCH = 'desktop'