You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
218 lines
8.8 KiB
218 lines
8.8 KiB
from couchpotato.core.helpers.variable import splitString
|
|
from couchpotato.core.logger import CPLog
|
|
from couchpotato.core.notifications.base import Notification
|
|
from urllib2 import URLError
|
|
import base64
|
|
import json
|
|
import socket
|
|
import traceback
|
|
import urllib
|
|
|
|
log = CPLog(__name__)
|
|
|
|
|
|
class XBMC(Notification):
|
|
|
|
listen_to = ['renamer.after', 'movie.snatched']
|
|
use_json_notifications = {}
|
|
http_time_between_calls = 0
|
|
|
|
def notify(self, message = '', data = {}, listener = None):
|
|
|
|
hosts = splitString(self.conf('host'))
|
|
|
|
successful = 0
|
|
max_successful = 0
|
|
for host in hosts:
|
|
|
|
if self.use_json_notifications.get(host) is None:
|
|
self.getXBMCJSONversion(host, message = message)
|
|
|
|
if self.use_json_notifications.get(host):
|
|
calls = [
|
|
('GUI.ShowNotification', {'title': self.default_title, 'message': message, 'image': self.getNotificationImage('small')}),
|
|
]
|
|
|
|
if data and data.get('destination_dir') and (not self.conf('only_first') or hosts.index(host) == 0):
|
|
param = {}
|
|
if self.conf('remote_dir_scan') or socket.getfqdn('localhost') == socket.getfqdn(host.split(':')[0]):
|
|
param = {'directory': data['destination_dir']}
|
|
|
|
calls.append(('VideoLibrary.Scan', param))
|
|
|
|
max_successful += len(calls)
|
|
response = self.request(host, calls)
|
|
else:
|
|
response = self.notifyXBMCnoJSON(host, {'title':self.default_title, 'message':message})
|
|
|
|
if data and data.get('destination_dir') and (not self.conf('only_first') or hosts.index(host) == 0):
|
|
response += self.request(host, [('VideoLibrary.Scan', {})])
|
|
max_successful += 1
|
|
|
|
max_successful += 1
|
|
|
|
try:
|
|
for result in response:
|
|
if (result.get('result') and result['result'] == 'OK'):
|
|
successful += 1
|
|
elif (result.get('error')):
|
|
log.error('XBMC error; %s: %s (%s)', (result['id'], result['error']['message'], result['error']['code']))
|
|
|
|
except:
|
|
log.error('Failed parsing results: %s', traceback.format_exc())
|
|
|
|
return successful == max_successful
|
|
|
|
def getXBMCJSONversion(self, host, message = ''):
|
|
|
|
success = False
|
|
|
|
# XBMC JSON-RPC version request
|
|
response = self.request(host, [
|
|
('JSONRPC.Version', {})
|
|
])
|
|
for result in response:
|
|
if (result.get('result') and type(result['result']['version']).__name__ == 'int'):
|
|
# only v2 and v4 return an int object
|
|
# v6 (as of XBMC v12(Frodo)) is required to send notifications
|
|
xbmc_rpc_version = str(result['result']['version'])
|
|
|
|
log.debug('XBMC JSON-RPC Version: %s ; Notifications by JSON-RPC only supported for v6 [as of XBMC v12(Frodo)]', xbmc_rpc_version)
|
|
|
|
# disable JSON use
|
|
self.use_json_notifications[host] = False
|
|
|
|
# send the text message
|
|
resp = self.notifyXBMCnoJSON(host, {'title':self.default_title, 'message':message})
|
|
for result in resp:
|
|
if (result.get('result') and result['result'] == 'OK'):
|
|
log.debug('Message delivered successfully!')
|
|
success = True
|
|
break
|
|
elif (result.get('error')):
|
|
log.error('XBMC error; %s: %s (%s)', (result['id'], result['error']['message'], result['error']['code']))
|
|
break
|
|
|
|
elif (result.get('result') and type(result['result']['version']).__name__ == 'dict'):
|
|
# XBMC JSON-RPC v6 returns an array object containing
|
|
# major, minor and patch number
|
|
xbmc_rpc_version = str(result['result']['version']['major'])
|
|
xbmc_rpc_version += '.' + str(result['result']['version']['minor'])
|
|
xbmc_rpc_version += '.' + str(result['result']['version']['patch'])
|
|
|
|
log.debug('XBMC JSON-RPC Version: %s', xbmc_rpc_version)
|
|
|
|
# ok, XBMC version is supported
|
|
self.use_json_notifications[host] = True
|
|
|
|
# send the text message
|
|
resp = self.request(host, [('GUI.ShowNotification', {'title':self.default_title, 'message':message, 'image': self.getNotificationImage('small')})])
|
|
for result in resp:
|
|
if (result.get('result') and result['result'] == 'OK'):
|
|
log.debug('Message delivered successfully!')
|
|
success = True
|
|
break
|
|
elif (result.get('error')):
|
|
log.error('XBMC error; %s: %s (%s)', (result['id'], result['error']['message'], result['error']['code']))
|
|
break
|
|
|
|
# error getting version info (we do have contact with XBMC though)
|
|
elif (result.get('error')):
|
|
log.error('XBMC error; %s: %s (%s)', (result['id'], result['error']['message'], result['error']['code']))
|
|
|
|
log.debug('Use JSON notifications: %s ', self.use_json_notifications)
|
|
|
|
return success
|
|
|
|
def notifyXBMCnoJSON(self, host, data):
|
|
|
|
server = 'http://%s/xbmcCmds/' % host
|
|
|
|
# Notification(title, message [, timeout , image])
|
|
cmd = "xbmcHttp?command=ExecBuiltIn(Notification(%s,%s,'',%s))" % (urllib.quote(data['title']), urllib.quote(data['message']), urllib.quote(self.getNotificationImage('medium')))
|
|
server += cmd
|
|
|
|
# I have no idea what to set to, just tried text/plain and seems to be working :)
|
|
headers = {
|
|
'Content-Type': 'text/plain',
|
|
}
|
|
|
|
# authentication support
|
|
if self.conf('password'):
|
|
base64string = base64.encodestring('%s:%s' % (self.conf('username'), self.conf('password'))).replace('\n', '')
|
|
headers['Authorization'] = 'Basic %s' % base64string
|
|
|
|
try:
|
|
log.debug('Sending non-JSON-type request to %s: %s', (host, data))
|
|
|
|
# response wil either be 'OK':
|
|
# <html>
|
|
# <li>OK
|
|
# </html>
|
|
#
|
|
# or 'Error':
|
|
# <html>
|
|
# <li>Error:<message>
|
|
# </html>
|
|
#
|
|
response = self.urlopen(server, headers = headers, timeout = 3, show_error = False)
|
|
|
|
if 'OK' in response:
|
|
log.debug('Returned from non-JSON-type request %s: %s', (host, response))
|
|
# manually fake expected response array
|
|
return [{'result': 'OK'}]
|
|
else:
|
|
log.error('Returned from non-JSON-type request %s: %s', (host, response))
|
|
# manually fake expected response array
|
|
return [{'result': 'Error'}]
|
|
|
|
except URLError, e:
|
|
if isinstance(e.reason, socket.timeout):
|
|
log.info('Couldn\'t send request to XBMC, assuming it\'s turned off')
|
|
return [{'result': 'Error'}]
|
|
else:
|
|
log.error('Failed sending non-JSON-type request to XBMC: %s', traceback.format_exc())
|
|
return [{'result': 'Error'}]
|
|
except:
|
|
log.error('Failed sending non-JSON-type request to XBMC: %s', traceback.format_exc())
|
|
return [{'result': 'Error'}]
|
|
|
|
def request(self, host, requests):
|
|
server = 'http://%s/jsonrpc' % host
|
|
|
|
data = []
|
|
for req in requests:
|
|
method, kwargs = req
|
|
data.append({
|
|
'method': method,
|
|
'params': kwargs,
|
|
'jsonrpc': '2.0',
|
|
'id': method,
|
|
})
|
|
data = json.dumps(data)
|
|
|
|
headers = {
|
|
'Content-Type': 'application/json',
|
|
}
|
|
|
|
if self.conf('password'):
|
|
base64string = base64.encodestring('%s:%s' % (self.conf('username'), self.conf('password'))).replace('\n', '')
|
|
headers['Authorization'] = 'Basic %s' % base64string
|
|
|
|
try:
|
|
log.debug('Sending request to %s: %s', (host, data))
|
|
response = self.getJsonData(server, headers = headers, params = data, timeout = 3, show_error = False)
|
|
log.debug('Returned from request %s: %s', (host, response))
|
|
|
|
return response
|
|
except URLError, e:
|
|
if isinstance(e.reason, socket.timeout):
|
|
log.info('Couldn\'t send request to XBMC, assuming it\'s turned off')
|
|
return []
|
|
else:
|
|
log.error('Failed sending request to XBMC: %s', traceback.format_exc())
|
|
return []
|
|
except:
|
|
log.error('Failed sending request to XBMC: %s', traceback.format_exc())
|
|
return []
|
|
|
|
|