339 lines
11 KiB
Python
339 lines
11 KiB
Python
|
# -*- coding: utf-8 -*-
|
||
|
from logging import getLogger
|
||
|
|
||
|
from xbmc import sleep, executebuiltin
|
||
|
|
||
|
from downloadutils import DownloadUtils as DU
|
||
|
from utils import dialog, language as lang, settings, tryEncode
|
||
|
import variables as v
|
||
|
import state
|
||
|
|
||
|
###############################################################################
|
||
|
LOG = getLogger("PLEX." + __name__)
|
||
|
###############################################################################
|
||
|
|
||
|
|
||
|
def choose_home_user(token):
|
||
|
"""
|
||
|
Let's user choose from a list of Plex home users. Will switch to that
|
||
|
user accordingly.
|
||
|
|
||
|
Returns a dict:
|
||
|
{
|
||
|
'username': Unicode
|
||
|
'userid': '' Plex ID of the user
|
||
|
'token': '' User's token
|
||
|
'protected': True if PIN is needed, else False
|
||
|
}
|
||
|
|
||
|
Will return False if something went wrong (wrong PIN, no connection)
|
||
|
"""
|
||
|
# Get list of Plex home users
|
||
|
users = list_home_users(token)
|
||
|
if not users:
|
||
|
LOG.error("User download failed.")
|
||
|
return False
|
||
|
userlist = []
|
||
|
userlist_coded = []
|
||
|
for user in users:
|
||
|
username = user['title']
|
||
|
userlist.append(username)
|
||
|
# To take care of non-ASCII usernames
|
||
|
userlist_coded.append(tryEncode(username))
|
||
|
usernumber = len(userlist)
|
||
|
username = ''
|
||
|
usertoken = ''
|
||
|
trials = 0
|
||
|
while trials < 3:
|
||
|
if usernumber > 1:
|
||
|
# Select user
|
||
|
user_select = dialog('select', lang(29999) + lang(39306),
|
||
|
userlist_coded)
|
||
|
if user_select == -1:
|
||
|
LOG.info("No user selected.")
|
||
|
settings('username', value='')
|
||
|
executebuiltin('Addon.OpenSettings(%s)' % v.ADDON_ID)
|
||
|
return False
|
||
|
# Only 1 user received, choose that one
|
||
|
else:
|
||
|
user_select = 0
|
||
|
selected_user = userlist[user_select]
|
||
|
LOG.info("Selected user: %s", selected_user)
|
||
|
user = users[user_select]
|
||
|
# Ask for PIN, if protected:
|
||
|
pin = None
|
||
|
if user['protected'] == '1':
|
||
|
LOG.debug('Asking for users PIN')
|
||
|
pin = dialog('input',
|
||
|
lang(39307) + selected_user,
|
||
|
'',
|
||
|
type='{numeric}',
|
||
|
option='{hide}')
|
||
|
# User chose to cancel
|
||
|
# Plex bug: don't call url for protected user with empty PIN
|
||
|
if not pin:
|
||
|
trials += 1
|
||
|
continue
|
||
|
# Switch to this Plex Home user, if applicable
|
||
|
result = switch_home_user(user['id'],
|
||
|
pin,
|
||
|
token,
|
||
|
settings('plex_machineIdentifier'))
|
||
|
if result:
|
||
|
# Successfully retrieved username: break out of while loop
|
||
|
username = result['username']
|
||
|
usertoken = result['usertoken']
|
||
|
break
|
||
|
# Couldn't get user auth
|
||
|
else:
|
||
|
trials += 1
|
||
|
# Could not login user, please try again
|
||
|
if not dialog('yesno',
|
||
|
heading='{plex}',
|
||
|
line1=lang(39308) + selected_user,
|
||
|
line2=lang(39309)):
|
||
|
# User chose to cancel
|
||
|
break
|
||
|
if not username:
|
||
|
LOG.error('Failed signing in a user to plex.tv')
|
||
|
executebuiltin('Addon.OpenSettings(%s)' % v.ADDON_ID)
|
||
|
return False
|
||
|
return {
|
||
|
'username': username,
|
||
|
'userid': user['id'],
|
||
|
'protected': True if user['protected'] == '1' else False,
|
||
|
'token': usertoken
|
||
|
}
|
||
|
|
||
|
|
||
|
def switch_home_user(userid, pin, token, machineIdentifier):
|
||
|
"""
|
||
|
Retrieves Plex home token for a Plex home user.
|
||
|
Returns False if unsuccessful
|
||
|
|
||
|
Input:
|
||
|
userid id of the Plex home user
|
||
|
pin PIN of the Plex home user, if protected
|
||
|
token token for plex.tv
|
||
|
|
||
|
Output:
|
||
|
{
|
||
|
'username'
|
||
|
'usertoken' Might be empty strings if no token found
|
||
|
for the machineIdentifier that was chosen
|
||
|
}
|
||
|
|
||
|
settings('userid') and settings('username') with new plex token
|
||
|
"""
|
||
|
LOG.info('Switching to user %s', userid)
|
||
|
url = 'https://plex.tv/api/home/users/' + userid + '/switch'
|
||
|
if pin:
|
||
|
url += '?pin=' + pin
|
||
|
answer = DU().downloadUrl(url,
|
||
|
authenticate=False,
|
||
|
action_type="POST",
|
||
|
headerOptions={'X-Plex-Token': token})
|
||
|
try:
|
||
|
answer.attrib
|
||
|
except AttributeError:
|
||
|
LOG.error('Error: plex.tv switch HomeUser change failed')
|
||
|
return False
|
||
|
|
||
|
username = answer.attrib.get('title', '')
|
||
|
token = answer.attrib.get('authenticationToken', '')
|
||
|
|
||
|
# Write to settings file
|
||
|
settings('username', username)
|
||
|
settings('accessToken', token)
|
||
|
settings('userid', answer.attrib.get('id', ''))
|
||
|
settings('plex_restricteduser',
|
||
|
'true' if answer.attrib.get('restricted', '0') == '1'
|
||
|
else 'false')
|
||
|
state.RESTRICTED_USER = True if \
|
||
|
answer.attrib.get('restricted', '0') == '1' else False
|
||
|
|
||
|
# 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
|
||
|
LOG.debug('Our machineIdentifier is %s', machineIdentifier)
|
||
|
for device in xml:
|
||
|
identifier = device.attrib.get('clientIdentifier')
|
||
|
LOG.debug('Found a Plex machineIdentifier: %s', identifier)
|
||
|
if identifier == machineIdentifier:
|
||
|
found += 1
|
||
|
token = device.attrib.get('accessToken')
|
||
|
|
||
|
result = {
|
||
|
'username': username,
|
||
|
}
|
||
|
if found == 0:
|
||
|
LOG.info('No tokens found for your server! Using empty string')
|
||
|
result['usertoken'] = ''
|
||
|
else:
|
||
|
result['usertoken'] = token
|
||
|
LOG.info('Plex.tv switch HomeUser change successfull for user %s',
|
||
|
username)
|
||
|
return result
|
||
|
|
||
|
|
||
|
def list_home_users(token):
|
||
|
"""
|
||
|
Returns a list for myPlex home users for the current plex.tv account.
|
||
|
|
||
|
Input:
|
||
|
token for plex.tv
|
||
|
Output:
|
||
|
List of users, where one entry is of the form:
|
||
|
"id": userId,
|
||
|
"admin": '1'/'0',
|
||
|
"guest": '1'/'0',
|
||
|
"restricted": '1'/'0',
|
||
|
"protected": '1'/'0',
|
||
|
"email": email,
|
||
|
"title": title,
|
||
|
"username": username,
|
||
|
"thumb": thumb_url
|
||
|
}
|
||
|
If any value is missing, None is returned instead (or "" from plex.tv)
|
||
|
If an error is encountered, False is returned
|
||
|
"""
|
||
|
xml = DU().downloadUrl('https://plex.tv/api/home/users/',
|
||
|
authenticate=False,
|
||
|
headerOptions={'X-Plex-Token': token})
|
||
|
try:
|
||
|
xml.attrib
|
||
|
except AttributeError:
|
||
|
LOG.error('Download of Plex home users failed.')
|
||
|
return False
|
||
|
users = []
|
||
|
for user in xml:
|
||
|
users.append(user.attrib)
|
||
|
return users
|
||
|
|
||
|
|
||
|
def sign_in_with_pin():
|
||
|
"""
|
||
|
Prompts user to sign in by visiting https://plex.tv/pin
|
||
|
|
||
|
Writes to Kodi settings file. Also returns:
|
||
|
{
|
||
|
'plexhome': 'true' if Plex Home, 'false' otherwise
|
||
|
'username':
|
||
|
'avatar': URL to user avator
|
||
|
'token':
|
||
|
'plexid': Plex user ID
|
||
|
'homesize': Number of Plex home users (defaults to '1')
|
||
|
}
|
||
|
Returns False if authentication did not work.
|
||
|
"""
|
||
|
code, identifier = get_pin()
|
||
|
if not code:
|
||
|
# Problems trying to contact plex.tv. Try again later
|
||
|
dialog('ok', heading='{plex}', line1=lang(39303))
|
||
|
return False
|
||
|
# Go to https://plex.tv/pin and enter the code:
|
||
|
# Or press No to cancel the sign in.
|
||
|
answer = dialog('yesno',
|
||
|
heading='{plex}',
|
||
|
line1=lang(39304) + "\n\n",
|
||
|
line2=code + "\n\n",
|
||
|
line3=lang(39311))
|
||
|
if not answer:
|
||
|
return False
|
||
|
count = 0
|
||
|
# Wait for approx 30 seconds (since the PIN is not visible anymore :-))
|
||
|
while count < 30:
|
||
|
xml = check_pin(identifier)
|
||
|
if xml is not False:
|
||
|
break
|
||
|
# Wait for 1 seconds
|
||
|
sleep(1000)
|
||
|
count += 1
|
||
|
if xml is False:
|
||
|
# Could not sign in to plex.tv Try again later
|
||
|
dialog('ok', heading='{plex}', line1=lang(39305))
|
||
|
return False
|
||
|
# Parse xml
|
||
|
userid = xml.attrib.get('id')
|
||
|
home = xml.get('home', '0')
|
||
|
if home == '1':
|
||
|
home = 'true'
|
||
|
else:
|
||
|
home = 'false'
|
||
|
username = xml.get('username', '')
|
||
|
avatar = xml.get('thumb', '')
|
||
|
token = xml.findtext('authentication-token')
|
||
|
home_size = xml.get('homeSize', '1')
|
||
|
result = {
|
||
|
'plexhome': home,
|
||
|
'username': username,
|
||
|
'avatar': avatar,
|
||
|
'token': token,
|
||
|
'plexid': userid,
|
||
|
'homesize': home_size
|
||
|
}
|
||
|
settings('plexLogin', username)
|
||
|
settings('plexToken', token)
|
||
|
settings('plexhome', home)
|
||
|
settings('plexid', userid)
|
||
|
settings('plexAvatar', avatar)
|
||
|
settings('plexHomeSize', home_size)
|
||
|
# Let Kodi log into plex.tv on startup from now on
|
||
|
settings('myplexlogin', 'true')
|
||
|
settings('plex_status', value=lang(39227))
|
||
|
return result
|
||
|
|
||
|
|
||
|
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
|
||
|
|
||
|
Returns False if not yet done so, or the XML response file as etree
|
||
|
"""
|
||
|
# 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")
|
||
|
return False
|
||
|
if not temp_token:
|
||
|
return False
|
||
|
# 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
|