diff --git a/couchpotato/core/_base/_core.py b/couchpotato/core/_base/_core.py index b3efa45..f455340 100644 --- a/couchpotato/core/_base/_core.py +++ b/couchpotato/core/_base/_core.py @@ -5,6 +5,7 @@ import signal import time import traceback import webbrowser +import sys from couchpotato.api import addApiView from couchpotato.core.event import fireEvent, addEvent @@ -64,6 +65,14 @@ class Core(Plugin): import socket socket.setdefaulttimeout(30) + # Don't check ssl by default + try: + if sys.version_info >= (2, 7, 9): + import ssl + ssl._create_default_https_context = ssl._create_unverified_context + except: + log.debug('Failed setting default ssl context: %s', traceback.format_exc()) + def md5Password(self, value): return md5(value) if value else '' diff --git a/couchpotato/core/media/_base/providers/torrent/rarbg.py b/couchpotato/core/media/_base/providers/torrent/rarbg.py new file mode 100644 index 0000000..807f536 --- /dev/null +++ b/couchpotato/core/media/_base/providers/torrent/rarbg.py @@ -0,0 +1,232 @@ +import re +import traceback +import random +import time +from datetime import datetime + +from couchpotato.core.helpers.variable import tryInt, getIdentifier +from couchpotato.core.logger import CPLog +from couchpotato.core.media._base.providers.torrent.base import TorrentMagnetProvider + +log = CPLog(__name__) +tokenreceived = 0 +rarbgtoken = 0 + +class Base(TorrentMagnetProvider): + + urls = { + 'test': 'https://torrentapi.org/pubapi_v2.php?app_id=couchpotato', + 'token': 'https://torrentapi.org/pubapi_v2.php?get_token=get_token&app_id=couchpotato', + 'search': 'https://torrentapi.org/pubapi_v2.php?token=%s&mode=search&search_imdb=%s&min_seeders=%s&min_leechers' + '=%s&ranked=%s&category=movies&format=json_extended&app_id=couchpotato', + } + + http_time_between_calls = 2 # Seconds + user_agent = 'CouchPotato/1.0' + + @staticmethod + def find_info(filename): + # CODEC # + codec = 'x264' + v = re.search("(?i)(x265|h265|h\.265)", filename) + if v: + codec = 'x265' + + v = re.search("(?i)(xvid)", filename) + if v: + codec = 'xvid' + + # RESOLUTION # + resolution = 'SD' + a = re.search("(?i)(720p)", filename) + if a: + resolution = '720p' + + a = re.search("(?i)(1080p)", filename) + if a: + resolution = '1080p' + + a = re.search("(?i)(2160p)", filename) + if a: + resolution = '2160p' + + # SOURCE # + source = 'HD-Rip' + s = re.search("(?i)(WEB-DL|WEB_DL|WEB\.DL)", filename) + if s: + source = 'WEB-DL' + + s = re.search("(?i)(WEBRIP)", filename) + if s: + source = 'WEBRIP' + + s = re.search("(?i)(DVDR|DVDRip|DVD-Rip)", filename) + if s: + source = 'DVD-R' + + s = re.search("(?i)(BRRIP|BDRIP|BluRay)", filename) + if s: + source = 'BR-Rip' + + s = re.search("(?i)BluRay(.*)REMUX", filename) + if s: + source = 'BluRay-Remux' + + s = re.search("(?i)BluRay(.*)\.(AVC|VC-1)\.", filename) + if s: + source = 'BluRay-Full' + + return_info = [codec, resolution, source] + return return_info + + @staticmethod + def get_token(self): + global tokenreceived + global rarbgtoken + now = int(time.time()) + + if (rarbgtoken == 0) or (tokenreceived == 0) or (now > (tokenreceived+(15*60))): + log.debug("RARBG: Getting Rarbg token") + tokendata = self.getJsonData(self.urls['token']) + + if tokendata: + try: + tokenreceived = int(time.time()) + rarbgtoken = tokendata['token'] + log.debug("RARBG: GOT TOKEN: %s", rarbgtoken) + except RuntimeError: + log.error('RARBG: Failed getting token from Rarbg') + rarbgtoken = 0 + + # return token + + def _search(self, movie, quality, results): + hasresults = 0 + curryear = datetime.now().year + self.get_token(self) + movieid = getIdentifier(movie) + try: + movieyear = movie['info']['year'] + except: + log.error("RARBG: Couldn't get movie year") + movieyear = 0 + + if (rarbgtoken != 0) and (movieyear == 0 or movieyear <= curryear): + data = self.getJsonData(self.urls['search'] % (rarbgtoken, movieid, self.conf('min_seeders'), + self.conf('min_leechers'), self.conf('ranked_only'))) + + if data: + if 'error_code' in data: + if data['error'] == 'No results found': + log.debug("RARBG: No results returned from Rarbg") + else: + if data['error_code'] == 10: + log.error(data['error'], movieid) + else: + log.error("RARBG: There is an error in the returned JSON: %s", data['error']) + else: + hasresults = 1 + try: + if hasresults: + for result in data['torrent_results']: + name = result['title'] + titlesplit = re.split("-", name) + releasegroup = titlesplit[len(titlesplit)-1] + + xtrainfo = self.find_info(name) + encoding = xtrainfo[0] + resolution = xtrainfo[1] + # source = xtrainfo[2] + pubdate = result['pubdate'] # .strip(" +0000") + try: + pubdate = datetime.strptime(pubdate, '%Y-%m-%d %H:%M:%S +0000') + now = datetime.utcnow() + age = (now - pubdate).days + except ValueError: + log.debug("RARBG: Bad pubdate") + age = 0 + + torrentscore = self.conf('extra_score') + seeders = tryInt(result['seeders']) + torrent_desc = '/ %s / %s / %s / %s seeders' % (releasegroup, resolution, encoding, seeders) + + if seeders == 0: + torrentscore = 0 + + sliceyear = result['pubdate'][0:4] + year = tryInt(sliceyear) + + results.append({ + 'id': random.randint(100, 9999), + 'name': re.sub('[^A-Za-z0-9\-_ \(\).]+', '', '%s (%s) %s' % (name, year, torrent_desc)), + 'url': result['download'], + 'detail_url': result['info_page'], + 'size': tryInt(result['size']/1048576), # rarbg sends in bytes + 'seeders': tryInt(result['seeders']), + 'leechers': tryInt(result['leechers']), + 'age': tryInt(age), + 'score': torrentscore + }) + + except RuntimeError: + log.error('RARBG: Failed getting results from %s: %s', (self.getName(), traceback.format_exc())) +config = [{ + 'name': 'rarbg', + 'groups': [ + { + 'tab': 'searcher', + 'list': 'torrent_providers', + 'name': 'Rarbg', + 'wizard': True, + 'description': 'RARBG', + 'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAB+UlEQVQ4jYXTP2hcRxDH8c8JJZjbYNy8V7gIr0qhg5AiFnETX' + '+PmVAtSmKDaUhUiFyGxjXFlp0hhHy5cqFd9lSGcU55cBU6EEMIj5dsmMewSjNGmOJ3852wysMyww37n94OdXimlh49xDR/hxGr' + '8hZ/xx0qnlHK5lPKk/H/8U0r5oZTyQSmltzzr+AKfT+ed8UFLeHNAH1UVbA2r88NBfQcX8O2yv74sUqKNWT+T01sy2+zpUbS/w' + '/awvo7H+O0NQEA/LPKlQWXrSgUmR9HxcZQwmbZGw/pc4MsVAIT+IjcNw80aTjaaem1vPCNlGakj1C6uWFiqeDtyTvoyqAKhBn+' + '+E7CkxC6Zzjop57XpUSenpIuMhpXAc/zyHkAicRSjw6fHZ1ewPdqwszWAB2hXACln8+NWSlld9zX9YN7GhajQXz5+joPXR66de' + 'U1J27Zi7FzaqE0OdmwNGzF2Ymzt3j+E8/gJH64AFlozKS4+Be7tjwyaIKVsOpnavX0II9x8ByDLKco5SwvjL0MI/z64tyOcwsf' + 'jQw8PJvAdvsb6GSBlxI7UyTnD37i7OWhe3NrflvOit3djbDKdwR181SulXMXdrkubbdvKaOpK09S/4jP8iG9m8zmJjCoEg0HzO' + '77vna7zp7ju1TqfYIyZxT7dwCd4eWr7BR7h2X8S6gShJlbKYQAAAABJRU5ErkJggg==', + 'options': [ + { + 'name': 'enabled', + 'type': 'enabler', + 'default': False, + }, + { + 'name': 'ranked_only', + 'advanced': True, + 'label': 'Ranked Only', + 'type': 'int', + 'default': 1, + 'description': 'Only ranked torrents (internal), scene releases, rarbg releases. ' + 'Enter 1 (true) or 0 (false)', + }, + { + 'name': 'min_seeders', + 'advanced': True, + 'label': 'Minimum Seeders', + 'type': 'int', + 'default': 10, + 'description': 'Minium amount of seeders the release must have.', + }, + { + 'name': 'min_leechers', + 'advanced': True, + 'label': 'Minimum leechers', + 'type': 'int', + 'default': 0, + 'description': 'Minium amount of leechers the release must have.', + }, + { + 'name': 'extra_score', + 'advanced': True, + 'label': 'Extra Score', + 'type': 'int', + 'default': 0, + 'description': 'Starting score for each release found via this provider.', + } + ], + }, + ], +}] diff --git a/couchpotato/core/media/movie/providers/torrent/rarbg.py b/couchpotato/core/media/movie/providers/torrent/rarbg.py new file mode 100644 index 0000000..849388e --- /dev/null +++ b/couchpotato/core/media/movie/providers/torrent/rarbg.py @@ -0,0 +1,11 @@ +from couchpotato.core.logger import CPLog +from couchpotato.core.media._base.providers.torrent.rarbg import Base +from couchpotato.core.media.movie.providers.base import MovieProvider + +log = CPLog(__name__) + +autoload = 'Rarbg' + + +class Rarbg(MovieProvider, Base): + pass diff --git a/couchpotato/core/notifications/slack.py b/couchpotato/core/notifications/slack.py new file mode 100644 index 0000000..5da8456 --- /dev/null +++ b/couchpotato/core/notifications/slack.py @@ -0,0 +1,126 @@ +import json +from couchpotato.core.logger import CPLog +from couchpotato.core.notifications.base import Notification + +log = CPLog(__name__) +autoload = 'Slack' + + +class Slack(Notification): + url = 'https://slack.com/api/chat.postMessage' + required_confs = ('token', 'channels',) + + def notify(self, message='', data=None, listener=None): + for key in self.required_confs: + if not self.conf(key): + log.warning('Slack notifications are enabled, but ' + '"{0}" is not specified.'.format(key)) + return False + + data = data or {} + message = message.strip() + + if self.conf('include_imdb') and 'identifier' in data: + template = ' http://www.imdb.com/title/{0[identifier]}/' + message += template.format(data) + + payload = { + 'token': self.conf('token'), + 'text': message, + 'username': self.conf('bot_name'), + 'unfurl_links': self.conf('include_imdb'), + 'as_user': self.conf('as_user'), + 'icon_url': self.conf('icon_url'), + 'icon_emoji': self.conf('icon_emoji') + } + + channels = self.conf('channels').split(',') + for channel in channels: + payload['channel'] = channel.strip() + response = self.urlopen(self.url, data=payload) + response = json.loads(response) + if not response['ok']: + log.warning('Notification sending to Slack has failed. Error ' + 'code: %s.', response['error']) + return False + return True + + +config = [{ + 'name': 'slack', + 'groups': [ + { + 'tab': 'notifications', + 'list': 'notification_providers', + 'name': 'slack', + 'options': [ + { + 'name': 'enabled', + 'default': 0, + 'type': 'enabler', + }, + { + 'name': 'token', + 'description': ( + 'Your Slack authentication token.', + 'Can be created at https://api.slack.com/web' + ) + }, + { + 'name': 'channels', + 'description': ( + 'Channel to send notifications to.', + 'Can be a public channel, private group or IM ' + 'channel. Can be an encoded ID or a name ' + '(staring with a hashtag, e.g. #general). ' + 'Separate with commas in order to notify multiple ' + 'channels. It is however recommended to send ' + 'notifications to only one channel due to ' + 'the Slack API rate limits.' + ) + }, + { + 'name': 'include_imdb', + 'default': True, + 'type': 'bool', + 'descrpition': 'Include a link to the movie page on IMDB.' + }, + { + 'name': 'bot_name', + 'description': 'Name of bot.', + 'default': 'CouchPotato', + 'advanced': True, + }, + { + 'name': 'as_user', + 'description': 'Send message as the authentication token ' + ' user.', + 'default': False, + 'type': 'bool', + 'advanced': True + }, + { + 'name': 'icon_url', + 'description': 'URL to an image to use as the icon for ' + 'notifications.', + 'advanced': True, + }, + { + 'name': 'icon_emoji', + 'description': ( + 'Emoji to use as the icon for notifications.', + 'Overrides icon_url' + ), + 'advanced': True, + }, + { + 'name': 'on_snatch', + 'default': 0, + 'type': 'bool', + 'advanced': True, + 'description': 'Also send message when movie is snatched.', + }, + ], + } + ], +}] diff --git a/couchpotato/core/plugins/base.py b/couchpotato/core/plugins/base.py index dc5a5f7..57a31e2 100644 --- a/couchpotato/core/plugins/base.py +++ b/couchpotato/core/plugins/base.py @@ -145,7 +145,7 @@ class Plugin(object): f.close() os.chmod(path, Env.getPermission('file')) except: - log.error('Unable writing to file "%s": %s', (path, traceback.format_exc())) + log.error('Unable to write file "%s": %s', (path, traceback.format_exc())) if os.path.isfile(path): os.remove(path) diff --git a/couchpotato/core/plugins/renamer.py b/couchpotato/core/plugins/renamer.py index aa983b1..2874983 100755 --- a/couchpotato/core/plugins/renamer.py +++ b/couchpotato/core/plugins/renamer.py @@ -1447,7 +1447,7 @@ config = [{ 'default': 'link', 'type': 'dropdown', 'values': [('Link', 'link'), ('Copy', 'copy'), ('Move', 'move')], - 'description': 'See above. It is prefered to use link when downloading torrents as it will save you space, while still beeing able to seed.', + 'description': 'See above. It is prefered to use link when downloading torrents as it will save you space, while still being able to seed.', 'advanced': True, }, { diff --git a/couchpotato/templates/login.html b/couchpotato/templates/login.html index 5a922f9..c35c213 100644 --- a/couchpotato/templates/login.html +++ b/couchpotato/templates/login.html @@ -36,8 +36,8 @@

CouchPotato

-
-
+
+
diff --git a/init/freebsd b/init/freebsd index d389933..bf67b48 100644 --- a/init/freebsd +++ b/init/freebsd @@ -1,7 +1,7 @@ #!/bin/sh # # PROVIDE: couchpotato -# REQUIRE: DAEMON +# REQUIRE: LOGIN # KEYWORD: shutdown # Add the following lines to /etc/rc.conf to enable couchpotato: