Browse Source

Providers, wizard base, core notifier base

pull/1/merge
Ruud 14 years ago
parent
commit
49bf6caa52
  1. 2
      couchpotato/__init__.py
  2. 2
      couchpotato/cli.py
  3. 4
      couchpotato/core/event.py
  4. 55
      couchpotato/core/loader.py
  5. 6
      couchpotato/core/notifications/core/__init__.py
  6. 49
      couchpotato/core/notifications/core/main.py
  7. 42
      couchpotato/core/notifications/core/static/notification.js
  8. 20
      couchpotato/core/plugins/base.py
  9. 22
      couchpotato/core/plugins/wizard/__init__.py
  10. 13
      couchpotato/core/plugins/wizard/main.py
  11. 306
      couchpotato/core/plugins/wizard/static/spotlight.js
  12. 76
      couchpotato/core/plugins/wizard/static/wizard.js
  13. 4
      couchpotato/core/providers/base.py
  14. 132
      couchpotato/core/providers/nzb/newzbin/main.py
  15. 1
      couchpotato/core/providers/nzb/newznab/__init__.py
  16. 120
      couchpotato/core/providers/nzb/newznab/main.py
  17. 2
      couchpotato/core/providers/nzb/nzbmatrix/__init__.py
  18. 15
      couchpotato/core/providers/nzb/nzbmatrix/main.py
  19. 124
      couchpotato/core/providers/nzb/nzbmatrix/nzbmatrix.py
  20. 116
      couchpotato/core/providers/nzb/nzbs/main.py
  21. 142
      couchpotato/core/providers/torrent/thepiratebay/main.py
  22. 2
      couchpotato/core/settings/__init__.py
  23. 57
      couchpotato/static/scripts/library/mootools_more.js
  24. 14
      couchpotato/static/scripts/page/settings.js
  25. 4
      couchpotato/templates/_desktop.html

2
couchpotato/__init__.py

@ -26,6 +26,8 @@ def get_session(engine = None):
def get_engine():
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.route('/')

2
couchpotato/cli.py

@ -90,7 +90,7 @@ def cmd_couchpotato(base_path, args):
# Load configs & plugins
loader = Env.get('loader')
loader.preload(root = base_path)
loader.addModule('core', 'couchpotato.core', 'core')
loader.addModule(0, 'core', 'couchpotato.core', 'core')
loader.run()

4
couchpotato/core/event.py

@ -20,7 +20,7 @@ def removeEvent(name, handler):
e -= handler
def fireEvent(name, *args, **kwargs):
log.debug('Firing "%s": %s, %s' % (name, args, kwargs))
#log.debug('Firing "%s": %s, %s' % (name, args, kwargs))
try:
# Return single handler
@ -64,7 +64,7 @@ def fireEvent(name, *args, **kwargs):
log.error('%s: %s' % (name, e))
def fireEventAsync(name, *args, **kwargs):
log.debug('Async "%s": %s, %s' % (name, args, kwargs))
#log.debug('Async "%s": %s, %s' % (name, args, kwargs))
try:
e = events[name]
e.asynchronous = True

55
couchpotato/core/loader.py

@ -18,47 +18,48 @@ class Loader:
providers = os.path.join(root, 'couchpotato', 'core', 'providers')
self.paths = {
'plugin' : ('couchpotato.core.plugins', os.path.join(core, 'plugins')),
'notifications' : ('couchpotato.core.notifications', os.path.join(core, 'notifications')),
'downloaders' : ('couchpotato.core.downloaders', os.path.join(root, 'couchpotato', 'core', 'downloaders')),
'movie_provider' : ('couchpotato.core.providers.movie', os.path.join(providers, 'movie')),
'nzb_provider' : ('couchpotato.core.providers.nzb', os.path.join(providers, 'nzb')),
'torrent_provider' : ('couchpotato.core.providers.torrent', os.path.join(providers, 'torrent')),
'trailer_provider' : ('couchpotato.core.providers.trailer', os.path.join(providers, 'trailer')),
'subtitle_provider' : ('couchpotato.core.providers.subtitle', os.path.join(providers, 'subtitle')),
'plugin' : (0, 'couchpotato.core.plugins', os.path.join(core, 'plugins')),
'notifications' : (20, 'couchpotato.core.notifications', os.path.join(core, 'notifications')),
'downloaders' : (20, 'couchpotato.core.downloaders', os.path.join(core, 'downloaders')),
'movie_provider' : (20, 'couchpotato.core.providers.movie', os.path.join(providers, 'movie')),
'nzb_provider' : (20, 'couchpotato.core.providers.nzb', os.path.join(providers, 'nzb')),
'torrent_provider' : (20, 'couchpotato.core.providers.torrent', os.path.join(providers, 'torrent')),
'trailer_provider' : (20, 'couchpotato.core.providers.trailer', os.path.join(providers, 'trailer')),
'subtitle_provider' : (20, 'couchpotato.core.providers.subtitle', os.path.join(providers, 'subtitle')),
}
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):
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
try:
m = getattr(self.loadModule(module_name), plugin.get('name'))
log.info("Loading %s: %s" % (plugin['type'], plugin['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'))
except Exception, e:
log.error(e)
self.loadPlugins(m, plugin.get('name'))
except Exception, e:
log.error('Can\'t import %s: %s' % (plugin.get('name'), e))
if did_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, '*')):
name = os.path.basename(file)
if os.path.isdir(os.path.join(dir, 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):
try:
@ -82,8 +83,14 @@ class Loader:
log.error("Failed loading plugin '%s': %s" % (name, e))
return False
def addModule(self, type, module, name):
self.modules[module] = {
def addModule(self, priority, type, module, name):
if not self.modules.get(priority):
self.modules[priority] = {}
self.modules[priority][module] = {
'priority': priority,
'module': module,
'type': type,
'name': name,
}

6
couchpotato/core/notifications/core/__init__.py

@ -0,0 +1,6 @@
from .main import CoreNotifier
def start():
return CoreNotifier()
config = []

49
couchpotato/core/notifications/core/main.py

@ -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,
})

42
couchpotato/core/notifications/core/static/notification.js

@ -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();

20
couchpotato/core/plugins/base.py

@ -1,7 +1,8 @@
from couchpotato.api import addApiView
from couchpotato import addView
from couchpotato.environment import Env
from flask.helpers import send_from_directory
import os.path
import re
class Plugin():
@ -12,20 +13,23 @@ class Plugin():
def getName(self):
return self.__class__.__name__
def registerStatic(self, file_path):
def registerStatic(self, plugin_file):
class_name = self.__class__.__name__.lower()
self.plugin_file = file_path
path = class_name + '.static/'
# Register plugin path
self.plugin_path = os.path.dirname(plugin_file)
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
def showStatic(self, file = ''):
plugin_dir = os.path.dirname(self.plugin_file)
dir = os.path.join(plugin_dir, 'static')
dir = os.path.join(self.plugin_path, 'static')
return send_from_directory(dir, file)

22
couchpotato/core/plugins/wizard/__init__.py

@ -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',
},
],
},
],
}]

13
couchpotato/core/plugins/wizard/main.py

@ -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')

306
couchpotato/core/plugins/wizard/static/spotlight.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;
}
});

76
couchpotato/core/plugins/wizard/static/wizard.js

@ -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
}
}
])

4
couchpotato/core/providers/base.py

@ -20,6 +20,10 @@ class NZBProvider(Provider):
time_between_searches = 10 # Seconds
def isEnabled(self):
return True # nzb_downloaded is enabled check
class TorrentProvider(Provider):
type = 'torrent'

132
couchpotato/core/providers/nzb/newzbin/main.py

@ -1,8 +1,138 @@
from couchpotato.core.event import addEvent
from couchpotato.core.logger import CPLog
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__)
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')

1
couchpotato/core/providers/nzb/newznab/__init__.py

@ -17,6 +17,7 @@ config = [{
{
'name': 'host',
'default': 'http://nzb.su',
'description': 'The hostname of your newznab provider, like http://nzb.su'
},
{
'name': 'api_key',

120
couchpotato/core/providers/nzb/newznab/main.py

@ -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.providers.base import NZBProvider
from dateutil.parser import parse
from urllib import urlencode
from urllib2 import URLError
import time
log = CPLog(__name__)
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

2
couchpotato/core/providers/nzb/nzbmatrix/__init__.py

@ -20,7 +20,7 @@ config = [{
},
{
'name': 'api_key',
'default': '9b939aee0aaafc12a65bf448e4af9543',
'default': '',
'label': 'Api Key',
},
],

15
couchpotato/core/providers/nzb/nzbmatrix/main.py

@ -1,5 +1,10 @@
from couchpotato.core.event import addEvent
from couchpotato.core.logger import CPLog
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__)
@ -20,12 +25,10 @@ class NZBMatrix(NZBProvider):
]
cat_backup_id = 2
def __init__(self, config):
log.info('Using NZBMatrix provider')
def __init__(self):
addEvent('provider.nzb.search', self.search)
self.config = config
def find(self, movie, quality, type, retry = False):
def search(self, movie, quality):
self.cleanCache();
@ -113,4 +116,4 @@ class NZBMatrix(NZBProvider):
return '&username=%s&apikey=%s' % (self.conf('username'), self.conf('apikey'))
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')

124
couchpotato/core/providers/nzb/nzbmatrix/nzbmatrix.py

@ -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'))

116
couchpotato/core/providers/nzb/nzbs/main.py

@ -1,8 +1,122 @@
from couchpotato.core.event import addEvent
from couchpotato.core.logger import CPLog
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__)
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'))

142
couchpotato/core/providers/torrent/thepiratebay/main.py

@ -5,4 +5,144 @@ log = CPLog(__name__)
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('&nbsp;', ' ').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)

2
couchpotato/core/settings/__init__.py

@ -45,7 +45,7 @@ class Settings():
for option, value in options.iteritems():
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:
self.save(self)

57
couchpotato/static/scripts/library/mootools_more.js

@ -1,6 +1,6 @@
// MooTools: the javascript framework.
// Load this file's selection again by visiting: http://mootools.net/more/2b832e45b9bf2f9e5fdbdafc9b16febf
// 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
// 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/Request.Periodical More/Spinner
/*
---
@ -1757,6 +1757,59 @@ 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);
}
});
/*
---
script: Class.Refactor.js
name: Class.Refactor

14
couchpotato/static/scripts/page/settings.js

@ -9,7 +9,8 @@ Page.Settings = new Class({
'general': {},
'providers': {},
'downloaders': {},
'notifications': {}
'notifications': {},
'renamer': {}
},
open: function(action, params){
@ -114,15 +115,16 @@ Page.Settings = new Class({
section.groups.sortBy('order').each(function(group){
// Create the group
var group_el = self.createGroup(group).inject(self.tabs[group.tab].content);
self.tabs[group.tab].groups[group.name] = group_el
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
}
// Add options to group
group.options.sortBy('order').each(function(option){
var class_name = (option.type || 'string').capitalize();
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')
});
@ -142,7 +144,7 @@ Page.Settings = new Class({
if(self.tabs[tab_name] && 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(
new Element('a', {
'href': '/'+self.name+'/'+tab_name+'/',

4
couchpotato/templates/_desktop.html

@ -27,9 +27,9 @@
<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) %}
<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) %}
<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 rel="apple-touch-icon" href="{{ url_for('.static', filename='images/homescreen.png') }}" />

Loading…
Cancel
Save