Merge branch 'refs/heads/develop'

This commit is contained in:
Ruud
2013-09-28 23:43:49 +02:00
20 changed files with 380 additions and 122 deletions
+2 -2
View File
@@ -1,4 +1,4 @@
config = {
config = [{
'name': 'download_providers',
'groups': [
{
@@ -10,4 +10,4 @@ config = {
'options': [],
},
],
}
}]
@@ -136,11 +136,11 @@ class Transmission(Downloader):
def removeFailed(self, item):
log.info('%s failed downloading, deleting...', item['name'])
return self.trpc.remove_torrent(item['hashString'], True)
return self.trpc.remove_torrent(item['id'], True)
def processComplete(self, item, delete_files = False):
log.debug('Requesting Transmission to remove the torrent %s%s.', (item['name'], ' and cleanup the downloaded files' if delete_files else ''))
return self.trpc.remove_torrent(item['hashString'], delete_files)
return self.trpc.remove_torrent(item['id'], delete_files)
class TransmissionRPC(object):
@@ -107,9 +107,9 @@ class uTorrent(Downloader):
count += 1
# Check if torrent is saved in subfolder of torrent name
data = self.utorrent_api.get_files(torrent_hash)
getfiles_data = self.utorrent_api.get_files(torrent_hash)
torrent_files = json.loads(data)
torrent_files = json.loads(getfiles_data)
if torrent_files.get('error'):
log.error('Error getting data from uTorrent: %s', torrent_files.get('error'))
return False
@@ -200,7 +200,7 @@ class uTorrent(Downloader):
if not self.connect():
return False
return self.utorrent_api.remove_torrent(item['id'], remove_data = delete_files)
def removeReadOnly(self, folder):
#Removes all read-only flags in a folder
if folder and os.path.isdir(folder):
+3 -11
View File
@@ -1,6 +1,5 @@
from couchpotato.core.event import fireEvent
from couchpotato.core.logger import CPLog
import glob
import os
import traceback
@@ -81,17 +80,10 @@ class Loader(object):
def addFromDir(self, plugin_type, priority, module, dir_name):
# Load dir module
try:
m = __import__(module)
splitted = module.split('.')
for sub in splitted[1:]:
m = getattr(m, sub)
except:
raise
self.addModule(priority, plugin_type, module, os.path.basename(dir_name))
for cur_file in glob.glob(os.path.join(dir_name, '*')):
name = os.path.basename(cur_file)
if os.path.isdir(os.path.join(dir_name, name)) and name != 'static' and os.path.isfile(os.path.join(cur_file, '__init__.py')):
for name in os.listdir(dir_name):
if os.path.isdir(os.path.join(dir_name, name)) and name != 'static' and os.path.isfile(os.path.join(dir_name, name, '__init__.py')):
module_name = '%s.%s' % (module, name)
self.addModule(priority, plugin_type, module_name, name)
+2 -2
View File
@@ -1,4 +1,4 @@
config = {
config = [{
'name': 'notification_providers',
'groups': [
{
@@ -10,4 +10,4 @@ config = {
'options': [],
},
],
}
}]
+2 -1
View File
@@ -2,6 +2,7 @@ from couchpotato.core.helpers.encoding import toUnicode
from couchpotato.core.helpers.variable import splitString
from couchpotato.core.logger import CPLog
from couchpotato.core.notifications.base import Notification
from couchpotato.environment import Env
from email.mime.text import MIMEText
import smtplib
import traceback
@@ -23,7 +24,7 @@ class Email(Notification):
smtp_pass = self.conf('smtp_pass')
# Make the basic message
message = MIMEText(toUnicode(message))
message = MIMEText(toUnicode(message), _charset = Env.get('encoding'))
message['Subject'] = self.default_title
message['From'] = from_address
message['To'] = to_address
+8 -3
View File
@@ -17,10 +17,15 @@ config = [{
'type': 'enabler',
},
{
'name': 'host',
'name': 'media_server',
'label': 'Media Server',
'default': 'localhost',
'description': 'Default should be on localhost',
'advanced': True,
'description': 'Hostname/IP, default localhost'
},
{
'name': 'clients',
'default': '',
'description': 'Comma separated list of client names\'s (computer names). Top right when you start Plex'
},
{
'name': 'on_snatch',
+155 -54
View File
@@ -1,79 +1,184 @@
from couchpotato.core.event import addEvent
from couchpotato.core.helpers.encoding import tryUrlencode
from couchpotato.core.helpers.variable import cleanHost, splitString
from couchpotato.core.helpers.variable import cleanHost
from couchpotato.core.logger import CPLog
from couchpotato.core.notifications.base import Notification
from urllib2 import URLError
from datetime import datetime
from urlparse import urlparse
from xml.dom import minidom
import json
import requests
import traceback
try:
import xml.etree.cElementTree as etree
except ImportError:
import xml.etree.ElementTree as etree
log = CPLog(__name__)
class Plex(Notification):
client_update_time = 5 * 60
http_time_between_calls = 0
def __init__(self):
super(Plex, self).__init__()
self.clients = {}
self.clients_updated = None
addEvent('renamer.after', self.addToLibrary)
def addToLibrary(self, message = None, group = None):
def updateClients(self, force = False):
if not self.conf('media_server'):
log.warning("Plex media server hostname is required")
return
since_update = ((datetime.now() - self.clients_updated).total_seconds())\
if self.clients_updated is not None else None
if force or self.clients_updated is None or since_update > self.client_update_time:
self.clients = {}
data = self.urlopen('%s/clients' % self.createHost(self.conf('media_server'), port = 32400))
client_result = etree.fromstring(data)
clients = [x.strip().lower() for x in self.conf('clients').split(',')]
for server in client_result.findall('Server'):
if server.get('name').lower() in clients:
clients.remove(server.get('name').lower())
protocol = server.get('protocol', 'xbmchttp')
if protocol in ['xbmcjson', 'xbmchttp']:
self.clients[server.get('name')] = {
'name': server.get('name'),
'address': server.get('address'),
'port': server.get('port'),
'protocol': protocol
}
if len(clients) > 0:
log.info2('Unable to find plex clients: %s', ', '.join(clients))
log.info2('Found hosts: %s', ', '.join(self.clients.keys()))
self.clients_updated = datetime.now()
def addToLibrary(self, message = None, group = {}):
if self.isDisabled(): return
if not group: group = {}
log.info('Sending notification to Plex')
hosts = self.getHosts(port = 32400)
for host in hosts:
source_type = ['movie']
base_url = '%s/library/sections' % self.createHost(self.conf('media_server'), port = 32400)
refresh_url = '%s/%%s/refresh' % base_url
source_type = ['movie']
base_url = '%s/library/sections' % host
refresh_url = '%s/%%s/refresh' % base_url
try:
sections_xml = self.urlopen(base_url)
xml_sections = minidom.parseString(sections_xml)
sections = xml_sections.getElementsByTagName('Directory')
try:
sections_xml = self.urlopen(base_url)
xml_sections = minidom.parseString(sections_xml)
sections = xml_sections.getElementsByTagName('Directory')
for s in sections:
if s.getAttribute('type') in source_type:
url = refresh_url % s.getAttribute('key')
x = self.urlopen(url)
for s in sections:
if s.getAttribute('type') in source_type:
url = refresh_url % s.getAttribute('key')
self.urlopen(url)
except:
log.error('Plex library update failed for %s, Media Server not running: %s', (host, traceback.format_exc(1)))
return False
except:
log.error('Plex library update failed for %s, Media Server not running: %s',
(self.conf('media_server'), traceback.format_exc(1)))
return False
return True
def notify(self, message = '', data = None, listener = None):
if not data: data = {}
def sendHTTP(self, command, client):
url = 'http://%s:%s/xbmcCmds/xbmcHttp/?%s' % (
client['address'],
client['port'],
tryUrlencode(command)
)
hosts = self.getHosts(port = 3000)
successful = 0
for host in hosts:
if self.send({'command': 'ExecBuiltIn', 'parameter': 'Notification(CouchPotato, %s)' % message}, host):
successful += 1
return successful == len(hosts)
def send(self, command, host):
url = '%s/xbmcCmds/xbmcHttp/?%s' % (host, tryUrlencode(command))
headers = {}
try:
self.urlopen(url, headers = headers, show_error = False)
except URLError:
log.error("Couldn't sent command to Plex, probably just running Media Server")
return False
except:
log.error("Couldn't sent command to Plex: %s", traceback.format_exc())
self.urlopen(url, headers = headers, timeout = 3, show_error = False)
except Exception, err:
log.error("Couldn't sent command to Plex: %s", err)
return False
log.info('Plex notification to %s successful.', host)
return True
def notifyHTTP(self, message = '', data = {}, listener = None):
total = 0
successful = 0
data = {
'command': 'ExecBuiltIn',
'parameter': 'Notification(CouchPotato, %s)' % message
}
for name, client in self.clients.items():
if client['protocol'] == 'xbmchttp':
total += 1
if self.sendHTTP(data, client):
successful += 1
return successful == total
def sendJSON(self, method, params, client):
log.debug('sendJSON("%s", %s, %s)', (method, params, client))
url = 'http://%s:%s/jsonrpc' % (
client['address'],
client['port']
)
headers = {
'Content-Type': 'application/json'
}
request = {
'id':1,
'jsonrpc': '2.0',
'method': method,
'params': params
}
try:
requests.post(url, headers = headers, timeout = 3, data = json.dumps(request))
except Exception, err:
log.error("Couldn't sent command to Plex: %s", err)
return False
return True
def notifyJSON(self, message = '', data = {}, listener = None):
total = 0
successful = 0
params = {
'title': 'CouchPotato',
'message': message
}
for name, client in self.clients.items():
if client['protocol'] == 'xbmcjson':
total += 1
if self.sendJSON('GUI.ShowNotification', params, client):
successful += 1
return successful == total
def notify(self, message = '', data = {}, listener = None, force = False):
self.updateClients(force)
http_result = self.notifyHTTP(message, data, listener)
json_result = self.notifyJSON(message, data, listener)
return http_result and json_result
def test(self, **kwargs):
test_type = self.testNotifyName()
@@ -83,7 +188,8 @@ class Plex(Notification):
success = self.notify(
message = self.test_message,
data = {},
listener = 'test'
listener = 'test',
force = True
)
success2 = self.addToLibrary()
@@ -91,17 +197,12 @@ class Plex(Notification):
'success': success or success2
}
def getHosts(self, port = None):
def createHost(self, host, port = None):
raw_hosts = splitString(self.conf('host'))
hosts = []
h = cleanHost(host)
p = urlparse(h)
h = h.rstrip('/')
if port and not p.port:
h += ':%s' % port
for h in raw_hosts:
h = cleanHost(h)
p = urlparse(h)
h = h.rstrip('/')
if port and not p.port:
h += ':%s' % port
hosts.append(h)
return hosts
return h
+1 -1
View File
@@ -121,7 +121,7 @@ class Plugin(object):
# http request
def urlopen(self, url, timeout = 30, params = None, headers = None, opener = None, multipart = False, show_error = True):
url = ss(url)
url = urllib2.quote(ss(url), safe = "%/:=&?~#+!$,;'@()*[]")
if not headers: headers = {}
if not params: params = {}
+9 -5
View File
@@ -1,7 +1,7 @@
from couchpotato import get_session
from couchpotato.api import addApiView
from couchpotato.core.event import addEvent
from couchpotato.core.helpers.encoding import toUnicode
from couchpotato.core.helpers.encoding import toUnicode, ss
from couchpotato.core.helpers.variable import mergeDicts, md5, getExt
from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin
@@ -192,15 +192,19 @@ class QualityPlugin(Plugin):
return None
def containsTag(self, quality, words, cur_file = ''):
cur_file = ss(cur_file)
# Check alt and tags
for tag_type in ['alternative', 'tags']:
for alt in quality.get(tag_type, []):
if isinstance(alt, tuple) and '.'.join(alt) in '.'.join(words):
for tag_type in ['alternative', 'tags', 'label']:
qualities = quality.get(tag_type, [])
qualities = [qualities] if isinstance(qualities, (str, unicode)) else qualities
for alt in qualities:
if (isinstance(alt, tuple) and '.'.join(alt) in '.'.join(words)) or (isinstance(alt, (str, unicode)) and ss(alt.lower()) in cur_file.lower()):
log.debug('Found %s via %s %s in %s', (quality['identifier'], tag_type, quality.get(tag_type), cur_file))
return True
if list(set(quality.get(tag_type, [])) & set(words)):
if list(set(qualities) & set(words)):
log.debug('Found %s via %s %s in %s', (quality['identifier'], tag_type, quality.get(tag_type), cur_file))
return True
+8 -3
View File
@@ -1,7 +1,8 @@
from couchpotato import get_session
from couchpotato.core.event import fireEvent, addEvent
from couchpotato.core.helpers.encoding import toUnicode, simplifyString, ss
from couchpotato.core.helpers.variable import getExt, getImdb, tryInt
from couchpotato.core.helpers.variable import getExt, getImdb, tryInt, \
splitString
from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin
from couchpotato.core.settings.model import File, Movie
@@ -741,9 +742,14 @@ class Scanner(Plugin):
def createStringIdentifier(self, file_path, folder = '', exclude_filename = False):
identifier = file_path.replace(folder, '') # root folder
year = self.findYear(file_path)
identifier = file_path.replace(folder, '').lstrip(os.path.sep) # root folder
identifier = os.path.splitext(identifier)[0] # ext
path_split = splitString(identifier, os.path.sep)
identifier = path_split[-2] if len(path_split) > 1 and len(path_split[-2]) > len(path_split[-1]) else path_split[-1] # Only get filename
if exclude_filename:
identifier = identifier[:len(identifier) - len(os.path.split(identifier)[-1])]
@@ -757,7 +763,6 @@ class Scanner(Plugin):
identifier = re.sub(self.clean, '::', simplifyString(identifier)).strip(':')
# Year
year = self.findYear(identifier)
if year and identifier[:4] != year:
identifier = '%s %s' % (identifier.split(year)[0].strip(), year)
else:
@@ -1,4 +1,4 @@
config = {
config = [{
'name': 'automation_providers',
'groups': [
{
@@ -18,4 +18,4 @@ config = {
'options': [],
},
],
}
}]
@@ -18,6 +18,13 @@ config = [{
'default': False,
'type': 'enabler',
},
{
'name': 'backlog',
'advanced': True,
'description': 'Parses the history until the minimum movie year is reached. (Will be disabled once it has completed)',
'default': False,
'type': 'bool',
},
],
},
],
@@ -1,3 +1,4 @@
from bs4 import BeautifulSoup
from couchpotato.core.helpers.rss import RSS
from couchpotato.core.helpers.variable import tryInt
from couchpotato.core.logger import CPLog
@@ -10,11 +11,49 @@ class Bluray(Automation, RSS):
interval = 1800
rss_url = 'http://www.blu-ray.com/rss/newreleasesfeed.xml'
backlog_url = 'http://www.blu-ray.com/movies/movies.php?show=newreleases&page=%s'
def getIMDBids(self):
movies = []
if self.conf('backlog'):
page = 0
while True:
page = page + 1
url = self.backlog_url % page
data = self.getHTMLData(url)
soup = BeautifulSoup(data)
try:
# Stop if the release year is before the minimal year
page_year = soup.body.find_all('center')[3].table.tr.find_all('td', recursive = False)[3].h3.get_text().split(', ')[1]
if tryInt(page_year) < self.getMinimal('year'):
break
for table in soup.body.find_all('center')[3].table.tr.find_all('td', recursive = False)[3].find_all('table')[1:20]:
name = table.h3.get_text().lower().split('blu-ray')[0].strip()
year = table.small.get_text().split('|')[1].strip()
if not name.find('/') == -1: # make sure it is not a double movie release
continue
if tryInt(year) < self.getMinimal('year'):
continue
imdb = self.search(name, year)
if imdb:
if self.isMinimalMovie(imdb):
movies.append(imdb['imdb'])
except:
log.debug('Error loading page: %s', page)
break
self.conf('backlog', value = False)
rss_movies = self.getRSSData(self.rss_url)
for movie in rss_movies:
@@ -0,0 +1,34 @@
from .main import Flixster
def start():
return Flixster()
config = [{
'name': 'flixster',
'groups': [
{
'tab': 'automation',
'list': 'watchlist_providers',
'name': 'flixster_automation',
'label': 'Flixster',
'description': 'Import movies from any public <a href="http://www.flixster.com/">Flixster</a> watchlist',
'options': [
{
'name': 'automation_enabled',
'default': False,
'type': 'enabler',
},
{
'name': 'automation_ids_use',
'label': 'Use',
},
{
'name': 'automation_ids',
'label': 'User ID',
'type': 'combined',
'combine': ['automation_ids_use', 'automation_ids'],
},
],
},
],
}]
@@ -0,0 +1,48 @@
from couchpotato.core.helpers.variable import tryInt, splitString
from couchpotato.core.logger import CPLog
from couchpotato.core.providers.automation.base import Automation
import json
log = CPLog(__name__)
class Flixster(Automation):
url = 'http://www.flixster.com/api/users/%s/movies/ratings?scoreTypes=wts'
interval = 60
def getIMDBids(self):
ids = splitString(self.conf('automation_ids'))
if len(ids) == 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_ids_use'))]
ids = splitString(self.conf('automation_ids'))
index = -1
movies = []
for user_id in ids:
index += 1
if not enablers[index]:
continue
data = json.loads(self.getHTMLData(self.url % user_id))
for movie in data:
movies.append({'title': movie['movie']['title'], 'year': movie['movie']['year'] })
return movies
+2 -2
View File
@@ -1,4 +1,4 @@
config = {
config = [{
'name': 'nzb_providers',
'groups': [
{
@@ -11,4 +11,4 @@ config = {
'options': [],
},
],
}
}]
@@ -1,4 +1,4 @@
config = {
config = [{
'name': 'torrent_providers',
'groups': [
{
@@ -11,4 +11,4 @@ config = {
'options': [],
},
],
}
}]
@@ -15,7 +15,7 @@ class TorrentShack(TorrentProvider):
'login' : 'https://torrentshack.net/login.php',
'login_check': 'https://torrentshack.net/inbox.php',
'detail' : 'https://torrentshack.net/torrent/%s',
'search' : 'https://torrentshack.net/torrents.php?searchstr=%s&filter_cat[%d]=1',
'search' : 'https://torrentshack.net/torrents.php?action=advanced&searchstr=%s&scene=%s&filter_cat[%d]=1',
'download' : 'https://torrentshack.net/%s',
}
@@ -31,7 +31,9 @@ class TorrentShack(TorrentProvider):
def _searchOnTitle(self, title, movie, quality, results):
url = self.urls['search'] % (tryUrlencode('"%s" %s' % (title.replace(':', ''), movie['library']['year'])), self.getCatId(quality['identifier'])[0])
scene_only = '1' if self.conf('scene_only') else ''
url = self.urls['search'] % (tryUrlencode('%s %s' % (title.replace(':', ''), movie['library']['year'])), scene_only, self.getCatId(quality['identifier'])[0])
data = self.getHTMLData(url, opener = self.login_opener)
if data:
@@ -49,22 +51,15 @@ class TorrentShack(TorrentProvider):
link = result.find('span', attrs = {'class' : 'torrent_name_link'}).parent
url = result.find('td', attrs = {'class' : 'torrent_td'}).find('a')
extra_info = ''
if result.find('span', attrs = {'class' : 'torrent_extra_info'}):
extra_info = result.find('span', attrs = {'class' : 'torrent_extra_info'}).text
if not self.conf('scene_only') or extra_info != '[NotScene]':
results.append({
'id': link['href'].replace('torrents.php?torrentid=', ''),
'name': unicode(link.span.string).translate({ord(u'\xad'): None}),
'url': self.urls['download'] % url['href'],
'detail_url': self.urls['download'] % link['href'],
'size': self.parseSize(result.find_all('td')[4].string),
'seeders': tryInt(result.find_all('td')[6].string),
'leechers': tryInt(result.find_all('td')[7].string),
})
else:
log.info('Not adding release %s [NotScene]' % unicode(link.span.string).translate({ord(u'\xad'): None}))
results.append({
'id': link['href'].replace('torrents.php?torrentid=', ''),
'name': unicode(link.span.string).translate({ord(u'\xad'): None}),
'url': self.urls['download'] % url['href'],
'detail_url': self.urls['download'] % link['href'],
'size': self.parseSize(result.find_all('td')[4].string),
'seeders': tryInt(result.find_all('td')[6].string),
'leechers': tryInt(result.find_all('td')[7].string),
})
except:
log.error('Failed to parsing %s: %s', (self.getName(), traceback.format_exc()))
+38 -11
View File
@@ -1,4 +1,5 @@
import os
import platform
from collections import defaultdict
from itertools import imap
@@ -23,22 +24,48 @@ class DelugeClient(object):
self._request_counter = 0
def _get_local_auth(self):
xdg_config = os.path.expanduser(os.environ.get("XDG_CONFIG_HOME", "~/.config"))
config_home = os.path.join(xdg_config, "deluge")
auth_file = os.path.join(config_home, "auth")
auth_file = ""
username = password = ""
with open(auth_file) as fd:
for line in fd:
if platform.system() in ('Windows', 'Microsoft'):
appDataPath = os.environ.get("APPDATA")
if not appDataPath:
import _winreg
hkey = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders")
appDataReg = _winreg.QueryValueEx(hkey, "AppData")
appDataPath = appDataReg[0]
_winreg.CloseKey(hkey)
auth_file = os.path.join(appDataPath, "deluge", "auth")
else:
from xdg.BaseDirectory import save_config_path
try:
auth_file = os.path.join(save_config_path("deluge"), "auth")
except OSError, e:
return username, password
if os.path.exists(auth_file):
for line in open(auth_file):
if line.startswith("#"):
# This is a comment line
continue
line = line.strip()
try:
lsplit = line.split(":")
except Exception, e:
continue
auth = line.split(":")
if len(auth) >= 2 and auth[0] == "localclient":
username, password = auth[0], auth[1]
break
if len(lsplit) == 2:
username, password = lsplit
elif len(lsplit) == 3:
username, password, level = lsplit
else:
continue
return username, password
if username == "localclient":
return (username, password)
return ("", "")
def _create_module_method(self, module, method):
fullname = "{0}.{1}".format(module, method)