# -*- coding: utf-8 -*- from logging import getLogger from xbmc import sleep, executebuiltin from utils import window, settings, dialog, language as lang, tryEncode from clientinfo import getXArgsDeviceInfo from downloadutils import DownloadUtils import variables as v import state ############################################################################### log = getLogger("PLEX."+__name__) ############################################################################### def my_plex_sign_in(username, password, options): """ MyPlex Sign In parameters: username - Plex forum name, MyPlex login, or email address password options - dict() of PlexConnect-options as received from aTV - necessary: PlexConnectUDID result: username authtoken - token for subsequent communication with MyPlex """ # create POST request xml = DownloadUtils().downloadUrl( 'https://plex.tv/users/sign_in.xml', action_type='POST', headerOptions=getXArgsDeviceInfo(options), authenticate=False, auth=(username, password)) try: xml.attrib except AttributeError: log.error('Could not sign in to plex.tv') return ('', '') el_username = xml.find('username') el_authtoken = xml.find('authentication-token') if el_username is None or \ el_authtoken is None: username = '' authtoken = '' else: username = el_username.text authtoken = el_authtoken.text return (username, authtoken) def check_plex_tv_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 = DownloadUtils().downloadUrl( 'https://plex.tv/pins/%s.xml' % identifier, authenticate=False) try: temp_token = xml.find('auth_token').text except: 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 = DownloadUtils().downloadUrl('https://plex.tv/users/account', authenticate=False, parameters={'X-Plex-Token': temp_token}) return xml def get_plex_pin(): """ For plex.tv sign-in: returns 4-digit code and identifier as 2 str """ code = None identifier = None # Download xml = DownloadUtils().downloadUrl('https://plex.tv/pins.xml', authenticate=False, action_type="POST") try: xml.attrib except: 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 get_plex_login_password(): """ Signs in to plex.tv. plexLogin, authtoken = get_plex_login_password() Input: nothing Output: plexLogin plex.tv username authtoken token for plex.tv Also writes 'plexLogin' and 'token_plex.tv' to Kodi settings file If not logged in, empty strings are returned for both. """ retrievedPlexLogin = '' plexLogin = 'dummy' authtoken = '' while retrievedPlexLogin == '' and plexLogin != '': # Enter plex.tv username. Or nothing to cancel. plexLogin = dialog('input', lang(29999) + lang(39300), type='{alphanum}') if plexLogin != "": # Enter password for plex.tv user plexPassword = dialog('input', lang(39301) + plexLogin, type='{alphanum}', option='{hide_input}') retrievedPlexLogin, authtoken = my_plex_sign_in( plexLogin, plexPassword, {'X-Plex-Client-Identifier': window('plex_client_Id')}) log.debug("plex.tv username and token: %s, %s" % (plexLogin, authtoken)) if plexLogin == '': # Could not sign in user dialog('ok', lang(29999), lang(39302) + plexLogin) # Write to Kodi settings file settings('plexLogin', value=retrievedPlexLogin) settings('plexToken', value=authtoken) return (retrievedPlexLogin, authtoken) def plex_tv_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_plex_pin() if not code: # Problems trying to contact plex.tv. Try again later dialog('ok', lang(29999), 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', lang(29999), lang(39304) + "\n\n", code + "\n\n", 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_plex_tv_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', lang(29999), 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') homeSize = xml.get('homeSize', '1') result = { 'plexhome': home, 'username': username, 'avatar': avatar, 'token': token, 'plexid': userid, 'homesize': homeSize } settings('plexLogin', username) settings('plexToken', token) settings('plexhome', home) settings('plexid', userid) settings('plexAvatar', avatar) settings('plexHomeSize', homeSize) # Let Kodi log into plex.tv on startup from now on settings('myplexlogin', 'true') settings('plex_status', value=lang(39227)) return result def list_plex_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 = DownloadUtils().downloadUrl('https://plex.tv/api/home/users/', authenticate=False, headerOptions={'X-Plex-Token': token}) try: xml.attrib except: log.error('Download of Plex home users failed.') return False users = [] for user in xml: users.append(user.attrib) return users 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 = DownloadUtils.downloadUrl( url, authenticate=False, action_type="POST", headerOptions={'X-Plex-Token': token}) try: answer.attrib except: 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 = DownloadUtils.downloadUrl(url, authenticate=False, headerOptions={'X-Plex-Token': token}) try: xml.attrib except: 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 in machineIdentifier or machineIdentifier in identifier): 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 ChoosePlexHomeUser(plexToken): """ 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_plex_home_users(plexToken) if not users: log.error("User download failed.") return False userlist = [] userlistCoded = [] for user in users: username = user['title'] userlist.append(username) # To take care of non-ASCII usernames userlistCoded.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), userlistCoded) 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_input}') # 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, plexToken, 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', lang(29999), lang(39308) + selected_user, 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 get_user_artwork_url(username): """ Returns the URL for the user's Avatar. Or False if something went wrong. """ plexToken = settings('plexToken') users = list_plex_home_users(plexToken) url = '' # If an error is encountered, set to False if not users: log.info("Couldnt get user from plex.tv. No URL for user avatar") return False for user in users: if username in user['title']: url = user['thumb'] log.debug("Avatar url for user %s is: %s" % (username, url)) return url