Providers, wizard base, core notifier base
This commit is contained in:
@@ -26,6 +26,8 @@ def get_session(engine = None):
|
|||||||
def get_engine():
|
def get_engine():
|
||||||
return create_engine(Env.get('db_path'), echo = False)
|
return create_engine(Env.get('db_path'), echo = False)
|
||||||
|
|
||||||
|
def addView(route, func, static = False):
|
||||||
|
web.add_url_rule(route + ('' if static else '/'), endpoint = route if route else 'index', view_func = func)
|
||||||
|
|
||||||
""" Web view """
|
""" Web view """
|
||||||
@web.route('/')
|
@web.route('/')
|
||||||
|
|||||||
+1
-1
@@ -90,7 +90,7 @@ def cmd_couchpotato(base_path, args):
|
|||||||
# Load configs & plugins
|
# Load configs & plugins
|
||||||
loader = Env.get('loader')
|
loader = Env.get('loader')
|
||||||
loader.preload(root = base_path)
|
loader.preload(root = base_path)
|
||||||
loader.addModule('core', 'couchpotato.core', 'core')
|
loader.addModule(0, 'core', 'couchpotato.core', 'core')
|
||||||
loader.run()
|
loader.run()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ def removeEvent(name, handler):
|
|||||||
e -= handler
|
e -= handler
|
||||||
|
|
||||||
def fireEvent(name, *args, **kwargs):
|
def fireEvent(name, *args, **kwargs):
|
||||||
log.debug('Firing "%s": %s, %s' % (name, args, kwargs))
|
#log.debug('Firing "%s": %s, %s' % (name, args, kwargs))
|
||||||
try:
|
try:
|
||||||
|
|
||||||
# Return single handler
|
# Return single handler
|
||||||
@@ -64,7 +64,7 @@ def fireEvent(name, *args, **kwargs):
|
|||||||
log.error('%s: %s' % (name, e))
|
log.error('%s: %s' % (name, e))
|
||||||
|
|
||||||
def fireEventAsync(name, *args, **kwargs):
|
def fireEventAsync(name, *args, **kwargs):
|
||||||
log.debug('Async "%s": %s, %s' % (name, args, kwargs))
|
#log.debug('Async "%s": %s, %s' % (name, args, kwargs))
|
||||||
try:
|
try:
|
||||||
e = events[name]
|
e = events[name]
|
||||||
e.asynchronous = True
|
e.asynchronous = True
|
||||||
|
|||||||
+31
-24
@@ -18,47 +18,48 @@ class Loader:
|
|||||||
providers = os.path.join(root, 'couchpotato', 'core', 'providers')
|
providers = os.path.join(root, 'couchpotato', 'core', 'providers')
|
||||||
|
|
||||||
self.paths = {
|
self.paths = {
|
||||||
'plugin' : ('couchpotato.core.plugins', os.path.join(core, 'plugins')),
|
'plugin' : (0, 'couchpotato.core.plugins', os.path.join(core, 'plugins')),
|
||||||
'notifications' : ('couchpotato.core.notifications', os.path.join(core, 'notifications')),
|
'notifications' : (20, 'couchpotato.core.notifications', os.path.join(core, 'notifications')),
|
||||||
'downloaders' : ('couchpotato.core.downloaders', os.path.join(root, 'couchpotato', 'core', 'downloaders')),
|
'downloaders' : (20, 'couchpotato.core.downloaders', os.path.join(core, 'downloaders')),
|
||||||
'movie_provider' : ('couchpotato.core.providers.movie', os.path.join(providers, 'movie')),
|
'movie_provider' : (20, 'couchpotato.core.providers.movie', os.path.join(providers, 'movie')),
|
||||||
'nzb_provider' : ('couchpotato.core.providers.nzb', os.path.join(providers, 'nzb')),
|
'nzb_provider' : (20, 'couchpotato.core.providers.nzb', os.path.join(providers, 'nzb')),
|
||||||
'torrent_provider' : ('couchpotato.core.providers.torrent', os.path.join(providers, 'torrent')),
|
'torrent_provider' : (20, 'couchpotato.core.providers.torrent', os.path.join(providers, 'torrent')),
|
||||||
'trailer_provider' : ('couchpotato.core.providers.trailer', os.path.join(providers, 'trailer')),
|
'trailer_provider' : (20, 'couchpotato.core.providers.trailer', os.path.join(providers, 'trailer')),
|
||||||
'subtitle_provider' : ('couchpotato.core.providers.subtitle', os.path.join(providers, 'subtitle')),
|
'subtitle_provider' : (20, 'couchpotato.core.providers.subtitle', os.path.join(providers, 'subtitle')),
|
||||||
}
|
}
|
||||||
|
|
||||||
for type, tuple in self.paths.iteritems():
|
for type, tuple in self.paths.iteritems():
|
||||||
self.addFromDir(type, tuple[0], tuple[1])
|
priority, module, dir = tuple
|
||||||
|
self.addFromDir(type, priority, module, dir)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
did_save = 0
|
did_save = 0
|
||||||
|
|
||||||
for module_name, plugin in sorted(self.modules.iteritems()):
|
for priority in self.modules:
|
||||||
|
for module_name, plugin in sorted(self.modules[priority].iteritems()):
|
||||||
|
# Load module
|
||||||
|
try:
|
||||||
|
m = getattr(self.loadModule(module_name), plugin.get('name'))
|
||||||
|
|
||||||
# Load module
|
log.info("Loading %s: %s" % (plugin['type'], plugin['name']))
|
||||||
try:
|
|
||||||
m = getattr(self.loadModule(module_name), plugin.get('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
|
self.loadPlugins(m, plugin.get('name'))
|
||||||
did_save += self.loadSettings(m, module_name, save = False)
|
except Exception, e:
|
||||||
|
log.error('Can\'t import %s: %s' % (plugin.get('name'), e))
|
||||||
self.loadPlugins(m, plugin.get('name'))
|
|
||||||
except Exception, e:
|
|
||||||
log.error(e)
|
|
||||||
|
|
||||||
if did_save:
|
if did_save:
|
||||||
fireEvent('settings.save')
|
fireEvent('settings.save')
|
||||||
|
|
||||||
def addFromDir(self, type, module, dir):
|
def addFromDir(self, type, priority, module, dir):
|
||||||
|
|
||||||
for file in glob.glob(os.path.join(dir, '*')):
|
for file in glob.glob(os.path.join(dir, '*')):
|
||||||
name = os.path.basename(file)
|
name = os.path.basename(file)
|
||||||
if os.path.isdir(os.path.join(dir, name)):
|
if os.path.isdir(os.path.join(dir, name)):
|
||||||
module_name = '%s.%s' % (module, name)
|
module_name = '%s.%s' % (module, name)
|
||||||
self.addModule(type, module_name, name)
|
self.addModule(priority, type, module_name, name)
|
||||||
|
|
||||||
def loadSettings(self, module, name, save = True):
|
def loadSettings(self, module, name, save = True):
|
||||||
try:
|
try:
|
||||||
@@ -82,8 +83,14 @@ class Loader:
|
|||||||
log.error("Failed loading plugin '%s': %s" % (name, e))
|
log.error("Failed loading plugin '%s': %s" % (name, e))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def addModule(self, type, module, name):
|
def addModule(self, priority, type, module, name):
|
||||||
self.modules[module] = {
|
|
||||||
|
if not self.modules.get(priority):
|
||||||
|
self.modules[priority] = {}
|
||||||
|
|
||||||
|
self.modules[priority][module] = {
|
||||||
|
'priority': priority,
|
||||||
|
'module': module,
|
||||||
'type': type,
|
'type': type,
|
||||||
'name': name,
|
'name': name,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
from .main import CoreNotifier
|
||||||
|
|
||||||
|
def start():
|
||||||
|
return CoreNotifier()
|
||||||
|
|
||||||
|
config = []
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
from couchpotato.api import addApiView
|
||||||
|
from couchpotato.core.event import addEvent, fireEvent
|
||||||
|
from couchpotato.core.helpers.request import jsonified
|
||||||
|
from couchpotato.core.logger import CPLog
|
||||||
|
from couchpotato.core.plugins.base import Plugin
|
||||||
|
import time
|
||||||
|
|
||||||
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class CoreNotifier(Plugin):
|
||||||
|
|
||||||
|
messages = []
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
addEvent('notify', self.notify)
|
||||||
|
addEvent('notify.core_notifier', self.notify)
|
||||||
|
addEvent('core_notifier.frontend', self.frontend)
|
||||||
|
|
||||||
|
addApiView('core_notifier.listener', self.listener)
|
||||||
|
|
||||||
|
static = self.registerStatic(__file__)
|
||||||
|
fireEvent('register_script', static + 'notification.js')
|
||||||
|
|
||||||
|
def notify(self, message = '', data = {}):
|
||||||
|
self.add(data = {
|
||||||
|
'message': message,
|
||||||
|
'raw': data,
|
||||||
|
})
|
||||||
|
|
||||||
|
def frontend(self, type = 'notification', data = {}):
|
||||||
|
|
||||||
|
self.messages.append({
|
||||||
|
'time': time.time(),
|
||||||
|
'type': type,
|
||||||
|
'data': data,
|
||||||
|
})
|
||||||
|
|
||||||
|
def listener(self):
|
||||||
|
|
||||||
|
for message in self.messages:
|
||||||
|
#delete message older then 15s
|
||||||
|
if message['time'] < (time.time() - 15):
|
||||||
|
del message
|
||||||
|
|
||||||
|
return jsonified({
|
||||||
|
'success': True,
|
||||||
|
'result': self.messages,
|
||||||
|
})
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
var NotificationBase = new Class({
|
||||||
|
|
||||||
|
Extends: BlockBase,
|
||||||
|
Implements: [Options, Events],
|
||||||
|
|
||||||
|
initialize: function(options){
|
||||||
|
var self = this;
|
||||||
|
self.setOptions(options);
|
||||||
|
|
||||||
|
//App.addEvent('load', self.request.bind(self));
|
||||||
|
|
||||||
|
self.addEvent('notification', self.notify.bind(self))
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
request: function(){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
Api.request('core_notifier.listener', {
|
||||||
|
'initialDelay': 100,
|
||||||
|
'delay': 3000,
|
||||||
|
'onComplete': self.processData.bind(self)
|
||||||
|
}).startTimer()
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
notify: function(data){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
processData: function(json){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
Array.each(json.result, function(result){
|
||||||
|
self.fireEvent(result.type, result.data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
window.Notification = new NotificationBase();
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
from couchpotato.api import addApiView
|
from couchpotato import addView
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
from flask.helpers import send_from_directory
|
from flask.helpers import send_from_directory
|
||||||
import os.path
|
import os.path
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
class Plugin():
|
class Plugin():
|
||||||
@@ -12,20 +13,23 @@ class Plugin():
|
|||||||
def getName(self):
|
def getName(self):
|
||||||
return self.__class__.__name__
|
return self.__class__.__name__
|
||||||
|
|
||||||
def registerStatic(self, file_path):
|
def registerStatic(self, plugin_file):
|
||||||
|
|
||||||
class_name = self.__class__.__name__.lower()
|
# Register plugin path
|
||||||
self.plugin_file = file_path
|
self.plugin_path = os.path.dirname(plugin_file)
|
||||||
path = class_name + '.static/'
|
|
||||||
|
|
||||||
addApiView(path + '<path:file>', self.showStatic, static = True)
|
# Get plugin_name from PluginName
|
||||||
|
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', self.__class__.__name__)
|
||||||
|
class_name = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
|
||||||
|
|
||||||
|
path = 'static/' + class_name + '/'
|
||||||
|
addView(path + '<path:file>', self.showStatic, static = True)
|
||||||
|
|
||||||
return path
|
return path
|
||||||
|
|
||||||
def showStatic(self, file = ''):
|
def showStatic(self, file = ''):
|
||||||
|
|
||||||
plugin_dir = os.path.dirname(self.plugin_file)
|
dir = os.path.join(self.plugin_path, 'static')
|
||||||
dir = os.path.join(plugin_dir, 'static')
|
|
||||||
|
|
||||||
return send_from_directory(dir, file)
|
return send_from_directory(dir, file)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
from .main import Wizard
|
||||||
|
|
||||||
|
def start():
|
||||||
|
return Wizard()
|
||||||
|
|
||||||
|
config = [{
|
||||||
|
'name': 'global',
|
||||||
|
'groups': [
|
||||||
|
{
|
||||||
|
'tab': 'general',
|
||||||
|
'name': 'advanced',
|
||||||
|
'options': [
|
||||||
|
{
|
||||||
|
'name': 'show_wizard',
|
||||||
|
'label': 'Run the wizard',
|
||||||
|
'default': True,
|
||||||
|
'type': 'bool',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}]
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
from couchpotato.core.event import fireEvent
|
||||||
|
from couchpotato.core.logger import CPLog
|
||||||
|
from couchpotato.core.plugins.base import Plugin
|
||||||
|
|
||||||
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Wizard(Plugin):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
path = self.registerStatic(__file__)
|
||||||
|
fireEvent('register_script', path + 'spotlight.js')
|
||||||
|
fireEvent('register_script', path + 'wizard.js')
|
||||||
@@ -0,0 +1,306 @@
|
|||||||
|
/*
|
||||||
|
---
|
||||||
|
description: Fill the empty space around elements, creating a spotlight effect.
|
||||||
|
|
||||||
|
license: GPL v3.0
|
||||||
|
|
||||||
|
authors:
|
||||||
|
- Ruud Burger
|
||||||
|
|
||||||
|
requires:
|
||||||
|
- core/1.3: [Class.Extras, Element.Dimensions]
|
||||||
|
|
||||||
|
provides: [Spotlight]
|
||||||
|
|
||||||
|
...
|
||||||
|
*/
|
||||||
|
|
||||||
|
var Spotlight = new Class({
|
||||||
|
|
||||||
|
Implements: [Options],
|
||||||
|
|
||||||
|
options: {
|
||||||
|
'fillClass': 'spotlight_fill',
|
||||||
|
'fillColor': [255,255,255],
|
||||||
|
'fillOpacity': 1,
|
||||||
|
'parent': null,
|
||||||
|
'inject': null,
|
||||||
|
'soften': 10
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize: function(elements, options){
|
||||||
|
var self = this;
|
||||||
|
self.setOptions(options);
|
||||||
|
|
||||||
|
self.setElements(elements);
|
||||||
|
self.clean();
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
clean: function(){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.range = []; self.fills = []; self.edges = [];
|
||||||
|
|
||||||
|
self.vert = [];
|
||||||
|
self.vert_el = [];
|
||||||
|
|
||||||
|
self.top = []; self.left = [];
|
||||||
|
self.width = []; self.height = [];
|
||||||
|
},
|
||||||
|
|
||||||
|
setElements: function(elements){
|
||||||
|
this.elements = elements;
|
||||||
|
},
|
||||||
|
|
||||||
|
addElement: function(element){
|
||||||
|
this.elements.include(element);
|
||||||
|
},
|
||||||
|
|
||||||
|
create: function(){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.destroy();
|
||||||
|
|
||||||
|
var page_c = $(self.options.parent || window).getScrollSize();
|
||||||
|
var soften = self.options.soften;
|
||||||
|
|
||||||
|
// Get the top and bottom of all the elements
|
||||||
|
self.elements.each(function(el, nr){
|
||||||
|
var c = el.getCoordinates();
|
||||||
|
|
||||||
|
if(c.top > 0 && nr == 0){
|
||||||
|
self.vert.append([0]);
|
||||||
|
self.vert_el.append([null]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Top
|
||||||
|
self.vert.append([c.top-soften]);
|
||||||
|
self.vert_el.append([el]);
|
||||||
|
|
||||||
|
// Bottom
|
||||||
|
self.vert.append([c.top+c.height+soften]);
|
||||||
|
self.vert_el.append([el]);
|
||||||
|
|
||||||
|
// Add it to range, for later calculation from left to right
|
||||||
|
self.range.append([{
|
||||||
|
'el': el,
|
||||||
|
'top': c.top-soften,
|
||||||
|
'bottom': c.top+c.height+soften,
|
||||||
|
'left': c.left-soften,
|
||||||
|
'right': c.left+c.width+soften
|
||||||
|
}])
|
||||||
|
|
||||||
|
// Create soft edge around element
|
||||||
|
self.soften(el);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
if(self.elements.length == 0){
|
||||||
|
self.vert.append([0]);
|
||||||
|
self.vert_el.append([null]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reorder
|
||||||
|
var vert = self.vert.clone().sort(self.numberSort) // Use custom sort function because apparently 100 is less then 20..
|
||||||
|
vert_el_new = [], vert_new = [];
|
||||||
|
vert.each(function(v){
|
||||||
|
var old_nr = self.vert.indexOf(v);
|
||||||
|
vert_el_new.append([self.vert_el[old_nr]]);
|
||||||
|
vert_new.append([v]);
|
||||||
|
|
||||||
|
});
|
||||||
|
self.vert = vert_new;
|
||||||
|
self.vert_el = vert_el_new;
|
||||||
|
|
||||||
|
// Shorten vars
|
||||||
|
var vert = self.vert,
|
||||||
|
vert_el = self.vert_el;
|
||||||
|
var t, h, l, w, left, width,
|
||||||
|
row_el, cursor = 0;
|
||||||
|
|
||||||
|
// Loop over all vertical lines
|
||||||
|
vert.each(function(v, nr){
|
||||||
|
|
||||||
|
// Use defaults if el == null (for first fillblock)
|
||||||
|
var c = vert_el[nr] ? vert_el[nr].getCoordinates() : {
|
||||||
|
'left': 0,
|
||||||
|
'top': 0,
|
||||||
|
'width': page_c.x,
|
||||||
|
'height': 0
|
||||||
|
};
|
||||||
|
|
||||||
|
// Loop till cursor gets to parent_element.width
|
||||||
|
var fail_safe = 0;
|
||||||
|
while (cursor < page_c.x && fail_safe < 10){
|
||||||
|
|
||||||
|
t = vert[nr]; // Top is the same for every element in a row
|
||||||
|
h = (nr == vert.length-1) ? (page_c.y - t) : vert[nr+1] - vert[nr]; // So is hight
|
||||||
|
|
||||||
|
// First element get special treatment
|
||||||
|
if(nr == 0){
|
||||||
|
l = 0;
|
||||||
|
w = c.width+(2*soften);
|
||||||
|
cursor += w;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
row_el = self.firstFromLeft(cursor, t) // First next element
|
||||||
|
left = row_el.el ? row_el.left : c.left-soften;
|
||||||
|
width = row_el.el ? row_el.left - cursor : c.left-soften;
|
||||||
|
|
||||||
|
if(t == c.bottom+soften && !row_el.el)
|
||||||
|
width = page_c.x;
|
||||||
|
|
||||||
|
l = cursor;
|
||||||
|
if(cursor < left){
|
||||||
|
w = width;
|
||||||
|
cursor += w+(row_el.right - row_el.left);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
w = page_c.x-l;
|
||||||
|
cursor += w;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add it to the pile!
|
||||||
|
if(h > 0 && w > 0){
|
||||||
|
self.top.append([t]); self.left.append([l]);
|
||||||
|
self.width.append([w]); self.height.append([h]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fail_safe++;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor = 0; // New line, reset cursor position
|
||||||
|
fail_safe = 0;
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create the fill blocks
|
||||||
|
self.top.each(self.createFillItem.bind(self));
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
createFillItem: function(top, nr){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var fill = new Element('div', {
|
||||||
|
'class': self.options.fillClass,
|
||||||
|
'styles': {
|
||||||
|
'position': 'absolute',
|
||||||
|
'background-color': 'rgba('+self.options.fillColor.join(',')+', '+self.options.fillOpacity+')',
|
||||||
|
'display': 'block',
|
||||||
|
'z-index': 2,
|
||||||
|
'top': self.top[nr],
|
||||||
|
'left': self.left[nr],
|
||||||
|
'height': self.height[nr],
|
||||||
|
'width': self.width[nr]
|
||||||
|
}
|
||||||
|
}).inject(self.options.inject || document.body);
|
||||||
|
|
||||||
|
self.fills.include(fill);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Find the first element after x,y coordinates
|
||||||
|
firstFromLeft: function(x, y){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var lowest_left = null;
|
||||||
|
var return_data = {};
|
||||||
|
|
||||||
|
self.range.each(function(range){
|
||||||
|
var is_within_height_range = range.top <= y && range.bottom > y,
|
||||||
|
is_within_width_range = range.left >= x,
|
||||||
|
more_left_then_previous = range.left < lowest_left || lowest_left == null;
|
||||||
|
|
||||||
|
if(is_within_height_range && is_within_width_range && more_left_then_previous){
|
||||||
|
lowest_left = range.left;
|
||||||
|
return_data = range;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return return_data
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
soften: function(el){
|
||||||
|
var self = this;
|
||||||
|
var soften = self.options.soften;
|
||||||
|
|
||||||
|
var c = el.getCoordinates();
|
||||||
|
var from_color = 'rgba('+self.options.fillColor.join(',')+', '+self.options.fillOpacity+')';
|
||||||
|
var to_color = 'rgba('+self.options.fillColor.join(',')+', 0)';
|
||||||
|
|
||||||
|
// Top
|
||||||
|
self.createEdge({
|
||||||
|
'top': c.top-soften,
|
||||||
|
'left': c.left-soften,
|
||||||
|
'width': c.width+(2*soften),
|
||||||
|
'background': '-webkit-gradient(linear, left top, left bottom, from('+from_color+'), to('+to_color+'))',
|
||||||
|
'background': '-moz-linear-gradient(top, '+from_color+', '+to_color+')'
|
||||||
|
})
|
||||||
|
|
||||||
|
// Right
|
||||||
|
self.createEdge({
|
||||||
|
'top': c.top-soften,
|
||||||
|
'left': c.right,
|
||||||
|
'height': c.height+(2*soften),
|
||||||
|
'background': '-webkit-gradient(linear, left, right, from('+from_color+'), to('+to_color+'))',
|
||||||
|
'background': '-moz-linear-gradient(right, '+from_color+', '+to_color+')'
|
||||||
|
})
|
||||||
|
|
||||||
|
// Bottom
|
||||||
|
self.createEdge({
|
||||||
|
'top': c.bottom,
|
||||||
|
'left': c.left-soften,
|
||||||
|
'width': c.width+(2*soften),
|
||||||
|
'background': '-webkit-gradient(linear, left bottom, left top, from('+from_color+'), to('+to_color+'))',
|
||||||
|
'background': '-moz-linear-gradient(bottom, '+from_color+', '+to_color+')'
|
||||||
|
})
|
||||||
|
|
||||||
|
// Left
|
||||||
|
self.createEdge({
|
||||||
|
'top': c.top-soften,
|
||||||
|
'left': c.left-soften,
|
||||||
|
'height': c.height+(2*soften),
|
||||||
|
'background': '-webkit-gradient(linear, right, left, from('+from_color+'), to('+to_color+'))',
|
||||||
|
'background': '-moz-linear-gradient(left, '+from_color+', '+to_color+')'
|
||||||
|
})
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
createEdge: function(style){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var soften = self.options.soften;
|
||||||
|
var edge = new Element('div', {
|
||||||
|
'styles': Object.merge({
|
||||||
|
'position': 'absolute',
|
||||||
|
'width': soften,
|
||||||
|
'height': soften,
|
||||||
|
}, style)
|
||||||
|
}).inject(self.options.inject || document.body)
|
||||||
|
|
||||||
|
self.edges.include(edge);
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy: function(){
|
||||||
|
var self = this;
|
||||||
|
self.fills.each(function(fill){
|
||||||
|
fill.destroy();
|
||||||
|
})
|
||||||
|
self.edges.each(function(edge){
|
||||||
|
edge.destroy();
|
||||||
|
})
|
||||||
|
self.clean();
|
||||||
|
},
|
||||||
|
|
||||||
|
numberSort: function (a, b) {
|
||||||
|
return a - b;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
var WizardBase = new Class({
|
||||||
|
|
||||||
|
Implements: [Options, Events],
|
||||||
|
|
||||||
|
initialize: function(steps){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.steps = steps;
|
||||||
|
self.start();
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
start: function(){
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
nextStep: function(){
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
previousStep: function(){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
WizardBase.Screen = new Class({
|
||||||
|
|
||||||
|
initialize: function(data){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.data = data;
|
||||||
|
self.create()
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
create: function(){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.el = new Element('div.')
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy: function(){
|
||||||
|
this.el.destroy();
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
window.Wizard = new WizardBase([
|
||||||
|
{
|
||||||
|
'title': 'Fill in your username and password',
|
||||||
|
'Description': 'Outside blabla',
|
||||||
|
'tab': 'general',
|
||||||
|
'fields': ['username', 'password']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': 'What do you use to download your movies',
|
||||||
|
'answers': [
|
||||||
|
{'name': 'nzb', 'label': 'Usenet'},
|
||||||
|
{'name': 'torrent', 'label': 'Torrents'}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': 'Do you have a login for any of the following sites',
|
||||||
|
'tab': 'providers',
|
||||||
|
'needs': function(){
|
||||||
|
return self.config_nzb || self.config_torrent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
@@ -20,6 +20,10 @@ class NZBProvider(Provider):
|
|||||||
time_between_searches = 10 # Seconds
|
time_between_searches = 10 # Seconds
|
||||||
|
|
||||||
|
|
||||||
|
def isEnabled(self):
|
||||||
|
return True # nzb_downloaded is enabled check
|
||||||
|
|
||||||
|
|
||||||
class TorrentProvider(Provider):
|
class TorrentProvider(Provider):
|
||||||
type = 'torrent'
|
type = 'torrent'
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,138 @@
|
|||||||
|
from couchpotato.core.event import addEvent
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.base import NZBProvider
|
from couchpotato.core.providers.base import NZBProvider
|
||||||
|
from dateutil.parser import parse
|
||||||
|
from urllib import urlencode
|
||||||
|
from urllib2 import URLError
|
||||||
|
import time
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Newzbin(NZBProvider):
|
class Newzbin(NZBProvider):
|
||||||
pass
|
searchUrl = 'https://www.newzbin.com/search/'
|
||||||
|
|
||||||
|
formatIds = {
|
||||||
|
2: ['scr'],
|
||||||
|
1: ['cam'],
|
||||||
|
4: ['tc'],
|
||||||
|
8: ['ts'],
|
||||||
|
1024: ['r5'],
|
||||||
|
}
|
||||||
|
cat_ids = [
|
||||||
|
([2097152], ['1080p']),
|
||||||
|
([524288], ['720p']),
|
||||||
|
([262144], ['brrip']),
|
||||||
|
([2], ['dvdr']),
|
||||||
|
]
|
||||||
|
cat_backup_id = -1
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
addEvent('provider.nzb.search', self.search)
|
||||||
|
|
||||||
|
def search(self, movie, quality):
|
||||||
|
|
||||||
|
self.cleanCache();
|
||||||
|
|
||||||
|
results = []
|
||||||
|
if not self.enabled() or not self.isAvailable(self.searchUrl):
|
||||||
|
return results
|
||||||
|
|
||||||
|
formatId = self.getFormatId(type)
|
||||||
|
catId = self.getCatId(type)
|
||||||
|
|
||||||
|
arguments = urlencode({
|
||||||
|
'searchaction': 'Search',
|
||||||
|
'u_url_posts_only': '0',
|
||||||
|
'u_show_passworded': '0',
|
||||||
|
'q_url': 'imdb.com/title/' + movie.imdb,
|
||||||
|
'sort': 'ps_totalsize',
|
||||||
|
'order': 'asc',
|
||||||
|
'u_post_results_amt': '100',
|
||||||
|
'feed': 'rss',
|
||||||
|
'category': '6',
|
||||||
|
'ps_rb_video_format': str(catId),
|
||||||
|
'ps_rb_source': str(formatId),
|
||||||
|
})
|
||||||
|
|
||||||
|
url = "%s?%s" % (self.searchUrl, arguments)
|
||||||
|
cacheId = str('%s %s %s' % (movie.imdb, str(formatId), str(catId)))
|
||||||
|
singleCat = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
cached = False
|
||||||
|
if(self.cache.get(cacheId)):
|
||||||
|
data = True
|
||||||
|
cached = True
|
||||||
|
log.info('Getting RSS from cache: %s.' % cacheId)
|
||||||
|
else:
|
||||||
|
log.info('Searching: %s' % url)
|
||||||
|
data = self.urlopen(url, username = self.conf('username'), password = self.conf('password'))
|
||||||
|
self.cache[cacheId] = {
|
||||||
|
'time': time.time()
|
||||||
|
}
|
||||||
|
|
||||||
|
except (IOError, URLError):
|
||||||
|
log.error('Failed to open %s.' % url)
|
||||||
|
return results
|
||||||
|
|
||||||
|
if data:
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
if cached:
|
||||||
|
xml = self.cache[cacheId]['xml']
|
||||||
|
else:
|
||||||
|
xml = self.getItems(data)
|
||||||
|
self.cache[cacheId]['xml'] = xml
|
||||||
|
except:
|
||||||
|
log.debug('No valid xml or to many requests.. You never know with %s.' % self.name)
|
||||||
|
return results
|
||||||
|
|
||||||
|
for item in xml:
|
||||||
|
|
||||||
|
title = self.gettextelement(item, "title")
|
||||||
|
if 'error' in title.lower(): continue
|
||||||
|
|
||||||
|
REPORT_NS = 'http://www.newzbin.com/DTD/2007/feeds/report/';
|
||||||
|
|
||||||
|
# Add attributes to name
|
||||||
|
for attr in item.find('{%s}attributes' % REPORT_NS):
|
||||||
|
title += ' ' + attr.text
|
||||||
|
|
||||||
|
id = int(self.gettextelement(item, '{%s}id' % REPORT_NS))
|
||||||
|
size = str(int(self.gettextelement(item, '{%s}size' % REPORT_NS)) / 1024 / 1024) + ' mb'
|
||||||
|
date = str(self.gettextelement(item, '{%s}postdate' % REPORT_NS))
|
||||||
|
|
||||||
|
new = self.feedItem()
|
||||||
|
new.id = id
|
||||||
|
new.type = 'nzb'
|
||||||
|
new.name = title
|
||||||
|
new.date = int(time.mktime(parse(date).timetuple()))
|
||||||
|
new.size = self.parseSize(size)
|
||||||
|
new.url = str(self.gettextelement(item, '{%s}nzb' % REPORT_NS))
|
||||||
|
new.detailUrl = str(self.gettextelement(item, 'link'))
|
||||||
|
new.content = self.gettextelement(item, "description")
|
||||||
|
new.score = self.calcScore(new, movie)
|
||||||
|
new.addbyid = True
|
||||||
|
new.checkNZB = False
|
||||||
|
|
||||||
|
if new.date > time.time() - (int(self.config.get('NZB', 'retention')) * 24 * 60 * 60) and self.isCorrectMovie(new, movie, type, imdbResults = True, singleCategory = singleCat):
|
||||||
|
results.append(new)
|
||||||
|
log.info('Found: %s' % new.name)
|
||||||
|
|
||||||
|
return results
|
||||||
|
except SyntaxError:
|
||||||
|
log.error('Failed to parse XML response from newzbin.com')
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
def getFormatId(self, format):
|
||||||
|
for id, quality in self.formatIds.iteritems():
|
||||||
|
for q in quality:
|
||||||
|
if q == format:
|
||||||
|
return id
|
||||||
|
|
||||||
|
return self.catBackupId
|
||||||
|
|
||||||
|
def isEnabled(self):
|
||||||
|
return NZBProvider.isEnabled(self) and self.conf('enabled') and self.conf('username') and self.conf('password')
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ config = [{
|
|||||||
{
|
{
|
||||||
'name': 'host',
|
'name': 'host',
|
||||||
'default': 'http://nzb.su',
|
'default': 'http://nzb.su',
|
||||||
|
'description': 'The hostname of your newznab provider, like http://nzb.su'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'api_key',
|
'name': 'api_key',
|
||||||
|
|||||||
@@ -1,8 +1,126 @@
|
|||||||
|
from couchpotato.core.event import addEvent
|
||||||
|
from couchpotato.core.helpers.variable import cleanHost
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.base import NZBProvider
|
from couchpotato.core.providers.base import NZBProvider
|
||||||
|
from dateutil.parser import parse
|
||||||
|
from urllib import urlencode
|
||||||
|
from urllib2 import URLError
|
||||||
|
import time
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Newznab(NZBProvider):
|
class Newznab(NZBProvider):
|
||||||
pass
|
|
||||||
|
urls = {
|
||||||
|
'download': 'get&id=%s%s',
|
||||||
|
'detail': 'details&id=%s',
|
||||||
|
}
|
||||||
|
|
||||||
|
cat_ids = [
|
||||||
|
([2000], ['brrip']),
|
||||||
|
([2010], ['dvdr']),
|
||||||
|
([2030], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr']),
|
||||||
|
([2040], ['720p', '1080p']),
|
||||||
|
]
|
||||||
|
cat_backup_id = 2000
|
||||||
|
|
||||||
|
time_between_searches = 1 # Seconds
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
addEvent('provider.nzb.search', self.search)
|
||||||
|
|
||||||
|
def getUrl(self, type):
|
||||||
|
return cleanHost(self.conf('host')) + 'api?t=' + type
|
||||||
|
|
||||||
|
def search(self, movie, quality):
|
||||||
|
|
||||||
|
self.cleanCache();
|
||||||
|
|
||||||
|
results = []
|
||||||
|
if not self.enabled() or not self.isAvailable(self.getUrl(self.searchUrl)):
|
||||||
|
return results
|
||||||
|
|
||||||
|
catId = self.getCatId(type)
|
||||||
|
arguments = urlencode({
|
||||||
|
'imdbid': movie.imdb.replace('tt', ''),
|
||||||
|
'cat': catId,
|
||||||
|
'apikey': self.conf('apikey'),
|
||||||
|
't': self.searchUrl,
|
||||||
|
'extended': 1
|
||||||
|
})
|
||||||
|
url = "%s&%s" % (self.getUrl(self.searchUrl), arguments)
|
||||||
|
cacheId = str(movie.imdb) + '-' + str(catId)
|
||||||
|
singleCat = (len(self.catIds.get(catId)) == 1 and catId != self.catBackupId)
|
||||||
|
|
||||||
|
try:
|
||||||
|
cached = False
|
||||||
|
if(self.cache.get(cacheId)):
|
||||||
|
data = True
|
||||||
|
cached = True
|
||||||
|
log.info('Getting RSS from cache: %s.' % cacheId)
|
||||||
|
else:
|
||||||
|
log.info('Searching: %s' % url)
|
||||||
|
data = self.urlopen(url)
|
||||||
|
self.cache[cacheId] = {
|
||||||
|
'time': time.time()
|
||||||
|
}
|
||||||
|
|
||||||
|
except (IOError, URLError):
|
||||||
|
log.error('Failed to open %s.' % url)
|
||||||
|
return results
|
||||||
|
|
||||||
|
if data:
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
if cached:
|
||||||
|
xml = self.cache[cacheId]['xml']
|
||||||
|
else:
|
||||||
|
xml = self.getItems(data)
|
||||||
|
self.cache[cacheId]['xml'] = xml
|
||||||
|
except:
|
||||||
|
log.debug('No valid xml or to many requests.' % self.name)
|
||||||
|
return results
|
||||||
|
|
||||||
|
results = []
|
||||||
|
for nzb in xml:
|
||||||
|
|
||||||
|
for item in nzb:
|
||||||
|
if item.attrib.get('name') == 'size':
|
||||||
|
size = item.attrib.get('value')
|
||||||
|
elif item.attrib.get('name') == 'usenetdate':
|
||||||
|
date = item.attrib.get('value')
|
||||||
|
|
||||||
|
new = self.feedItem()
|
||||||
|
new.id = self.gettextelement(nzb, "guid").split('/')[-1:].pop()
|
||||||
|
new.type = 'nzb'
|
||||||
|
new.name = self.gettextelement(nzb, "title")
|
||||||
|
new.date = int(time.mktime(parse(date).timetuple()))
|
||||||
|
new.size = int(size) / 1024 / 1024
|
||||||
|
new.url = self.downloadLink(new.id)
|
||||||
|
new.detailUrl = self.detailLink(new.id)
|
||||||
|
new.content = self.gettextelement(nzb, "description")
|
||||||
|
new.score = self.calcScore(new, movie)
|
||||||
|
|
||||||
|
if new.date > time.time() - (int(self.config.get('NZB', 'retention')) * 24 * 60 * 60) and self.isCorrectMovie(new, movie, type, imdbResults = True, singleCategory = singleCat):
|
||||||
|
results.append(new)
|
||||||
|
log.info('Found: %s' % new.name)
|
||||||
|
|
||||||
|
return results
|
||||||
|
except SyntaxError:
|
||||||
|
log.error('Failed to parse XML response from Newznab')
|
||||||
|
return False
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
def isEnabled(self):
|
||||||
|
return NZBProvider.isEnabled(self) and self.conf('enabled') and self.conf('host') and self.conf('apikey')
|
||||||
|
|
||||||
|
def getApiExt(self):
|
||||||
|
return '&apikey=%s' % self.conf('apikey')
|
||||||
|
|
||||||
|
def downloadLink(self, id):
|
||||||
|
return self.getUrl(self.downloadUrl) % (id, self.getApiExt())
|
||||||
|
|
||||||
|
def detailLink(self, id):
|
||||||
|
return self.getUrl(self.detailUrl) % id
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ config = [{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'api_key',
|
'name': 'api_key',
|
||||||
'default': '9b939aee0aaafc12a65bf448e4af9543',
|
'default': '',
|
||||||
'label': 'Api Key',
|
'label': 'Api Key',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
|
from couchpotato.core.event import addEvent
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.base import NZBProvider
|
from couchpotato.core.providers.base import NZBProvider
|
||||||
|
from dateutil.parser import parse
|
||||||
|
from urllib import urlencode
|
||||||
|
from urllib2 import URLError
|
||||||
|
import time
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
@@ -20,12 +25,10 @@ class NZBMatrix(NZBProvider):
|
|||||||
]
|
]
|
||||||
cat_backup_id = 2
|
cat_backup_id = 2
|
||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self):
|
||||||
log.info('Using NZBMatrix provider')
|
addEvent('provider.nzb.search', self.search)
|
||||||
|
|
||||||
self.config = config
|
def search(self, movie, quality):
|
||||||
|
|
||||||
def find(self, movie, quality, type, retry = False):
|
|
||||||
|
|
||||||
self.cleanCache();
|
self.cleanCache();
|
||||||
|
|
||||||
@@ -113,4 +116,4 @@ class NZBMatrix(NZBProvider):
|
|||||||
return '&username=%s&apikey=%s' % (self.conf('username'), self.conf('apikey'))
|
return '&username=%s&apikey=%s' % (self.conf('username'), self.conf('apikey'))
|
||||||
|
|
||||||
def isEnabled(self):
|
def isEnabled(self):
|
||||||
return self.conf('enabled') and self.conf('username') and self.conf('apikey')
|
return NZBProvider.isEnabled(self) and self.conf('enabled') and self.conf('username') and self.conf('apikey')
|
||||||
|
|||||||
@@ -1,124 +0,0 @@
|
|||||||
from app.config.cplog import CPLog
|
|
||||||
from app.lib.provider.yarr.base import nzbBase
|
|
||||||
from dateutil.parser import parse
|
|
||||||
from urllib import urlencode
|
|
||||||
from urllib2 import URLError
|
|
||||||
import time
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
|
||||||
|
|
||||||
class nzbMatrix(nzbBase):
|
|
||||||
"""Api for NZBMatrix"""
|
|
||||||
|
|
||||||
name = 'NZBMatrix'
|
|
||||||
downloadUrl = 'https://api.nzbmatrix.com/v1.1/download.php?id=%s%s'
|
|
||||||
detailUrl = 'https://nzbmatrix.com/nzb-details.php?id=%s&hit=1'
|
|
||||||
searchUrl = 'http://rss.nzbmatrix.com/rss.php'
|
|
||||||
|
|
||||||
catIds = {
|
|
||||||
42: ['720p', '1080p'],
|
|
||||||
2: ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr'],
|
|
||||||
54: ['brrip'],
|
|
||||||
1: ['dvdr']
|
|
||||||
}
|
|
||||||
catBackupId = 2
|
|
||||||
|
|
||||||
timeBetween = 10 # Seconds
|
|
||||||
|
|
||||||
def __init__(self, config):
|
|
||||||
log.info('Using NZBMatrix provider')
|
|
||||||
|
|
||||||
self.config = config
|
|
||||||
|
|
||||||
def conf(self, option):
|
|
||||||
return self.config.get('NZBMatrix', option)
|
|
||||||
|
|
||||||
def enabled(self):
|
|
||||||
return self.conf('enabled') and self.config.get('NZB', 'enabled') and self.conf('username') and self.conf('apikey')
|
|
||||||
|
|
||||||
def find(self, movie, quality, type, retry = False):
|
|
||||||
|
|
||||||
self.cleanCache();
|
|
||||||
|
|
||||||
results = []
|
|
||||||
if not self.enabled() or not self.isAvailable(self.searchUrl):
|
|
||||||
return results
|
|
||||||
|
|
||||||
catId = self.getCatId(type)
|
|
||||||
arguments = urlencode({
|
|
||||||
'term': movie.imdb,
|
|
||||||
'subcat': catId,
|
|
||||||
'username': self.conf('username'),
|
|
||||||
'apikey': self.conf('apikey'),
|
|
||||||
'searchin': 'weblink',
|
|
||||||
'english': 1 if self.conf('english') else 0,
|
|
||||||
})
|
|
||||||
url = "%s?%s" % (self.searchUrl, arguments)
|
|
||||||
cacheId = str(movie.imdb) + '-' + str(catId)
|
|
||||||
singleCat = (len(self.catIds.get(catId)) == 1 and catId != self.catBackupId)
|
|
||||||
|
|
||||||
try:
|
|
||||||
cached = False
|
|
||||||
if(self.cache.get(cacheId)):
|
|
||||||
data = True
|
|
||||||
cached = True
|
|
||||||
log.info('Getting RSS from cache: %s.' % cacheId)
|
|
||||||
else:
|
|
||||||
log.info('Searching: %s' % url)
|
|
||||||
data = self.urlopen(url)
|
|
||||||
self.cache[cacheId] = {
|
|
||||||
'time': time.time()
|
|
||||||
}
|
|
||||||
|
|
||||||
except (IOError, URLError):
|
|
||||||
log.error('Failed to open %s.' % url)
|
|
||||||
return results
|
|
||||||
|
|
||||||
if data:
|
|
||||||
try:
|
|
||||||
try:
|
|
||||||
if cached:
|
|
||||||
xml = self.cache[cacheId]['xml']
|
|
||||||
else:
|
|
||||||
xml = self.getItems(data)
|
|
||||||
self.cache[cacheId]['xml'] = xml
|
|
||||||
except:
|
|
||||||
log.debug('No valid xml or to many requests.. You never know with %s.' % self.name)
|
|
||||||
return results
|
|
||||||
|
|
||||||
for nzb in xml:
|
|
||||||
|
|
||||||
title = self.gettextelement(nzb, "title")
|
|
||||||
if 'error' in title.lower(): continue
|
|
||||||
|
|
||||||
id = int(self.gettextelement(nzb, "link").split('&')[0].partition('id=')[2])
|
|
||||||
size = self.gettextelement(nzb, "description").split('<br /><b>')[2].split('> ')[1]
|
|
||||||
date = str(self.gettextelement(nzb, "description").split('<br /><b>')[3].partition('Added:</b> ')[2])
|
|
||||||
|
|
||||||
new = self.feedItem()
|
|
||||||
new.id = id
|
|
||||||
new.type = 'nzb'
|
|
||||||
new.name = title
|
|
||||||
new.date = int(time.mktime(parse(date).timetuple()))
|
|
||||||
new.size = self.parseSize(size)
|
|
||||||
new.url = self.downloadLink(id)
|
|
||||||
new.detailUrl = self.detailLink(id)
|
|
||||||
new.content = self.gettextelement(nzb, "description")
|
|
||||||
new.score = self.calcScore(new, movie)
|
|
||||||
new.checkNZB = True
|
|
||||||
|
|
||||||
if new.date > time.time() - (int(self.config.get('NZB', 'retention')) * 24 * 60 * 60):
|
|
||||||
if self.isCorrectMovie(new, movie, type, imdbResults = True, singleCategory = singleCat):
|
|
||||||
results.append(new)
|
|
||||||
log.info('Found: %s' % new.name)
|
|
||||||
else:
|
|
||||||
log.info('Found outside retention: %s' % new.name)
|
|
||||||
|
|
||||||
return results
|
|
||||||
except SyntaxError:
|
|
||||||
log.error('Failed to parse XML response from NZBMatrix.com')
|
|
||||||
|
|
||||||
return results
|
|
||||||
|
|
||||||
def getApiExt(self):
|
|
||||||
return '&username=%s&apikey=%s' % (self.conf('username'), self.conf('apikey'))
|
|
||||||
@@ -1,8 +1,122 @@
|
|||||||
|
from couchpotato.core.event import addEvent
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.base import NZBProvider
|
from couchpotato.core.providers.base import NZBProvider
|
||||||
|
from dateutil.parser import parse
|
||||||
|
from urllib import urlencode
|
||||||
|
from urllib2 import URLError
|
||||||
|
import time
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Nzbs(NZBProvider):
|
class Nzbs(NZBProvider):
|
||||||
pass
|
|
||||||
|
urls = {
|
||||||
|
'download': 'http://nzbs.org/index.php?action=getnzb&nzbid=%s%s',
|
||||||
|
'nfo': 'http://nzbs.org/index.php?action=view&nzbid=%s&nfo=1',
|
||||||
|
'detail': 'http://nzbs.org/index.php?action=view&nzbid=%s',
|
||||||
|
'api': 'http://nzbs.org/rss.php',
|
||||||
|
}
|
||||||
|
|
||||||
|
cat_ids = [
|
||||||
|
([4], ['720p', '1080p']),
|
||||||
|
([2], ['cam', 'ts', 'dvdrip', 'tc', 'brrip', 'r5', 'scr']),
|
||||||
|
([9], ['dvdr']),
|
||||||
|
]
|
||||||
|
cat_backup_id = 't2'
|
||||||
|
|
||||||
|
time_between_searches = 3 # Seconds
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
addEvent('provider.nzb.search', self.search)
|
||||||
|
|
||||||
|
def search(self, movie, quality):
|
||||||
|
|
||||||
|
self.cleanCache();
|
||||||
|
|
||||||
|
results = []
|
||||||
|
if not self.enabled() or not self.isAvailable(self.apiUrl + '?test' + self.getApiExt()):
|
||||||
|
return results
|
||||||
|
|
||||||
|
catId = self.getCatId(type)
|
||||||
|
arguments = urlencode({
|
||||||
|
'action':'search',
|
||||||
|
'q': self.toSearchString(movie.name),
|
||||||
|
'catid': catId,
|
||||||
|
'i': self.conf('id'),
|
||||||
|
'h': self.conf('key'),
|
||||||
|
'age': self.config.get('NZB', 'retention')
|
||||||
|
})
|
||||||
|
url = "%s?%s" % (self.apiUrl, arguments)
|
||||||
|
cacheId = str(movie.imdb) + '-' + str(catId)
|
||||||
|
singleCat = (len(self.catIds.get(catId)) == 1 and catId != self.catBackupId)
|
||||||
|
|
||||||
|
try:
|
||||||
|
cached = False
|
||||||
|
if(self.cache.get(cacheId)):
|
||||||
|
data = True
|
||||||
|
cached = True
|
||||||
|
log.info('Getting RSS from cache: %s.' % cacheId)
|
||||||
|
else:
|
||||||
|
log.info('Searching: %s' % url)
|
||||||
|
data = self.urlopen(url)
|
||||||
|
self.cache[cacheId] = {
|
||||||
|
'time': time.time()
|
||||||
|
}
|
||||||
|
except (IOError, URLError):
|
||||||
|
log.error('Failed to open %s.' % url)
|
||||||
|
return results
|
||||||
|
|
||||||
|
if data:
|
||||||
|
log.debug('Parsing NZBs.org RSS.')
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
if cached:
|
||||||
|
xml = self.cache[cacheId]['xml']
|
||||||
|
else:
|
||||||
|
xml = self.getItems(data)
|
||||||
|
self.cache[cacheId]['xml'] = xml
|
||||||
|
except:
|
||||||
|
retry = False
|
||||||
|
if retry == False:
|
||||||
|
log.error('No valid xml, to many requests? Try again in 15sec.')
|
||||||
|
time.sleep(15)
|
||||||
|
return self.find(movie, quality, type, retry = True)
|
||||||
|
else:
|
||||||
|
log.error('Failed again.. disable %s for 15min.' % self.name)
|
||||||
|
self.available = False
|
||||||
|
return results
|
||||||
|
|
||||||
|
for nzb in xml:
|
||||||
|
|
||||||
|
id = int(self.gettextelement(nzb, "link").partition('nzbid=')[2])
|
||||||
|
|
||||||
|
size = self.gettextelement(nzb, "description").split('</a><br />')[1].split('">')[1]
|
||||||
|
|
||||||
|
new = self.feedItem()
|
||||||
|
new.id = id
|
||||||
|
new.type = 'nzb'
|
||||||
|
new.name = self.gettextelement(nzb, "title")
|
||||||
|
new.date = int(time.mktime(parse(self.gettextelement(nzb, "pubDate")).timetuple()))
|
||||||
|
new.size = self.parseSize(size)
|
||||||
|
new.url = self.downloadLink(id)
|
||||||
|
new.detailUrl = self.detailLink(id)
|
||||||
|
new.content = self.gettextelement(nzb, "description")
|
||||||
|
new.score = self.calcScore(new, movie)
|
||||||
|
|
||||||
|
if self.isCorrectMovie(new, movie, type, singleCategory = singleCat):
|
||||||
|
results.append(new)
|
||||||
|
log.info('Found: %s' % new.name)
|
||||||
|
|
||||||
|
return results
|
||||||
|
except SyntaxError:
|
||||||
|
log.error('Failed to parse XML response from NZBs.org')
|
||||||
|
return False
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
def isEnabled(self):
|
||||||
|
return NZBProvider.isEnabled(self) and self.conf('enabled') and self.conf('id') and self.conf('key')
|
||||||
|
|
||||||
|
def getApiExt(self):
|
||||||
|
return '&i=%s&h=%s' % (self.conf('id'), self.conf('key'))
|
||||||
|
|||||||
@@ -5,4 +5,144 @@ log = CPLog(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class ThePirateBay(TorrentProvider):
|
class ThePirateBay(TorrentProvider):
|
||||||
pass
|
|
||||||
|
urls = {
|
||||||
|
'download': 'http://torrents.thepiratebay.org/%s/%s.torrent',
|
||||||
|
'nfo': 'http://thepiratebay.org/torrent/%s',
|
||||||
|
'detail': 'http://thepiratebay.org/torrent/%s',
|
||||||
|
'search': 'http://thepiratebay.org/search/%s/0/7/%d',
|
||||||
|
}
|
||||||
|
|
||||||
|
cat_ids = [
|
||||||
|
([207], ['720p', '1080p']),
|
||||||
|
([200], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr', 'brrip']),
|
||||||
|
([202], ['dvdr'])
|
||||||
|
]
|
||||||
|
cat_backup_id = 200
|
||||||
|
|
||||||
|
ignore_string = {
|
||||||
|
'720p': ' -brrip -bdrip',
|
||||||
|
'1080p': ' -brrip -bdrip'
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def find(self, movie, quality, type):
|
||||||
|
|
||||||
|
results = []
|
||||||
|
if not self.enabled() or not self.isAvailable(self.apiUrl):
|
||||||
|
return results
|
||||||
|
|
||||||
|
url = self.apiUrl % (quote_plus(self.toSearchString(movie.name + ' ' + quality) + self.makeIgnoreString(type)), self.getCatId(type))
|
||||||
|
|
||||||
|
log.info('Searching: %s' % url)
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = urllib2.urlopen(url, timeout = self.timeout).read()
|
||||||
|
except (IOError, URLError):
|
||||||
|
log.error('Failed to open %s.' % url)
|
||||||
|
return results
|
||||||
|
|
||||||
|
try:
|
||||||
|
tables = SoupStrainer('table')
|
||||||
|
html = BeautifulSoup(data, parseOnlyThese = tables)
|
||||||
|
resultTable = html.find('table', attrs = {'id':'searchResult'})
|
||||||
|
for result in resultTable.findAll('tr'):
|
||||||
|
details = result.find('a', attrs = {'class':'detLink'})
|
||||||
|
if details:
|
||||||
|
href = re.search('/(?P<id>\d+)/', details['href'])
|
||||||
|
id = href.group('id')
|
||||||
|
name = self.toSaveString(details.contents[0])
|
||||||
|
desc = result.find('font', attrs = {'class':'detDesc'}).contents[0].split(',')
|
||||||
|
date = ''
|
||||||
|
size = 0
|
||||||
|
for item in desc:
|
||||||
|
# Weird date stuff
|
||||||
|
if 'uploaded' in item.lower():
|
||||||
|
date = item.replace('Uploaded', '')
|
||||||
|
date = date.replace('Today', '')
|
||||||
|
|
||||||
|
# Do something with yesterday
|
||||||
|
yesterdayMinus = 0
|
||||||
|
if 'Y-day' in date:
|
||||||
|
date = date.replace('Y-day', '')
|
||||||
|
yesterdayMinus = 86400
|
||||||
|
|
||||||
|
datestring = date.replace(' ', ' ').strip()
|
||||||
|
date = int(time.mktime(parse(datestring).timetuple())) - yesterdayMinus
|
||||||
|
# size
|
||||||
|
elif 'size' in item.lower():
|
||||||
|
size = item.replace('Size', '')
|
||||||
|
|
||||||
|
seedleech = []
|
||||||
|
for td in result.findAll('td'):
|
||||||
|
try:
|
||||||
|
seedleech.append(int(td.contents[0]))
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
seeders = 0
|
||||||
|
leechers = 0
|
||||||
|
if len(seedleech) == 2 and seedleech[0] > 0 and seedleech[1] > 0:
|
||||||
|
seeders = seedleech[0]
|
||||||
|
leechers = seedleech[1]
|
||||||
|
|
||||||
|
# to item
|
||||||
|
new = self.feedItem()
|
||||||
|
new.id = id
|
||||||
|
new.type = 'torrent'
|
||||||
|
new.name = name
|
||||||
|
new.date = date
|
||||||
|
new.size = self.parseSize(size)
|
||||||
|
new.seeders = seeders
|
||||||
|
new.leechers = leechers
|
||||||
|
new.url = self.downloadLink(id, name)
|
||||||
|
new.score = self.calcScore(new, movie) + self.uploader(result) + (seeders / 10)
|
||||||
|
|
||||||
|
if seeders > 0 and (new.date + (int(self.conf('wait')) * 60 * 60) < time.time()) and Qualities.types.get(type).get('minSize') <= new.size:
|
||||||
|
new.detailUrl = self.detailLink(id)
|
||||||
|
new.content = self.getInfo(new.detailUrl)
|
||||||
|
if self.isCorrectMovie(new, movie, type):
|
||||||
|
results.append(new)
|
||||||
|
log.info('Found: %s' % new.name)
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
except AttributeError:
|
||||||
|
log.debug('No search results found.')
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
def makeIgnoreString(self, type):
|
||||||
|
ignore = self.ignoreString.get(type)
|
||||||
|
return ignore if ignore else ''
|
||||||
|
|
||||||
|
def uploader(self, html):
|
||||||
|
score = 0
|
||||||
|
if html.find('img', attr = {'alt':'VIP'}):
|
||||||
|
score += 3
|
||||||
|
if html.find('img', attr = {'alt':'Trusted'}):
|
||||||
|
score += 1
|
||||||
|
return score
|
||||||
|
|
||||||
|
|
||||||
|
def getInfo(self, url):
|
||||||
|
log.debug('Getting info: %s' % url)
|
||||||
|
try:
|
||||||
|
data = urllib2.urlopen(url, timeout = self.timeout).read()
|
||||||
|
pass
|
||||||
|
except IOError:
|
||||||
|
log.error('Failed to open %s.' % url)
|
||||||
|
return ''
|
||||||
|
|
||||||
|
div = SoupStrainer('div')
|
||||||
|
html = BeautifulSoup(data, parseOnlyThese = div)
|
||||||
|
html = html.find('div', attrs = {'class':'nfo'})
|
||||||
|
return str(html).decode("utf-8", "replace")
|
||||||
|
|
||||||
|
def downloadLink(self, id, name):
|
||||||
|
return self.downloadUrl % (id, quote_plus(name))
|
||||||
|
|
||||||
|
def isEnabled(self):
|
||||||
|
return self.conf('enabled') and TorrentProvider.isEnabled(self)
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class Settings():
|
|||||||
for option, value in options.iteritems():
|
for option, value in options.iteritems():
|
||||||
self.setDefault(section_name, option, value)
|
self.setDefault(section_name, option, value)
|
||||||
|
|
||||||
self.log.debug('Defaults for "%s": %s' % (section_name, options))
|
#self.log.debug('Defaults for "%s": %s' % (section_name, options))
|
||||||
|
|
||||||
if save:
|
if save:
|
||||||
self.save(self)
|
self.save(self)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// MooTools: the javascript framework.
|
// MooTools: the javascript framework.
|
||||||
// Load this file's selection again by visiting: http://mootools.net/more/2b832e45b9bf2f9e5fdbdafc9b16febf
|
// Load this file's selection again by visiting: http://mootools.net/more/1e3edb90c5e02d9b9013b54e6ab001ea
|
||||||
// Or build this file again with packager using: packager build More/Element.Forms More/Element.Delegation More/Element.Shortcuts More/Fx.Slide More/Sortables More/Request.JSONP More/Spinner
|
// Or build this file again with packager using: packager build More/Element.Forms More/Element.Delegation More/Element.Shortcuts More/Fx.Slide More/Sortables More/Request.JSONP More/Request.Periodical More/Spinner
|
||||||
/*
|
/*
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -1754,6 +1754,59 @@ Request.JSONP.counter = 0;
|
|||||||
Request.JSONP.request_map = {};
|
Request.JSONP.request_map = {};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
---
|
||||||
|
|
||||||
|
script: Request.Periodical.js
|
||||||
|
|
||||||
|
name: Request.Periodical
|
||||||
|
|
||||||
|
description: Requests the same URL to pull data from a server but increases the intervals if no data is returned to reduce the load
|
||||||
|
|
||||||
|
license: MIT-style license
|
||||||
|
|
||||||
|
authors:
|
||||||
|
- Christoph Pojer
|
||||||
|
|
||||||
|
requires:
|
||||||
|
- Core/Request
|
||||||
|
- /MooTools.More
|
||||||
|
|
||||||
|
provides: [Request.Periodical]
|
||||||
|
|
||||||
|
...
|
||||||
|
*/
|
||||||
|
|
||||||
|
Request.implement({
|
||||||
|
|
||||||
|
options: {
|
||||||
|
initialDelay: 5000,
|
||||||
|
delay: 5000,
|
||||||
|
limit: 60000
|
||||||
|
},
|
||||||
|
|
||||||
|
startTimer: function(data){
|
||||||
|
var fn = function(){
|
||||||
|
if (!this.running) this.send({data: data});
|
||||||
|
};
|
||||||
|
this.lastDelay = this.options.initialDelay;
|
||||||
|
this.timer = fn.delay(this.lastDelay, this);
|
||||||
|
this.completeCheck = function(response){
|
||||||
|
clearTimeout(this.timer);
|
||||||
|
this.lastDelay = (response) ? this.options.delay : (this.lastDelay + this.options.delay).min(this.options.limit);
|
||||||
|
this.timer = fn.delay(this.lastDelay, this);
|
||||||
|
};
|
||||||
|
return this.addEvent('complete', this.completeCheck);
|
||||||
|
},
|
||||||
|
|
||||||
|
stopTimer: function(){
|
||||||
|
clearTimeout(this.timer);
|
||||||
|
return this.removeEvent('complete', this.completeCheck);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ Page.Settings = new Class({
|
|||||||
'general': {},
|
'general': {},
|
||||||
'providers': {},
|
'providers': {},
|
||||||
'downloaders': {},
|
'downloaders': {},
|
||||||
'notifications': {}
|
'notifications': {},
|
||||||
|
'renamer': {}
|
||||||
},
|
},
|
||||||
|
|
||||||
open: function(action, params){
|
open: function(action, params){
|
||||||
@@ -114,15 +115,16 @@ Page.Settings = new Class({
|
|||||||
section.groups.sortBy('order').each(function(group){
|
section.groups.sortBy('order').each(function(group){
|
||||||
|
|
||||||
// Create the group
|
// Create the group
|
||||||
var group_el = self.createGroup(group).inject(self.tabs[group.tab].content);
|
if(!self.tabs[group.tab].groups[group.name]){
|
||||||
|
var group_el = self.createGroup(group).inject(self.tabs[group.tab].content);
|
||||||
self.tabs[group.tab].groups[group.name] = group_el
|
self.tabs[group.tab].groups[group.name] = group_el
|
||||||
|
}
|
||||||
|
|
||||||
// Add options to group
|
// Add options to group
|
||||||
group.options.sortBy('order').each(function(option){
|
group.options.sortBy('order').each(function(option){
|
||||||
var class_name = (option.type || 'string').capitalize();
|
var class_name = (option.type || 'string').capitalize();
|
||||||
var input = new Option[class_name](self, section_name, option.name, option);
|
var input = new Option[class_name](self, section_name, option.name, option);
|
||||||
input.inject(group_el);
|
input.inject(self.tabs[group.tab].groups[group.name]);
|
||||||
input.fireEvent('injected')
|
input.fireEvent('injected')
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -142,7 +144,7 @@ Page.Settings = new Class({
|
|||||||
if(self.tabs[tab_name] && self.tabs[tab_name].tab)
|
if(self.tabs[tab_name] && self.tabs[tab_name].tab)
|
||||||
return self.tabs[tab_name].tab
|
return self.tabs[tab_name].tab
|
||||||
|
|
||||||
var label = (tab.label || tab.name).capitalize()
|
var label = (tab.label || tab.name || tab_name).capitalize()
|
||||||
var tab_el = new Element('li').adopt(
|
var tab_el = new Element('li').adopt(
|
||||||
new Element('a', {
|
new Element('a', {
|
||||||
'href': '/'+self.name+'/'+tab_name+'/',
|
'href': '/'+self.name+'/'+tab_name+'/',
|
||||||
|
|||||||
@@ -27,9 +27,9 @@
|
|||||||
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/page/manage.js') }}"></script>
|
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/page/manage.js') }}"></script>
|
||||||
|
|
||||||
{% for url in fireEvent('clientscript.get_scripts', as_html = True, single = True) %}
|
{% for url in fireEvent('clientscript.get_scripts', as_html = True, single = True) %}
|
||||||
<script type="text/javascript" src="{{ url_for('api.index') }}{{ url }}"></script>{% endfor %}
|
<script type="text/javascript" src="{{ url_for('web.index') }}{{ url }}"></script>{% endfor %}
|
||||||
{% for url in fireEvent('clientscript.get_styles', as_html = True, single = True) %}
|
{% for url in fireEvent('clientscript.get_styles', as_html = True, single = True) %}
|
||||||
<link rel="stylesheet" href="{{ url_for('api.index') }}{{ url }}" type="text/css">{% endfor %}
|
<link rel="stylesheet" href="{{ url_for('web.index') }}{{ url }}" type="text/css">{% endfor %}
|
||||||
|
|
||||||
<link href="{{ url_for('.static', filename='images/favicon.ico') }}" rel="icon" type="image/x-icon" />
|
<link href="{{ url_for('.static', filename='images/favicon.ico') }}" rel="icon" type="image/x-icon" />
|
||||||
<link rel="apple-touch-icon" href="{{ url_for('.static', filename='images/homescreen.png') }}" />
|
<link rel="apple-touch-icon" href="{{ url_for('.static', filename='images/homescreen.png') }}" />
|
||||||
|
|||||||
Reference in New Issue
Block a user