Merge pull request #396 from adam111316/feature/ChangeKodiNotifier

Change Kodi notifier to use requests as opposed to urllib
This commit is contained in:
adam111316 2015-06-11 19:01:23 +08:00
commit 0e7c9656b9
2 changed files with 76 additions and 90 deletions

View file

@ -34,6 +34,7 @@
* Add py2/3 regression testing for exception clauses * Add py2/3 regression testing for exception clauses
* Change py2 exception clauses to py2/3 compatible clauses * Change py2 exception clauses to py2/3 compatible clauses
* Change py2 print statements to py2/3 compatible functions * Change py2 print statements to py2/3 compatible functions
* Change Kodi notifier to use requests as opposed to urllib
[develop changelog] [develop changelog]
* Update Requests library 2.7.0 (ab1f493) to 2.7.0 (8b5e457) * Update Requests library 2.7.0 (ab1f493) to 2.7.0 (8b5e457)

View file

@ -16,24 +16,13 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with SickGear. If not, see <http://www.gnu.org/licenses/>. # along with SickGear. If not, see <http://www.gnu.org/licenses/>.
import urllib
import urllib2
import socket
import base64 import base64
import time import requests
import sickbeard import sickbeard
from sickbeard import logger, common
from sickbeard import logger
from sickbeard import common
from sickbeard.exceptions import ex from sickbeard.exceptions import ex
from sickbeard.encodingKludge import fixStupidEncodings from sickbeard.encodingKludge import fixStupidEncodings
try:
import xml.etree.cElementTree as etree
except ImportError:
import xml.etree.ElementTree as etree
try: try:
import json import json
except ImportError: except ImportError:
@ -41,7 +30,8 @@ except ImportError:
class KODINotifier: class KODINotifier:
sg_logo_url = 'https://raw.githubusercontent.com/SickGear/SickGear/master/gui/slick/images/ico/apple-touch-icon-precomposed.png' sg_logo_url = 'https://raw.githubusercontent.com/SickGear/SickGear/master/gui/slick/images/ico/apple-touch-icon' \
'-precomposed.png'
def _notify_kodi(self, message, title='SickGear', host=None, username=None, password=None, force=False): def _notify_kodi(self, message, title='SickGear', host=None, username=None, password=None, force=False):
@ -55,21 +45,19 @@ class KODINotifier:
# suppress notifications if the notifier is disabled but the notify options are checked # suppress notifications if the notifier is disabled but the notify options are checked
if not sickbeard.USE_KODI and not force: if not sickbeard.USE_KODI and not force:
logger.log(u'KODI: Notifications are not enabled, skipping this notification', logger.DEBUG)
return False return False
result = '' result = ''
for curHost in [x.strip() for x in host.split(',')]: for curHost in [x.strip() for x in host.split(',')]:
logger.log(u'KODI: Sending Kodi notification to \'%s\' - %s' % (curHost, message), logger.MESSAGE) logger.log(u'KODI: Sending Kodi notification to "%s" - %s' % (curHost, message), logger.MESSAGE)
command = {'jsonrpc': '2.0', 'method': 'GUI.ShowNotification',
command = '{"jsonrpc":"2.0","method":"GUI.ShowNotification","params":{"title":"%s","message":"%s", "image": "%s"},"id":1}' % (title.encode('utf-8'), message.encode('utf-8'), self.sg_logo_url) 'params': {'title': title, 'message': message, 'image': self.sg_logo_url}, 'id': 1}
notifyResult = self._send_to_kodi(command, curHost, username, password) notifyResult = self._send_to_kodi(command, curHost, username, password)
if notifyResult: if notifyResult:
result += curHost + ':' + notifyResult['result'].decode(sickbeard.SYS_ENCODING) result += '%s:%s' % (curHost, notifyResult['result'])
else: else:
if sickbeard.KODI_ALWAYS_ON or force: if sickbeard.KODI_ALWAYS_ON or force:
result += curHost + ':False' result += '%s:False' % curHost
return result return result
def _send_to_kodi(self, command, host=None, username=None, password=None): def _send_to_kodi(self, command, host=None, username=None, password=None):
@ -84,40 +72,38 @@ class KODINotifier:
logger.log(u'KODI: No host specified, check your settings', logger.ERROR) logger.log(u'KODI: No host specified, check your settings', logger.ERROR)
return False return False
command = command.encode('utf-8') data = json.dumps(command)
logger.log(u'KODI: JSON command: ' + command, logger.DEBUG) logger.log(u'KODI: JSON command: %s' % data, logger.DEBUG)
url = 'http://%s/jsonrpc' % host
headers = {'Content-type': 'application/json'}
# if we have a password, use authentication
if password:
base64string = base64.encodestring('%s:%s' % (username, password))[:-1]
authheader = 'Basic %s' % base64string
headers['Authorization'] = authheader
logger.log(u'KODI: Contacting (with auth header) via url: %s' % fixStupidEncodings(url), logger.DEBUG)
else:
logger.log(u'KODI: Contacting via url: %s' % fixStupidEncodings(url), logger.DEBUG)
url = 'http://%s/jsonrpc' % (host)
try: try:
req = urllib2.Request(url, command) response = requests.post(url, data=data, headers=headers)
req.add_header('Content-type', 'application/json') except Exception as e:
# if we have a password, use authentication logger.log(u'KODI: Warning: Couldn\'t contact Kodi at %s - %s' % (host, ex(e)), logger.WARNING)
if password: return False
base64string = base64.encodestring('%s:%s' % (username, password))[:-1]
authheader = 'Basic %s' % base64string
req.add_header('Authorization', authheader)
logger.log(u'KODI: Contacting (with auth header) via url: ' + fixStupidEncodings(url), logger.DEBUG)
else:
logger.log(u'KODI: Contacting via url: ' + fixStupidEncodings(url), logger.DEBUG)
try: if response.status_code == 401:
response = urllib2.urlopen(req) logger.log(u'KODI: Invalid login credentials', logger.ERROR)
except urllib2.URLError as e: return False
logger.log(u'KODI: Warning: Couldn\'t contact Kodi at ' + host + '- ' + ex(e), logger.WARNING)
return False
# parse the json result # parse the json result
try: try:
result = json.load(response) result = response.json()
response.close() logger.log(u'KODI: JSON response: %s' % result, logger.DEBUG)
logger.log(u'KODI: JSON response: ' + str(result), logger.DEBUG) return result # need to return response for parsing
return result # need to return response for parsing except ValueError as e:
except ValueError as e: logger.log(u'KODI: Unable to decode JSON response: %s' % response.text, logger.WARNING)
logger.log(u'KODI: Unable to decode JSON response: ' + response, logger.WARNING)
return False
except IOError as e:
logger.log(u'KODI: Warning: Couldn\'t contact Kodi at ' + host + ' - ' + ex(e), logger.WARNING)
return False return False
def _update_library(self, host=None, showName=None): def _update_library(self, host=None, showName=None):
@ -126,70 +112,66 @@ class KODINotifier:
logger.log(u'KODI: No host specified, check your settings', logger.DEBUG) logger.log(u'KODI: No host specified, check your settings', logger.DEBUG)
return False return False
logger.log(u'KODI: Updating library on host: ' + host, logger.MESSAGE) logger.log(u'KODI: Updating library on host: %s' % host, logger.MESSAGE)
# if we're doing per-show # if we're doing per-show
if showName: if showName:
tvshowid = -1 logger.log(u'KODI: Updating library for show %s' % showName, logger.DEBUG)
logger.log(u'KODI: Updating library for show ' + showName, logger.DEBUG)
# get tvshowid by showName # get tvshowid by showName
showsCommand = '{"jsonrpc":"2.0","method":"VideoLibrary.GetTVShows","id":1}' showsCommand = {'jsonrpc': '2.0', 'method': 'VideoLibrary.GetTVShows', 'id': 1}
showsResponse = self._send_to_kodi(showsCommand, host) showsResponse = self._send_to_kodi(showsCommand, host)
if showsResponse and 'result' in showsResponse and 'tvshows' in showsResponse['result']: try:
shows = showsResponse['result']['tvshows'] shows = showsResponse['result']['tvshows']
else: except:
logger.log(u'KODI: No TV shows in Kodi TV show list', logger.DEBUG) logger.log(u'KODI: No TV shows in Kodi TV show list', logger.DEBUG)
return False return False
for show in shows: try:
if (show['label'] == showName): tvshowid = next((show['tvshowid'] for show in shows if show['label'] == showName))
tvshowid = show['tvshowid'] except StopIteration:
break # exit out of loop otherwise the label and showname will not match up logger.log(u'XBMC: Exact show name not matched in XBMC TV show list', logger.DEBUG)
# this can be big, so free some memory
del shows
# we didn't find the show (exact match), thus revert to just doing a full update if enabled
if (tvshowid == -1):
logger.log(u'KODI: Exact show name not matched in KODI TV show list', logger.DEBUG)
return False return False
# lookup tv-show path # lookup tv-show path
pathCommand = '{"jsonrpc":"2.0","method":"VideoLibrary.GetTVShowDetails","params":{"tvshowid":%d, "properties": ["file"]},"id":1}' % (tvshowid) pathCommand = {'jsonrpc': '2.0', 'method': 'VideoLibrary.GetTVShowDetails',
'params': {'tvshowid': tvshowid, 'properties': ['file']}, 'id': 1}
pathResponse = self._send_to_kodi(pathCommand, host) pathResponse = self._send_to_kodi(pathCommand, host)
path = pathResponse['result']['tvshowdetails']['file'] path = pathResponse['result']['tvshowdetails']['file']
logger.log(u'KODI: Received Show: ' + showName + ' with ID: ' + str(tvshowid) + ' Path: ' + path, logger.DEBUG) logger.log(u'KODI: Received Show: %s with ID: %s Path: %s' % (showName, tvshowid, path), logger.DEBUG)
if (len(path) < 1): if len(path) < 1:
logger.log(u'KODI: No valid path found for ' + showName + ' with ID: ' + str(tvshowid) + ' on ' + host, logger.WARNING) logger.log(u'KODI: No valid path found for %s with ID: %s on %s' % (showName, tvshowid, host),
logger.WARNING)
return False return False
logger.log(u'KODI: Updating ' + showName + ' on ' + host + ' at ' + path, logger.DEBUG) logger.log(u'KODI: Updating %s on %s at %s' % (showName, host, path), logger.DEBUG)
updateCommand = '{"jsonrpc":"2.0","method":"VideoLibrary.Scan","params":{"directory":%s},"id":1}' % (json.dumps(path)) updateCommand = {'jsonrpc': '2.0', 'method': 'VideoLibrary.Scan',
'params': {'directory': json.dumps(path)}, 'id': 1}
request = self._send_to_kodi(updateCommand, host) request = self._send_to_kodi(updateCommand, host)
if not request: if not request:
logger.log(u'KODI: Update of show directory failed on ' + showName + ' on ' + host + ' at ' + path, logger.ERROR) logger.log(u'KODI: Update of show directory failed on %s on %s at %s' % (showName, host, path),
logger.ERROR)
return False return False
# catch if there was an error in the returned request # catch if there was an error in the returned request
for r in request: for r in request:
if 'error' in r: if 'error' in r:
logger.log(u'KODI: Error while attempting to update show directory for ' + showName + ' on ' + host + ' at ' + path, logger.ERROR) logger.log(u'KODI: Error while attempting to update show directory for %s on %s at %s'
% (showName, host, path), logger.ERROR)
return False return False
# do a full update if requested # do a full update if requested
else: else:
logger.log(u'KODI: Performing full library update on host: ' + host, logger.DEBUG) logger.log(u'KODI: Performing full library update on host: %s' % host, logger.DEBUG)
updateCommand = '{"jsonrpc":"2.0","method":"VideoLibrary.Scan","id":1}' updateCommand = {'jsonrpc': '2.0', 'method': 'VideoLibrary.Scan', 'id': 1}
request = self._send_to_kodi(updateCommand, host, sickbeard.KODI_USERNAME, sickbeard.KODI_PASSWORD) request = self._send_to_kodi(updateCommand, host, sickbeard.KODI_USERNAME, sickbeard.KODI_PASSWORD)
if not request: if not request:
logger.log(u'KODI: Full library update failed on host: ' + host, logger.ERROR) logger.log(u'KODI: Full library update failed on host: %s' % host, logger.ERROR)
return False return False
return True return True
def notify_snatch(self, ep_name): def notify_snatch(self, ep_name):
@ -202,16 +184,17 @@ class KODINotifier:
def notify_subtitle_download(self, ep_name, lang): def notify_subtitle_download(self, ep_name, lang):
if sickbeard.KODI_NOTIFY_ONSUBTITLEDOWNLOAD: if sickbeard.KODI_NOTIFY_ONSUBTITLEDOWNLOAD:
self._notify_kodi(ep_name + ': ' + lang, common.notifyStrings[common.NOTIFY_SUBTITLE_DOWNLOAD]) self._notify_kodi('%s: %s' % (ep_name, lang), common.notifyStrings[common.NOTIFY_SUBTITLE_DOWNLOAD])
def notify_git_update(self, new_version = '??'): def notify_git_update(self, new_version='??'):
if sickbeard.USE_KODI: if sickbeard.USE_KODI:
update_text=common.notifyStrings[common.NOTIFY_GIT_UPDATE_TEXT] update_text = common.notifyStrings[common.NOTIFY_GIT_UPDATE_TEXT]
title=common.notifyStrings[common.NOTIFY_GIT_UPDATE] title = common.notifyStrings[common.NOTIFY_GIT_UPDATE]
self._notify_kodi(update_text + new_version, title) self._notify_kodi('%s %s' % (update_text, new_version), title)
def test_notify(self, host, username, password): def test_notify(self, host, username, password):
return self._notify_kodi('Testing Kodi notifications from SickGear', 'Test', host, username, password, force=True) return self._notify_kodi(
'Testing Kodi notifications from SickGear', 'Test', host, username, password, force=True)
def update_library(self, showName=None): def update_library(self, showName=None):
@ -225,17 +208,19 @@ class KODINotifier:
for host in [x.strip() for x in sickbeard.KODI_HOST.split(',')]: for host in [x.strip() for x in sickbeard.KODI_HOST.split(',')]:
if self._update_library(host, showName): if self._update_library(host, showName):
if sickbeard.KODI_UPDATE_ONLYFIRST: if sickbeard.KODI_UPDATE_ONLYFIRST:
logger.log(u'KODI: Update first host successful on host ' + host + ', stopped sending library update commands', logger.DEBUG) logger.log(
u'KODI: Update first host successful on host %s , stopped sending library update commands'
% host, logger.DEBUG)
return True return True
else: else:
if sickbeard.KODI_ALWAYS_ON: if sickbeard.KODI_ALWAYS_ON:
result = result + 1 result = result + 1
# needed for the 'update kodi' submenu command # needed for the 'update kodi' submenu command as it only cares of the final result vs the individual ones
# as it only cares of the final result vs the individual ones
if result == 0: if result == 0:
return True return True
else: else:
return False return False
notifier = KODINotifier notifier = KODINotifier