diff --git a/resources/lib/PlexAPI.py b/resources/lib/PlexAPI.py index 3c0543ca..b28b8a90 100644 --- a/resources/lib/PlexAPI.py +++ b/resources/lib/PlexAPI.py @@ -67,21 +67,10 @@ class PlexAPI(): # CONSTANTS # Timeout for POST/GET commands, I guess in seconds timeout = 10 - # VARIABLES def __init__(self): self.__language__ = xbmcaddon.Addon().getLocalizedString self.g_PMS = {} - client = clientinfo.ClientInfo() - self.addonId = client.getAddonId() - self.clientId = client.getDeviceId() - self.deviceName = client.getDeviceName() - self.plexversion = client.getVersion() - self.platform = client.getPlatform() - self.userId = utils.window('currUserId') - self.token = utils.window('pms_token') - self.server = utils.window('pms_server') - self.doUtils = downloadutils.DownloadUtils() def GetPlexLoginFromSettings(self): @@ -145,7 +134,8 @@ class PlexAPI(): retrievedPlexLogin, authtoken = self.MyPlexSignIn( plexLogin, plexPassword, - {'X-Plex-Client-Identifier': self.clientId}) + {'X-Plex-Client-Identifier': + clientinfo.ClientInfo().getDeviceId()}) self.logMsg("plex.tv username and token: %s, %s" % (plexLogin, authtoken), 1) if plexLogin == '': @@ -345,7 +335,7 @@ class PlexAPI(): """ # Add '/clients' to URL because then an authentication is necessary # If a plex.tv URL was passed, this does not work. - header = self.getXArgsDeviceInfo() + header = clientinfo.ClientInfo().getXArgsDeviceInfo() if token: header['X-Plex-Token'] = token sslverify = utils.settings('sslverify') @@ -816,148 +806,6 @@ class PlexAPI(): XML = self.getXMLFromPMS(PMS['baseURL'],PMS['path'],PMS['options'],PMS['token']) queue.put( (PMS['data'], XML) ) - def getXArgsDeviceInfo(self, options={}): - """ - Returns a dictionary that can be used as headers for GET and POST - requests. An authentication option is NOT yet added. - - Inputs: - options: dictionary of options that will override the - standard header options otherwise set. - Output: - header dictionary - """ - # Get addon infos - xargs = { - 'Accept': '*/*', - 'Connection': 'keep-alive', - "Content-Type": "application/x-www-form-urlencoded", - # "Access-Control-Allow-Origin": "*", - 'X-Plex-Language': 'en', - 'X-Plex-Device': self.addonName, - 'X-Plex-Client-Platform': self.platform, - 'X-Plex-Device-Name': self.deviceName, - 'X-Plex-Platform': self.addonName, - 'X-Plex-Platform-Version': 'unknown', - 'X-Plex-Model': 'unknown', - 'X-Plex-Product': self.addonName, - 'X-Plex-Version': self.plexversion, - 'X-Plex-Client-Identifier': self.clientId, - 'X-Plex-Provides': 'player', - } - - if self.token: - xargs['X-Plex-Token'] = self.token - if options: - xargs.update(options) - return xargs - - def getXMLFromMultiplePMS(self, ATV_udid, path, type, options={}): - """ - provide combined XML representation of local servers' XMLs, eg. /library/section - - parameters: - ATV_udid - path - type - owned <> shared (previously: local, myplex) - options - result: - XML - """ - queue = Queue.Queue() - threads = [] - - root = etree.Element("MediaConverter") - root.set('friendlyName', type+' Servers') - - for uuid in g_PMS.get(ATV_udid, {}): - if (type=='all' and getPMSProperty(ATV_udid, uuid, 'name')!='plex.tv') or \ - (type=='owned' and getPMSProperty(ATV_udid, uuid, 'owned')=='1') or \ - (type=='shared' and getPMSProperty(ATV_udid, uuid, 'owned')=='0') or \ - (type=='local' and getPMSProperty(ATV_udid, uuid, 'local')=='1') or \ - (type=='remote' and getPMSProperty(ATV_udid, uuid, 'local')=='0'): - Server = etree.SubElement(root, 'Server') # create "Server" node - Server.set('name', getPMSProperty(ATV_udid, uuid, 'name')) - Server.set('address', getPMSProperty(ATV_udid, uuid, 'ip')) - Server.set('port', getPMSProperty(ATV_udid, uuid, 'port')) - Server.set('baseURL', getPMSProperty(ATV_udid, uuid, 'baseURL')) - Server.set('local', getPMSProperty(ATV_udid, uuid, 'local')) - Server.set('owned', getPMSProperty(ATV_udid, uuid, 'owned')) - - baseURL = getPMSProperty(ATV_udid, uuid, 'baseURL') - token = getPMSProperty(ATV_udid, uuid, 'accesstoken') - PMS_mark = 'PMS(' + getPMSProperty(ATV_udid, uuid, 'address') + ')' - - Server.set('searchKey', PMS_mark + getURL('', '', '/Search/Entry.xml')) - - # request XMLs, one thread for each - PMS = { 'baseURL':baseURL, 'path':path, 'options':options, 'token':token, \ - 'data': {'uuid': uuid, 'Server': Server} } - t = Thread(target=getXMLFromPMSToQueue, args=(PMS, queue)) - t.start() - threads.append(t) - - # wait for requests being answered - for t in threads: - t.join() - - # add new data to root XML, individual Server - while not queue.empty(): - (data, XML) = queue.get() - uuid = data['uuid'] - Server = data['Server'] - - baseURL = getPMSProperty(ATV_udid, uuid, 'baseURL') - token = getPMSProperty(ATV_udid, uuid, 'accesstoken') - PMS_mark = 'PMS(' + getPMSProperty(ATV_udid, uuid, 'address') + ')' - - if XML==False: - Server.set('size', '0') - else: - Server.set('size', XML.getroot().get('size', '0')) - - for Dir in XML.getiterator('Directory'): # copy "Directory" content, add PMS to links - key = Dir.get('key') # absolute path - Dir.set('key', PMS_mark + getURL('', path, key)) - Dir.set('refreshKey', getURL(baseURL, path, key) + '/refresh') - if 'thumb' in Dir.attrib: - Dir.set('thumb', PMS_mark + getURL('', path, Dir.get('thumb'))) - if 'art' in Dir.attrib: - Dir.set('art', PMS_mark + getURL('', path, Dir.get('art'))) - Server.append(Dir) - - for Playlist in XML.getiterator('Playlist'): # copy "Playlist" content, add PMS to links - key = Playlist.get('key') # absolute path - Playlist.set('key', PMS_mark + getURL('', path, key)) - if 'composite' in Playlist.attrib: - Playlist.set('composite', PMS_mark + getURL('', path, Playlist.get('composite'))) - Server.append(Playlist) - - for Video in XML.getiterator('Video'): # copy "Video" content, add PMS to links - key = Video.get('key') # absolute path - Video.set('key', PMS_mark + getURL('', path, key)) - if 'thumb' in Video.attrib: - Video.set('thumb', PMS_mark + getURL('', path, Video.get('thumb'))) - if 'parentKey' in Video.attrib: - Video.set('parentKey', PMS_mark + getURL('', path, Video.get('parentKey'))) - if 'parentThumb' in Video.attrib: - Video.set('parentThumb', PMS_mark + getURL('', path, Video.get('parentThumb'))) - if 'grandparentKey' in Video.attrib: - Video.set('grandparentKey', PMS_mark + getURL('', path, Video.get('grandparentKey'))) - if 'grandparentThumb' in Video.attrib: - Video.set('grandparentThumb', PMS_mark + getURL('', path, Video.get('grandparentThumb'))) - Server.append(Video) - - root.set('size', str(len(root.findall('Server')))) - - XML = etree.ElementTree(root) - - dprint(__name__, 1, "====== Local Server/Sections XML ======") - dprint(__name__, 1, XML.getroot()) - dprint(__name__, 1, "====== Local Server/Sections XML finished ======") - - return XML # XML representation - created "just in time". Do we need to cache it? - def getURL(self, baseURL, path, key): if key.startswith('http://') or key.startswith('https://'): # external server URL = key @@ -988,7 +836,7 @@ class PlexAPI(): MyPlexURL = 'https://' + MyPlexHost + MyPlexSignInPath # create POST request - xargs = self.getXArgsDeviceInfo(options) + xargs = clientinfo.ClientInfo().getXArgsDeviceInfo(options) self.logMsg("Header is: %s" % xargs, 1) request = urllib2.Request(MyPlexURL, None, xargs) request.get_method = lambda: 'POST' @@ -1120,7 +968,7 @@ class PlexAPI(): self.logMsg("No user selected.", 0) utils.settings('username', value='') xbmc.executebuiltin('Addon.OpenSettings(%s)' - % self.addonId) + % clientinfo.ClientInfo().getAddonId()) return False # Only 1 user received, choose that one else: @@ -1164,7 +1012,8 @@ class PlexAPI(): if not username: self.logMsg('Failed signing in a user to plex.tv', -1) - xbmc.executebuiltin('Addon.OpenSettings(%s)' % self.addonId) + xbmc.executebuiltin('Addon.OpenSettings(%s)' + % clientinfo.ClientInfo().getAddonId()) return False return { @@ -1385,7 +1234,7 @@ class PlexAPI(): args['protocol'] = 'http' args['maxAudioBitrate'] = maxAudioBitrate - xargs = getXArgsDeviceInfo(options) + xargs = clientinfo.ClientInfo().getXArgsDeviceInfo(options) if not AuthToken=='': xargs['X-Plex-Token'] = AuthToken @@ -1465,13 +1314,8 @@ class API(): self.item = item # which media part in the XML response shall we look at? self.part = 0 - self.clientinfo = clientinfo.ClientInfo() - self.clientId = self.clientinfo.getDeviceId() self.__language__ = xbmcaddon.Addon().getLocalizedString - - self.userId = utils.window('currUserId') self.server = utils.window('pms_server') - self.token = utils.window('pms_token') def setPartNumber(self, number=None): """ @@ -1888,7 +1732,7 @@ class API(): arguments overrule everything """ - xargs = PlexAPI().getXArgsDeviceInfo() + xargs = clientinfo.ClientInfo().getXArgsDeviceInfo() xargs.update(arguments) if '?' not in url: url = "%s?%s" % (url, urlencode(xargs)) @@ -1902,10 +1746,12 @@ class API(): url may or may not already contain a '?' """ + if utils.window('pms_token') == '': + return url if '?' not in url: - url = "%s?X-Plex-Token=%s" % (url, self.token) + url = "%s?X-Plex-Token=%s" % (url, utils.window('pms_token')) else: - url = "%s&X-Plex-Token=%s" % (url, self.token) + url = "%s&X-Plex-Token=%s" % (url, utils.window('pms_token')) return url def GetPlayQueueItemID(self): @@ -2176,7 +2022,7 @@ class API(): TODO: mediaIndex """ - xargs = PlexAPI().getXArgsDeviceInfo() + xargs = clientinfo.ClientInfo().getXArgsDeviceInfo() # For DirectPlay, path/key of PART is needed if action == "DirectPlay": path = self.item[0][self.part].attrib['key'] diff --git a/resources/lib/clientinfo.py b/resources/lib/clientinfo.py index a264a118..2197ba82 100644 --- a/resources/lib/clientinfo.py +++ b/resources/lib/clientinfo.py @@ -7,40 +7,72 @@ from uuid import uuid4 import xbmc import xbmcaddon -import utils +from utils import logging, window, settings ############################################################################### -@utils.logging +@logging class ClientInfo(): def __init__(self): - self.addon = xbmcaddon.Addon() + def getXArgsDeviceInfo(self, options=None): + """ + Returns a dictionary that can be used as headers for GET and POST + requests. An authentication option is NOT yet added. + + Inputs: + options: dictionary of options that will override the + standard header options otherwise set. + Output: + header dictionary + """ + # Get addon infos + xargs = { + 'Accept': '*/*', + 'Connection': 'keep-alive', + "Content-Type": "application/x-www-form-urlencoded", + # "Access-Control-Allow-Origin": "*", + # 'X-Plex-Language': 'en', + 'X-Plex-Device': self.getAddonName(), + 'X-Plex-Client-Platform': self.getPlatform(), + 'X-Plex-Device-Name': self.getDeviceName(), + 'X-Plex-Platform': self.getPlatform(), + # 'X-Plex-Platform-Version': 'unknown', + # 'X-Plex-Model': 'unknown', + 'X-Plex-Product': self.getAddonName(), + 'X-Plex-Version': self.getVersion(), + 'X-Plex-Client-Identifier': self.getDeviceId(), + 'X-Plex-Provides': 'player', + } + + if window('pms_token'): + xargs['X-Plex-Token'] = window('pms_token') + if options is not None: + xargs.update(options) + return xargs + def getAddonName(self): # Used for logging return self.addon.getAddonInfo('name') def getAddonId(self): - return "plugin.video.plexkodiconnect" def getVersion(self): - return self.addon.getAddonInfo('version') def getDeviceName(self): - - if utils.settings('deviceNameOpt') == "false": + if settings('deviceNameOpt') == "false": # Use Kodi's deviceName - deviceName = xbmc.getInfoLabel('System.FriendlyName').decode('utf-8') + deviceName = xbmc.getInfoLabel( + 'System.FriendlyName').decode('utf-8') else: - deviceName = utils.settings('deviceName') + deviceName = settings('deviceName') deviceName = deviceName.replace("\"", "_") deviceName = deviceName.replace("/", "_") - return deviceName def getPlatform(self): @@ -69,25 +101,25 @@ class ClientInfo(): If id does not exist, create one and save in Kodi settings file. """ - if reset is True: - utils.window('plex_client_Id', clear=True) - utils.settings('plex_client_Id', value="") + window('plex_client_Id', clear=True) + settings('plex_client_Id', value="") - clientId = utils.window('plex_client_Id') + clientId = window('plex_client_Id') if clientId: return clientId - clientId = utils.settings('plex_client_Id') + clientId = settings('plex_client_Id') # Because Kodi appears to cache file settings!! if clientId != "" and reset is False: - utils.window('plex_client_Id', value=clientId) - self.logMsg("Unique device Id plex_client_Id loaded: %s" % clientId, 1) + window('plex_client_Id', value=clientId) + self.logMsg("Unique device Id plex_client_Id loaded: %s" + % clientId, 1) return clientId self.logMsg("Generating a new deviceid.", 0) clientId = str(uuid4()) - utils.settings('plex_client_Id', value=clientId) - utils.window('plex_client_Id', value=clientId) + settings('plex_client_Id', value=clientId) + window('plex_client_Id', value=clientId) self.logMsg("Unique device Id plex_client_Id loaded: %s" % clientId, 1) return clientId diff --git a/resources/lib/downloadutils.py b/resources/lib/downloadutils.py index 28a41d01..434170dc 100644 --- a/resources/lib/downloadutils.py +++ b/resources/lib/downloadutils.py @@ -13,8 +13,6 @@ import xbmcgui import utils import clientinfo -import PlexAPI - ############################################################################### # Disable requests logging @@ -30,16 +28,16 @@ class DownloadUtils(): # Borg - multiple instances, shared state _shared_state = {} - clientInfo = clientinfo.ClientInfo() # Requests session s = None timeout = 30 def __init__(self): - self.__dict__ = self._shared_state + self.clientInfo = clientinfo.ClientInfo() + def setUsername(self, username): # Reserved for userclient only self.username = username @@ -175,17 +173,14 @@ class DownloadUtils(): self.logMsg("Requests session could not be terminated.", 1) def getHeader(self, authenticate=True, options={}): - plx = PlexAPI.PlexAPI() if authenticate: - header = plx.getXArgsDeviceInfo(options=options) + header = self.clientInfo.getXArgsDeviceInfo(options=options) else: - header = plx.getXArgsDeviceInfo(options=options) + header = self.clientInfo.getXArgsDeviceInfo(options=options) return header - def downloadUrl(self, url, postBody=None, type="GET", parameters=None, authenticate=True, headerOptions={}): - - # self.logMsg("=== ENTER downloadUrl ===", 2) - + def downloadUrl(self, url, postBody=None, type="GET", parameters=None, + authenticate=True, headerOptions={}): timeout = self.timeout default_link = "" @@ -276,7 +271,8 @@ class DownloadUtils(): # If user is not authenticated elif not authenticate: - header = self.getHeader(authenticate=False, options=headerOptions) + header = self.getHeader(authenticate=False, + options=headerOptions) # If user enables ssl verification try: @@ -413,4 +409,4 @@ class DownloadUtils(): self.logMsg("Unknown error connecting to: %s" % url, 0) self.logMsg(e, 1) - return default_link \ No newline at end of file + return default_link