PlexKodiConnect/resources/lib/plex_tv.py

330 lines
10 KiB
Python
Raw Normal View History

#!/usr/bin/env python
2018-02-11 03:59:20 +11:00
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
2018-02-11 03:59:20 +11:00
from logging import getLogger
2018-09-11 04:53:46 +10:00
import time
import threading
import xbmc
import xbmcgui
2018-02-11 03:59:20 +11:00
2018-06-22 03:24:37 +10:00
from .downloadutils import DownloadUtils as DU
2018-09-11 04:53:46 +10:00
from . import utils, variables as v, state
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 homeuser_to_settings(user):
"""
Writes one HomeUser to the Kodi settings file
"""
utils.settings('myplexlogin', 'true')
utils.settings('plexLogin', user.title)
utils.settings('plexToken', user.authToken)
utils.settings('plexid', user.id)
utils.settings('plexAvatar', user.thumb)
utils.settings('plex_status', value=utils.lang(39227))
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
2018-06-22 03:24:37 +10:00
utils.settings('userid') and utils.settings('username') with new plex token
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
# Write to settings file
2018-06-22 03:24:37 +10:00
utils.settings('username', username)
utils.settings('accessToken', token)
2018-09-11 04:53:46 +10:00
utils.settings('userid', xml.get('id', ''))
2018-06-22 03:24:37 +10:00
utils.settings('plex_restricteduser',
2018-09-11 04:53:46 +10:00
'true' if xml.get('restricted', '0') == '1'
2018-06-22 03:24:37 +10:00
else 'false')
2018-02-11 03:59:20 +11:00
state.RESTRICTED_USER = True if \
2018-09-11 04:53:46 +10:00
xml.get('restricted', '0') == '1' else False
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 = []
found = 0
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
found += 1
token = device.attrib.get('accessToken')
if found == 0:
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.')
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
not state.STOP_PKC):
xml = DU().downloadUrl(self.POLL.format(self.id),
authenticate=False)
try:
token = xml.find('auth_token').text
except AttributeError:
time.sleep(self.POLL_INTERVAL)
continue
if token:
self.token = token
break
time.sleep(self.POLL_INTERVAL)
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-09-11 04:53:46 +10:00
xml = _sign_in_with_pin()
if not xml:
return
user = HomeUser(xml.attrib)
homeuser_to_settings(user)
return user
class TestWindow(xbmcgui.Window):
def onAction(self, action):
LOG.debug('onAction: %s', action)
def _sign_in_with_pin():
"""
Returns the user xml answer from plex.tv or None if unsuccessful
"""
from .dialogs import signin
return
back = signin.Background.create()
try:
pre = signin.PreSignInWindow.open()
try:
if not pre.doSignin:
return
finally:
del pre
while True:
pin_login_window = signin.PinLoginWindow.create()
try:
try:
pinlogin = PinLogin()
except RuntimeError:
# Could not sign in to plex.tv Try again later
utils.dialog('ok',
heading='{plex}',
line1=utils.lang(39305))
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
time.sleep(0.1)
if not pinlogin.expired:
if pinlogin.xml:
pin_login_window.setLinking()
return pinlogin.xml
return
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