Plex.tv sign in with 4 digit PIN

This commit is contained in:
tomkat83 2016-01-14 10:20:19 +01:00
parent 1f8eb7f2ab
commit 6e8bd3e7da
3 changed files with 198 additions and 45 deletions

View file

@ -49,9 +49,11 @@ import gzip
from threading import Thread
import Queue
import traceback
import requests
import re
import json
import uuid
try:
import xml.etree.cElementTree as etree
@ -112,7 +114,7 @@ class PlexAPI():
Returns (myplexlogin, plexLogin, plexToken) from the Kodi file
settings. Returns empty strings if not found.
myplexlogin is 'true' if user opted to log into plex.tv
myplexlogin is 'true' if user opted to log into plex.tv (the default)
"""
plexLogin = utils.settings('plexLogin')
plexToken = utils.settings('plexToken')
@ -163,6 +165,153 @@ class PlexAPI():
self.SetPlexLoginToSettings(retrievedPlexLogin, authtoken)
return (retrievedPlexLogin, authtoken)
def PlexTvSignInWithPin(self):
"""
Prompts user to sign in by visiting https://plex.tv/pin
Writes username and token to Kodi settings file. Returns:
{
'home': '1' if Plex Home, '0' otherwise
'username':
'avatar': URL to user avator
'token':
}
Returns False if authentication did not work.
"""
code, identifier = self.GetPlexPin()
dialog = xbmcgui.Dialog()
if not code:
dialog.ok(self.addonName,
'Problems trying to contact plex.tv',
'Try again later')
return False
answer = dialog.yesno(self.addonName,
'Go to https://plex.tv/pin and enter the code:',
'',
code)
if not answer:
return False
count = 0
# Wait for approx 30 seconds (since the PIN is not visible anymore :-))
while count < 6:
xml = self.CheckPlexTvSignin(identifier)
if xml:
break
# Wait for 5 seconds
xbmc.sleep(5000)
count += 1
if not xml:
dialog.ok(self.addonName,
'Could not sign in to plex.tv',
'Try again later')
return False
# Parse xml
home = xml.get('home', '0')
username = xml.get('username', '')
avatar = xml.get('thumb')
token = xml.findtext('authentication-token')
result = {
'home': home,
'username': username,
'avatar': avatar,
'token': token
}
self.SetPlexLoginToSettings(username, token)
return result
def CheckPlexTvSignin(self, 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
url = 'https://plex.tv/pins/%s.xml' % identifier
xml = self.TalkToPlexServer(url, talkType="GET2")
try:
temp_token = xml.find('auth_token').text
except:
self.logMsg("Error: Could not find token in plex.tv answer.", -1)
return False
self.logMsg("temp token from plex.tv is: %s" % temp_token, 2)
if not temp_token:
return False
# Use temp token to get the final plex credentials
url = 'https://plex.tv/users/account?X-Plex-Token=%s' % temp_token
xml = self.TalkToPlexServer(url, talkType="GET")
return xml
def GetPlexPin(self):
"""
For plex.tv sign-in: returns 4-digit code and identifier as 2 str
"""
url = 'https://plex.tv/pins.xml'
code = None
identifier = None
# Download
xml = self.TalkToPlexServer(url, talkType="POST")
if not xml:
return code, identifier
try:
code = xml.find('code').text
identifier = xml.find('id').text
except:
self.logMsg("Error, no PIN from plex.tv provided", -1)
self.logMsg("plex.tv/pin: Code is: %s" % code, 2)
self.logMsg("plex.tv/pin: Identifier is: %s" % identifier, 2)
return code, identifier
def TalkToPlexServer(self, url, talkType="GET", verify=True):
"""
Start request with PMS with url.
Returns the parsed XML answer as an etree object. Or False.
"""
header = self.getXArgsDeviceInfo()
timeout = (3, 10)
try:
if talkType == "GET":
answer = requests.get(url,
headers={},
params=header,
verify=verify,
timeout=timeout)
if talkType == "GET2":
answer = requests.get(url,
headers=header,
params={},
verify=verify,
timeout=timeout)
elif talkType == "POST":
answer = requests.post(url,
data='',
headers=header,
params={},
verify=verify,
timeout=timeout)
except requests.exceptions.ConnectionError as e:
self.logMsg("Server is offline or cannot be reached. Url: %s."
"Header: %s. Error message: %s"
% (url, header, e), -1)
return False
except requests.exceptions.ReadTimeout:
self.logMsg("Server timeout reached for Url %s with header %s"
% (url, header), -1)
return False
# We received an answer from the server, but not as expected.
if answer.status_code >= 400:
self.logMsg("Error, answer from server %s was not as expected. "
"HTTP status code: %s" % (url, answer.status_code), -1)
return False
xml = answer.text.encode('utf-8')
self.logMsg("xml received from server %s: %s" % (url, xml), 2)
try:
xml = etree.fromstring(xml)
except:
self.logMsg("Error parsing XML answer from %s" % url, -1)
return False
return xml
def CheckConnection(self, url, token):
"""
Checks connection to a Plex server, available at url. Can also be used
@ -635,24 +784,21 @@ class PlexAPI():
"""
# Get addon infos
xargs = {
'User-agent': self.addonName,
'X-Plex-Device': self.deviceName,
'X-Plex-Platform': self.platform,
'X-Plex-Language': 'en',
'X-Plex-Device': self.addonName,
'X-Plex-Client-Platform': self.platform,
'X-Plex-Device-Name': self.deviceName,
'X-Plex-Platform': self.addonName,
'X-Plex-Platform-Version': 'no idea',
'X-Plex-Model': 'unknown',
'X-Plex-Product': self.addonName,
'X-Plex-Version': self.plexversion,
'X-Plex-Client-Identifier': self.clientId,
'machineIdentifier': self.machineIdentifier,
'Connection': 'keep-alive',
'X-Plex-Provides': 'player',
'Accept': 'application/xml'
}
try:
if self.token:
xargs['X-Plex-Token'] = self.token
except NameError:
# no token needed/saved yet
pass
if JSON:
xargs['Accept'] = 'application/json'
if options:

View file

@ -69,30 +69,26 @@ class ClientInfo():
return "Unknown"
def getDeviceId(self):
"""
Returns a unique Plex client id "X-Plex-Client-Identifier" from Kodi
settings file.
Also loads Kodi window property 'plex_client_Id'
clientId = utils.window('emby_deviceId')
If id does not exist, create one and save in Kodi settings file.
"""
clientId = utils.window('plex_client_Id')
if clientId:
return clientId
addon_path = self.addon.getAddonInfo('path').decode('utf-8')
GUID_file = xbmc.translatePath(os.path.join(addon_path, "machine_guid")).decode('utf-8')
clientId = utils.settings('plex_client_Id')
if clientId:
utils.window('plex_client_Id', value=clientId)
self.logMsg("Unique device Id plex_client_Id loaded: %s" % clientId, 1)
return clientId
try:
GUID = open(GUID_file)
except Exception as e: # machine_guid does not exists.
self.logMsg("Generating a new deviceid: %s" % e, 1)
clientId = str("%012X" % uuid4())
GUID = open(GUID_file, 'w')
GUID.write(clientId)
else: # machine_guid already exists. Get guid.
clientId = GUID.read()
finally:
GUID.close()
self.logMsg("DeviceId loaded: %s" % clientId, 1)
utils.window('emby_deviceId', value=clientId)
return clientId
self.logMsg("Generating a new deviceid.", 0)
clientId = str(uuid4())
utils.settings('plex_client_Id', value=clientId)
utils.window('plex_client_Id', value=clientId)
self.logMsg("Unique device Id plex_client_Id loaded: %s" % clientId, 1)
return clientId

View file

@ -46,7 +46,7 @@ class InitialSetup():
##### SERVER INFO #####
self.logMsg("Initial setup called.", 2)
self.logMsg("Initial setup called.", 0)
server = self.userClient.getServer()
clientId = self.clientInfo.getDeviceId()
serverid = self.userClient.getServerId()
@ -63,7 +63,10 @@ class InitialSetup():
'Could not login to plex.tv.',
'Please try signing in again.'
)
plexLogin, plexToken = self.plx.GetPlexLoginAndPassword()
result = self.plx.PlexTvSignInWithPin()
if result:
plexLogin = result['username']
plexToken = result['token']
elif chk == "":
dialog = xbmcgui.Dialog()
dialog.ok(
@ -73,18 +76,20 @@ class InitialSetup():
)
# If a Plex server IP has already been set, return.
if server:
self.logMsg("Server is already set.", 2)
self.logMsg("Server is already set.", 0)
self.logMsg(
"url: %s, Plex machineIdentifier: %s"
% (server, serverid),
2
)
0)
return
# If not already retrieved myplex info, optionally let user sign in
# to plex.tv.
if not plexToken and myplexlogin == 'true':
plexLogin, plexToken = self.plx.GetPlexLoginAndPassword()
result = self.plx.PlexTvSignInWithPin()
if result:
plexLogin = result['username']
plexToken = result['token']
# Get g_PMS list of servers (saved to plx.g_PMS)
serverNum = 1
while serverNum > 0:
@ -110,10 +115,14 @@ class InitialSetup():
if serverNum == 0:
break
for server in serverlist:
dialoglist.append(str(server['name']) + ' (IP: ' + str(server['ip']) + ')')
if server['local'] == '1':
# server is in the same network as client
dialoglist.append(str(server['name']) + ' (nearby)')
else:
dialoglist.append(str(server['name']))
dialog = xbmcgui.Dialog()
resp = dialog.select(
'What Plex server would you like to connect to?',
'Plex server to connect to?',
dialoglist)
server = serverlist[resp]
activeServer = server['machineIdentifier']
@ -129,15 +138,17 @@ class InitialSetup():
chk = self.plx.CheckConnection(url, server['accesstoken'])
# Unauthorized
if chk == 401:
dialog = xbmcgui.Dialog()
dialog.ok(
self.addonName,
'Not yet authorized for Plex server %s' % str(server['name']),
'Please sign in to plex.tv.'
)
plexLogin, plexToken = self.plx.GetPlexLoginAndPassword()
# Exit while loop if user cancels
if plexLogin == '':
result = self.plx.PlexTvSignInWithPin()
if result:
plexLogin = result['username']
plexToken = result['token']
else:
# Exit while loop if user cancels
break
# Problems connecting
elif chk == '':