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