PlexKodiConnect/resources/lib/plex_tv.py

307 lines
9.6 KiB
Python
Raw Permalink Normal View History

#!/usr/bin/env python
2018-02-11 03:59:20 +11:00
# -*- coding: utf-8 -*-
from logging import getLogger
2018-09-11 04:53:46 +10:00
import time
import threading
2018-12-03 02:03:20 +11:00
import xbmc
2018-02-11 03:59:20 +11:00
2018-06-22 03:24:37 +10:00
from .downloadutils import DownloadUtils as DU
2018-11-19 00:59:17 +11:00
from . import utils, app
2018-02-11 03:59:20 +11:00
###############################################################################
2018-09-11 04:53:46 +10:00
LOG = getLogger('PLEX.plex_tv')
2018-02-11 03:59:20 +11:00
###############################################################################
2018-09-11 04:53:46 +10:00
class HomeUser(utils.AttributeDict):
2018-02-11 03:59:20 +11:00
"""
2018-09-11 04:53:46 +10:00
Turns an etree xml answer into an object with attributes
2018-09-15 23:46:06 +10:00
Adds the additional properties:
isProtected
isAdmin
isManaged
2018-02-11 03:59:20 +11:00
"""
2018-09-15 23:46:06 +10:00
@property
def isProtected(self):
return self.protected == '1'
@property
def isAdmin(self):
return self.admin == '1'
@property
def isManaged(self):
return self.restricted == '1'
2018-09-11 04:53:46 +10:00
def switch_home_user(userid, pin, token, machine_identifier):
2018-02-11 03:59:20 +11:00
"""
2018-09-11 04:53:46 +10:00
Retrieves Plex home token for a Plex home user. Returns None if this fails
2018-02-11 03:59:20 +11:00
Input:
userid id of the Plex home user
pin PIN of the Plex home user, if protected
token token for plex.tv
Output:
2018-09-11 04:53:46 +10:00
usertoken Might be empty strings if no token found
for the machine_identifier that was chosen
2018-02-11 03:59:20 +11:00
"""
LOG.info('Switching to user %s', userid)
2018-09-11 04:53:46 +10:00
url = 'https://plex.tv/api/home/users/%s/switch' % userid
2018-02-11 03:59:20 +11:00
if pin:
2018-09-11 04:53:46 +10:00
url += '?pin=%s' % pin
xml = DU().downloadUrl(url,
authenticate=False,
action_type="POST",
headerOptions={'X-Plex-Token': token})
2018-02-11 03:59:20 +11:00
try:
2018-09-11 04:53:46 +10:00
xml.attrib
2018-02-11 03:59:20 +11:00
except AttributeError:
2018-09-11 04:53:46 +10:00
LOG.error('Switch HomeUser change failed')
return
2018-02-11 03:59:20 +11:00
2018-09-11 04:53:46 +10:00
username = xml.get('title', '')
token = xml.get('authenticationToken', '')
2018-02-11 03:59:20 +11:00
# Get final token to the PMS we've chosen
url = 'https://plex.tv/api/resources?includeHttps=1'
xml = DU().downloadUrl(url,
authenticate=False,
headerOptions={'X-Plex-Token': token})
try:
xml.attrib
except AttributeError:
LOG.error('Answer from plex.tv not as excepted')
# Set to empty iterable list for loop
xml = []
2018-09-11 04:53:46 +10:00
LOG.debug('Our machine_identifier is %s', machine_identifier)
2018-02-11 03:59:20 +11:00
for device in xml:
identifier = device.attrib.get('clientIdentifier')
2018-09-11 04:53:46 +10:00
LOG.debug('Found the Plex clientIdentifier: %s', identifier)
if identifier == machine_identifier:
2018-02-11 03:59:20 +11:00
token = device.attrib.get('accessToken')
2021-01-04 03:17:47 +11:00
break
else:
2018-02-11 03:59:20 +11:00
LOG.info('No tokens found for your server! Using empty string')
2018-09-11 04:53:46 +10:00
token = ''
2018-02-11 03:59:20 +11:00
LOG.info('Plex.tv switch HomeUser change successfull for user %s',
username)
2018-09-11 04:53:46 +10:00
return token
2018-02-11 03:59:20 +11:00
2018-09-11 04:53:46 +10:00
def plex_home_users(token):
2018-02-11 03:59:20 +11:00
"""
2018-09-11 04:53:46 +10:00
Returns a list of HomeUser elements from plex.tv
2018-02-11 03:59:20 +11:00
"""
xml = DU().downloadUrl('https://plex.tv/api/home/users/',
authenticate=False,
headerOptions={'X-Plex-Token': token})
2018-09-11 04:53:46 +10:00
users = []
2018-02-11 03:59:20 +11:00
try:
xml.attrib
except AttributeError:
LOG.error('Download of Plex home users failed.')
# Plex.tv did not provide us a valid list of Plex users, sorry.
utils.messageDialog(utils.lang(29999), utils.lang(33011))
2018-09-11 04:53:46 +10:00
else:
for user in xml:
users.append(HomeUser(user.attrib))
2018-02-11 03:59:20 +11:00
return users
2018-09-11 04:53:46 +10:00
class PinLogin(object):
"""
Signs user in to plex.tv
"""
INIT = 'https://plex.tv/pins.xml'
POLL = 'https://plex.tv/pins/{0}.xml'
ACCOUNT = 'https://plex.tv/users/account'
POLL_INTERVAL = 1
def __init__(self, callback=None):
self._callback = callback
self.id = None
self.pin = None
self.token = None
self.finished = False
self._abort = False
self.expired = False
self.xml = None
self._init()
def _init(self):
xml = DU().downloadUrl(self.INIT,
authenticate=False,
action_type="POST")
try:
xml.attrib
except AttributeError:
LOG.error("Error, no PIN from plex.tv provided")
raise RuntimeError
self.pin = xml.find('code').text
self.id = xml.find('id').text
LOG.debug('Successfully retrieved code and id from plex.tv')
def _poll(self):
LOG.debug('Start polling plex.tv for token')
start = time.time()
while (not self._abort and
time.time() - start < 300 and
2018-11-19 00:59:17 +11:00
not app.APP.stop_pkc):
2018-09-11 04:53:46 +10:00
xml = DU().downloadUrl(self.POLL.format(self.id),
authenticate=False)
try:
token = xml.find('auth_token').text
except AttributeError:
app.APP.monitor.waitForAbort(self.POLL_INTERVAL)
2018-09-11 04:53:46 +10:00
continue
if token:
self.token = token
break
app.APP.monitor.waitForAbort(self.POLL_INTERVAL)
2018-09-11 04:53:46 +10:00
if self._callback:
self._callback(self.token, self.xml)
if self.token:
# Use temp token to get the final plex credentials
self.xml = DU().downloadUrl(self.ACCOUNT,
authenticate=False,
parameters={'X-Plex-Token': self.token})
self.finished = True
LOG.debug('Polling done')
def start_token_poll(self):
t = threading.Thread(target=self._poll, name='PIN-LOGIN:Token-Poll')
t.start()
return t
def wait_for_token(self):
t = self.start_token_poll()
t.join()
return self.token
def abort(self):
self._abort = True
2018-02-11 03:59:20 +11:00
def sign_in_with_pin():
"""
Prompts user to sign in by visiting https://plex.tv/pin
2018-09-11 04:53:46 +10:00
Writes to Kodi settings file and returns the HomeUser or None
2018-02-11 03:59:20 +11:00
"""
2018-12-03 02:03:20 +11:00
LOG.info('Showing plex.tv sign in window')
# Fix for:
# DEBUG: Activating window ID: 13000
# INFO: Activate of window '13000' refused because there are active modal dialogs
# DEBUG: Activating window ID: 13000
xbmc.executebuiltin("Dialog.Close(all, true)")
2018-09-17 00:08:44 +10:00
from .windows import background
bkgrd = background.BackgroundWindow.create(function=_sign_in_with_pin)
bkgrd.modal()
xml = bkgrd.result
del bkgrd
2018-12-03 02:02:46 +11:00
if xml is None:
2018-09-11 04:53:46 +10:00
return
user = HomeUser(xml.attrib)
utils.settings('myplexlogin', 'true')
utils.settings('plex_status', value=utils.lang(39227))
utils.settings('plexLogin', user.title)
utils.settings('plexid', user.id)
utils.settings('plexToken', user.authToken)
2018-09-11 04:53:46 +10:00
return user
def _sign_in_with_pin():
"""
Returns the user xml answer from plex.tv or None if unsuccessful
"""
2018-09-16 22:00:52 +10:00
from .windows import signin, background
background.setSplash(False)
2018-09-16 22:02:27 +10:00
back = signin.Background.create()
2018-09-16 22:00:52 +10:00
2018-09-11 04:53:46 +10:00
try:
while True:
pin_login_window = signin.PinLoginWindow.create()
try:
try:
pinlogin = PinLogin()
except RuntimeError:
# Could not sign in to plex.tv Try again later
2018-09-17 00:13:08 +10:00
utils.messageDialog(utils.lang(29999), utils.lang(39305))
2018-09-11 04:53:46 +10:00
return
pin_login_window.setPin(pinlogin.pin)
pinlogin.start_token_poll()
while not pinlogin.finished:
if pin_login_window.abort:
LOG.debug('Pin login aborted')
pinlogin.abort()
return
app.APP.monitor.waitForAbort(0.1)
2018-09-11 04:53:46 +10:00
if not pinlogin.expired:
2019-02-02 21:34:16 +11:00
if pinlogin.xml is not None:
2018-09-11 04:53:46 +10:00
pin_login_window.setLinking()
2018-09-17 01:18:46 +10:00
return pinlogin.xml
2018-09-11 04:53:46 +10:00
finally:
pin_login_window.doClose()
del pin_login_window
if pinlogin.expired:
LOG.debug('Pin expired')
expired_window = signin.ExpiredWindow.open()
try:
if not expired_window.refresh:
LOG.debug('Pin refresh aborted')
return
finally:
del expired_window
finally:
back.doClose()
del back
2018-02-11 03:59:20 +11:00
def get_pin():
"""
For plex.tv sign-in: returns 4-digit code and identifier as 2 str
"""
code = None
identifier = None
# Download
xml = DU().downloadUrl('https://plex.tv/pins.xml',
authenticate=False,
action_type="POST")
try:
xml.attrib
except AttributeError:
LOG.error("Error, no PIN from plex.tv provided")
return None, None
code = xml.find('code').text
identifier = xml.find('id').text
LOG.info('Successfully retrieved code and id from plex.tv')
return code, identifier
def check_pin(identifier):
"""
Checks with plex.tv whether user entered the correct PIN on plex.tv/pin
2018-09-11 04:53:46 +10:00
Returns None if not yet done so, or the XML response file as etree
2018-02-11 03:59:20 +11:00
"""
# Try to get a temporary token
xml = DU().downloadUrl('https://plex.tv/pins/%s.xml' % identifier,
authenticate=False)
try:
temp_token = xml.find('auth_token').text
except AttributeError:
LOG.error("Could not find token in plex.tv answer")
2018-09-11 04:53:46 +10:00
return
2018-02-11 03:59:20 +11:00
if not temp_token:
2018-09-11 04:53:46 +10:00
return
2018-02-11 03:59:20 +11:00
# Use temp token to get the final plex credentials
xml = DU().downloadUrl('https://plex.tv/users/account',
authenticate=False,
parameters={'X-Plex-Token': temp_token})
return xml