2015-03-14 08:24:59 +11:00
|
|
|
import xbmc
|
|
|
|
import xbmcgui
|
|
|
|
import xbmcaddon
|
2015-04-22 07:34:56 +10:00
|
|
|
|
|
|
|
import requests
|
|
|
|
import json
|
|
|
|
import logging
|
|
|
|
|
|
|
|
import Utils as utils
|
2015-03-14 08:24:59 +11:00
|
|
|
from ClientInformation import ClientInformation
|
2015-04-24 20:17:08 +10:00
|
|
|
from requests.packages.urllib3.exceptions import InsecureRequestWarning
|
2015-04-22 07:34:56 +10:00
|
|
|
|
|
|
|
# Disable requests logging
|
2015-04-24 20:17:08 +10:00
|
|
|
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
|
2015-04-22 07:34:56 +10:00
|
|
|
logging.getLogger("requests").setLevel(logging.WARNING)
|
2015-03-14 08:24:59 +11:00
|
|
|
|
|
|
|
class DownloadUtils():
|
2015-04-22 07:34:56 +10:00
|
|
|
|
|
|
|
# Borg - multiple instances, shared state
|
|
|
|
_shared_state = {}
|
|
|
|
clientInfo = ClientInformation()
|
2015-03-14 08:24:59 +11:00
|
|
|
|
2015-04-22 07:34:56 +10:00
|
|
|
addonName = clientInfo.getAddonName()
|
|
|
|
addonId = clientInfo.getAddonId()
|
|
|
|
addon = xbmcaddon.Addon(id=addonId)
|
2015-04-03 21:13:01 +11:00
|
|
|
WINDOW = xbmcgui.Window(10000)
|
|
|
|
|
2015-04-22 07:34:56 +10:00
|
|
|
# Requests session
|
|
|
|
s = None
|
|
|
|
timeout = 30
|
2015-03-22 21:58:02 +11:00
|
|
|
|
2015-04-22 07:34:56 +10:00
|
|
|
def __init__(self):
|
2015-03-14 08:24:59 +11:00
|
|
|
|
2015-04-22 07:34:56 +10:00
|
|
|
self.__dict__ = self._shared_state
|
|
|
|
self.className = self.__class__.__name__
|
2015-03-14 08:24:59 +11:00
|
|
|
|
2015-04-22 07:34:56 +10:00
|
|
|
def logMsg(self, msg, lvl=1):
|
2015-03-14 08:24:59 +11:00
|
|
|
|
2015-04-24 20:17:08 +10:00
|
|
|
utils.logMsg("%s %s" % (self.addonName, self.className), msg, int(lvl))
|
2015-03-14 08:24:59 +11:00
|
|
|
|
2015-04-22 07:34:56 +10:00
|
|
|
def setUsername(self, username):
|
|
|
|
# Reserved for UserClient only
|
|
|
|
self.username = username
|
|
|
|
self.logMsg("Set username: %s" % username, 1)
|
2015-03-14 08:24:59 +11:00
|
|
|
|
2015-04-22 07:34:56 +10:00
|
|
|
def setUserId(self, userId):
|
|
|
|
# Reserved for UserClient only
|
|
|
|
self.userId = userId
|
|
|
|
self.logMsg("Set userId: %s" % userId, 2)
|
|
|
|
|
|
|
|
def setServer(self, server):
|
|
|
|
# Reserved for UserClient only
|
|
|
|
self.server = server
|
|
|
|
self.logMsg("Set server: %s" % server, 2)
|
|
|
|
|
|
|
|
def setToken(self, token):
|
|
|
|
# Reserved for UserClient only
|
|
|
|
self.token = token
|
|
|
|
self.logMsg("Set token: %s" % token, 2)
|
|
|
|
|
2015-04-25 11:57:04 +10:00
|
|
|
def setSSL(self, ssl, sslclient):
|
2015-04-24 20:17:08 +10:00
|
|
|
# Reserved for UserClient only
|
2015-04-25 11:57:04 +10:00
|
|
|
self.sslverify = ssl
|
|
|
|
self.sslclient = sslclient
|
|
|
|
self.logMsg("Verify SSL host certificate: %s" % ssl, 2)
|
|
|
|
self.logMsg("SSL client side certificate: %s" % sslclient, 2)
|
2015-04-24 20:17:08 +10:00
|
|
|
|
2015-04-22 07:34:56 +10:00
|
|
|
def postCapabilities(self, deviceId):
|
|
|
|
|
|
|
|
# Get sessionId
|
|
|
|
url = "{server}/mediabrowser/Sessions?DeviceId=%s&format=json" % deviceId
|
|
|
|
result = self.downloadUrl(url)
|
|
|
|
# sessionId result
|
2015-04-27 06:41:39 +10:00
|
|
|
self.logMsg("Session result: %s" % result, 2)
|
2015-04-22 07:34:56 +10:00
|
|
|
self.sessionId = result[0][u'Id']
|
|
|
|
|
|
|
|
# Settings for capabilities
|
2015-03-23 19:44:16 +11:00
|
|
|
playableMediaTypes = "Audio,Video"
|
|
|
|
supportedCommands = "Play,Playstate,SendString,DisplayMessage,PlayNext"
|
2015-04-22 07:34:56 +10:00
|
|
|
|
|
|
|
# Post settings to sessionId
|
|
|
|
url = "{server}/mediabrowser/Sessions/Capabilities?Id=%s&PlayableMediaTypes=%s&SupportedCommands=%s&SupportsMediaControl=True" % (self.sessionId, playableMediaTypes, supportedCommands)
|
|
|
|
data = {}
|
|
|
|
self.logMsg("Capabilities URL: %s" % url, 2)
|
|
|
|
self.logMsg("PostData: %s" % data, 2)
|
|
|
|
|
|
|
|
self.downloadUrl(url, postBody=data, type="POST")
|
|
|
|
self.logMsg("Posted capabilities to sessionId: %s" % self.sessionId, 1)
|
|
|
|
|
|
|
|
def startSession(self):
|
|
|
|
|
|
|
|
self.deviceId = self.clientInfo.getMachineId()
|
|
|
|
|
|
|
|
# User is identified from this point
|
|
|
|
# Attach authenticated header to the session
|
2015-04-24 20:17:08 +10:00
|
|
|
verify = None
|
2015-04-25 11:57:04 +10:00
|
|
|
cert = None
|
|
|
|
header = self.getHeader()
|
2015-04-24 20:17:08 +10:00
|
|
|
|
2015-04-25 11:57:04 +10:00
|
|
|
# If user enabled host certificate verification
|
|
|
|
if self.sslverify:
|
2015-04-24 20:17:08 +10:00
|
|
|
verify = True
|
2015-04-25 11:57:04 +10:00
|
|
|
cert = self.sslclient
|
|
|
|
|
2015-04-22 07:34:56 +10:00
|
|
|
# Start session
|
|
|
|
self.s = requests.Session()
|
2015-04-24 20:17:08 +10:00
|
|
|
self.s.headers = header
|
|
|
|
self.s.verify = verify
|
2015-04-25 11:57:04 +10:00
|
|
|
self.s.cert = cert
|
2015-04-22 07:34:56 +10:00
|
|
|
# Retry connections to the server
|
|
|
|
self.s.mount("http://", requests.adapters.HTTPAdapter(max_retries=1))
|
|
|
|
self.s.mount("https://", requests.adapters.HTTPAdapter(max_retries=1))
|
|
|
|
|
|
|
|
self.logMsg("Requests session started on: %s" % self.server)
|
|
|
|
self.postCapabilities(self.deviceId)
|
2015-03-14 08:24:59 +11:00
|
|
|
|
|
|
|
def imageUrl(self, id, type, index, width, height):
|
2015-04-22 07:34:56 +10:00
|
|
|
# To move to API.py
|
|
|
|
return "%s/mediabrowser/Items/%s/Images/%s?MaxWidth=%s&MaxHeight=%s&Index=%s" % (self.server, id, type, width, height, index)
|
|
|
|
|
|
|
|
def getHeader(self, authenticate=True):
|
|
|
|
|
|
|
|
clientInfo = self.clientInfo
|
|
|
|
|
|
|
|
deviceName = clientInfo.getDeviceName()
|
|
|
|
deviceId = clientInfo.getMachineId()
|
2015-03-14 08:24:59 +11:00
|
|
|
version = clientInfo.getVersion()
|
2015-04-22 07:34:56 +10:00
|
|
|
|
|
|
|
if not authenticate:
|
|
|
|
# If user is not authenticated
|
|
|
|
auth = 'MediaBrowser Client="Kodi", Device="%s", DeviceId="%s", Version="%s"' % (deviceName, deviceId, version)
|
|
|
|
header = {"Accept-encoding": "gzip", "Accept-Charset": "UTF-8,*", "Authorization": auth}
|
|
|
|
|
|
|
|
self.logMsg("Header: %s" % header, 2)
|
|
|
|
return header
|
2015-03-14 08:24:59 +11:00
|
|
|
|
|
|
|
else:
|
2015-04-22 07:34:56 +10:00
|
|
|
userId = self.userId
|
|
|
|
token = self.token
|
|
|
|
# Attached to the requests session
|
|
|
|
auth = 'MediaBrowser UserId="%s", Client="Kodi", Device="%s", DeviceId="%s", Version="%s"' % (userId, deviceName, deviceId, version)
|
|
|
|
header = {"Accept-encoding": "gzip", "Accept-Charset": "UTF-8,*", "Authorization": auth, "X-MediaBrowser-Token": token}
|
2015-03-14 08:24:59 +11:00
|
|
|
|
2015-04-22 07:34:56 +10:00
|
|
|
self.logMsg("Header: %s" % header, 2)
|
|
|
|
return header
|
|
|
|
|
|
|
|
def downloadUrl(self, url, postBody=None, type="GET", authenticate=True):
|
2015-03-14 13:37:03 +11:00
|
|
|
|
2015-04-22 07:34:56 +10:00
|
|
|
self.logMsg("=== ENTER downloadUrl ===", 2)
|
|
|
|
|
|
|
|
WINDOW = self.WINDOW
|
|
|
|
timeout = self.timeout
|
|
|
|
default_link = ""
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
2015-04-27 06:41:39 +10:00
|
|
|
# If user is authenticated
|
|
|
|
if (authenticate):
|
|
|
|
# Get requests session
|
|
|
|
s = self.s
|
|
|
|
# Replace for the real values and append api_key
|
|
|
|
url = url.replace("{server}", self.server, 1)
|
|
|
|
url = url.replace("{UserId}", self.userId, 1)
|
|
|
|
url = "%s&api_key=%s" % (url, self.token)
|
|
|
|
|
|
|
|
self.logMsg("URL: %s" % url, 2)
|
|
|
|
# Prepare request
|
|
|
|
if type == "GET":
|
|
|
|
r = s.get(url, params=postBody, timeout=timeout)
|
|
|
|
elif type == "POST":
|
|
|
|
r = s.post(url, params=postBody, timeout=timeout)
|
|
|
|
elif type == "DELETE":
|
|
|
|
r = s.delete(url, params=postBody, timeout=timeout)
|
|
|
|
|
|
|
|
# If user is not authenticated
|
|
|
|
elif not authenticate:
|
|
|
|
|
|
|
|
self.logMsg("URL: %s" % url, 2)
|
|
|
|
header = self.getHeader(authenticate=False)
|
|
|
|
verifyssl = False
|
|
|
|
|
|
|
|
# If user enables ssl verification
|
|
|
|
try:
|
|
|
|
verifyssl = self.sslverify
|
|
|
|
except AttributeError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
# Prepare request
|
|
|
|
if type == "GET":
|
|
|
|
r = requests.get(url, params=postBody, headers=header, timeout=timeout, verify=verifyssl)
|
|
|
|
elif type == "POST":
|
|
|
|
r = requests.post(url, params=postBody, headers=header, timeout=timeout, verify=verifyssl)
|
|
|
|
|
|
|
|
# Process the response
|
2015-04-22 07:34:56 +10:00
|
|
|
if r.status_code == 204:
|
2015-04-27 06:41:39 +10:00
|
|
|
# No body in the response
|
2015-04-24 20:17:08 +10:00
|
|
|
self.logMsg("====== 204 Success ======", 2)
|
2015-04-22 07:34:56 +10:00
|
|
|
return default_link
|
2015-04-27 06:41:39 +10:00
|
|
|
|
2015-04-22 07:34:56 +10:00
|
|
|
elif r.status_code == requests.codes.ok:
|
2015-03-14 08:24:59 +11:00
|
|
|
try:
|
2015-04-22 07:34:56 +10:00
|
|
|
# UTF-8 - JSON object
|
|
|
|
r = r.json()
|
2015-04-24 20:17:08 +10:00
|
|
|
self.logMsg("====== 200 Success ======", 2)
|
2015-04-22 07:34:56 +10:00
|
|
|
return r
|
|
|
|
except:
|
|
|
|
self.logMsg("Unable to convert the response for: %s" % url, 1)
|
2015-04-27 06:41:39 +10:00
|
|
|
else:
|
|
|
|
r.raise_for_status()
|
2015-04-22 07:34:56 +10:00
|
|
|
|
|
|
|
return default_link
|
|
|
|
|
|
|
|
# TO REVIEW EXCEPTIONS
|
|
|
|
except requests.exceptions.ConnectionError as e:
|
2015-04-27 06:41:39 +10:00
|
|
|
# Addon is already aware of status
|
|
|
|
if WINDOW.getProperty("Server_online") == "true":
|
|
|
|
self.logMsg("Server unreachable at: %s" % url, 0)
|
|
|
|
self.logMsg(e, 2)
|
|
|
|
WINDOW.setProperty("Server_online", "false")
|
|
|
|
pass
|
2015-03-14 08:24:59 +11:00
|
|
|
|
2015-04-22 07:34:56 +10:00
|
|
|
except requests.exceptions.ConnectTimeout as e:
|
|
|
|
self.logMsg("Server timeout at: %s" % url, 0)
|
2015-04-27 06:41:39 +10:00
|
|
|
self.logMsg(e, 2)
|
2015-04-22 07:34:56 +10:00
|
|
|
|
|
|
|
except requests.exceptions.HTTPError as e:
|
|
|
|
|
2015-04-27 06:41:39 +10:00
|
|
|
if (r.status_code == 301) or (r.status_code == 302):
|
|
|
|
# Redirects
|
|
|
|
pass
|
|
|
|
|
|
|
|
elif (r.status_code == 400):
|
|
|
|
# Bad requests
|
|
|
|
pass
|
|
|
|
|
|
|
|
elif (r.status_code == 401):
|
2015-04-22 07:34:56 +10:00
|
|
|
# Unauthorized
|
2015-04-03 21:13:01 +11:00
|
|
|
status = WINDOW.getProperty("Server_status")
|
|
|
|
if (status == "401") or (status == "Auth"):
|
2015-03-14 08:24:59 +11:00
|
|
|
pass
|
2015-04-03 21:13:01 +11:00
|
|
|
else:
|
|
|
|
# Tell UserClient token has been revoked.
|
|
|
|
WINDOW.setProperty("Server_status", "401")
|
2015-04-22 07:34:56 +10:00
|
|
|
self.logMsg("HTTP Error: %s" % e, 0)
|
|
|
|
|
2015-04-27 06:41:39 +10:00
|
|
|
elif (r.status_code == 500):
|
|
|
|
# Out of memory
|
2015-03-14 08:24:59 +11:00
|
|
|
pass
|
|
|
|
|
2015-04-24 20:17:08 +10:00
|
|
|
except requests.exceptions.SSLError as e:
|
|
|
|
self.logMsg("Invalid SSL certificate for: %s" % url, 0)
|
2015-04-27 06:41:39 +10:00
|
|
|
self.logMsg(e, 2)
|
2015-04-24 20:17:08 +10:00
|
|
|
|
2015-04-22 07:34:56 +10:00
|
|
|
except requests.exceptions.RequestException as e:
|
|
|
|
self.logMsg("Unknown error connecting to: %s" % url, 0)
|
2015-04-27 06:41:39 +10:00
|
|
|
self.logMsg(e, 2)
|
2015-04-22 07:34:56 +10:00
|
|
|
|
|
|
|
return default_link
|