mirror of
https://github.com/pkkid/python-plexapi
synced 2024-11-10 06:04:15 +00:00
Update MyPlexAccount
to use Plex API v2 (#1129)
* Update MyPlexAccount to use Plex API v2 * Update MyPlexAccount test * Add rememberMe option to MyPlexAccount sign in * Update MyPlexAccount do string
This commit is contained in:
parent
d94b6efc41
commit
013d1a2d00
4 changed files with 152 additions and 87 deletions
|
@ -22,51 +22,76 @@ from requests.status_codes import _codes as codes
|
||||||
|
|
||||||
class MyPlexAccount(PlexObject):
|
class MyPlexAccount(PlexObject):
|
||||||
""" MyPlex account and profile information. This object represents the data found Account on
|
""" 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
|
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
|
method provided at :class:`~plexapi.server.PlexServer.myPlexAccount()` which will create
|
||||||
and return this object.
|
and return this object.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
username (str): Your MyPlex username.
|
username (str): Plex login username if not using a token.
|
||||||
password (str): Your MyPlex password.
|
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
|
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).
|
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:
|
Attributes:
|
||||||
SIGNIN (str): 'https://plex.tv/users/sign_in.xml'
|
key (str): 'https://plex.tv/api/v2/user'
|
||||||
key (str): 'https://plex.tv/users/account'
|
adsConsent (str): Unknown.
|
||||||
authenticationToken (str): Unknown.
|
adsConsentReminderAt (str): Unknown.
|
||||||
certificateVersion (str): Unknown.
|
adsConsentSetAt (str): Unknown.
|
||||||
cloudSyncDevice (str): Unknown.
|
anonymous (str): Unknown.
|
||||||
email (str): Your current Plex email address.
|
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.
|
entitlements (List<str>): List of devices your allowed to use with this account.
|
||||||
guest (bool): Unknown.
|
guest (bool): If the account is a Plex Home guest user.
|
||||||
home (bool): Unknown.
|
hasPassword (bool): If the account has a password.
|
||||||
homeSize (int): Unknown.
|
home (bool): If the account is a Plex Home user.
|
||||||
id (int): Your Plex account ID.
|
homeAdmin (bool): If the account is the Plex Home admin.
|
||||||
locale (str): Your Plex locale
|
homeSize (int): The number of accounts in the Plex Home.
|
||||||
mailing_list_status (str): Your current mailing list status.
|
id (int): The Plex account ID.
|
||||||
maxHomeSize (int): Unknown.
|
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.
|
pin (str): The hashed Plex Home PIN.
|
||||||
queueEmail (str): Email address to add items to your `Watch Later` queue.
|
profileAutoSelectAudio (bool): If the account has automatically select audio and subtitle tracks enabled.
|
||||||
queueUid (str): Unknown.
|
profileDefaultAudioLanguage (str): The preferred audio language for the account.
|
||||||
restricted (bool): Unknown.
|
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.
|
roles: (List<str>) Lit of account roles. Plexpass membership listed here.
|
||||||
scrobbleTypes (str): Description
|
scrobbleTypes (List<int>): Unknown.
|
||||||
secure (bool): Description
|
subscriptionActive (bool): If the account's Plex Pass subscription is active.
|
||||||
subscriptionActive (bool): True if your subscription is active.
|
subscriptionDescription (str): Description of the Plex Pass subscription.
|
||||||
subscriptionFeatures: (List<str>) List of features allowed on your subscription.
|
subscriptionFeatures: (List<str>) List of features allowed on your Plex Pass subscription.
|
||||||
subscriptionPlan (str): Name of subscription plan.
|
subscriptionPaymentService (str): Payment service used for your Plex Pass subscription.
|
||||||
subscriptionStatus (str): String representation of `subscriptionActive`.
|
subscriptionPlan (str): Name of Plex Pass subscription plan.
|
||||||
thumb (str): URL of your account thumbnail.
|
subscriptionStatus (str): String representation of ``subscriptionActive``.
|
||||||
title (str): Unknown. - Looks like an alias for `username`.
|
subscriptionSubscribedAt (datetime): Date the account subscribed to Plex Pass.
|
||||||
username (str): Your account username.
|
thumb (str): URL of the account thumbnail.
|
||||||
uuid (str): Unknown.
|
title (str): The title of the account (username or friendly name).
|
||||||
_token (str): Token used to access this client.
|
twoFactorEnabled (bool): If two-factor authentication is enabled.
|
||||||
_session (obj): Requests session object used to access this client.
|
username (str): The account username.
|
||||||
|
uuid (str): The account UUID.
|
||||||
"""
|
"""
|
||||||
FRIENDINVITE = 'https://plex.tv/api/servers/{machineId}/shared_servers' # post with data
|
FRIENDINVITE = 'https://plex.tv/api/servers/{machineId}/shared_servers' # post with data
|
||||||
HOMEUSERS = 'https://plex.tv/api/home/users'
|
HOMEUSERS = 'https://plex.tv/api/home/users'
|
||||||
|
@ -77,7 +102,8 @@ class MyPlexAccount(PlexObject):
|
||||||
FRIENDUPDATE = 'https://plex.tv/api/friends/{userId}' # put with args, delete
|
FRIENDUPDATE = 'https://plex.tv/api/friends/{userId}' # put with args, delete
|
||||||
HOMEUSER = 'https://plex.tv/api/home/users/{userId}' # delete, put
|
HOMEUSER = 'https://plex.tv/api/home/users/{userId}' # delete, put
|
||||||
MANAGEDHOMEUSER = 'https://plex.tv/api/v2/home/users/restricted/{userId}' # 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
|
WEBHOOKS = 'https://plex.tv/api/v2/user/webhooks' # get, post with data
|
||||||
OPTOUTS = 'https://plex.tv/api/v2/user/{userUUID}/settings/opt_outs' # get
|
OPTOUTS = 'https://plex.tv/api/v2/user/{userUUID}/settings/opt_outs' # get
|
||||||
LINK = 'https://plex.tv/api/v2/pins/link' # put
|
LINK = 'https://plex.tv/api/v2/pins/link' # put
|
||||||
|
@ -86,86 +112,106 @@ class MyPlexAccount(PlexObject):
|
||||||
VOD = 'https://vod.provider.plex.tv' # get
|
VOD = 'https://vod.provider.plex.tv' # get
|
||||||
MUSIC = 'https://music.provider.plex.tv' # get
|
MUSIC = 'https://music.provider.plex.tv' # get
|
||||||
METADATA = 'https://metadata.provider.plex.tv'
|
METADATA = 'https://metadata.provider.plex.tv'
|
||||||
# Key may someday switch to the following url. For now the current value works.
|
key = 'https://plex.tv/api/v2/user'
|
||||||
# https://plex.tv/api/v2/user?X-Plex-Token={token}&X-Plex-Client-Identifier={clientId}
|
|
||||||
key = 'https://plex.tv/users/account'
|
|
||||||
|
|
||||||
def __init__(self, username=None, password=None, token=None, session=None, timeout=None, code=None):
|
def __init__(self, username=None, password=None, token=None, session=None, timeout=None, code=None, remember=True):
|
||||||
self._token = token or CONFIG.get('auth.server_token')
|
self._token = logfilter.add_secret(token or CONFIG.get('auth.server_token'))
|
||||||
self._session = session or requests.Session()
|
self._session = session or requests.Session()
|
||||||
self._sonos_cache = []
|
self._sonos_cache = []
|
||||||
self._sonos_cache_timestamp = 0
|
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)
|
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:
|
if self._token:
|
||||||
return self.query(self.key), self.key
|
return self.query(self.key), self.key
|
||||||
username = username or CONFIG.get('auth.myplex_username')
|
payload = {
|
||||||
password = password or CONFIG.get('auth.myplex_password')
|
'login': username or CONFIG.get('auth.myplex_username'),
|
||||||
|
'password': password or CONFIG.get('auth.myplex_password'),
|
||||||
|
'rememberMe': remember
|
||||||
|
}
|
||||||
if code:
|
if code:
|
||||||
password += code
|
payload['verificationCode'] = code
|
||||||
data = self.query(self.SIGNIN, method=self._session.post, auth=(username, password), timeout=timeout)
|
data = self.query(self.SIGNIN, method=self._session.post, data=payload, timeout=timeout)
|
||||||
return data, self.SIGNIN
|
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):
|
def _loadData(self, data):
|
||||||
""" Load attribute values from Plex XML response. """
|
""" Load attribute values from Plex XML response. """
|
||||||
self._data = data
|
self._data = data
|
||||||
self._token = logfilter.add_secret(data.attrib.get('authenticationToken'))
|
self._token = logfilter.add_secret(data.attrib.get('authToken'))
|
||||||
self._webhooks = []
|
self._webhooks = []
|
||||||
self.authenticationToken = self._token
|
|
||||||
self.certificateVersion = data.attrib.get('certificateVersion')
|
self.adsConsent = data.attrib.get('adsConsent')
|
||||||
self.cloudSyncDevice = data.attrib.get('cloudSyncDevice')
|
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.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.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.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.homeSize = utils.cast(int, data.attrib.get('homeSize'))
|
||||||
self.id = utils.cast(int, data.attrib.get('id'))
|
self.id = utils.cast(int, data.attrib.get('id'))
|
||||||
|
self.joinedAt = utils.toDatetime(data.attrib.get('joinedAt'))
|
||||||
self.locale = data.attrib.get('locale')
|
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.maxHomeSize = utils.cast(int, data.attrib.get('maxHomeSize'))
|
||||||
self.pin = data.attrib.get('pin')
|
self.pin = data.attrib.get('pin')
|
||||||
self.queueEmail = data.attrib.get('queueEmail')
|
self.protected = utils.cast(bool, data.attrib.get('protected'))
|
||||||
self.queueUid = data.attrib.get('queueUid')
|
self.rememberExpiresAt = utils.toDatetime(data.attrib.get('rememberExpiresAt'))
|
||||||
self.restricted = utils.cast(bool, data.attrib.get('restricted'))
|
self.restricted = utils.cast(bool, data.attrib.get('restricted'))
|
||||||
self.scrobbleTypes = data.attrib.get('scrobbleTypes')
|
self.scrobbleTypes = [utils.cast(int, x) for x in data.attrib.get('scrobbleTypes').split(',')]
|
||||||
self.secure = utils.cast(bool, data.attrib.get('secure'))
|
|
||||||
self.thumb = data.attrib.get('thumb')
|
self.thumb = data.attrib.get('thumb')
|
||||||
self.title = data.attrib.get('title')
|
self.title = data.attrib.get('title')
|
||||||
|
self.twoFactorEnabled = utils.cast(bool, data.attrib.get('twoFactorEnabled'))
|
||||||
self.username = data.attrib.get('username')
|
self.username = data.attrib.get('username')
|
||||||
self.uuid = data.attrib.get('uuid')
|
self.uuid = data.attrib.get('uuid')
|
||||||
|
|
||||||
subscription = data.find('subscription')
|
subscription = data.find('subscription')
|
||||||
self.subscriptionActive = utils.cast(bool, subscription.attrib.get('active'))
|
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.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.entitlements = self.listAttrs(data, 'id', rtag='entitlements', etag='entitlement')
|
||||||
|
self.roles = self.listAttrs(data, 'id', rtag='roles', etag='role')
|
||||||
|
|
||||||
# TODO: Fetch missing MyPlexAccount attributes
|
# TODO: Fetch missing MyPlexAccount services
|
||||||
self.profile_settings = None
|
|
||||||
self.services = None
|
self.services = None
|
||||||
self.joined_at = None
|
|
||||||
|
|
||||||
def device(self, name=None, clientId=None):
|
@property
|
||||||
""" Returns the :class:`~plexapi.myplex.MyPlexDevice` that matches the name specified.
|
def authenticationToken(self):
|
||||||
|
""" Returns the authentication token for the account. Alias for ``authToken``. """
|
||||||
|
return self.authToken
|
||||||
|
|
||||||
Parameters:
|
def _reload(self, key=None, **kwargs):
|
||||||
name (str): Name to match against.
|
""" Perform the actual reload. """
|
||||||
clientId (str): clientIdentifier to match against.
|
data = self.query(self.key)
|
||||||
"""
|
self._loadData(data)
|
||||||
for device in self.devices():
|
return self
|
||||||
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 _headers(self, **kwargs):
|
def _headers(self, **kwargs):
|
||||||
""" Returns dict containing base headers for all requests to the server. """
|
""" Returns dict containing base headers for all requests to the server. """
|
||||||
|
@ -198,6 +244,23 @@ class MyPlexAccount(PlexObject):
|
||||||
data = response.text.encode('utf8')
|
data = response.text.encode('utf8')
|
||||||
return ElementTree.fromstring(data) if data.strip() else None
|
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):
|
def resource(self, name):
|
||||||
""" Returns the :class:`~plexapi.myplex.MyPlexResource` that matches the name specified.
|
""" Returns the :class:`~plexapi.myplex.MyPlexResource` that matches the name specified.
|
||||||
|
|
||||||
|
|
|
@ -157,7 +157,7 @@ def account_synctarget(account_plexpass):
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def mocked_account(requests_mock):
|
def mocked_account(requests_mock):
|
||||||
requests_mock.get("https://plex.tv/users/account", text=ACCOUNT_XML)
|
requests_mock.get("https://plex.tv/api/v2/user", text=ACCOUNT_XML)
|
||||||
return MyPlexAccount(token="faketoken")
|
return MyPlexAccount(token="faketoken")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,21 @@
|
||||||
ACCOUNT_XML = """<?xml version="1.0" encoding="UTF-8"?>
|
ACCOUNT_XML = """<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<user email="testuser@email.com" id="12345" uuid="1234567890" mailing_list_status="active" thumb="https://plex.tv/users/1234567890abcdef/avatar?c=12345" username="testuser" title="testuser" cloudSyncDevice="" locale="" authenticationToken="faketoken" authToken="faketoken" scrobbleTypes="" restricted="0" home="1" guest="0" queueEmail="queue+1234567890@save.plex.tv" queueUid="" hasPassword="true" homeSize="2" maxHomeSize="15" secure="1" certificateVersion="2">
|
<user id="12345" uuid="1234567890" username="testuser" title="Test User" email="testuser@email.com" friendlyName="Test User" locale="" confirmed="1" joinedAt="946730096" emailOnlyAuth="0" hasPassword="1" protected="0" thumb="https://plex.tv/users/1234567890abcdef/avatar?c=12345" authToken="faketoken" mailingListStatus="unsubscribed" mailingListActive="0" scrobbleTypes="" country="CA" subscriptionDescription="" restricted="0" anonymous="" home="1" guest="0" homeSize="2" homeAdmin="1" maxHomeSize="15" rememberExpiresAt="1680893707" adsConsent="" adsConsentSetAt="" adsConsentReminderAt="" experimentalFeatures="0" twoFactorEnabled="1" backupCodesCreated="1">
|
||||||
<subscription active="1" status="Active" plan="lifetime">
|
<subscription active="1" subscribedAt="2023-03-24 00:00:00 UTC" status="Active" paymentService="" plan="lifetime">
|
||||||
|
<features>
|
||||||
<feature id="companions_sonos"/>
|
<feature id="companions_sonos"/>
|
||||||
|
</features>
|
||||||
</subscription>
|
</subscription>
|
||||||
|
<profile autoSelectAudio="0" defaultAudioLanguage="en" defaultSubtitleLanguage="en" autoSelectSubtitle="0" defaultSubtitleAccessibility="0" defaultSubtitleForced="0"/>
|
||||||
|
<entitlements>
|
||||||
|
<entitlement id="all"/>
|
||||||
|
</entitlements>
|
||||||
<roles>
|
<roles>
|
||||||
<role id="plexpass"/>
|
<role id="plexpass"/>
|
||||||
</roles>
|
</roles>
|
||||||
<entitlements all="1"/>
|
<subscriptions>
|
||||||
<profile_settings default_audio_language="en" default_subtitle_language="en" auto_select_subtitle="1" auto_select_audio="1" default_subtitle_accessibility="0" default_subtitle_forced="0"/>
|
</subscriptions>
|
||||||
<services/>
|
<services>
|
||||||
<username>testuser</username>
|
</services>
|
||||||
<email>testuser@email.com</email>
|
|
||||||
<joined-at type="datetime">2000-01-01 12:348:56 UTC</joined-at>
|
|
||||||
<authentication-token>faketoken</authentication-token>
|
|
||||||
</user>
|
</user>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,6 @@ def test_myplex_accounts(account, plex):
|
||||||
assert account.authenticationToken, "Account has no authenticationToken"
|
assert account.authenticationToken, "Account has no authenticationToken"
|
||||||
assert account.email, "Account has no email"
|
assert account.email, "Account has no email"
|
||||||
assert account.home is not None, "Account has no home"
|
assert account.home is not None, "Account has no home"
|
||||||
assert account.queueEmail, "Account has no queueEmail"
|
|
||||||
account = plex.account()
|
account = plex.account()
|
||||||
print("Local PlexServer.account():")
|
print("Local PlexServer.account():")
|
||||||
print(f"username: {account.username}")
|
print(f"username: {account.username}")
|
||||||
|
|
Loading…
Reference in a new issue