202 lines
6.7 KiB
Python
202 lines
6.7 KiB
Python
|
from xml.etree import ElementTree
|
||
|
|
||
|
import http
|
||
|
import exceptions
|
||
|
import plexobjects
|
||
|
import plexconnection
|
||
|
import util
|
||
|
|
||
|
RESOURCES = 'https://plex.tv/api/resources?includeHttps=1'
|
||
|
|
||
|
|
||
|
class PlexResource(object):
|
||
|
def __init__(self, data):
|
||
|
self.connection = None
|
||
|
self.connections = []
|
||
|
self.accessToken = None
|
||
|
self.sourceType = None
|
||
|
|
||
|
if data is None:
|
||
|
return
|
||
|
|
||
|
self.accessToken = data.attrib.get('accessToken')
|
||
|
self.httpsRequired = data.attrib.get('httpsRequired') == '1'
|
||
|
self.type = data.attrib.get('type')
|
||
|
self.clientIdentifier = data.attrib.get('clientIdentifier')
|
||
|
self.product = data.attrib.get('product')
|
||
|
self.provides = data.attrib.get('provides')
|
||
|
self.serverClass = data.attrib.get('serverClass')
|
||
|
self.sourceType = data.attrib.get('sourceType')
|
||
|
self.uuid = self.clientIdentifier
|
||
|
|
||
|
hasSecureConn = False
|
||
|
|
||
|
for conn in data.findall('Connection'):
|
||
|
if conn.attrib.get('protocol') == "https":
|
||
|
hasSecureConn = True
|
||
|
break
|
||
|
|
||
|
for conn in data.findall('Connection'):
|
||
|
connection = plexconnection.PlexConnection(
|
||
|
plexconnection.PlexConnection.SOURCE_MYPLEX,
|
||
|
conn.attrib.get('uri'),
|
||
|
conn.attrib.get('local') == '1',
|
||
|
self.accessToken,
|
||
|
hasSecureConn and conn.attrib.get('protocol') != "https"
|
||
|
)
|
||
|
|
||
|
# Keep the secure connection on top
|
||
|
if connection.isSecure:
|
||
|
self.connections.insert(0, connection)
|
||
|
else:
|
||
|
self.connections.append(connection)
|
||
|
|
||
|
# If the connection is one of our plex.direct secure connections, add
|
||
|
# the nonsecure variant as well, unless https is required.
|
||
|
#
|
||
|
if self.httpsRequired and conn.attrib.get('protocol') == "https" and conn.attrib.get('address') not in conn.attrib.get('uri'):
|
||
|
self.connections.append(
|
||
|
plexconnection.PlexConnection(
|
||
|
plexconnection.PlexConnection.SOURCE_MYPLEX,
|
||
|
"http://{0}:{1}".format(conn.attrib.get('address'), conn.attrib.get('port')),
|
||
|
conn.attrib.get('local') == '1',
|
||
|
self.accessToken,
|
||
|
True
|
||
|
)
|
||
|
)
|
||
|
|
||
|
def __repr__(self):
|
||
|
return '<{0}:{1}>'.format(self.__class__.__name__, self.name.encode('utf8'))
|
||
|
|
||
|
|
||
|
class ResourceConnection(plexobjects.PlexObject):
|
||
|
# Constants
|
||
|
STATE_UNKNOWN = "unknown"
|
||
|
STATE_UNREACHABLE = "unreachable"
|
||
|
STATE_REACHABLE = "reachable"
|
||
|
STATE_UNAUTHORIZED = "unauthorized"
|
||
|
STATE_INSECURE = "insecure_untested"
|
||
|
|
||
|
SOURCE_MANUAL = 1
|
||
|
SOURCE_DISCOVERED = 2
|
||
|
SOURCE_MYPLEX = 4
|
||
|
|
||
|
SCORE_REACHABLE = 4
|
||
|
SCORE_LOCAL = 2
|
||
|
SCORE_SECURE = 1
|
||
|
|
||
|
def init(self, data):
|
||
|
self.secure = True
|
||
|
self.reachable = False
|
||
|
self.data = None
|
||
|
|
||
|
def __repr__(self):
|
||
|
return '<{0}:{1}>'.format(self.__class__.__name__, self.uri.encode('utf8'))
|
||
|
|
||
|
@property
|
||
|
def http_uri(self):
|
||
|
return 'http://{0}:{1}'.format(self.address, self.port)
|
||
|
|
||
|
@property
|
||
|
def URL(self):
|
||
|
if self.secure:
|
||
|
return self.uri
|
||
|
else:
|
||
|
return self.http_url
|
||
|
|
||
|
def connect(self):
|
||
|
util.LOG('Connecting: {0}'.format(util.cleanToken(self.URL)))
|
||
|
try:
|
||
|
self.data = self.query('/')
|
||
|
self.reachable = True
|
||
|
return True
|
||
|
except Exception as err:
|
||
|
util.ERROR(util.cleanToken(self.URL), err)
|
||
|
|
||
|
util.LOG('Connecting: Secure failed, trying insecure...')
|
||
|
self.secure = False
|
||
|
|
||
|
try:
|
||
|
self.data = self.query('/')
|
||
|
self.reachable = True
|
||
|
return True
|
||
|
except Exception as err:
|
||
|
util.ERROR(util.cleanToken(self.URL), err)
|
||
|
|
||
|
return False
|
||
|
|
||
|
def headers(self, token=None):
|
||
|
headers = util.BASE_HEADERS.copy()
|
||
|
if token:
|
||
|
headers['X-Plex-Token'] = token
|
||
|
return headers
|
||
|
|
||
|
def query(self, path, method=None, token=None, **kwargs):
|
||
|
method = method or http.requests.get
|
||
|
url = self.getURL(path)
|
||
|
util.LOG('{0} {1}'.format(method.__name__.upper(), url))
|
||
|
response = method(url, headers=self.headers(token), timeout=util.TIMEOUT, **kwargs)
|
||
|
if response.status_code not in (200, 201):
|
||
|
codename = http.status_codes.get(response.status_code)[0]
|
||
|
raise exceptions.BadRequest('({0}) {1}'.format(response.status_code, codename))
|
||
|
data = response.text.encode('utf8')
|
||
|
|
||
|
return ElementTree.fromstring(data) if data else None
|
||
|
|
||
|
def getURL(self, path, token=None):
|
||
|
if token:
|
||
|
delim = '&' if '?' in path else '?'
|
||
|
return '{base}{path}{delim}X-Plex-Token={token}'.format(base=self.URL, path=path, delim=delim, token=util.hideToken(token))
|
||
|
return '{0}{1}'.format(self.URL, path)
|
||
|
|
||
|
|
||
|
class PlexResourceList(plexobjects.PlexItemList):
|
||
|
def __init__(self, data, initpath=None, server=None):
|
||
|
self._data = data
|
||
|
self.initpath = initpath
|
||
|
self._server = server
|
||
|
self._items = None
|
||
|
|
||
|
@property
|
||
|
def items(self):
|
||
|
if self._items is None:
|
||
|
if self._data is not None:
|
||
|
self._items = [PlexResource(elem, initpath=self.initpath, server=self._server) for elem in self._data]
|
||
|
else:
|
||
|
self._items = []
|
||
|
|
||
|
return self._items
|
||
|
|
||
|
|
||
|
def fetchResources(token):
|
||
|
headers = util.BASE_HEADERS.copy()
|
||
|
headers['X-Plex-Token'] = token
|
||
|
util.LOG('GET {0}?X-Plex-Token={1}'.format(RESOURCES, util.hideToken(token)))
|
||
|
response = http.GET(RESOURCES)
|
||
|
data = ElementTree.fromstring(response.text.encode('utf8'))
|
||
|
import plexserver
|
||
|
return [plexserver.PlexServer(elem) for elem in data]
|
||
|
|
||
|
|
||
|
def findResource(resources, search, port=32400):
|
||
|
""" Searches server.name """
|
||
|
search = search.lower()
|
||
|
util.LOG('Looking for server: {0}'.format(search))
|
||
|
for server in resources:
|
||
|
if search == server.name.lower():
|
||
|
util.LOG('Server found: {0}'.format(server))
|
||
|
return server
|
||
|
util.LOG('Unable to find server: {0}'.format(search))
|
||
|
raise exceptions.NotFound('Unable to find server: {0}'.format(search))
|
||
|
|
||
|
|
||
|
def findResourceByID(resources, ID):
|
||
|
""" Searches server.clientIdentifier """
|
||
|
util.LOG('Looking for server by ID: {0}'.format(ID))
|
||
|
for server in resources:
|
||
|
if ID == server.clientIdentifier:
|
||
|
util.LOG('Server found by ID: {0}'.format(server))
|
||
|
return server
|
||
|
util.LOG('Unable to find server by ID: {0}'.format(ID))
|
||
|
raise exceptions.NotFound('Unable to find server by ID: {0}'.format(ID))
|