Notifications
This commit is contained in:
1 parent
288aa8c0a1
commit
85fc1c01ee
67 files changed
+3270
-400
No files matched your search
@@ -9,35 +9,28 @@ config = [{
|
||||
{
|
||||
'tab': 'general',
|
||||
'name': 'basics',
|
||||
'label': 'Basics',
|
||||
'description': 'Needs restart before changes take effect.',
|
||||
'options': [
|
||||
{
|
||||
'name': 'username',
|
||||
'default': '',
|
||||
'type': 'string',
|
||||
'label': 'Username',
|
||||
},
|
||||
{
|
||||
'name': 'password',
|
||||
'default': '',
|
||||
'password': True,
|
||||
'type': 'string',
|
||||
'label': 'Password',
|
||||
'type': 'password',
|
||||
},
|
||||
{
|
||||
'name': 'host',
|
||||
'advanced': True,
|
||||
'default': '0.0.0.0',
|
||||
'type': 'string',
|
||||
'label': 'Host',
|
||||
'label': 'IP',
|
||||
'description': 'Host that I should listen to. "0.0.0.0" listens to all ips.',
|
||||
},
|
||||
{
|
||||
'name': 'port',
|
||||
'default': 5000,
|
||||
'type': 'int',
|
||||
'label': 'Port',
|
||||
'description': 'The port I should listen to.',
|
||||
},
|
||||
{
|
||||
@@ -52,14 +45,12 @@ config = [{
|
||||
{
|
||||
'tab': 'general',
|
||||
'name': 'advanced',
|
||||
'label': 'Advanced',
|
||||
'description': "For those who know what the're doing",
|
||||
'advanced': True,
|
||||
'options': [
|
||||
{
|
||||
'name': 'api_key',
|
||||
'default': uuid4().hex,
|
||||
'type': 'string',
|
||||
'readonly': True,
|
||||
'label': 'Api Key',
|
||||
'description': "This is top-secret! Don't share this!",
|
||||
@@ -74,9 +65,8 @@ config = [{
|
||||
{
|
||||
'name': 'url_base',
|
||||
'default': '',
|
||||
'type': 'string',
|
||||
'label': 'Url Base',
|
||||
'description': 'When using mod_proxy use this to prepend the url with this.',
|
||||
'description': 'When using mod_proxy use this to append the url with this.',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
Whitespace-only changes.
@@ -0,0 +1,20 @@
|
||||
from couchpotato.core.event import addEvent
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.environment import Env
|
||||
|
||||
class Downloader(Plugin):
|
||||
|
||||
def __init__(self):
|
||||
addEvent('download', self.download)
|
||||
|
||||
def download(self, data = {}):
|
||||
pass
|
||||
|
||||
def conf(self, attr):
|
||||
return Env.setting(attr, self.__class__.__name__.lower())
|
||||
|
||||
def isDisabled(self):
|
||||
return not self.isEnabled()
|
||||
|
||||
def isEnabled(self):
|
||||
return self.conf('enabled', True)
|
||||
@@ -0,0 +1,32 @@
|
||||
from .main import Blackhole
|
||||
|
||||
def start():
|
||||
return Blackhole()
|
||||
|
||||
config = [{
|
||||
'name': 'blackhole',
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'downloaders',
|
||||
'name': 'blackhole',
|
||||
'label': 'Black hole',
|
||||
'description': 'Fill in your Sabnzbd settings.',
|
||||
'options': [
|
||||
{
|
||||
'name': 'enabled',
|
||||
'default': False,
|
||||
'type': 'bool',
|
||||
'label': 'Enabled',
|
||||
'description': 'Send snatched NZBs to Sabnzbd',
|
||||
},
|
||||
{
|
||||
'name': 'directory',
|
||||
'default': '',
|
||||
'type': 'directory',
|
||||
'label': 'Directory',
|
||||
'description': 'Directory where the .nzb (or .torrent) file is saved to.',
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
}]
|
||||
@@ -0,0 +1,36 @@
|
||||
from __future__ import with_statement
|
||||
from couchpotato.core.helpers.encoding import toSafeString
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.downloaders.base import Downloader
|
||||
import os
|
||||
import urllib
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
class Blackhole(Downloader):
|
||||
|
||||
type = ['nzb', 'torrent']
|
||||
|
||||
def download(self, data = {}):
|
||||
|
||||
if self.isDisabled() or not self.isCorrectType(data.get('type')):
|
||||
return
|
||||
|
||||
directory = self.conf('directory')
|
||||
|
||||
if not directory or not os.path.isdir(directory):
|
||||
log.error('No directory set for blackhole %s download.' % data.get('type'))
|
||||
else:
|
||||
fullPath = os.path.join(directory, toSafeString(data.get('name')) + '.' + data)
|
||||
|
||||
if not os.path.isfile(fullPath):
|
||||
log.info('Downloading %s to %s.' % (data.get('type'), fullPath))
|
||||
file = urllib.urlopen(data.get('url')).read()
|
||||
with open(fullPath, 'wb') as f:
|
||||
f.write(file)
|
||||
|
||||
return True
|
||||
else:
|
||||
log.error('File %s already exists.' % fullPath)
|
||||
|
||||
return False
|
||||
@@ -0,0 +1,39 @@
|
||||
from .main import Sabnzbd
|
||||
|
||||
def start():
|
||||
return Sabnzbd()
|
||||
|
||||
config = [{
|
||||
'name': 'sabnzbd',
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'downloaders',
|
||||
'name': 'sabnzbd',
|
||||
'label': 'Sabnzbd',
|
||||
'description': 'Fill in your Sabnzbd settings.',
|
||||
'options': [
|
||||
{
|
||||
'name': 'enabled',
|
||||
'default': False,
|
||||
'type': 'bool',
|
||||
'label': 'Enabled',
|
||||
'description': 'Send snatched NZBs to Sabnzbd',
|
||||
},
|
||||
{
|
||||
'name': 'host',
|
||||
'default': 'localhost:8080',
|
||||
'type': 'string',
|
||||
'label': 'Host',
|
||||
'description': 'Test',
|
||||
},
|
||||
{
|
||||
'name': 'api_key',
|
||||
'default': '',
|
||||
'type': 'string',
|
||||
'label': 'Api Key',
|
||||
'description': 'Used for all calls to Sabnzbd.',
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
}]
|
||||
@@ -0,0 +1,120 @@
|
||||
from couchpotato.core.downloaders.base import Downloader
|
||||
from couchpotato.core.helpers.variable import cleanHost
|
||||
from couchpotato.core.logger import CPLog
|
||||
from tempfile import mkstemp
|
||||
from urllib import urlencode
|
||||
import base64
|
||||
import os
|
||||
import re
|
||||
import urllib2
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
class Sabnzbd(Downloader):
|
||||
|
||||
type = ['nzb']
|
||||
|
||||
def download(self, data = {}):
|
||||
|
||||
if self.isDisabled() or not self.isCorrectType(data.get('type')):
|
||||
return
|
||||
|
||||
log.info("Sending '%s' to SABnzbd." % data.get('name'))
|
||||
|
||||
if self.conf('ppDir') and data.get('imdb_id'):
|
||||
try:
|
||||
pp_script_fn = self.buildPp(data.get('imdb_id'))
|
||||
except:
|
||||
log.info("Failed to create post-processing script.")
|
||||
pp_script_fn = False
|
||||
if not pp_script_fn:
|
||||
pp = False
|
||||
else:
|
||||
pp = True
|
||||
else:
|
||||
pp = False
|
||||
|
||||
params = {
|
||||
'apikey': self.conf('apikey'),
|
||||
'cat': self.conf('category'),
|
||||
'mode': 'addurl',
|
||||
'name': data.get('url')
|
||||
}
|
||||
|
||||
# sabNzbd complains about "invalid archive file" for newzbin urls
|
||||
# added using addurl, works fine with addid
|
||||
if data.get('addbyid'):
|
||||
params['mode'] = 'addid'
|
||||
|
||||
if pp:
|
||||
params['script'] = pp_script_fn
|
||||
|
||||
url = cleanHost(self.conf('host')) + "api?" + urlencode(params)
|
||||
log.info("URL: " + url)
|
||||
|
||||
try:
|
||||
r = urllib2.urlopen(url, timeout = 30)
|
||||
except:
|
||||
log.error("Unable to connect to SAB.")
|
||||
return False
|
||||
|
||||
result = r.read().strip()
|
||||
if not result:
|
||||
log.error("SABnzbd didn't return anything.")
|
||||
return False
|
||||
|
||||
log.debug("Result text from SAB: " + result)
|
||||
if result == "ok":
|
||||
log.info("NZB sent to SAB successfully.")
|
||||
return True
|
||||
elif result == "Missing authentication":
|
||||
log.error("Incorrect username/password.")
|
||||
return False
|
||||
else:
|
||||
log.error("Unknown error: " + result)
|
||||
return False
|
||||
|
||||
def buildPp(self, imdb_id):
|
||||
|
||||
pp_script_path = self.getPpFile()
|
||||
|
||||
scriptB64 = '''IyEvdXNyL2Jpbi9weXRob24KaW1wb3J0IG9zCmltcG9ydCBzeXMKcHJpbnQgIkNyZWF0aW5nIGNwLmNw
|
||||
bmZvIGZvciAlcyIgJSBzeXMuYXJndlsxXQppbWRiSWQgPSB7W0lNREJJREhFUkVdfQpwYXRoID0gb3Mu
|
||||
cGF0aC5qb2luKHN5cy5hcmd2WzFdLCAiY3AuY3BuZm8iKQp0cnk6CiBmID0gb3BlbihwYXRoLCAndycp
|
||||
CmV4Y2VwdCBJT0Vycm9yOgogcHJpbnQgIlVuYWJsZSB0byBvcGVuICVzIGZvciB3cml0aW5nIiAlIHBh
|
||||
dGgKIHN5cy5leGl0KDEpCnRyeToKIGYud3JpdGUob3MucGF0aC5iYXNlbmFtZShzeXMuYXJndlswXSkr
|
||||
IlxuIitpbWRiSWQpCmV4Y2VwdDoKIHByaW50ICJVbmFibGUgdG8gd3JpdGUgdG8gZmlsZTogJXMiICUg
|
||||
cGF0aAogc3lzLmV4aXQoMikKZi5jbG9zZSgpCnByaW50ICJXcm90ZSBpbWRiIGlkLCAlcywgdG8gZmls
|
||||
ZTogJXMiICUgKGltZGJJZCwgcGF0aCkK'''
|
||||
|
||||
script = re.sub(r"\{\[IMDBIDHERE\]\}", "'%s'" % imdb_id, base64.b64decode(scriptB64))
|
||||
|
||||
try:
|
||||
f = open(pp_script_path, 'wb')
|
||||
except:
|
||||
log.info("Unable to open post-processing script for writing. Check permissions: %s" % pp_script_path)
|
||||
return False
|
||||
|
||||
try:
|
||||
f.write(script)
|
||||
f.close()
|
||||
except:
|
||||
log.info("Unable to write to post-processing script. Check permissions: %s" % pp_script_path)
|
||||
return False
|
||||
|
||||
log.info("Wrote post-processing script to: %s" % pp_script_path)
|
||||
|
||||
return os.path.basename(pp_script_path)
|
||||
|
||||
def getPpFile(self):
|
||||
|
||||
pp_script_handle, pp_script_path = mkstemp(suffix = '.py', dir = self.conf('ppDir'))
|
||||
pp_sh = os.fdopen(pp_script_handle)
|
||||
pp_sh.close()
|
||||
|
||||
try:
|
||||
os.chmod(pp_script_path, int('777', 8))
|
||||
except:
|
||||
log.info("Unable to set post-processing script permissions to 777 (may still work correctly): %s" % pp_script_path)
|
||||
|
||||
return pp_script_path
|
||||
@@ -1,4 +1,5 @@
|
||||
from axl.axel import Event
|
||||
from couchpotato.core.helpers.variable import merge_dicts
|
||||
from couchpotato.core.logger import CPLog
|
||||
import traceback
|
||||
|
||||
@@ -19,31 +20,64 @@ def removeEvent(name, handler):
|
||||
e -= handler
|
||||
|
||||
def fireEvent(name, *args, **kwargs):
|
||||
log.debug('Firing "%s": %s, %s' % (name, args, kwargs))
|
||||
try:
|
||||
|
||||
# Return single handler
|
||||
single = False
|
||||
try:
|
||||
del kwargs['single']
|
||||
single = True
|
||||
except: pass
|
||||
|
||||
# Merge items
|
||||
merge = False
|
||||
try:
|
||||
del kwargs['merge']
|
||||
merge = True
|
||||
except: pass
|
||||
|
||||
e = events[name]
|
||||
e.asynchronous = False
|
||||
result = e(*args, **kwargs)
|
||||
|
||||
results = []
|
||||
for r in result:
|
||||
if r[0] == True:
|
||||
results.append(r[1])
|
||||
else:
|
||||
etype, value, tb = r[1]
|
||||
log.debug(''.join(traceback.format_exception(etype, value, tb)))
|
||||
if single and not merge:
|
||||
results = result[0][1]
|
||||
else:
|
||||
results = []
|
||||
for r in result:
|
||||
if r[0] == True:
|
||||
results.append(r[1])
|
||||
else:
|
||||
errorHandler(r[1])
|
||||
|
||||
# Merge the results
|
||||
if merge:
|
||||
merged = {}
|
||||
for result in results:
|
||||
merged = merge_dicts(merged, result)
|
||||
|
||||
results = merged
|
||||
|
||||
return results
|
||||
except Exception, e:
|
||||
log.debug(e)
|
||||
log.error('%s: %s' % (name, e))
|
||||
|
||||
def fireEventAsync(name, *args, **kwargs):
|
||||
log.debug('Async "%s": %s, %s' % (name, args, kwargs))
|
||||
try:
|
||||
e = events[name]
|
||||
e.asynchronous = True
|
||||
e.error_handler = errorHandler
|
||||
|
||||
e(*args, **kwargs)
|
||||
return True
|
||||
except Exception, e:
|
||||
log.debug(e)
|
||||
log.error('%s: %s' % (name, e))
|
||||
|
||||
def errorHandler(error):
|
||||
etype, value, tb = error
|
||||
log.error(''.join(traceback.format_exception(etype, value, tb)))
|
||||
|
||||
def getEvent(name):
|
||||
return events[name]
|
||||
@@ -5,16 +5,19 @@ import unicodedata
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
def toSafeString(original):
|
||||
valid_chars = "-_.() %s%s" % (ascii_letters, digits)
|
||||
cleanedFilename = unicodedata.normalize('NFKD', toUnicode(original)).encode('ASCII', 'ignore')
|
||||
return ''.join(c for c in cleanedFilename if c in valid_chars)
|
||||
|
||||
|
||||
def simplifyString(original):
|
||||
string = toSafeString(original)
|
||||
split = re.split('\W+', string.lower())
|
||||
return toUnicode(' '.join(split))
|
||||
|
||||
|
||||
def toUnicode(original, *args):
|
||||
try:
|
||||
if type(original) is unicode:
|
||||
|
||||
@@ -42,7 +42,7 @@ def dictToList(params):
|
||||
new = {}
|
||||
for x, value in params.iteritems():
|
||||
try:
|
||||
new_value = [dictToList(value2) for value2 in value.itervalues()]
|
||||
new_value = [dictToList(value[k]) for k in sorted(value.iterkeys())]
|
||||
except:
|
||||
new_value = value
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import hashlib
|
||||
import os.path
|
||||
|
||||
def is_dict(object):
|
||||
return isinstance(object, dict)
|
||||
|
||||
|
||||
def merge_dicts(a, b):
|
||||
assert is_dict(a), is_dict(b)
|
||||
dst = a.copy()
|
||||
|
||||
stack = [(dst, b)]
|
||||
while stack:
|
||||
current_dst, current_src = stack.pop()
|
||||
for key in current_src:
|
||||
if key not in current_dst:
|
||||
current_dst[key] = current_src[key]
|
||||
else:
|
||||
if is_dict(current_src[key]) and is_dict(current_dst[key]) :
|
||||
stack.append((current_dst[key], current_src[key]))
|
||||
else:
|
||||
current_dst[key] = current_src[key]
|
||||
return dst
|
||||
|
||||
def md5(text):
|
||||
return hashlib.md5(text).hexdigest()
|
||||
|
||||
def getExt(filename):
|
||||
return os.path.splitext(filename)[1][1:]
|
||||
|
||||
def cleanHost(host):
|
||||
if not host.startswith(('http://', 'https://')):
|
||||
host = 'http://' + host
|
||||
|
||||
if not host.endswith('/'):
|
||||
host += '/'
|
||||
|
||||
return host
|
||||
@@ -1,4 +1,4 @@
|
||||
from couchpotato.core.event import fireEvent, fireEventAsync
|
||||
from couchpotato.core.event import fireEvent
|
||||
from couchpotato.core.logger import CPLog
|
||||
import glob
|
||||
import os
|
||||
@@ -17,6 +17,8 @@ class Loader:
|
||||
self.paths = {
|
||||
'plugin' : ('couchpotato.core.plugins', os.path.join(root, 'couchpotato', 'core', 'plugins')),
|
||||
'provider' : ('couchpotato.core.providers', os.path.join(root, 'couchpotato', 'core', 'providers')),
|
||||
'notifications' : ('couchpotato.core.notifications', os.path.join(root, 'couchpotato', 'core', 'notifications')),
|
||||
'downloaders' : ('couchpotato.core.downloaders', os.path.join(root, 'couchpotato', 'core', 'downloaders')),
|
||||
}
|
||||
|
||||
for type, tuple in self.paths.iteritems():
|
||||
@@ -28,14 +30,17 @@ class Loader:
|
||||
for module_name, plugin in sorted(self.modules.iteritems()):
|
||||
|
||||
# Load module
|
||||
m = getattr(self.loadModule(module_name), plugin.get('name'))
|
||||
try:
|
||||
m = getattr(self.loadModule(module_name), plugin.get('name'))
|
||||
|
||||
log.info("Loading '%s'" % module_name)
|
||||
log.info("Loading %s: %s" % (plugin['type'], plugin['name']))
|
||||
|
||||
# Save default settings for plugin/provider
|
||||
did_save += self.loadSettings(m, module_name, save = False)
|
||||
# Save default settings for plugin/provider
|
||||
did_save += self.loadSettings(m, module_name, save = False)
|
||||
|
||||
self.loadPlugins(m, plugin.get('name'))
|
||||
self.loadPlugins(m, plugin.get('name'))
|
||||
except Exception, e:
|
||||
log.error(e)
|
||||
|
||||
if did_save:
|
||||
fireEvent('settings.save')
|
||||
@@ -51,12 +56,12 @@ class Loader:
|
||||
def loadSettings(self, module, name, save = True):
|
||||
try:
|
||||
for section in module.config:
|
||||
fireEventAsync('settings.options', section['name'], section)
|
||||
fireEvent('settings.options', section['name'], section)
|
||||
options = {}
|
||||
for group in section['groups']:
|
||||
for option in group['options']:
|
||||
options[option['name']] = option['default']
|
||||
fireEventAsync('settings.register', section_name = section['name'], options = options, save = save)
|
||||
fireEvent('settings.register', section_name = section['name'], options = options, save = save)
|
||||
return True
|
||||
except Exception, e:
|
||||
log.debug("Failed loading settings for '%s': %s" % (name, e))
|
||||
@@ -67,7 +72,7 @@ class Loader:
|
||||
module.start()
|
||||
return True
|
||||
except Exception, e:
|
||||
log.debug("Failed loading plugin '%s': %s" % (name, e))
|
||||
log.error("Failed loading plugin '%s': %s" % (name, e))
|
||||
return False
|
||||
|
||||
def addModule(self, type, module, name):
|
||||
|
||||
Whitespace-only changes.
@@ -0,0 +1,29 @@
|
||||
from couchpotato.core.event import addEvent
|
||||
from couchpotato.core.helpers.request import jsonified
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.environment import Env
|
||||
|
||||
class Notification(Plugin):
|
||||
|
||||
default_title = 'CouchPotato'
|
||||
test_message = 'ZOMG Lazors Pewpewpew!'
|
||||
|
||||
def __init__(self):
|
||||
addEvent('notify', self.notify)
|
||||
|
||||
def notify(self, message = '', data = {}):
|
||||
pass
|
||||
|
||||
def conf(self, attr):
|
||||
return Env.setting(attr, self.__class__.__name__.lower())
|
||||
|
||||
def isDisabled(self):
|
||||
return not self.isEnabled()
|
||||
|
||||
def isEnabled(self):
|
||||
return self.conf('enabled', True)
|
||||
|
||||
def test(self):
|
||||
success = self.notify(message = self.test_message)
|
||||
|
||||
return jsonified({'success': success})
|
||||
@@ -0,0 +1,33 @@
|
||||
from .main import Growl
|
||||
|
||||
def start():
|
||||
return Growl()
|
||||
|
||||
config = [{
|
||||
'name': 'growl',
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'notifications',
|
||||
'name': 'growl',
|
||||
'options': [
|
||||
{
|
||||
'name': 'enabled',
|
||||
'default': False,
|
||||
'type': 'enabler',
|
||||
'description': '',
|
||||
},
|
||||
{
|
||||
'name': 'host',
|
||||
'default': 'localhost',
|
||||
'description': '',
|
||||
},
|
||||
{
|
||||
'name': 'password',
|
||||
'default': '',
|
||||
'type': 'password',
|
||||
'description': '',
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
}]
|
||||
@@ -0,0 +1,111 @@
|
||||
# Based on netprowl by the following authors.
|
||||
|
||||
# Altered 1st October 2010 - Tim Child.
|
||||
# Have added the ability for the command line arguments to take a password.
|
||||
|
||||
# Altered 1-17-2010 - Tanner Stokes - www.tannr.com
|
||||
# Added support for command line arguments
|
||||
|
||||
# ORIGINAL CREDITS
|
||||
# """Growl 0.6 Network Protocol Client for Python"""
|
||||
# __version__ = "0.6.3"
|
||||
# __author__ = "Rui Carmo (http://the.taoofmac.com)"
|
||||
# __copyright__ = "(C) 2004 Rui Carmo. Code under BSD License."
|
||||
# __contributors__ = "Ingmar J Stein (Growl Team), John Morrissey (hashlib patch)"
|
||||
|
||||
import struct
|
||||
|
||||
try:
|
||||
import hashlib
|
||||
md5_constructor = hashlib.md5
|
||||
except ImportError:
|
||||
import md5
|
||||
md5_constructor = md5.new
|
||||
|
||||
GROWL_UDP_PORT = 9887
|
||||
GROWL_PROTOCOL_VERSION = 1
|
||||
GROWL_TYPE_REGISTRATION = 0
|
||||
GROWL_TYPE_NOTIFICATION = 1
|
||||
|
||||
|
||||
class GrowlRegistrationPacket:
|
||||
"""Builds a Growl Network Registration packet.
|
||||
Defaults to emulating the command-line growlnotify utility."""
|
||||
|
||||
def __init__(self, application = "CouchPotato", password = None):
|
||||
self.notifications = []
|
||||
self.defaults = [] # array of indexes into notifications
|
||||
self.application = application.encode("utf-8")
|
||||
self.password = password
|
||||
|
||||
def addNotification(self, notification = "General Notification", enabled = True):
|
||||
"""Adds a notification type and sets whether it is enabled on the GUI"""
|
||||
|
||||
self.notifications.append(notification)
|
||||
if enabled:
|
||||
self.defaults.append(len(self.notifications) - 1)
|
||||
|
||||
def payload(self):
|
||||
"""Returns the packet payload."""
|
||||
self.data = struct.pack("!BBH",
|
||||
GROWL_PROTOCOL_VERSION,
|
||||
GROWL_TYPE_REGISTRATION,
|
||||
len(self.application)
|
||||
)
|
||||
self.data += struct.pack("BB",
|
||||
len(self.notifications),
|
||||
len(self.defaults)
|
||||
)
|
||||
self.data += self.application
|
||||
for notification in self.notifications:
|
||||
encoded = notification.encode("utf-8")
|
||||
self.data += struct.pack("!H", len(encoded))
|
||||
self.data += encoded
|
||||
for default in self.defaults:
|
||||
self.data += struct.pack("B", default)
|
||||
self.checksum = md5()
|
||||
self.checksum.update(self.data)
|
||||
if self.password:
|
||||
self.checksum.update(self.password)
|
||||
self.data += self.checksum.digest()
|
||||
return self.data
|
||||
|
||||
class GrowlNotificationPacket:
|
||||
"""Builds a Growl Network Notification packet.
|
||||
Defaults to emulating the command-line growlnotify utility."""
|
||||
|
||||
def __init__(self, application = "CouchPotato",
|
||||
notification = "General Notification", title = "Title",
|
||||
description = "Description", priority = 0, sticky = False, password = None):
|
||||
|
||||
self.application = application.encode("utf-8")
|
||||
self.notification = notification.encode("utf-8")
|
||||
self.title = title.encode("utf-8")
|
||||
self.description = description.encode("utf-8")
|
||||
flags = (priority & 0x07) * 2
|
||||
if priority < 0:
|
||||
flags |= 0x08
|
||||
if sticky:
|
||||
flags = flags | 0x0100
|
||||
self.data = struct.pack("!BBHHHHH",
|
||||
GROWL_PROTOCOL_VERSION,
|
||||
GROWL_TYPE_NOTIFICATION,
|
||||
flags,
|
||||
len(self.notification),
|
||||
len(self.title),
|
||||
len(self.description),
|
||||
len(self.application)
|
||||
)
|
||||
self.data += self.notification
|
||||
self.data += self.title
|
||||
self.data += self.description
|
||||
self.data += self.application
|
||||
self.checksum = md5_constructor()
|
||||
self.checksum.update(self.data)
|
||||
if password:
|
||||
self.checksum.update(password)
|
||||
self.data += self.checksum.digest()
|
||||
|
||||
def payload(self):
|
||||
"""Returns the packet payload."""
|
||||
return self.data
|
||||
@@ -0,0 +1,45 @@
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import addEvent
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.notifications.base import Notification
|
||||
from couchpotato.core.notifications.growl.growl import GROWL_UDP_PORT, \
|
||||
GrowlRegistrationPacket, GrowlNotificationPacket
|
||||
from couchpotato.environment import Env
|
||||
from socket import AF_INET, SOCK_DGRAM, socket
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
class Growl(Notification):
|
||||
|
||||
def __init__(self):
|
||||
addEvent('notify', self.notify)
|
||||
addEvent('notify.growl', self.notify)
|
||||
|
||||
addApiView('notify.growl.test', self.test)
|
||||
|
||||
def conf(self, attr):
|
||||
return Env.setting(attr, 'growl')
|
||||
|
||||
def notify(self, message = '', data = {}):
|
||||
|
||||
if self.isDisabled():
|
||||
return
|
||||
|
||||
hosts = [x.strip() for x in self.conf('host').split(",")]
|
||||
password = self.conf('password')
|
||||
|
||||
for curHost in hosts:
|
||||
addr = (curHost, GROWL_UDP_PORT)
|
||||
|
||||
s = socket(AF_INET, SOCK_DGRAM)
|
||||
p = GrowlRegistrationPacket(password = password)
|
||||
p.addNotification()
|
||||
s.sendto(p.payload(), addr)
|
||||
|
||||
# send notification
|
||||
p = GrowlNotificationPacket(title = self.default_title, description = message, priority = 0, sticky = False, password = password)
|
||||
s.sendto(p.payload(), addr)
|
||||
s.close()
|
||||
|
||||
log.info('Growl notifications sent.')
|
||||
@@ -0,0 +1,37 @@
|
||||
from .main import NMJ
|
||||
|
||||
def start():
|
||||
return NMJ()
|
||||
|
||||
config = [{
|
||||
'name': 'nmj',
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'notifications',
|
||||
'name': 'nmj',
|
||||
'label': 'NMJ',
|
||||
'options': [
|
||||
{
|
||||
'name': 'enabled',
|
||||
'default': False,
|
||||
'type': 'enabler',
|
||||
},
|
||||
{
|
||||
'name': 'host',
|
||||
'default': 'localhost',
|
||||
'description': '',
|
||||
},
|
||||
{
|
||||
'name': 'database',
|
||||
'default': '',
|
||||
'description': '',
|
||||
},
|
||||
{
|
||||
'name': 'mount',
|
||||
'default': '',
|
||||
'description': '',
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
}]
|
||||
@@ -0,0 +1,131 @@
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import addEvent
|
||||
from couchpotato.core.helpers.request import getParams, jsonified
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.notifications.base import Notification
|
||||
from couchpotato.environment import Env
|
||||
import re
|
||||
import telnetlib
|
||||
import urllib
|
||||
import urllib2
|
||||
|
||||
try:
|
||||
import xml.etree.cElementTree as etree
|
||||
except ImportError:
|
||||
import xml.etree.ElementTree as etree
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
class NMJ(Notification):
|
||||
|
||||
def __init__(self):
|
||||
addEvent('notify', self.notify)
|
||||
addEvent('notify.nmj', self.notify)
|
||||
|
||||
addApiView('notify.nmj.test', self.test)
|
||||
addApiView('notify.nmj.auto_config', self.autoConfig)
|
||||
|
||||
def conf(self, attr):
|
||||
return Env.setting(attr, 'nmj')
|
||||
|
||||
def autoConfig(self):
|
||||
|
||||
params = getParams()
|
||||
host = params.get('host', 'localhost')
|
||||
|
||||
database = ''
|
||||
mount = ''
|
||||
|
||||
try:
|
||||
terminal = telnetlib.Telnet(host)
|
||||
except Exception:
|
||||
log.error('Warning: unable to get a telnet session to %s' % (host))
|
||||
return self.failed()
|
||||
|
||||
log.debug('Connected to %s via telnet' % (host))
|
||||
terminal.read_until('sh-3.00# ')
|
||||
terminal.write('cat /tmp/source\n')
|
||||
terminal.write('cat /tmp/netshare\n')
|
||||
terminal.write('exit\n')
|
||||
tnoutput = terminal.read_all()
|
||||
|
||||
match = re.search(r'(.+\.db)\r\n?(.+)(?=sh-3.00# cat /tmp/netshare)', tnoutput)
|
||||
|
||||
if match:
|
||||
database = match.group(1)
|
||||
device = match.group(2)
|
||||
log.info('Found NMJ database %s on device %s' % (database, device))
|
||||
else:
|
||||
log.error('Could not get current NMJ database on %s, NMJ is probably not running!' % (host))
|
||||
return self.failed()
|
||||
|
||||
if device.startswith('NETWORK_SHARE/'):
|
||||
match = re.search('.*(?=\r\n?%s)' % (re.escape(device[14:])), tnoutput)
|
||||
|
||||
if match:
|
||||
mount = match.group().replace('127.0.0.1', host)
|
||||
log.info('Found mounting url on the Popcorn Hour in configuration: %s' % (mount))
|
||||
else:
|
||||
log.error('Detected a network share on the Popcorn Hour, but could not get the mounting url')
|
||||
return self.failed()
|
||||
|
||||
return jsonified({
|
||||
'success': True,
|
||||
'database': database,
|
||||
'mount': mount,
|
||||
})
|
||||
|
||||
def notify(self, message = '', data = {}):
|
||||
|
||||
if self.isDisabled():
|
||||
return False
|
||||
|
||||
host = self.conf('host')
|
||||
mount = self.conf('mount')
|
||||
database = self.conf('database')
|
||||
|
||||
if self.mount:
|
||||
try:
|
||||
req = urllib2.Request(mount)
|
||||
log.debug('Try to mount network drive via url: %s' % (mount))
|
||||
handle = urllib2.urlopen(req)
|
||||
except IOError, e:
|
||||
log.error('Warning: Couldn\'t contact popcorn hour on host %s: %s' % (host, e))
|
||||
return False
|
||||
|
||||
params = {
|
||||
'arg0': 'scanner_start',
|
||||
'arg1': database,
|
||||
'arg2': 'background',
|
||||
'arg3': '',
|
||||
}
|
||||
params = urllib.urlencode(params)
|
||||
UPDATE_URL = 'http://%(host)s:8008/metadata_database?%(params)s'
|
||||
updateUrl = UPDATE_URL % {'host': host, 'params': params}
|
||||
|
||||
try:
|
||||
req = urllib2.Request(updateUrl)
|
||||
log.debug('Sending NMJ scan update command via url: %s' % (updateUrl))
|
||||
handle = urllib2.urlopen(req)
|
||||
response = handle.read()
|
||||
except IOError, e:
|
||||
log.error('Warning: Couldn\'t contact Popcorn Hour on host %s: %s' % (host, e))
|
||||
return False
|
||||
|
||||
try:
|
||||
et = etree.fromstring(response)
|
||||
result = et.findtext('returnValue')
|
||||
except SyntaxError, e:
|
||||
log.error('Unable to parse XML returned from the Popcorn Hour: %s' % (e))
|
||||
return False
|
||||
|
||||
if int(result) > 0:
|
||||
log.error('Popcorn Hour returned an errorcode: %s' % (result))
|
||||
return False
|
||||
else:
|
||||
log.info('NMJ started background scan')
|
||||
return True
|
||||
|
||||
def failed(self):
|
||||
return jsonified({'success': False})
|
||||
@@ -0,0 +1,32 @@
|
||||
from .main import Notifo
|
||||
|
||||
def start():
|
||||
return Notifo()
|
||||
|
||||
config = [{
|
||||
'name': 'notifo',
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'notifications',
|
||||
'name': 'notifo',
|
||||
'description': '',
|
||||
'options': [
|
||||
{
|
||||
'name': 'enabled',
|
||||
'default': False,
|
||||
'type': 'enabler',
|
||||
},
|
||||
{
|
||||
'name': 'username',
|
||||
'default': '',
|
||||
'type': 'string',
|
||||
},
|
||||
{
|
||||
'name': 'password',
|
||||
'default': '',
|
||||
'type': 'password',
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
}]
|
||||
@@ -0,0 +1,53 @@
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import addEvent
|
||||
from couchpotato.core.helpers.encoding import toUnicode
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.notifications.base import Notification
|
||||
from couchpotato.environment import Env
|
||||
from flask.helpers import json
|
||||
import base64
|
||||
import urllib
|
||||
import urllib2
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
class Notifo(Notification):
|
||||
|
||||
url = 'https://api.notifo.com/v1/send_notification'
|
||||
|
||||
def __init__(self):
|
||||
addEvent('notify', self.notify)
|
||||
addEvent('notify.notifo', self.notify)
|
||||
|
||||
addApiView('notify.notifo.test', self.test)
|
||||
|
||||
def conf(self, attr):
|
||||
return Env.setting(attr, 'notifo')
|
||||
|
||||
def notify(self, message = '', data = {}):
|
||||
|
||||
if self.isDisabled():
|
||||
return False
|
||||
|
||||
try:
|
||||
data = urllib.urlencode({
|
||||
'msg': toUnicode(message),
|
||||
})
|
||||
|
||||
req = urllib2.Request(self.url)
|
||||
authHeader = "Basic %s" % base64.encodestring('%s:%s' % (self.conf('username'), self.conf('api_key')))[:-1]
|
||||
req.add_header("Authorization", authHeader)
|
||||
|
||||
handle = urllib2.urlopen(req, data)
|
||||
result = json.load(handle)
|
||||
|
||||
if result['status'] != 'success' or result['response_message'] != 'OK':
|
||||
raise Exception
|
||||
|
||||
except Exception, e:
|
||||
log.error('Notification failed: %s' % e)
|
||||
return False
|
||||
|
||||
log.info('Notifo notification successful.')
|
||||
return True
|
||||
@@ -0,0 +1,33 @@
|
||||
from .main import Plex
|
||||
|
||||
def start():
|
||||
return Plex()
|
||||
|
||||
config = [{
|
||||
'name': 'plex',
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'notifications',
|
||||
'name': 'plex',
|
||||
'options': [
|
||||
{
|
||||
'name': 'enabled',
|
||||
'default': False,
|
||||
'type': 'enabler',
|
||||
'description': '',
|
||||
},
|
||||
{
|
||||
'name': 'host',
|
||||
'default': 'localhost',
|
||||
'description': '',
|
||||
},
|
||||
{
|
||||
'name': 'password',
|
||||
'default': '',
|
||||
'type': 'password',
|
||||
'description': '',
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
}]
|
||||
@@ -0,0 +1,43 @@
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import addEvent
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.notifications.base import Notification
|
||||
from xml.dom import minidom
|
||||
import urllib
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
class Plex(Notification):
|
||||
|
||||
def __init__(self):
|
||||
addEvent('notify', self.notify)
|
||||
addEvent('notify.plex', self.notify)
|
||||
|
||||
addApiView('notify.plex.test', self.test)
|
||||
|
||||
def notify(self, message = '', data = {}):
|
||||
|
||||
if self.isDisabled():
|
||||
return
|
||||
|
||||
log.info('Sending notification to Plex')
|
||||
hosts = [x.strip() for x in self.conf('host').split(",")]
|
||||
|
||||
for host in hosts:
|
||||
|
||||
source_type = ['movie']
|
||||
base_url = 'http://%s/library/sections' % host
|
||||
refresh_url = '%s/%%s/refresh' % base_url
|
||||
|
||||
try:
|
||||
xml_sections = minidom.parse(urllib.urlopen(base_url))
|
||||
sections = xml_sections.getElementsByTagName('Directory')
|
||||
for s in sections:
|
||||
if s.getAttribute('type') in source_type:
|
||||
url = refresh_url % s.getAttribute('key')
|
||||
x = urllib.urlopen(url)
|
||||
except:
|
||||
log.error('Plex library update failed for %s.' % host)
|
||||
|
||||
return True
|
||||
@@ -0,0 +1,35 @@
|
||||
from .main import Prowl
|
||||
|
||||
def start():
|
||||
return Prowl()
|
||||
|
||||
config = [{
|
||||
'name': 'prowl',
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'notifications',
|
||||
'name': 'prowl',
|
||||
'options': [
|
||||
{
|
||||
'name': 'enabled',
|
||||
'default': False,
|
||||
'type': 'enabler',
|
||||
'description': '',
|
||||
},
|
||||
{
|
||||
'name': 'api_key',
|
||||
'default': '',
|
||||
'label': 'Api key',
|
||||
'description': '',
|
||||
},
|
||||
{
|
||||
'name': 'priority',
|
||||
'default': '0',
|
||||
'type': 'dropdown',
|
||||
'description': '',
|
||||
'values': [('Very Low', -2), ('Moderate', -1), ('Normal', 0), ('High', 1), ('Emergency', 2)]
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
}]
|
||||
@@ -0,0 +1,51 @@
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import addEvent
|
||||
from couchpotato.core.helpers.encoding import toUnicode
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.notifications.base import Notification
|
||||
from httplib import HTTPSConnection
|
||||
from urllib import urlencode
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
class Prowl(Notification):
|
||||
|
||||
def __init__(self):
|
||||
addEvent('notify', self.notify)
|
||||
addEvent('notify.prowl', self.notify)
|
||||
|
||||
addApiView('notify.prowl.test', self.test)
|
||||
|
||||
def notify(self, message = '', data = {}):
|
||||
|
||||
if self.isDisabled():
|
||||
return
|
||||
|
||||
http_handler = HTTPSConnection('api.prowlapp.com')
|
||||
|
||||
data = {
|
||||
'apikey': self.conf('api_key'),
|
||||
'application': self.default_title,
|
||||
'event': self.default_title,
|
||||
'description': toUnicode(message),
|
||||
'priority': self.conf('priority'),
|
||||
}
|
||||
|
||||
http_handler.request('POST',
|
||||
'/publicapi/add',
|
||||
headers = {'Content-type': 'application/x-www-form-urlencoded'},
|
||||
body = urlencode(data)
|
||||
)
|
||||
response = http_handler.getresponse()
|
||||
request_status = response.status
|
||||
|
||||
if request_status == 200:
|
||||
log.info('Prowl notifications sent.')
|
||||
return True
|
||||
elif request_status == 401:
|
||||
log.error('Prowl auth failed: %s' % response.reason)
|
||||
return False
|
||||
else:
|
||||
log.error('Prowl notification failed.')
|
||||
return False
|
||||
@@ -0,0 +1,38 @@
|
||||
from .main import XBMC
|
||||
|
||||
def start():
|
||||
return XBMC()
|
||||
|
||||
config = [{
|
||||
'name': 'xbmc',
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'notifications',
|
||||
'name': 'xbmc',
|
||||
'options': [
|
||||
{
|
||||
'name': 'enabled',
|
||||
'default': False,
|
||||
'type': 'enabler',
|
||||
'description': '',
|
||||
},
|
||||
{
|
||||
'name': 'host',
|
||||
'default': 'localhost:8080',
|
||||
'description': '',
|
||||
},
|
||||
{
|
||||
'name': 'username',
|
||||
'default': 'xbmc',
|
||||
'description': '',
|
||||
},
|
||||
{
|
||||
'name': 'password',
|
||||
'default': 'xbmc',
|
||||
'type': 'password',
|
||||
'description': '',
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
}]
|
||||
@@ -0,0 +1,47 @@
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import addEvent
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.notifications.base import Notification
|
||||
import base64
|
||||
import urllib
|
||||
import urllib2
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
class XBMC(Notification):
|
||||
|
||||
def __init__(self):
|
||||
addEvent('notify', self.notify)
|
||||
addEvent('notify.xbmc', self.notify)
|
||||
|
||||
addApiView('notify.xbmc.test', self.test)
|
||||
|
||||
def notify(self, message = '', data = {}):
|
||||
|
||||
if self.isDisabled():
|
||||
return
|
||||
|
||||
for host in [x.strip() for x in self.conf('host').split(",")]:
|
||||
self.send({'command': 'ExecBuiltIn', 'parameter': 'Notification(CouchPotato, %s)' % message}, host)
|
||||
self.send({'command': 'ExecBuiltIn', 'parameter': 'XBMC.updatelibrary(video)'}, host)
|
||||
|
||||
return True
|
||||
|
||||
def send(self, command, host):
|
||||
|
||||
url = 'http://%s/xbmcCmds/xbmcHttp/?%s' % (host, urllib.urlencode(command))
|
||||
|
||||
try:
|
||||
req = urllib2.Request(url)
|
||||
if self.password:
|
||||
authHeader = "Basic %s" % base64.encodestring('%s:%s' % (self.conf('username'), self.conf('password')))[:-1]
|
||||
req.add_header("Authorization", authHeader)
|
||||
|
||||
urllib2.urlopen(req, timeout = 10).read()
|
||||
except Exception, e:
|
||||
log.error("Couldn't sent command to XBMC. %s" % e)
|
||||
return False
|
||||
|
||||
log.info('XBMC notification to %s successful.' % host)
|
||||
return True
|
||||
@@ -0,0 +1,6 @@
|
||||
from .main import FileManager
|
||||
|
||||
def start():
|
||||
return FileManager()
|
||||
|
||||
config = []
|
||||
@@ -0,0 +1,103 @@
|
||||
from couchpotato import get_session
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import addEvent
|
||||
from couchpotato.core.helpers.variable import md5, getExt
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.core.settings.model import FileType, File
|
||||
from couchpotato.environment import Env
|
||||
from flask.helpers import send_from_directory
|
||||
import os.path
|
||||
import urllib2
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
class FileManager(Plugin):
|
||||
|
||||
def __init__(self):
|
||||
addEvent('file.add', self.add)
|
||||
addEvent('file.download', self.download)
|
||||
addEvent('file.types', self.getTypes)
|
||||
|
||||
addApiView('file.cache/<path:file>', self.showImage)
|
||||
|
||||
def showImage(self, file = ''):
|
||||
|
||||
cache_dir = Env.get('cache_dir')
|
||||
filename = file.replace(cache_dir[1:] + '/', '')
|
||||
|
||||
return send_from_directory(cache_dir, filename)
|
||||
|
||||
def download(self, url = '', dest = None, overwrite = False):
|
||||
|
||||
try:
|
||||
file = urllib2.urlopen(url)
|
||||
|
||||
if not dest: # to Cache
|
||||
dest = os.path.join(Env.get('cache_dir'), '%s.%s' % (md5(url), getExt(url)))
|
||||
|
||||
if overwrite or not os.path.exists(dest):
|
||||
log.debug('Writing file to: %s' % dest)
|
||||
output = open(dest, 'wb')
|
||||
output.write(file.read())
|
||||
output.close()
|
||||
else:
|
||||
log.debug('File already exists: %s' % dest)
|
||||
|
||||
return dest
|
||||
|
||||
except Exception, e:
|
||||
log.error('Unable to download file "%s": %s' % (url, e))
|
||||
|
||||
return False
|
||||
|
||||
def add(self, path = '', part = 1, type = (), properties = {}):
|
||||
|
||||
db = get_session()
|
||||
|
||||
f = db.query(File).filter_by(path = path).first()
|
||||
if not f:
|
||||
f = File()
|
||||
db.add(f)
|
||||
|
||||
f.path = path
|
||||
f.part = part
|
||||
f.type_id = self.getType(type).id
|
||||
|
||||
db.commit()
|
||||
|
||||
db.expunge(f)
|
||||
return f
|
||||
|
||||
def getType(self, type):
|
||||
|
||||
db = get_session()
|
||||
|
||||
type, identifier = type
|
||||
|
||||
ft = db.query(FileType).filter_by(identifier = identifier).first()
|
||||
if not ft:
|
||||
ft = FileType(
|
||||
type = type,
|
||||
identifier = identifier,
|
||||
name = identifier[0].capitalize() + identifier[1:]
|
||||
)
|
||||
|
||||
db.add(ft)
|
||||
db.commit()
|
||||
|
||||
return ft
|
||||
|
||||
def getTypes(self):
|
||||
|
||||
db = get_session()
|
||||
|
||||
results = db.query(FileType).all()
|
||||
|
||||
types = []
|
||||
for type in results:
|
||||
temp = type.to_dict()
|
||||
types.append(temp)
|
||||
|
||||
return types
|
||||
@@ -1,4 +1,4 @@
|
||||
from couchpotato.core.plugins.file_browser.main import FileBrowser
|
||||
from .main import FileBrowser
|
||||
|
||||
def start():
|
||||
return FileBrowser()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from couchpotato.core.plugins.library.main import LibraryPlugin
|
||||
from .main import LibraryPlugin
|
||||
|
||||
def start():
|
||||
return LibraryPlugin()
|
||||
|
||||
@@ -1,32 +1,81 @@
|
||||
from couchpotato import get_session
|
||||
from couchpotato.core.event import addEvent
|
||||
from couchpotato.core.event import addEvent, fireEventAsync, fireEvent
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.core.settings.model import Library
|
||||
from couchpotato.core.settings.model import Library, LibraryTitle
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
class LibraryPlugin(Plugin):
|
||||
|
||||
def __init__(self):
|
||||
addEvent('library.add', self.add)
|
||||
addEvent('library.update', self.update)
|
||||
|
||||
def add(self, attrs = {}):
|
||||
|
||||
db = get_session();
|
||||
db = get_session()
|
||||
|
||||
l = db.query(Library).filter_by(identifier = attrs.get('identifier')).first()
|
||||
|
||||
if not l:
|
||||
l = Library(
|
||||
name = attrs.get('name'),
|
||||
year = attrs.get('year'),
|
||||
identifier = attrs.get('identifier'),
|
||||
description = attrs.get('description')
|
||||
plot = attrs.get('plot'),
|
||||
tagline = attrs.get('tagline')
|
||||
)
|
||||
|
||||
title = LibraryTitle(
|
||||
title = attrs.get('title')
|
||||
)
|
||||
|
||||
l.titles.append(title)
|
||||
|
||||
db.add(l)
|
||||
db.commit()
|
||||
|
||||
# Update library info
|
||||
fireEventAsync('library.update', library = l, default_title = attrs.get('title', ''))
|
||||
|
||||
#db.remove()
|
||||
return l
|
||||
|
||||
def update(self, item):
|
||||
def update(self, library, default_title = ''):
|
||||
|
||||
pass
|
||||
db = get_session()
|
||||
library = db.query(Library).filter_by(identifier = library.identifier).first()
|
||||
|
||||
info = fireEvent('provider.movie.info', merge = True, identifier = library.identifier)
|
||||
|
||||
# Main info
|
||||
library.plot = info.get('plot', '')
|
||||
library.tagline = info.get('tagline', '')
|
||||
library.year = info.get('year', 0)
|
||||
|
||||
# Titles
|
||||
[db.delete(title) for title in library.titles]
|
||||
titles = info.get('titles')
|
||||
|
||||
log.debug('Adding titles: %s' % titles)
|
||||
for title in titles:
|
||||
t = LibraryTitle(
|
||||
title = title,
|
||||
default = title.lower() == default_title.lower()
|
||||
)
|
||||
library.titles.append(t)
|
||||
|
||||
db.commit()
|
||||
|
||||
# Files
|
||||
images = info.get('images')
|
||||
for type in images:
|
||||
for image in images[type]:
|
||||
file_path = fireEvent('file.download', url = image, single = True)
|
||||
file = fireEvent('file.add', path = file_path, type = ('image', type[:-1]), single = True)
|
||||
try:
|
||||
library.files.append(file)
|
||||
db.commit()
|
||||
except:
|
||||
log.debug('File already attached to library')
|
||||
|
||||
fireEvent('library.update.after')
|
||||
@@ -1,4 +1,4 @@
|
||||
from couchpotato.core.plugins.movie.main import MoviePlugin
|
||||
from .main import MoviePlugin
|
||||
|
||||
def start():
|
||||
return MoviePlugin()
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from couchpotato import get_session
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import fireEvent
|
||||
from couchpotato.core.event import fireEvent, fireEventAsync
|
||||
from couchpotato.core.helpers.request import getParams, jsonified
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.core.settings.model import Movie, Release, Profile
|
||||
from couchpotato.core.settings.model import Movie
|
||||
from couchpotato.environment import Env
|
||||
from urllib import urlencode
|
||||
|
||||
@@ -13,30 +13,28 @@ class MoviePlugin(Plugin):
|
||||
def __init__(self):
|
||||
addApiView('movie.search', self.search)
|
||||
addApiView('movie.list', self.list)
|
||||
addApiView('movie.refresh', self.refresh)
|
||||
|
||||
addApiView('movie.add', self.add)
|
||||
addApiView('movie.edit', self.edit)
|
||||
addApiView('movie.delete', self.delete)
|
||||
|
||||
def list(self):
|
||||
|
||||
a = getParams()
|
||||
params = getParams()
|
||||
db = get_session()
|
||||
|
||||
results = get_session().query(Movie).filter(
|
||||
Movie.releases.any(
|
||||
Release.status.has(identifier = 'wanted')
|
||||
)
|
||||
).all()
|
||||
results = db.query(Movie).filter(
|
||||
Movie.status.has(identifier = params.get('status', 'active'))
|
||||
).all()
|
||||
|
||||
movies = []
|
||||
for movie in results:
|
||||
temp = {
|
||||
'id': movie.id,
|
||||
'name': movie.id,
|
||||
'releases': [],
|
||||
}
|
||||
for release in movie.releases:
|
||||
temp['releases'].append({
|
||||
'status': release.status.label,
|
||||
'quality': release.quality.label
|
||||
})
|
||||
temp = movie.to_dict(deep = {
|
||||
'releases': {'status': {}, 'quality': {}},
|
||||
'library': {'titles': {}, 'files':{}},
|
||||
'files': {}
|
||||
})
|
||||
|
||||
movies.append(temp)
|
||||
|
||||
@@ -46,22 +44,41 @@ class MoviePlugin(Plugin):
|
||||
'movies': movies,
|
||||
})
|
||||
|
||||
def refresh(self):
|
||||
|
||||
params = getParams()
|
||||
db = get_session()
|
||||
|
||||
movie = db.query(Movie).filter_by(id = params.get('id')).first()
|
||||
|
||||
# Get current selected title
|
||||
default_title = ''
|
||||
for title in movie.library.titles:
|
||||
if title.default: default_title = title.title
|
||||
|
||||
if movie:
|
||||
#addEvent('library.update.after', )
|
||||
fireEventAsync('library.update', library = movie.library, default_title = default_title)
|
||||
|
||||
return jsonified({
|
||||
'success': True,
|
||||
})
|
||||
|
||||
def search(self):
|
||||
|
||||
a = getParams()
|
||||
cache_key = '%s/%s' % (__name__, urlencode(a))
|
||||
params = getParams()
|
||||
cache_key = '%s/%s' % (__name__, urlencode(params))
|
||||
movies = Env.get('cache').get(cache_key)
|
||||
|
||||
if not movies:
|
||||
results = fireEvent('provider.movie.search', q = a.get('q'))
|
||||
results = fireEvent('provider.movie.search', q = params.get('q'))
|
||||
|
||||
# Combine movie results
|
||||
movies = []
|
||||
for r in results:
|
||||
movies += r
|
||||
|
||||
Env.get('cache').set(cache_key, movies, timeout = 10)
|
||||
|
||||
Env.get('cache').set(cache_key, movies)
|
||||
|
||||
return jsonified({
|
||||
'success': True,
|
||||
@@ -71,24 +88,46 @@ class MoviePlugin(Plugin):
|
||||
|
||||
def add(self):
|
||||
|
||||
a = getParams()
|
||||
params = getParams()
|
||||
db = get_session();
|
||||
|
||||
library = fireEvent('library.add', attrs = a)
|
||||
profile = db.query(Profile).filter_by(identifier = a.get('profile_identifier'))
|
||||
|
||||
m = db.query(Movie).filter_by(library = library).first()
|
||||
library = fireEvent('library.add', single = True, attrs = params)
|
||||
status = fireEvent('status.add', 'active', single = True)
|
||||
|
||||
m = db.query(Movie).filter_by(library_id = library.id).first()
|
||||
if not m:
|
||||
m = Movie(
|
||||
library = library,
|
||||
profile = profile,
|
||||
library_id = library.id,
|
||||
profile_id = params.get('profile_id')
|
||||
)
|
||||
db.add(m)
|
||||
db.commit()
|
||||
|
||||
m.status_id = status.id
|
||||
db.commit()
|
||||
|
||||
return jsonified({
|
||||
'success': True,
|
||||
'added': True,
|
||||
'params': a,
|
||||
'movie': m.to_dict(deep = {
|
||||
'releases': {'status': {}, 'quality': {}},
|
||||
'library': {'titles': {}}
|
||||
})
|
||||
})
|
||||
|
||||
def edit(self):
|
||||
pass
|
||||
|
||||
def delete(self):
|
||||
|
||||
params = getParams()
|
||||
db = get_session()
|
||||
|
||||
status = fireEvent('status.add', 'deleted', single = True)
|
||||
|
||||
movie = db.query(Movie).filter_by(id = params.get('id')).first()
|
||||
movie.status_id = status.id
|
||||
db.commit()
|
||||
|
||||
return jsonified({
|
||||
'success': True,
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
from couchpotato.core.plugins.profile.main import ProfilePlugin
|
||||
from .main import ProfilePlugin
|
||||
|
||||
def start():
|
||||
return ProfilePlugin()
|
||||
|
||||
@@ -1,28 +1,90 @@
|
||||
from couchpotato import get_session
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import addEvent
|
||||
from couchpotato.core.helpers.request import jsonified, getParams
|
||||
from couchpotato.core.helpers.request import jsonified, getParams, getParam
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.core.settings.model import Profile, ProfileType
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
class ProfilePlugin(Plugin):
|
||||
|
||||
def __init__(self):
|
||||
addEvent('profile.get', self.get)
|
||||
addEvent('profile.all', self.all)
|
||||
|
||||
addApiView('profile.save', self.save)
|
||||
addApiView('profile.delete', self.delete)
|
||||
|
||||
def get(self, key = ''):
|
||||
def all(self):
|
||||
|
||||
pass
|
||||
db = get_session()
|
||||
profiles = db.query(Profile).all()
|
||||
|
||||
temp = []
|
||||
for profile in profiles:
|
||||
temp.append(profile.to_dict(deep = {'types': {}}))
|
||||
|
||||
return temp
|
||||
|
||||
def save(self):
|
||||
|
||||
a = getParams()
|
||||
params = getParams()
|
||||
|
||||
db = get_session()
|
||||
|
||||
p = db.query(Profile).filter_by(id = params.get('id')).first()
|
||||
if not p:
|
||||
p = Profile()
|
||||
db.add(p)
|
||||
|
||||
p.label = params.get('label')
|
||||
p.order = params.get('order', p.order if p.order else 0)
|
||||
p.core = params.get('core', False)
|
||||
|
||||
#delete old types
|
||||
[db.delete(t) for t in p.types]
|
||||
|
||||
order = 0
|
||||
for type in params.get('types', []):
|
||||
t = ProfileType(
|
||||
order = order,
|
||||
finish = type.get('finish'),
|
||||
wait_for = params.get('wait_for'),
|
||||
quality_id = type.get('quality_id')
|
||||
)
|
||||
p.types.append(t)
|
||||
|
||||
order += 1
|
||||
|
||||
db.commit()
|
||||
|
||||
return jsonified({
|
||||
'success': True,
|
||||
'a': a
|
||||
'profile': p.to_dict(deep = {'types': {}})
|
||||
})
|
||||
|
||||
def delete(self):
|
||||
pass
|
||||
|
||||
id = getParam('id')
|
||||
|
||||
db = get_session()
|
||||
|
||||
success = False
|
||||
message = ''
|
||||
try:
|
||||
p = db.query(Profile).filter_by(id = id).first()
|
||||
|
||||
db.delete(p)
|
||||
db.commit()
|
||||
|
||||
success = True
|
||||
except Exception, e:
|
||||
message = 'Failed deleting Profile: %s' % e
|
||||
log.error(message)
|
||||
|
||||
return jsonified({
|
||||
'success': success,
|
||||
'message': message
|
||||
})
|
||||
@@ -0,0 +1,6 @@
|
||||
from .main import QualityPlugin
|
||||
|
||||
def start():
|
||||
return QualityPlugin()
|
||||
|
||||
config = []
|
||||
@@ -0,0 +1,97 @@
|
||||
from couchpotato import get_session
|
||||
from couchpotato.core.event import addEvent
|
||||
from couchpotato.core.helpers.encoding import toUnicode
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.settings.model import Quality, Profile, ProfileType
|
||||
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
class QualityPlugin:
|
||||
|
||||
qualities = [
|
||||
{'identifier': 'bd50', 'size': (15000, 60000), 'label': 'BR-Disk', 'alternative': ['1080p', 'bd25'], 'allow': [], 'ext':[], 'tags': ['x264', 'h264', 'blu ray']},
|
||||
{'identifier': '1080p', 'size': (5000, 20000), 'label': '1080P', 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts'], 'tags': ['x264', 'h264', 'bluray']},
|
||||
{'identifier': '720p', 'size': (3500, 10000), 'label': '720P', 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts'], 'tags': ['x264', 'h264', 'bluray']},
|
||||
{'identifier': 'brrip', 'size': (700, 7000), 'label': 'BR-Rip', 'alternative': ['bdrip'], 'allow': ['720p'], 'ext':['mkv', 'avi']},
|
||||
{'identifier': 'dvdr', 'size': (3000, 10000), 'label': 'DVD-R', 'alternative': [], 'allow': [], 'ext':['iso', 'img'], 'tags': ['pal', 'ntsc']},
|
||||
{'identifier': 'dvdrip', 'size': (600, 2400), 'label': 'DVD-Rip', 'alternative': [], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
|
||||
{'identifier': 'scr', 'size': (600, 1600), 'label': 'Screener', 'alternative': ['dvdscr'], 'allow': ['dvdr'], 'ext':['avi', 'mpg', 'mpeg']},
|
||||
{'identifier': 'r5', 'size': (600, 1000), 'label': 'R5', 'alternative': [], 'allow': ['dvdr'], 'ext':['avi', 'mpg', 'mpeg']},
|
||||
{'identifier': 'tc', 'size': (600, 1000), 'label': 'TeleCine', 'alternative': ['telecine'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
|
||||
{'identifier': 'ts', 'size': (600, 1000), 'label': 'TeleSync', 'alternative': ['telesync'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
|
||||
{'identifier': 'cam', 'size': (600, 1000), 'label': 'Cam', 'alternative': [], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']}
|
||||
]
|
||||
pre_releases = ['cam', 'ts', 'tc', 'r5', 'scr']
|
||||
|
||||
def __init__(self):
|
||||
addEvent('quality.all', self.all)
|
||||
addEvent('app.load', self.fill)
|
||||
|
||||
def all(self):
|
||||
|
||||
db = get_session()
|
||||
|
||||
qualities = db.query(Quality).all()
|
||||
|
||||
temp = []
|
||||
for quality in qualities:
|
||||
q = dict(self.getQuality(quality.identifier), **quality.to_dict())
|
||||
temp.append(q)
|
||||
|
||||
return temp
|
||||
|
||||
def getQuality(self, identifier):
|
||||
|
||||
for q in self.qualities:
|
||||
if identifier == q.get('identifier'):
|
||||
return q
|
||||
|
||||
def fill(self):
|
||||
|
||||
db = get_session();
|
||||
|
||||
order = 0
|
||||
for q in self.qualities:
|
||||
|
||||
# Create quality
|
||||
quality = db.query(Quality).filter_by(identifier = q.get('identifier')).first()
|
||||
|
||||
if not quality:
|
||||
log.info('Creating quality: %s' % q.get('label'))
|
||||
quality = Quality()
|
||||
db.add(quality)
|
||||
|
||||
quality.order = order
|
||||
quality.identifier = q.get('identifier')
|
||||
quality.label = q.get('label')
|
||||
quality.size_min, quality.size_max = q.get('size')
|
||||
|
||||
# Create single quality profile
|
||||
profile = db.query(Profile).filter(
|
||||
Profile.core == True
|
||||
).filter(
|
||||
Profile.types.any(quality = quality)
|
||||
).all()
|
||||
|
||||
if not profile:
|
||||
log.info('Creating profile: %s' % q.get('label'))
|
||||
profile = Profile(
|
||||
core = True,
|
||||
label = toUnicode(quality.label),
|
||||
order = order
|
||||
)
|
||||
db.add(profile)
|
||||
|
||||
profile_type = ProfileType(
|
||||
quality = quality,
|
||||
profile = profile,
|
||||
finish = True,
|
||||
order = 0
|
||||
)
|
||||
profile.types.append(profile_type)
|
||||
|
||||
order += 1
|
||||
db.commit()
|
||||
|
||||
return True
|
||||
@@ -0,0 +1,6 @@
|
||||
from .main import StatusPlugin
|
||||
|
||||
def start():
|
||||
return StatusPlugin()
|
||||
|
||||
config = []
|
||||
@@ -0,0 +1,68 @@
|
||||
from couchpotato import get_session
|
||||
from couchpotato.core.event import addEvent
|
||||
from couchpotato.core.helpers.encoding import toUnicode
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.settings.model import Status
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
class StatusPlugin:
|
||||
|
||||
statuses = {
|
||||
'active': 'Active',
|
||||
'done': 'Done',
|
||||
'downloaded': 'Downloaded',
|
||||
'wanted': 'Wanted',
|
||||
'deleted': 'Deleted',
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
addEvent('status.add', self.add)
|
||||
addEvent('status.all', self.all)
|
||||
addEvent('app.load', self.fill)
|
||||
|
||||
def all(self):
|
||||
|
||||
db = get_session()
|
||||
|
||||
statuses = db.query(Status).all()
|
||||
|
||||
temp = []
|
||||
for status in statuses:
|
||||
s = status.to_dict()
|
||||
temp.append(s)
|
||||
|
||||
return temp
|
||||
|
||||
def add(self, identifier):
|
||||
|
||||
db = get_session()
|
||||
|
||||
s = db.query(Status).filter_by(identifier = identifier).first()
|
||||
if not s:
|
||||
s = Status(
|
||||
identifier = identifier,
|
||||
label = identifier.capitalize()
|
||||
)
|
||||
db.add(s)
|
||||
db.commit()
|
||||
|
||||
#db.remove()
|
||||
return s
|
||||
|
||||
def fill(self):
|
||||
|
||||
db = get_session()
|
||||
|
||||
for identifier, label in self.statuses.iteritems():
|
||||
s = db.query(Status).filter_by(identifier = identifier).first()
|
||||
if not s:
|
||||
log.info('Creating status: %s' % label)
|
||||
s = Status(
|
||||
identifier = identifier,
|
||||
label = toUnicode(label)
|
||||
)
|
||||
db.add(s)
|
||||
|
||||
s.label = label
|
||||
db.commit()
|
||||
@@ -1,8 +1,9 @@
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
class Provider():
|
||||
class Provider(Plugin):
|
||||
|
||||
type = None # movie, nzb, torrent, subtitle, trailer
|
||||
timeout = 10 # Default timeout for url requests
|
||||
|
||||
File renamed without changes.
@@ -0,0 +1,116 @@
|
||||
from __future__ import with_statement
|
||||
from couchpotato.core.event import addEvent
|
||||
from couchpotato.core.helpers.encoding import simplifyString, toUnicode
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.providers.base import Provider
|
||||
from couchpotato.environment import Env
|
||||
from libs.themoviedb import tmdb
|
||||
import copy
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
class TMDBWrapper(Provider):
|
||||
"""Api for theMovieDb"""
|
||||
|
||||
type = 'movie'
|
||||
apiUrl = 'http://api.themoviedb.org/2.1'
|
||||
imageUrl = 'http://hwcdn.themoviedb.org'
|
||||
|
||||
def __init__(self):
|
||||
addEvent('provider.movie.search', self.search)
|
||||
addEvent('provider.movie.info', self.getInfo)
|
||||
|
||||
# Use base wrapper
|
||||
tmdb.Config.api_key = self.conf('api_key')
|
||||
|
||||
def conf(self, attr):
|
||||
return Env.setting(attr, 'themoviedb')
|
||||
|
||||
def search(self, q, limit = 12):
|
||||
''' Find movie by name '''
|
||||
|
||||
if self.isDisabled():
|
||||
return False
|
||||
|
||||
log.debug('TheMovieDB - Searching for movie: %s' % q)
|
||||
raw = tmdb.search(simplifyString(q))
|
||||
|
||||
results = []
|
||||
if raw:
|
||||
try:
|
||||
nr = 0
|
||||
for movie in raw:
|
||||
|
||||
results.append(self.parseMovie(movie))
|
||||
|
||||
nr += 1
|
||||
if nr == limit:
|
||||
break
|
||||
|
||||
log.info('TheMovieDB - Found: %s' % [result['titles'][0] + ' (' + str(result['year']) + ')' for result in results])
|
||||
return results
|
||||
except SyntaxError, e:
|
||||
log.error('Failed to parse XML response: %s' % e)
|
||||
return False
|
||||
|
||||
return results
|
||||
|
||||
def getInfo(self, identifier = None):
|
||||
result = {}
|
||||
|
||||
movie = tmdb.imdbLookup(id = identifier)[0]
|
||||
|
||||
if movie:
|
||||
result = self.parseMovie(movie)
|
||||
|
||||
return result
|
||||
|
||||
def parseMovie(self, movie):
|
||||
|
||||
year = str(movie.get('released', 'none'))[:4]
|
||||
|
||||
# Poster url
|
||||
poster = self.getImage(movie, type = 'poster')
|
||||
backdrop = self.getImage(movie, type = 'backdrop')
|
||||
|
||||
# 1900 is the same as None
|
||||
if year == '1900' or year.lower() == 'none':
|
||||
year = None
|
||||
|
||||
movie_data = {
|
||||
'id': int(movie.get('id', 0)),
|
||||
'titles': [toUnicode(movie.get('name'))],
|
||||
'images': {
|
||||
'posters': [poster],
|
||||
'backdrops': [backdrop],
|
||||
},
|
||||
'imdb': movie.get('imdb_id'),
|
||||
'year': year,
|
||||
'plot': movie.get('overview', ''),
|
||||
'tagline': '',
|
||||
}
|
||||
|
||||
# Add alternative names
|
||||
for alt in ['original_name', 'alternative_name']:
|
||||
alt_name = toUnicode(movie.get(alt))
|
||||
if alt_name and not alt_name in movie_data['titles'] and alt_name.lower() != 'none' and alt_name != None:
|
||||
movie_data['titles'].append(alt_name)
|
||||
|
||||
return movie_data
|
||||
|
||||
def getImage(self, movie, type = 'poster'):
|
||||
|
||||
image = ''
|
||||
for image in movie.get('images', []):
|
||||
if(image.get('type') == type):
|
||||
image = image.get('thumb')
|
||||
break
|
||||
|
||||
return image
|
||||
|
||||
def isDisabled(self):
|
||||
if self.conf('api_key') == '':
|
||||
log.error('No API key provided.')
|
||||
True
|
||||
else:
|
||||
False
|
||||
@@ -1,107 +0,0 @@
|
||||
from __future__ import with_statement
|
||||
from couchpotato.core.event import addEvent
|
||||
from couchpotato.core.helpers.encoding import simplifyString, toUnicode
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.providers.base import Provider
|
||||
from couchpotato.environment import Env
|
||||
from libs.themoviedb import tmdb
|
||||
from urllib import quote_plus
|
||||
import copy
|
||||
import simplejson as json
|
||||
import urllib2
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
class TMDBWrapper(Provider):
|
||||
"""Api for theMovieDb"""
|
||||
|
||||
type = 'movie'
|
||||
apiUrl = 'http://api.themoviedb.org/2.1'
|
||||
imageUrl = 'http://hwcdn.themoviedb.org'
|
||||
|
||||
def __init__(self):
|
||||
addEvent('provider.movie.search', self.search)
|
||||
addEvent('provider.movie.info', self.getInfo)
|
||||
|
||||
# Use base wrapper
|
||||
tmdb.Config.api_key = self.conf('api_key')
|
||||
|
||||
def conf(self, attr):
|
||||
return Env.setting(attr, 'themoviedb')
|
||||
|
||||
def search(self, q, limit = 12, alternative = True):
|
||||
''' Find movie by name '''
|
||||
|
||||
if self.isDisabled():
|
||||
return False
|
||||
|
||||
log.debug('TheMovieDB - Searching for movie: %s' % q)
|
||||
|
||||
raw = tmdb.search(simplifyString(q))
|
||||
|
||||
#url = "%s/%s/%s/json/%s/%s" % (self.apiUrl, 'Movie.search', 'en', self.conf('api_key'), quote_plus(simplifyString(q)))
|
||||
|
||||
|
||||
# data = urllib2.urlopen(url)
|
||||
# jsn = json.load(data)
|
||||
|
||||
if raw:
|
||||
log.debug('TheMovieDB - Parsing RSS')
|
||||
try:
|
||||
results = []
|
||||
nr = 0
|
||||
for movie in raw:
|
||||
|
||||
for k, x in movie.iteritems():
|
||||
print k
|
||||
print x
|
||||
|
||||
year = str(movie.get('released', 'none'))[:4]
|
||||
|
||||
# Poster url
|
||||
poster = ''
|
||||
for p in movie.get('images'):
|
||||
if(p.get('type') == 'poster'):
|
||||
poster = p.get('thumb')
|
||||
break
|
||||
|
||||
# 1900 is the same as None
|
||||
if year == '1900' or year.lower() == 'none':
|
||||
year = None
|
||||
|
||||
movie_data = {
|
||||
'id': int(movie.get('id', 0)),
|
||||
'name': toUnicode(movie.get('name')),
|
||||
'poster': poster,
|
||||
'imdb': movie.get('imdb_id'),
|
||||
'year': year,
|
||||
'tagline': 'This is the tagline of the movie',
|
||||
}
|
||||
results.append(copy.deepcopy(movie_data))
|
||||
|
||||
alternativeName = movie.get('alternative_name')
|
||||
if alternativeName and alternative:
|
||||
if alternativeName.lower() != movie['name'].lower() and alternativeName.lower() != 'none' and alternativeName != None:
|
||||
movie_data['name'] = toUnicode(alternativeName)
|
||||
results.append(copy.deepcopy(movie_data))
|
||||
nr += 1
|
||||
if nr == limit:
|
||||
break
|
||||
|
||||
log.info('TheMovieDB - Found: %s' % [result['name'] + u' (' + str(result['year']) + ')' for result in results])
|
||||
return results
|
||||
except SyntaxError, e:
|
||||
log.error('TheMovieDB - Failed to parse XML response from TheMovieDb: %s' % e)
|
||||
return False
|
||||
|
||||
return results
|
||||
|
||||
def getInfo(self):
|
||||
pass
|
||||
|
||||
def isDisabled(self):
|
||||
if self.conf('api_key') == '':
|
||||
log.error('TheMovieDB - No API key provided for TheMovieDB')
|
||||
True
|
||||
else:
|
||||
False
|
||||
@@ -2,8 +2,10 @@ from elixir.entity import Entity
|
||||
from elixir.fields import Field
|
||||
from elixir.options import options_defaults
|
||||
from elixir.relationships import OneToMany, ManyToOne
|
||||
from sqlalchemy.types import Integer, String, Unicode, UnicodeText, Boolean, \
|
||||
Float
|
||||
from libs.elixir.options import using_options
|
||||
from libs.elixir.relationships import ManyToMany
|
||||
from sqlalchemy.types import Integer, Unicode, UnicodeText, Boolean, Float, \
|
||||
String
|
||||
|
||||
options_defaults["shortnames"] = True
|
||||
|
||||
@@ -20,24 +22,48 @@ class Movie(Entity):
|
||||
The files belonging to the movie object are global for the whole movie
|
||||
such as trailers, nfo, thumbnails"""
|
||||
|
||||
library = ManyToOne('Library')
|
||||
last_edit = Field(Integer)
|
||||
|
||||
library = ManyToOne('Library')
|
||||
status = ManyToOne('Status')
|
||||
profile = ManyToOne('Profile')
|
||||
releases = OneToMany('Release')
|
||||
files = OneToMany('File')
|
||||
files = ManyToMany('File')
|
||||
|
||||
|
||||
class Library(Entity):
|
||||
""""""
|
||||
|
||||
title = Field(Unicode)
|
||||
year = Field(Integer)
|
||||
identifier = Field(Unicode)
|
||||
identifier = Field(String(20))
|
||||
rating = Field(Float)
|
||||
|
||||
plot = Field(UnicodeText)
|
||||
tagline = Field(UnicodeText(255))
|
||||
|
||||
status = ManyToOne('Status')
|
||||
movie = OneToMany('Movie')
|
||||
titles = OneToMany('LibraryTitle')
|
||||
files = ManyToMany('File')
|
||||
|
||||
|
||||
class LibraryTitle(Entity):
|
||||
""""""
|
||||
|
||||
title = Field(Unicode)
|
||||
default = Field(Boolean)
|
||||
|
||||
language = OneToMany('Language')
|
||||
libraries = ManyToOne('Library')
|
||||
|
||||
|
||||
class Language(Entity):
|
||||
""""""
|
||||
|
||||
identifier = Field(String(20))
|
||||
label = Field(Unicode)
|
||||
|
||||
titles = ManyToOne('LibraryTitle')
|
||||
|
||||
|
||||
class Release(Entity):
|
||||
@@ -47,7 +73,7 @@ class Release(Entity):
|
||||
movie = ManyToOne('Movie')
|
||||
status = ManyToOne('Status')
|
||||
quality = ManyToOne('Quality')
|
||||
files = OneToMany('File')
|
||||
files = ManyToMany('File')
|
||||
history = OneToMany('History')
|
||||
|
||||
|
||||
@@ -55,41 +81,49 @@ class Status(Entity):
|
||||
"""The status of a release, such as Downloaded, Deleted, Wanted etc"""
|
||||
|
||||
identifier = Field(String(20), unique = True)
|
||||
label = Field(String(20))
|
||||
label = Field(Unicode(20))
|
||||
|
||||
releases = OneToMany('Release')
|
||||
movies = OneToMany('Movie')
|
||||
|
||||
|
||||
class Quality(Entity):
|
||||
"""Quality name of a release, DVD, 720P, DVD-Rip etc"""
|
||||
using_options(order_by = 'order')
|
||||
|
||||
identifier = Field(String(20), unique = True)
|
||||
label = Field(String(20))
|
||||
label = Field(Unicode(20))
|
||||
order = Field(Integer)
|
||||
|
||||
size_min = Field(Integer)
|
||||
size_max = Field(Integer)
|
||||
|
||||
releases = OneToMany('Release')
|
||||
profile_types = ManyToOne('ProfileType')
|
||||
profile_types = OneToMany('ProfileType')
|
||||
|
||||
|
||||
class Profile(Entity):
|
||||
""""""
|
||||
using_options(order_by = 'order')
|
||||
|
||||
identifier = Field(String(20), unique = True)
|
||||
label = Field(Unicode(50))
|
||||
order = Field(Integer)
|
||||
wait_for = Field(Integer)
|
||||
core = Field(Boolean)
|
||||
hide = Field(Boolean)
|
||||
|
||||
movie = OneToMany('Movie')
|
||||
profile_type = OneToMany('ProfileType')
|
||||
types = OneToMany('ProfileType', cascade = 'all, delete-orphan')
|
||||
|
||||
|
||||
class ProfileType(Entity):
|
||||
""""""
|
||||
using_options(order_by = 'order')
|
||||
|
||||
order = Field(Integer)
|
||||
mark_completed = Field(Boolean)
|
||||
finish = Field(Boolean)
|
||||
wait_for = Field(Integer)
|
||||
|
||||
type = OneToMany('Quality')
|
||||
quality = ManyToOne('Quality')
|
||||
profile = ManyToOne('Profile')
|
||||
|
||||
|
||||
@@ -97,19 +131,22 @@ class File(Entity):
|
||||
"""File that belongs to a release."""
|
||||
|
||||
path = Field(Unicode(255), nullable = False, unique = True)
|
||||
part = Field(Integer)
|
||||
part = Field(Integer, default = 1)
|
||||
|
||||
history = OneToMany('RenameHistory')
|
||||
movie = ManyToOne('Movie')
|
||||
release = ManyToOne('Release')
|
||||
type = ManyToOne('FileType')
|
||||
properties = OneToMany('FileProperty')
|
||||
|
||||
history = OneToMany('RenameHistory')
|
||||
movie = ManyToMany('Movie')
|
||||
release = ManyToMany('Release')
|
||||
library = ManyToMany('Library')
|
||||
|
||||
|
||||
class FileType(Entity):
|
||||
"""Types could be trailer, subtitle, movie, partial movie etc."""
|
||||
|
||||
identifier = Field(String(20), unique = True)
|
||||
type = Field(Unicode(20))
|
||||
name = Field(Unicode(50), nullable = False)
|
||||
|
||||
files = OneToMany('File')
|
||||
@@ -135,8 +172,8 @@ class History(Entity):
|
||||
class RenameHistory(Entity):
|
||||
"""Remembers from where to where files have been moved."""
|
||||
|
||||
old = Field(String(255))
|
||||
new = Field(String(255))
|
||||
old = Field(Unicode(255))
|
||||
new = Field(Unicode(255))
|
||||
|
||||
file = ManyToOne('File')
|
||||
|
||||
|
||||
Reference in new issue
Block a user