diff --git a/data/images/notifiers/betaseries.png b/data/images/notifiers/betaseries.png new file mode 100644 index 0000000000000000000000000000000000000000..2a803b6453cf5bfbda8b7f539333b2cb394fa7fa Binary files /dev/null and b/data/images/notifiers/betaseries.png differ diff --git a/data/images/providers/sotorrent.png b/data/images/providers/sotorrent.png new file mode 100644 index 0000000000000000000000000000000000000000..09b6ce575caa6fb748a13947018f2a62bdb5d33e Binary files /dev/null and b/data/images/providers/sotorrent.png differ diff --git a/data/interfaces/default/config_notifications.tmpl b/data/interfaces/default/config_notifications.tmpl index 75f00a7aa0aa73f29ffc1fcbed6b1bfe1369864f..ca0de96f0176ca0f913f0d5407e19917e26b6e4e 100755 --- a/data/interfaces/default/config_notifications.tmpl +++ b/data/interfaces/default/config_notifications.tmpl @@ -1078,7 +1078,7 @@ </div> <div class="field-pair"> <label class="nocheck clearfix"> - <span class="component-title">Pushbullet Devices</span> + <span class="component-title">Pushbullet Devices and Channels</span> <select name="pushbullet_device_list" id="pushbullet_device_list"></select> <input type="hidden" id="pushbullet_device" value="$sickbeard.PUSHBULLET_DEVICE"> <input type="button" class="btn" value="Update device list" id="getPushbulletDevices" /> @@ -1261,6 +1261,50 @@ </fieldset> </div><!-- /trakt component-group //--> + <div class="component-group clearfix"> + <div class="component-group-desc"> + <img class="notifier-icon" src="$sbRoot/images/notifiers/betaseries.png" alt="" title="BetaSeries" /> + <h3><a href="http://betaseries.com/" rel="noreferrer" onclick="window.open(this.href, '_blank'); return false;">{ betaseries.com }</a></h3> + <p>French equivalent of trakt.tv.</p> + </div> + <fieldset class="component-group-list"> + <div class="field-pair"> + <input type="checkbox" class="enabler" name="use_betaseries" id="use_betaseries" #if $sickbeard.USE_BETASERIES then "checked=\"checked\"" else ""# /> + <label class="clearfix" for="use_betaseries"> + <span class="component-title">Enable</span> + <span class="component-desc">Should Sick Beard send BetaSeries notifications?</span> + </label> + </div> + + <div id="content_use_betaseries"> + <div class="field-pair"> + <label class="nocheck clearfix"> + <span class="component-title">BetaSeries Username</span> + <input type="text" name="betaseries_username" id="betaseries_username" value="$sickbeard.BETASERIES_USERNAME" size="35" /> + </label> + <label class="nocheck clearfix"> + <span class="component-title"> </span> + <span class="component-desc">Username of your BetaSeries account.</span> + </label> + </div> + <div class="field-pair"> + <label class="nocheck clearfix"> + <span class="component-title">BetaSeries Password</span> + <input type="password" name="betaseries_password" id="betaseries_password" value="$sickbeard.BETASERIES_PASSWORD" size="35" /> + </label> + <label class="nocheck clearfix"> + <span class="component-title"> </span> + <span class="component-desc">Password of your BetaSeries account.</span> + </label> + </div> + <div class="testNotification" id="testBetaSeries-result">Click below to test.</div> + <input type="button" class="btn" value="Test BetaSeries" id="testBetaSeries" /> + <input type="submit" class="btn config_submitter" value="Save Changes" /> + </div><!-- /content_use_betaseries //--> + + </fieldset> + </div><!-- /betaseries component-group //--> + <div class="component-group clearfix"> <div class="component-group-desc"> <img class="notifier-icon" src="$sbRoot/images/notifiers/mail.png" alt="" title="Mail" /> diff --git a/data/interfaces/default/config_providers.tmpl b/data/interfaces/default/config_providers.tmpl index 86445278473089974c447b66a8b11b7c9d9a08e5..a2ff07ad1cc7ac4fea276fab19022a1fb6ca16c3 100644 --- a/data/interfaces/default/config_providers.tmpl +++ b/data/interfaces/default/config_providers.tmpl @@ -99,7 +99,7 @@ var show_nzb_providers = #if $sickbeard.USE_NZBS then "true" else "false"#; <span class="component-title jumbo">Configure Provider:</span> <span class="component-desc"> #set $provider_config_list = [] - #for $cur_provider in ("omgwtfnzbs", "tvtorrents", "torrentleech", "btn", "binnewz", "t411", "ftdb", "addict", "fnt", "libertalia", "piratebay", "ethor", "thinkgeek", "xthor"): + #for $cur_provider in ("omgwtfnzbs", "tvtorrents", "torrentleech", "btn", "binnewz", "t411", "ftdb", "addict", "fnt", "libertalia", "sotorrent", "piratebay", "ethor", "thinkgeek", "xthor"): #set $cur_provider_obj = $sickbeard.providers.getProviderClass($cur_provider) #if $cur_provider_obj.providerType == $GenericProvider.NZB and not $sickbeard.USE_NZBS: #continue @@ -300,6 +300,21 @@ Nothing to set up for this provider </div> </div><!-- /libertaliaDiv //--> + <div class="providerDiv" id="sotorrentDiv"> + <div class="field-pair"> + <label class="clearfix"> + <span class="component-title">So-torrent Username:</span> + <input class="component-desc" type="text" name="sotorrent_username" value="$sickbeard.SOTORRENT_USERNAME" size="25" /> + </label> + </div> + <div class="field-pair"> + <label class="clearfix"> + <span class="component-title">So-torrent Password:</span> + <input class="component-desc" type="password" name="sotorrent_password" value="$sickbeard.SOTORRENT_PASSWORD" size="25" /> + </label> + </div> + </div><!-- /sotorrentDiv //--> + <div class="providerDiv" id="xthorDiv"> <div class="field-pair"> <label class="clearfix"> diff --git a/data/js/configNotifications.js b/data/js/configNotifications.js index 2e207b088798d61a0dbc2ef8cf28878ca8abbe08..1e1f93c8f9141407bcfa79dfce9fa9558a7a4e24 100644 --- a/data/js/configNotifications.js +++ b/data/js/configNotifications.js @@ -246,18 +246,37 @@ $(document).ready(function () { $("#pushbullet_api").focus(); return false; } - + $("#pushbullet_device_list").html(''); var current_pushbullet_device = $("#pushbullet_device").val(); $.get(sbRoot + "/home/getPushbulletDevices", {'api': pushbullet_api}, function (data) { var devices = jQuery.parseJSON(data).devices; - $("#pushbullet_device_list").html(''); + for (var i = 0; i < devices.length; i++) { if(devices[i].active == true && devices[i].pushable == true){ - if(current_pushbullet_device == devices[i].iden) { - $("#pushbullet_device_list").append('<option value="'+devices[i].iden+'" selected>' + devices[i].nickname + '</option>') + if(current_pushbullet_device == 'device:'+devices[i].iden) { + $("#pushbullet_device_list").append('<option value="device:'+devices[i].iden+'" selected>' + devices[i].nickname + '</option>') } else { - $("#pushbullet_device_list").append('<option value="'+devices[i].iden+'">' + devices[i].nickname + '</option>') + $("#pushbullet_device_list").append('<option value="device:'+devices[i].iden+'">' + devices[i].nickname + '</option>') + } + } + } + if(msg) { + $('#testPushbullet-result').html(msg); + } + } + ); + + $.get(sbRoot + "/home/getPushbulletChannels", {'api': pushbullet_api}, + function (data) { + var channels = jQuery.parseJSON(data).channels; + + for (var i = 0; i < channels.length; i++) { + if(channels[i].active == true){ + if(current_pushbullet_device == 'channel:'+channels[i].tag) { + $("#pushbullet_device_list").append('<option value="channel:'+channels[i].tag+'" selected>' + channels[i].name + '</option>') + } else { + $("#pushbullet_device_list").append('<option value="channel:'+channels[i].tag+'">' + channels[i].name + '</option>') } } } @@ -279,6 +298,15 @@ $(document).ready(function () { // we have to call this function on dom ready to create the devices select get_pushbullet_devices(); + + $('#testBetaSeries').click(function () { + $('#testBetaSeries-result').html(loading); + var betaseries_username = $("#betaseries_username").val(); + var betaseries_password = $("#betaseries_password").val(); + + $.get(sbRoot + "/home/testBetaSeries", {'username': betaseries_username, 'password': betaseries_password}, + function (data) { $('#testBetaSeries-result').html(data); }); + }); $('#email_show').change(function () { var key = parseInt($('#email_show').val(), 10); diff --git a/sickbeard/__init__.py b/sickbeard/__init__.py index ca77bffdbfc6fb1dc8bcde24d0db9b3091bf6d6e..ee9112dc2718e5eabf07f8bae0db71c9bbe89e01 100644 --- a/sickbeard/__init__.py +++ b/sickbeard/__init__.py @@ -30,7 +30,7 @@ from threading import Lock # apparently py2exe won't build these unless they're imported somewhere from sickbeard import providers, metadata -from providers import ezrss, tvtorrents, torrentleech, btn, nzbsrus, newznab, womble, nzbx, omgwtfnzbs, binnewz, t411, ftdb, libertalia, tpi, fnt, addict, cpasbien, piratebay, gks, kat, ethor, xthor, thinkgeek +from providers import ezrss, tvtorrents, torrentleech, btn, nzbsrus, newznab, womble, nzbx, omgwtfnzbs, binnewz, t411, ftdb, libertalia, sotorrent, tpi, fnt, addict, cpasbien, piratebay, gks, kat, ethor, xthor, thinkgeek from sickbeard.config import CheckSection, check_setting_int, check_setting_str, ConfigMigrator from sickbeard import searchCurrent, searchBacklog, showUpdater, versionChecker, properFinder, frenchFinder, autoPostProcesser, subtitles, traktWatchListChecker, SentFTPChecker @@ -236,6 +236,10 @@ LIBERTALIA = False LIBERTALIA_USERNAME = None LIBERTALIA_PASSWORD = None +SOTORRENT = False +SOTORRENT_USERNAME = None +SOTORRENT_PASSWORD = None + XTHOR = False XTHOR_USERNAME = None XTHOR_PASSWORD = None @@ -378,6 +382,13 @@ TRAKT_USE_WATCHLIST = False TRAKT_METHOD_ADD = 0 TRAKT_START_PAUSED = False +USE_BETASERIES = False +BETASERIES_USERNAME = None +BETASERIES_PASSWORD = None +# This key is registered for Sick Beard. +# For your application, please get one at http://www.betaseries.com/api/ +BETASERIES_API = '94318dc0bb30' + USE_PYTIVO = False PYTIVO_NOTIFY_ONSNATCH = False PYTIVO_NOTIFY_ONDOWNLOAD = False @@ -475,6 +486,7 @@ def initialize(consoleLogging=True): USE_XBMC, XBMC_NOTIFY_ONSNATCH, XBMC_NOTIFY_ONDOWNLOAD, XBMC_NOTIFY_ONSUBTITLEDOWNLOAD, XBMC_UPDATE_FULL, XBMC_UPDATE_ONLYFIRST, \ XBMC_UPDATE_LIBRARY, XBMC_HOST, XBMC_USERNAME, XBMC_PASSWORD, \ USE_TRAKT, TRAKT_USERNAME, TRAKT_PASSWORD, TRAKT_API,TRAKT_REMOVE_WATCHLIST,TRAKT_USE_WATCHLIST,TRAKT_METHOD_ADD,TRAKT_START_PAUSED,traktWatchListCheckerSchedular, \ + USE_BETASERIES, BETASERIES_USERNAME, BETASERIES_PASSWORD, BETASERIES_API, \ USE_PLEX, PLEX_NOTIFY_ONSNATCH, PLEX_NOTIFY_ONDOWNLOAD, PLEX_NOTIFY_ONSUBTITLEDOWNLOAD, PLEX_UPDATE_LIBRARY, \ PLEX_SERVER_HOST, PLEX_HOST, PLEX_USERNAME, PLEX_PASSWORD, \ showUpdateScheduler, __INITIALIZED__, LAUNCH_BROWSER, UPDATE_SHOWS_ON_START, SORT_ARTICLE, FRENCH_COLUMN, showList, loadingShowList, \ @@ -486,6 +498,7 @@ def initialize(consoleLogging=True): ADDICT, ADDICT_USERNAME, ADDICT_PASSWORD, \ FNT, FNT_USERNAME, FNT_PASSWORD, \ LIBERTALIA, LIBERTALIA_USERNAME, LIBERTALIA_PASSWORD, \ + SOTORRENT, SOTORRENT_USERNAME, SOTORRENT_PASSWORD, \ XTHOR, XTHOR_USERNAME, XTHOR_PASSWORD, \ THINKGEEK, THINKGEEK_USERNAME, THINKGEEK_PASSWORD, \ THEPIRATEBAY, THEPIRATEBAY_PROXY, THEPIRATEBAY_PROXY_URL, THEPIRATEBAY_TRUSTED, \ @@ -798,6 +811,11 @@ def initialize(consoleLogging=True): LIBERTALIA_USERNAME = check_setting_str(CFG, 'LIBERTALIA', 'username', '') LIBERTALIA_PASSWORD = check_setting_str(CFG, 'LIBERTALIA', 'password', '') + CheckSection(CFG, 'SOTORRENT') + SOTORRENT = bool(check_setting_int(CFG, 'SOTORRENT', 'sotorrent', 0)) + SOTORRENT_USERNAME = check_setting_str(CFG, 'SOTORRENT', 'username', '') + SOTORRENT_PASSWORD = check_setting_str(CFG, 'SOTORRENT', 'password', '') + CheckSection(CFG, 'XTHOR') XTHOR = bool(check_setting_int(CFG, 'XTHOR', 'xthor', 0)) XTHOR_USERNAME = check_setting_str(CFG, 'XTHOR', 'username', '') @@ -978,7 +996,13 @@ def initialize(consoleLogging=True): TRAKT_USE_WATCHLIST = bool(check_setting_int(CFG, 'Trakt', 'trakt_use_watchlist', 0)) TRAKT_METHOD_ADD = check_setting_str(CFG, 'Trakt', 'trakt_method_add', "0") TRAKT_START_PAUSED = bool(check_setting_int(CFG, 'Trakt', 'trakt_start_paused', 0)) - + + CheckSection(CFG, 'BetaSeries') + USE_BETASERIES = bool(check_setting_int(CFG, 'BetaSeries', 'use_betaseries', 0)) + BETASERIES_USERNAME = check_setting_str(CFG, 'BetaSeries', 'betaseries_username', '') + BETASERIES_PASSWORD = check_setting_str(CFG, 'BetaSeries', 'betaseries_password', '') + BETASERIES_API = check_setting_str(CFG, 'BetaSeries', 'betaseries_api', '') + CheckSection(CFG, 'pyTivo') USE_PYTIVO = bool(check_setting_int(CFG, 'pyTivo', 'use_pytivo', 0)) PYTIVO_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'pyTivo', 'pytivo_notify_onsnatch', 0)) @@ -1558,6 +1582,11 @@ def save_config(): new_config['LIBERTALIA']['username'] = LIBERTALIA_USERNAME new_config['LIBERTALIA']['password'] = LIBERTALIA_PASSWORD + new_config['SOTORRENT'] = {} + new_config['SOTORRENT']['sotorrent'] = int(SOTORRENT) + new_config['SOTORRENT']['username'] = SOTORRENT_USERNAME + new_config['SOTORRENT']['password'] = SOTORRENT_PASSWORD + new_config['XTHOR'] = {} new_config['XTHOR']['xthor'] = int(XTHOR) new_config['XTHOR']['username'] = XTHOR_USERNAME @@ -1734,7 +1763,13 @@ def save_config(): new_config['Trakt']['trakt_use_watchlist'] = int(TRAKT_USE_WATCHLIST) new_config['Trakt']['trakt_method_add'] = TRAKT_METHOD_ADD new_config['Trakt']['trakt_start_paused'] = int(TRAKT_START_PAUSED) - + + new_config['BetaSeries'] = {} + new_config['BetaSeries']['use_betaseries'] = int(USE_BETASERIES) + new_config['BetaSeries']['betaseries_username'] = BETASERIES_USERNAME + new_config['BetaSeries']['betaseries_password'] = BETASERIES_PASSWORD + new_config['BetaSeries']['betaseries_api'] = BETASERIES_API + new_config['pyTivo'] = {} new_config['pyTivo']['use_pytivo'] = int(USE_PYTIVO) new_config['pyTivo']['pytivo_notify_onsnatch'] = int(PYTIVO_NOTIFY_ONSNATCH) diff --git a/sickbeard/common.py b/sickbeard/common.py index c4c8c2894bcd8af03937e4bed971a37e7c4fb263..45c789275c7c8d0b5e954cb76c5c72414607736d 100644 --- a/sickbeard/common.py +++ b/sickbeard/common.py @@ -45,7 +45,7 @@ NOTIFY_SUBTITLE_DOWNLOAD = 3 notifyStrings = {} notifyStrings[NOTIFY_SNATCH] = "Download en cours" -notifyStrings[NOTIFY_DOWNLOAD] = "Download finis" +notifyStrings[NOTIFY_DOWNLOAD] = "Download fini" notifyStrings[NOTIFY_SUBTITLE_DOWNLOAD] = "Sous-titre" ### Episode statuses diff --git a/sickbeard/name_parser/regexes.py b/sickbeard/name_parser/regexes.py index 813eff9cc390c10b303ff6d99cd379f18034af68..d2c79ad1ed6b8dae1cb762479b29a62e2ea1f985 100644 --- a/sickbeard/name_parser/regexes.py +++ b/sickbeard/name_parser/regexes.py @@ -73,7 +73,7 @@ ep_regexes = [ ('standard_cpas_bien', # [www.Cpasbien.me] Dexter.S07E04.FRENCH.LD.HDTV.XviD-MiNDe ''' - \[[a-zA-Z0-9\.]{2,20}\][. _-]+ + \[[a-zA-Z0-9\.\s]{2,20}\][. _-]+ (?P<series_name>.+?)[. _-]+ # Show_Name and separator s(?P<season_num>\d+)[. _-]* # S01 and optional separator e(?P<ep_num>\d+) # E02 and separator diff --git a/sickbeard/notifiers/__init__.py b/sickbeard/notifiers/__init__.py index 8f9759f870fe6aaedf2080fbdb0409a99dd03da5..747918baad2bca563e3e1e0dc629dd88ed418772 100755 --- a/sickbeard/notifiers/__init__.py +++ b/sickbeard/notifiers/__init__.py @@ -39,6 +39,7 @@ import pushbullet import tweet import trakt +import betaseries from sickbeard.common import * @@ -63,6 +64,7 @@ pushbullet_notifier = pushbullet.PushbulletNotifier() # online twitter_notifier = tweet.TwitterNotifier() trakt_notifier = trakt.TraktNotifier() +betaseries_notifier = betaseries.BetaSeriesNotifier() mail_notifier = mail.MailNotifier() notifiers = [ @@ -84,6 +86,7 @@ notifiers = [ pushbullet_notifier, twitter_notifier, trakt_notifier, + betaseries_notifier, mail_notifier, ] diff --git a/sickbeard/notifiers/betaseries.py b/sickbeard/notifiers/betaseries.py new file mode 100644 index 0000000000000000000000000000000000000000..1bb5a75dcf3eafe7fbdd529de8306d5ce67ab1f1 --- /dev/null +++ b/sickbeard/notifiers/betaseries.py @@ -0,0 +1,165 @@ +# Author: Gregoire Astruc <gregoire.astruc@gmail.com> +# URL: http://code.google.com/p/sickbeard/ +# +# This file is part of Sick Beard. +# +# Sick Beard is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Sick Beard is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Sick Beard. If not, see <http://www.gnu.org/licenses/>. +import urllib2, urllib + +from hashlib import md5 + +try: + import json +except ImportError: + from lib import simplejson as json + +import sickbeard + +from sickbeard import logger + +class BetaSeriesNotifier: + """ + A "notifier" for betaseries.com which keeps track of what has and hasn't been added to your library. + """ + + def notify_snatch(self, ep_name): + pass + + def notify_download(self, ep_name): + pass + + def notify_subtitle_download(self, ep_name, lang): + pass + + def update_library(self, ep_obj): + """ + Sends a request to betaseries indicating that the given episode is part of our library. + + ep_obj: The TVEpisode object to add to betaseries + """ + if not sickbeard.USE_BETASERIES: + return + + # TODO: Figure out if the show is present, and eventually add it. + + self._notifyBetaSeries('episodes/downloaded', {'thetvdb_id': ep_obj.tvdbid}) + + def test_notify(self, username, password): + """ + Sends a test notification to trakt with the given authentication info and returns a boolean + representing success. + + api: The api string to use + username: The username to use + password: The password to use + + Returns: True if the request succeeded, False otherwise + """ + return self._notifyBetaSeries("timeline/home", login=username, password=password) + + def _login(self): + return sickbeard.BETASERIES_USERNAME + + def _password(self): + return sickbeard.BETASERIES_PASSWORD + + def _api(self): + return sickbeard.BETASERIES_API + + def _use_me(self): + return sickbeard.USE_BETASERIES + + def _notifyBetaSeries(self, method, data = {}, login = None, password = None): + """ + A generic method for communicating with betaseries. Uses the method and data provided along + with the auth info to send the command. + + method: The URL to use at trakt, relative, no leading slash. + api: The API string to provide to trakt + login: The username to use when logging in + password: The unencrypted password to use when logging in + + Returns: A boolean representing success + """ + logger.log("betaseries_notifier: Call method {0}".format(method), logger.DEBUG) + + # if the login isn't given then use the config login + if not login: + login = self._login() + + # if the password isn't given then use the config password + if not password: + password = self._password() + + password = md5(password).hexdigest() + token = None + + try: + stream = urllib2.urlopen(self._requestBetaSeries('members/auth', {"login": login, "password": password})) + auth_resp = stream.read() + auth_resp = json.loads(auth_resp) + + token = auth_resp["token"] + except urllib2.URLError, e: + error = ''.join(e.readlines()) + logger.log("betaseries_notifier: Failed authenticating: {0} ({1})".format(e, error), logger.ERROR) + return False + except IOError, e: + logger.log("betaseries_notifier: Failed authenticating: {0}".format(e), logger.ERROR) + return False + + + # request the URL from betaseries and parse the result as json + try: + stream = urllib2.urlopen(self._requestBetaSeries(method, data, token)) + resp = stream.read() + + resp = json.loads(resp) + + if resp["errors"]: + raise Exception(resp["errors"]) + except urllib2.URLError, e: + error = ''.join(e.readlines()) + logger.log("betaseries_notifier: Failed method call on {0}: {1} ({2})".format(method, e, error), logger.ERROR) + + except IOError: + logger.log("betaseries_notifier: Failed calling method {0}".format(method), logger.ERROR) + return False + + if not resp["errors"]: + logger.log("betaseries_notifier: Succeeded calling {0}. Result: {1}".format(method, resp), logger.DEBUG) + return True + + #TODO: Destroy token. + + logger.log("betaseries_notifier: Failed calling method", logger.ERROR) + return False + + def _requestBetaSeries(self, method, data, token = None, version = "2.2"): + logger.log("betaseries_notifier: building request for {0} with payload {1}".format(method, data), logger.DEBUG) + request = urllib2.Request( + url="https://api.betaseries.com/{0}".format(method), + headers={ + "User-Agent": "Sickbeard/1.0", + "X-BetaSeries-Version": "2.2", + "X-BetaSeries-Key": self._api()}) + if data: + request.add_data(urllib.urlencode(data)) + + if token: + request.add_header("X-BetaSeries-Token", token) + + return request + +notifier = BetaSeriesNotifier diff --git a/sickbeard/notifiers/pushbullet.py b/sickbeard/notifiers/pushbullet.py index 64801afd861fa16f2a6774a3325cadf1fb99f4e3..a6c60a1f1daf859b60d856863b84d7876bd9650e 100644 --- a/sickbeard/notifiers/pushbullet.py +++ b/sickbeard/notifiers/pushbullet.py @@ -34,6 +34,9 @@ class PushbulletNotifier: def get_devices(self, pushbullet_api): return self._sendPushbullet(pushbullet_api, method="GET", force=True) + def get_channels(self, pushbullet_api): + return self._sendPushbullet(pushbullet_api, method="GET", force=True, event="getChannels") + def notify_snatch(self, ep_name): if sickbeard.PUSHBULLET_NOTIFY_ONSNATCH: self._sendPushbullet(pushbullet_api=None, event=common.notifyStrings[common.NOTIFY_SNATCH], message=ep_name, notificationType="note", method="POST") @@ -55,12 +58,15 @@ class PushbulletNotifier: pushbullet_api = sickbeard.PUSHBULLET_API if pushbullet_device == None: pushbullet_device = sickbeard.PUSHBULLET_DEVICE - + if method == 'POST': uri = '/v2/pushes' - else: + else: uri = '/v2/devices' + if event == 'getChannels': + uri = '/v2/channels' + logger.log(u"Pushbullet event: " + str(event), logger.DEBUG) logger.log(u"Pushbullet message: " + str(message), logger.DEBUG) logger.log(u"Pushbullet api: " + str(pushbullet_api), logger.DEBUG) @@ -80,11 +86,20 @@ class PushbulletNotifier: else: testMessage = False try: - data = { - 'title': event.encode('utf-8'), - 'body': message.encode('utf-8'), - 'device_iden': pushbullet_device, - 'type': notificationType} + device = pushbullet_device.split(':'); + if device[0] == 'device': + data = { + 'title': event.encode('utf-8'), + 'body': message.encode('utf-8'), + 'device_iden': device[1], + 'type': notificationType} + else: + data = { + 'title': event.encode('utf-8'), + 'body': message.encode('utf-8'), + 'channel_tag': device[1], + 'type': notificationType} + data = json.dumps(data) http_handler.request(method, uri, body=data, headers={'Content-Type': 'application/json', 'Authorization': 'Bearer %s' % pushbullet_api}) diff --git a/sickbeard/postProcessor.py b/sickbeard/postProcessor.py index a1dccfa93951aee19da1c459724ed00c174fc9ee..7dd5d79e76bb70e6ca168a679fc209ba78646e01 100755 --- a/sickbeard/postProcessor.py +++ b/sickbeard/postProcessor.py @@ -1050,6 +1050,9 @@ class PostProcessor(object): # do the library update for Trakt notifiers.trakt_notifier.update_library(ep_obj) + # do the library update for BetaSeries + notifiers.betaseries_notifier.update_library(ep_obj) + self._run_extra_scripts(ep_obj) return True diff --git a/sickbeard/providers/__init__.py b/sickbeard/providers/__init__.py index b769ebd06dca967972eb032357708accb876ea76..55de106f39193cb6332cd6244ffc077ea00fd670 100644 --- a/sickbeard/providers/__init__.py +++ b/sickbeard/providers/__init__.py @@ -28,6 +28,7 @@ __all__ = ['ezrss', 'addict', 'fnt', 'libertalia', + 'sotorrent', 'cpasbien', 'piratebay', 'kat', diff --git a/sickbeard/providers/libertalia.py b/sickbeard/providers/libertalia.py index 173c9eb6f68c7af806e9196c33f8f7ba2a670327..3f6f14b513f2ac75de0de466430b679c8080fcf2 100644 --- a/sickbeard/providers/libertalia.py +++ b/sickbeard/providers/libertalia.py @@ -150,7 +150,17 @@ class LIBERTALIAProvider(generic.TorrentProvider): #bypass first row because title only columns = row.find('td', {"class" : "torrent_name"} ) logger.log(u"LIBERTALIA found rows ! " , logger.DEBUG) - link = columns.find("a", href=re.compile("torrents")) + isvfclass = row.find('td', {"class" : "sprite-vf"} ) + isvostfrclass = row.find('td', {"class" : "sprite-vostfr"} ) + link = columns.find("a", href=re.compile("torrents")) + if link: + if isvostfrclass and str(show.audio_lang)=='fr': + logger.log(u"LIBERTALIA found VOSTFR et demande *"+str(show.audio_lang)+"* je skip ! " + link.text , logger.DEBUG) + link = columns.find("a", href=re.compile("nepastrouver")) + if link: + if isvfclass and str(show.audio_lang)!='fr' : + logger.log(u"LIBERTALIA found VF et demande *"+str(show.audio_lang)+"* je skip ! " + link.text , logger.DEBUG) + link = columns.find("a", href=re.compile("nepastrouver")) if link: title = link.text recherched=searchUrl.split("&[PARAMSTR]=")[1] @@ -170,7 +180,9 @@ class LIBERTALIAProvider(generic.TorrentProvider): results.append( LIBERTALIASearchResult( self.opener, title, downloadURL, quality, 'fr' ) ) else: results.append( LIBERTALIASearchResult( self.opener, title, downloadURL, quality ) ) - + else: + logger.log(u"Pas de table trouvée ! je délogue", logger.DEBUG) + self.login_done = False return results def getResult(self, episodes): diff --git a/sickbeard/providers/sotorrent.py b/sickbeard/providers/sotorrent.py new file mode 100644 index 0000000000000000000000000000000000000000..7dd3e796231c20eb5e998af9bbf86ec7a62528d8 --- /dev/null +++ b/sickbeard/providers/sotorrent.py @@ -0,0 +1,183 @@ +# -*- coding: latin-1 -*- +# Author: Staros +# URL: http://code.google.com/p/sickbeard/ +# +# This file is part of Sick Beard. +# +# Sick Beard is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Sick Beard is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Sick Beard. If not, see <http://www.gnu.org/licenses/>. + +from bs4 import BeautifulSoup +from sickbeard import classes, show_name_helpers, logger +from sickbeard.common import Quality + +import generic +import cookielib +import sickbeard +import urllib +import urllib2 +import random + +class SOTORRENTProvider(generic.TorrentProvider): + + def __init__(self): + generic.TorrentProvider.__init__(self, "Sotorrent") + self.supportsBacklog = True + + self.cj = cookielib.CookieJar() + self.opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cj)) + self.url = "https://so-torrent.com" + + self.login_done = False + + self.params = "q=&c73=1&c75=1" # English (HD and SD) + + def imageName(self): + return 'sotorrent.png' + + def isEnabled(self): + return sickbeard.SOTORRENT + + def getSearchParams(self, searchString, audio_lang, subcat, french=None, season=None): + """ + @q string / exact for sphinx research / c[NUMCAT] + """ + if(season): + if audio_lang == "en" and french==None: + self.params = urllib.urlencode({ 'q': searchString})+"&c85=1" + elif audio_lang == "fr" or french: + self.params = urllib.urlencode({ 'q': searchString})+"&71=1" + else: + self.params = urllib.urlencode({ 'q': searchString})+"&71=1&85=1" + else: + if audio_lang == "en" and french==None: + self.params = urllib.urlencode({ 'q': searchString})+"&c73=1&c75=1" + elif audio_lang == "fr" or french: + self.params = urllib.urlencode({ 'q': searchString})+"&c74=1&c72=1" + else: + self.params = urllib.urlencode({ 'q': searchString})+"&c74=1&c72=1&c73=1&c75=1" + + return self.params + + def _get_season_search_strings(self, show, season): + """ + Don't find how to test this but seem working + """ + results = [] + possible_show = show_name_helpers.allPossibleShowNames(ep_obj.show) + list_show = set(possible_show) + for show in list_show: + results.append(self.getSearchParams("+S%02d" % season, show.audio_lang, season)) + return results + + def _get_episode_search_strings(self, ep_obj, french=None): + """ + """ + results = [] + possible_show = show_name_helpers.allPossibleShowNames(ep_obj.show) + list_show = set(possible_show) + for show in list_show: + results.append(self.getSearchParams("%s S%02dE%02d" % (show, ep_obj.scene_season, ep_obj.scene_episode), ep_obj.show.audio_lang, french)) + return results + + + def _get_title_and_url(self, item): + return (item.title, item.url) + + def getQuality(self, item): + return item.getQuality() + + def _doLogin(self, username, password): + + listeUserAgents = [ 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_5; fr-fr) AppleWebKit/525.18 (KHTML, like Gecko) Version/3.1.2 Safari/525.20.1', + 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.186 Safari/535.1', + 'Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/525.13 (KHTML, like Gecko) Chrome/0.2.149.27 Safari/525.13', + 'Mozilla/5.0 (X11; U; Linux x86_64; en-us) AppleWebKit/528.5+ (KHTML, like Gecko, Safari/528.5+) midori', + 'Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.107 Safari/535.1', + 'Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/312.1 (KHTML, like Gecko) Safari/312', + 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.12 Safari/535.11', + 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.8 (KHTML, like Gecko) Chrome/17.0.940.0 Safari/535.8' ] + + self.opener.addheaders = [('User-agent', random.choice(listeUserAgents))] + data = urllib.urlencode({'username': username,'password': password, 'submit': ' Se Connecter '}) + r = self.opener.open(self.url + '/connect.php',data) + + for index, cookie in enumerate(self.cj): + if (cookie.name == "pwSoTorrent"): self.login_done = True + + if not self.login_done: + logger.log(u"Unable to login to so-torrent. Please check username and password.", logger.WARNING) + + if self.login_done: + logger.log(u"Login to so-torrent successful", logger.MESSAGE) + + def _doSearch(self, searchString, show=None, season=None, french=None): + if not self.login_done: + self._doLogin(sickbeard.SOTORRENT_USERNAME, sickbeard.SOTORRENT_PASSWORD) + + results = [] + + search_url = "{0}/sphinx.php?{1}".format(self.url, searchString.replace('!','')) + req = self.opener.open(search_url) + page = BeautifulSoup(req) + + torrent_table = page.find("table", {"id" : "torrent_list"}) + if torrent_table: + logger.log(u"So-torrent found shows ! " , logger.DEBUG) + torrent_rows = torrent_table.findAll("tr", {"id" : "infos_sphinx"}) + + for row in torrent_rows: + release = row.strong.string + id_search = row.find("img", {"alt" : "+"}) + id_torrent = id_search['id'].replace('expandoGif', '') + download_url = "https://so-torrent.com/get.php?id={0}".format(id_search['id'].replace('expandoGif', '')) + id_quality = Quality.nameQuality(release) + + if show and french==None: + results.append(SOTORRENTSearchResult(self.opener, release, download_url, id_quality, str(show.audio_lang))) + elif show and french: + results.append(SOTORRENTSearchResult(self.opener, release, download_url, id_quality, 'fr')) + else: + results.append(SOTORRENTSearchResult(self.opener, release, download_url, id_quality)) + + else: + logger.log(u"No table founded.", logger.DEBUG) + self.login_done = False + return results + + def getResult(self, episodes): + """ + Returns a result of the correct type for this provider + """ + result = classes.TorrentDataSearchResult(episodes) + result.provider = self + + return result + +class SOTORRENTSearchResult: + def __init__(self, opener, title, url, quality, audio_langs=None): + self.opener = opener + self.title = title + self.url = url + self.quality = quality + self.audio_langs=audio_langs + + def getNZB(self): + return self.opener.open( self.url , 'wb').read() + + def getQuality(self): + return self.quality + +provider = SOTORRENTProvider() + + diff --git a/sickbeard/providers/t411.py b/sickbeard/providers/t411.py index b9e8494f5febc344be2303ad3a03851aa5f492d1..1f5e6f4d71ffe113a4df7a2889063a93c6bf0c69 100644 --- a/sickbeard/providers/t411.py +++ b/sickbeard/providers/t411.py @@ -39,7 +39,7 @@ class T411Provider(generic.TorrentProvider): self.opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cj)) self.opener.addheaders = [('Content-Type', 'application/x-www-form-urlencoded'),('User-Agent', 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7 (.NET CLR 3.5.30729)')] - self.url = "http://www.t411.me" + self.url = "http://www.t411.io" self.login_done = False @@ -133,7 +133,7 @@ class T411Provider(generic.TorrentProvider): link = row.find("a", title=True) title = link['title'] id = row.find_all('td')[2].find_all('a')[0]['href'][1:].replace('torrents/nfo/?id=','') - downloadURL = ('http://www.t411.me/torrents/download/?id=%s' % id) + downloadURL = ('http://www.t411.io/torrents/download/?id=%s' % id) quality = Quality.nameQuality( title ) if quality==Quality.UNKNOWN and title: diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py index 2a6a9e2982163a3cb70ee145fe3d6b4fd9ced908..54e57667397b43540cb98d53b7f765f27e445d67 100644 --- a/sickbeard/webserve.py +++ b/sickbeard/webserve.py @@ -1361,7 +1361,7 @@ class ConfigProviders: tvtorrents_digest=None, tvtorrents_hash=None, torrentleech_key=None, btn_api_key=None, - newzbin_username=None, newzbin_password=None,t411_username=None,t411_password=None,ftdb_username=None,ftdb_password=None,addict_username=None,addict_password=None,fnt_username=None,fnt_password=None,libertalia_username=None,libertalia_password=None,xthor_username=None,xthor_password=None,thinkgeek_username=None,thinkgeek_password=None, + newzbin_username=None, newzbin_password=None,t411_username=None,t411_password=None,ftdb_username=None,ftdb_password=None,addict_username=None,addict_password=None,fnt_username=None,fnt_password=None,libertalia_username=None,libertalia_password=None,sotorrent_username=None,sotorrent_password=None,xthor_username=None,xthor_password=None,thinkgeek_username=None,thinkgeek_password=None, ethor_key=None, provider_order=None): @@ -1444,6 +1444,8 @@ class ConfigProviders: sickbeard.FNT = curEnabled elif curProvider == 'libertalia': sickbeard.LIBERTALIA = curEnabled + elif curProvider == 'sotorrent': + sickbeard.SOTORRENT = curEnabled elif curProvider == 'xthor': sickbeard.XTHOR = curEnabled elif curProvider == 'thinkgeek': @@ -1484,6 +1486,9 @@ class ConfigProviders: sickbeard.LIBERTALIA_USERNAME = libertalia_username sickbeard.LIBERTALIA_PASSWORD = libertalia_password + + sickbeard.SOTORRENT_USERNAME = sotorrent_username + sickbeard.SOTORRENT_PASSWORD = sotorrent_password sickbeard.XTHOR_USERNAME = xthor_username sickbeard.XTHOR_PASSWORD = xthor_password @@ -1535,12 +1540,13 @@ class ConfigNotifications: use_nmj=None, nmj_host=None, nmj_database=None, nmj_mount=None, use_synoindex=None, use_nmjv2=None, nmjv2_host=None, nmjv2_dbloc=None, nmjv2_database=None, use_trakt=None, trakt_username=None, trakt_password=None, trakt_api=None,trakt_remove_watchlist=None,trakt_use_watchlist=None,trakt_start_paused=None,trakt_method_add=None, + use_betaseries=None, betaseries_username=None, betaseries_password=None, use_synologynotifier=None, synologynotifier_notify_onsnatch=None, synologynotifier_notify_ondownload=None, synologynotifier_notify_onsubtitledownload=None, use_pytivo=None, pytivo_notify_onsnatch=None, pytivo_notify_ondownload=None, pytivo_notify_onsubtitledownload=None, pytivo_update_library=None, pytivo_host=None, pytivo_share_name=None, pytivo_tivo_name=None, use_nma=None, nma_notify_onsnatch=None, nma_notify_ondownload=None, nma_notify_onsubtitledownload=None, nma_api=None, nma_priority=0, use_pushalot=None, pushalot_notify_onsnatch=None, pushalot_notify_ondownload=None, pushalot_notify_onsubtitledownload=None, pushalot_authorizationtoken=None, - use_pushbullet=None, pushbullet_notify_onsnatch=None, pushbullet_notify_ondownload=None, pushbullet_notify_onsubtitledownload=None, pushbullet_api=None, pushbullet_device=None, pushbullet_device_list=None, + use_pushbullet=None, pushbullet_notify_onsnatch=None, pushbullet_notify_ondownload=None, pushbullet_notify_onsubtitledownload=None, pushbullet_api=None, pushbullet_device=None, pushbullet_device_list=None, pushbullet_channel_list=None, use_mail=None, mail_username=None, mail_password=None, mail_server=None, mail_ssl=None, mail_from=None, mail_to=None, mail_notify_onsnatch=None ): @@ -1761,6 +1767,11 @@ class ConfigNotifications: else: trakt_start_paused = 0 + if use_betaseries == "on": + use_betaseries = 1 + else: + use_betaseries = 0 + if use_pytivo == "on": use_pytivo = 1 else: @@ -1971,6 +1982,10 @@ class ConfigNotifications: sickbeard.TRAKT_METHOD_ADD = trakt_method_add sickbeard.TRAKT_START_PAUSED = trakt_start_paused + sickbeard.USE_BETASERIES = use_betaseries + sickbeard.BETASERIES_USERNAME = betaseries_username + sickbeard.BETASERIES_PASSWORD = betaseries_password + sickbeard.USE_PYTIVO = use_pytivo sickbeard.PYTIVO_NOTIFY_ONSNATCH = pytivo_notify_onsnatch == "off" sickbeard.PYTIVO_NOTIFY_ONDOWNLOAD = pytivo_notify_ondownload == "off" @@ -2008,6 +2023,7 @@ class ConfigNotifications: sickbeard.PUSHBULLET_NOTIFY_ONSUBTITLEDOWNLOAD = pushbullet_notify_onsubtitledownload sickbeard.PUSHBULLET_API = pushbullet_api sickbeard.PUSHBULLET_DEVICE = pushbullet_device_list + sickbeard.PUSHBULLET_CHANNEL = pushbullet_channel_list sickbeard.save_config() @@ -2855,6 +2871,16 @@ class Home: else: return "Test notice failed to Trakt" + @cherrypy.expose + def testBetaSeries(self, username=None, password=None): + cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store" + + result = notifiers.betaseries_notifier.test_notify(username, password) + if result: + return "Test notice sent successfully to BetaSeries" + else: + return "Test notice failed to BetaSeries" + @cherrypy.expose def testMail(self, mail_from=None, mail_to=None, mail_server=None, mail_ssl=None, mail_user=None, mail_password=None): cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store" @@ -2908,6 +2934,18 @@ class Home: else: return "Error sending Pushbullet notification" + @cherrypy.expose + + #get channels + def getPushbulletChannels(self, api=None): + cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store" + + result = notifiers.pushbullet_notifier.get_channels(api) + if result: + return result + else: + return "Error sending Pushbullet notification" + @cherrypy.expose def shutdown(self, pid=None): @@ -3935,7 +3973,7 @@ class WebInterface: myDB = db.DBConnection() # Limit dates - past_date = (datetime.date.today() + datetime.timedelta(weeks=-52)).toordinal() + past_date = (datetime.date.today() + datetime.timedelta(weeks=-2)).toordinal() future_date = (datetime.date.today() + datetime.timedelta(weeks=52)).toordinal() # Get all the shows that are not paused and are currently on air (from kjoconnor Fork) @@ -3943,16 +3981,17 @@ class WebInterface: for show in calendar_shows: # Get all episodes of this show airing between today and next month episode_list = myDB.select("SELECT tvdbid, name, season, episode, description, airdate FROM tv_episodes WHERE airdate >= ? AND airdate < ? AND showid = ?", (past_date, future_date, int(show["tvdb_id"]))) + + # Get local timezone and load network timezones + local_zone = tz.tzlocal() + try: + network_zone = network_timezones.get_network_timezone(show['network'], network_timezones.load_network_dict(), local_zone) + except: + # Dummy network_zone for exceptions + network_zone = None for episode in episode_list: - # Get local timezone and load network timezones - local_zone = tz.tzlocal() - try: - network_zone = network_timezones.get_network_timezone(show['network'], network_timezones.load_network_dict(), local_zone) - except: - # Dummy network_zone for exceptions - network_zone = None # Get the air date and time air_date = datetime.datetime.fromordinal(int(episode['airdate']))