mirror of
https://github.com/pkkid/python-plexapi
synced 2024-11-22 19:53:17 +00:00
Add support for SSL
This commit is contained in:
parent
527b0ee323
commit
433e0a18b4
11 changed files with 131 additions and 175 deletions
|
@ -69,7 +69,7 @@ def example_008_get_stream_url(plex):
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
parser = argparse.ArgumentParser(description='Run PlexAPI examples.')
|
parser = argparse.ArgumentParser(description='Run PlexAPI examples.')
|
||||||
parser.add_argument('-s', '--server', help='Name of the Plex server (requires user/pass).')
|
parser.add_argument('-r', '--resource', help='Name of the Plex resource (requires user/pass).')
|
||||||
parser.add_argument('-u', '--username', help='Username for the Plex server.')
|
parser.add_argument('-u', '--username', help='Username for the Plex server.')
|
||||||
parser.add_argument('-p', '--password', help='Password for the Plex server.')
|
parser.add_argument('-p', '--password', help='Password for the Plex server.')
|
||||||
parser.add_argument('-n', '--name', help='Only run tests containing this string. Leave blank to run all examples.')
|
parser.add_argument('-n', '--name', help='Only run tests containing this string. Leave blank to run all examples.')
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
"""
|
"""
|
||||||
Test Library Functions
|
Test Library Functions
|
||||||
|
|
||||||
As of Plex version 0.9.11 I noticed that you must be logged in
|
As of Plex version 0.9.11 I noticed that you must be logged in
|
||||||
to browse even the plex server locatewd at localhost. You can
|
to browse even the plex server locatewd at localhost. You can
|
||||||
run this test suite with the following command:
|
run this test suite with the following command:
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ def test_003_search_show(plex, user=None):
|
||||||
assert result_server, 'Show not found.'
|
assert result_server, 'Show not found.'
|
||||||
assert result_server == result_library == result_shows, 'Show searches not consistent.'
|
assert result_server == result_library == result_shows, 'Show searches not consistent.'
|
||||||
assert not result_movies, 'Movie search returned show title.'
|
assert not result_movies, 'Movie search returned show title.'
|
||||||
|
|
||||||
|
|
||||||
def test_004_search_movie(plex, user=None):
|
def test_004_search_movie(plex, user=None):
|
||||||
result_server = plex.search(MOVIE_TITLE)
|
result_server = plex.search(MOVIE_TITLE)
|
||||||
|
@ -124,7 +124,7 @@ def test_008_mark_movie_watched(plex, user=None):
|
||||||
log(2, 'View count: %s' % movie.viewCount)
|
log(2, 'View count: %s' % movie.viewCount)
|
||||||
movie.markWatched()
|
movie.markWatched()
|
||||||
log(2, 'View count: %s' % movie.viewCount)
|
log(2, 'View count: %s' % movie.viewCount)
|
||||||
assert movie.viewCount == 1, 'View count 0 after watched.'
|
assert movie.viewCount == 1, 'View count 0 after watched.'
|
||||||
movie.markUnwatched()
|
movie.markUnwatched()
|
||||||
log(2, 'View count: %s' % movie.viewCount)
|
log(2, 'View count: %s' % movie.viewCount)
|
||||||
assert movie.viewCount == 0, 'View count 1 after unwatched.'
|
assert movie.viewCount == 0, 'View count 1 after unwatched.'
|
||||||
|
@ -147,13 +147,19 @@ def test_011_play_media(plex, user=None):
|
||||||
# Make sure the client is turned on!
|
# Make sure the client is turned on!
|
||||||
episode = plex.library.get(SHOW_TITLE).get(SHOW_EPISODE)
|
episode = plex.library.get(SHOW_TITLE).get(SHOW_EPISODE)
|
||||||
client = plex.client(PLEX_CLIENT)
|
client = plex.client(PLEX_CLIENT)
|
||||||
client.playMedia(episode); time.sleep(10)
|
client.playMedia(episode)
|
||||||
client.pause(); time.sleep(3)
|
time.sleep(10)
|
||||||
client.stepForward(); time.sleep(3)
|
client.pause()
|
||||||
client.play(); time.sleep(3)
|
time.sleep(3)
|
||||||
client.stop(); time.sleep(3)
|
client.stepForward()
|
||||||
|
time.sleep(3)
|
||||||
|
client.play()
|
||||||
|
time.sleep(3)
|
||||||
|
client.stop()
|
||||||
|
time.sleep(3)
|
||||||
movie = plex.library.get(MOVIE_TITLE)
|
movie = plex.library.get(MOVIE_TITLE)
|
||||||
movie.play(client); time.sleep(10)
|
movie.play(client)
|
||||||
|
time.sleep(10)
|
||||||
client.stop()
|
client.stop()
|
||||||
|
|
||||||
|
|
||||||
|
@ -226,7 +232,7 @@ def test_015_list_devices(plex, user=None):
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
parser = argparse.ArgumentParser(description='Run PlexAPI tests.')
|
parser = argparse.ArgumentParser(description='Run PlexAPI tests.')
|
||||||
parser.add_argument('-s', '--server', help='Name of the Plex server (requires user/pass).')
|
parser.add_argument('-r', '--resource', help='Name of the Plex resource (requires user/pass).')
|
||||||
parser.add_argument('-u', '--username', help='Username for the Plex server.')
|
parser.add_argument('-u', '--username', help='Username for the Plex server.')
|
||||||
parser.add_argument('-p', '--password', help='Password for the Plex server.')
|
parser.add_argument('-p', '--password', help='Password for the Plex server.')
|
||||||
parser.add_argument('-n', '--name', help='Only run tests containing this string. Leave blank to run all tests.')
|
parser.add_argument('-n', '--name', help='Only run tests containing this string. Leave blank to run all tests.')
|
||||||
|
|
|
@ -13,9 +13,9 @@ def log(indent, message):
|
||||||
|
|
||||||
|
|
||||||
def fetch_server(args):
|
def fetch_server(args):
|
||||||
if args.server:
|
if args.resource:
|
||||||
user = MyPlexUser.signin(args.username, args.password)
|
user = MyPlexUser.signin(args.username, args.password)
|
||||||
return user.getServer(args.server).connect(), user
|
return user.getResource(args.resource).connect(), user
|
||||||
return server.PlexServer(), None
|
return server.PlexServer(), None
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ from plexapi.config import PlexConfig
|
||||||
from uuid import getnode
|
from uuid import getnode
|
||||||
|
|
||||||
PROJECT = 'PlexAPI'
|
PROJECT = 'PlexAPI'
|
||||||
VERSION = '0.9.5'
|
VERSION = '0.9.6'
|
||||||
|
|
||||||
# Load User Defined Config
|
# Load User Defined Config
|
||||||
CONFIG_PATH = os.path.expanduser('~/.config/plexapi/config.ini')
|
CONFIG_PATH = os.path.expanduser('~/.config/plexapi/config.ini')
|
||||||
|
|
|
@ -32,13 +32,6 @@ class Client(object):
|
||||||
self.state = data.attrib.get('state')
|
self.state = data.attrib.get('state')
|
||||||
self._sendCommandsTo = SERVER
|
self._sendCommandsTo = SERVER
|
||||||
|
|
||||||
|
|
||||||
# machineIdentifier = data.attrib.get('audioCodec') "f9b12e31-9604-485e-a2a1-8cfe7dd8de0d"
|
|
||||||
# platform="Chrome"
|
|
||||||
# product="Plex Web"
|
|
||||||
# state="paused"
|
|
||||||
# title="Plex Web (Chrome)
|
|
||||||
|
|
||||||
def sendCommandsTo(self, value):
|
def sendCommandsTo(self, value):
|
||||||
self._sendCommandsTo = value
|
self._sendCommandsTo = value
|
||||||
|
|
||||||
|
@ -66,29 +59,29 @@ class Client(object):
|
||||||
return 'http://%s:%s/player/%s' % (self.address, self.port, path.lstrip('/'))
|
return 'http://%s:%s/player/%s' % (self.address, self.port, path.lstrip('/'))
|
||||||
|
|
||||||
# Navigation Commands
|
# Navigation Commands
|
||||||
def moveUp(self): self.sendCommand('navigation/moveUp')
|
def moveUp(self): self.sendCommand('navigation/moveUp') # noqa
|
||||||
def moveDown(self): self.sendCommand('navigation/moveDown')
|
def moveDown(self): self.sendCommand('navigation/moveDown') # noqa
|
||||||
def moveLeft(self): self.sendCommand('navigation/moveLeft')
|
def moveLeft(self): self.sendCommand('navigation/moveLeft') # noqa
|
||||||
def moveRight(self): self.sendCommand('navigation/moveRight')
|
def moveRight(self): self.sendCommand('navigation/moveRight') # noqa
|
||||||
def pageUp(self): self.sendCommand('navigation/pageUp')
|
def pageUp(self): self.sendCommand('navigation/pageUp') # noqa
|
||||||
def pageDown(self): self.sendCommand('navigation/pageDown')
|
def pageDown(self): self.sendCommand('navigation/pageDown') # noqa
|
||||||
def nextLetter(self): self.sendCommand('navigation/nextLetter')
|
def nextLetter(self): self.sendCommand('navigation/nextLetter') # noqa
|
||||||
def previousLetter(self): self.sendCommand('navigation/previousLetter')
|
def previousLetter(self): self.sendCommand('navigation/previousLetter') # noqa
|
||||||
def select(self): self.sendCommand('navigation/select')
|
def select(self): self.sendCommand('navigation/select') # noqa
|
||||||
def back(self): self.sendCommand('navigation/back')
|
def back(self): self.sendCommand('navigation/back') # noqa
|
||||||
def contextMenu(self): self.sendCommand('navigation/contextMenu')
|
def contextMenu(self): self.sendCommand('navigation/contextMenu') # noqa
|
||||||
def toggleOSD(self): self.sendCommand('navigation/toggleOSD')
|
def toggleOSD(self): self.sendCommand('navigation/toggleOSD') # noqa
|
||||||
|
|
||||||
# Playback Commands
|
# Playback Commands
|
||||||
def play(self): self.sendCommand('playback/play')
|
def play(self): self.sendCommand('playback/play') # noqa
|
||||||
def pause(self): self.sendCommand('playback/pause')
|
def pause(self): self.sendCommand('playback/pause') # noqa
|
||||||
def stop(self): self.sendCommand('playback/stop')
|
def stop(self): self.sendCommand('playback/stop') # noqa
|
||||||
def stepForward(self): self.sendCommand('playback/stepForward')
|
def stepForward(self): self.sendCommand('playback/stepForward') # noqa
|
||||||
def bigStepForward(self): self.sendCommand('playback/bigStepForward')
|
def bigStepForward(self): self.sendCommand('playback/bigStepForward') # noqa
|
||||||
def stepBack(self): self.sendCommand('playback/stepBack')
|
def stepBack(self): self.sendCommand('playback/stepBack') # noqa
|
||||||
def bigStepBack(self): self.sendCommand('playback/bigStepBack')
|
def bigStepBack(self): self.sendCommand('playback/bigStepBack') # noqa
|
||||||
def skipNext(self): self.sendCommand('playback/skipNext')
|
def skipNext(self): self.sendCommand('playback/skipNext') # noqa
|
||||||
def skipPrevious(self): self.sendCommand('playback/skipPrevious')
|
def skipPrevious(self): self.sendCommand('playback/skipPrevious') # noqa
|
||||||
|
|
||||||
def playMedia(self, video, viewOffset=0):
|
def playMedia(self, video, viewOffset=0):
|
||||||
playqueue = self.server.createPlayQueue(video)
|
playqueue = self.server.createPlayQueue(video)
|
||||||
|
@ -100,36 +93,14 @@ class Client(object):
|
||||||
})
|
})
|
||||||
|
|
||||||
def timeline(self):
|
def timeline(self):
|
||||||
"""
|
|
||||||
Returns an XML ElementTree object corresponding to the timeline for
|
|
||||||
this client. Holds the information about what media is playing on this
|
|
||||||
client.
|
|
||||||
"""
|
|
||||||
|
|
||||||
url = self.url('timeline/poll')
|
url = self.url('timeline/poll')
|
||||||
params = {
|
params = {'wait':1, 'commandID':4}
|
||||||
'wait': 1,
|
|
||||||
'commandID': 4,
|
|
||||||
}
|
|
||||||
xml_text = requests.get(url, params=params, headers=BASE_HEADERS).text
|
xml_text = requests.get(url, params=params, headers=BASE_HEADERS).text
|
||||||
return ElementTree.fromstring(xml_text)
|
return ElementTree.fromstring(xml_text)
|
||||||
|
|
||||||
def isPlayingMedia(self):
|
def isPlayingMedia(self):
|
||||||
"""
|
|
||||||
Returns True if any of the media types for this client have the status
|
|
||||||
of "playing", False otherwise. Also returns True if media is paused.
|
|
||||||
"""
|
|
||||||
|
|
||||||
timeline = self.timeline()
|
timeline = self.timeline()
|
||||||
for media_type in timeline:
|
for media_type in timeline:
|
||||||
if media_type.get('state') == 'playing':
|
if media_type.get('state') == 'playing':
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# def rewind(self): self.sendCommand('playback/rewind')
|
|
||||||
# def fastForward(self): self.sendCommand('playback/fastForward')
|
|
||||||
# def playFile(self): pass
|
|
||||||
# def screenshot(self): pass
|
|
||||||
# def sendString(self): pass
|
|
||||||
# def sendKey(self): pass
|
|
||||||
# def sendVirtualKey(self): pass
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ from plexapi.exceptions import NotFound
|
||||||
|
|
||||||
|
|
||||||
class Library(object):
|
class Library(object):
|
||||||
|
|
||||||
def __init__(self, server, data):
|
def __init__(self, server, data):
|
||||||
self.server = server
|
self.server = server
|
||||||
self.identifier = data.attrib.get('identifier')
|
self.identifier = data.attrib.get('identifier')
|
||||||
|
@ -122,7 +122,7 @@ class LibrarySection(object):
|
||||||
|
|
||||||
def contentRating(self, input=None):
|
def contentRating(self, input=None):
|
||||||
return self._secondary_list('contentRating', input)
|
return self._secondary_list('contentRating', input)
|
||||||
|
|
||||||
def firstCharacter(self, input=None):
|
def firstCharacter(self, input=None):
|
||||||
return self._secondary_list('firstCharacter', input)
|
return self._secondary_list('firstCharacter', input)
|
||||||
|
|
||||||
|
@ -169,7 +169,7 @@ class MovieSection(LibrarySection):
|
||||||
|
|
||||||
def country(self, input=None):
|
def country(self, input=None):
|
||||||
return self._secondary_list('country', input)
|
return self._secondary_list('country', input)
|
||||||
|
|
||||||
def decade(self, input=None):
|
def decade(self, input=None):
|
||||||
return self._secondary_list('decade', input)
|
return self._secondary_list('decade', input)
|
||||||
|
|
||||||
|
@ -188,7 +188,7 @@ class MovieSection(LibrarySection):
|
||||||
|
|
||||||
class ShowSection(LibrarySection):
|
class ShowSection(LibrarySection):
|
||||||
TYPE = 'show'
|
TYPE = 'show'
|
||||||
|
|
||||||
def recentlyViewedShows(self):
|
def recentlyViewedShows(self):
|
||||||
return self._primary_list('recentlyViewedShows')
|
return self._primary_list('recentlyViewedShows')
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ from plexapi.utils import cast
|
||||||
|
|
||||||
class Media(object):
|
class Media(object):
|
||||||
TYPE = 'Media'
|
TYPE = 'Media'
|
||||||
|
|
||||||
def __init__(self, server, data, initpath, video):
|
def __init__(self, server, data, initpath, video):
|
||||||
self.server = server
|
self.server = server
|
||||||
self.initpath = initpath
|
self.initpath = initpath
|
||||||
|
@ -159,12 +159,12 @@ class VideoTag(object):
|
||||||
return '<%s:%s:%s>' % (self.__class__.__name__, self.id, tag)
|
return '<%s:%s:%s>' % (self.__class__.__name__, self.id, tag)
|
||||||
|
|
||||||
def related(self, vtype=None):
|
def related(self, vtype=None):
|
||||||
return self.server.library.search(None, **{self.FILTER:self})
|
return self.server.search(None, **{self.FILTER:self})
|
||||||
|
|
||||||
|
|
||||||
class Country(VideoTag): TYPE='Country'; FILTER='country'
|
class Country(VideoTag): TYPE='Country'; FILTER='country' # noqa
|
||||||
class Director(VideoTag): TYPE = 'Director'; FILTER='director'
|
class Director(VideoTag): TYPE = 'Director'; FILTER='director' # noqa
|
||||||
class Genre(VideoTag): TYPE='Genre'; FILTER='genre'
|
class Genre(VideoTag): TYPE='Genre'; FILTER='genre' # noqa
|
||||||
class Producer(VideoTag): TYPE = 'Producer'; FILTER='producer'
|
class Producer(VideoTag): TYPE = 'Producer'; FILTER='producer' # noqa
|
||||||
class Actor(VideoTag): TYPE = 'Role'; FILTER='actor'
|
class Actor(VideoTag): TYPE = 'Role'; FILTER='actor' # noqa
|
||||||
class Writer(VideoTag): TYPE = 'Writer'; FILTER='writer'
|
class Writer(VideoTag): TYPE = 'Writer'; FILTER='writer' # noqa
|
||||||
|
|
|
@ -4,7 +4,7 @@ PlexAPI MyPlex
|
||||||
import plexapi, requests
|
import plexapi, requests
|
||||||
from plexapi import TIMEOUT, log
|
from plexapi import TIMEOUT, log
|
||||||
from plexapi.exceptions import BadRequest, NotFound, Unauthorized
|
from plexapi.exceptions import BadRequest, NotFound, Unauthorized
|
||||||
from plexapi.utils import addrToIP, cast, toDatetime
|
from plexapi.utils import cast, toDatetime
|
||||||
from requests.status_codes import _codes as codes
|
from requests.status_codes import _codes as codes
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from xml.etree import ElementTree
|
from xml.etree import ElementTree
|
||||||
|
@ -28,14 +28,14 @@ class MyPlexUser:
|
||||||
self.queueEmail = data.attrib.get('queueEmail')
|
self.queueEmail = data.attrib.get('queueEmail')
|
||||||
self.queueUid = data.attrib.get('queueUid')
|
self.queueUid = data.attrib.get('queueUid')
|
||||||
|
|
||||||
def servers(self):
|
def resources(self):
|
||||||
return MyPlexServer.fetch_servers(self.authenticationToken)
|
return MyPlexResource.fetch_resources(self.authenticationToken)
|
||||||
|
|
||||||
def getServer(self, search, port=32400):
|
def getResource(self, search, port=32400):
|
||||||
""" Searches server.name, server.sourceTitle and server.host:server.port
|
""" Searches server.name, server.sourceTitle and server.host:server.port
|
||||||
from the list of available for this PlexUser.
|
from the list of available for this PlexUser.
|
||||||
"""
|
"""
|
||||||
return _findServer(self.servers(), search, port)
|
return _findResource(self.resources(), search, port)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def signin(cls, username, password):
|
def signin(cls, username, password):
|
||||||
|
@ -71,87 +71,98 @@ class MyPlexAccount:
|
||||||
self.subscriptionActive = data.attrib.get('subscriptionActive')
|
self.subscriptionActive = data.attrib.get('subscriptionActive')
|
||||||
self.subscriptionState = data.attrib.get('subscriptionState')
|
self.subscriptionState = data.attrib.get('subscriptionState')
|
||||||
|
|
||||||
def servers(self):
|
def resources(self):
|
||||||
return MyPlexServer.fetch_servers(self.authToken)
|
return MyPlexResource.fetch_resources(self.authToken)
|
||||||
|
|
||||||
def getServer(self, search, port=32400):
|
def getResource(self, search, port=32400):
|
||||||
""" Searches server.name, server.sourceTitle and server.host:server.port
|
""" Searches server.name, server.sourceTitle and server.host:server.port
|
||||||
from the list of available for this PlexAccount.
|
from the list of available for this PlexAccount.
|
||||||
"""
|
"""
|
||||||
return _findServer(self.servers(), search, port)
|
return _findResource(self.resources(), search, port)
|
||||||
|
|
||||||
|
|
||||||
class MyPlexServer:
|
class MyPlexResource:
|
||||||
SERVERS = 'https://plex.tv/pms/servers.xml?includeLite=1'
|
RESOURCES = 'https://plex.tv/api/resources?includeHttps=1'
|
||||||
|
|
||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
self.accessToken = data.attrib.get('accessToken')
|
|
||||||
self.name = data.attrib.get('name')
|
self.name = data.attrib.get('name')
|
||||||
self.address = data.attrib.get('address')
|
self.accessToken = data.attrib.get('accessToken')
|
||||||
self.port = cast(int, data.attrib.get('port'))
|
self.product = data.attrib.get('product')
|
||||||
self.version = data.attrib.get('version')
|
self.productVersion = data.attrib.get('productVersion')
|
||||||
self.scheme = data.attrib.get('scheme')
|
self.platform = data.attrib.get('platform')
|
||||||
self.host = data.attrib.get('host')
|
self.platformVersion = data.attrib.get('platformVersion')
|
||||||
self.localAddresses = data.attrib.get('localAddresses', '').split(',')
|
self.device = data.attrib.get('device')
|
||||||
self.machineIdentifier = data.attrib.get('machineIdentifier')
|
self.clientIdentifier = data.attrib.get('clientIdentifier')
|
||||||
self.createdAt = toDatetime(data.attrib.get('createdAt'))
|
self.createdAt = toDatetime(data.attrib.get('createdAt'))
|
||||||
self.updatedAt = toDatetime(data.attrib.get('updatedAt'))
|
self.lastSeenAt = toDatetime(data.attrib.get('lastSeenAt'))
|
||||||
|
self.provides = data.attrib.get('provides')
|
||||||
self.owned = cast(bool, data.attrib.get('owned'))
|
self.owned = cast(bool, data.attrib.get('owned'))
|
||||||
|
self.home = cast(bool, data.attrib.get('home'))
|
||||||
self.synced = cast(bool, data.attrib.get('synced'))
|
self.synced = cast(bool, data.attrib.get('synced'))
|
||||||
self.sourceTitle = data.attrib.get('sourceTitle', '')
|
self.presence = cast(bool, data.attrib.get('presence'))
|
||||||
self.ownerId = cast(int, data.attrib.get('ownerId'))
|
self.connections = [ResourceConnection(elem) for elem in data if elem.tag == 'Connection']
|
||||||
self.home = data.attrib.get('home')
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<%s:%s>' % (self.__class__.__name__, self.name.encode('utf8'))
|
return '<%s:%s>' % (self.__class__.__name__, self.name.encode('utf8'))
|
||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
# Try connecting to all known addresses in parellel to save time, but
|
# Only check non-local connections unless we own the resource
|
||||||
|
connections = sorted(self.connections, key=lambda c:c.local, reverse=True)
|
||||||
|
if not self.owned:
|
||||||
|
connections = [c for c in connections if c.local is False]
|
||||||
|
# Try connecting to all known resource connections in parellel, but
|
||||||
# only return the first server (in order) that provides a response.
|
# only return the first server (in order) that provides a response.
|
||||||
addresses = [self.address]
|
threads = [None] * len(connections)
|
||||||
if self.owned:
|
results = [None] * len(connections)
|
||||||
addresses = self.localAddresses + [self.address]
|
for i in range(len(connections)):
|
||||||
threads = [None] * len(addresses)
|
args = (connections[i].uri, results, i)
|
||||||
results = [None] * len(addresses)
|
|
||||||
for i in range(len(addresses)):
|
|
||||||
args = (addresses[i], results, i)
|
|
||||||
threads[i] = Thread(target=self._connect, args=args)
|
threads[i] = Thread(target=self._connect, args=args)
|
||||||
threads[i].start()
|
threads[i].start()
|
||||||
for thread in threads:
|
for thread in threads:
|
||||||
thread.join()
|
thread.join()
|
||||||
results = list(filter(None, results))
|
results = [r for r in results if r]
|
||||||
if results:
|
if not results:
|
||||||
log.info('Connecting to server: %s', results[0])
|
raise NotFound('Unable to connect to resource: %s' % self.name)
|
||||||
return results[0]
|
log.info('Connecting to server: %s', results[0])
|
||||||
raise NotFound('Unable to connect to server: %s' % self.name)
|
return results[0]
|
||||||
|
|
||||||
def _connect(self, address, results, i):
|
def _connect(self, uri, results, i):
|
||||||
from plexapi.server import PlexServer
|
|
||||||
try:
|
try:
|
||||||
results[i] = PlexServer(address, self.port, self.accessToken)
|
from plexapi.server import PlexServer
|
||||||
|
results[i] = PlexServer(uri, self.accessToken)
|
||||||
except NotFound:
|
except NotFound:
|
||||||
results[i] = None
|
results[i] = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def fetch_servers(cls, token):
|
def fetch_resources(cls, token):
|
||||||
headers = plexapi.BASE_HEADERS
|
headers = plexapi.BASE_HEADERS
|
||||||
headers['X-Plex-Token'] = token
|
headers['X-Plex-Token'] = token
|
||||||
log.info('GET %s?X-Plex-Token=%s', cls.SERVERS, token)
|
log.info('GET %s?X-Plex-Token=%s', cls.RESOURCES, token)
|
||||||
response = requests.get(cls.SERVERS, headers=headers, timeout=TIMEOUT)
|
response = requests.get(cls.RESOURCES, headers=headers, timeout=TIMEOUT)
|
||||||
data = ElementTree.fromstring(response.text.encode('utf8'))
|
data = ElementTree.fromstring(response.text.encode('utf8'))
|
||||||
return [MyPlexServer(elem) for elem in data]
|
return [MyPlexResource(elem) for elem in data]
|
||||||
|
|
||||||
|
|
||||||
def _findServer(servers, search, port=32400):
|
class ResourceConnection:
|
||||||
""" Searches server.name, server.sourceTitle and server.host:server.port """
|
|
||||||
|
def __init__(self, data):
|
||||||
|
self.protocol = data.attrib.get('protocol')
|
||||||
|
self.address = data.attrib.get('address')
|
||||||
|
self.port = cast(int, data.attrib.get('port'))
|
||||||
|
self.uri = data.attrib.get('uri')
|
||||||
|
self.local = cast(bool, data.attrib.get('local'))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<%s:%s>' % (self.__class__.__name__, self.uri.encode('utf8'))
|
||||||
|
|
||||||
|
|
||||||
|
def _findResource(resources, search, port=32400):
|
||||||
|
""" Searches server.name """
|
||||||
search = search.lower()
|
search = search.lower()
|
||||||
ipaddr = addrToIP(search)
|
log.info('Looking for server: %s', search)
|
||||||
log.info('Looking for server: %s (host: %s:%s)', search, ipaddr, port)
|
for server in resources:
|
||||||
for server in servers:
|
if search == server.name.lower():
|
||||||
serverName = server.name.lower() if server.name else 'NA'
|
|
||||||
sourceTitle = server.sourceTitle.lower() if server.sourceTitle else 'NA'
|
|
||||||
if (search in [serverName, sourceTitle]) or (server.host == ipaddr and server.port == port):
|
|
||||||
log.info('Server found: %s', server)
|
log.info('Server found: %s', server)
|
||||||
return server
|
return server
|
||||||
log.info('Unable to find server: %s (host: %s:%s)', search, ipaddr, port)
|
log.info('Unable to find server: %s', search)
|
||||||
raise NotFound('Unable to find server: %s (host: %s:%s)' % (search, ipaddr, port))
|
raise NotFound('Unable to find server: %s' % search)
|
||||||
|
|
|
@ -13,13 +13,13 @@ from plexapi.playqueue import PlayQueue
|
||||||
from xml.etree import ElementTree
|
from xml.etree import ElementTree
|
||||||
|
|
||||||
TOTAL_QUERIES = 0
|
TOTAL_QUERIES = 0
|
||||||
|
DEFAULT_BASEURI = 'http://localhost:32400'
|
||||||
|
|
||||||
|
|
||||||
class PlexServer(object):
|
class PlexServer(object):
|
||||||
|
|
||||||
def __init__(self, address='localhost', port=32400, token=None):
|
def __init__(self, baseuri=None, token=None):
|
||||||
self.address = self._cleanAddress(address)
|
self.baseuri = baseuri or DEFAULT_BASEURI
|
||||||
self.port = port
|
|
||||||
self.token = token
|
self.token = token
|
||||||
data = self._connect()
|
data = self._connect()
|
||||||
self.friendlyName = data.attrib.get('friendlyName')
|
self.friendlyName = data.attrib.get('friendlyName')
|
||||||
|
@ -36,20 +36,14 @@ class PlexServer(object):
|
||||||
self.version = data.attrib.get('version')
|
self.version = data.attrib.get('version')
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<%s:%s:%s>' % (self.__class__.__name__, self.address, self.port)
|
return '<%s:%s>' % (self.__class__.__name__, self.baseuri)
|
||||||
|
|
||||||
def _cleanAddress(self, address):
|
|
||||||
address = address.lower().strip('/')
|
|
||||||
if address.startswith('http://'):
|
|
||||||
address = address[8:]
|
|
||||||
return address
|
|
||||||
|
|
||||||
def _connect(self):
|
def _connect(self):
|
||||||
try:
|
try:
|
||||||
return self.query('/')
|
return self.query('/')
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
log.error('%s:%s: %s', self.address, self.port, err)
|
log.error('%s: %s', self.baseuri, err)
|
||||||
raise NotFound('No server found at: %s:%s' % (self.address, self.port))
|
raise NotFound('No server found at: %s' % self.baseuri)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def library(self):
|
def library(self):
|
||||||
|
@ -103,6 +97,7 @@ class PlexServer(object):
|
||||||
return video.list_items(self, '/status/sessions')
|
return video.list_items(self, '/status/sessions')
|
||||||
|
|
||||||
def url(self, path):
|
def url(self, path):
|
||||||
url = 'http://%s:%s/%s' % (self.address, self.port, path.lstrip('/'))
|
if self.token:
|
||||||
if self.token: url += '?X-Plex-Token=%s' % self.token
|
delim = '&' if '?' in path else '?'
|
||||||
return url
|
return '%s%s%sX-Plex-Token=%s' % (self.baseuri, path, delim, self.token)
|
||||||
|
return '%s%s' % (self.baseuri, path)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
"""
|
"""
|
||||||
PlexAPI Utils
|
PlexAPI Utils
|
||||||
"""
|
"""
|
||||||
import socket, urllib
|
import urllib
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
NA = '__NA__' # Value not available
|
NA = '__NA__' # Value not available
|
||||||
|
@ -43,21 +43,6 @@ class PlexPartialObject(object):
|
||||||
self._loadData(data[0])
|
self._loadData(data[0])
|
||||||
|
|
||||||
|
|
||||||
def addrToIP(addr):
|
|
||||||
# Check it's already a valid IP
|
|
||||||
try:
|
|
||||||
socket.inet_aton(addr)
|
|
||||||
return addr
|
|
||||||
except socket.error:
|
|
||||||
pass
|
|
||||||
# Try getting the IP
|
|
||||||
try:
|
|
||||||
addr = addr.replace('http://', '')
|
|
||||||
return str(socket.gethostbyname(addr))
|
|
||||||
except socket.error:
|
|
||||||
return addr
|
|
||||||
|
|
||||||
|
|
||||||
def cast(func, value):
|
def cast(func, value):
|
||||||
if value not in [None, NA]:
|
if value not in [None, NA]:
|
||||||
if func == bool:
|
if func == bool:
|
||||||
|
@ -80,15 +65,3 @@ def toDatetime(value, format=None):
|
||||||
if format: value = datetime.strptime(value, format)
|
if format: value = datetime.strptime(value, format)
|
||||||
else: value = datetime.fromtimestamp(int(value))
|
else: value = datetime.fromtimestamp(int(value))
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
def lazyproperty(func):
|
|
||||||
""" Decorator: Memoize method result. """
|
|
||||||
attr = '_lazy_%s' % func.__name__
|
|
||||||
|
|
||||||
@property
|
|
||||||
def wrapper(self):
|
|
||||||
if not hasattr(self, attr):
|
|
||||||
setattr(self, attr, func(self))
|
|
||||||
return getattr(self, attr)
|
|
||||||
return wrapper
|
|
||||||
|
|
|
@ -77,7 +77,7 @@ class Video(PlexPartialObject):
|
||||||
params: Dict of additional parameters to include in URL.
|
params: Dict of additional parameters to include in URL.
|
||||||
"""
|
"""
|
||||||
params = {}
|
params = {}
|
||||||
params['path'] = 'http://127.0.0.1:32400%s' % self.key
|
params['path'] = self.key
|
||||||
params['offset'] = offset
|
params['offset'] = offset
|
||||||
params['copyts'] = kwargs.get('copyts', 1)
|
params['copyts'] = kwargs.get('copyts', 1)
|
||||||
params['mediaIndex'] = kwargs.get('mediaIndex', 0)
|
params['mediaIndex'] = kwargs.get('mediaIndex', 0)
|
||||||
|
|
Loading…
Reference in a new issue