Bump plexapi from 4.13.4 to 4.15.0 (#2132)

* Bump plexapi from 4.13.4 to 4.15.0

Bumps [plexapi](https://github.com/pkkid/python-plexapi) from 4.13.4 to 4.15.0.
- [Release notes](https://github.com/pkkid/python-plexapi/releases)
- [Commits](https://github.com/pkkid/python-plexapi/compare/4.13.4...4.15.0)

---
updated-dependencies:
- dependency-name: plexapi
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update plexapi==4.15.0

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com>

[skip ci]
This commit is contained in:
dependabot[bot] 2023-08-24 12:10:56 -07:00 committed by GitHub
parent 2c42150799
commit b2c16eba07
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 988 additions and 534 deletions

View file

@ -7,8 +7,9 @@ from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
from xml.etree import ElementTree
import requests
from plexapi import (BASE_HEADERS, CONFIG, TIMEOUT, X_PLEX_CONTAINER_SIZE,
X_PLEX_ENABLE_FAST_CONNECT, X_PLEX_IDENTIFIER, log, logfilter, utils)
from plexapi import (BASE_HEADERS, CONFIG, TIMEOUT, X_PLEX_ENABLE_FAST_CONNECT, X_PLEX_IDENTIFIER,
log, logfilter, utils)
from plexapi.base import PlexObject
from plexapi.client import PlexClient
from plexapi.exceptions import BadRequest, NotFound, Unauthorized
@ -21,51 +22,76 @@ from requests.status_codes import _codes as codes
class MyPlexAccount(PlexObject):
""" MyPlex account and profile information. This object represents the data found Account on
the myplex.tv servers at the url https://plex.tv/users/account. You may create this object
the myplex.tv servers at the url https://plex.tv/api/v2/user. You may create this object
directly by passing in your username & password (or token). There is also a convenience
method provided at :class:`~plexapi.server.PlexServer.myPlexAccount()` which will create
and return this object.
Parameters:
username (str): Your MyPlex username.
password (str): Your MyPlex password.
username (str): Plex login username if not using a token.
password (str): Plex login password if not using a token.
token (str): Plex authentication token instead of username and password.
session (requests.Session, optional): Use your own session object if you want to
cache the http responses from PMS
cache the http responses from PMS.
timeout (int): timeout in seconds on initial connect to myplex (default config.TIMEOUT).
code (str): Two-factor authentication code to use when logging in.
code (str): Two-factor authentication code to use when logging in with username and password.
remember (bool): Remember the account token for 14 days (Default True).
Attributes:
SIGNIN (str): 'https://plex.tv/users/sign_in.xml'
key (str): 'https://plex.tv/users/account'
authenticationToken (str): Unknown.
certificateVersion (str): Unknown.
cloudSyncDevice (str): Unknown.
email (str): Your current Plex email address.
key (str): 'https://plex.tv/api/v2/user'
adsConsent (str): Unknown.
adsConsentReminderAt (str): Unknown.
adsConsentSetAt (str): Unknown.
anonymous (str): Unknown.
authToken (str): The account token.
backupCodesCreated (bool): If the two-factor authentication backup codes have been created.
confirmed (bool): If the account has been confirmed.
country (str): The account country.
email (str): The account email address.
emailOnlyAuth (bool): If login with email only is enabled.
experimentalFeatures (bool): If experimental features are enabled.
friendlyName (str): Your account full name.
entitlements (List<str>): List of devices your allowed to use with this account.
guest (bool): Unknown.
home (bool): Unknown.
homeSize (int): Unknown.
id (int): Your Plex account ID.
locale (str): Your Plex locale
mailing_list_status (str): Your current mailing list status.
maxHomeSize (int): Unknown.
guest (bool): If the account is a Plex Home guest user.
hasPassword (bool): If the account has a password.
home (bool): If the account is a Plex Home user.
homeAdmin (bool): If the account is the Plex Home admin.
homeSize (int): The number of accounts in the Plex Home.
id (int): The Plex account ID.
joinedAt (datetime): Date the account joined Plex.
locale (str): the account locale
mailingListActive (bool): If you are subscribed to the Plex newsletter.
mailingListStatus (str): Your current mailing list status.
maxHomeSize (int): The maximum number of accounts allowed in the Plex Home.
pin (str): The hashed Plex Home PIN.
queueEmail (str): Email address to add items to your `Watch Later` queue.
queueUid (str): Unknown.
restricted (bool): Unknown.
profileAutoSelectAudio (bool): If the account has automatically select audio and subtitle tracks enabled.
profileDefaultAudioLanguage (str): The preferred audio language for the account.
profileDefaultSubtitleLanguage (str): The preferred subtitle language for the account.
profileAutoSelectSubtitle (int): The auto-select subtitle mode
(0 = Manually selected, 1 = Shown with foreign audio, 2 = Always enabled).
profileDefaultSubtitleAccessibility (int): The subtitles for the deaf or hard-of-hearing (SDH) searches mode
(0 = Prefer non-SDH subtitles, 1 = Prefer SDH subtitles, 2 = Only show SDH subtitles,
3 = Only shown non-SDH subtitles).
profileDefaultSubtitleForced (int): The forced subtitles searches mode
(0 = Prefer non-forced subtitles, 1 = Prefer forced subtitles, 2 = Only show forced subtitles,
3 = Only show non-forced subtitles).
protected (bool): If the account has a Plex Home PIN enabled.
rememberExpiresAt (datetime): Date the token expires.
restricted (bool): If the account is a Plex Home managed user.
roles: (List<str>) Lit of account roles. Plexpass membership listed here.
scrobbleTypes (str): Description
secure (bool): Description
subscriptionActive (bool): True if your subscription is active.
subscriptionFeatures: (List<str>) List of features allowed on your subscription.
subscriptionPlan (str): Name of subscription plan.
subscriptionStatus (str): String representation of `subscriptionActive`.
thumb (str): URL of your account thumbnail.
title (str): Unknown. - Looks like an alias for `username`.
username (str): Your account username.
uuid (str): Unknown.
_token (str): Token used to access this client.
_session (obj): Requests session object used to access this client.
scrobbleTypes (List<int>): Unknown.
subscriptionActive (bool): If the account's Plex Pass subscription is active.
subscriptionDescription (str): Description of the Plex Pass subscription.
subscriptionFeatures: (List<str>) List of features allowed on your Plex Pass subscription.
subscriptionPaymentService (str): Payment service used for your Plex Pass subscription.
subscriptionPlan (str): Name of Plex Pass subscription plan.
subscriptionStatus (str): String representation of ``subscriptionActive``.
subscriptionSubscribedAt (datetime): Date the account subscribed to Plex Pass.
thumb (str): URL of the account thumbnail.
title (str): The title of the account (username or friendly name).
twoFactorEnabled (bool): If two-factor authentication is enabled.
username (str): The account username.
uuid (str): The account UUID.
"""
FRIENDINVITE = 'https://plex.tv/api/servers/{machineId}/shared_servers' # post with data
HOMEUSERS = 'https://plex.tv/api/home/users'
@ -76,7 +102,8 @@ class MyPlexAccount(PlexObject):
FRIENDUPDATE = 'https://plex.tv/api/friends/{userId}' # put with args, delete
HOMEUSER = 'https://plex.tv/api/home/users/{userId}' # delete, put
MANAGEDHOMEUSER = 'https://plex.tv/api/v2/home/users/restricted/{userId}' # put
SIGNIN = 'https://plex.tv/users/sign_in.xml' # get with auth
SIGNIN = 'https://plex.tv/api/v2/users/signin' # post with auth
SIGNOUT = 'https://plex.tv/api/v2/users/signout' # delete
WEBHOOKS = 'https://plex.tv/api/v2/user/webhooks' # get, post with data
OPTOUTS = 'https://plex.tv/api/v2/user/{userUUID}/settings/opt_outs' # get
LINK = 'https://plex.tv/api/v2/pins/link' # put
@ -85,86 +112,106 @@ class MyPlexAccount(PlexObject):
VOD = 'https://vod.provider.plex.tv' # get
MUSIC = 'https://music.provider.plex.tv' # get
METADATA = 'https://metadata.provider.plex.tv'
# Key may someday switch to the following url. For now the current value works.
# https://plex.tv/api/v2/user?X-Plex-Token={token}&X-Plex-Client-Identifier={clientId}
key = 'https://plex.tv/users/account'
key = 'https://plex.tv/api/v2/user'
def __init__(self, username=None, password=None, token=None, session=None, timeout=None, code=None):
self._token = token or CONFIG.get('auth.server_token')
def __init__(self, username=None, password=None, token=None, session=None, timeout=None, code=None, remember=True):
self._token = logfilter.add_secret(token or CONFIG.get('auth.server_token'))
self._session = session or requests.Session()
self._sonos_cache = []
self._sonos_cache_timestamp = 0
data, initpath = self._signin(username, password, code, timeout)
data, initpath = self._signin(username, password, code, remember, timeout)
super(MyPlexAccount, self).__init__(self, data, initpath)
def _signin(self, username, password, code, timeout):
def _signin(self, username, password, code, remember, timeout):
if self._token:
return self.query(self.key), self.key
username = username or CONFIG.get('auth.myplex_username')
password = password or CONFIG.get('auth.myplex_password')
payload = {
'login': username or CONFIG.get('auth.myplex_username'),
'password': password or CONFIG.get('auth.myplex_password'),
'rememberMe': remember
}
if code:
password += code
data = self.query(self.SIGNIN, method=self._session.post, auth=(username, password), timeout=timeout)
payload['verificationCode'] = code
data = self.query(self.SIGNIN, method=self._session.post, data=payload, timeout=timeout)
return data, self.SIGNIN
def signout(self):
""" Sign out of the Plex account. Invalidates the authentication token. """
return self.query(self.SIGNOUT, method=self._session.delete)
def _loadData(self, data):
""" Load attribute values from Plex XML response. """
self._data = data
self._token = logfilter.add_secret(data.attrib.get('authenticationToken'))
self._token = logfilter.add_secret(data.attrib.get('authToken'))
self._webhooks = []
self.authenticationToken = self._token
self.certificateVersion = data.attrib.get('certificateVersion')
self.cloudSyncDevice = data.attrib.get('cloudSyncDevice')
self.adsConsent = data.attrib.get('adsConsent')
self.adsConsentReminderAt = data.attrib.get('adsConsentReminderAt')
self.adsConsentSetAt = data.attrib.get('adsConsentSetAt')
self.anonymous = data.attrib.get('anonymous')
self.authToken = self._token
self.backupCodesCreated = utils.cast(bool, data.attrib.get('backupCodesCreated'))
self.confirmed = utils.cast(bool, data.attrib.get('confirmed'))
self.country = data.attrib.get('country')
self.email = data.attrib.get('email')
self.emailOnlyAuth = utils.cast(bool, data.attrib.get('emailOnlyAuth'))
self.experimentalFeatures = utils.cast(bool, data.attrib.get('experimentalFeatures'))
self.friendlyName = data.attrib.get('friendlyName')
self.guest = utils.cast(bool, data.attrib.get('guest'))
self.hasPassword = utils.cast(bool, data.attrib.get('hasPassword'))
self.home = utils.cast(bool, data.attrib.get('home'))
self.homeAdmin = utils.cast(bool, data.attrib.get('homeAdmin'))
self.homeSize = utils.cast(int, data.attrib.get('homeSize'))
self.id = utils.cast(int, data.attrib.get('id'))
self.joinedAt = utils.toDatetime(data.attrib.get('joinedAt'))
self.locale = data.attrib.get('locale')
self.mailing_list_status = data.attrib.get('mailing_list_status')
self.mailingListActive = utils.cast(bool, data.attrib.get('mailingListActive'))
self.mailingListStatus = data.attrib.get('mailingListStatus')
self.maxHomeSize = utils.cast(int, data.attrib.get('maxHomeSize'))
self.pin = data.attrib.get('pin')
self.queueEmail = data.attrib.get('queueEmail')
self.queueUid = data.attrib.get('queueUid')
self.protected = utils.cast(bool, data.attrib.get('protected'))
self.rememberExpiresAt = utils.toDatetime(data.attrib.get('rememberExpiresAt'))
self.restricted = utils.cast(bool, data.attrib.get('restricted'))
self.scrobbleTypes = data.attrib.get('scrobbleTypes')
self.secure = utils.cast(bool, data.attrib.get('secure'))
self.scrobbleTypes = [utils.cast(int, x) for x in data.attrib.get('scrobbleTypes').split(',')]
self.thumb = data.attrib.get('thumb')
self.title = data.attrib.get('title')
self.twoFactorEnabled = utils.cast(bool, data.attrib.get('twoFactorEnabled'))
self.username = data.attrib.get('username')
self.uuid = data.attrib.get('uuid')
subscription = data.find('subscription')
self.subscriptionActive = utils.cast(bool, subscription.attrib.get('active'))
self.subscriptionStatus = subscription.attrib.get('status')
self.subscriptionDescription = data.attrib.get('subscriptionDescription')
self.subscriptionFeatures = self.listAttrs(subscription, 'id', rtag='features', etag='feature')
self.subscriptionPaymentService = subscription.attrib.get('paymentService')
self.subscriptionPlan = subscription.attrib.get('plan')
self.subscriptionFeatures = self.listAttrs(subscription, 'id', etag='feature')
self.subscriptionStatus = subscription.attrib.get('status')
self.subscriptionSubscribedAt = utils.toDatetime(subscription.attrib.get('subscribedAt'), '%Y-%m-%d %H:%M:%S %Z')
self.roles = self.listAttrs(data, 'id', rtag='roles', etag='role')
profile = data.find('profile')
self.profileAutoSelectAudio = utils.cast(bool, profile.attrib.get('autoSelectAudio'))
self.profileDefaultAudioLanguage = profile.attrib.get('defaultAudioLanguage')
self.profileDefaultSubtitleLanguage = profile.attrib.get('defaultSubtitleLanguage')
self.profileAutoSelectSubtitle = utils.cast(int, profile.attrib.get('autoSelectSubtitle'))
self.profileDefaultSubtitleAccessibility = utils.cast(int, profile.attrib.get('defaultSubtitleAccessibility'))
self.profileDefaultSubtitleForces = utils.cast(int, profile.attrib.get('defaultSubtitleForces'))
self.entitlements = self.listAttrs(data, 'id', rtag='entitlements', etag='entitlement')
self.roles = self.listAttrs(data, 'id', rtag='roles', etag='role')
# TODO: Fetch missing MyPlexAccount attributes
self.profile_settings = None
# TODO: Fetch missing MyPlexAccount services
self.services = None
self.joined_at = None
def device(self, name=None, clientId=None):
""" Returns the :class:`~plexapi.myplex.MyPlexDevice` that matches the name specified.
@property
def authenticationToken(self):
""" Returns the authentication token for the account. Alias for ``authToken``. """
return self.authToken
Parameters:
name (str): Name to match against.
clientId (str): clientIdentifier to match against.
"""
for device in self.devices():
if (name and device.name.lower() == name.lower() or device.clientIdentifier == clientId):
return device
raise NotFound(f'Unable to find device {name}')
def devices(self):
""" Returns a list of all :class:`~plexapi.myplex.MyPlexDevice` objects connected to the server. """
data = self.query(MyPlexDevice.key)
return [MyPlexDevice(self, elem) for elem in data]
def _reload(self, key=None, **kwargs):
""" Perform the actual reload. """
data = self.query(self.key)
self._loadData(data)
return self
def _headers(self, **kwargs):
""" Returns dict containing base headers for all requests to the server. """
@ -188,6 +235,8 @@ class MyPlexAccount(PlexObject):
raise Unauthorized(message)
elif response.status_code == 404:
raise NotFound(message)
elif response.status_code == 422 and "Invalid token" in response.text:
raise Unauthorized(message)
else:
raise BadRequest(message)
if headers.get('Accept') == 'application/json':
@ -195,6 +244,23 @@ class MyPlexAccount(PlexObject):
data = response.text.encode('utf8')
return ElementTree.fromstring(data) if data.strip() else None
def device(self, name=None, clientId=None):
""" Returns the :class:`~plexapi.myplex.MyPlexDevice` that matches the name specified.
Parameters:
name (str): Name to match against.
clientId (str): clientIdentifier to match against.
"""
for device in self.devices():
if (name and device.name.lower() == name.lower() or device.clientIdentifier == clientId):
return device
raise NotFound(f'Unable to find device {name}')
def devices(self):
""" Returns a list of all :class:`~plexapi.myplex.MyPlexDevice` objects connected to the server. """
data = self.query(MyPlexDevice.key)
return [MyPlexDevice(self, elem) for elem in data]
def resource(self, name):
""" Returns the :class:`~plexapi.myplex.MyPlexResource` that matches the name specified.
@ -784,7 +850,7 @@ class MyPlexAccount(PlexObject):
raise BadRequest(f'({response.status_code}) {codename} {response.url}; {errtext}')
return response.json()['token']
def history(self, maxresults=9999999, mindate=None):
def history(self, maxresults=None, mindate=None):
""" Get Play History for all library sections on all servers for the owner.
Parameters:
@ -817,7 +883,7 @@ class MyPlexAccount(PlexObject):
data = self.query(f'{self.MUSIC}/hubs')
return self.findItems(data)
def watchlist(self, filter=None, sort=None, libtype=None, maxresults=9999999, **kwargs):
def watchlist(self, filter=None, sort=None, libtype=None, maxresults=None, **kwargs):
""" Returns a list of :class:`~plexapi.video.Movie` and :class:`~plexapi.video.Show` items in the user's watchlist.
Note: The objects returned are from Plex's online metadata. To get the matching item on a Plex server,
search for the media using the guid.
@ -857,23 +923,10 @@ class MyPlexAccount(PlexObject):
if libtype:
params['type'] = utils.searchType(libtype)
params['X-Plex-Container-Start'] = 0
params['X-Plex-Container-Size'] = min(X_PLEX_CONTAINER_SIZE, maxresults)
params.update(kwargs)
results, subresults = [], '_init'
while subresults and maxresults > len(results):
data = self.query(f'{self.METADATA}/library/sections/watchlist/{filter}', params=params)
subresults = self.findItems(data)
results += subresults[:maxresults - len(results)]
params['X-Plex-Container-Start'] += params['X-Plex-Container-Size']
# totalSize is available in first response, update maxresults from it
totalSize = utils.cast(int, data.attrib.get('totalSize'))
if maxresults > totalSize:
maxresults = totalSize
return self._toOnlineMetadata(results, **kwargs)
key = f'{self.METADATA}/library/sections/watchlist/{filter}{utils.joinArgs(params)}'
return self._toOnlineMetadata(self.fetchItems(key, maxresults=maxresults), **kwargs)
def onWatchlist(self, item):
""" Returns True if the item is on the user's watchlist.
@ -936,6 +989,48 @@ class MyPlexAccount(PlexObject):
data = self.query(f"{self.METADATA}/library/metadata/{ratingKey}/userState")
return self.findItem(data, cls=UserState)
def isPlayed(self, item):
""" Return True if the item is played on Discover.
Parameters:
item (:class:`~plexapi.video.Movie`,
:class:`~plexapi.video.Show`, :class:`~plexapi.video.Season` or
:class:`~plexapi.video.Episode`): Object from searchDiscover().
Can be also result from Plex Movie or Plex TV Series agent.
"""
userState = self.userState(item)
return bool(userState.viewCount > 0) if userState.viewCount else False
def markPlayed(self, item):
""" Mark the Plex object as played on Discover.
Parameters:
item (:class:`~plexapi.video.Movie`,
:class:`~plexapi.video.Show`, :class:`~plexapi.video.Season` or
:class:`~plexapi.video.Episode`): Object from searchDiscover().
Can be also result from Plex Movie or Plex TV Series agent.
"""
key = f'{self.METADATA}/actions/scrobble'
ratingKey = item.guid.rsplit('/', 1)[-1]
params = {'key': ratingKey, 'identifier': 'com.plexapp.plugins.library'}
self.query(key, params=params)
return self
def markUnplayed(self, item):
""" Mark the Plex object as unplayed on Discover.
Parameters:
item (:class:`~plexapi.video.Movie`,
:class:`~plexapi.video.Show`, :class:`~plexapi.video.Season` or
:class:`~plexapi.video.Episode`): Object from searchDiscover().
Can be also result from Plex Movie or Plex TV Series agent.
"""
key = f'{self.METADATA}/actions/unscrobble'
ratingKey = item.guid.rsplit('/', 1)[-1]
params = {'key': ratingKey, 'identifier': 'com.plexapp.plugins.library'}
self.query(key, params=params)
return self
def searchDiscover(self, query, limit=30, libtype=None):
""" Search for movies and TV shows in Discover.
Returns a list of :class:`~plexapi.video.Movie` and :class:`~plexapi.video.Show` objects.
@ -1117,7 +1212,7 @@ class MyPlexUser(PlexObject):
raise NotFound(f'Unable to find server {name}')
def history(self, maxresults=9999999, mindate=None):
def history(self, maxresults=None, mindate=None):
""" Get all Play History for a user in all shared servers.
Parameters:
maxresults (int): Only return the specified number of results (optional).
@ -1191,7 +1286,7 @@ class Section(PlexObject):
self.sectionId = self.id # For backwards compatibility
self.sectionKey = self.key # For backwards compatibility
def history(self, maxresults=9999999, mindate=None):
def history(self, maxresults=None, mindate=None):
""" Get all Play History for a user for this section in this shared server.
Parameters:
maxresults (int): Only return the specified number of results (optional).
@ -1266,21 +1361,25 @@ class MyPlexResource(PlexObject):
""" This object represents resources connected to your Plex server that can provide
content such as Plex Media Servers, iPhone or Android clients, etc. The raw xml
for the data presented here can be found at:
https://plex.tv/api/resources?includeHttps=1&includeRelay=1
https://plex.tv/api/v2/resources?includeHttps=1&includeRelay=1
Attributes:
TAG (str): 'Device'
key (str): 'https://plex.tv/api/resources?includeHttps=1&includeRelay=1'
accessToken (str): This resources accesstoken.
key (str): 'https://plex.tv/api/v2/resources?includeHttps=1&includeRelay=1'
accessToken (str): This resource's Plex access token.
clientIdentifier (str): Unique ID for this resource.
connections (list): List of :class:`~plexapi.myplex.ResourceConnection` objects
for this resource.
createdAt (datetime): Timestamp this resource first connected to your server.
device (str): Best guess on the type of device this is (PS, iPhone, Linux, etc).
dnsRebindingProtection (bool): True if the server had DNS rebinding protection.
home (bool): Unknown
httpsRequired (bool): True if the resource requires https.
lastSeenAt (datetime): Timestamp this resource last connected.
name (str): Descriptive name of this resource.
natLoopbackSupported (bool): True if the resource supports NAT loopback.
owned (bool): True if this resource is one of your own (you logged into it).
ownerId (int): ID of the user that owns this resource (shared resources only).
platform (str): OS the resource is running (Linux, Windows, Chrome, etc.)
platformVersion (str): Version of the platform.
presence (bool): True if the resource is online
@ -1288,10 +1387,13 @@ class MyPlexResource(PlexObject):
productVersion (str): Version of the product.
provides (str): List of services this resource provides (client, server,
player, pubsub-player, etc.)
publicAddressMatches (bool): True if the public IP address matches the client's public IP address.
relay (bool): True if this resource has the Plex Relay enabled.
sourceTitle (str): Username of the user that owns this resource (shared resources only).
synced (bool): Unknown (possibly True if the resource has synced content?)
"""
TAG = 'Device'
key = 'https://plex.tv/api/resources?includeHttps=1&includeRelay=1'
TAG = 'resource'
key = 'https://plex.tv/api/v2/resources?includeHttps=1&includeRelay=1'
# Default order to prioritize available resource connections
DEFAULT_LOCATION_ORDER = ['local', 'remote', 'relay']
@ -1299,33 +1401,35 @@ class MyPlexResource(PlexObject):
def _loadData(self, data):
self._data = data
self.name = data.attrib.get('name')
self.accessToken = logfilter.add_secret(data.attrib.get('accessToken'))
self.product = data.attrib.get('product')
self.productVersion = data.attrib.get('productVersion')
self.clientIdentifier = data.attrib.get('clientIdentifier')
self.connections = self.findItems(data, ResourceConnection, rtag='connections')
self.createdAt = utils.toDatetime(data.attrib.get('createdAt'), "%Y-%m-%dT%H:%M:%SZ")
self.device = data.attrib.get('device')
self.dnsRebindingProtection = utils.cast(bool, data.attrib.get('dnsRebindingProtection'))
self.home = utils.cast(bool, data.attrib.get('home'))
self.httpsRequired = utils.cast(bool, data.attrib.get('httpsRequired'))
self.lastSeenAt = utils.toDatetime(data.attrib.get('lastSeenAt'), "%Y-%m-%dT%H:%M:%SZ")
self.name = data.attrib.get('name')
self.natLoopbackSupported = utils.cast(bool, data.attrib.get('natLoopbackSupported'))
self.owned = utils.cast(bool, data.attrib.get('owned'))
self.ownerId = utils.cast(int, data.attrib.get('ownerId', 0))
self.platform = data.attrib.get('platform')
self.platformVersion = data.attrib.get('platformVersion')
self.device = data.attrib.get('device')
self.clientIdentifier = data.attrib.get('clientIdentifier')
self.createdAt = utils.toDatetime(data.attrib.get('createdAt'))
self.lastSeenAt = utils.toDatetime(data.attrib.get('lastSeenAt'))
self.provides = data.attrib.get('provides')
self.owned = utils.cast(bool, data.attrib.get('owned'))
self.home = utils.cast(bool, data.attrib.get('home'))
self.synced = utils.cast(bool, data.attrib.get('synced'))
self.presence = utils.cast(bool, data.attrib.get('presence'))
self.connections = self.findItems(data, ResourceConnection)
self.product = data.attrib.get('product')
self.productVersion = data.attrib.get('productVersion')
self.provides = data.attrib.get('provides')
self.publicAddressMatches = utils.cast(bool, data.attrib.get('publicAddressMatches'))
# This seems to only be available if its not your device (say are shared server)
self.httpsRequired = utils.cast(bool, data.attrib.get('httpsRequired'))
self.ownerid = utils.cast(int, data.attrib.get('ownerId', 0))
self.sourceTitle = data.attrib.get('sourceTitle') # owners plex username.
self.relay = utils.cast(bool, data.attrib.get('relay'))
self.sourceTitle = data.attrib.get('sourceTitle')
self.synced = utils.cast(bool, data.attrib.get('synced'))
def preferred_connections(
self,
ssl=None,
locations=DEFAULT_LOCATION_ORDER,
schemes=DEFAULT_SCHEME_ORDER,
locations=None,
schemes=None,
):
""" Returns a sorted list of the available connection addresses for this resource.
Often times there is more than one address specified for a server or client.
@ -1336,6 +1440,11 @@ class MyPlexResource(PlexObject):
only connect to HTTP connections. Set None (default) to connect to any
HTTP or HTTPS connection.
"""
if locations is None:
locations = self.DEFAULT_LOCATION_ORDER[:]
if schemes is None:
schemes = self.DEFAULT_SCHEME_ORDER[:]
connections_dict = {location: {scheme: [] for scheme in schemes} for location in locations}
for connection in self.connections:
# Only check non-local connections unless we own the resource
@ -1359,8 +1468,8 @@ class MyPlexResource(PlexObject):
self,
ssl=None,
timeout=None,
locations=DEFAULT_LOCATION_ORDER,
schemes=DEFAULT_SCHEME_ORDER,
locations=None,
schemes=None,
):
""" Returns a new :class:`~plexapi.server.PlexServer` or :class:`~plexapi.client.PlexClient` object.
Uses `MyPlexResource.preferred_connections()` to generate the priority order of connection addresses.
@ -1376,11 +1485,16 @@ class MyPlexResource(PlexObject):
Raises:
:exc:`~plexapi.exceptions.NotFound`: When unable to connect to any addresses for this resource.
"""
if locations is None:
locations = self.DEFAULT_LOCATION_ORDER[:]
if schemes is None:
schemes = self.DEFAULT_SCHEME_ORDER[:]
connections = self.preferred_connections(ssl, locations, schemes)
# Try connecting to all known resource connections in parallel, but
# only return the first server (in order) that provides a response.
cls = PlexServer if 'server' in self.provides else PlexClient
listargs = [[cls, url, self.accessToken, timeout] for url in connections]
listargs = [[cls, url, self.accessToken, self._server._session, timeout] for url in connections]
log.debug('Testing %s resource connections..', len(listargs))
results = utils.threaded(_connect, listargs)
return _chooseConnection('Resource', self.name, results)
@ -1392,24 +1506,27 @@ class ResourceConnection(PlexObject):
Attributes:
TAG (str): 'Connection'
address (str): Local IP address
httpuri (str): Full local address
local (bool): True if local
port (int): 32400
address (str): The connection IP address
httpuri (str): Full HTTP URL
ipv6 (bool): True if the address is IPv6
local (bool): True if the address is local
port (int): The connection port
protocol (str): HTTP or HTTPS
uri (str): External address
relay (bool): True if the address uses the Plex Relay
uri (str): Full connetion URL
"""
TAG = 'Connection'
TAG = 'connection'
def _loadData(self, data):
self._data = data
self.protocol = data.attrib.get('protocol')
self.address = data.attrib.get('address')
self.port = utils.cast(int, data.attrib.get('port'))
self.uri = data.attrib.get('uri')
self.ipv6 = utils.cast(bool, data.attrib.get('IPv6'))
self.local = utils.cast(bool, data.attrib.get('local'))
self.httpuri = f'http://{self.address}:{self.port}'
self.port = utils.cast(int, data.attrib.get('port'))
self.protocol = data.attrib.get('protocol')
self.relay = utils.cast(bool, data.attrib.get('relay'))
self.uri = data.attrib.get('uri')
self.httpuri = f'http://{self.address}:{self.port}'
class MyPlexDevice(PlexObject):
@ -1475,7 +1592,7 @@ class MyPlexDevice(PlexObject):
:exc:`~plexapi.exceptions.NotFound`: When unable to connect to any addresses for this device.
"""
cls = PlexServer if 'server' in self.provides else PlexClient
listargs = [[cls, url, self.token, timeout] for url in self.connections]
listargs = [[cls, url, self.token, self._server._session, timeout] for url in self.connections]
log.debug('Testing %s device connections..', len(listargs))
results = utils.threaded(_connect, listargs)
return _chooseConnection('Device', self.name, results)
@ -1725,7 +1842,7 @@ class MyPlexPinLogin:
return ElementTree.fromstring(data) if data.strip() else None
def _connect(cls, url, token, timeout, results, i, job_is_done_event=None):
def _connect(cls, url, token, session, timeout, results, i, job_is_done_event=None):
""" Connects to the specified cls with url and token. Stores the connection
information to results[i] in a threadsafe way.
@ -1733,6 +1850,7 @@ def _connect(cls, url, token, timeout, results, i, job_is_done_event=None):
cls: the class which is responsible for establishing connection, basically it's
:class:`~plexapi.client.PlexClient` or :class:`~plexapi.server.PlexServer`
url (str): url which should be passed as `baseurl` argument to cls.__init__()
session (requests.Session): session which sould be passed as `session` argument to cls.__init()
token (str): authentication token which should be passed as `baseurl` argument to cls.__init__()
timeout (int): timeout which should be passed as `baseurl` argument to cls.__init__()
results (list): pre-filled list for results
@ -1742,7 +1860,7 @@ def _connect(cls, url, token, timeout, results, i, job_is_done_event=None):
"""
starttime = time.time()
try:
device = cls(baseurl=url, token=token, timeout=timeout)
device = cls(baseurl=url, token=token, session=session, timeout=timeout)
runtime = int(time.time() - starttime)
results[i] = (url, token, device, runtime)
if X_PLEX_ENABLE_FAST_CONNECT and job_is_done_event: