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
|
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:
|
||||||
|
|
|
@ -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.", 0)
|
||||||
self.logMsg("Generating a new deviceid: %s" % e, 1)
|
clientId = str(uuid4())
|
||||||
clientId = str("%012X" % uuid4())
|
utils.settings('plex_client_Id', value=clientId)
|
||||||
GUID = open(GUID_file, 'w')
|
utils.window('plex_client_Id', value=clientId)
|
||||||
GUID.write(clientId)
|
self.logMsg("Unique device Id plex_client_Id loaded: %s" % clientId, 1)
|
||||||
|
|
||||||
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
|
return clientId
|
|
@ -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()
|
||||||
|
if result:
|
||||||
|
plexLogin = result['username']
|
||||||
|
plexToken = result['token']
|
||||||
|
else:
|
||||||
# Exit while loop if user cancels
|
# Exit while loop if user cancels
|
||||||
if plexLogin == '':
|
|
||||||
break
|
break
|
||||||
# Problems connecting
|
# Problems connecting
|
||||||
elif chk == '':
|
elif chk == '':
|
||||||
|
|
Loading…
Reference in a new issue