diff --git a/.gitignore b/.gitignore index e134ddb6..e156f873 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ *.pyc /data/ -/_source/ \ No newline at end of file +/_source/ +.project +.pydevproject diff --git a/couchpotato/core/_base/updater/main.py b/couchpotato/core/_base/updater/main.py index 980e247b..3ddec1fe 100644 --- a/couchpotato/core/_base/updater/main.py +++ b/couchpotato/core/_base/updater/main.py @@ -305,7 +305,7 @@ class SourceUpdater(BaseUpdater): if not os.path.isdir(dirname): self.makeDir(dirname) - os.rename(fromfile, tofile) + shutil.move(fromfile, tofile) try: existing_files.remove(tofile) except ValueError: diff --git a/couchpotato/core/downloaders/base.py b/couchpotato/core/downloaders/base.py index c910edd2..76f8cdff 100644 --- a/couchpotato/core/downloaders/base.py +++ b/couchpotato/core/downloaders/base.py @@ -1,3 +1,4 @@ +from base64 import b32decode, b16encode from couchpotato.core.event import addEvent from couchpotato.core.helpers.encoding import toSafeString from couchpotato.core.logger import CPLog @@ -48,7 +49,12 @@ class Downloader(Plugin): return is_correct def magnetToTorrent(self, magnet_link): - torrent_hash = re.findall('urn:btih:([\w]{40})', magnet_link)[0] + torrent_hash = re.findall('urn:btih:([\w]{32,40})', magnet_link)[0] + + # Convert base 32 to hex + if len(torrent_hash) == 32: + torrent_hash = b16encode(b32decode(torrent_hash)) + url = 'http://torrage.com/torrent/%s.torrent' % torrent_hash try: diff --git a/couchpotato/core/downloaders/blackhole/main.py b/couchpotato/core/downloaders/blackhole/main.py index 4713adf7..ca38fc49 100644 --- a/couchpotato/core/downloaders/blackhole/main.py +++ b/couchpotato/core/downloaders/blackhole/main.py @@ -11,7 +11,9 @@ class Blackhole(Downloader): type = ['nzb', 'torrent', 'torrent_magnet'] def download(self, data = {}, movie = {}, manual = False, filedata = None): - if self.isDisabled(manual) or (not self.isCorrectType(data.get('type')) or (not self.conf('use_for') in ['both', data.get('type')])): + if self.isDisabled(manual) or \ + (not self.isCorrectType(data.get('type')) or \ + (not self.conf('use_for') in ['both', 'torrent' if 'torrent' in data.get('type') else data.get('type')])): return directory = self.conf('directory') diff --git a/couchpotato/core/downloaders/sabnzbd/main.py b/couchpotato/core/downloaders/sabnzbd/main.py index 86d5459d..abff97d7 100644 --- a/couchpotato/core/downloaders/sabnzbd/main.py +++ b/couchpotato/core/downloaders/sabnzbd/main.py @@ -92,10 +92,9 @@ class Sabnzbd(Downloader): return False for slot in history['queue']['slots']: - if slot['cat'] == self.conf('category'): - log.debug('Found %s in SabNZBd queue, which is %s, with %s left', (slot['filename'], slot['status'], slot['timeleft'])) - if slot['filename'] == nzbname: - return slot['status'].lower() + log.debug('Found %s in SabNZBd queue, which is %s, with %s left', (slot['filename'], slot['status'], slot['timeleft'])) + if slot['filename'] == nzbname: + return slot['status'].lower() # Go through history items params = { @@ -119,44 +118,44 @@ class Sabnzbd(Downloader): return for slot in history['history']['slots']: - if slot['category'] == self.conf('category'): - log.debug('Found %s in SabNZBd history, which has %s', (slot['name'], slot['status'])) - if slot['name'] == nzbname: - if slot['status'] == 'Failed' or slot['fail_message'].strip(): + log.debug('Found %s in SabNZBd history, which has %s', (slot['name'], slot['status'])) + if slot['name'] == nzbname: + # Note: if post process even if failed is on in SabNZBd, it will complete with a fail message + if slot['status'] == 'Failed' or (slot['status'] == 'Completed' and slot['fail_message'].strip()): - # Delete failed download - if self.conf('delete_failed', default = True): + # Delete failed download + if self.conf('delete_failed', default = True): - log.info('%s failed downloading, deleting...', slot['name']) - params = { - 'apikey': self.conf('api_key'), - 'mode': 'history', - 'name': 'delete', - 'del_files': '1', - 'value': slot['nzo_id'] - } - url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params) + log.info('%s failed downloading, deleting...', slot['name']) + params = { + 'apikey': self.conf('api_key'), + 'mode': 'history', + 'name': 'delete', + 'del_files': '1', + 'value': slot['nzo_id'] + } + url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params) - try: - sab = self.urlopen(url, timeout = 60, show_error = False) - except: - log.error('Failed deleting: %s', traceback.format_exc()) - return False + try: + sab = self.urlopen(url, timeout = 60, show_error = False) + except: + log.error('Failed deleting: %s', traceback.format_exc()) + return False - result = sab.strip() - if not result: - log.error("SABnzbd didn't return anything.") + result = sab.strip() + if not result: + log.error("SABnzbd didn't return anything.") - log.debug("Result text from SAB: " + result[:40]) - if result == "ok": - log.info('SabNZBd deleted failed release %s successfully.', slot['name']) - elif result == "Missing authentication": - log.error("Incorrect username/password or API?.") - else: - log.error("Unknown error: " + result[:40]) + log.debug("Result text from SAB: " + result[:40]) + if result == "ok": + log.info('SabNZBd deleted failed release %s successfully.', slot['name']) + elif result == "Missing authentication": + log.error("Incorrect username/password or API?.") + else: + log.error("Unknown error: " + result[:40]) - return 'failed' - else: - return slot['status'].lower() + return 'failed' + else: + return slot['status'].lower() return 'not_found' diff --git a/couchpotato/core/helpers/encoding.py b/couchpotato/core/helpers/encoding.py index 9b1f575c..b4f4b70a 100644 --- a/couchpotato/core/helpers/encoding.py +++ b/couchpotato/core/helpers/encoding.py @@ -2,6 +2,7 @@ from couchpotato.core.logger import CPLog from string import ascii_letters, digits from urllib import quote_plus import re +import traceback import unicodedata log = CPLog(__name__) @@ -30,8 +31,8 @@ def toUnicode(original, *args): return ek(original, *args) except: raise - except UnicodeDecodeError: - log.error('Unable to decode value: %s... ', repr(original)[:20]) + except: + log.error('Unable to decode value "%s..." : %s ', (repr(original)[:20], traceback.format_exc())) ascii_text = str(original).encode('string_escape') return toUnicode(ascii_text) diff --git a/couchpotato/core/plugins/renamer/main.py b/couchpotato/core/plugins/renamer/main.py index 9a443ed2..437374ea 100644 --- a/couchpotato/core/plugins/renamer/main.py +++ b/couchpotato/core/plugins/renamer/main.py @@ -314,6 +314,7 @@ class Renamer(Plugin): break # Remove files + delete_folders = [] for src in remove_files: if isinstance(src, File): @@ -329,13 +330,17 @@ class Renamer(Plugin): os.remove(src) parent_dir = os.path.normpath(os.path.dirname(src)) - if os.path.isdir(parent_dir) and destination != parent_dir: - self.deleteEmptyFolder(parent_dir, show_error = False) + if delete_folders.count(parent_dir) == 0 and os.path.isdir(parent_dir) and destination != parent_dir: + delete_folders.append(parent_dir) except: log.error('Failed removing %s: %s', (src, traceback.format_exc())) self.tagDir(group, 'failed_remove') + # Delete leftover folder from older releases + for delete_folder in delete_folders: + self.deleteEmptyFolder(delete_folder, show_error = False) + # Rename all files marked group['renamed_files'] = [] for src in rename_files: diff --git a/couchpotato/core/plugins/searcher/main.py b/couchpotato/core/plugins/searcher/main.py index afabeae8..c464741f 100644 --- a/couchpotato/core/plugins/searcher/main.py +++ b/couchpotato/core/plugins/searcher/main.py @@ -23,7 +23,7 @@ class Searcher(Plugin): in_progress = False def __init__(self): - addEvent('searcher.all', self.all_movies) + addEvent('searcher.all', self.allMovies) addEvent('searcher.single', self.single) addEvent('searcher.correct_movie', self.correctMovie) addEvent('searcher.download', self.download) @@ -37,11 +37,11 @@ class Searcher(Plugin): }) # Schedule cronjob - fireEvent('schedule.cron', 'searcher.all', self.all_movies, day = self.conf('cron_day'), hour = self.conf('cron_hour'), minute = self.conf('cron_minute')) + fireEvent('schedule.cron', 'searcher.all', self.allMovies, day = self.conf('cron_day'), hour = self.conf('cron_hour'), minute = self.conf('cron_minute')) fireEvent('schedule.interval', 'searcher.check_snatched', self.checkSnatched, minutes = self.conf('run_every')) - def all_movies(self): + def allMovies(self): if self.in_progress: log.info('Search already in progress') @@ -328,10 +328,6 @@ class Searcher(Plugin): if len(movie_words) <= 2 and self.correctYear([nzb['name']], movie['library']['year'], 0): return True - # Get the nfo and see if it contains the proper imdb url - if self.checkNFO(nzb['name'], movie['library']['identifier']): - return True - log.info("Wrong: %s, undetermined naming. Looking for '%s (%s)'" % (nzb['name'], movie_name, movie['library']['year'])) return False @@ -406,19 +402,6 @@ class Searcher(Plugin): return False - def checkNFO(self, check_name, imdb_id): - cache_key = 'srrdb.com %s' % simplifyString(check_name) - - nfo = self.getCache(cache_key) - if not nfo: - try: - nfo = self.urlopen('http://www.srrdb.com/showfile.php?release=%s' % check_name, show_error = False) - self.setCache(cache_key, nfo) - except: - pass - - return nfo and getImdb(nfo) == imdb_id - def couldBeReleased(self, wanted_quality, dates, pre_releases): now = int(time.time()) @@ -453,6 +436,8 @@ class Searcher(Plugin): ignored_status = fireEvent('status.get', 'ignored', single = True) failed_status = fireEvent('status.get', 'failed', single = True) + done_status = fireEvent('status.get', 'done', single = True) + db = get_session() rels = db.query(Release).filter_by(status_id = snatched_status.get('id')) @@ -470,6 +455,13 @@ class Searcher(Plugin): log.debug('Checking snatched movie: %s' , default_title) + # Check if movie has already completed and is manage tab (legacy db correction) + if rel.movie.status_id == done_status.get('id'): + log.debug('Found a completed movie with a snatched release : %s. Setting release status to ignored...' , default_title) + rel.status_id = ignored_status.get('id') + db.commit() + continue + item = {} for info in rel.info: item[info.identifier] = info.value @@ -520,8 +512,6 @@ class Searcher(Plugin): ignored_status = fireEvent('status.get', 'ignored', single = True) try: - movie_dict = fireEvent('movie.get', movie_id, single = True) - db = get_session() rels = db.query(Release).filter_by( status_id = snatched_status.get('id'), @@ -532,6 +522,7 @@ class Searcher(Plugin): rel.status_id = ignored_status.get('id') db.commit() + movie_dict = fireEvent('movie.get', movie_id, single = True) log.info('Trying next release for: %s', getTitle(movie_dict['library'])) fireEvent('searcher.single', movie_dict) diff --git a/couchpotato/core/plugins/userscript/main.py b/couchpotato/core/plugins/userscript/main.py index 9c5f05f0..e5999ab3 100644 --- a/couchpotato/core/plugins/userscript/main.py +++ b/couchpotato/core/plugins/userscript/main.py @@ -15,7 +15,7 @@ log = CPLog(__name__) class Userscript(Plugin): - version = 2 + version = 3 def __init__(self): addApiView('userscript.get//', self.getUserScript, static = True) diff --git a/couchpotato/core/plugins/userscript/template.js b/couchpotato/core/plugins/userscript/template.js index 8b07272b..c30fad5b 100644 --- a/couchpotato/core/plugins/userscript/template.js +++ b/couchpotato/core/plugins/userscript/template.js @@ -1,6 +1,7 @@ // ==UserScript== // @name CouchPotato UserScript // @description Add movies like a real CouchPotato +// @grant none // @version {{version}} // @match {{host}}* @@ -44,21 +45,19 @@ function create() { return A; } -if (typeof GM_addStyle == 'undefined'){ - GM_addStyle = function(css) { - var head = document.getElementsByTagName('head')[0], - style = document.createElement('style'); - if (!head) - return; +var addStyle = function(css) { + var head = document.getElementsByTagName('head')[0], + style = document.createElement('style'); + if (!head) + return; - style.type = 'text/css'; - style.textContent = css; - head.appendChild(style); - } + style.type = 'text/css'; + style.textContent = css; + head.appendChild(style); } // Styles -GM_addStyle('\ +addStyle('\ #cp_popup { font-family: "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif; -moz-border-radius: 6px 0px 0px 6px; -webkit-border-radius: 6px 0px 0px 6px; border-radius: 6px 0px 0px 6px; -moz-box-shadow: 0 0 20px rgba(0,0,0,0.5); -webkit-box-shadow: 0 0 20px rgba(0,0,0,0.5); box-shadow: 0 0 20px rgba(0,0,0,0.5); position:fixed; z-index:9999; bottom:0; right:0; font-size:15px; margin: 20px 0; display: block; background:#4E5969; } \ #cp_popup.opened { width: 492px; } \ #cp_popup a#add_to { cursor:pointer; text-align:center; text-decoration:none; color: #000; display:block; padding:5px 0 5px 5px; } \ diff --git a/couchpotato/core/providers/movie/_modifier/main.py b/couchpotato/core/providers/movie/_modifier/main.py index d0886702..5af1659e 100644 --- a/couchpotato/core/providers/movie/_modifier/main.py +++ b/couchpotato/core/providers/movie/_modifier/main.py @@ -31,7 +31,7 @@ class MovieResultModifier(Plugin): order.append(imdb) if item.get('via_imdb'): - if order.index(imdb): + if order.count(imdb): order.remove(imdb) order.insert(0, imdb) diff --git a/couchpotato/core/providers/movie/imdbapi/main.py b/couchpotato/core/providers/movie/imdbapi/main.py index c81a9aba..9238f45a 100644 --- a/couchpotato/core/providers/movie/imdbapi/main.py +++ b/couchpotato/core/providers/movie/imdbapi/main.py @@ -27,7 +27,7 @@ class IMDBAPI(MovieProvider): name_year = fireEvent('scanner.name_year', q, single = True) - if not q or not name_year.get('name'): + if not q or not name_year or (name_year and not name_year.get('name')): return [] cache_key = 'imdbapi.cache.%s' % q diff --git a/couchpotato/core/providers/torrent/passthepopcorn/__init__.py b/couchpotato/core/providers/torrent/passthepopcorn/__init__.py new file mode 100644 index 00000000..67bf6665 --- /dev/null +++ b/couchpotato/core/providers/torrent/passthepopcorn/__init__.py @@ -0,0 +1,36 @@ +from main import PassThePopcorn + +def start(): + return PassThePopcorn() + +config = [{ + 'name': 'passthepopcorn', + 'groups': [{ + 'tab': 'searcher', + 'subtab': 'providers', + 'name': 'PassThePopcorn', + 'description': 'See PassThePopcorn.me', + 'options': [ + { + 'name': 'enabled', + 'type': 'enabler', + 'default': False + }, + { + 'name': 'domain', + 'advanced': True, + 'label': 'Proxy server', + 'description': 'Domain for requests (HTTPS only!), keep empty to use default (tls.passthepopcorn.me).', + }, + { + 'name': 'username', + 'default': '', + }, + { + 'name': 'password', + 'default': '', + 'type': 'password', + } + ], + }] +}] diff --git a/couchpotato/core/providers/torrent/passthepopcorn/main.py b/couchpotato/core/providers/torrent/passthepopcorn/main.py new file mode 100644 index 00000000..f9416f74 --- /dev/null +++ b/couchpotato/core/providers/torrent/passthepopcorn/main.py @@ -0,0 +1,254 @@ +from couchpotato.core.event import fireEvent +from couchpotato.core.helpers.encoding import tryUrlencode +from couchpotato.core.helpers.variable import getTitle, tryInt, mergeDicts +from couchpotato.core.logger import CPLog +from couchpotato.core.providers.torrent.base import TorrentProvider +from dateutil.parser import parse +import cookielib +import htmlentitydefs +import json +import re +import time +import traceback +import urllib2 + +log = CPLog(__name__) + + +class PassThePopcorn(TorrentProvider): + + urls = { + 'domain': 'https://tls.passthepopcorn.me', + 'detail': 'https://tls.passthepopcorn.me/torrents.php?torrentid=%s', + 'torrent': 'https://tls.passthepopcorn.me/torrents.php', + 'login': 'https://tls.passthepopcorn.me/login.php', + 'search': 'https://tls.passthepopcorn.me/search/%s/0/7/%d' + } + + quality_search_params = { + 'bd50': {'media': 'Blu-ray', 'format': 'BD50'}, + '1080p': {'resolution': '1080p'}, + '720p': {'resolution': '720p'}, + 'brrip': {'media': 'Blu-ray'}, + 'dvdr': {'resolution': 'anysd'}, + 'dvdrip': {'media': 'DVD'}, + 'scr': {'media': 'DVD-Screener'}, + 'r5': {'media': 'R5'}, + 'tc': {'media': 'TC'}, + 'ts': {'media': 'TS'}, + 'cam': {'media': 'CAM'} + } + + post_search_filters = { + 'bd50': {'Codec': ['BD50']}, + '1080p': {'Resolution': ['1080p']}, + '720p': {'Resolution': ['720p']}, + 'brrip': {'Source': ['Blu-ray'], 'Quality': ['High Definition'], 'Container': ['!ISO']}, + 'dvdr': {'Codec': ['DVD5', 'DVD9']}, + 'dvdrip': {'Source': ['DVD'], 'Codec': ['!DVD5', '!DVD9']}, + 'scr': {'Source': ['DVD-Screener']}, + 'r5': {'Source': ['R5']}, + 'tc': {'Source': ['TC']}, + 'ts': {'Source': ['TS']}, + 'cam': {'Source': ['CAM']} + } + + class NotLoggedInHTTPError(urllib2.HTTPError): + def __init__(self, url, code, msg, headers, fp): + urllib2.HTTPError.__init__(self, url, code, msg, headers, fp) + + class PTPHTTPRedirectHandler(urllib2.HTTPRedirectHandler): + def http_error_302(self, req, fp, code, msg, headers): + log.debug("302 detected; redirected to %s" % headers['Location']) + if (headers['Location'] != 'login.php'): + return urllib2.HTTPRedirectHandler.http_error_302(self, req, fp, code, msg, headers) + else: + raise PassThePopcorn.NotLoggedInHTTPError(req.get_full_url(), code, msg, headers, fp) + + def search(self, movie, quality): + + results = [] + + if self.isDisabled(): + return results + + movie_title = getTitle(movie['library']) + quality_id = quality['identifier'] + + log.info('Searching for %s at quality %s' % (movie_title, quality_id)) + + params = mergeDicts(self.quality_search_params[quality_id].copy(), { + 'order_by': 'relevance', + 'order_way': 'descending', + 'searchstr': movie['library']['identifier'] + }) + + # Do login for the cookies + if not self.login_opener and not self.login(): + return results + + try: + url = '%s?json=noredirect&%s' % (self.urls['torrent'], tryUrlencode(params)) + txt = self.urlopen(url, opener = self.login_opener) + res = json.loads(txt) + except: + log.error('Search on PassThePopcorn.me (%s) failed (could not decode JSON)' % params) + return [] + + try: + if not 'Movies' in res: + log.info("PTP search returned nothing for '%s' at quality '%s' with search parameters %s" % (movie_title, quality_id, params)) + return [] + + authkey = res['AuthKey'] + passkey = res['PassKey'] + + for ptpmovie in res['Movies']: + if not 'Torrents' in ptpmovie: + log.debug('Movie %s (%s) has NO torrents' % (ptpmovie['Title'], ptpmovie['Year'])) + continue + + log.debug('Movie %s (%s) has %d torrents' % (ptpmovie['Title'], ptpmovie['Year'], len(ptpmovie['Torrents']))) + for torrent in ptpmovie['Torrents']: + torrent_id = tryInt(torrent['Id']) + torrentdesc = '%s %s %s' % (torrent['Resolution'], torrent['Source'], torrent['Codec']) + + if 'GoldenPopcorn' in torrent and torrent['GoldenPopcorn']: + torrentdesc += ' HQ' + if 'Scene' in torrent and torrent['Scene']: + torrentdesc += ' Scene' + if 'RemasterTitle' in torrent and torrent['RemasterTitle']: + # eliminate odd characters... + torrentdesc += self.htmlToASCII(' %s' % torrent['RemasterTitle']) + + torrentdesc += ' (%s)' % quality_id + torrent_name = re.sub('[^A-Za-z0-9\-_ \(\).]+', '', '%s (%s) - %s' % (movie_title, ptpmovie['Year'], torrentdesc)) + + def extra_check(item): + return self.torrentMeetsQualitySpec(item, type) + + def extra_score(item): + return 50 if torrent['GoldenPopcorn'] else 0 + + new = { + 'id': torrent_id, + 'type': 'torrent', + 'provider': self.getName(), + 'name': torrent_name, + 'description': '', + 'url': '%s?action=download&id=%d&authkey=%s&torrent_pass=%s' % (self.urls['torrent'], torrent_id, authkey, passkey), + 'detail_url': self.urls['detail'] % torrent_id, + 'date': tryInt(time.mktime(parse(torrent['UploadTime']).timetuple())), + 'size': tryInt(torrent['Size']) / (1024 * 1024), + 'provider': self.getName(), + 'seeders': tryInt(torrent['Seeders']), + 'leechers': tryInt(torrent['Leechers']), + 'extra_score': extra_score, + 'extra_check': extra_check, + 'download': self.loginDownload, + } + + new['score'] = fireEvent('score.calculate', new, movie, single = True) + + if fireEvent('searcher.correct_movie', nzb = new, movie = movie, quality = quality): + results.append(new) + self.found(new) + + return results + except: + log.error('Failed getting results from %s: %s', (self.getName(), traceback.format_exc())) + + return [] + + def login(self): + + cookieprocessor = urllib2.HTTPCookieProcessor(cookielib.CookieJar()) + opener = urllib2.build_opener(cookieprocessor, PassThePopcorn.PTPHTTPRedirectHandler()) + opener.addheaders = [ + ('User-Agent', 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.75 Safari/537.1'), + ('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'), + ('Accept-Language', 'en-gb,en;q=0.5'), + ('Accept-Charset', 'ISO-8859-1,utf-8;q=0.7,*;q=0.7'), + ('Keep-Alive', '115'), + ('Connection', 'keep-alive'), + ('Cache-Control', 'max-age=0'), + ] + + try: + response = opener.open(self.urls['login'], self.getLoginParams()) + except urllib2.URLError as e: + log.error('Login to PassThePopcorn failed: %s' % e) + return False + + if response.getcode() == 200: + log.debug('Login HTTP status 200; seems successful') + self.login_opener = opener + return True + else: + log.error('Login to PassThePopcorn failed: returned code %d' % response.getcode()) + return False + + def torrentMeetsQualitySpec(self, torrent, quality): + + if not quality in self.post_search_filters: + return True + + for field, specs in self.post_search_filters[quality].items(): + matches_one = False + seen_one = False + + if not field in torrent: + log.debug('Torrent with ID %s has no field "%s"; cannot apply post-search-filter for quality "%s"' % (torrent['Id'], field, quality)) + continue + + for spec in specs: + if len(spec) > 0 and spec[0] == '!': + # a negative rule; if the field matches, return False + if torrent[field] == spec[1:]: + return False + else: + # a positive rule; if any of the possible positive values match the field, return True + seen_one = True + if torrent[field] == spec: + matches_one = True + + if seen_one and not matches_one: + return False + + return True + + def htmlToUnicode(self, text): + def fixup(m): + text = m.group(0) + if text[:2] == "&#": + # character reference + try: + if text[:3] == "&#x": + return unichr(int(text[3:-1], 16)) + else: + return unichr(int(text[2:-1])) + except ValueError: + pass + else: + # named entity + try: + text = unichr(htmlentitydefs.name2codepoint[text[1:-1]]) + except KeyError: + pass + return text # leave as is + return re.sub("&#?\w+;", fixup, u'%s' % text) + + def unicodeToASCII(self, text): + import unicodedata + return ''.join(c for c in unicodedata.normalize('NFKD', text) if unicodedata.category(c) != 'Mn') + + def htmlToASCII(self, text): + return self.unicodeToASCII(self.htmlToUnicode(text)) + + def getLoginParams(self): + return tryUrlencode({ + 'username': self.conf('username'), + 'password': self.conf('password'), + 'keeplogged': '1', + 'login': 'Login' + }) diff --git a/couchpotato/core/providers/torrent/thepiratebay/main.py b/couchpotato/core/providers/torrent/thepiratebay/main.py index 7b102958..a4ce3631 100644 --- a/couchpotato/core/providers/torrent/thepiratebay/main.py +++ b/couchpotato/core/providers/torrent/thepiratebay/main.py @@ -37,6 +37,8 @@ class ThePirateBay(TorrentProvider): 'https://piratereverse.info', 'https://tpb.pirateparty.org.uk', 'https://argumentomteemigreren.nl', + 'https://livepirate.com/', + 'https://www.getpirate.com/', ] def __init__(self): diff --git a/couchpotato/core/providers/trailer/hdtrailers/main.py b/couchpotato/core/providers/trailer/hdtrailers/main.py index d11f9231..09e24036 100644 --- a/couchpotato/core/providers/trailer/hdtrailers/main.py +++ b/couchpotato/core/providers/trailer/hdtrailers/main.py @@ -3,7 +3,7 @@ from couchpotato.core.helpers.encoding import tryUrlencode from couchpotato.core.helpers.variable import mergeDicts, getTitle from couchpotato.core.logger import CPLog from couchpotato.core.providers.trailer.base import TrailerProvider -from string import letters, digits +from string import digits, ascii_letters import re log = CPLog(__name__) @@ -46,7 +46,7 @@ class HDTrailers(TrailerProvider): movie_name = getTitle(group['library']) - url = "%s?%s" % (self.url['backup'], tryUrlencode({'s':movie_name})) + url = "%s?%s" % (self.urls['backup'], tryUrlencode({'s':movie_name})) data = self.getCache('hdtrailers.alt.%s' % group['library']['identifier'], url) try: @@ -100,7 +100,7 @@ class HDTrailers(TrailerProvider): return results def movieUrlName(self, string): - safe_chars = letters + digits + ' ' + safe_chars = ascii_letters + digits + ' ' r = ''.join([char if char in safe_chars else ' ' for char in string]) name = re.sub('\s+' , '-', r).lower() diff --git a/couchpotato/static/style/page/settings.css b/couchpotato/static/style/page/settings.css index b7fbaabf..686c3b21 100644 --- a/couchpotato/static/style/page/settings.css +++ b/couchpotato/static/style/page/settings.css @@ -24,39 +24,36 @@ } .page.settings .tabs a { display: block; - padding: 11px 15px; + padding: 7px 15px; font-weight: normal; - transition: all 0.1s ease-in-out; + transition: all 0.3s ease-in-out; color: rgba(255, 255, 255, 0.8); + text-shadow: none; } - .page.settings .tabs a:hover, .page.settings .tabs .active a { + .page.settings .tabs a:hover, + .page.settings .tabs .active a { background: rgb(78, 89, 105); - font-weight: bold; - font-size: 25px; color: #fff; } + .page.settings .tabs > li { + border-bottom: 1px solid rgb(78, 89, 105); + } .page.settings .tabs .subtabs { list-style: none; padding: 0; - overflow: hidden; - transition: all 1s ease-in-out; - max-height: 0; + margin: -5px 0 10px; } - .page.settings .tabs > .active .subtabs { - max-height: 300px; - } .page.settings .tabs .subtabs a { - font-size: 15px; - padding: 1px 15px; + font-size: 13px; + padding: 0 15px; font-weight: normal; - color: rgba(255, 255, 255, 0.8); - background: rgba(78, 89, 105, 0.4); + transition: all .3s ease-in-out; + color: rgba(255, 255, 255, 0.7); } .page.settings .tabs .subtabs .active a { - font-weight: bold; color: #fff; background: rgb(78, 89, 105); } diff --git a/couchpotato/templates/_desktop.html b/couchpotato/templates/_desktop.html index 0c228dca..80a095e3 100644 --- a/couchpotato/templates/_desktop.html +++ b/couchpotato/templates/_desktop.html @@ -43,7 +43,7 @@ - +