55 changed files with 1084 additions and 549 deletions
@ -0,0 +1,46 @@ |
|||||
|
from .main import NZBVortex |
||||
|
|
||||
|
def start(): |
||||
|
return NZBVortex() |
||||
|
|
||||
|
config = [{ |
||||
|
'name': 'nzbvortex', |
||||
|
'groups': [ |
||||
|
{ |
||||
|
'tab': 'downloaders', |
||||
|
'name': 'nzbvortex', |
||||
|
'label': 'NZBVortex', |
||||
|
'description': 'Use <a href="http://www.nzbvortex.com/landing/" target="_blank">NZBVortex</a> to download NZBs.', |
||||
|
'wizard': True, |
||||
|
'options': [ |
||||
|
{ |
||||
|
'name': 'enabled', |
||||
|
'default': 0, |
||||
|
'type': 'enabler', |
||||
|
'radio_group': 'nzb', |
||||
|
}, |
||||
|
{ |
||||
|
'name': 'host', |
||||
|
'default': 'https://localhost:4321', |
||||
|
}, |
||||
|
{ |
||||
|
'name': 'api_key', |
||||
|
'label': 'Api Key', |
||||
|
}, |
||||
|
{ |
||||
|
'name': 'manual', |
||||
|
'default': False, |
||||
|
'type': 'bool', |
||||
|
'advanced': True, |
||||
|
'description': 'Disable this downloader for automated searches, but use it when I manually send a release.', |
||||
|
}, |
||||
|
{ |
||||
|
'name': 'delete_failed', |
||||
|
'default': True, |
||||
|
'type': 'bool', |
||||
|
'description': 'Delete a release after the download has failed.', |
||||
|
}, |
||||
|
], |
||||
|
} |
||||
|
], |
||||
|
}] |
@ -0,0 +1,176 @@ |
|||||
|
from base64 import b64encode |
||||
|
from couchpotato.core.downloaders.base import Downloader |
||||
|
from couchpotato.core.helpers.encoding import tryUrlencode, ss |
||||
|
from couchpotato.core.helpers.variable import cleanHost |
||||
|
from couchpotato.core.logger import CPLog |
||||
|
from urllib2 import URLError |
||||
|
from uuid import uuid4 |
||||
|
import hashlib |
||||
|
import httplib |
||||
|
import json |
||||
|
import socket |
||||
|
import ssl |
||||
|
import sys |
||||
|
import traceback |
||||
|
import urllib2 |
||||
|
|
||||
|
log = CPLog(__name__) |
||||
|
|
||||
|
class NZBVortex(Downloader): |
||||
|
|
||||
|
type = ['nzb'] |
||||
|
api_level = None |
||||
|
session_id = None |
||||
|
|
||||
|
def download(self, data = {}, movie = {}, manual = False, filedata = None): |
||||
|
|
||||
|
if self.isDisabled(manual) or not self.isCorrectType(data.get('type')) or not self.getApiLevel(): |
||||
|
return |
||||
|
|
||||
|
# Send the nzb |
||||
|
try: |
||||
|
nzb_filename = self.createFileName(data, filedata, movie) |
||||
|
self.call('nzb/add', params = {'file': (ss(nzb_filename), filedata)}, multipart = True) |
||||
|
|
||||
|
return True |
||||
|
except: |
||||
|
log.error('Something went wrong sending the NZB file: %s', traceback.format_exc()) |
||||
|
return False |
||||
|
|
||||
|
def getAllDownloadStatus(self): |
||||
|
|
||||
|
if self.isDisabled(manual = True): |
||||
|
return False |
||||
|
|
||||
|
raw_statuses = self.call('nzb') |
||||
|
|
||||
|
statuses = [] |
||||
|
for item in raw_statuses.get('nzbs', []): |
||||
|
|
||||
|
# Check status |
||||
|
status = 'busy' |
||||
|
if item['state'] == 20: |
||||
|
status = 'completed' |
||||
|
elif item['state'] in [21, 22, 24]: |
||||
|
status = 'failed' |
||||
|
|
||||
|
statuses.append({ |
||||
|
'id': item['id'], |
||||
|
'name': item['uiTitle'], |
||||
|
'status': status, |
||||
|
'original_status': item['state'], |
||||
|
'timeleft':-1, |
||||
|
}) |
||||
|
|
||||
|
return statuses |
||||
|
|
||||
|
def removeFailed(self, item): |
||||
|
|
||||
|
if not self.conf('delete_failed', default = True): |
||||
|
return False |
||||
|
|
||||
|
log.info('%s failed downloading, deleting...', item['name']) |
||||
|
|
||||
|
try: |
||||
|
self.call('nzb/%s/cancel' % item['id']) |
||||
|
except: |
||||
|
log.error('Failed deleting: %s', traceback.format_exc(0)) |
||||
|
return False |
||||
|
|
||||
|
return True |
||||
|
|
||||
|
def login(self): |
||||
|
|
||||
|
nonce = self.call('auth/nonce', auth = False).get('authNonce') |
||||
|
cnonce = uuid4().hex |
||||
|
hashed = b64encode(hashlib.sha256('%s:%s:%s' % (nonce, cnonce, self.conf('api_key'))).digest()) |
||||
|
|
||||
|
params = { |
||||
|
'nonce': nonce, |
||||
|
'cnonce': cnonce, |
||||
|
'hash': hashed |
||||
|
} |
||||
|
|
||||
|
login_data = self.call('auth/login', parameters = params, auth = False) |
||||
|
|
||||
|
# Save for later |
||||
|
if login_data.get('loginResult') == 'successful': |
||||
|
self.session_id = login_data.get('sessionID') |
||||
|
return True |
||||
|
|
||||
|
log.error('Login failed, please check you api-key') |
||||
|
return False |
||||
|
|
||||
|
|
||||
|
def call(self, call, parameters = {}, repeat = False, auth = True, *args, **kwargs): |
||||
|
|
||||
|
# Login first |
||||
|
if not self.session_id and auth: |
||||
|
self.login() |
||||
|
|
||||
|
# Always add session id to request |
||||
|
if self.session_id: |
||||
|
parameters['sessionid'] = self.session_id |
||||
|
|
||||
|
params = tryUrlencode(parameters) |
||||
|
|
||||
|
url = cleanHost(self.conf('host')) + 'api/' + call |
||||
|
url_opener = urllib2.build_opener(HTTPSHandler()) |
||||
|
|
||||
|
try: |
||||
|
data = self.urlopen('%s?%s' % (url, params), opener = url_opener, *args, **kwargs) |
||||
|
|
||||
|
if data: |
||||
|
return json.loads(data) |
||||
|
except URLError, e: |
||||
|
if hasattr(e, 'code') and e.code == 403: |
||||
|
# Try login and do again |
||||
|
if not repeat: |
||||
|
self.login() |
||||
|
return self.call(call, parameters = parameters, repeat = True, *args, **kwargs) |
||||
|
|
||||
|
log.error('Failed to parsing %s: %s', (self.getName(), traceback.format_exc())) |
||||
|
except: |
||||
|
log.error('Failed to parsing %s: %s', (self.getName(), traceback.format_exc())) |
||||
|
|
||||
|
return {} |
||||
|
|
||||
|
def getApiLevel(self): |
||||
|
|
||||
|
if not self.api_level: |
||||
|
|
||||
|
url = cleanHost(self.conf('host')) + 'api/app/apilevel' |
||||
|
url_opener = urllib2.build_opener(HTTPSHandler()) |
||||
|
|
||||
|
try: |
||||
|
data = self.urlopen(url, opener = url_opener, show_error = False) |
||||
|
self.api_level = float(json.loads(data).get('apilevel')) |
||||
|
except URLError, e: |
||||
|
if hasattr(e, 'code') and e.code == 403: |
||||
|
log.error('This version of NZBVortex isn\'t supported. Please update to 2.8.6 or higher') |
||||
|
else: |
||||
|
log.error('NZBVortex doesn\'t seem to be running or maybe the remote option isn\'t enabled yet: %s', traceback.format_exc(1)) |
||||
|
|
||||
|
return self.api_level |
||||
|
|
||||
|
|
||||
|
class HTTPSConnection(httplib.HTTPSConnection): |
||||
|
def __init__(self, *args, **kwargs): |
||||
|
httplib.HTTPSConnection.__init__(self, *args, **kwargs) |
||||
|
|
||||
|
def connect(self): |
||||
|
sock = socket.create_connection((self.host, self.port), self.timeout) |
||||
|
if sys.version_info < (2, 6, 7): |
||||
|
if hasattr(self, '_tunnel_host'): |
||||
|
self.sock = sock |
||||
|
self._tunnel() |
||||
|
else: |
||||
|
if self._tunnel_host: |
||||
|
self.sock = sock |
||||
|
self._tunnel() |
||||
|
|
||||
|
self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, ssl_version = ssl.PROTOCOL_TLSv1) |
||||
|
|
||||
|
class HTTPSHandler(urllib2.HTTPSHandler): |
||||
|
def https_open(self, req): |
||||
|
return self.do_open(HTTPSConnection, req) |
@ -0,0 +1,32 @@ |
|||||
|
from .main import Toasty |
||||
|
|
||||
|
def start(): |
||||
|
return Toasty() |
||||
|
|
||||
|
config = [{ |
||||
|
'name': 'toasty', |
||||
|
'groups': [ |
||||
|
{ |
||||
|
'tab': 'notifications', |
||||
|
'name': 'toasty', |
||||
|
'options': [ |
||||
|
{ |
||||
|
'name': 'enabled', |
||||
|
'default': 0, |
||||
|
'type': 'enabler', |
||||
|
}, |
||||
|
{ |
||||
|
'name': 'api_key', |
||||
|
'label': 'Device ID', |
||||
|
}, |
||||
|
{ |
||||
|
'name': 'on_snatch', |
||||
|
'default': 0, |
||||
|
'type': 'bool', |
||||
|
'advanced': True, |
||||
|
'description': 'Also send message when movie is snatched.', |
||||
|
}, |
||||
|
], |
||||
|
} |
||||
|
], |
||||
|
}] |
@ -0,0 +1,30 @@ |
|||||
|
from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode |
||||
|
from couchpotato.core.logger import CPLog |
||||
|
from couchpotato.core.notifications.base import Notification |
||||
|
import traceback |
||||
|
|
||||
|
log = CPLog(__name__) |
||||
|
|
||||
|
class Toasty(Notification): |
||||
|
|
||||
|
urls = { |
||||
|
'api': 'http://api.supertoasty.com/notify/%s?%s' |
||||
|
} |
||||
|
|
||||
|
def notify(self, message = '', data = {}, listener = None): |
||||
|
if self.isDisabled(): return |
||||
|
|
||||
|
data = { |
||||
|
'title': self.default_title, |
||||
|
'text': toUnicode(message), |
||||
|
'sender': toUnicode("CouchPotato"), |
||||
|
'image': 'https://raw.github.com/RuudBurger/CouchPotatoServer/master/couchpotato/static/images/homescreen.png', |
||||
|
} |
||||
|
|
||||
|
try: |
||||
|
self.urlopen(self.urls['api'] % (self.conf('api_key'), tryUrlencode(data)), show_error = False) |
||||
|
return True |
||||
|
except: |
||||
|
log.error('Toasty failed: %s', traceback.format_exc()) |
||||
|
|
||||
|
return False |
@ -0,0 +1,35 @@ |
|||||
|
from .main import ITunes |
||||
|
|
||||
|
def start(): |
||||
|
return ITunes() |
||||
|
|
||||
|
config = [{ |
||||
|
'name': 'itunes', |
||||
|
'groups': [ |
||||
|
{ |
||||
|
'tab': 'automation', |
||||
|
'name': 'itunes_automation', |
||||
|
'label': 'iTunes', |
||||
|
'description': 'From any <a href="http://itunes.apple.com/rss">iTunes</a> Store feed. Url should be the RSS link. (uses minimal requirements)', |
||||
|
'options': [ |
||||
|
{ |
||||
|
'name': 'automation_enabled', |
||||
|
'default': False, |
||||
|
'type': 'enabler', |
||||
|
}, |
||||
|
{ |
||||
|
'name': 'automation_urls_use', |
||||
|
'label': 'Use', |
||||
|
'default': ',', |
||||
|
}, |
||||
|
{ |
||||
|
'name': 'automation_urls', |
||||
|
'label': 'url', |
||||
|
'type': 'combined', |
||||
|
'combine': ['automation_urls_use', 'automation_urls'], |
||||
|
'default': 'https://itunes.apple.com/rss/topmovies/limit=25/xml,', |
||||
|
}, |
||||
|
], |
||||
|
}, |
||||
|
], |
||||
|
}] |
@ -0,0 +1,63 @@ |
|||||
|
from couchpotato.core.helpers.rss import RSS |
||||
|
from couchpotato.core.helpers.variable import md5, splitString, tryInt |
||||
|
from couchpotato.core.logger import CPLog |
||||
|
from couchpotato.core.providers.automation.base import Automation |
||||
|
from xml.etree.ElementTree import QName |
||||
|
import datetime |
||||
|
import traceback |
||||
|
import xml.etree.ElementTree as XMLTree |
||||
|
|
||||
|
log = CPLog(__name__) |
||||
|
|
||||
|
|
||||
|
class ITunes(Automation, RSS): |
||||
|
|
||||
|
interval = 1800 |
||||
|
|
||||
|
def getIMDBids(self): |
||||
|
|
||||
|
if self.isDisabled(): |
||||
|
return |
||||
|
|
||||
|
movies = [] |
||||
|
|
||||
|
enablers = [tryInt(x) for x in splitString(self.conf('automation_urls_use'))] |
||||
|
urls = splitString(self.conf('automation_urls')) |
||||
|
|
||||
|
namespace = 'http://www.w3.org/2005/Atom' |
||||
|
namespaceIM = 'http://itunes.apple.com/rss' |
||||
|
|
||||
|
index = -1 |
||||
|
for url in urls: |
||||
|
|
||||
|
index += 1 |
||||
|
if not enablers[index]: |
||||
|
continue |
||||
|
|
||||
|
try: |
||||
|
cache_key = 'itunes.rss.%s' % md5(url) |
||||
|
rss_data = self.getCache(cache_key, url) |
||||
|
|
||||
|
data = XMLTree.fromstring(rss_data) |
||||
|
|
||||
|
if data is not None: |
||||
|
entry_tag = str(QName(namespace, 'entry')) |
||||
|
rss_movies = self.getElements(data, entry_tag) |
||||
|
|
||||
|
for movie in rss_movies: |
||||
|
name_tag = str(QName(namespaceIM, 'name')) |
||||
|
name = self.getTextElement(movie, name_tag) |
||||
|
|
||||
|
releaseDate_tag = str(QName(namespaceIM, 'releaseDate')) |
||||
|
releaseDateText = self.getTextElement(movie, releaseDate_tag) |
||||
|
year = datetime.datetime.strptime(releaseDateText, '%Y-%m-%dT00:00:00-07:00').strftime("%Y") |
||||
|
|
||||
|
imdb = self.search(name, year) |
||||
|
|
||||
|
if imdb and self.isMinimalMovie(imdb): |
||||
|
movies.append(imdb['imdb']) |
||||
|
|
||||
|
except: |
||||
|
log.error('Failed loading iTunes rss feed: %s %s', (url, traceback.format_exc())) |
||||
|
|
||||
|
return movies |
@ -0,0 +1,23 @@ |
|||||
|
from .main import Moviemeter |
||||
|
|
||||
|
def start(): |
||||
|
return Moviemeter() |
||||
|
|
||||
|
config = [{ |
||||
|
'name': 'moviemeter', |
||||
|
'groups': [ |
||||
|
{ |
||||
|
'tab': 'automation', |
||||
|
'name': 'moviemeter_automation', |
||||
|
'label': 'Moviemeter', |
||||
|
'description': 'Imports movies from the current top 10 of moviemeter.nl. (uses minimal requirements)', |
||||
|
'options': [ |
||||
|
{ |
||||
|
'name': 'automation_enabled', |
||||
|
'default': False, |
||||
|
'type': 'enabler', |
||||
|
}, |
||||
|
], |
||||
|
}, |
||||
|
], |
||||
|
}] |
@ -0,0 +1,28 @@ |
|||||
|
from couchpotato.core.event import fireEvent |
||||
|
from couchpotato.core.helpers.rss import RSS |
||||
|
from couchpotato.core.logger import CPLog |
||||
|
from couchpotato.core.providers.automation.base import Automation |
||||
|
|
||||
|
log = CPLog(__name__) |
||||
|
|
||||
|
|
||||
|
class Moviemeter(Automation, RSS): |
||||
|
|
||||
|
interval = 1800 |
||||
|
rss_url = 'http://www.moviemeter.nl/rss/cinema' |
||||
|
|
||||
|
def getIMDBids(self): |
||||
|
|
||||
|
movies = [] |
||||
|
|
||||
|
rss_movies = self.getRSSData(self.rss_url) |
||||
|
|
||||
|
for movie in rss_movies: |
||||
|
|
||||
|
name_year = fireEvent('scanner.name_year', self.getTextElement(movie, 'title'), single = True) |
||||
|
imdb = self.search(name_year.get('name'), name_year.get('year')) |
||||
|
|
||||
|
if imdb and self.isMinimalMovie(imdb): |
||||
|
movies.append(imdb['imdb']) |
||||
|
|
||||
|
return movies |
@ -1,6 +0,0 @@ |
|||||
from .main import IMDBAPI |
|
||||
|
|
||||
def start(): |
|
||||
return IMDBAPI() |
|
||||
|
|
||||
config = [] |
|
@ -0,0 +1,6 @@ |
|||||
|
from .main import OMDBAPI |
||||
|
|
||||
|
def start(): |
||||
|
return OMDBAPI() |
||||
|
|
||||
|
config = [] |
Loading…
Reference in new issue