# -*- coding: utf-8 -*- # This file is part of Tautulli. # # Tautulli is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Tautulli is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Tautulli. If not, see . from __future__ import absolute_import from __future__ import unicode_literals from builtins import next from builtins import str from builtins import object import base64 import json import plexpy from plexpy import common from plexpy import helpers from plexpy import http_handler from plexpy import logger from plexpy import users from plexpy import pmsconnect from plexpy import session def get_server_resources(return_presence=False, return_server=False, **kwargs): if not return_presence: logger.info("Tautulli PlexTV :: Requesting resources for server...") server = {'pms_name': plexpy.CONFIG.PMS_NAME, 'pms_version': plexpy.CONFIG.PMS_VERSION, 'pms_platform': plexpy.CONFIG.PMS_PLATFORM, 'pms_ip': plexpy.CONFIG.PMS_IP, 'pms_port': plexpy.CONFIG.PMS_PORT, 'pms_ssl': plexpy.CONFIG.PMS_SSL, 'pms_is_remote': plexpy.CONFIG.PMS_IS_REMOTE, 'pms_is_cloud': plexpy.CONFIG.PMS_IS_CLOUD, 'pms_url': plexpy.CONFIG.PMS_URL, 'pms_url_manual': plexpy.CONFIG.PMS_URL_MANUAL, 'pms_identifier': plexpy.CONFIG.PMS_IDENTIFIER } if kwargs: server.update(kwargs) for k in ['pms_ssl', 'pms_is_remote', 'pms_is_cloud', 'pms_url_manual']: server[k] = int(server[k]) if server['pms_url_manual'] and server['pms_ssl'] or server['pms_is_cloud']: scheme = 'https' else: scheme = 'http' fallback_url = '{scheme}://{hostname}:{port}'.format(scheme=scheme, hostname=server['pms_ip'], port=server['pms_port']) plex_tv = PlexTV() result = plex_tv.get_server_connections(pms_identifier=server['pms_identifier'], pms_ip=server['pms_ip'], pms_port=server['pms_port'], include_https=server['pms_ssl']) if result: connections = result.pop('connections', []) server.update(result) presence = server.pop('pms_presence', 0) else: connections = [] presence = 0 if return_presence: return presence plexpass = plex_tv.get_plexpass_status() server['pms_plexpass'] = int(plexpass) # Only need to retrieve PMS_URL if using SSL if not server['pms_url_manual'] and server['pms_ssl']: if connections: if server['pms_is_remote']: # Get all remote connections conns = [c for c in connections if c['local'] == '0' and ('plex.direct' in c['uri'] or 'plex.service' in c['uri'])] else: # Get all local connections conns = [c for c in connections if c['local'] == '1' and ('plex.direct' in c['uri'] or 'plex.service' in c['uri'])] if conns: # Get connection with matching address, otherwise return first connection conn = next((c for c in conns if c['address'] == server['pms_ip'] and c['port'] == str(server['pms_port'])), conns[0]) server['pms_url'] = conn['uri'] logger.info("Tautulli PlexTV :: Server URL retrieved.") # get_server_urls() failed or PMS_URL not found, fallback url doesn't use SSL if not server['pms_url']: server['pms_url'] = fallback_url logger.warn("Tautulli PlexTV :: Unable to retrieve server URLs. Using user-defined value without SSL.") # Not using SSL, remote has no effect else: server['pms_url'] = fallback_url logger.info("Tautulli PlexTV :: Using user-defined URL.") if return_server: return server plexpy.CONFIG.process_kwargs(server) plexpy.CONFIG.write() class PlexTV(object): """ Plex.tv authentication """ def __init__(self, username=None, password=None, token=None, headers=None): self.username = username self.password = password self.token = token self.urls = 'https://plex.tv' self.timeout = plexpy.CONFIG.PMS_TIMEOUT self.ssl_verify = plexpy.CONFIG.VERIFY_SSL_CERT if self.username is None and self.password is None: if not self.token: # Check if we should use the admin token, or the guest server token if session.get_session_user_id(): user_data = users.Users() user_tokens = user_data.get_tokens(user_id=session.get_session_user_id()) self.token = user_tokens['server_token'] else: self.token = plexpy.CONFIG.PMS_TOKEN if not self.token: logger.error("Tautulli PlexTV :: PlexTV called, but no token provided.") return self.request_handler = http_handler.HTTPHandler(urls=self.urls, token=self.token, timeout=self.timeout, ssl_verify=self.ssl_verify, headers=headers) def get_plex_auth(self, output_format='raw'): uri = '/users/sign_in.xml' base64string = base64.b64encode(('%s:%s' % (self.username, self.password)).encode('utf-8')) headers = {'Content-Type': 'application/xml; charset=utf-8', 'Authorization': 'Basic %s' % base64string} request = self.request_handler.make_request(uri=uri, request_type='POST', headers=headers, output_format=output_format, no_token=True) return request def get_token(self): plextv_response = self.get_plex_auth(output_format='xml') if plextv_response: try: xml_head = plextv_response.getElementsByTagName('user') if xml_head: user = {'auth_token': xml_head[0].getAttribute('authenticationToken'), 'user_id': xml_head[0].getAttribute('id') } else: logger.warn("Tautulli PlexTV :: Could not get Plex authentication token.") except Exception as e: logger.warn("Tautulli PlexTV :: Unable to parse XML for get_token: %s." % e) return None return user else: return None def get_plexpy_pms_token(self, force=False): if force: logger.debug("Tautulli PlexTV :: Forcing refresh of Plex.tv token.") devices_list = self.get_devices_list() device_id = next((d for d in devices_list if d['device_identifier'] == plexpy.CONFIG.PMS_UUID), {}).get('device_id', None) if device_id: logger.debug("Tautulli PlexTV :: Removing Tautulli from Plex.tv devices.") try: self.delete_plextv_device(device_id=device_id) except: logger.error("Tautulli PlexTV :: Failed to remove Tautulli from Plex.tv devices.") return None else: logger.warn("Tautulli PlexTV :: No existing Tautulli device found.") logger.info("Tautulli PlexTV :: Fetching a new Plex.tv token for Tautulli.") user = self.get_token() if user: token = user['auth_token'] plexpy.CONFIG.__setattr__('PMS_TOKEN', token) plexpy.CONFIG.write() logger.info("Tautulli PlexTV :: Updated Plex.tv token for Tautulli.") return token def get_server_token(self): servers = self.get_plextv_resources(output_format='xml') server_token = '' try: xml_head = servers.getElementsByTagName('Device') except Exception as e: logger.warn("Tautulli PlexTV :: Unable to parse XML for get_server_token: %s." % e) return None for a in xml_head: if helpers.get_xml_attr(a, 'clientIdentifier') == plexpy.CONFIG.PMS_IDENTIFIER \ and 'server' in helpers.get_xml_attr(a, 'provides'): server_token = helpers.get_xml_attr(a, 'accessToken') break return server_token def get_plextv_pin(self, pin='', output_format=''): if pin: uri = '/api/v2/pins/' + pin request = self.request_handler.make_request(uri=uri, request_type='GET', output_format=output_format, no_token=True) else: uri = '/api/v2/pins?strong=true' request = self.request_handler.make_request(uri=uri, request_type='POST', output_format=output_format, no_token=True) return request def get_pin(self, pin=''): plextv_response = self.get_plextv_pin(pin=pin, output_format='xml') if plextv_response: try: xml_head = plextv_response.getElementsByTagName('pin') if xml_head: pin = {'id': xml_head[0].getAttribute('id'), 'code': xml_head[0].getAttribute('code'), 'token': xml_head[0].getAttribute('authToken') } return pin else: logger.warn("Tautulli PlexTV :: Could not get Plex authentication pin.") return None except Exception as e: logger.warn("Tautulli PlexTV :: Unable to parse XML for get_pin: %s." % e) return None else: return None def get_plextv_user_data(self): plextv_response = self.get_plex_auth(output_format='dict') if plextv_response: return plextv_response else: return [] def get_plextv_friends(self, output_format=''): uri = '/api/users' request = self.request_handler.make_request(uri=uri, request_type='GET', output_format=output_format) return request def get_plextv_user_details(self, output_format=''): uri = '/users/account' request = self.request_handler.make_request(uri=uri, request_type='GET', output_format=output_format) return request def get_plextv_devices_list(self, output_format=''): uri = '/devices.xml' request = self.request_handler.make_request(uri=uri, request_type='GET', output_format=output_format) return request def get_plextv_server_list(self, output_format=''): uri = '/pms/servers.xml' request = self.request_handler.make_request(uri=uri, request_type='GET', output_format=output_format) return request def get_plextv_shared_servers(self, machine_id='', output_format=''): uri = '/api/servers/%s/shared_servers' % machine_id request = self.request_handler.make_request(uri=uri, request_type='GET', output_format=output_format) return request def get_plextv_sync_lists(self, machine_id='', output_format=''): uri = '/servers/%s/sync_lists' % machine_id request = self.request_handler.make_request(uri=uri, request_type='GET', output_format=output_format) return request def get_plextv_resources(self, include_https=False, output_format=''): if include_https: uri = '/api/resources?includeHttps=1' else: uri = '/api/resources' request = self.request_handler.make_request(uri=uri, request_type='GET', output_format=output_format) return request def get_plextv_downloads(self, plexpass=False, output_format=''): if plexpass: uri = '/api/downloads/5.json?channel=plexpass' else: uri = '/api/downloads/1.json' request = self.request_handler.make_request(uri=uri, request_type='GET', output_format=output_format) return request def delete_plextv_device(self, device_id='', output_format=''): uri = '/devices/%s.xml' % device_id request = self.request_handler.make_request(uri=uri, request_type='DELETE', output_format=output_format) return request def delete_plextv_device_sync_lists(self, client_id='', output_format=''): uri = '/devices/%s/sync_items' % client_id request = self.request_handler.make_request(uri=uri, request_type='GET', output_format=output_format) return request def delete_plextv_sync(self, client_id='', sync_id='', output_format=''): uri = '/devices/%s/sync_items/%s' % (client_id, sync_id) request = self.request_handler.make_request(uri=uri, request_type='DELETE', output_format=output_format) return request def cloud_server_status(self, output_format=''): uri = '/api/v2/cloud_server' request = self.request_handler.make_request(uri=uri, request_type='GET', output_format=output_format) return request def get_full_users_list(self): own_account = self.get_plextv_user_details(output_format='xml') friends_list = self.get_plextv_friends(output_format='xml') shared_servers = self.get_plextv_shared_servers(machine_id=plexpy.CONFIG.PMS_IDENTIFIER, output_format='xml') users_list = [] try: xml_head = own_account.getElementsByTagName('user') except Exception as e: logger.warn("Tautulli PlexTV :: Unable to parse own account XML for get_full_users_list: %s." % e) return [] for a in xml_head: own_details = {"user_id": helpers.get_xml_attr(a, 'id'), "username": helpers.get_xml_attr(a, 'username'), "thumb": helpers.get_xml_attr(a, 'thumb'), "email": helpers.get_xml_attr(a, 'email'), "is_admin": 1, "is_home_user": helpers.get_xml_attr(a, 'home'), "is_allow_sync": 1, "is_restricted": helpers.get_xml_attr(a, 'restricted'), "filter_all": helpers.get_xml_attr(a, 'filterAll'), "filter_movies": helpers.get_xml_attr(a, 'filterMovies'), "filter_tv": helpers.get_xml_attr(a, 'filterTelevision'), "filter_music": helpers.get_xml_attr(a, 'filterMusic'), "filter_photos": helpers.get_xml_attr(a, 'filterPhotos'), "user_token": helpers.get_xml_attr(a, 'authToken'), "server_token": helpers.get_xml_attr(a, 'authToken'), "shared_libraries": None, } users_list.append(own_details) try: xml_head = friends_list.getElementsByTagName('User') except Exception as e: logger.warn("Tautulli PlexTV :: Unable to parse friends list XML for get_full_users_list: %s." % e) return [] for a in xml_head: friend = {"user_id": helpers.get_xml_attr(a, 'id'), "username": helpers.get_xml_attr(a, 'title'), "thumb": helpers.get_xml_attr(a, 'thumb'), "email": helpers.get_xml_attr(a, 'email'), "is_admin": 0, "is_home_user": helpers.get_xml_attr(a, 'home'), "is_allow_sync": helpers.get_xml_attr(a, 'allowSync'), "is_restricted": helpers.get_xml_attr(a, 'restricted'), "filter_all": helpers.get_xml_attr(a, 'filterAll'), "filter_movies": helpers.get_xml_attr(a, 'filterMovies'), "filter_tv": helpers.get_xml_attr(a, 'filterTelevision'), "filter_music": helpers.get_xml_attr(a, 'filterMusic'), "filter_photos": helpers.get_xml_attr(a, 'filterPhotos') } users_list.append(friend) try: xml_head = shared_servers.getElementsByTagName('SharedServer') except Exception as e: logger.warn("Tautulli PlexTV :: Unable to parse shared server list XML for get_full_users_list: %s." % e) return [] user_map = {} for a in xml_head: user_id = helpers.get_xml_attr(a, 'userID') server_token = helpers.get_xml_attr(a, 'accessToken') sections = a.getElementsByTagName('Section') shared_libraries = [helpers.get_xml_attr(s, 'key') for s in sections if helpers.get_xml_attr(s, 'shared') == '1'] user_map[user_id] = {'server_token': server_token, 'shared_libraries': shared_libraries} for u in users_list: d = user_map.get(u['user_id'], {}) u.update(d) return users_list def get_synced_items(self, machine_id=None, client_id_filter=None, user_id_filter=None, rating_key_filter=None, sync_id_filter=None): if not machine_id: machine_id = plexpy.CONFIG.PMS_IDENTIFIER if isinstance(rating_key_filter, list): rating_key_filter = [str(k) for k in rating_key_filter] elif rating_key_filter: rating_key_filter = [str(rating_key_filter)] if isinstance(user_id_filter, list): user_id_filter = [str(k) for k in user_id_filter] elif user_id_filter: user_id_filter = [str(user_id_filter)] sync_list = self.get_plextv_sync_lists(machine_id, output_format='xml') user_data = users.Users() synced_items = [] try: xml_head = sync_list.getElementsByTagName('SyncList') except Exception as e: logger.warn("Tautulli PlexTV :: Unable to parse XML for get_synced_items: %s." % e) return {} for a in xml_head: client_id = helpers.get_xml_attr(a, 'clientIdentifier') # Filter by client_id if client_id_filter and str(client_id_filter) != client_id: continue sync_list_id = helpers.get_xml_attr(a, 'id') sync_device = a.getElementsByTagName('Device') for device in sync_device: device_user_id = helpers.get_xml_attr(device, 'userID') try: device_username = user_data.get_details(user_id=device_user_id)['username'] device_friendly_name = user_data.get_details(user_id=device_user_id)['friendly_name'] except: device_username = '' device_friendly_name = '' device_name = helpers.get_xml_attr(device, 'name') device_product = helpers.get_xml_attr(device, 'product') device_product_version = helpers.get_xml_attr(device, 'productVersion') device_platform = helpers.get_xml_attr(device, 'platform') device_platform_version = helpers.get_xml_attr(device, 'platformVersion') device_type = helpers.get_xml_attr(device, 'device') device_model = helpers.get_xml_attr(device, 'model') device_last_seen = helpers.get_xml_attr(device, 'lastSeenAt') # Filter by user_id if user_id_filter and device_user_id not in user_id_filter: continue for synced in a.getElementsByTagName('SyncItems'): sync_item = synced.getElementsByTagName('SyncItem') for item in sync_item: for location in item.getElementsByTagName('Location'): clean_uri = helpers.get_xml_attr(location, 'uri').split('%2F') rating_key = next((clean_uri[(idx + 1) % len(clean_uri)] for idx, item in enumerate(clean_uri) if item == 'metadata'), None) # Filter by rating_key if rating_key_filter and rating_key not in rating_key_filter: continue sync_id = helpers.get_xml_attr(item, 'id') # Filter by sync_id if sync_id_filter and str(sync_id_filter) != sync_id: continue sync_version = helpers.get_xml_attr(item, 'version') sync_root_title = helpers.get_xml_attr(item, 'rootTitle') sync_title = helpers.get_xml_attr(item, 'title') sync_metadata_type = helpers.get_xml_attr(item, 'metadataType') sync_content_type = helpers.get_xml_attr(item, 'contentType') for status in item.getElementsByTagName('Status'): status_failure_code = helpers.get_xml_attr(status, 'failureCode') status_failure = helpers.get_xml_attr(status, 'failure') status_state = helpers.get_xml_attr(status, 'state') status_item_count = helpers.get_xml_attr(status, 'itemsCount') status_item_complete_count = helpers.get_xml_attr(status, 'itemsCompleteCount') status_item_downloaded_count = helpers.get_xml_attr(status, 'itemsDownloadedCount') status_item_ready_count = helpers.get_xml_attr(status, 'itemsReadyCount') status_item_successful_count = helpers.get_xml_attr(status, 'itemsSuccessfulCount') status_total_size = helpers.get_xml_attr(status, 'totalSize') status_item_download_percent_complete = helpers.get_percent( status_item_downloaded_count, status_item_count) for settings in item.getElementsByTagName('MediaSettings'): settings_video_bitrate = helpers.get_xml_attr(settings, 'maxVideoBitrate') settings_video_quality = helpers.get_xml_attr(settings, 'videoQuality') settings_video_resolution = helpers.get_xml_attr(settings, 'videoResolution') settings_audio_boost = helpers.get_xml_attr(settings, 'audioBoost') settings_audio_bitrate = helpers.get_xml_attr(settings, 'musicBitrate') settings_photo_quality = helpers.get_xml_attr(settings, 'photoQuality') settings_photo_resolution = helpers.get_xml_attr(settings, 'photoResolution') sync_details = {"device_name": device_name, "platform": device_platform, "user_id": device_user_id, "user": device_friendly_name, "username": device_username, "root_title": sync_root_title, "sync_title": sync_title, "metadata_type": sync_metadata_type, "content_type": sync_content_type, "rating_key": rating_key, "state": status_state, "item_count": status_item_count, "item_complete_count": status_item_complete_count, "item_downloaded_count": status_item_downloaded_count, "item_downloaded_percent_complete": status_item_download_percent_complete, "video_bitrate": settings_video_bitrate, "audio_bitrate": settings_audio_bitrate, "photo_quality": settings_photo_quality, "video_quality": settings_video_quality, "total_size": status_total_size, "failure": status_failure, "client_id": client_id, "sync_id": sync_id } synced_items.append(sync_details) return session.filter_session_info(synced_items, filter_key='user_id') def delete_sync(self, client_id, sync_id): logger.info("Tautulli PlexTV :: Deleting sync item '%s'." % sync_id) self.delete_plextv_sync(client_id=client_id, sync_id=sync_id) def get_server_connections(self, pms_identifier='', pms_ip='', pms_port=32400, include_https=True): if not pms_identifier: logger.error("Tautulli PlexTV :: Unable to retrieve server connections: no pms_identifier provided.") return {} plextv_resources = self.get_plextv_resources(include_https=include_https, output_format='xml') try: xml_head = plextv_resources.getElementsByTagName('Device') except Exception as e: logger.warn("Tautulli PlexTV :: Unable to parse XML for get_server_urls: %s." % e) return {} # Function to get all connections for a device def get_connections(device): conn = [] connections = device.getElementsByTagName('Connection') server = {'pms_identifier': helpers.get_xml_attr(device, 'clientIdentifier'), 'pms_name': helpers.get_xml_attr(device, 'name'), 'pms_version': helpers.get_xml_attr(device, 'productVersion'), 'pms_platform': helpers.get_xml_attr(device, 'platform'), 'pms_presence': helpers.get_xml_attr(device, 'presence'), 'pms_is_cloud': 1 if helpers.get_xml_attr(device, 'platform') == 'Cloud' else 0 } for c in connections: server_details = {'protocol': helpers.get_xml_attr(c, 'protocol'), 'address': helpers.get_xml_attr(c, 'address'), 'port': helpers.get_xml_attr(c, 'port'), 'uri': helpers.get_xml_attr(c, 'uri'), 'local': helpers.get_xml_attr(c, 'local') } conn.append(server_details) server['connections'] = conn return server server = {} # Try to match the device for a in xml_head: if helpers.get_xml_attr(a, 'clientIdentifier') == pms_identifier: server = get_connections(a) break # Else no device match found if not server: # Try to match the PMS_IP and PMS_PORT for a in xml_head: if helpers.get_xml_attr(a, 'provides') == 'server': connections = a.getElementsByTagName('Connection') for connection in connections: if helpers.get_xml_attr(connection, 'address') == pms_ip and \ helpers.get_xml_attr(connection, 'port') == str(pms_port): server = get_connections(a) break if server.get('connections'): break return server def get_server_times(self): servers = self.get_plextv_server_list(output_format='xml') server_times = {} try: xml_head = servers.getElementsByTagName('Server') except Exception as e: logger.warn("Tautulli PlexTV :: Unable to parse XML for get_server_times: %s." % e) return {} for a in xml_head: if helpers.get_xml_attr(a, 'machineIdentifier') == plexpy.CONFIG.PMS_IDENTIFIER: server_times = {"created_at": helpers.get_xml_attr(a, 'createdAt'), "updated_at": helpers.get_xml_attr(a, 'updatedAt'), "version": helpers.get_xml_attr(a, 'version') } break return server_times def discover(self, include_cloud=True, all_servers=False): """ Query plex for all servers online. Returns the ones you own in a selectize format """ # Try to discover localhost server local_machine_identifier = None request_handler = http_handler.HTTPHandler(urls='http://127.0.0.1:32400', timeout=1, ssl_verify=False, silent=True) request = request_handler.make_request(uri='/identity', request_type='GET', output_format='xml') if request: xml_head = request.getElementsByTagName('MediaContainer')[0] local_machine_identifier = xml_head.getAttribute('machineIdentifier') local_server = {'httpsRequired': '0', 'clientIdentifier': local_machine_identifier, 'label': 'Local', 'ip': '127.0.0.1', 'port': '32400', 'uri': 'http://127.0.0.1:32400', 'local': '1', 'value': '127.0.0.1:32400', 'is_cloud': False } servers = self.get_plextv_resources(include_https=True, output_format='xml') clean_servers = [] try: xml_head = servers.getElementsByTagName('MediaContainer') except Exception as e: logger.warn("Tautulli PlexTV :: Failed to get servers from plex: %s." % e) return [] for a in xml_head: if a.getAttribute('size'): if a.getAttribute('size') == '0': return [] if a.getElementsByTagName('Device'): devices = a.getElementsByTagName('Device') for d in devices: if helpers.get_xml_attr(d, 'presence') == '1' and \ helpers.get_xml_attr(d, 'owned') == '1' and \ helpers.get_xml_attr(d, 'provides') == 'server': is_cloud = (helpers.get_xml_attr(d, 'platform').lower() == 'cloud') if not include_cloud and is_cloud: continue connections = d.getElementsByTagName('Connection') for c in connections: if not all_servers: # If this is a remote server don't show any local IPs. if helpers.get_xml_attr(d, 'publicAddressMatches') == '0' and \ helpers.get_xml_attr(c, 'local') == '1': continue # If this is a local server don't show any remote IPs. if helpers.get_xml_attr(d, 'publicAddressMatches') == '1' and \ helpers.get_xml_attr(c, 'local') == '0': continue if helpers.get_xml_attr(d, 'clientIdentifier') == local_machine_identifier: local_server['httpsRequired'] = helpers.get_xml_attr(d, 'httpsRequired') local_server['label'] = helpers.get_xml_attr(d, 'name') clean_servers.append(local_server) local_machine_identifier = None server = {'httpsRequired': '1' if is_cloud else helpers.get_xml_attr(d, 'httpsRequired'), 'clientIdentifier': helpers.get_xml_attr(d, 'clientIdentifier'), 'label': helpers.get_xml_attr(d, 'name'), 'ip': helpers.get_xml_attr(c, 'address'), 'port': helpers.get_xml_attr(c, 'port'), 'uri': helpers.get_xml_attr(c, 'uri'), 'local': helpers.get_xml_attr(c, 'local'), 'value': helpers.get_xml_attr(c, 'address') + ':' + helpers.get_xml_attr(c, 'port'), 'is_cloud': is_cloud } clean_servers.append(server) if local_machine_identifier: clean_servers.append(local_server) clean_servers.sort(key=lambda s: (s['label'], -int(s['local']), s['ip'])) return clean_servers def get_plex_downloads(self): logger.debug("Tautulli PlexTV :: Retrieving current server version.") pms_connect = pmsconnect.PmsConnect() pms_connect.set_server_version() update_channel = pms_connect.get_server_update_channel() logger.debug("Tautulli PlexTV :: Plex update channel is %s." % update_channel) plex_downloads = self.get_plextv_downloads(plexpass=(update_channel == 'beta')) try: available_downloads = json.loads(plex_downloads) except Exception as e: logger.warn("Tautulli PlexTV :: Unable to load JSON for get_plex_updates.") return {} # Get the updates for the platform pms_platform = common.PMS_PLATFORM_NAME_OVERRIDES.get(plexpy.CONFIG.PMS_PLATFORM, plexpy.CONFIG.PMS_PLATFORM) platform_downloads = available_downloads.get('computer').get(pms_platform) or \ available_downloads.get('nas').get(pms_platform) if not platform_downloads: logger.error("Tautulli PlexTV :: Unable to retrieve Plex updates: Could not match server platform: %s." % pms_platform) return {} v_old = helpers.cast_to_int("".join(v.zfill(4) for v in plexpy.CONFIG.PMS_VERSION.split('-')[0].split('.')[:4])) v_new = helpers.cast_to_int("".join(v.zfill(4) for v in platform_downloads.get('version', '').split('-')[0].split('.')[:4])) if not v_old: logger.error("Tautulli PlexTV :: Unable to retrieve Plex updates: Invalid current server version: %s." % plexpy.CONFIG.PMS_VERSION) return {} if not v_new: logger.error("Tautulli PlexTV :: Unable to retrieve Plex updates: Invalid new server version: %s." % platform_downloads.get('version')) return {} # Get proper download releases = platform_downloads.get('releases', [{}]) release = next((r for r in releases if r['distro'] == plexpy.CONFIG.PMS_UPDATE_DISTRO and r['build'] == plexpy.CONFIG.PMS_UPDATE_DISTRO_BUILD), releases[0]) download_info = {'update_available': v_new > v_old, 'platform': platform_downloads.get('name'), 'release_date': platform_downloads.get('release_date'), 'version': platform_downloads.get('version'), 'requirements': platform_downloads.get('requirements'), 'extra_info': platform_downloads.get('extra_info'), 'changelog_added': platform_downloads.get('items_added'), 'changelog_fixed': platform_downloads.get('items_fixed'), 'label': release.get('label'), 'distro': release.get('distro'), 'distro_build': release.get('build'), 'download_url': release.get('url'), } return download_info def get_plexpass_status(self): account_data = self.get_plextv_user_details(output_format='xml') try: subscription = account_data.getElementsByTagName('subscription') except Exception as e: logger.warn("Tautulli PlexTV :: Unable to parse XML for get_plexpass_status: %s." % e) return False if subscription and helpers.get_xml_attr(subscription[0], 'active') == '1': plexpy.CONFIG.__setattr__('PMS_PLEXPASS', 1) plexpy.CONFIG.write() return True else: logger.debug("Tautulli PlexTV :: Plex Pass subscription not found.") plexpy.CONFIG.__setattr__('PMS_PLEXPASS', 0) plexpy.CONFIG.write() return False def get_devices_list(self): devices = self.get_plextv_devices_list(output_format='xml') try: xml_head = devices.getElementsByTagName('Device') except Exception as e: logger.warn("Tautulli PlexTV :: Unable to parse XML for get_devices_list: %s." % e) return [] devices_list = [] for a in xml_head: device = {"device_name": helpers.get_xml_attr(a, 'name'), "product": helpers.get_xml_attr(a, 'product'), "product_version": helpers.get_xml_attr(a, 'productVersion'), "platform": helpers.get_xml_attr(a, 'platform'), "platform_version": helpers.get_xml_attr(a, 'platformVersion'), "device": helpers.get_xml_attr(a, 'device'), "model": helpers.get_xml_attr(a, 'model'), "vendor": helpers.get_xml_attr(a, 'vendor'), "provides": helpers.get_xml_attr(a, 'provides'), "device_identifier": helpers.get_xml_attr(a, 'clientIdentifier'), "device_id": helpers.get_xml_attr(a, 'id'), "token": helpers.get_xml_attr(a, 'token') } devices_list.append(device) return devices_list def get_cloud_server_status(self): cloud_status = self.cloud_server_status(output_format='xml') try: status_info = cloud_status.getElementsByTagName('info') except Exception as e: logger.warn("Tautulli PlexTV :: Unable to parse XML for get_cloud_server_status: %s." % e) return False for info in status_info: servers = info.getElementsByTagName('server') for s in servers: if helpers.get_xml_attr(s, 'address') == plexpy.CONFIG.PMS_IP: if helpers.get_xml_attr(info, 'running') == '1': return True else: return False def get_plex_account_details(self): account_data = self.get_plextv_user_details(output_format='xml') try: xml_head = account_data.getElementsByTagName('user') except Exception as e: logger.warn("Tautulli PlexTV :: Unable to parse XML for get_plex_account_details: %s." % e) return None for a in xml_head: account_details = {"user_id": helpers.get_xml_attr(a, 'id'), "username": helpers.get_xml_attr(a, 'username'), "thumb": helpers.get_xml_attr(a, 'thumb'), "email": helpers.get_xml_attr(a, 'email'), "is_home_user": helpers.get_xml_attr(a, 'home'), "is_restricted": helpers.get_xml_attr(a, 'restricted'), "filter_all": helpers.get_xml_attr(a, 'filterAll'), "filter_movies": helpers.get_xml_attr(a, 'filterMovies'), "filter_tv": helpers.get_xml_attr(a, 'filterTelevision'), "filter_music": helpers.get_xml_attr(a, 'filterMusic'), "filter_photos": helpers.get_xml_attr(a, 'filterPhotos'), "user_token": helpers.get_xml_attr(a, 'authToken') } return account_details