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 from threading import Thread
import Queue import Queue
import traceback import traceback
import requests
import re import re
import json import json
import uuid
try: try:
import xml.etree.cElementTree as etree import xml.etree.cElementTree as etree
@ -112,7 +114,7 @@ class PlexAPI():
Returns (myplexlogin, plexLogin, plexToken) from the Kodi file Returns (myplexlogin, plexLogin, plexToken) from the Kodi file
settings. Returns empty strings if not found. 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') plexLogin = utils.settings('plexLogin')
plexToken = utils.settings('plexToken') plexToken = utils.settings('plexToken')
@ -163,6 +165,153 @@ class PlexAPI():
self.SetPlexLoginToSettings(retrievedPlexLogin, authtoken) self.SetPlexLoginToSettings(retrievedPlexLogin, authtoken)
return (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): def CheckConnection(self, url, token):
""" """
Checks connection to a Plex server, available at url. Can also be used Checks connection to a Plex server, available at url. Can also be used
@ -635,24 +784,21 @@ class PlexAPI():
""" """
# Get addon infos # Get addon infos
xargs = { xargs = {
'User-agent': self.addonName, 'X-Plex-Language': 'en',
'X-Plex-Device': self.deviceName, 'X-Plex-Device': self.addonName,
'X-Plex-Platform': self.platform,
'X-Plex-Client-Platform': self.platform, '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-Product': self.addonName,
'X-Plex-Version': self.plexversion, 'X-Plex-Version': self.plexversion,
'X-Plex-Client-Identifier': self.clientId, 'X-Plex-Client-Identifier': self.clientId,
'machineIdentifier': self.machineIdentifier,
'Connection': 'keep-alive',
'X-Plex-Provides': 'player', 'X-Plex-Provides': 'player',
'Accept': 'application/xml'
} }
try: if self.token:
xargs['X-Plex-Token'] = self.token xargs['X-Plex-Token'] = self.token
except NameError:
# no token needed/saved yet
pass
if JSON: if JSON:
xargs['Accept'] = 'application/json' xargs['Accept'] = 'application/json'
if options: if options:

View file

@ -69,30 +69,26 @@ class ClientInfo():
return "Unknown" return "Unknown"
def getDeviceId(self): 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: if clientId:
return clientId return clientId
addon_path = self.addon.getAddonInfo('path').decode('utf-8') clientId = utils.settings('plex_client_Id')
GUID_file = xbmc.translatePath(os.path.join(addon_path, "machine_guid")).decode('utf-8') if clientId:
utils.window('plex_client_Id', value=clientId)
try: self.logMsg("Unique device Id plex_client_Id loaded: %s" % clientId, 1)
GUID = open(GUID_file) return clientId
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)
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 return clientId

View file

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