Plex.tv sign in with 4 digit PIN
This commit is contained in:
parent
1f8eb7f2ab
commit
6e8bd3e7da
3 changed files with 198 additions and 45 deletions
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 == '':
|
||||
|
|
Loading…
Reference in a new issue