Code refactoring
This commit is contained in:
parent
4fca4ecf63
commit
1151076660
8 changed files with 1086 additions and 1404 deletions
File diff suppressed because it is too large
Load diff
|
@ -1,21 +1,31 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
from urllib import urlencode
|
from urllib import urlencode, quote_plus
|
||||||
from ast import literal_eval
|
from ast import literal_eval
|
||||||
from urlparse import urlparse, parse_qsl
|
from urlparse import urlparse, parse_qsl
|
||||||
import re
|
from re import compile as re_compile
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
from time import time
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
import downloadutils
|
from xbmc import sleep
|
||||||
from utils import settings
|
|
||||||
|
from downloadutils import DownloadUtils as DU
|
||||||
|
from utils import settings, tryEncode, tryDecode
|
||||||
from variables import PLEX_TO_KODI_TIMEFACTOR
|
from variables import PLEX_TO_KODI_TIMEFACTOR
|
||||||
|
import plex_tv
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
LOG = getLogger("PLEX." + __name__)
|
||||||
log = getLogger("PLEX."+__name__)
|
|
||||||
|
|
||||||
CONTAINERSIZE = int(settings('limitindex'))
|
CONTAINERSIZE = int(settings('limitindex'))
|
||||||
REGEX_PLEX_KEY = re.compile(r'''/(.+)/(\d+)$''')
|
REGEX_PLEX_KEY = re_compile(r'''/(.+)/(\d+)$''')
|
||||||
|
|
||||||
|
# For discovery of PMS in the local LAN
|
||||||
|
PLEX_GDM_IP = '239.0.0.250' # multicast to PMS
|
||||||
|
PLEX_GDM_PORT = 32414
|
||||||
|
PLEX_GDM_MSG = 'M-SEARCH * HTTP/1.0'
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
|
||||||
|
@ -76,6 +86,374 @@ def GetMethodFromPlexType(plexType):
|
||||||
return methods[plexType]
|
return methods[plexType]
|
||||||
|
|
||||||
|
|
||||||
|
def GetPlexLoginFromSettings():
|
||||||
|
"""
|
||||||
|
Returns a dict:
|
||||||
|
'plexLogin': settings('plexLogin'),
|
||||||
|
'plexToken': settings('plexToken'),
|
||||||
|
'plexhome': settings('plexhome'),
|
||||||
|
'plexid': settings('plexid'),
|
||||||
|
'myplexlogin': settings('myplexlogin'),
|
||||||
|
'plexAvatar': settings('plexAvatar'),
|
||||||
|
'plexHomeSize': settings('plexHomeSize')
|
||||||
|
|
||||||
|
Returns strings or unicode
|
||||||
|
|
||||||
|
Returns empty strings '' for a setting if not found.
|
||||||
|
|
||||||
|
myplexlogin is 'true' if user opted to log into plex.tv (the default)
|
||||||
|
plexhome is 'true' if plex home is used (the default)
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
'plexLogin': settings('plexLogin'),
|
||||||
|
'plexToken': settings('plexToken'),
|
||||||
|
'plexhome': settings('plexhome'),
|
||||||
|
'plexid': settings('plexid'),
|
||||||
|
'myplexlogin': settings('myplexlogin'),
|
||||||
|
'plexAvatar': settings('plexAvatar'),
|
||||||
|
'plexHomeSize': settings('plexHomeSize')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def check_connection(url, token=None, verifySSL=None):
|
||||||
|
"""
|
||||||
|
Checks connection to a Plex server, available at url. Can also be used
|
||||||
|
to check for connection with plex.tv.
|
||||||
|
|
||||||
|
Override SSL to skip the check by setting verifySSL=False
|
||||||
|
if 'None', SSL will be checked (standard requests setting)
|
||||||
|
if 'True', SSL settings from file settings are used (False/True)
|
||||||
|
|
||||||
|
Input:
|
||||||
|
url URL to Plex server (e.g. https://192.168.1.1:32400)
|
||||||
|
token appropriate token to access server. If None is passed,
|
||||||
|
the current token is used
|
||||||
|
Output:
|
||||||
|
False if server could not be reached or timeout occured
|
||||||
|
200 if connection was successfull
|
||||||
|
int or other HTML status codes as received from the server
|
||||||
|
"""
|
||||||
|
# Add '/clients' to URL because then an authentication is necessary
|
||||||
|
# If a plex.tv URL was passed, this does not work.
|
||||||
|
header_options = None
|
||||||
|
if token is not None:
|
||||||
|
header_options = {'X-Plex-Token': token}
|
||||||
|
if verifySSL is True:
|
||||||
|
verifySSL = None if settings('sslverify') == 'true' else False
|
||||||
|
if 'plex.tv' in url:
|
||||||
|
url = 'https://plex.tv/api/home/users'
|
||||||
|
else:
|
||||||
|
url = url + '/library/onDeck'
|
||||||
|
LOG.debug("Checking connection to server %s with verifySSL=%s",
|
||||||
|
url, verifySSL)
|
||||||
|
answer = DU().downloadUrl(url,
|
||||||
|
authenticate=False,
|
||||||
|
headerOptions=header_options,
|
||||||
|
verifySSL=verifySSL,
|
||||||
|
timeout=10)
|
||||||
|
if answer is None:
|
||||||
|
LOG.debug("Could not connect to %s", url)
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
# xml received?
|
||||||
|
answer.attrib
|
||||||
|
except AttributeError:
|
||||||
|
if answer is True:
|
||||||
|
# Maybe no xml but connection was successful nevertheless
|
||||||
|
answer = 200
|
||||||
|
else:
|
||||||
|
# Success - we downloaded an xml!
|
||||||
|
answer = 200
|
||||||
|
# We could connect but maybe were not authenticated. No worries
|
||||||
|
LOG.debug("Checking connection successfull. Answer: %s", answer)
|
||||||
|
return answer
|
||||||
|
|
||||||
|
|
||||||
|
def discover_pms(token=None):
|
||||||
|
"""
|
||||||
|
Optional parameter:
|
||||||
|
token token for plex.tv
|
||||||
|
|
||||||
|
Returns a list of available PMS to connect to, one entry is the dict:
|
||||||
|
{
|
||||||
|
'machineIdentifier' [str] unique identifier of the PMS
|
||||||
|
'name' [str] name of the PMS
|
||||||
|
'token' [str] token needed to access that PMS
|
||||||
|
'ownername' [str] name of the owner of this PMS or None if
|
||||||
|
the owner itself supplied tries to connect
|
||||||
|
'product' e.g. 'Plex Media Server' or None
|
||||||
|
'version' e.g. '1.11.2.4772-3e...' or None
|
||||||
|
'device': e.g. 'PC' or 'Windows' or None
|
||||||
|
'platform': e.g. 'Windows', 'Android' or None
|
||||||
|
'local' [bool] True if plex.tv supplied
|
||||||
|
'publicAddressMatches'='1'
|
||||||
|
or if found using Plex GDM in the local LAN
|
||||||
|
'owned' [bool] True if it's the owner's PMS
|
||||||
|
'relay' [bool] True if plex.tv supplied 'relay'='1'
|
||||||
|
'presence' [bool] True if plex.tv supplied 'presence'='1'
|
||||||
|
'httpsRequired' [bool] True if plex.tv supplied
|
||||||
|
'httpsRequired'='1'
|
||||||
|
'scheme' [str] either 'http' or 'https'
|
||||||
|
'ip': [str] IP of the PMS, e.g. '192.168.1.1'
|
||||||
|
'port': [str] Port of the PMS, e.g. '32400'
|
||||||
|
'baseURL': [str] <scheme>://<ip>:<port> of the PMS
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
LOG.info('Start discovery of Plex Media Servers')
|
||||||
|
# Look first for local PMS in the LAN
|
||||||
|
local_pms_list = _plex_gdm()
|
||||||
|
LOG.debug('PMS found in the local LAN using Plex GDM: %s', local_pms_list)
|
||||||
|
# Get PMS from plex.tv
|
||||||
|
if token:
|
||||||
|
LOG.info('Checking with plex.tv for more PMS to connect to')
|
||||||
|
plex_pms_list = _pms_list_from_plex_tv(token)
|
||||||
|
LOG.debug('PMS found on plex.tv: %s', plex_pms_list)
|
||||||
|
else:
|
||||||
|
LOG.info('No plex token supplied, only checked LAN for available PMS')
|
||||||
|
plex_pms_list = []
|
||||||
|
|
||||||
|
# See if we found a PMS both locally and using plex.tv. If so, use local
|
||||||
|
# connection data
|
||||||
|
all_pms = []
|
||||||
|
for pms in local_pms_list:
|
||||||
|
for i, plex_pms in enumerate(plex_pms_list):
|
||||||
|
if pms['machineIdentifier'] == plex_pms['machineIdentifier']:
|
||||||
|
# Update with GDM data - potentially more reliable than plex.tv
|
||||||
|
LOG.debug('Found this PMS also in the LAN: %s', plex_pms)
|
||||||
|
plex_pms['ip'] = pms['ip']
|
||||||
|
plex_pms['port'] = pms['port']
|
||||||
|
plex_pms['local'] = True
|
||||||
|
# Use all the other data we know from plex.tv
|
||||||
|
pms = plex_pms
|
||||||
|
# Remove this particular pms since we already know it
|
||||||
|
plex_pms_list.pop(i)
|
||||||
|
break
|
||||||
|
https = _pms_https_enabled('%s:%s' % (pms['ip'], pms['port']))
|
||||||
|
if https is None:
|
||||||
|
# Error contacting url. Skip and ignore this PMS for now
|
||||||
|
continue
|
||||||
|
elif https is True:
|
||||||
|
pms['scheme'] = 'https'
|
||||||
|
pms['baseURL'] = 'https://%s:%s' % (pms['ip'], pms['port'])
|
||||||
|
else:
|
||||||
|
pms['scheme'] = 'http'
|
||||||
|
pms['baseURL'] = 'http://%s:%s' % (pms['ip'], pms['port'])
|
||||||
|
all_pms.append(pms)
|
||||||
|
# Now add the remaining PMS from plex.tv (where we already checked connect.)
|
||||||
|
for plex_pms in plex_pms_list:
|
||||||
|
all_pms.append(plex_pms)
|
||||||
|
LOG.debug('Found the following PMS in total: %s', all_pms)
|
||||||
|
return all_pms
|
||||||
|
|
||||||
|
|
||||||
|
def _plex_gdm():
|
||||||
|
"""
|
||||||
|
PlexGDM - looks for PMS in the local LAN and returns a list of the PMS found
|
||||||
|
"""
|
||||||
|
# Import here because we might not need to do gdm because we already
|
||||||
|
# connected to a PMS successfully in the past
|
||||||
|
import struct
|
||||||
|
import socket
|
||||||
|
|
||||||
|
# setup socket for discovery -> multicast message
|
||||||
|
gdm = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
gdm.settimeout(2.0)
|
||||||
|
# Set the time-to-live for messages to 2 for local network
|
||||||
|
ttl = struct.pack('b', 2)
|
||||||
|
gdm.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl)
|
||||||
|
|
||||||
|
return_data = []
|
||||||
|
try:
|
||||||
|
# Send data to the multicast group
|
||||||
|
gdm.sendto(PLEX_GDM_MSG, (PLEX_GDM_IP, PLEX_GDM_PORT))
|
||||||
|
|
||||||
|
# Look for responses from all recipients
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
data, server = gdm.recvfrom(1024)
|
||||||
|
return_data.append({'from': server, 'data': data})
|
||||||
|
except socket.timeout:
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
# Probably error: (101, 'Network is unreachable')
|
||||||
|
LOG.error(e)
|
||||||
|
import traceback
|
||||||
|
LOG.error("Traceback:\n%s", traceback.format_exc())
|
||||||
|
finally:
|
||||||
|
gdm.close()
|
||||||
|
LOG.debug('Plex GDM returned the data: %s', return_data)
|
||||||
|
pms_list = []
|
||||||
|
for response in return_data:
|
||||||
|
# Check if we had a positive HTTP response
|
||||||
|
if '200 OK' not in response['data']:
|
||||||
|
continue
|
||||||
|
pms = {
|
||||||
|
'ip': response['from'][0],
|
||||||
|
'scheme': None,
|
||||||
|
'local': True, # Since we found it using GDM
|
||||||
|
'product': None,
|
||||||
|
'baseURL': None,
|
||||||
|
'name': None,
|
||||||
|
'version': None,
|
||||||
|
'token': None,
|
||||||
|
'ownername': None,
|
||||||
|
'device': None,
|
||||||
|
'platform': None,
|
||||||
|
'owned': None,
|
||||||
|
'relay': None,
|
||||||
|
'presence': True, # Since we're talking to the PMS
|
||||||
|
'httpsRequired': None,
|
||||||
|
}
|
||||||
|
for line in response['data'].split('\n'):
|
||||||
|
if 'Content-Type:' in line:
|
||||||
|
pms['product'] = tryDecode(line.split(':')[1].strip())
|
||||||
|
elif 'Host:' in line:
|
||||||
|
pms['baseURL'] = line.split(':')[1].strip()
|
||||||
|
elif 'Name:' in line:
|
||||||
|
pms['name'] = tryDecode(line.split(':')[1].strip())
|
||||||
|
elif 'Port:' in line:
|
||||||
|
pms['port'] = line.split(':')[1].strip()
|
||||||
|
elif 'Resource-Identifier:' in line:
|
||||||
|
pms['machineIdentifier'] = line.split(':')[1].strip()
|
||||||
|
elif 'Version:' in line:
|
||||||
|
pms['version'] = line.split(':')[1].strip()
|
||||||
|
pms_list.append(pms)
|
||||||
|
return pms_list
|
||||||
|
|
||||||
|
|
||||||
|
def _pms_list_from_plex_tv(token):
|
||||||
|
"""
|
||||||
|
get Plex media Server List from plex.tv/pms/resources
|
||||||
|
"""
|
||||||
|
xml = DU().downloadUrl('https://plex.tv/api/resources',
|
||||||
|
authenticate=False,
|
||||||
|
parameters={'includeHttps': 1},
|
||||||
|
headerOptions={'X-Plex-Token': token})
|
||||||
|
try:
|
||||||
|
xml.attrib
|
||||||
|
except AttributeError:
|
||||||
|
LOG.error('Could not get list of PMS from plex.tv')
|
||||||
|
return
|
||||||
|
|
||||||
|
from Queue import Queue
|
||||||
|
queue = Queue()
|
||||||
|
thread_queue = []
|
||||||
|
|
||||||
|
max_age_in_seconds = 2*60*60*24
|
||||||
|
for device in xml.findall('Device'):
|
||||||
|
if 'server' not in device.get('provides'):
|
||||||
|
# No PMS - skip
|
||||||
|
continue
|
||||||
|
if device.find('Connection') is None:
|
||||||
|
# no valid connection - skip
|
||||||
|
continue
|
||||||
|
# check MyPlex data age - skip if >2 days
|
||||||
|
info_age = time() - int(device.get('lastSeenAt'))
|
||||||
|
if info_age > max_age_in_seconds:
|
||||||
|
LOG.debug("Skip server %s not seen for 2 days", device.get('name'))
|
||||||
|
continue
|
||||||
|
pms = {
|
||||||
|
'machineIdentifier': device.get('clientIdentifier'),
|
||||||
|
'name': device.get('name'),
|
||||||
|
'token': device.get('accessToken'),
|
||||||
|
'ownername': device.get('sourceTitle'),
|
||||||
|
'product': device.get('product'), # e.g. 'Plex Media Server'
|
||||||
|
'version': device.get('productVersion'), # e.g. '1.11.2.4772-3e...'
|
||||||
|
'device': device.get('device'), # e.g. 'PC' or 'Windows'
|
||||||
|
'platform': device.get('platform'), # e.g. 'Windows', 'Android'
|
||||||
|
'local': device.get('publicAddressMatches') == '1',
|
||||||
|
'owned': device.get('owned') == '1',
|
||||||
|
'relay': device.get('relay') == '1',
|
||||||
|
'presence': device.get('presence') == '1',
|
||||||
|
'httpsRequired': device.get('httpsRequired') == '1',
|
||||||
|
'connections': []
|
||||||
|
}
|
||||||
|
# Try a local connection first, no matter what plex.tv tells us
|
||||||
|
for connection in device.findall('Connection'):
|
||||||
|
if connection.get('local') == '1':
|
||||||
|
pms['connections'].append(connection)
|
||||||
|
# Then try non-local
|
||||||
|
for connection in device.findall('Connection'):
|
||||||
|
if connection.get('local') != '1':
|
||||||
|
pms['connections'].append(connection)
|
||||||
|
# Spawn threads to ping each PMS simultaneously
|
||||||
|
thread = Thread(target=_poke_pms, args=(pms, queue))
|
||||||
|
thread_queue.append(thread)
|
||||||
|
|
||||||
|
max_threads = 5
|
||||||
|
threads = []
|
||||||
|
# poke PMS, own thread for each PMS
|
||||||
|
while True:
|
||||||
|
# Remove finished threads
|
||||||
|
for thread in threads:
|
||||||
|
if not thread.isAlive():
|
||||||
|
threads.remove(thread)
|
||||||
|
if len(threads) < max_threads:
|
||||||
|
try:
|
||||||
|
thread = thread_queue.pop()
|
||||||
|
except IndexError:
|
||||||
|
# We have done our work
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
thread.start()
|
||||||
|
threads.append(thread)
|
||||||
|
else:
|
||||||
|
sleep(50)
|
||||||
|
# wait for requests being answered
|
||||||
|
for thread in threads:
|
||||||
|
thread.join()
|
||||||
|
# declare new PMSs
|
||||||
|
pms_list = []
|
||||||
|
while not queue.empty():
|
||||||
|
pms = queue.get()
|
||||||
|
del pms['connections']
|
||||||
|
pms_list.append(pms)
|
||||||
|
queue.task_done()
|
||||||
|
return pms_list
|
||||||
|
|
||||||
|
|
||||||
|
def _poke_pms(pms, queue):
|
||||||
|
data = pms['connections'][0].attrib
|
||||||
|
if data['local'] == '1':
|
||||||
|
protocol = data['protocol']
|
||||||
|
address = data['address']
|
||||||
|
port = data['port']
|
||||||
|
url = '%s://%s:%s' % (protocol, address, port)
|
||||||
|
else:
|
||||||
|
url = data['uri']
|
||||||
|
if url.count(':') == 1:
|
||||||
|
url = '%s:%s' % (url, data['port'])
|
||||||
|
protocol, address, port = url.split(':', 2)
|
||||||
|
address = address.replace('/', '')
|
||||||
|
xml = DU().downloadUrl('%s/identity' % url,
|
||||||
|
authenticate=False,
|
||||||
|
headerOptions={'X-Plex-Token': pms['token']},
|
||||||
|
verifySSL=False,
|
||||||
|
timeout=10)
|
||||||
|
try:
|
||||||
|
xml.attrib['machineIdentifier']
|
||||||
|
except (AttributeError, KeyError):
|
||||||
|
# No connection, delete the one we just tested
|
||||||
|
del pms['connections'][0]
|
||||||
|
if pms['connections']:
|
||||||
|
# Still got connections left, try them
|
||||||
|
return _poke_pms(pms, queue)
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
# Connection successful - correct pms?
|
||||||
|
if xml.get('machineIdentifier') == pms['machineIdentifier']:
|
||||||
|
# process later
|
||||||
|
pms['baseURL'] = url
|
||||||
|
pms['protocol'] = protocol
|
||||||
|
pms['ip'] = address
|
||||||
|
pms['port'] = port
|
||||||
|
queue.put(pms)
|
||||||
|
return
|
||||||
|
LOG.info('Found a pms at %s, but the expected machineIdentifier of '
|
||||||
|
'%s did not match the one we found: %s',
|
||||||
|
url, pms['uuid'], xml.get('machineIdentifier'))
|
||||||
|
|
||||||
|
|
||||||
def GetPlexMetadata(key):
|
def GetPlexMetadata(key):
|
||||||
"""
|
"""
|
||||||
Returns raw API metadata for key as an etree XML.
|
Returns raw API metadata for key as an etree XML.
|
||||||
|
@ -102,7 +480,7 @@ def GetPlexMetadata(key):
|
||||||
# 'includeConcerts': 1
|
# 'includeConcerts': 1
|
||||||
}
|
}
|
||||||
url = url + '?' + urlencode(arguments)
|
url = url + '?' + urlencode(arguments)
|
||||||
xml = downloadutils.DownloadUtils().downloadUrl(url)
|
xml = DU().downloadUrl(url)
|
||||||
if xml == 401:
|
if xml == 401:
|
||||||
# Either unauthorized (taken care of by doUtils) or PMS under strain
|
# Either unauthorized (taken care of by doUtils) or PMS under strain
|
||||||
return 401
|
return 401
|
||||||
|
@ -111,7 +489,7 @@ def GetPlexMetadata(key):
|
||||||
xml.attrib
|
xml.attrib
|
||||||
# Nope we did not receive a valid XML
|
# Nope we did not receive a valid XML
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
log.error("Error retrieving metadata for %s" % url)
|
LOG.error("Error retrieving metadata for %s", url)
|
||||||
xml = None
|
xml = None
|
||||||
return xml
|
return xml
|
||||||
|
|
||||||
|
@ -153,22 +531,21 @@ def DownloadChunks(url):
|
||||||
"""
|
"""
|
||||||
xml = None
|
xml = None
|
||||||
pos = 0
|
pos = 0
|
||||||
errorCounter = 0
|
error_counter = 0
|
||||||
while errorCounter < 10:
|
while error_counter < 10:
|
||||||
args = {
|
args = {
|
||||||
'X-Plex-Container-Size': CONTAINERSIZE,
|
'X-Plex-Container-Size': CONTAINERSIZE,
|
||||||
'X-Plex-Container-Start': pos
|
'X-Plex-Container-Start': pos
|
||||||
}
|
}
|
||||||
xmlpart = downloadutils.DownloadUtils().downloadUrl(
|
xmlpart = DU().downloadUrl(url + urlencode(args))
|
||||||
url + urlencode(args))
|
|
||||||
# If something went wrong - skip in the hope that it works next time
|
# If something went wrong - skip in the hope that it works next time
|
||||||
try:
|
try:
|
||||||
xmlpart.attrib
|
xmlpart.attrib
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
log.error('Error while downloading chunks: %s'
|
LOG.error('Error while downloading chunks: %s',
|
||||||
% (url + urlencode(args)))
|
url + urlencode(args))
|
||||||
pos += CONTAINERSIZE
|
pos += CONTAINERSIZE
|
||||||
errorCounter += 1
|
error_counter += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Very first run: starting xml (to retain data in xml's root!)
|
# Very first run: starting xml (to retain data in xml's root!)
|
||||||
|
@ -186,8 +563,8 @@ def DownloadChunks(url):
|
||||||
if len(xmlpart) < CONTAINERSIZE:
|
if len(xmlpart) < CONTAINERSIZE:
|
||||||
break
|
break
|
||||||
pos += CONTAINERSIZE
|
pos += CONTAINERSIZE
|
||||||
if errorCounter == 10:
|
if error_counter == 10:
|
||||||
log.error('Fatal error while downloading chunks for %s' % url)
|
LOG.error('Fatal error while downloading chunks for %s', url)
|
||||||
return None
|
return None
|
||||||
return xml
|
return xml
|
||||||
|
|
||||||
|
@ -235,8 +612,7 @@ def get_plex_sections():
|
||||||
"""
|
"""
|
||||||
Returns all Plex sections (libraries) of the PMS as an etree xml
|
Returns all Plex sections (libraries) of the PMS as an etree xml
|
||||||
"""
|
"""
|
||||||
return downloadutils.DownloadUtils().downloadUrl(
|
return DU().downloadUrl('{server}/library/sections')
|
||||||
'{server}/library/sections')
|
|
||||||
|
|
||||||
|
|
||||||
def init_plex_playqueue(itemid, librarySectionUUID, mediatype='movie',
|
def init_plex_playqueue(itemid, librarySectionUUID, mediatype='movie',
|
||||||
|
@ -255,17 +631,16 @@ def init_plex_playqueue(itemid, librarySectionUUID, mediatype='movie',
|
||||||
}
|
}
|
||||||
if trailers is True:
|
if trailers is True:
|
||||||
args['extrasPrefixCount'] = settings('trailerNumber')
|
args['extrasPrefixCount'] = settings('trailerNumber')
|
||||||
xml = downloadutils.DownloadUtils().downloadUrl(
|
xml = DU().downloadUrl(url + '?' + urlencode(args), action_type="POST")
|
||||||
url + '?' + urlencode(args), action_type="POST")
|
|
||||||
try:
|
try:
|
||||||
xml[0].tag
|
xml[0].tag
|
||||||
except (IndexError, TypeError, AttributeError):
|
except (IndexError, TypeError, AttributeError):
|
||||||
log.error("Error retrieving metadata for %s" % url)
|
LOG.error("Error retrieving metadata for %s", url)
|
||||||
return None
|
return None
|
||||||
return xml
|
return xml
|
||||||
|
|
||||||
|
|
||||||
def PMSHttpsEnabled(url):
|
def _pms_https_enabled(url):
|
||||||
"""
|
"""
|
||||||
Returns True if the PMS can talk https, False otherwise.
|
Returns True if the PMS can talk https, False otherwise.
|
||||||
None if error occured, e.g. the connection timed out
|
None if error occured, e.g. the connection timed out
|
||||||
|
@ -277,21 +652,20 @@ def PMSHttpsEnabled(url):
|
||||||
|
|
||||||
Prefers HTTPS over HTTP
|
Prefers HTTPS over HTTP
|
||||||
"""
|
"""
|
||||||
doUtils = downloadutils.DownloadUtils().downloadUrl
|
res = DU().downloadUrl('https://%s/identity' % url,
|
||||||
res = doUtils('https://%s/identity' % url,
|
|
||||||
authenticate=False,
|
authenticate=False,
|
||||||
verifySSL=False)
|
verifySSL=False)
|
||||||
try:
|
try:
|
||||||
res.attrib
|
res.attrib
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# Might have SSL deactivated. Try with http
|
# Might have SSL deactivated. Try with http
|
||||||
res = doUtils('http://%s/identity' % url,
|
res = DU().downloadUrl('http://%s/identity' % url,
|
||||||
authenticate=False,
|
authenticate=False,
|
||||||
verifySSL=False)
|
verifySSL=False)
|
||||||
try:
|
try:
|
||||||
res.attrib
|
res.attrib
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
log.error("Could not contact PMS %s" % url)
|
LOG.error("Could not contact PMS %s", url)
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
# Received a valid XML. Server wants to talk HTTP
|
# Received a valid XML. Server wants to talk HTTP
|
||||||
|
@ -307,17 +681,17 @@ def GetMachineIdentifier(url):
|
||||||
|
|
||||||
Returns None if something went wrong
|
Returns None if something went wrong
|
||||||
"""
|
"""
|
||||||
xml = downloadutils.DownloadUtils().downloadUrl('%s/identity' % url,
|
xml = DU().downloadUrl('%s/identity' % url,
|
||||||
authenticate=False,
|
authenticate=False,
|
||||||
verifySSL=False,
|
verifySSL=False,
|
||||||
timeout=10)
|
timeout=10)
|
||||||
try:
|
try:
|
||||||
machineIdentifier = xml.attrib['machineIdentifier']
|
machineIdentifier = xml.attrib['machineIdentifier']
|
||||||
except (AttributeError, KeyError):
|
except (AttributeError, KeyError):
|
||||||
log.error('Could not get the PMS machineIdentifier for %s' % url)
|
LOG.error('Could not get the PMS machineIdentifier for %s', url)
|
||||||
return None
|
return None
|
||||||
log.debug('Found machineIdentifier %s for the PMS %s'
|
LOG.debug('Found machineIdentifier %s for the PMS %s',
|
||||||
% (machineIdentifier, url))
|
machineIdentifier, url)
|
||||||
return machineIdentifier
|
return machineIdentifier
|
||||||
|
|
||||||
|
|
||||||
|
@ -337,8 +711,7 @@ def GetPMSStatus(token):
|
||||||
or an empty dict.
|
or an empty dict.
|
||||||
"""
|
"""
|
||||||
answer = {}
|
answer = {}
|
||||||
xml = downloadutils.DownloadUtils().downloadUrl(
|
xml = DU().downloadUrl('{server}/status/sessions',
|
||||||
'{server}/status/sessions',
|
|
||||||
headerOptions={'X-Plex-Token': token})
|
headerOptions={'X-Plex-Token': token})
|
||||||
try:
|
try:
|
||||||
xml.attrib
|
xml.attrib
|
||||||
|
@ -377,8 +750,8 @@ def scrobble(ratingKey, state):
|
||||||
url = "{server}/:/unscrobble?" + urlencode(args)
|
url = "{server}/:/unscrobble?" + urlencode(args)
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
downloadutils.DownloadUtils().downloadUrl(url)
|
DU().downloadUrl(url)
|
||||||
log.info("Toggled watched state for Plex item %s" % ratingKey)
|
LOG.info("Toggled watched state for Plex item %s", ratingKey)
|
||||||
|
|
||||||
|
|
||||||
def delete_item_from_pms(plexid):
|
def delete_item_from_pms(plexid):
|
||||||
|
@ -388,24 +761,76 @@ def delete_item_from_pms(plexid):
|
||||||
|
|
||||||
Returns True if successful, False otherwise
|
Returns True if successful, False otherwise
|
||||||
"""
|
"""
|
||||||
if downloadutils.DownloadUtils().downloadUrl(
|
if DU().downloadUrl('{server}/library/metadata/%s' % plexid,
|
||||||
'{server}/library/metadata/%s' % plexid,
|
|
||||||
action_type="DELETE") is True:
|
action_type="DELETE") is True:
|
||||||
log.info('Successfully deleted Plex id %s from the PMS' % plexid)
|
LOG.info('Successfully deleted Plex id %s from the PMS', plexid)
|
||||||
return True
|
return True
|
||||||
else:
|
LOG.error('Could not delete Plex id %s from the PMS', plexid)
|
||||||
log.error('Could not delete Plex id %s from the PMS' % plexid)
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def get_PMS_settings(url, token):
|
def get_PMS_settings(url, token):
|
||||||
"""
|
"""
|
||||||
Retrieve the PMS' settings via <url>/:/
|
Retrieve the PMS' settings via <url>/:/prefs
|
||||||
|
|
||||||
Call with url: scheme://ip:port
|
Call with url: scheme://ip:port
|
||||||
"""
|
"""
|
||||||
return downloadutils.DownloadUtils().downloadUrl(
|
return DU().downloadUrl(
|
||||||
'%s/:/prefs' % url,
|
'%s/:/prefs' % url,
|
||||||
authenticate=False,
|
authenticate=False,
|
||||||
verifySSL=False,
|
verifySSL=False,
|
||||||
headerOptions={'X-Plex-Token': token} if token else None)
|
headerOptions={'X-Plex-Token': token} if token else None)
|
||||||
|
|
||||||
|
|
||||||
|
def GetUserArtworkURL(username):
|
||||||
|
"""
|
||||||
|
Returns the URL for the user's Avatar. Or False if something went
|
||||||
|
wrong.
|
||||||
|
"""
|
||||||
|
users = plex_tv.list_home_users(settings('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
|
||||||
|
|
||||||
|
|
||||||
|
def transcode_image_path(key, AuthToken, path, width, height):
|
||||||
|
"""
|
||||||
|
Transcode Image support
|
||||||
|
|
||||||
|
parameters:
|
||||||
|
key
|
||||||
|
AuthToken
|
||||||
|
path - source path of current XML: path[srcXML]
|
||||||
|
width
|
||||||
|
height
|
||||||
|
result:
|
||||||
|
final path to image file
|
||||||
|
"""
|
||||||
|
# external address - can we get a transcoding request for external images?
|
||||||
|
if key.startswith('http://') or key.startswith('https://'):
|
||||||
|
path = key
|
||||||
|
elif key.startswith('/'): # internal full path.
|
||||||
|
path = 'http://127.0.0.1:32400' + key
|
||||||
|
else: # internal path, add-on
|
||||||
|
path = 'http://127.0.0.1:32400' + path + '/' + key
|
||||||
|
path = tryEncode(path)
|
||||||
|
# This is bogus (note the extra path component) but ATV is stupid when it
|
||||||
|
# comes to caching images, it doesn't use querystrings. Fortunately PMS is
|
||||||
|
# lenient...
|
||||||
|
transcode_path = ('/photo/:/transcode/%sx%s/%s'
|
||||||
|
% (width, height, quote_plus(path)))
|
||||||
|
args = {
|
||||||
|
'width': width,
|
||||||
|
'height': height,
|
||||||
|
'url': path
|
||||||
|
}
|
||||||
|
if AuthToken:
|
||||||
|
args['X-Plex-Token'] = AuthToken
|
||||||
|
return transcode_path + '?' + urlencode(args)
|
||||||
|
|
|
@ -40,7 +40,7 @@ def chooseServer():
|
||||||
|
|
||||||
import initialsetup
|
import initialsetup
|
||||||
setup = initialsetup.InitialSetup()
|
setup = initialsetup.InitialSetup()
|
||||||
server = setup.PickPMS(showDialog=True)
|
server = setup.pick_pms(showDialog=True)
|
||||||
if server is None:
|
if server is None:
|
||||||
log.error('We did not connect to a new PMS, aborting')
|
log.error('We did not connect to a new PMS, aborting')
|
||||||
plex_command('SUSPEND_USER_CLIENT', 'False')
|
plex_command('SUSPEND_USER_CLIENT', 'False')
|
||||||
|
@ -48,7 +48,7 @@ def chooseServer():
|
||||||
return
|
return
|
||||||
|
|
||||||
log.info("User chose server %s" % server['name'])
|
log.info("User chose server %s" % server['name'])
|
||||||
setup.WritePMStoSettings(server)
|
setup.write_pms_to_settings(server)
|
||||||
|
|
||||||
if not __LogOut():
|
if not __LogOut():
|
||||||
return
|
return
|
||||||
|
@ -87,7 +87,7 @@ def togglePlexTV():
|
||||||
else:
|
else:
|
||||||
log.info('Login to plex.tv')
|
log.info('Login to plex.tv')
|
||||||
import initialsetup
|
import initialsetup
|
||||||
initialsetup.InitialSetup().PlexTVSignIn()
|
initialsetup.InitialSetup().plex_tv_sign_in()
|
||||||
dialog('notification',
|
dialog('notification',
|
||||||
lang(29999),
|
lang(29999),
|
||||||
lang(39221),
|
lang(39221),
|
||||||
|
|
|
@ -4,17 +4,16 @@ from logging import getLogger
|
||||||
from Queue import Queue
|
from Queue import Queue
|
||||||
import xml.etree.ElementTree as etree
|
import xml.etree.ElementTree as etree
|
||||||
|
|
||||||
import xbmc
|
from xbmc import executebuiltin, translatePath
|
||||||
import xbmcgui
|
|
||||||
|
|
||||||
from utils import settings, window, language as lang, tryEncode, tryDecode, \
|
from utils import settings, window, language as lang, tryEncode, tryDecode, \
|
||||||
XmlKodiSetting, reboot_kodi
|
XmlKodiSetting, reboot_kodi, dialog
|
||||||
from migration import check_migration
|
from migration import check_migration
|
||||||
from downloadutils import DownloadUtils as DU
|
from downloadutils import DownloadUtils as DU
|
||||||
from userclient import UserClient
|
from userclient import UserClient
|
||||||
from clientinfo import getDeviceId
|
from clientinfo import getDeviceId
|
||||||
from PlexAPI import PlexAPI
|
import PlexFunctions as PF
|
||||||
from PlexFunctions import GetMachineIdentifier, get_PMS_settings
|
import plex_tv
|
||||||
from json_rpc import get_setting, set_setting
|
from json_rpc import get_setting, set_setting
|
||||||
import playqueue as PQ
|
import playqueue as PQ
|
||||||
from videonodes import VideoNodes
|
from videonodes import VideoNodes
|
||||||
|
@ -77,7 +76,7 @@ def reload_pkc():
|
||||||
set_webserver()
|
set_webserver()
|
||||||
# To detect Kodi profile switches
|
# To detect Kodi profile switches
|
||||||
window('plex_kodiProfile',
|
window('plex_kodiProfile',
|
||||||
value=tryDecode(xbmc.translatePath("special://profile")))
|
value=tryDecode(translatePath("special://profile")))
|
||||||
getDeviceId()
|
getDeviceId()
|
||||||
# Initialize the PKC playqueues
|
# Initialize the PKC playqueues
|
||||||
PQ.init_playqueues()
|
PQ.init_playqueues()
|
||||||
|
@ -115,188 +114,12 @@ def set_webserver():
|
||||||
state.WEBSERVER_PASSWORD = get_setting('services.webserverpassword')
|
state.WEBSERVER_PASSWORD = get_setting('services.webserverpassword')
|
||||||
|
|
||||||
|
|
||||||
class InitialSetup():
|
def _write_pms_settings(url, token):
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
LOG.debug('Entering initialsetup class')
|
|
||||||
self.plx = PlexAPI()
|
|
||||||
self.dialog = xbmcgui.Dialog()
|
|
||||||
|
|
||||||
self.server = UserClient().getServer()
|
|
||||||
self.serverid = settings('plex_machineIdentifier')
|
|
||||||
# Get Plex credentials from settings file, if they exist
|
|
||||||
plexdict = self.plx.GetPlexLoginFromSettings()
|
|
||||||
self.myplexlogin = plexdict['myplexlogin'] == 'true'
|
|
||||||
self.plexLogin = plexdict['plexLogin']
|
|
||||||
self.plexToken = plexdict['plexToken']
|
|
||||||
self.plexid = plexdict['plexid']
|
|
||||||
# Token for the PMS, not plex.tv
|
|
||||||
self.pms_token = settings('accessToken')
|
|
||||||
if self.plexToken:
|
|
||||||
LOG.debug('Found a plex.tv token in the settings')
|
|
||||||
|
|
||||||
def PlexTVSignIn(self):
|
|
||||||
"""
|
|
||||||
Signs (freshly) in to plex.tv (will be saved to file settings)
|
|
||||||
|
|
||||||
Returns True if successful, or False if not
|
|
||||||
"""
|
|
||||||
result = self.plx.PlexTvSignInWithPin()
|
|
||||||
if result:
|
|
||||||
self.plexLogin = result['username']
|
|
||||||
self.plexToken = result['token']
|
|
||||||
self.plexid = result['plexid']
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def CheckPlexTVSignIn(self):
|
|
||||||
"""
|
|
||||||
Checks existing connection to plex.tv. If not, triggers sign in
|
|
||||||
|
|
||||||
Returns True if signed in, False otherwise
|
|
||||||
"""
|
|
||||||
answer = True
|
|
||||||
chk = self.plx.CheckConnection('plex.tv', token=self.plexToken)
|
|
||||||
if chk in (401, 403):
|
|
||||||
# HTTP Error: unauthorized. Token is no longer valid
|
|
||||||
LOG.info('plex.tv connection returned HTTP %s', str(chk))
|
|
||||||
# Delete token in the settings
|
|
||||||
settings('plexToken', value='')
|
|
||||||
settings('plexLogin', value='')
|
|
||||||
# Could not login, please try again
|
|
||||||
self.dialog.ok(lang(29999), lang(39009))
|
|
||||||
answer = self.PlexTVSignIn()
|
|
||||||
elif chk is False or chk >= 400:
|
|
||||||
# Problems connecting to plex.tv. Network or internet issue?
|
|
||||||
LOG.info('Problems connecting to plex.tv; connection returned '
|
|
||||||
'HTTP %s', str(chk))
|
|
||||||
self.dialog.ok(lang(29999), lang(39010))
|
|
||||||
answer = False
|
|
||||||
else:
|
|
||||||
LOG.info('plex.tv connection with token successful')
|
|
||||||
settings('plex_status', value=lang(39227))
|
|
||||||
# Refresh the info from Plex.tv
|
|
||||||
xml = DU().downloadUrl('https://plex.tv/users/account',
|
|
||||||
authenticate=False,
|
|
||||||
headerOptions={'X-Plex-Token': self.plexToken})
|
|
||||||
try:
|
|
||||||
self.plexLogin = xml.attrib['title']
|
|
||||||
except (AttributeError, KeyError):
|
|
||||||
LOG.error('Failed to update Plex info from plex.tv')
|
|
||||||
else:
|
|
||||||
settings('plexLogin', value=self.plexLogin)
|
|
||||||
home = 'true' if xml.attrib.get('home') == '1' else 'false'
|
|
||||||
settings('plexhome', value=home)
|
|
||||||
settings('plexAvatar', value=xml.attrib.get('thumb'))
|
|
||||||
settings('plexHomeSize', value=xml.attrib.get('homeSize', '1'))
|
|
||||||
LOG.info('Updated Plex info from plex.tv')
|
|
||||||
return answer
|
|
||||||
|
|
||||||
def CheckPMS(self):
|
|
||||||
"""
|
|
||||||
Check the PMS that was set in file settings.
|
|
||||||
Will return False if we need to reconnect, because:
|
|
||||||
PMS could not be reached (no matter the authorization)
|
|
||||||
machineIdentifier did not match
|
|
||||||
|
|
||||||
Will also set the PMS machineIdentifier in the file settings if it was
|
|
||||||
not set before
|
|
||||||
"""
|
|
||||||
answer = True
|
|
||||||
chk = self.plx.CheckConnection(self.server, verifySSL=False)
|
|
||||||
if chk is False:
|
|
||||||
LOG.warn('Could not reach PMS %s', self.server)
|
|
||||||
answer = False
|
|
||||||
if answer is True and not self.serverid:
|
|
||||||
LOG.info('No PMS machineIdentifier found for %s. Trying to '
|
|
||||||
'get the PMS unique ID', self.server)
|
|
||||||
self.serverid = GetMachineIdentifier(self.server)
|
|
||||||
if self.serverid is None:
|
|
||||||
LOG.warn('Could not retrieve machineIdentifier')
|
|
||||||
answer = False
|
|
||||||
else:
|
|
||||||
settings('plex_machineIdentifier', value=self.serverid)
|
|
||||||
elif answer is True:
|
|
||||||
tempServerid = GetMachineIdentifier(self.server)
|
|
||||||
if tempServerid != self.serverid:
|
|
||||||
LOG.warn('The current PMS %s was expected to have a '
|
|
||||||
'unique machineIdentifier of %s. But we got '
|
|
||||||
'%s. Pick a new server to be sure',
|
|
||||||
self.server, self.serverid, tempServerid)
|
|
||||||
answer = False
|
|
||||||
return answer
|
|
||||||
|
|
||||||
def _getServerList(self):
|
|
||||||
"""
|
|
||||||
Returns a list of servers from GDM and possibly plex.tv
|
|
||||||
"""
|
|
||||||
self.plx.discoverPMS(xbmc.getIPAddress(),
|
|
||||||
plexToken=self.plexToken)
|
|
||||||
serverlist = self.plx.returnServerList(self.plx.g_PMS)
|
|
||||||
LOG.debug('PMS serverlist: %s', serverlist)
|
|
||||||
return serverlist
|
|
||||||
|
|
||||||
def _checkServerCon(self, server):
|
|
||||||
"""
|
|
||||||
Checks for server's connectivity. Returns CheckConnection result
|
|
||||||
"""
|
|
||||||
# Re-direct via plex if remote - will lead to the correct SSL
|
|
||||||
# certificate
|
|
||||||
if server['local'] == '1':
|
|
||||||
url = '%s://%s:%s' \
|
|
||||||
% (server['scheme'], server['ip'], server['port'])
|
|
||||||
# Deactive SSL verification if the server is local!
|
|
||||||
verifySSL = False
|
|
||||||
else:
|
|
||||||
url = server['baseURL']
|
|
||||||
verifySSL = True
|
|
||||||
chk = self.plx.CheckConnection(url,
|
|
||||||
token=server['accesstoken'],
|
|
||||||
verifySSL=verifySSL)
|
|
||||||
return chk
|
|
||||||
|
|
||||||
def PickPMS(self, showDialog=False):
|
|
||||||
"""
|
|
||||||
Searches for PMS in local Lan and optionally (if self.plexToken set)
|
|
||||||
also on plex.tv
|
|
||||||
showDialog=True: let the user pick one
|
|
||||||
showDialog=False: automatically pick PMS based on machineIdentifier
|
|
||||||
|
|
||||||
Returns the picked PMS' detail as a dict:
|
|
||||||
{
|
|
||||||
'name': friendlyName, the Plex server's name
|
|
||||||
'address': ip:port
|
|
||||||
'ip': ip, without http/https
|
|
||||||
'port': port
|
|
||||||
'scheme': 'http'/'https', nice for checking for secure connections
|
|
||||||
'local': '1'/'0', Is the server a local server?
|
|
||||||
'owned': '1'/'0', Is the server owned by the user?
|
|
||||||
'machineIdentifier': id, Plex server machine identifier
|
|
||||||
'accesstoken': token Access token to this server
|
|
||||||
'baseURL': baseURL scheme://ip:port
|
|
||||||
'ownername' Plex username of PMS owner
|
|
||||||
}
|
|
||||||
|
|
||||||
or None if unsuccessful
|
|
||||||
"""
|
|
||||||
server = None
|
|
||||||
# If no server is set, let user choose one
|
|
||||||
if not self.server or not self.serverid:
|
|
||||||
showDialog = True
|
|
||||||
if showDialog is True:
|
|
||||||
server = self._UserPickPMS()
|
|
||||||
else:
|
|
||||||
server = self._AutoPickPMS()
|
|
||||||
if server is not None:
|
|
||||||
self._write_PMS_settings(server['baseURL'], server['accesstoken'])
|
|
||||||
return server
|
|
||||||
|
|
||||||
def _write_PMS_settings(self, url, token):
|
|
||||||
"""
|
"""
|
||||||
Sets certain settings for server by asking for the PMS' settings
|
Sets certain settings for server by asking for the PMS' settings
|
||||||
Call with url: scheme://ip:port
|
Call with url: scheme://ip:port
|
||||||
"""
|
"""
|
||||||
xml = get_PMS_settings(url, token)
|
xml = PF.get_PMS_settings(url, token)
|
||||||
try:
|
try:
|
||||||
xml.attrib
|
xml.attrib
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
@ -309,19 +132,196 @@ class InitialSetup():
|
||||||
window('plex_allows_mediaDeletion',
|
window('plex_allows_mediaDeletion',
|
||||||
value=entry.attrib.get('value', 'true'))
|
value=entry.attrib.get('value', 'true'))
|
||||||
|
|
||||||
def _AutoPickPMS(self):
|
|
||||||
|
class InitialSetup(object):
|
||||||
|
"""
|
||||||
|
Will load Plex PMS settings (e.g. address) and token
|
||||||
|
Will ask the user initial questions on first PKC boot
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
LOG.debug('Entering initialsetup class')
|
||||||
|
self.server = UserClient().getServer()
|
||||||
|
self.serverid = settings('plex_machineIdentifier')
|
||||||
|
# Get Plex credentials from settings file, if they exist
|
||||||
|
plexdict = PF.GetPlexLoginFromSettings()
|
||||||
|
self.myplexlogin = plexdict['myplexlogin'] == 'true'
|
||||||
|
self.plex_login = plexdict['plexLogin']
|
||||||
|
self.plex_token = plexdict['plexToken']
|
||||||
|
self.plexid = plexdict['plexid']
|
||||||
|
# Token for the PMS, not plex.tv
|
||||||
|
self.pms_token = settings('accessToken')
|
||||||
|
if self.plex_token:
|
||||||
|
LOG.debug('Found a plex.tv token in the settings')
|
||||||
|
|
||||||
|
def plex_tv_sign_in(self):
|
||||||
|
"""
|
||||||
|
Signs (freshly) in to plex.tv (will be saved to file settings)
|
||||||
|
|
||||||
|
Returns True if successful, or False if not
|
||||||
|
"""
|
||||||
|
result = plex_tv.sign_in_with_pin()
|
||||||
|
if result:
|
||||||
|
self.plex_login = result['username']
|
||||||
|
self.plex_token = result['token']
|
||||||
|
self.plexid = result['plexid']
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def check_plex_tv_sign_in(self):
|
||||||
|
"""
|
||||||
|
Checks existing connection to plex.tv. If not, triggers sign in
|
||||||
|
|
||||||
|
Returns True if signed in, False otherwise
|
||||||
|
"""
|
||||||
|
answer = True
|
||||||
|
chk = PF.check_connection('plex.tv', token=self.plex_token)
|
||||||
|
if chk in (401, 403):
|
||||||
|
# HTTP Error: unauthorized. Token is no longer valid
|
||||||
|
LOG.info('plex.tv connection returned HTTP %s', str(chk))
|
||||||
|
# Delete token in the settings
|
||||||
|
settings('plexToken', value='')
|
||||||
|
settings('plexLogin', value='')
|
||||||
|
# Could not login, please try again
|
||||||
|
dialog('ok', lang(29999), lang(39009))
|
||||||
|
answer = self.plex_tv_sign_in()
|
||||||
|
elif chk is False or chk >= 400:
|
||||||
|
# Problems connecting to plex.tv. Network or internet issue?
|
||||||
|
LOG.info('Problems connecting to plex.tv; connection returned '
|
||||||
|
'HTTP %s', str(chk))
|
||||||
|
dialog('ok', lang(29999), lang(39010))
|
||||||
|
answer = False
|
||||||
|
else:
|
||||||
|
LOG.info('plex.tv connection with token successful')
|
||||||
|
settings('plex_status', value=lang(39227))
|
||||||
|
# Refresh the info from Plex.tv
|
||||||
|
xml = DU().downloadUrl('https://plex.tv/users/account',
|
||||||
|
authenticate=False,
|
||||||
|
headerOptions={'X-Plex-Token': self.plex_token})
|
||||||
|
try:
|
||||||
|
self.plex_login = xml.attrib['title']
|
||||||
|
except (AttributeError, KeyError):
|
||||||
|
LOG.error('Failed to update Plex info from plex.tv')
|
||||||
|
else:
|
||||||
|
settings('plexLogin', value=self.plex_login)
|
||||||
|
home = 'true' if xml.attrib.get('home') == '1' else 'false'
|
||||||
|
settings('plexhome', value=home)
|
||||||
|
settings('plexAvatar', value=xml.attrib.get('thumb'))
|
||||||
|
settings('plexHomeSize', value=xml.attrib.get('homeSize', '1'))
|
||||||
|
LOG.info('Updated Plex info from plex.tv')
|
||||||
|
return answer
|
||||||
|
|
||||||
|
def check_existing_pms(self):
|
||||||
|
"""
|
||||||
|
Check the PMS that was set in file settings.
|
||||||
|
Will return False if we need to reconnect, because:
|
||||||
|
PMS could not be reached (no matter the authorization)
|
||||||
|
machineIdentifier did not match
|
||||||
|
|
||||||
|
Will also set the PMS machineIdentifier in the file settings if it was
|
||||||
|
not set before
|
||||||
|
"""
|
||||||
|
answer = True
|
||||||
|
chk = PF.check_connection(self.server, verifySSL=False)
|
||||||
|
if chk is False:
|
||||||
|
LOG.warn('Could not reach PMS %s', self.server)
|
||||||
|
answer = False
|
||||||
|
if answer is True and not self.serverid:
|
||||||
|
LOG.info('No PMS machineIdentifier found for %s. Trying to '
|
||||||
|
'get the PMS unique ID', self.server)
|
||||||
|
self.serverid = PF.GetMachineIdentifier(self.server)
|
||||||
|
if self.serverid is None:
|
||||||
|
LOG.warn('Could not retrieve machineIdentifier')
|
||||||
|
answer = False
|
||||||
|
else:
|
||||||
|
settings('plex_machineIdentifier', value=self.serverid)
|
||||||
|
elif answer is True:
|
||||||
|
temp_server_id = PF.GetMachineIdentifier(self.server)
|
||||||
|
if temp_server_id != self.serverid:
|
||||||
|
LOG.warn('The current PMS %s was expected to have a '
|
||||||
|
'unique machineIdentifier of %s. But we got '
|
||||||
|
'%s. Pick a new server to be sure',
|
||||||
|
self.server, self.serverid, temp_server_id)
|
||||||
|
answer = False
|
||||||
|
return answer
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _check_pms_connectivity(server):
|
||||||
|
"""
|
||||||
|
Checks for server's connectivity. Returns check_connection result
|
||||||
|
"""
|
||||||
|
# Re-direct via plex if remote - will lead to the correct SSL
|
||||||
|
# certificate
|
||||||
|
if server['local']:
|
||||||
|
url = ('%s://%s:%s'
|
||||||
|
% (server['scheme'], server['ip'], server['port']))
|
||||||
|
# Deactive SSL verification if the server is local!
|
||||||
|
verifySSL = False
|
||||||
|
else:
|
||||||
|
url = server['baseURL']
|
||||||
|
verifySSL = True
|
||||||
|
chk = PF.check_connection(url,
|
||||||
|
token=server['token'],
|
||||||
|
verifySSL=verifySSL)
|
||||||
|
return chk
|
||||||
|
|
||||||
|
def pick_pms(self, showDialog=False):
|
||||||
|
"""
|
||||||
|
Searches for PMS in local Lan and optionally (if self.plex_token set)
|
||||||
|
also on plex.tv
|
||||||
|
showDialog=True: let the user pick one
|
||||||
|
showDialog=False: automatically pick PMS based on machineIdentifier
|
||||||
|
|
||||||
|
Returns the picked PMS' detail as a dict:
|
||||||
|
{
|
||||||
|
'machineIdentifier' [str] unique identifier of the PMS
|
||||||
|
'name' [str] name of the PMS
|
||||||
|
'token' [str] token needed to access that PMS
|
||||||
|
'ownername' [str] name of the owner of this PMS or None if
|
||||||
|
the owner itself supplied tries to connect
|
||||||
|
'product' e.g. 'Plex Media Server' or None
|
||||||
|
'version' e.g. '1.11.2.4772-3e...' or None
|
||||||
|
'device': e.g. 'PC' or 'Windows' or None
|
||||||
|
'platform': e.g. 'Windows', 'Android' or None
|
||||||
|
'local' [bool] True if plex.tv supplied
|
||||||
|
'publicAddressMatches'='1'
|
||||||
|
or if found using Plex GDM in the local LAN
|
||||||
|
'owned' [bool] True if it's the owner's PMS
|
||||||
|
'relay' [bool] True if plex.tv supplied 'relay'='1'
|
||||||
|
'presence' [bool] True if plex.tv supplied 'presence'='1'
|
||||||
|
'httpsRequired' [bool] True if plex.tv supplied
|
||||||
|
'httpsRequired'='1'
|
||||||
|
'scheme' [str] either 'http' or 'https'
|
||||||
|
'ip': [str] IP of the PMS, e.g. '192.168.1.1'
|
||||||
|
'port': [str] Port of the PMS, e.g. '32400'
|
||||||
|
'baseURL': [str] <scheme>://<ip>:<port> of the PMS
|
||||||
|
}
|
||||||
|
or None if unsuccessful
|
||||||
|
"""
|
||||||
|
server = None
|
||||||
|
# If no server is set, let user choose one
|
||||||
|
if not self.server or not self.serverid:
|
||||||
|
showDialog = True
|
||||||
|
if showDialog is True:
|
||||||
|
server = self._user_pick_pms()
|
||||||
|
else:
|
||||||
|
server = self._auto_pick_pms()
|
||||||
|
if server is not None:
|
||||||
|
_write_pms_settings(server['baseURL'], server['token'])
|
||||||
|
return server
|
||||||
|
|
||||||
|
def _auto_pick_pms(self):
|
||||||
"""
|
"""
|
||||||
Will try to pick PMS based on machineIdentifier saved in file settings
|
Will try to pick PMS based on machineIdentifier saved in file settings
|
||||||
but only once
|
but only once
|
||||||
|
|
||||||
Returns server or None if unsuccessful
|
Returns server or None if unsuccessful
|
||||||
"""
|
"""
|
||||||
httpsUpdated = False
|
https_updated = False
|
||||||
checkedPlexTV = False
|
checked_plex_tv = False
|
||||||
server = None
|
server = None
|
||||||
while True:
|
while True:
|
||||||
if httpsUpdated is False:
|
if https_updated is False:
|
||||||
serverlist = self._getServerList()
|
serverlist = PF.discover_pms(self.plex_token)
|
||||||
for item in serverlist:
|
for item in serverlist:
|
||||||
if item.get('machineIdentifier') == self.serverid:
|
if item.get('machineIdentifier') == self.serverid:
|
||||||
server = item
|
server = item
|
||||||
|
@ -331,26 +331,30 @@ class InitialSetup():
|
||||||
'machineIdentifier of %s and name %s is '
|
'machineIdentifier of %s and name %s is '
|
||||||
'offline', self.serverid, name)
|
'offline', self.serverid, name)
|
||||||
return
|
return
|
||||||
chk = self._checkServerCon(server)
|
chk = self._check_pms_connectivity(server)
|
||||||
if chk == 504 and httpsUpdated is False:
|
if chk == 504 and https_updated is False:
|
||||||
# Not able to use HTTP, try HTTPs for now
|
# switch HTTPS to HTTP or vice-versa
|
||||||
|
if server['scheme'] == 'https':
|
||||||
|
server['scheme'] = 'http'
|
||||||
|
else:
|
||||||
server['scheme'] = 'https'
|
server['scheme'] = 'https'
|
||||||
httpsUpdated = True
|
https_updated = True
|
||||||
continue
|
continue
|
||||||
if chk == 401:
|
if chk == 401:
|
||||||
LOG.warn('Not yet authorized for Plex server %s',
|
LOG.warn('Not yet authorized for Plex server %s',
|
||||||
server['name'])
|
server['name'])
|
||||||
if self.CheckPlexTVSignIn() is True:
|
if self.check_plex_tv_sign_in() is True:
|
||||||
if checkedPlexTV is False:
|
if checked_plex_tv is False:
|
||||||
# Try again
|
# Try again
|
||||||
checkedPlexTV = True
|
checked_plex_tv = True
|
||||||
httpsUpdated = False
|
https_updated = False
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
LOG.warn('Not authorized even though we are signed '
|
LOG.warn('Not authorized even though we are signed '
|
||||||
' in to plex.tv correctly')
|
' in to plex.tv correctly')
|
||||||
self.dialog.ok(lang(29999), '%s %s'
|
dialog('ok',
|
||||||
% (lang(39214),
|
lang(29999),
|
||||||
|
'%s %s' % (lang(39214),
|
||||||
tryEncode(server['name'])))
|
tryEncode(server['name'])))
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
|
@ -364,25 +368,25 @@ class InitialSetup():
|
||||||
server['name'])
|
server['name'])
|
||||||
return server
|
return server
|
||||||
|
|
||||||
def _UserPickPMS(self):
|
def _user_pick_pms(self):
|
||||||
"""
|
"""
|
||||||
Lets user pick his/her PMS from a list
|
Lets user pick his/her PMS from a list
|
||||||
|
|
||||||
Returns server or None if unsuccessful
|
Returns server or None if unsuccessful
|
||||||
"""
|
"""
|
||||||
httpsUpdated = False
|
https_updated = False
|
||||||
while True:
|
while True:
|
||||||
if httpsUpdated is False:
|
if https_updated is False:
|
||||||
serverlist = self._getServerList()
|
serverlist = PF.discover_pms(self.plex_token)
|
||||||
# Exit if no servers found
|
# Exit if no servers found
|
||||||
if len(serverlist) == 0:
|
if not serverlist:
|
||||||
LOG.warn('No plex media servers found!')
|
LOG.warn('No plex media servers found!')
|
||||||
self.dialog.ok(lang(29999), lang(39011))
|
dialog('ok', lang(29999), lang(39011))
|
||||||
return
|
return
|
||||||
# Get a nicer list
|
# Get a nicer list
|
||||||
dialoglist = []
|
dialoglist = []
|
||||||
for server in serverlist:
|
for server in serverlist:
|
||||||
if server['local'] == '1':
|
if server['local']:
|
||||||
# server is in the same network as client.
|
# server is in the same network as client.
|
||||||
# Add"local"
|
# Add"local"
|
||||||
msg = lang(39022)
|
msg = lang(39022)
|
||||||
|
@ -399,34 +403,34 @@ class InitialSetup():
|
||||||
dialoglist.append('%s (%s)'
|
dialoglist.append('%s (%s)'
|
||||||
% (server['name'], msg))
|
% (server['name'], msg))
|
||||||
# Let user pick server from a list
|
# Let user pick server from a list
|
||||||
resp = self.dialog.select(lang(39012), dialoglist)
|
resp = dialog('select', lang(39012), dialoglist)
|
||||||
if resp == -1:
|
if resp == -1:
|
||||||
# User cancelled
|
# User cancelled
|
||||||
return
|
return
|
||||||
|
|
||||||
server = serverlist[resp]
|
server = serverlist[resp]
|
||||||
chk = self._checkServerCon(server)
|
chk = self._check_pms_connectivity(server)
|
||||||
if chk == 504 and httpsUpdated is False:
|
if chk == 504 and https_updated is False:
|
||||||
# Not able to use HTTP, try HTTPs for now
|
# Not able to use HTTP, try HTTPs for now
|
||||||
serverlist[resp]['scheme'] = 'https'
|
serverlist[resp]['scheme'] = 'https'
|
||||||
httpsUpdated = True
|
https_updated = True
|
||||||
continue
|
continue
|
||||||
httpsUpdated = False
|
https_updated = False
|
||||||
if chk == 401:
|
if chk == 401:
|
||||||
LOG.warn('Not yet authorized for Plex server %s',
|
LOG.warn('Not yet authorized for Plex server %s',
|
||||||
server['name'])
|
server['name'])
|
||||||
# Please sign in to plex.tv
|
# Please sign in to plex.tv
|
||||||
self.dialog.ok(lang(29999),
|
dialog('ok',
|
||||||
|
lang(29999),
|
||||||
lang(39013) + server['name'],
|
lang(39013) + server['name'],
|
||||||
lang(39014))
|
lang(39014))
|
||||||
if self.PlexTVSignIn() is False:
|
if self.plex_tv_sign_in() is False:
|
||||||
# Exit while loop if user cancels
|
# Exit while loop if user cancels
|
||||||
return
|
return
|
||||||
# Problems connecting
|
# Problems connecting
|
||||||
elif chk >= 400 or chk is False:
|
elif chk >= 400 or chk is False:
|
||||||
# Problems connecting to server. Pick another server?
|
# Problems connecting to server. Pick another server?
|
||||||
answ = self.dialog.yesno(lang(29999),
|
answ = dialog('yesno', lang(29999), lang(39015))
|
||||||
lang(39015))
|
|
||||||
# Exit while loop if user chooses No
|
# Exit while loop if user chooses No
|
||||||
if not answ:
|
if not answ:
|
||||||
return
|
return
|
||||||
|
@ -434,30 +438,16 @@ class InitialSetup():
|
||||||
else:
|
else:
|
||||||
return server
|
return server
|
||||||
|
|
||||||
def WritePMStoSettings(self, server):
|
@staticmethod
|
||||||
|
def write_pms_to_settings(server):
|
||||||
"""
|
"""
|
||||||
Saves server to file settings. server is a dict of the form:
|
Saves server to file settings
|
||||||
{
|
|
||||||
'name': friendlyName, the Plex server's name
|
|
||||||
'address': ip:port
|
|
||||||
'ip': ip, without http/https
|
|
||||||
'port': port
|
|
||||||
'scheme': 'http'/'https', nice for checking for secure connections
|
|
||||||
'local': '1'/'0', Is the server a local server?
|
|
||||||
'owned': '1'/'0', Is the server owned by the user?
|
|
||||||
'machineIdentifier': id, Plex server machine identifier
|
|
||||||
'accesstoken': token Access token to this server
|
|
||||||
'baseURL': baseURL scheme://ip:port
|
|
||||||
'ownername' Plex username of PMS owner
|
|
||||||
}
|
|
||||||
"""
|
"""
|
||||||
settings('plex_machineIdentifier', server['machineIdentifier'])
|
settings('plex_machineIdentifier', server['machineIdentifier'])
|
||||||
settings('plex_servername', server['name'])
|
settings('plex_servername', server['name'])
|
||||||
settings('plex_serverowned',
|
settings('plex_serverowned', 'true' if server['owned'] else 'false')
|
||||||
'true' if server['owned'] == '1'
|
|
||||||
else 'false')
|
|
||||||
# Careful to distinguish local from remote PMS
|
# Careful to distinguish local from remote PMS
|
||||||
if server['local'] == '1':
|
if server['local']:
|
||||||
scheme = server['scheme']
|
scheme = server['scheme']
|
||||||
settings('ipaddress', server['ip'])
|
settings('ipaddress', server['ip'])
|
||||||
settings('port', server['port'])
|
settings('port', server['port'])
|
||||||
|
@ -491,7 +481,6 @@ class InitialSetup():
|
||||||
path.
|
path.
|
||||||
"""
|
"""
|
||||||
LOG.info("Initial setup called.")
|
LOG.info("Initial setup called.")
|
||||||
dialog = self.dialog
|
|
||||||
try:
|
try:
|
||||||
with XmlKodiSetting('advancedsettings.xml',
|
with XmlKodiSetting('advancedsettings.xml',
|
||||||
force_create=True,
|
force_create=True,
|
||||||
|
@ -537,9 +526,11 @@ class InitialSetup():
|
||||||
# Missing smb:// occurences, re-add.
|
# Missing smb:// occurences, re-add.
|
||||||
for _ in range(0, count):
|
for _ in range(0, count):
|
||||||
source = etree.SubElement(root, 'source')
|
source = etree.SubElement(root, 'source')
|
||||||
etree.SubElement(source,
|
etree.SubElement(
|
||||||
|
source,
|
||||||
'name').text = "PlexKodiConnect Masterlock Hack"
|
'name').text = "PlexKodiConnect Masterlock Hack"
|
||||||
etree.SubElement(source,
|
etree.SubElement(
|
||||||
|
source,
|
||||||
'path',
|
'path',
|
||||||
attrib={'pathversion': "1"}).text = "smb://"
|
attrib={'pathversion': "1"}).text = "smb://"
|
||||||
etree.SubElement(source, 'allowsharing').text = "true"
|
etree.SubElement(source, 'allowsharing').text = "true"
|
||||||
|
@ -555,23 +546,23 @@ class InitialSetup():
|
||||||
# return only if the right machine identifier is found
|
# return only if the right machine identifier is found
|
||||||
if self.server:
|
if self.server:
|
||||||
LOG.info("PMS is already set: %s. Checking now...", self.server)
|
LOG.info("PMS is already set: %s. Checking now...", self.server)
|
||||||
if self.CheckPMS():
|
if self.check_existing_pms():
|
||||||
LOG.info("Using PMS %s with machineIdentifier %s",
|
LOG.info("Using PMS %s with machineIdentifier %s",
|
||||||
self.server, self.serverid)
|
self.server, self.serverid)
|
||||||
self._write_PMS_settings(self.server, self.pms_token)
|
_write_pms_settings(self.server, self.pms_token)
|
||||||
if reboot is True:
|
if reboot is True:
|
||||||
reboot_kodi()
|
reboot_kodi()
|
||||||
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. This DOES get called on very first install run
|
# to plex.tv. This DOES get called on very first install run
|
||||||
if not self.plexToken and self.myplexlogin:
|
if not self.plex_token and self.myplexlogin:
|
||||||
self.PlexTVSignIn()
|
self.plex_tv_sign_in()
|
||||||
|
|
||||||
server = self.PickPMS()
|
server = self.pick_pms()
|
||||||
if server is not None:
|
if server is not None:
|
||||||
# Write our chosen server to Kodi settings file
|
# Write our chosen server to Kodi settings file
|
||||||
self.WritePMStoSettings(server)
|
self.write_pms_to_settings(server)
|
||||||
|
|
||||||
# User already answered the installation questions
|
# User already answered the installation questions
|
||||||
if settings('InstallQuestionsAnswered') == 'true':
|
if settings('InstallQuestionsAnswered') == 'true':
|
||||||
|
@ -581,8 +572,9 @@ class InitialSetup():
|
||||||
|
|
||||||
# Additional settings where the user needs to choose
|
# Additional settings where the user needs to choose
|
||||||
# Direct paths (\\NAS\mymovie.mkv) or addon (http)?
|
# Direct paths (\\NAS\mymovie.mkv) or addon (http)?
|
||||||
goToSettings = False
|
goto_settings = False
|
||||||
if dialog.yesno(lang(29999),
|
if dialog('yesno',
|
||||||
|
lang(29999),
|
||||||
lang(39027),
|
lang(39027),
|
||||||
lang(39028),
|
lang(39028),
|
||||||
nolabel="Addon (Default)",
|
nolabel="Addon (Default)",
|
||||||
|
@ -592,39 +584,40 @@ class InitialSetup():
|
||||||
state.DIRECT_PATHS = True
|
state.DIRECT_PATHS = True
|
||||||
# Are you on a system where you would like to replace paths
|
# Are you on a system where you would like to replace paths
|
||||||
# \\NAS\mymovie.mkv with smb://NAS/mymovie.mkv? (e.g. Windows)
|
# \\NAS\mymovie.mkv with smb://NAS/mymovie.mkv? (e.g. Windows)
|
||||||
if dialog.yesno(heading=lang(29999), line1=lang(39033)):
|
if dialog('yesno', heading=lang(29999), line1=lang(39033)):
|
||||||
LOG.debug("User chose to replace paths with smb")
|
LOG.debug("User chose to replace paths with smb")
|
||||||
else:
|
else:
|
||||||
settings('replaceSMB', value="false")
|
settings('replaceSMB', value="false")
|
||||||
|
|
||||||
# complete replace all original Plex library paths with custom SMB
|
# complete replace all original Plex library paths with custom SMB
|
||||||
if dialog.yesno(heading=lang(29999), line1=lang(39043)):
|
if dialog('yesno', heading=lang(29999), line1=lang(39043)):
|
||||||
LOG.debug("User chose custom smb paths")
|
LOG.debug("User chose custom smb paths")
|
||||||
settings('remapSMB', value="true")
|
settings('remapSMB', value="true")
|
||||||
# Please enter your custom smb paths in the settings under
|
# Please enter your custom smb paths in the settings under
|
||||||
# "Sync Options" and then restart Kodi
|
# "Sync Options" and then restart Kodi
|
||||||
dialog.ok(heading=lang(29999), line1=lang(39044))
|
dialog('ok', heading=lang(29999), line1=lang(39044))
|
||||||
goToSettings = True
|
goto_settings = True
|
||||||
|
|
||||||
# Go to network credentials?
|
# Go to network credentials?
|
||||||
if dialog.yesno(heading=lang(29999),
|
if dialog('yesno',
|
||||||
|
heading=lang(29999),
|
||||||
line1=lang(39029),
|
line1=lang(39029),
|
||||||
line2=lang(39030)):
|
line2=lang(39030)):
|
||||||
LOG.debug("Presenting network credentials dialog.")
|
LOG.debug("Presenting network credentials dialog.")
|
||||||
from utils import passwordsXML
|
from utils import passwordsXML
|
||||||
passwordsXML()
|
passwordsXML()
|
||||||
# Disable Plex music?
|
# Disable Plex music?
|
||||||
if dialog.yesno(heading=lang(29999), line1=lang(39016)):
|
if dialog('yesno', heading=lang(29999), line1=lang(39016)):
|
||||||
LOG.debug("User opted to disable Plex music library.")
|
LOG.debug("User opted to disable Plex music library.")
|
||||||
settings('enableMusic', value="false")
|
settings('enableMusic', value="false")
|
||||||
|
|
||||||
# Download additional art from FanArtTV
|
# Download additional art from FanArtTV
|
||||||
if dialog.yesno(heading=lang(29999), line1=lang(39061)):
|
if dialog('yesno', heading=lang(29999), line1=lang(39061)):
|
||||||
LOG.debug("User opted to use FanArtTV")
|
LOG.debug("User opted to use FanArtTV")
|
||||||
settings('FanartTV', value="true")
|
settings('FanartTV', value="true")
|
||||||
# Do you want to replace your custom user ratings with an indicator of
|
# Do you want to replace your custom user ratings with an indicator of
|
||||||
# how many versions of a media item you posses?
|
# how many versions of a media item you posses?
|
||||||
if dialog.yesno(heading=lang(29999), line1=lang(39718)):
|
if dialog('yesno', heading=lang(29999), line1=lang(39718)):
|
||||||
LOG.debug("User opted to replace user ratings with version number")
|
LOG.debug("User opted to replace user ratings with version number")
|
||||||
settings('indicate_media_versions', value="true")
|
settings('indicate_media_versions', value="true")
|
||||||
|
|
||||||
|
@ -637,12 +630,13 @@ class InitialSetup():
|
||||||
# Make sure that we only ask these questions upon first installation
|
# Make sure that we only ask these questions upon first installation
|
||||||
settings('InstallQuestionsAnswered', value='true')
|
settings('InstallQuestionsAnswered', value='true')
|
||||||
|
|
||||||
if goToSettings is False:
|
if goto_settings is False:
|
||||||
# Open Settings page now? You will need to restart!
|
# Open Settings page now? You will need to restart!
|
||||||
goToSettings = dialog.yesno(heading=lang(29999), line1=lang(39017))
|
goto_settings = dialog('yesno',
|
||||||
if goToSettings:
|
heading=lang(29999),
|
||||||
|
line1=lang(39017))
|
||||||
|
if goto_settings:
|
||||||
state.PMS_STATUS = 'Stop'
|
state.PMS_STATUS = 'Stop'
|
||||||
xbmc.executebuiltin(
|
executebuiltin('Addon.OpenSettings(plugin.video.plexkodiconnect)')
|
||||||
'Addon.OpenSettings(plugin.video.plexkodiconnect)')
|
|
||||||
elif reboot is True:
|
elif reboot is True:
|
||||||
reboot_kodi()
|
reboot_kodi()
|
||||||
|
|
338
resources/lib/plex_tv.py
Normal file
338
resources/lib/plex_tv.py
Normal file
|
@ -0,0 +1,338 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from logging import getLogger
|
||||||
|
|
||||||
|
from xbmc import sleep, executebuiltin
|
||||||
|
|
||||||
|
from downloadutils import DownloadUtils as DU
|
||||||
|
from utils import dialog, language as lang, settings, tryEncode
|
||||||
|
import variables as v
|
||||||
|
import state
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
LOG = getLogger("PLEX." + __name__)
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
|
||||||
|
def choose_home_user(token):
|
||||||
|
"""
|
||||||
|
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_home_users(token)
|
||||||
|
if not users:
|
||||||
|
LOG.error("User download failed.")
|
||||||
|
return False
|
||||||
|
userlist = []
|
||||||
|
userlist_coded = []
|
||||||
|
for user in users:
|
||||||
|
username = user['title']
|
||||||
|
userlist.append(username)
|
||||||
|
# To take care of non-ASCII usernames
|
||||||
|
userlist_coded.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),
|
||||||
|
userlist_coded)
|
||||||
|
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}')
|
||||||
|
# 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,
|
||||||
|
token,
|
||||||
|
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',
|
||||||
|
heading='{plex}',
|
||||||
|
line1=lang(39308) + selected_user,
|
||||||
|
line2=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 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 = DU().downloadUrl(url,
|
||||||
|
authenticate=False,
|
||||||
|
action_type="POST",
|
||||||
|
headerOptions={'X-Plex-Token': token})
|
||||||
|
try:
|
||||||
|
answer.attrib
|
||||||
|
except AttributeError:
|
||||||
|
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 = DU().downloadUrl(url,
|
||||||
|
authenticate=False,
|
||||||
|
headerOptions={'X-Plex-Token': token})
|
||||||
|
try:
|
||||||
|
xml.attrib
|
||||||
|
except AttributeError:
|
||||||
|
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 == machineIdentifier:
|
||||||
|
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 list_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 = DU().downloadUrl('https://plex.tv/api/home/users/',
|
||||||
|
authenticate=False,
|
||||||
|
headerOptions={'X-Plex-Token': token})
|
||||||
|
try:
|
||||||
|
xml.attrib
|
||||||
|
except AttributeError:
|
||||||
|
LOG.error('Download of Plex home users failed.')
|
||||||
|
return False
|
||||||
|
users = []
|
||||||
|
for user in xml:
|
||||||
|
users.append(user.attrib)
|
||||||
|
return users
|
||||||
|
|
||||||
|
|
||||||
|
def 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_pin()
|
||||||
|
if not code:
|
||||||
|
# Problems trying to contact plex.tv. Try again later
|
||||||
|
dialog('ok', heading='{plex}', line1=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',
|
||||||
|
heading='{plex}',
|
||||||
|
line1=lang(39304) + "\n\n",
|
||||||
|
line2=code + "\n\n",
|
||||||
|
line3=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_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', heading='{plex}', line1=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')
|
||||||
|
home_size = xml.get('homeSize', '1')
|
||||||
|
result = {
|
||||||
|
'plexhome': home,
|
||||||
|
'username': username,
|
||||||
|
'avatar': avatar,
|
||||||
|
'token': token,
|
||||||
|
'plexid': userid,
|
||||||
|
'homesize': home_size
|
||||||
|
}
|
||||||
|
settings('plexLogin', username)
|
||||||
|
settings('plexToken', token)
|
||||||
|
settings('plexhome', home)
|
||||||
|
settings('plexid', userid)
|
||||||
|
settings('plexAvatar', avatar)
|
||||||
|
settings('plexHomeSize', home_size)
|
||||||
|
# Let Kodi log into plex.tv on startup from now on
|
||||||
|
settings('myplexlogin', 'true')
|
||||||
|
settings('plex_status', value=lang(39227))
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def get_pin():
|
||||||
|
"""
|
||||||
|
For plex.tv sign-in: returns 4-digit code and identifier as 2 str
|
||||||
|
"""
|
||||||
|
code = None
|
||||||
|
identifier = None
|
||||||
|
# Download
|
||||||
|
xml = DU().downloadUrl('https://plex.tv/pins.xml',
|
||||||
|
authenticate=False,
|
||||||
|
action_type="POST")
|
||||||
|
try:
|
||||||
|
xml.attrib
|
||||||
|
except AttributeError:
|
||||||
|
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 check_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 = DU().downloadUrl('https://plex.tv/pins/%s.xml' % identifier,
|
||||||
|
authenticate=False)
|
||||||
|
try:
|
||||||
|
temp_token = xml.find('auth_token').text
|
||||||
|
except AttributeError:
|
||||||
|
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 = DU().downloadUrl('https://plex.tv/users/account',
|
||||||
|
authenticate=False,
|
||||||
|
parameters={'X-Plex-Token': temp_token})
|
||||||
|
return xml
|
|
@ -1,25 +1,21 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
import xbmc
|
from xbmc import sleep, executebuiltin, translatePath
|
||||||
import xbmcgui
|
|
||||||
import xbmcaddon
|
import xbmcaddon
|
||||||
from xbmcvfs import exists
|
from xbmcvfs import exists
|
||||||
|
|
||||||
|
from utils import window, settings, language as lang, thread_methods, dialog
|
||||||
from utils import window, settings, language as lang, thread_methods
|
from downloadutils import DownloadUtils as DU
|
||||||
import downloadutils
|
import plex_tv
|
||||||
|
import PlexFunctions as PF
|
||||||
import PlexAPI
|
|
||||||
from PlexFunctions import GetMachineIdentifier
|
|
||||||
import state
|
import state
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
log = getLogger("PLEX."+__name__)
|
LOG = getLogger("PLEX." + __name__)
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
@ -45,7 +41,7 @@ class UserClient(Thread):
|
||||||
self.userSettings = None
|
self.userSettings = None
|
||||||
|
|
||||||
self.addon = xbmcaddon.Addon()
|
self.addon = xbmcaddon.Addon()
|
||||||
self.doUtils = downloadutils.DownloadUtils()
|
self.doUtils = DU()
|
||||||
|
|
||||||
Thread.__init__(self)
|
Thread.__init__(self)
|
||||||
|
|
||||||
|
@ -55,10 +51,10 @@ class UserClient(Thread):
|
||||||
"""
|
"""
|
||||||
username = settings('username')
|
username = settings('username')
|
||||||
if not username:
|
if not username:
|
||||||
log.debug("No username saved, trying to get Plex username")
|
LOG.debug("No username saved, trying to get Plex username")
|
||||||
username = settings('plexLogin')
|
username = settings('plexLogin')
|
||||||
if not username:
|
if not username:
|
||||||
log.debug("Also no Plex username found")
|
LOG.debug("Also no Plex username found")
|
||||||
return ""
|
return ""
|
||||||
return username
|
return username
|
||||||
|
|
||||||
|
@ -73,7 +69,7 @@ class UserClient(Thread):
|
||||||
server = host + ":" + port
|
server = host + ":" + port
|
||||||
|
|
||||||
if not host:
|
if not host:
|
||||||
log.debug("No server information saved.")
|
LOG.debug("No server information saved.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# If https is true
|
# If https is true
|
||||||
|
@ -84,11 +80,11 @@ class UserClient(Thread):
|
||||||
server = "http://%s" % server
|
server = "http://%s" % server
|
||||||
# User entered IP; we need to get the machineIdentifier
|
# User entered IP; we need to get the machineIdentifier
|
||||||
if self.machineIdentifier == '' and prefix is True:
|
if self.machineIdentifier == '' and prefix is True:
|
||||||
self.machineIdentifier = GetMachineIdentifier(server)
|
self.machineIdentifier = PF.GetMachineIdentifier(server)
|
||||||
if self.machineIdentifier is None:
|
if self.machineIdentifier is None:
|
||||||
self.machineIdentifier = ''
|
self.machineIdentifier = ''
|
||||||
settings('plex_machineIdentifier', value=self.machineIdentifier)
|
settings('plex_machineIdentifier', value=self.machineIdentifier)
|
||||||
log.debug('Returning active server: %s' % server)
|
LOG.debug('Returning active server: %s', server)
|
||||||
return server
|
return server
|
||||||
|
|
||||||
def getSSLverify(self):
|
def getSSLverify(self):
|
||||||
|
@ -101,10 +97,10 @@ class UserClient(Thread):
|
||||||
else settings('sslcert')
|
else settings('sslcert')
|
||||||
|
|
||||||
def setUserPref(self):
|
def setUserPref(self):
|
||||||
log.debug('Setting user preferences')
|
LOG.debug('Setting user preferences')
|
||||||
# Only try to get user avatar if there is a token
|
# Only try to get user avatar if there is a token
|
||||||
if self.currToken:
|
if self.currToken:
|
||||||
url = PlexAPI.PlexAPI().GetUserArtworkURL(self.currUser)
|
url = PF.GetUserArtworkURL(self.currUser)
|
||||||
if url:
|
if url:
|
||||||
window('PlexUserImage', value=url)
|
window('PlexUserImage', value=url)
|
||||||
# Set resume point max
|
# Set resume point max
|
||||||
|
@ -116,7 +112,7 @@ class UserClient(Thread):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def loadCurrUser(self, username, userId, usertoken, authenticated=False):
|
def loadCurrUser(self, username, userId, usertoken, authenticated=False):
|
||||||
log.debug('Loading current user')
|
LOG.debug('Loading current user')
|
||||||
doUtils = self.doUtils
|
doUtils = self.doUtils
|
||||||
|
|
||||||
self.currToken = usertoken
|
self.currToken = usertoken
|
||||||
|
@ -127,18 +123,18 @@ class UserClient(Thread):
|
||||||
if authenticated is False:
|
if authenticated is False:
|
||||||
if self.currServer is None:
|
if self.currServer is None:
|
||||||
return False
|
return False
|
||||||
log.debug('Testing validity of current token')
|
LOG.debug('Testing validity of current token')
|
||||||
res = PlexAPI.PlexAPI().CheckConnection(self.currServer,
|
res = PF.check_connection(self.currServer,
|
||||||
token=self.currToken,
|
token=self.currToken,
|
||||||
verifySSL=self.ssl)
|
verifySSL=self.ssl)
|
||||||
if res is False:
|
if res is False:
|
||||||
# PMS probably offline
|
# PMS probably offline
|
||||||
return False
|
return False
|
||||||
elif res == 401:
|
elif res == 401:
|
||||||
log.error('Token is no longer valid')
|
LOG.error('Token is no longer valid')
|
||||||
return 401
|
return 401
|
||||||
elif res >= 400:
|
elif res >= 400:
|
||||||
log.error('Answer from PMS is not as expected. Retrying')
|
LOG.error('Answer from PMS is not as expected. Retrying')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Set to windows property
|
# Set to windows property
|
||||||
|
@ -182,31 +178,29 @@ class UserClient(Thread):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def authenticate(self):
|
def authenticate(self):
|
||||||
log.debug('Authenticating user')
|
LOG.debug('Authenticating user')
|
||||||
dialog = xbmcgui.Dialog()
|
|
||||||
|
|
||||||
# Give attempts at entering password / selecting user
|
# Give attempts at entering password / selecting user
|
||||||
if self.retry >= 2:
|
if self.retry >= 2:
|
||||||
log.error("Too many retries to login.")
|
LOG.error("Too many retries to login.")
|
||||||
state.PMS_STATUS = 'Stop'
|
state.PMS_STATUS = 'Stop'
|
||||||
dialog.ok(lang(33001),
|
dialog('ok', lang(33001), lang(39023))
|
||||||
lang(39023))
|
executebuiltin(
|
||||||
xbmc.executebuiltin(
|
|
||||||
'Addon.OpenSettings(plugin.video.plexkodiconnect)')
|
'Addon.OpenSettings(plugin.video.plexkodiconnect)')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Get /profile/addon_data
|
# Get /profile/addon_data
|
||||||
addondir = xbmc.translatePath(self.addon.getAddonInfo('profile'))
|
addondir = translatePath(self.addon.getAddonInfo('profile'))
|
||||||
|
|
||||||
# If there's no settings.xml
|
# If there's no settings.xml
|
||||||
if not exists("%ssettings.xml" % addondir):
|
if not exists("%ssettings.xml" % addondir):
|
||||||
log.error("Error, no settings.xml found.")
|
LOG.error("Error, no settings.xml found.")
|
||||||
self.auth = False
|
self.auth = False
|
||||||
return False
|
return False
|
||||||
server = self.getServer()
|
server = self.getServer()
|
||||||
# If there is no server we can connect to
|
# If there is no server we can connect to
|
||||||
if not server:
|
if not server:
|
||||||
log.info("Missing server information.")
|
LOG.info("Missing server information.")
|
||||||
self.auth = False
|
self.auth = False
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -217,7 +211,7 @@ class UserClient(Thread):
|
||||||
enforceLogin = settings('enforceUserLogin')
|
enforceLogin = settings('enforceUserLogin')
|
||||||
# Found a user in the settings, try to authenticate
|
# Found a user in the settings, try to authenticate
|
||||||
if username and enforceLogin == 'false':
|
if username and enforceLogin == 'false':
|
||||||
log.debug('Trying to authenticate with old settings')
|
LOG.debug('Trying to authenticate with old settings')
|
||||||
answ = self.loadCurrUser(username,
|
answ = self.loadCurrUser(username,
|
||||||
userId,
|
userId,
|
||||||
usertoken,
|
usertoken,
|
||||||
|
@ -226,21 +220,19 @@ class UserClient(Thread):
|
||||||
# SUCCESS: loaded a user from the settings
|
# SUCCESS: loaded a user from the settings
|
||||||
return True
|
return True
|
||||||
elif answ == 401:
|
elif answ == 401:
|
||||||
log.error("User token no longer valid. Sign user out")
|
LOG.error("User token no longer valid. Sign user out")
|
||||||
settings('username', value='')
|
settings('username', value='')
|
||||||
settings('userid', value='')
|
settings('userid', value='')
|
||||||
settings('accessToken', value='')
|
settings('accessToken', value='')
|
||||||
else:
|
else:
|
||||||
log.debug("Could not yet authenticate user")
|
LOG.debug("Could not yet authenticate user")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
plx = PlexAPI.PlexAPI()
|
|
||||||
|
|
||||||
# Could not use settings - try to get Plex user list from plex.tv
|
# Could not use settings - try to get Plex user list from plex.tv
|
||||||
plextoken = settings('plexToken')
|
plextoken = settings('plexToken')
|
||||||
if plextoken:
|
if plextoken:
|
||||||
log.info("Trying to connect to plex.tv to get a user list")
|
LOG.info("Trying to connect to plex.tv to get a user list")
|
||||||
userInfo = plx.ChoosePlexHomeUser(plextoken)
|
userInfo = plex_tv.choose_home_user(plextoken)
|
||||||
if userInfo is False:
|
if userInfo is False:
|
||||||
# FAILURE: Something went wrong, try again
|
# FAILURE: Something went wrong, try again
|
||||||
self.auth = True
|
self.auth = True
|
||||||
|
@ -250,7 +242,7 @@ class UserClient(Thread):
|
||||||
userId = userInfo['userid']
|
userId = userInfo['userid']
|
||||||
usertoken = userInfo['token']
|
usertoken = userInfo['token']
|
||||||
else:
|
else:
|
||||||
log.info("Trying to authenticate without a token")
|
LOG.info("Trying to authenticate without a token")
|
||||||
username = ''
|
username = ''
|
||||||
userId = ''
|
userId = ''
|
||||||
usertoken = ''
|
usertoken = ''
|
||||||
|
@ -258,14 +250,13 @@ class UserClient(Thread):
|
||||||
if self.loadCurrUser(username, userId, usertoken, authenticated=False):
|
if self.loadCurrUser(username, userId, usertoken, authenticated=False):
|
||||||
# SUCCESS: loaded a user from the settings
|
# SUCCESS: loaded a user from the settings
|
||||||
return True
|
return True
|
||||||
else:
|
# Something went wrong, try again
|
||||||
# FAILUR: Something went wrong, try again
|
|
||||||
self.auth = True
|
self.auth = True
|
||||||
self.retry += 1
|
self.retry += 1
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def resetClient(self):
|
def resetClient(self):
|
||||||
log.debug("Reset UserClient authentication.")
|
LOG.debug("Reset UserClient authentication.")
|
||||||
self.doUtils.stopSession()
|
self.doUtils.stopSession()
|
||||||
|
|
||||||
window('plex_authenticated', clear=True)
|
window('plex_authenticated', clear=True)
|
||||||
|
@ -295,17 +286,17 @@ class UserClient(Thread):
|
||||||
self.retry = 0
|
self.retry = 0
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
log.info("----===## Starting UserClient ##===----")
|
LOG.info("----===## Starting UserClient ##===----")
|
||||||
thread_stopped = self.thread_stopped
|
thread_stopped = self.thread_stopped
|
||||||
thread_suspended = self.thread_suspended
|
thread_suspended = self.thread_suspended
|
||||||
while not thread_stopped():
|
while not thread_stopped():
|
||||||
while thread_suspended():
|
while thread_suspended():
|
||||||
if thread_stopped():
|
if thread_stopped():
|
||||||
break
|
break
|
||||||
xbmc.sleep(1000)
|
sleep(1000)
|
||||||
|
|
||||||
if state.PMS_STATUS == "Stop":
|
if state.PMS_STATUS == "Stop":
|
||||||
xbmc.sleep(500)
|
sleep(500)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Verify the connection status to server
|
# Verify the connection status to server
|
||||||
|
@ -318,7 +309,7 @@ class UserClient(Thread):
|
||||||
state.PMS_STATUS = 'Auth'
|
state.PMS_STATUS = 'Auth'
|
||||||
window('plex_serverStatus', value='Auth')
|
window('plex_serverStatus', value='Auth')
|
||||||
self.resetClient()
|
self.resetClient()
|
||||||
xbmc.sleep(3000)
|
sleep(3000)
|
||||||
|
|
||||||
if self.auth and (self.currUser is None):
|
if self.auth and (self.currUser is None):
|
||||||
# Try to authenticate user
|
# Try to authenticate user
|
||||||
|
@ -328,9 +319,9 @@ class UserClient(Thread):
|
||||||
self.auth = False
|
self.auth = False
|
||||||
if self.authenticate():
|
if self.authenticate():
|
||||||
# Successfully authenticated and loaded a user
|
# Successfully authenticated and loaded a user
|
||||||
log.info("Successfully authenticated!")
|
LOG.info("Successfully authenticated!")
|
||||||
log.info("Current user: %s" % self.currUser)
|
LOG.info("Current user: %s", self.currUser)
|
||||||
log.info("Current userId: %s" % state.PLEX_USER_ID)
|
LOG.info("Current userId: %s", state.PLEX_USER_ID)
|
||||||
self.retry = 0
|
self.retry = 0
|
||||||
state.SUSPEND_LIBRARY_THREAD = False
|
state.SUSPEND_LIBRARY_THREAD = False
|
||||||
window('plex_serverStatus', clear=True)
|
window('plex_serverStatus', clear=True)
|
||||||
|
@ -344,10 +335,10 @@ class UserClient(Thread):
|
||||||
# Or retried too many times
|
# Or retried too many times
|
||||||
if server and state.PMS_STATUS != "Stop":
|
if server and state.PMS_STATUS != "Stop":
|
||||||
# Only if there's information found to login
|
# Only if there's information found to login
|
||||||
log.debug("Server found: %s" % server)
|
LOG.debug("Server found: %s", server)
|
||||||
self.auth = True
|
self.auth = True
|
||||||
|
|
||||||
# Minimize CPU load
|
# Minimize CPU load
|
||||||
xbmc.sleep(100)
|
sleep(100)
|
||||||
|
|
||||||
log.info("##===---- UserClient Stopped ----===##")
|
LOG.info("##===---- UserClient Stopped ----===##")
|
||||||
|
|
|
@ -129,22 +129,19 @@ def dialog(typus, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Displays xbmcgui Dialog. Pass a string as typus:
|
Displays xbmcgui Dialog. Pass a string as typus:
|
||||||
'yesno', 'ok', 'notification', 'input', 'select', 'numeric'
|
'yesno', 'ok', 'notification', 'input', 'select', 'numeric'
|
||||||
|
|
||||||
kwargs:
|
kwargs:
|
||||||
heading='{plex}' title bar (here PlexKodiConnect)
|
heading='{plex}' title bar (here PlexKodiConnect)
|
||||||
message=lang(30128), Actual dialog content. Don't use with OK
|
message=lang(30128), Dialog content. Don't use with 'OK', 'yesno'
|
||||||
line1=str(), For 'OK' and 'yesno' dialogs use line1...line3!
|
line1=str(), For 'OK' and 'yesno' dialogs use line1...line3!
|
||||||
time=5000,
|
time=5000,
|
||||||
sound=True,
|
sound=True,
|
||||||
nolabel=str(), For 'yesno' dialogs
|
nolabel=str(), For 'yesno' dialogs
|
||||||
yeslabel=str(), For 'yesno' dialogs
|
yeslabel=str(), For 'yesno' dialogs
|
||||||
|
|
||||||
Icons:
|
Icons:
|
||||||
icon='{plex}' Display Plex standard icon
|
icon='{plex}' Display Plex standard icon
|
||||||
icon='{info}' xbmcgui.NOTIFICATION_INFO
|
icon='{info}' xbmcgui.NOTIFICATION_INFO
|
||||||
icon='{warning}' xbmcgui.NOTIFICATION_WARNING
|
icon='{warning}' xbmcgui.NOTIFICATION_WARNING
|
||||||
icon='{error}' xbmcgui.NOTIFICATION_ERROR
|
icon='{error}' xbmcgui.NOTIFICATION_ERROR
|
||||||
|
|
||||||
Input Types:
|
Input Types:
|
||||||
type='{alphanum}' xbmcgui.INPUT_ALPHANUM (standard keyboard)
|
type='{alphanum}' xbmcgui.INPUT_ALPHANUM (standard keyboard)
|
||||||
type='{numeric}' xbmcgui.INPUT_NUMERIC (format: #)
|
type='{numeric}' xbmcgui.INPUT_NUMERIC (format: #)
|
||||||
|
@ -153,9 +150,12 @@ def dialog(typus, *args, **kwargs):
|
||||||
type='{ipaddress}' xbmcgui.INPUT_IPADDRESS (format: #.#.#.#)
|
type='{ipaddress}' xbmcgui.INPUT_IPADDRESS (format: #.#.#.#)
|
||||||
type='{password}' xbmcgui.INPUT_PASSWORD
|
type='{password}' xbmcgui.INPUT_PASSWORD
|
||||||
(return md5 hash of input, input is masked)
|
(return md5 hash of input, input is masked)
|
||||||
|
Options:
|
||||||
|
option='{password}' xbmcgui.PASSWORD_VERIFY (verifies an existing
|
||||||
|
(default) md5 hashed password)
|
||||||
|
option='{hide}' xbmcgui.ALPHANUM_HIDE_INPUT (masks input)
|
||||||
"""
|
"""
|
||||||
d = xbmcgui.Dialog()
|
if 'icon' in kwargs:
|
||||||
if "icon" in kwargs:
|
|
||||||
types = {
|
types = {
|
||||||
'{plex}': 'special://home/addons/plugin.video.plexkodiconnect/icon.png',
|
'{plex}': 'special://home/addons/plugin.video.plexkodiconnect/icon.png',
|
||||||
'{info}': xbmcgui.NOTIFICATION_INFO,
|
'{info}': xbmcgui.NOTIFICATION_INFO,
|
||||||
|
@ -174,16 +174,23 @@ def dialog(typus, *args, **kwargs):
|
||||||
'{password}': xbmcgui.INPUT_PASSWORD
|
'{password}': xbmcgui.INPUT_PASSWORD
|
||||||
}
|
}
|
||||||
kwargs['type'] = types[kwargs['type']]
|
kwargs['type'] = types[kwargs['type']]
|
||||||
if "heading" in kwargs:
|
if 'option' in kwargs:
|
||||||
|
types = {
|
||||||
|
'{password}': xbmcgui.PASSWORD_VERIFY,
|
||||||
|
'{hide}': xbmcgui.ALPHANUM_HIDE_INPUT
|
||||||
|
}
|
||||||
|
kwargs['option'] = types[kwargs['option']]
|
||||||
|
if 'heading' in kwargs:
|
||||||
kwargs['heading'] = kwargs['heading'].replace("{plex}",
|
kwargs['heading'] = kwargs['heading'].replace("{plex}",
|
||||||
language(29999))
|
language(29999))
|
||||||
|
dia = xbmcgui.Dialog()
|
||||||
types = {
|
types = {
|
||||||
'yesno': d.yesno,
|
'yesno': dia.yesno,
|
||||||
'ok': d.ok,
|
'ok': dia.ok,
|
||||||
'notification': d.notification,
|
'notification': dia.notification,
|
||||||
'input': d.input,
|
'input': dia.input,
|
||||||
'select': d.select,
|
'select': dia.select,
|
||||||
'numeric': d.numeric
|
'numeric': dia.numeric
|
||||||
}
|
}
|
||||||
return types[typus](*args, **kwargs)
|
return types[typus](*args, **kwargs)
|
||||||
|
|
||||||
|
|
10
service.py
10
service.py
|
@ -35,7 +35,7 @@ from kodimonitor import KodiMonitor, SpecialMonitor
|
||||||
from librarysync import LibrarySync
|
from librarysync import LibrarySync
|
||||||
from websocket_client import PMS_Websocket, Alexa_Websocket
|
from websocket_client import PMS_Websocket, Alexa_Websocket
|
||||||
|
|
||||||
import PlexAPI
|
from PlexFunctions import check_connection
|
||||||
from PlexCompanion import PlexCompanion
|
from PlexCompanion import PlexCompanion
|
||||||
from command_pipeline import Monitor_Window
|
from command_pipeline import Monitor_Window
|
||||||
from playback_starter import Playback_Starter
|
from playback_starter import Playback_Starter
|
||||||
|
@ -116,8 +116,6 @@ class Service():
|
||||||
if settings('enableTextureCache') == "true":
|
if settings('enableTextureCache') == "true":
|
||||||
self.image_cache_thread = Image_Cache_Thread()
|
self.image_cache_thread = Image_Cache_Thread()
|
||||||
|
|
||||||
plx = PlexAPI.PlexAPI()
|
|
||||||
|
|
||||||
welcome_msg = True
|
welcome_msg = True
|
||||||
counter = 0
|
counter = 0
|
||||||
while not __stop_PKC():
|
while not __stop_PKC():
|
||||||
|
@ -208,7 +206,7 @@ class Service():
|
||||||
if server is False:
|
if server is False:
|
||||||
# No server info set in add-on settings
|
# No server info set in add-on settings
|
||||||
pass
|
pass
|
||||||
elif plx.CheckConnection(server, verifySSL=True) is False:
|
elif check_connection(server, verifySSL=True) is False:
|
||||||
# Server is offline or cannot be reached
|
# Server is offline or cannot be reached
|
||||||
# Alert the user and suppress future warning
|
# Alert the user and suppress future warning
|
||||||
if self.server_online:
|
if self.server_online:
|
||||||
|
@ -228,9 +226,9 @@ class Service():
|
||||||
if counter > 20:
|
if counter > 20:
|
||||||
counter = 0
|
counter = 0
|
||||||
setup = initialsetup.InitialSetup()
|
setup = initialsetup.InitialSetup()
|
||||||
tmp = setup.PickPMS()
|
tmp = setup.pick_pms()
|
||||||
if tmp is not None:
|
if tmp is not None:
|
||||||
setup.WritePMStoSettings(tmp)
|
setup.write_pms_to_settings(tmp)
|
||||||
else:
|
else:
|
||||||
# Server is online
|
# Server is online
|
||||||
counter = 0
|
counter = 0
|
||||||
|
|
Loading…
Reference in a new issue