Merge branch 'refs/heads/develop'
This commit is contained in:
commit
7f87b255f9
44 files changed
+536
-113
No files matched your search
@@ -70,7 +70,7 @@ config = [{
|
|||||||
'name': 'development',
|
'name': 'development',
|
||||||
'default': 0,
|
'default': 0,
|
||||||
'type': 'bool',
|
'type': 'bool',
|
||||||
'description': 'Disables some checks/downloads for faster reloading.',
|
'description': 'Enable this if you\'re developing, and NOT in any other case, thanks.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'data_dir',
|
'name': 'data_dir',
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from base64 import b32decode, b16encode
|
from base64 import b32decode, b16encode
|
||||||
from couchpotato.core.event import addEvent
|
from couchpotato.core.event import addEvent
|
||||||
|
from couchpotato.core.helpers.variable import mergeDicts
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.base import Provider
|
from couchpotato.core.providers.base import Provider
|
||||||
import random
|
import random
|
||||||
@@ -103,6 +104,12 @@ class Downloader(Provider):
|
|||||||
log.error('Failed converting magnet url to torrent: %s', (torrent_hash))
|
log.error('Failed converting magnet url to torrent: %s', (torrent_hash))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def downloadReturnId(self, download_id):
|
||||||
|
return {
|
||||||
|
'downloader': self.getName(),
|
||||||
|
'id': download_id
|
||||||
|
}
|
||||||
|
|
||||||
def isDisabled(self, manual, data):
|
def isDisabled(self, manual, data):
|
||||||
return not self.isEnabled(manual, data)
|
return not self.isEnabled(manual, data)
|
||||||
|
|
||||||
@@ -116,3 +123,34 @@ class Downloader(Provider):
|
|||||||
return super(Downloader, self).isEnabled() and \
|
return super(Downloader, self).isEnabled() and \
|
||||||
((d_manual and manual) or (d_manual is False)) and \
|
((d_manual and manual) or (d_manual is False)) and \
|
||||||
(not data or self.isCorrectType(data.get('type')))
|
(not data or self.isCorrectType(data.get('type')))
|
||||||
|
|
||||||
|
|
||||||
|
class StatusList(list):
|
||||||
|
|
||||||
|
provider = None
|
||||||
|
|
||||||
|
def __init__(self, provider, **kwargs):
|
||||||
|
|
||||||
|
self.provider = provider
|
||||||
|
self.kwargs = kwargs
|
||||||
|
|
||||||
|
super(StatusList, self).__init__()
|
||||||
|
|
||||||
|
def extend(self, results):
|
||||||
|
for r in results:
|
||||||
|
self.append(r)
|
||||||
|
|
||||||
|
def append(self, result):
|
||||||
|
new_result = self.fillResult(result)
|
||||||
|
super(StatusList, self).append(new_result)
|
||||||
|
|
||||||
|
def fillResult(self, result):
|
||||||
|
|
||||||
|
defaults = {
|
||||||
|
'id': 0,
|
||||||
|
'status': 'busy',
|
||||||
|
'downloader': self.provider.getName(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return mergeDicts(defaults, result)
|
||||||
|
|
||||||
@@ -48,6 +48,12 @@ config = [{
|
|||||||
'advanced': True,
|
'advanced': True,
|
||||||
'description': 'Disable this downloader for automated searches, but use it when I manually send a release.',
|
'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.',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
from base64 import standard_b64encode
|
from base64 import standard_b64encode
|
||||||
from couchpotato.core.downloaders.base import Downloader
|
from couchpotato.core.downloaders.base import Downloader, StatusList
|
||||||
from couchpotato.core.helpers.encoding import ss
|
from couchpotato.core.helpers.encoding import ss
|
||||||
from couchpotato.core.helpers.variable import tryInt
|
from couchpotato.core.helpers.variable import tryInt
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
|
from datetime import timedelta
|
||||||
import re
|
import re
|
||||||
|
import shutil
|
||||||
import socket
|
import socket
|
||||||
import traceback
|
import traceback
|
||||||
import xmlrpclib
|
import xmlrpclib
|
||||||
@@ -50,7 +52,107 @@ class NZBGet(Downloader):
|
|||||||
|
|
||||||
if xml_response:
|
if xml_response:
|
||||||
log.info('NZB sent successfully to NZBGet')
|
log.info('NZB sent successfully to NZBGet')
|
||||||
return True
|
groups = rpc.listgroups()
|
||||||
|
nzb_id = [item['NZBID'] for item in groups if item['NZBFilename'] == nzb_name][0]
|
||||||
|
return self.downloadReturnId(nzb_id)
|
||||||
else:
|
else:
|
||||||
log.error('NZBGet could not add %s to the queue.', nzb_name)
|
log.error('NZBGet could not add %s to the queue.', nzb_name)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def getAllDownloadStatus(self):
|
||||||
|
|
||||||
|
log.debug('Checking NZBGet download status.')
|
||||||
|
|
||||||
|
url = self.url % {'host': self.conf('host'), 'password': self.conf('password')}
|
||||||
|
|
||||||
|
rpc = xmlrpclib.ServerProxy(url)
|
||||||
|
try:
|
||||||
|
if rpc.writelog('INFO', 'CouchPotato connected to check status'):
|
||||||
|
log.info('Successfully connected to NZBGet')
|
||||||
|
else:
|
||||||
|
log.info('Successfully connected to NZBGet, but unable to send a message')
|
||||||
|
except socket.error:
|
||||||
|
log.error('NZBGet is not responding. Please ensure that NZBGet is running and host setting is correct.')
|
||||||
|
return False
|
||||||
|
except xmlrpclib.ProtocolError, e:
|
||||||
|
if e.errcode == 401:
|
||||||
|
log.error('Password is incorrect.')
|
||||||
|
else:
|
||||||
|
log.error('Protocol Error: %s', e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Get NZBGet data
|
||||||
|
try:
|
||||||
|
status = rpc.status()
|
||||||
|
groups = rpc.listgroups()
|
||||||
|
queue = rpc.postqueue(0)
|
||||||
|
history = rpc.history()
|
||||||
|
except:
|
||||||
|
log.error('Failed getting data: %s', traceback.format_exc(1))
|
||||||
|
return False
|
||||||
|
|
||||||
|
statuses = StatusList(self)
|
||||||
|
|
||||||
|
for item in groups:
|
||||||
|
log.debug('Found %s in NZBGet download queue', item['NZBFilename'])
|
||||||
|
statuses.append({
|
||||||
|
'id': item['NZBID'],
|
||||||
|
'name': item['NZBFilename'],
|
||||||
|
'original_status': 'DOWNLOADING' if item['ActiveDownloads'] > 0 else 'QUEUED',
|
||||||
|
# Seems to have no native API function for time left. This will return the time left after NZBGet started downloading this item
|
||||||
|
'timeleft': str(timedelta(seconds = item['RemainingSizeMB'] / status['DownloadRate'] * 2 ^ 20)) if item['ActiveDownloads'] > 0 and not (status['DownloadPaused'] or status['Download2Paused']) else -1,
|
||||||
|
})
|
||||||
|
|
||||||
|
for item in queue:
|
||||||
|
log.debug('Found %s in NZBGet postprocessing queue', item['NZBFilename'])
|
||||||
|
statuses.append({
|
||||||
|
'id': item['NZBID'],
|
||||||
|
'name': item['NZBFilename'],
|
||||||
|
'original_status': item['Stage'],
|
||||||
|
'timeleft': str(timedelta(seconds = 0)) if not status['PostPaused'] else -1,
|
||||||
|
})
|
||||||
|
|
||||||
|
for item in history:
|
||||||
|
log.debug('Found %s in NZBGet history. ParStatus: %s, ScriptStatus: %s, Log: %s', (item['NZBFilename'] , item['ParStatus'], item['ScriptStatus'] , item['Log']))
|
||||||
|
statuses.append({
|
||||||
|
'id': item['NZBID'],
|
||||||
|
'name': item['NZBFilename'],
|
||||||
|
'status': 'completed' if item['ParStatus'] == 'SUCCESS' and item['ScriptStatus'] == 'SUCCESS' else 'failed',
|
||||||
|
'original_status': item['ParStatus'] + ', ' + item['ScriptStatus'],
|
||||||
|
'timeleft': str(timedelta(seconds = 0)),
|
||||||
|
})
|
||||||
|
|
||||||
|
return statuses
|
||||||
|
|
||||||
|
def removeFailed(self, item):
|
||||||
|
|
||||||
|
log.info('%s failed downloading, deleting...', item['name'])
|
||||||
|
|
||||||
|
url = self.url % {'host': self.conf('host'), 'password': self.conf('password')}
|
||||||
|
|
||||||
|
rpc = xmlrpclib.ServerProxy(url)
|
||||||
|
try:
|
||||||
|
if rpc.writelog('INFO', 'CouchPotato connected to delete some history'):
|
||||||
|
log.info('Successfully connected to NZBGet')
|
||||||
|
else:
|
||||||
|
log.info('Successfully connected to NZBGet, but unable to send a message')
|
||||||
|
except socket.error:
|
||||||
|
log.error('NZBGet is not responding. Please ensure that NZBGet is running and host setting is correct.')
|
||||||
|
return False
|
||||||
|
except xmlrpclib.ProtocolError, e:
|
||||||
|
if e.errcode == 401:
|
||||||
|
log.error('Password is incorrect.')
|
||||||
|
else:
|
||||||
|
log.error('Protocol Error: %s', e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
history = rpc.history()
|
||||||
|
if rpc.editqueue('HistoryDelete', 0, "", [tryInt(item['id'])]):
|
||||||
|
path = [hist['DestDir'] for hist in history if hist['NZBID'] == item['id']][0]
|
||||||
|
shutil.rmtree(path, True)
|
||||||
|
except:
|
||||||
|
log.error('Failed deleting: %s', traceback.format_exc(0))
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
from couchpotato.core.downloaders.base import Downloader
|
from couchpotato.core.downloaders.base import Downloader, StatusList
|
||||||
from couchpotato.core.helpers.encoding import tryUrlencode, ss
|
from couchpotato.core.helpers.encoding import tryUrlencode, ss
|
||||||
from couchpotato.core.helpers.variable import cleanHost
|
from couchpotato.core.helpers.variable import cleanHost
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
@@ -29,7 +29,9 @@ class NZBVortex(Downloader):
|
|||||||
nzb_filename = self.createFileName(data, filedata, movie)
|
nzb_filename = self.createFileName(data, filedata, movie)
|
||||||
self.call('nzb/add', params = {'file': (ss(nzb_filename), filedata)}, multipart = True)
|
self.call('nzb/add', params = {'file': (ss(nzb_filename), filedata)}, multipart = True)
|
||||||
|
|
||||||
return True
|
raw_statuses = self.call('nzb')
|
||||||
|
nzb_id = [item['id'] for item in raw_statuses.get('nzbs', []) if item['name'] == nzb_filename][0]
|
||||||
|
return self.downloadReturnId(nzb_id)
|
||||||
except:
|
except:
|
||||||
log.error('Something went wrong sending the NZB file: %s', traceback.format_exc())
|
log.error('Something went wrong sending the NZB file: %s', traceback.format_exc())
|
||||||
return False
|
return False
|
||||||
@@ -38,7 +40,7 @@ class NZBVortex(Downloader):
|
|||||||
|
|
||||||
raw_statuses = self.call('nzb')
|
raw_statuses = self.call('nzb')
|
||||||
|
|
||||||
statuses = []
|
statuses = StatusList(self)
|
||||||
for item in raw_statuses.get('nzbs', []):
|
for item in raw_statuses.get('nzbs', []):
|
||||||
|
|
||||||
# Check status
|
# Check status
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from couchpotato.core.downloaders.base import Downloader
|
from couchpotato.core.downloaders.base import Downloader, StatusList
|
||||||
from couchpotato.core.helpers.encoding import tryUrlencode, ss
|
from couchpotato.core.helpers.encoding import tryUrlencode, ss
|
||||||
from couchpotato.core.helpers.variable import cleanHost, mergeDicts
|
from couchpotato.core.helpers.variable import cleanHost, mergeDicts
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
@@ -17,8 +17,7 @@ class Sabnzbd(Downloader):
|
|||||||
|
|
||||||
log.info('Sending "%s" to SABnzbd.', data.get('name'))
|
log.info('Sending "%s" to SABnzbd.', data.get('name'))
|
||||||
|
|
||||||
params = {
|
req_params = {
|
||||||
'apikey': self.conf('api_key'),
|
|
||||||
'cat': self.conf('category'),
|
'cat': self.conf('category'),
|
||||||
'mode': 'addurl',
|
'mode': 'addurl',
|
||||||
'nzbname': self.createNzbName(data, movie),
|
'nzbname': self.createNzbName(data, movie),
|
||||||
@@ -31,17 +30,15 @@ class Sabnzbd(Downloader):
|
|||||||
|
|
||||||
# If it's a .rar, it adds the .rar extension, otherwise it stays .nzb
|
# If it's a .rar, it adds the .rar extension, otherwise it stays .nzb
|
||||||
nzb_filename = self.createFileName(data, filedata, movie)
|
nzb_filename = self.createFileName(data, filedata, movie)
|
||||||
params['mode'] = 'addfile'
|
req_params['mode'] = 'addfile'
|
||||||
else:
|
else:
|
||||||
params['name'] = data.get('url')
|
req_params['name'] = data.get('url')
|
||||||
|
|
||||||
url = cleanHost(self.conf('host')) + 'api?' + tryUrlencode(params)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if params.get('mode') is 'addfile':
|
if req_params.get('mode') is 'addfile':
|
||||||
sab = self.urlopen(url, timeout = 60, params = {'nzbfile': (ss(nzb_filename), filedata)}, multipart = True, show_error = False, headers = {'User-Agent': Env.getIdentifier()})
|
sab_data = self.call(req_params, params = {'nzbfile': (ss(nzb_filename), filedata)}, multipart = True)
|
||||||
else:
|
else:
|
||||||
sab = self.urlopen(url, timeout = 60, show_error = False, headers = {'User-Agent': Env.getIdentifier()})
|
sab_data = self.call(req_params)
|
||||||
except URLError:
|
except URLError:
|
||||||
log.error('Failed sending release, probably wrong HOST: %s', traceback.format_exc(0))
|
log.error('Failed sending release, probably wrong HOST: %s', traceback.format_exc(0))
|
||||||
return False
|
return False
|
||||||
@@ -49,17 +46,19 @@ class Sabnzbd(Downloader):
|
|||||||
log.error('Failed sending release, use API key, NOT the NZB key: %s', traceback.format_exc(0))
|
log.error('Failed sending release, use API key, NOT the NZB key: %s', traceback.format_exc(0))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
result = sab.strip()
|
if sab_data.get('error'):
|
||||||
if not result:
|
log.error('Error getting data from SABNZBd: %s', sab_data.get('error'))
|
||||||
log.error('SABnzbd didn\'t return anything.')
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
log.debug('Result text from SAB: %s', result[:40])
|
log.debug('Result from SAB: %s', sab_data)
|
||||||
if result[:2] == 'ok':
|
if sab_data.get('status'):
|
||||||
log.info('NZB sent to SAB successfully.')
|
log.info('NZB sent to SAB successfully.')
|
||||||
return True
|
if filedata:
|
||||||
|
return self.downloadReturnId(sab_data.get('nzo_ids')[0])
|
||||||
|
else:
|
||||||
|
return True
|
||||||
else:
|
else:
|
||||||
log.error(result[:40])
|
log.error(sab_data)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def getAllDownloadStatus(self):
|
def getAllDownloadStatus(self):
|
||||||
@@ -85,14 +84,13 @@ class Sabnzbd(Downloader):
|
|||||||
log.error('Failed getting history json: %s', traceback.format_exc(1))
|
log.error('Failed getting history json: %s', traceback.format_exc(1))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
statuses = []
|
statuses = StatusList(self)
|
||||||
|
|
||||||
# Get busy releases
|
# Get busy releases
|
||||||
for item in queue.get('slots', []):
|
for item in queue.get('slots', []):
|
||||||
statuses.append({
|
statuses.append({
|
||||||
'id': item['nzo_id'],
|
'id': item['nzo_id'],
|
||||||
'name': item['filename'],
|
'name': item['filename'],
|
||||||
'status': 'busy',
|
|
||||||
'original_status': item['status'],
|
'original_status': item['status'],
|
||||||
'timeleft': item['timeleft'] if not queue['paused'] else -1,
|
'timeleft': item['timeleft'] if not queue['paused'] else -1,
|
||||||
})
|
})
|
||||||
@@ -133,21 +131,21 @@ class Sabnzbd(Downloader):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def call(self, params, use_json = True):
|
def call(self, request_params, use_json = True, **kwargs):
|
||||||
|
|
||||||
url = cleanHost(self.conf('host')) + 'api?' + tryUrlencode(mergeDicts(params, {
|
url = cleanHost(self.conf('host')) + 'api?' + tryUrlencode(mergeDicts(request_params, {
|
||||||
'apikey': self.conf('api_key'),
|
'apikey': self.conf('api_key'),
|
||||||
'output': 'json'
|
'output': 'json'
|
||||||
}))
|
}))
|
||||||
|
|
||||||
data = self.urlopen(url, timeout = 60, show_error = False, headers = {'User-Agent': Env.getIdentifier()})
|
data = self.urlopen(url, timeout = 60, show_error = False, headers = {'User-Agent': Env.getIdentifier()}, **kwargs)
|
||||||
if use_json:
|
if use_json:
|
||||||
d = json.loads(data)
|
d = json.loads(data)
|
||||||
if d.get('error'):
|
if d.get('error'):
|
||||||
log.error('Error getting data from SABNZBd: %s', d.get('error'))
|
log.error('Error getting data from SABNZBd: %s', d.get('error'))
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
return d[params['mode']]
|
return d.get(request_params['mode']) or d
|
||||||
else:
|
else:
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ class Transmission(Downloader):
|
|||||||
if torrent_params:
|
if torrent_params:
|
||||||
trpc.set_torrent(remote_torrent['torrent-added']['hashString'], torrent_params)
|
trpc.set_torrent(remote_torrent['torrent-added']['hashString'], torrent_params)
|
||||||
|
|
||||||
return True
|
return self.downloadReturnId(remote_torrent['torrent-added']['hashString'])
|
||||||
except Exception, err:
|
except Exception, err:
|
||||||
log.error('Failed to change settings for transfer: %s', err)
|
log.error('Failed to change settings for transfer: %s', err)
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from base64 import b16encode, b32decode
|
from base64 import b16encode, b32decode
|
||||||
from bencode import bencode, bdecode
|
from bencode import bencode, bdecode
|
||||||
from couchpotato.core.downloaders.base import Downloader
|
from couchpotato.core.downloaders.base import Downloader, StatusList
|
||||||
from couchpotato.core.helpers.encoding import isInt, ss
|
from couchpotato.core.helpers.encoding import isInt, ss
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from hashlib import sha1
|
from hashlib import sha1
|
||||||
@@ -66,7 +66,7 @@ class uTorrent(Downloader):
|
|||||||
self.utorrent_api.set_torrent(torrent_hash, torrent_params)
|
self.utorrent_api.set_torrent(torrent_hash, torrent_params)
|
||||||
if self.conf('paused', default = 0):
|
if self.conf('paused', default = 0):
|
||||||
self.utorrent_api.pause_torrent(torrent_hash)
|
self.utorrent_api.pause_torrent(torrent_hash)
|
||||||
return True
|
return self.downloadReturnId(torrent_hash)
|
||||||
except Exception, err:
|
except Exception, err:
|
||||||
log.error('Failed to send torrent to uTorrent: %s', err)
|
log.error('Failed to send torrent to uTorrent: %s', err)
|
||||||
return False
|
return False
|
||||||
@@ -103,7 +103,7 @@ class uTorrent(Downloader):
|
|||||||
log.debug('Nothing in queue')
|
log.debug('Nothing in queue')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
statuses = []
|
statuses = StatusList(self)
|
||||||
|
|
||||||
# Get torrents
|
# Get torrents
|
||||||
for item in queue.get('torrents', []):
|
for item in queue.get('torrents', []):
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ def fireEvent(name, *args, **kwargs):
|
|||||||
if isinstance(results[0], dict):
|
if isinstance(results[0], dict):
|
||||||
merged = {}
|
merged = {}
|
||||||
for result in results:
|
for result in results:
|
||||||
merged = mergeDicts(merged, result)
|
merged = mergeDicts(merged, result, prepend_list = True)
|
||||||
|
|
||||||
results = merged
|
results = merged
|
||||||
# Lists
|
# Lists
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ def getDataDir():
|
|||||||
def isDict(object):
|
def isDict(object):
|
||||||
return isinstance(object, dict)
|
return isinstance(object, dict)
|
||||||
|
|
||||||
def mergeDicts(a, b):
|
def mergeDicts(a, b, prepend_list = False):
|
||||||
assert isDict(a), isDict(b)
|
assert isDict(a), isDict(b)
|
||||||
dst = a.copy()
|
dst = a.copy()
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ def mergeDicts(a, b):
|
|||||||
if isDict(current_src[key]) and isDict(current_dst[key]):
|
if isDict(current_src[key]) and isDict(current_dst[key]):
|
||||||
stack.append((current_dst[key], current_src[key]))
|
stack.append((current_dst[key], current_src[key]))
|
||||||
elif isinstance(current_src[key], list) and isinstance(current_dst[key], list):
|
elif isinstance(current_src[key], list) and isinstance(current_dst[key], list):
|
||||||
current_dst[key].extend(current_src[key])
|
current_dst[key] = current_src[key] + current_dst[key] if prepend_list else current_dst[key] + current_src[key]
|
||||||
current_dst[key] = removeListDuplicates(current_dst[key])
|
current_dst[key] = removeListDuplicates(current_dst[key])
|
||||||
else:
|
else:
|
||||||
current_dst[key] = current_src[key]
|
current_dst[key] = current_src[key]
|
||||||
|
|||||||
@@ -2,13 +2,15 @@ from couchpotato.api import addApiView
|
|||||||
from couchpotato.core.event import addEvent
|
from couchpotato.core.event import addEvent
|
||||||
from couchpotato.core.helpers.request import jsonified
|
from couchpotato.core.helpers.request import jsonified
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.providers.base import Provider
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Notification(Plugin):
|
class Notification(Provider):
|
||||||
|
|
||||||
|
type = 'notification'
|
||||||
|
|
||||||
default_title = Env.get('appname')
|
default_title = Env.get('appname')
|
||||||
test_message = 'ZOMG Lazors Pewpewpew!'
|
test_message = 'ZOMG Lazors Pewpewpew!'
|
||||||
@@ -20,7 +22,7 @@ class Notification(Plugin):
|
|||||||
dont_listen_to = []
|
dont_listen_to = []
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
addEvent('notify.%s' % self.getName().lower(), self.notify)
|
addEvent('notify.%s' % self.getName().lower(), self._notify)
|
||||||
|
|
||||||
addApiView(self.testNotifyName(), self.test)
|
addApiView(self.testNotifyName(), self.test)
|
||||||
|
|
||||||
@@ -33,13 +35,17 @@ class Notification(Plugin):
|
|||||||
def notify(message = None, group = {}, data = None):
|
def notify(message = None, group = {}, data = None):
|
||||||
if not self.conf('on_snatch', default = True) and listener == 'movie.snatched':
|
if not self.conf('on_snatch', default = True) and listener == 'movie.snatched':
|
||||||
return
|
return
|
||||||
return self.notify(message = message, data = data if data else group, listener = listener)
|
return self._notify(message = message, data = data if data else group, listener = listener)
|
||||||
|
|
||||||
return notify
|
return notify
|
||||||
|
|
||||||
def getNotificationImage(self, size = 'small'):
|
def getNotificationImage(self, size = 'small'):
|
||||||
return 'https://raw.github.com/RuudBurger/CouchPotatoServer/master/couchpotato/static/images/notify.couch.%s.png' % size
|
return 'https://raw.github.com/RuudBurger/CouchPotatoServer/master/couchpotato/static/images/notify.couch.%s.png' % size
|
||||||
|
|
||||||
|
def _notify(self, *args, **kwargs):
|
||||||
|
if self.isEnabled():
|
||||||
|
self.notify(*args, **kwargs)
|
||||||
|
|
||||||
def notify(self, message = '', data = {}, listener = None):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -49,7 +55,7 @@ class Notification(Plugin):
|
|||||||
|
|
||||||
log.info('Sending test to %s', test_type)
|
log.info('Sending test to %s', test_type)
|
||||||
|
|
||||||
success = self.notify(
|
success = self._notify(
|
||||||
message = self.test_message,
|
message = self.test_message,
|
||||||
data = {},
|
data = {},
|
||||||
listener = 'test'
|
listener = 'test'
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ class Boxcar(Notification):
|
|||||||
url = 'https://boxcar.io/devices/providers/7MNNXY3UIzVBwvzkKwkC/notifications'
|
url = 'https://boxcar.io/devices/providers/7MNNXY3UIzVBwvzkKwkC/notifications'
|
||||||
|
|
||||||
def notify(self, message = '', data = {}, listener = None):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
message = message.strip()
|
message = message.strip()
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ log = CPLog(__name__)
|
|||||||
class Email(Notification):
|
class Email(Notification):
|
||||||
|
|
||||||
def notify(self, message = '', data = {}, listener = None):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
|
||||||
|
|
||||||
# Extract all the settings from settings
|
# Extract all the settings from settings
|
||||||
from_address = self.conf('from')
|
from_address = self.conf('from')
|
||||||
@@ -50,6 +49,5 @@ class Email(Notification):
|
|||||||
return True
|
return True
|
||||||
except:
|
except:
|
||||||
log.error('E-mail failed: %s', traceback.format_exc())
|
log.error('E-mail failed: %s', traceback.format_exc())
|
||||||
return False
|
|
||||||
|
|
||||||
return False
|
return False
|
||||||
@@ -44,7 +44,6 @@ class Growl(Notification):
|
|||||||
log.error('Failed register of growl: %s', traceback.format_exc())
|
log.error('Failed register of growl: %s', traceback.format_exc())
|
||||||
|
|
||||||
def notify(self, message = '', data = {}, listener = None):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
|
||||||
|
|
||||||
self.register()
|
self.register()
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ class Notifo(Notification):
|
|||||||
url = 'https://api.notifo.com/v1/send_notification'
|
url = 'https://api.notifo.com/v1/send_notification'
|
||||||
|
|
||||||
def notify(self, message = '', data = {}, listener = None):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
params = {
|
params = {
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ log = CPLog(__name__)
|
|||||||
class NotifyMyAndroid(Notification):
|
class NotifyMyAndroid(Notification):
|
||||||
|
|
||||||
def notify(self, message = '', data = {}, listener = None):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
|
||||||
|
|
||||||
nma = pynma.PyNMA()
|
nma = pynma.PyNMA()
|
||||||
keys = splitString(self.conf('api_key'))
|
keys = splitString(self.conf('api_key'))
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ log = CPLog(__name__)
|
|||||||
class NotifyMyWP(Notification):
|
class NotifyMyWP(Notification):
|
||||||
|
|
||||||
def notify(self, message = '', data = {}, listener = None):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
|
||||||
|
|
||||||
keys = splitString(self.conf('api_key'))
|
keys = splitString(self.conf('api_key'))
|
||||||
p = PyNMWP(keys, self.conf('dev_key'))
|
p = PyNMWP(keys, self.conf('dev_key'))
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ class Plex(Notification):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def notify(self, message = '', data = {}, listener = None):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
|
||||||
|
|
||||||
hosts = [x.strip() + ':3000' for x in self.conf('host').split(",")]
|
hosts = [x.strip() + ':3000' for x in self.conf('host').split(",")]
|
||||||
successful = 0
|
successful = 0
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ class Prowl(Notification):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def notify(self, message = '', data = {}, listener = None):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'apikey': self.conf('api_key'),
|
'apikey': self.conf('api_key'),
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ class Pushalot(Notification):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def notify(self, message = '', data = {}, listener = None):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'AuthorizationToken': self.conf('auth_token'),
|
'AuthorizationToken': self.conf('auth_token'),
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ class Pushover(Notification):
|
|||||||
app_token = 'YkxHMYDZp285L265L3IwH3LmzkTaCy'
|
app_token = 'YkxHMYDZp285L265L3IwH3LmzkTaCy'
|
||||||
|
|
||||||
def notify(self, message = '', data = {}, listener = None):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
|
||||||
|
|
||||||
http_handler = HTTPSConnection("api.pushover.net:443")
|
http_handler = HTTPSConnection("api.pushover.net:443")
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ class Toasty(Notification):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def notify(self, message = '', data = {}, listener = None):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'title': self.default_title,
|
'title': self.default_title,
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
from .main import Trakt
|
||||||
|
|
||||||
|
def start():
|
||||||
|
return Trakt()
|
||||||
|
|
||||||
|
config = [{
|
||||||
|
'name': 'trakt',
|
||||||
|
'groups': [
|
||||||
|
{
|
||||||
|
'tab': 'notifications',
|
||||||
|
'list': 'notification_providers',
|
||||||
|
'name': 'trakt',
|
||||||
|
'label': 'Trakt',
|
||||||
|
'description': 'add movies to your collection once downloaded. Fill in your username and password in the <a href="../automation/">Automation Trakt settings</a>',
|
||||||
|
'options': [
|
||||||
|
{
|
||||||
|
'name': 'notification_enabled',
|
||||||
|
'default': False,
|
||||||
|
'type': 'enabler',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'remove_watchlist_enabled',
|
||||||
|
'label': 'Remove from watchlist',
|
||||||
|
'default': False,
|
||||||
|
'type': 'bool',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}]
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
from couchpotato.core.logger import CPLog
|
||||||
|
from couchpotato.core.notifications.base import Notification
|
||||||
|
|
||||||
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
class Trakt(Notification):
|
||||||
|
|
||||||
|
urls = {
|
||||||
|
'base': 'http://api.trakt.tv/%s',
|
||||||
|
'library': 'movie/library/%s',
|
||||||
|
'unwatchlist': 'movie/unwatchlist/%s',
|
||||||
|
}
|
||||||
|
|
||||||
|
listen_to = ['movie.downloaded']
|
||||||
|
|
||||||
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
|
|
||||||
|
post_data = {
|
||||||
|
'username': self.conf('automation_username'),
|
||||||
|
'password' : self.conf('automation_password'),
|
||||||
|
'movies': [{
|
||||||
|
'imdb_id': data['library']['identifier'],
|
||||||
|
'title': data['library']['titles'][0]['title'],
|
||||||
|
'year': data['library']['year']
|
||||||
|
}] if data else []
|
||||||
|
}
|
||||||
|
|
||||||
|
result = self.call((self.urls['library'] % self.conf('automation_api_key')), post_data)
|
||||||
|
if self.conf('remove_watchlist_enabled'):
|
||||||
|
result = result and self.call((self.urls['unwatchlist'] % self.conf('automation_api_key')), post_data)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def call(self, method_url, post_data):
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = self.getJsonData(self.urls['base'] % method_url, params = post_data, cache_timeout = 1)
|
||||||
|
if response:
|
||||||
|
if response.get('status') == "success":
|
||||||
|
log.info('Successfully called Trakt')
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
log.error('Failed to call trakt, check your login.')
|
||||||
|
return False
|
||||||
@@ -32,7 +32,6 @@ class Twitter(Notification):
|
|||||||
addApiView('notify.%s.credentials' % self.getName().lower(), self.getCredentials)
|
addApiView('notify.%s.credentials' % self.getName().lower(), self.getCredentials)
|
||||||
|
|
||||||
def notify(self, message = '', data = {}, listener = None):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
|
||||||
|
|
||||||
api = Api(self.consumer_key, self.consumer_secret, self.conf('access_token_key'), self.conf('access_token_secret'))
|
api = Api(self.consumer_key, self.consumer_secret, self.conf('access_token_key'), self.conf('access_token_secret'))
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ class XBMC(Notification):
|
|||||||
use_json_notifications = {}
|
use_json_notifications = {}
|
||||||
|
|
||||||
def notify(self, message = '', data = {}, listener = None):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
|
||||||
|
|
||||||
hosts = splitString(self.conf('host'))
|
hosts = splitString(self.conf('host'))
|
||||||
|
|
||||||
|
|||||||
@@ -370,6 +370,7 @@ class MoviePlugin(Plugin):
|
|||||||
status_active = fireEvent('status.add', 'active', single = True)
|
status_active = fireEvent('status.add', 'active', single = True)
|
||||||
snatched_status = fireEvent('status.add', 'snatched', single = True)
|
snatched_status = fireEvent('status.add', 'snatched', single = True)
|
||||||
ignored_status = fireEvent('status.add', 'ignored', single = True)
|
ignored_status = fireEvent('status.add', 'ignored', single = True)
|
||||||
|
done_status = fireEvent('status.add', 'done', single = True)
|
||||||
downloaded_status = fireEvent('status.add', 'downloaded', single = True)
|
downloaded_status = fireEvent('status.add', 'downloaded', single = True)
|
||||||
|
|
||||||
default_profile = fireEvent('profile.default', single = True)
|
default_profile = fireEvent('profile.default', single = True)
|
||||||
@@ -397,7 +398,7 @@ class MoviePlugin(Plugin):
|
|||||||
|
|
||||||
# Clean snatched history
|
# Clean snatched history
|
||||||
for release in m.releases:
|
for release in m.releases:
|
||||||
if release.status_id in [downloaded_status.get('id'), snatched_status.get('id')]:
|
if release.status_id in [downloaded_status.get('id'), snatched_status.get('id'), done_status.get('id')]:
|
||||||
if params.get('ignore_previous', False):
|
if params.get('ignore_previous', False):
|
||||||
release.status_id = ignored_status.get('id')
|
release.status_id = ignored_status.get('id')
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -94,36 +94,30 @@ MA.Release = new Class({
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
||||||
var buttons_done = false;
|
var releases = self.movie.data.releases.sortBy('-info.score');
|
||||||
|
|
||||||
self.movie.data.releases.sortBy('-info.score').each(function(release){
|
for(x in releases){
|
||||||
if(buttons_done) return;
|
var release = releases[x],
|
||||||
|
status = Status.get(release.status_id);
|
||||||
var status = Status.get(release.status_id);
|
|
||||||
|
|
||||||
if((self.next_release && (status.identifier == 'ignored' || status.identifier == 'failed')) || (!self.next_release && status.identifier == 'available')){
|
if((self.next_release && (status.identifier == 'ignored' || status.identifier == 'failed')) || (!self.next_release && status.identifier == 'available')){
|
||||||
self.hide_on_click = false;
|
self.showHelper();
|
||||||
self.show();
|
break;
|
||||||
buttons_done = true;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
show: function(e){
|
createReleases: function(){
|
||||||
var self = this;
|
var self = this;
|
||||||
if(e)
|
|
||||||
(e).preventDefault();
|
|
||||||
|
|
||||||
if(!self.options_container){
|
if(!self.options_container){
|
||||||
self.options_container = new Element('div.options').adopt(
|
self.options_container = new Element('div.options').adopt(
|
||||||
self.release_container = new Element('div.releases.table').adopt(
|
self.release_container = new Element('div.releases.table').adopt(
|
||||||
self.trynext_container = new Element('div.buttons.try_container')
|
self.trynext_container = new Element('div.buttons.try_container')
|
||||||
)
|
)
|
||||||
).inject(self.movie, 'top');
|
);
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
new Element('div.item.head').adopt(
|
new Element('div.item.head').adopt(
|
||||||
@@ -238,9 +232,71 @@ MA.Release = new Class({
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
show: function(e){
|
||||||
|
var self = this;
|
||||||
|
if(e)
|
||||||
|
(e).preventDefault();
|
||||||
|
|
||||||
|
self.createReleases();
|
||||||
|
self.options_container.inject(self.movie, 'top');
|
||||||
self.movie.slide('in', self.options_container);
|
self.movie.slide('in', self.options_container);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
showHelper: function(e){
|
||||||
|
var self = this;
|
||||||
|
if(e)
|
||||||
|
(e).preventDefault();
|
||||||
|
|
||||||
|
self.createReleases();
|
||||||
|
self.trynext_container = new Element('div.buttons.trynext').inject(self.movie.info_container);
|
||||||
|
|
||||||
|
if(self.next_release || self.last_release){
|
||||||
|
|
||||||
|
self.trynext_container.adopt(
|
||||||
|
self.next_release ? [new Element('a.icon.readd', {
|
||||||
|
'text': self.last_release ? 'Download another release' : 'Download the best release',
|
||||||
|
'events': {
|
||||||
|
'click': self.tryNextRelease.bind(self)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
new Element('a.icon.download', {
|
||||||
|
'text': 'pick one yourself',
|
||||||
|
'events': {
|
||||||
|
'click': function(){
|
||||||
|
self.movie.quality.fireEvent('click');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})] : null,
|
||||||
|
new Element('a.icon.completed', {
|
||||||
|
'text': 'mark this movie done',
|
||||||
|
'events': {
|
||||||
|
'click': function(){
|
||||||
|
Api.request('movie.delete', {
|
||||||
|
'data': {
|
||||||
|
'id': self.movie.get('id'),
|
||||||
|
'delete_from': 'wanted'
|
||||||
|
},
|
||||||
|
'onComplete': function(){
|
||||||
|
var movie = $(self.movie);
|
||||||
|
movie.set('tween', {
|
||||||
|
'duration': 300,
|
||||||
|
'onComplete': function(){
|
||||||
|
self.movie.destroy()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
movie.tween('height', 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
get: function(release, type){
|
get: function(release, type){
|
||||||
return release.info[type] || 'n/a'
|
return release.info[type] || 'n/a'
|
||||||
},
|
},
|
||||||
@@ -251,14 +307,15 @@ MA.Release = new Class({
|
|||||||
var release_el = self.release_container.getElement('#release_'+release.id),
|
var release_el = self.release_container.getElement('#release_'+release.id),
|
||||||
icon = release_el.getElement('.download.icon');
|
icon = release_el.getElement('.download.icon');
|
||||||
|
|
||||||
icon.addClass('spinner');
|
self.movie.busy(true);
|
||||||
|
|
||||||
Api.request('release.download', {
|
Api.request('release.download', {
|
||||||
'data': {
|
'data': {
|
||||||
'id': release.id
|
'id': release.id
|
||||||
},
|
},
|
||||||
'onComplete': function(json){
|
'onComplete': function(json){
|
||||||
icon.removeClass('spinner')
|
self.movie.busy(false);
|
||||||
|
|
||||||
if(json.success)
|
if(json.success)
|
||||||
icon.addClass('completed');
|
icon.addClass('completed');
|
||||||
else
|
else
|
||||||
@@ -281,6 +338,8 @@ MA.Release = new Class({
|
|||||||
tryNextRelease: function(movie_id){
|
tryNextRelease: function(movie_id){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
self.createReleases();
|
||||||
|
|
||||||
if(self.last_release)
|
if(self.last_release)
|
||||||
self.ignore(self.last_release);
|
self.ignore(self.last_release);
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 180px;
|
height: 180px;
|
||||||
transition: all 0.2s linear;
|
transition: all 0.6s cubic-bezier(0.9,0,0.1,1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.movies.list_list .movie:not(.details_view),
|
.movies.list_list .movie:not(.details_view),
|
||||||
@@ -95,7 +95,7 @@
|
|||||||
width: 938px;
|
width: 938px;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
border: 0;
|
border: 0;
|
||||||
background: none;
|
background: #4e5969;
|
||||||
}
|
}
|
||||||
|
|
||||||
.movies.thumbs_list .data {
|
.movies.thumbs_list .data {
|
||||||
@@ -322,8 +322,8 @@
|
|||||||
right: 10px;
|
right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.movies .data:hover .action { opacity: 0.6; }
|
.movies .movie:hover .action { opacity: 0.6; }
|
||||||
.movies .data:hover .action:hover { opacity: 1; }
|
.movies .movie:hover .action:hover { opacity: 1; }
|
||||||
.movies.mass_edit_list .data .actions {
|
.movies.mass_edit_list .data .actions {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -338,8 +338,8 @@
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.movies.list_list .movie:not(.details_view) .data:hover .actions,
|
.movies.list_list .movie:not(.details_view):hover .actions,
|
||||||
.movies.mass_edit_list .data:hover .actions {
|
.movies.mass_edit_list .movie:hover .actions {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
background: #4e5969;
|
background: #4e5969;
|
||||||
top: 2px;
|
top: 2px;
|
||||||
@@ -510,6 +510,29 @@
|
|||||||
.movies .movie .releases .last_release > :first-child {
|
.movies .movie .releases .last_release > :first-child {
|
||||||
margin-left: -6px;
|
margin-left: -6px;
|
||||||
}
|
}
|
||||||
|
.movies .movie .trynext {
|
||||||
|
display: inline;
|
||||||
|
position: absolute;
|
||||||
|
right: 135px;
|
||||||
|
z-index: 2;
|
||||||
|
opacity: 0;
|
||||||
|
text-shadow: none;
|
||||||
|
background: #4e5969;
|
||||||
|
}
|
||||||
|
.movies .movie:hover .trynext {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies .movie .trynext a {
|
||||||
|
background-position: 5px center;
|
||||||
|
padding: 0 5px 0 25px;
|
||||||
|
margin-right: 10px;
|
||||||
|
color: #FFF;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
.movies .movie .trynext a:hover {
|
||||||
|
background-color: #369545;
|
||||||
|
}
|
||||||
|
|
||||||
.movies .load_more {
|
.movies .load_more {
|
||||||
display: block;
|
display: block;
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ class QualityPlugin(Plugin):
|
|||||||
|
|
||||||
qualities = [
|
qualities = [
|
||||||
{'identifier': 'bd50', 'hd': True, 'size': (15000, 60000), 'label': 'BR-Disk', 'alternative': ['bd25'], 'allow': ['1080p'], 'ext':[], 'tags': ['bdmv', 'certificate', ('complete', 'bluray')]},
|
{'identifier': 'bd50', 'hd': True, 'size': (15000, 60000), 'label': 'BR-Disk', 'alternative': ['bd25'], 'allow': ['1080p'], 'ext':[], 'tags': ['bdmv', 'certificate', ('complete', 'bluray')]},
|
||||||
{'identifier': '1080p', 'hd': True, 'size': (5000, 20000), 'label': '1080P', 'width': 1920, 'height': 1080, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts'], 'tags': ['m2ts']},
|
{'identifier': '1080p', 'hd': True, 'size': (5000, 20000), 'label': '1080p', 'width': 1920, 'height': 1080, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts'], 'tags': ['m2ts']},
|
||||||
{'identifier': '720p', 'hd': True, 'size': (3500, 10000), 'label': '720P', 'width': 1280, 'height': 720, 'alternative': [], 'allow': [], 'ext':['mkv', 'ts']},
|
{'identifier': '720p', 'hd': True, 'size': (3500, 10000), 'label': '720p', 'width': 1280, 'height': 720, 'alternative': [], 'allow': [], 'ext':['mkv', 'ts']},
|
||||||
{'identifier': 'brrip', 'hd': True, 'size': (700, 7000), 'label': 'BR-Rip', 'alternative': ['bdrip'], 'allow': ['720p', '1080p'], 'ext':['avi']},
|
{'identifier': 'brrip', 'hd': True, 'size': (700, 7000), 'label': 'BR-Rip', 'alternative': ['bdrip'], 'allow': ['720p', '1080p'], 'ext':['avi']},
|
||||||
{'identifier': 'dvdr', 'size': (3000, 10000), 'label': 'DVD-R', 'alternative': [], 'allow': [], 'ext':['iso', 'img'], 'tags': ['pal', 'ntsc', 'video_ts', 'audio_ts']},
|
{'identifier': 'dvdr', 'size': (3000, 10000), 'label': 'DVD-R', 'alternative': [], 'allow': [], 'ext':['iso', 'img'], 'tags': ['pal', 'ntsc', 'video_ts', 'audio_ts']},
|
||||||
{'identifier': 'dvdrip', 'size': (600, 2400), 'label': 'DVD-Rip', 'width': 720, 'alternative': ['dvdrip'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg'], 'tags': [('dvd', 'rip'), ('dvd', 'xvid'), ('dvd', 'divx')]},
|
{'identifier': 'dvdrip', 'size': (600, 2400), 'label': 'DVD-Rip', 'width': 720, 'alternative': ['dvdrip'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg'], 'tags': [('dvd', 'rip'), ('dvd', 'xvid'), ('dvd', 'divx')]},
|
||||||
|
|||||||
@@ -160,7 +160,9 @@ class Release(Plugin):
|
|||||||
|
|
||||||
db = get_session()
|
db = get_session()
|
||||||
id = getParam('id')
|
id = getParam('id')
|
||||||
status_snatched = fireEvent('status.add', 'snatched', single = True)
|
|
||||||
|
snatched_status = fireEvent('status.add', 'snatched', single = True)
|
||||||
|
done_status = fireEvent('status.get', 'done', single = True)
|
||||||
|
|
||||||
rel = db.query(Relea).filter_by(id = id).first()
|
rel = db.query(Relea).filter_by(id = id).first()
|
||||||
if rel:
|
if rel:
|
||||||
@@ -168,6 +170,8 @@ class Release(Plugin):
|
|||||||
for info in rel.info:
|
for info in rel.info:
|
||||||
item[info.identifier] = info.value
|
item[info.identifier] = info.value
|
||||||
|
|
||||||
|
fireEvent('notify.frontend', type = 'release.download', data = True, message = 'Snatching "%s"' % item['name'])
|
||||||
|
|
||||||
# Get matching provider
|
# Get matching provider
|
||||||
provider = fireEvent('provider.belongs_to', item['url'], provider = item.get('provider'), single = True)
|
provider = fireEvent('provider.belongs_to', item['url'], provider = item.get('provider'), single = True)
|
||||||
|
|
||||||
@@ -182,8 +186,14 @@ class Release(Plugin):
|
|||||||
}), manual = True, single = True)
|
}), manual = True, single = True)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
rel.status_id = status_snatched.get('id')
|
db.expunge_all()
|
||||||
db.commit()
|
rel = db.query(Relea).filter_by(id = id).first() # Get release again
|
||||||
|
|
||||||
|
if rel.status_id != done_status.get('id'):
|
||||||
|
rel.status_id = snatched_status.get('id')
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
fireEvent('notify.frontend', type = 'release.download', data = True, message = 'Successfully snatched "%s"' % item['name'])
|
||||||
|
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': success
|
'success': success
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ rename_options = {
|
|||||||
'thename': 'The Moviename',
|
'thename': 'The Moviename',
|
||||||
'year': 'Year (2011)',
|
'year': 'Year (2011)',
|
||||||
'first': 'First letter (M)',
|
'first': 'First letter (M)',
|
||||||
'quality': 'Quality (720P)',
|
'quality': 'Quality (720p)',
|
||||||
'video': 'Video (x264)',
|
'video': 'Video (x264)',
|
||||||
'audio': 'Audio (DTS)',
|
'audio': 'Audio (DTS)',
|
||||||
'group': 'Releasegroup name',
|
'group': 'Releasegroup name',
|
||||||
|
|||||||
@@ -571,8 +571,16 @@ class Renamer(Plugin):
|
|||||||
|
|
||||||
found = False
|
found = False
|
||||||
for item in statuses:
|
for item in statuses:
|
||||||
if item['name'] == nzbname or rel_dict['info']['name'] in item['name'] or getImdb(item['name']) == movie_dict['library']['identifier']:
|
found_release = False
|
||||||
|
if rel_dict['info'].get('download_id'):
|
||||||
|
if item['id'] == rel_dict['info']['download_id'] and item['downloader'] == rel_dict['info']['download_downloader']:
|
||||||
|
log.debug('Found release by id: %s', item['id'])
|
||||||
|
found_release = True
|
||||||
|
else:
|
||||||
|
if item['name'] == nzbname or rel_dict['info']['name'] in item['name'] or getImdb(item['name']) == movie_dict['library']['identifier']:
|
||||||
|
found_release = True
|
||||||
|
|
||||||
|
if found_release:
|
||||||
timeleft = 'N/A' if item['timeleft'] == -1 else item['timeleft']
|
timeleft = 'N/A' if item['timeleft'] == -1 else item['timeleft']
|
||||||
log.debug('Found %s: %s, time to go: %s', (item['name'], item['status'].upper(), timeleft))
|
log.debug('Found %s: %s, time to go: %s', (item['name'], item['status'].upper(), timeleft))
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from subliminal.videos import Video
|
|||||||
import enzyme
|
import enzyme
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import threading
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
@@ -74,7 +75,7 @@ class Scanner(Plugin):
|
|||||||
'hdtv': ['hdtv']
|
'hdtv': ['hdtv']
|
||||||
}
|
}
|
||||||
|
|
||||||
clean = '[ _\,\.\(\)\[\]\-](french|swedisch|danish|dutch|swesub|spanish|german|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdr|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|r3|r5|bd5|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|video_ts|audio_ts|480p|480i|576p|576i|720p|720i|1080p|1080i|hrhd|hrhdtv|hddvd|bluray|x264|h264|xvid|xvidvd|xxx|www.www|cd[1-9]|\[.*\])([ _\,\.\(\)\[\]\-]|$)'
|
clean = '[ _\,\.\(\)\[\]\-](extended.cut|directors.cut|french|swedisch|danish|dutch|swesub|spanish|german|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdr|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|r3|r5|bd5|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|video_ts|audio_ts|480p|480i|576p|576i|720p|720i|1080p|1080i|hrhd|hrhdtv|hddvd|bluray|x264|h264|xvid|xvidvd|xxx|www.www|cd[1-9]|\[.*\])([ _\,\.\(\)\[\]\-]|$)'
|
||||||
multipart_regex = [
|
multipart_regex = [
|
||||||
'[ _\.-]+cd[ _\.-]*([0-9a-d]+)', #*cd1
|
'[ _\.-]+cd[ _\.-]*([0-9a-d]+)', #*cd1
|
||||||
'[ _\.-]+dvd[ _\.-]*([0-9a-d]+)', #*dvd1
|
'[ _\.-]+dvd[ _\.-]*([0-9a-d]+)', #*dvd1
|
||||||
@@ -388,6 +389,11 @@ class Scanner(Plugin):
|
|||||||
if on_found:
|
if on_found:
|
||||||
on_found(group, total_found, total_found - len(processed_movies))
|
on_found(group, total_found, total_found - len(processed_movies))
|
||||||
|
|
||||||
|
# Wait for all the async events calm down a bit
|
||||||
|
while threading.activeCount() > 100 and not self.shuttingDown():
|
||||||
|
log.debug('Too many threads active, waiting a few seconds')
|
||||||
|
time.sleep(10)
|
||||||
|
|
||||||
if len(processed_movies) > 0:
|
if len(processed_movies) > 0:
|
||||||
log.info('Found %s movies in the folder %s', (len(processed_movies), folder))
|
log.info('Found %s movies in the folder %s', (len(processed_movies), folder))
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -285,10 +285,10 @@ class Searcher(Plugin):
|
|||||||
if filedata == 'try_next':
|
if filedata == 'try_next':
|
||||||
return filedata
|
return filedata
|
||||||
|
|
||||||
successful = fireEvent('download', data = data, movie = movie, manual = manual, filedata = filedata, single = True)
|
download_result = fireEvent('download', data = data, movie = movie, manual = manual, filedata = filedata, single = True)
|
||||||
|
log.debug('Downloader result: %s', download_result)
|
||||||
if successful:
|
|
||||||
|
|
||||||
|
if download_result:
|
||||||
try:
|
try:
|
||||||
# Mark release as snatched
|
# Mark release as snatched
|
||||||
db = get_session()
|
db = get_session()
|
||||||
@@ -298,6 +298,15 @@ class Searcher(Plugin):
|
|||||||
|
|
||||||
done_status = fireEvent('status.get', 'done', single = True)
|
done_status = fireEvent('status.get', 'done', single = True)
|
||||||
rls.status_id = done_status.get('id') if not renamer_enabled else snatched_status.get('id')
|
rls.status_id = done_status.get('id') if not renamer_enabled else snatched_status.get('id')
|
||||||
|
|
||||||
|
# Save download-id info if returned
|
||||||
|
if isinstance(download_result, dict):
|
||||||
|
for key in download_result:
|
||||||
|
rls_info = ReleaseInfo(
|
||||||
|
identifier = 'download_%s' % key,
|
||||||
|
value = toUnicode(download_result.get(key))
|
||||||
|
)
|
||||||
|
rls.info.append(rls_info)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
log_movie = '%s (%s) in %s' % (getTitle(movie['library']), movie['library']['year'], rls.quality.label)
|
log_movie = '%s (%s) in %s' % (getTitle(movie['library']), movie['library']['year'], rls.quality.label)
|
||||||
@@ -333,7 +342,7 @@ class Searcher(Plugin):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
log.info('Tried to download, but none of the "%s" downloaders are enabled', (data.get('type', '')))
|
log.info('Tried to download, but none of the "%s" downloaders are enabled or gave an error', (data.get('type', '')))
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ config = [{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'languages',
|
'name': 'languages',
|
||||||
'description': 'Comma separated, 2 letter country code. Example: en, nl',
|
'description': 'Comma separated, 2 letter country code. Example: en, nl. See the codes at <a href="http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes">on Wikipedia</a>',
|
||||||
},
|
},
|
||||||
# {
|
# {
|
||||||
# 'name': 'automatic',
|
# 'name': 'automatic',
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ config = [{
|
|||||||
'name': 'quality',
|
'name': 'quality',
|
||||||
'default': '720p',
|
'default': '720p',
|
||||||
'type': 'dropdown',
|
'type': 'dropdown',
|
||||||
'values': [('1080P', '1080p'), ('720P', '720p'), ('480P', '480p')],
|
'values': [('1080p', '1080p'), ('720p', '720p'), ('480P', '480p')],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'name',
|
'name': 'name',
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
from .main import Letterboxd
|
||||||
|
|
||||||
|
def start():
|
||||||
|
return Letterboxd()
|
||||||
|
|
||||||
|
config = [{
|
||||||
|
'name': 'letterboxd',
|
||||||
|
'groups': [
|
||||||
|
{
|
||||||
|
'tab': 'automation',
|
||||||
|
'list': 'watchlist_providers',
|
||||||
|
'name': 'letterboxd_automation',
|
||||||
|
'label': 'Letterboxd',
|
||||||
|
'description': 'Import movies from any public <a href="http://letterboxd.com/">Letterboxd</a> watchlist',
|
||||||
|
'options': [
|
||||||
|
{
|
||||||
|
'name': 'automation_enabled',
|
||||||
|
'default': False,
|
||||||
|
'type': 'enabler',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'automation_urls_use',
|
||||||
|
'label': 'Use',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'automation_urls',
|
||||||
|
'label': 'Username',
|
||||||
|
'type': 'combined',
|
||||||
|
'combine': ['automation_urls_use', 'automation_urls'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}]
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
from bs4 import BeautifulSoup
|
||||||
|
from couchpotato.core.helpers.variable import tryInt, splitString
|
||||||
|
from couchpotato.core.logger import CPLog
|
||||||
|
from couchpotato.core.providers.automation.base import Automation
|
||||||
|
import re
|
||||||
|
|
||||||
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Letterboxd(Automation):
|
||||||
|
|
||||||
|
url = 'http://letterboxd.com/%s/watchlist/'
|
||||||
|
pattern = re.compile(r'(.*)\((\d*)\)')
|
||||||
|
|
||||||
|
def getIMDBids(self):
|
||||||
|
|
||||||
|
urls = splitString(self.conf('automation_urls'))
|
||||||
|
|
||||||
|
if len(urls) == 0:
|
||||||
|
return []
|
||||||
|
|
||||||
|
movies = []
|
||||||
|
|
||||||
|
for movie in self.getWatchlist():
|
||||||
|
imdb_id = self.search(movie.get('title'), movie.get('year'), imdb_only = True)
|
||||||
|
movies.append(imdb_id)
|
||||||
|
|
||||||
|
return movies
|
||||||
|
|
||||||
|
def getWatchlist(self):
|
||||||
|
|
||||||
|
enablers = [tryInt(x) for x in splitString(self.conf('automation_urls_use'))]
|
||||||
|
urls = splitString(self.conf('automation_urls'))
|
||||||
|
|
||||||
|
index = -1
|
||||||
|
movies = []
|
||||||
|
for username in urls:
|
||||||
|
|
||||||
|
index += 1
|
||||||
|
if not enablers[index]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
soup = BeautifulSoup(self.getHTMLData(self.url % username))
|
||||||
|
|
||||||
|
for movie in soup.find_all('a', attrs = { 'class': 'frame' }):
|
||||||
|
match = filter(None, self.pattern.split(movie['title']))
|
||||||
|
movies.append({'title': match[0], 'year': match[1] })
|
||||||
|
|
||||||
|
return movies
|
||||||
@@ -76,7 +76,7 @@ class LibraryTitle(Entity):
|
|||||||
|
|
||||||
title = Field(Unicode)
|
title = Field(Unicode)
|
||||||
simple_title = Field(Unicode, index = True)
|
simple_title = Field(Unicode, index = True)
|
||||||
default = Field(Boolean, index = True)
|
default = Field(Boolean, default = False, index = True)
|
||||||
|
|
||||||
language = OneToMany('Language')
|
language = OneToMany('Language')
|
||||||
libraries = ManyToOne('Library')
|
libraries = ManyToOne('Library')
|
||||||
@@ -141,12 +141,12 @@ class Status(Entity):
|
|||||||
|
|
||||||
|
|
||||||
class Quality(Entity):
|
class Quality(Entity):
|
||||||
"""Quality name of a release, DVD, 720P, DVD-Rip etc"""
|
"""Quality name of a release, DVD, 720p, DVD-Rip etc"""
|
||||||
using_options(order_by = 'order')
|
using_options(order_by = 'order')
|
||||||
|
|
||||||
identifier = Field(String(20), unique = True)
|
identifier = Field(String(20), unique = True)
|
||||||
label = Field(Unicode(20))
|
label = Field(Unicode(20))
|
||||||
order = Field(Integer, index = True)
|
order = Field(Integer, default = 0, index = True)
|
||||||
|
|
||||||
size_min = Field(Integer)
|
size_min = Field(Integer)
|
||||||
size_max = Field(Integer)
|
size_max = Field(Integer)
|
||||||
@@ -160,21 +160,27 @@ class Profile(Entity):
|
|||||||
using_options(order_by = 'order')
|
using_options(order_by = 'order')
|
||||||
|
|
||||||
label = Field(Unicode(50))
|
label = Field(Unicode(50))
|
||||||
order = Field(Integer, index = True)
|
order = Field(Integer, default = 0, index = True)
|
||||||
core = Field(Boolean)
|
core = Field(Boolean, default = False)
|
||||||
hide = Field(Boolean)
|
hide = Field(Boolean, default = False)
|
||||||
|
|
||||||
movie = OneToMany('Movie')
|
movie = OneToMany('Movie')
|
||||||
types = OneToMany('ProfileType', cascade = 'all, delete-orphan')
|
types = OneToMany('ProfileType', cascade = 'all, delete-orphan')
|
||||||
|
|
||||||
|
def to_dict(self, deep = {}, exclude = []):
|
||||||
|
orig_dict = super(Profile, self).to_dict(deep = deep, exclude = exclude)
|
||||||
|
orig_dict['core'] = orig_dict.get('core') or False
|
||||||
|
orig_dict['hide'] = orig_dict.get('hide') or False
|
||||||
|
|
||||||
|
return orig_dict
|
||||||
|
|
||||||
class ProfileType(Entity):
|
class ProfileType(Entity):
|
||||||
""""""
|
""""""
|
||||||
using_options(order_by = 'order')
|
using_options(order_by = 'order')
|
||||||
|
|
||||||
order = Field(Integer, index = True)
|
order = Field(Integer, default = 0, index = True)
|
||||||
finish = Field(Boolean)
|
finish = Field(Boolean, default = True)
|
||||||
wait_for = Field(Integer)
|
wait_for = Field(Integer, default = 0)
|
||||||
|
|
||||||
quality = ManyToOne('Quality')
|
quality = ManyToOne('Quality')
|
||||||
profile = ManyToOne('Profile')
|
profile = ManyToOne('Profile')
|
||||||
@@ -185,7 +191,7 @@ class File(Entity):
|
|||||||
|
|
||||||
path = Field(Unicode(255), nullable = False, unique = True)
|
path = Field(Unicode(255), nullable = False, unique = True)
|
||||||
part = Field(Integer, default = 1)
|
part = Field(Integer, default = 1)
|
||||||
available = Field(Boolean)
|
available = Field(Boolean, default = True)
|
||||||
|
|
||||||
type = ManyToOne('FileType')
|
type = ManyToOne('FileType')
|
||||||
properties = OneToMany('FileProperty')
|
properties = OneToMany('FileProperty')
|
||||||
|
|||||||
@@ -190,9 +190,12 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
|
|||||||
version_control(db, repo, version = latest_db_version)
|
version_control(db, repo, version = latest_db_version)
|
||||||
current_db_version = db_version(db, repo)
|
current_db_version = db_version(db, repo)
|
||||||
|
|
||||||
if current_db_version < latest_db_version and not development:
|
if current_db_version < latest_db_version:
|
||||||
log.info('Doing database upgrade. From %d to %d', (current_db_version, latest_db_version))
|
if development:
|
||||||
upgrade(db, repo)
|
log.error('There is a database migration ready, but you are running development mode, so it won\'t be used. If you see this, you are stupid. Please disable development mode.')
|
||||||
|
else:
|
||||||
|
log.info('Doing database upgrade. From %d to %d', (current_db_version, latest_db_version))
|
||||||
|
upgrade(db, repo)
|
||||||
|
|
||||||
# Configure Database
|
# Configure Database
|
||||||
from couchpotato.core.settings.model import setup
|
from couchpotato.core.settings.model import setup
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ Page.Home = new Class({
|
|||||||
'identifier': 'snatched',
|
'identifier': 'snatched',
|
||||||
'load_more': false,
|
'load_more': false,
|
||||||
'view': 'list',
|
'view': 'list',
|
||||||
'actions': [MA.IMDB, MA.Trailer, MA.Files, MA.Release, MA.Edit, MA.Readd, MA.Refresh, MA.Delete],
|
'actions': [MA.IMDB, MA.Trailer, MA.Release, MA.Refresh, MA.Delete],
|
||||||
'title': 'Snatched & Available',
|
'title': 'Snatched & Available',
|
||||||
'on_empty_element': new Element('div'),
|
'on_empty_element': new Element('div'),
|
||||||
'filter': {
|
'filter': {
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ Page.Settings = new Class({
|
|||||||
|
|
||||||
return group_el
|
return group_el
|
||||||
},
|
},
|
||||||
|
|
||||||
createList: function(content_container){
|
createList: function(content_container){
|
||||||
return new Element('div.option_list').grab(
|
return new Element('div.option_list').grab(
|
||||||
new Element('h3', {
|
new Element('h3', {
|
||||||
@@ -1283,6 +1283,7 @@ Option.Combined = new Class({
|
|||||||
self.values = {}
|
self.values = {}
|
||||||
self.inputs = {}
|
self.inputs = {}
|
||||||
self.items = []
|
self.items = []
|
||||||
|
self.labels = {}
|
||||||
|
|
||||||
self.options.combine.each(function(name){
|
self.options.combine.each(function(name){
|
||||||
|
|
||||||
@@ -1302,9 +1303,10 @@ Option.Combined = new Class({
|
|||||||
var head = new Element('div.head').inject(self.combined_list)
|
var head = new Element('div.head').inject(self.combined_list)
|
||||||
|
|
||||||
Object.each(self.inputs, function(input, name){
|
Object.each(self.inputs, function(input, name){
|
||||||
|
self.labels[name] = input.getPrevious().get('text')
|
||||||
new Element('abbr', {
|
new Element('abbr', {
|
||||||
'class': name,
|
'class': name,
|
||||||
'text': input.getPrevious().get('text'),
|
'text': self.labels[name],
|
||||||
//'title': input.getNext().get('text')
|
//'title': input.getNext().get('text')
|
||||||
}).inject(head)
|
}).inject(head)
|
||||||
})
|
})
|
||||||
@@ -1367,7 +1369,7 @@ Option.Combined = new Class({
|
|||||||
value_count++;
|
value_count++;
|
||||||
new Element('input[type=text].inlay.'+name, {
|
new Element('input[type=text].inlay.'+name, {
|
||||||
'value': value,
|
'value': value,
|
||||||
'placeholder': name,
|
'placeholder': self.labels[name] || name,
|
||||||
'events': {
|
'events': {
|
||||||
'keyup': self.saveCombined.bind(self),
|
'keyup': self.saveCombined.bind(self),
|
||||||
'change': self.saveCombined.bind(self)
|
'change': self.saveCombined.bind(self)
|
||||||
|
|||||||
@@ -134,10 +134,9 @@ body > .spinner, .mask{
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
border-radius: 5px;
|
border-radius: 2px;
|
||||||
box-shadow: 0 1px 3px rgba(0,0,0,0.5);
|
box-shadow: 0 1px 2px rgba(0,0,0,0.3);
|
||||||
text-shadow: 0 -1px 1px rgba(0,0,0,0.25);
|
text-shadow: 0 -1px 1px rgba(0,0,0,0.25);
|
||||||
border-bottom: 1px solid rgba(0,0,0,0.25);
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.button.red { background-color: #ff0000; }
|
.button.red { background-color: #ff0000; }
|
||||||
|
|||||||
Reference in new issue
Block a user