1# -*- coding: utf-8 -*- 2 3# This file is part of Tautulli. 4# 5# Tautulli is free software: you can redistribute it and/or modify 6# it under the terms of the GNU General Public License as published by 7# the Free Software Foundation, either version 3 of the License, or 8# (at your option) any later version. 9# 10# Tautulli is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13# GNU General Public License for more details. 14# 15# You should have received a copy of the GNU General Public License 16# along with Tautulli. If not, see <http://www.gnu.org/licenses/>. 17 18from __future__ import unicode_literals 19from future.builtins import next 20from future.builtins import str 21from future.builtins import object 22from future.moves.urllib.parse import unquote 23 24import json 25 26import plexpy 27if plexpy.PYTHON2: 28 import common 29 import helpers 30 import http_handler 31 import logger 32 import users 33 import pmsconnect 34 import session 35 from plex import Plex 36else: 37 from plexpy import common 38 from plexpy import helpers 39 from plexpy import http_handler 40 from plexpy import logger 41 from plexpy import users 42 from plexpy import pmsconnect 43 from plexpy import session 44 from plexpy.plex import Plex 45 46 47def get_server_resources(return_presence=False, return_server=False, return_info=False, **kwargs): 48 if not return_presence and not return_info: 49 logger.info("Tautulli PlexTV :: Requesting resources for server...") 50 51 server = {'pms_name': plexpy.CONFIG.PMS_NAME, 52 'pms_version': plexpy.CONFIG.PMS_VERSION, 53 'pms_platform': plexpy.CONFIG.PMS_PLATFORM, 54 'pms_ip': plexpy.CONFIG.PMS_IP, 55 'pms_port': plexpy.CONFIG.PMS_PORT, 56 'pms_ssl': plexpy.CONFIG.PMS_SSL, 57 'pms_is_remote': plexpy.CONFIG.PMS_IS_REMOTE, 58 'pms_is_cloud': plexpy.CONFIG.PMS_IS_CLOUD, 59 'pms_url': plexpy.CONFIG.PMS_URL, 60 'pms_url_manual': plexpy.CONFIG.PMS_URL_MANUAL, 61 'pms_identifier': plexpy.CONFIG.PMS_IDENTIFIER, 62 'pms_plexpass': plexpy.CONFIG.PMS_PLEXPASS 63 } 64 65 if return_info: 66 return server 67 68 if kwargs: 69 server.update(kwargs) 70 for k in ['pms_ssl', 'pms_is_remote', 'pms_is_cloud', 'pms_url_manual']: 71 server[k] = int(server[k]) 72 73 if server['pms_url_manual'] and server['pms_ssl'] or server['pms_is_cloud']: 74 scheme = 'https' 75 else: 76 scheme = 'http' 77 78 fallback_url = '{scheme}://{hostname}:{port}'.format(scheme=scheme, 79 hostname=server['pms_ip'], 80 port=server['pms_port']) 81 82 plex_tv = PlexTV() 83 result = plex_tv.get_server_connections(pms_identifier=server['pms_identifier'], 84 pms_ip=server['pms_ip'], 85 pms_port=server['pms_port'], 86 include_https=server['pms_ssl']) 87 88 if result: 89 connections = result.pop('connections', []) 90 server.update(result) 91 presence = server.pop('pms_presence', 0) 92 else: 93 connections = [] 94 presence = 0 95 96 if return_presence: 97 return presence 98 99 plexpass = plex_tv.get_plexpass_status() 100 server['pms_plexpass'] = int(plexpass) 101 102 # Only need to retrieve PMS_URL if using SSL 103 if not server['pms_url_manual'] and server['pms_ssl']: 104 if connections: 105 if server['pms_is_remote']: 106 # Get all remote connections 107 conns = [c for c in connections if 108 c['local'] == '0' and ('plex.direct' in c['uri'] or 'plex.service' in c['uri'])] 109 else: 110 # Get all local connections 111 conns = [c for c in connections if 112 c['local'] == '1' and ('plex.direct' in c['uri'] or 'plex.service' in c['uri'])] 113 114 if conns: 115 # Get connection with matching address, otherwise return first connection 116 conn = next((c for c in conns if c['address'] == server['pms_ip'] 117 and c['port'] == str(server['pms_port'])), conns[0]) 118 server['pms_url'] = conn['uri'] 119 logger.info("Tautulli PlexTV :: Server URL retrieved.") 120 121 # get_server_urls() failed or PMS_URL not found, fallback url doesn't use SSL 122 if not server['pms_url']: 123 server['pms_url'] = fallback_url 124 logger.warn("Tautulli PlexTV :: Unable to retrieve server URLs. Using user-defined value without SSL.") 125 126 # Not using SSL, remote has no effect 127 else: 128 server['pms_url'] = fallback_url 129 logger.info("Tautulli PlexTV :: Using user-defined URL.") 130 131 if return_server: 132 return server 133 134 logger.info("Tautulli PlexTV :: Selected server: %s (%s) (%s - Version %s)", 135 server['pms_name'], server['pms_url'], server['pms_platform'], server['pms_version']) 136 137 plexpy.CONFIG.process_kwargs(server) 138 plexpy.CONFIG.write() 139 140 141class PlexTV(object): 142 """ 143 Plex.tv authentication 144 """ 145 146 def __init__(self, username=None, password=None, token=None, headers=None): 147 self.username = username 148 self.password = password 149 self.token = token 150 151 self.urls = 'https://plex.tv' 152 self.timeout = plexpy.CONFIG.PMS_TIMEOUT 153 self.ssl_verify = plexpy.CONFIG.VERIFY_SSL_CERT 154 155 if self.username is None and self.password is None: 156 if not self.token: 157 # Check if we should use the admin token, or the guest server token 158 if session.get_session_user_id(): 159 user_data = users.Users() 160 user_tokens = user_data.get_tokens(user_id=session.get_session_user_id()) 161 self.token = user_tokens['server_token'] 162 else: 163 self.token = plexpy.CONFIG.PMS_TOKEN 164 165 if not self.token: 166 logger.error("Tautulli PlexTV :: PlexTV called, but no token provided.") 167 return 168 169 self.request_handler = http_handler.HTTPHandler(urls=self.urls, 170 token=self.token, 171 timeout=self.timeout, 172 ssl_verify=self.ssl_verify, 173 headers=headers) 174 175 def get_server_token(self): 176 servers = self.get_plextv_resources(output_format='xml') 177 server_token = '' 178 179 try: 180 xml_head = servers.getElementsByTagName('Device') 181 except Exception as e: 182 logger.warn("Tautulli PlexTV :: Unable to parse XML for get_server_token: %s." % e) 183 return None 184 185 for a in xml_head: 186 if helpers.get_xml_attr(a, 'clientIdentifier') == plexpy.CONFIG.PMS_IDENTIFIER \ 187 and 'server' in helpers.get_xml_attr(a, 'provides'): 188 server_token = helpers.get_xml_attr(a, 'accessToken') 189 break 190 191 return server_token 192 193 def get_plextv_pin(self, pin='', output_format=''): 194 if pin: 195 uri = '/api/v2/pins/' + pin 196 request = self.request_handler.make_request(uri=uri, 197 request_type='GET', 198 output_format=output_format, 199 no_token=True) 200 else: 201 uri = '/api/v2/pins?strong=true' 202 request = self.request_handler.make_request(uri=uri, 203 request_type='POST', 204 output_format=output_format, 205 no_token=True) 206 return request 207 208 def get_pin(self, pin=''): 209 plextv_response = self.get_plextv_pin(pin=pin, 210 output_format='xml') 211 212 if plextv_response: 213 try: 214 xml_head = plextv_response.getElementsByTagName('pin') 215 if xml_head: 216 pin = {'id': xml_head[0].getAttribute('id'), 217 'code': xml_head[0].getAttribute('code'), 218 'token': xml_head[0].getAttribute('authToken') 219 } 220 return pin 221 else: 222 logger.warn("Tautulli PlexTV :: Could not get Plex authentication pin.") 223 return None 224 225 except Exception as e: 226 logger.warn("Tautulli PlexTV :: Unable to parse XML for get_pin: %s." % e) 227 return None 228 229 else: 230 return None 231 232 def get_plextv_friends(self, output_format=''): 233 uri = '/api/users' 234 request = self.request_handler.make_request(uri=uri, 235 request_type='GET', 236 output_format=output_format) 237 238 return request 239 240 def get_plextv_user_details(self, output_format=''): 241 uri = '/users/account' 242 request = self.request_handler.make_request(uri=uri, 243 request_type='GET', 244 output_format=output_format) 245 246 return request 247 248 def get_plextv_devices_list(self, output_format=''): 249 uri = '/devices.xml' 250 request = self.request_handler.make_request(uri=uri, 251 request_type='GET', 252 output_format=output_format) 253 254 return request 255 256 def get_plextv_server_list(self, output_format=''): 257 uri = '/pms/servers.xml' 258 request = self.request_handler.make_request(uri=uri, 259 request_type='GET', 260 output_format=output_format) 261 262 return request 263 264 def get_plextv_shared_servers(self, machine_id='', output_format=''): 265 uri = '/api/servers/%s/shared_servers' % machine_id 266 request = self.request_handler.make_request(uri=uri, 267 request_type='GET', 268 output_format=output_format) 269 270 return request 271 272 def get_plextv_sync_lists(self, machine_id='', output_format=''): 273 uri = '/servers/%s/sync_lists' % machine_id 274 request = self.request_handler.make_request(uri=uri, 275 request_type='GET', 276 output_format=output_format) 277 278 return request 279 280 def get_plextv_resources(self, include_https=False, output_format=''): 281 if include_https: 282 uri = '/api/resources?includeHttps=1' 283 else: 284 uri = '/api/resources' 285 request = self.request_handler.make_request(uri=uri, 286 request_type='GET', 287 output_format=output_format) 288 289 return request 290 291 def get_plextv_downloads(self, plexpass=False, output_format=''): 292 if plexpass: 293 uri = '/api/downloads/5.json?channel=plexpass' 294 else: 295 uri = '/api/downloads/1.json' 296 request = self.request_handler.make_request(uri=uri, 297 request_type='GET', 298 output_format=output_format) 299 300 return request 301 302 def delete_plextv_device(self, device_id='', output_format=''): 303 uri = '/devices/%s.xml' % device_id 304 request = self.request_handler.make_request(uri=uri, 305 request_type='DELETE', 306 output_format=output_format) 307 308 return request 309 310 def delete_plextv_device_sync_lists(self, client_id='', output_format=''): 311 uri = '/devices/%s/sync_items' % client_id 312 request = self.request_handler.make_request(uri=uri, 313 request_type='GET', 314 output_format=output_format) 315 316 return request 317 318 def delete_plextv_sync(self, client_id='', sync_id=''): 319 uri = '/devices/%s/sync_items/%s' % (client_id, sync_id) 320 request = self.request_handler.make_request(uri=uri, 321 request_type='DELETE', 322 return_response=True) 323 324 return request 325 326 def cloud_server_status(self, output_format=''): 327 uri = '/api/v2/cloud_server' 328 request = self.request_handler.make_request(uri=uri, 329 request_type='GET', 330 output_format=output_format) 331 332 return request 333 334 def get_plextv_geoip(self, ip_address='', output_format=''): 335 uri = '/api/v2/geoip?ip_address=%s' % ip_address 336 request = self.request_handler.make_request(uri=uri, 337 request_type='GET', 338 output_format=output_format) 339 340 return request 341 342 def get_full_users_list(self): 343 own_account = self.get_plextv_user_details(output_format='xml') 344 friends_list = self.get_plextv_friends(output_format='xml') 345 shared_servers = self.get_plextv_shared_servers(machine_id=plexpy.CONFIG.PMS_IDENTIFIER, 346 output_format='xml') 347 348 users_list = [] 349 350 try: 351 xml_head = own_account.getElementsByTagName('user') 352 except Exception as e: 353 logger.warn("Tautulli PlexTV :: Unable to parse own account XML for get_full_users_list: %s." % e) 354 return [] 355 356 for a in xml_head: 357 own_details = {"user_id": helpers.get_xml_attr(a, 'id'), 358 "username": helpers.get_xml_attr(a, 'username'), 359 "thumb": helpers.get_xml_attr(a, 'thumb'), 360 "email": helpers.get_xml_attr(a, 'email'), 361 "is_active": 1, 362 "is_admin": 1, 363 "is_home_user": helpers.get_xml_attr(a, 'home'), 364 "is_allow_sync": 1, 365 "is_restricted": helpers.get_xml_attr(a, 'restricted'), 366 "filter_all": helpers.get_xml_attr(a, 'filterAll'), 367 "filter_movies": helpers.get_xml_attr(a, 'filterMovies'), 368 "filter_tv": helpers.get_xml_attr(a, 'filterTelevision'), 369 "filter_music": helpers.get_xml_attr(a, 'filterMusic'), 370 "filter_photos": helpers.get_xml_attr(a, 'filterPhotos'), 371 "user_token": helpers.get_xml_attr(a, 'authToken'), 372 "server_token": helpers.get_xml_attr(a, 'authToken'), 373 "shared_libraries": None, 374 } 375 376 users_list.append(own_details) 377 378 try: 379 xml_head = friends_list.getElementsByTagName('User') 380 except Exception as e: 381 logger.warn("Tautulli PlexTV :: Unable to parse friends list XML for get_full_users_list: %s." % e) 382 return [] 383 384 for a in xml_head: 385 friend = {"user_id": helpers.get_xml_attr(a, 'id'), 386 "username": helpers.get_xml_attr(a, 'title'), 387 "thumb": helpers.get_xml_attr(a, 'thumb'), 388 "email": helpers.get_xml_attr(a, 'email'), 389 "is_active": 1, 390 "is_admin": 0, 391 "is_home_user": helpers.get_xml_attr(a, 'home'), 392 "is_allow_sync": helpers.get_xml_attr(a, 'allowSync'), 393 "is_restricted": helpers.get_xml_attr(a, 'restricted'), 394 "filter_all": helpers.get_xml_attr(a, 'filterAll'), 395 "filter_movies": helpers.get_xml_attr(a, 'filterMovies'), 396 "filter_tv": helpers.get_xml_attr(a, 'filterTelevision'), 397 "filter_music": helpers.get_xml_attr(a, 'filterMusic'), 398 "filter_photos": helpers.get_xml_attr(a, 'filterPhotos') 399 } 400 401 users_list.append(friend) 402 403 try: 404 xml_head = shared_servers.getElementsByTagName('SharedServer') 405 except Exception as e: 406 logger.warn("Tautulli PlexTV :: Unable to parse shared server list XML for get_full_users_list: %s." % e) 407 return [] 408 409 user_map = {} 410 for a in xml_head: 411 user_id = helpers.get_xml_attr(a, 'userID') 412 server_token = helpers.get_xml_attr(a, 'accessToken') 413 414 sections = a.getElementsByTagName('Section') 415 shared_libraries = [helpers.get_xml_attr(s, 'key') 416 for s in sections if helpers.get_xml_attr(s, 'shared') == '1'] 417 418 user_map[user_id] = {'server_token': server_token, 419 'shared_libraries': shared_libraries} 420 421 for u in users_list: 422 d = user_map.get(u['user_id'], {}) 423 u.update(d) 424 425 return users_list 426 427 def get_synced_items(self, machine_id=None, client_id_filter=None, user_id_filter=None, 428 rating_key_filter=None, sync_id_filter=None): 429 430 if not machine_id: 431 machine_id = plexpy.CONFIG.PMS_IDENTIFIER 432 433 if isinstance(rating_key_filter, list): 434 rating_key_filter = [str(k) for k in rating_key_filter] 435 elif rating_key_filter: 436 rating_key_filter = [str(rating_key_filter)] 437 438 if isinstance(user_id_filter, list): 439 user_id_filter = [str(k) for k in user_id_filter] 440 elif user_id_filter: 441 user_id_filter = [str(user_id_filter)] 442 443 sync_list = self.get_plextv_sync_lists(machine_id, output_format='xml') 444 user_data = users.Users() 445 446 synced_items = [] 447 448 try: 449 xml_head = sync_list.getElementsByTagName('SyncList') 450 except Exception as e: 451 logger.warn("Tautulli PlexTV :: Unable to parse XML for get_synced_items: %s." % e) 452 return {} 453 454 for a in xml_head: 455 client_id = helpers.get_xml_attr(a, 'clientIdentifier') 456 457 # Filter by client_id 458 if client_id_filter and str(client_id_filter) != client_id: 459 continue 460 461 sync_list_id = helpers.get_xml_attr(a, 'id') 462 sync_device = a.getElementsByTagName('Device') 463 464 for device in sync_device: 465 device_user_id = helpers.get_xml_attr(device, 'userID') 466 try: 467 device_username = user_data.get_details(user_id=device_user_id)['username'] 468 device_friendly_name = user_data.get_details(user_id=device_user_id)['friendly_name'] 469 except: 470 device_username = '' 471 device_friendly_name = '' 472 device_name = helpers.get_xml_attr(device, 'name') 473 device_product = helpers.get_xml_attr(device, 'product') 474 device_product_version = helpers.get_xml_attr(device, 'productVersion') 475 device_platform = helpers.get_xml_attr(device, 'platform') 476 device_platform_version = helpers.get_xml_attr(device, 'platformVersion') 477 device_type = helpers.get_xml_attr(device, 'device') 478 device_model = helpers.get_xml_attr(device, 'model') 479 device_last_seen = helpers.get_xml_attr(device, 'lastSeenAt') 480 481 # Filter by user_id 482 if user_id_filter and device_user_id not in user_id_filter: 483 continue 484 485 for synced in a.getElementsByTagName('SyncItems'): 486 sync_item = synced.getElementsByTagName('SyncItem') 487 for item in sync_item: 488 489 sync_media_type = None 490 rating_key = None 491 for location in item.getElementsByTagName('Location'): 492 location_uri = unquote(helpers.get_xml_attr(location, 'uri')) 493 494 if location_uri.startswith('library://'): 495 if 'collection' in location_uri: 496 sync_media_type = 'collection' 497 clean_uri = location_uri.split('/') 498 rating_key = next((j for i, j in zip(clean_uri[:-1], clean_uri[1:]) 499 if i in ('metadata', 'collections')), None) 500 501 elif location_uri.startswith('playlist://'): 502 sync_media_type = 'playlist' 503 tokens = users.Users().get_tokens(user_id=device_user_id) 504 if tokens['server_token']: 505 plex = Plex(token=tokens['server_token']) 506 for playlist in plex.PlexServer.playlists(): 507 if location_uri.endswith(playlist.guid): 508 rating_key = str(playlist.ratingKey) # String for backwards consistency 509 510 # Filter by rating_key 511 if rating_key_filter and rating_key not in rating_key_filter: 512 continue 513 514 sync_id = helpers.get_xml_attr(item, 'id') 515 516 # Filter by sync_id 517 if sync_id_filter and str(sync_id_filter) != sync_id: 518 continue 519 520 sync_version = helpers.get_xml_attr(item, 'version') 521 sync_root_title = helpers.get_xml_attr(item, 'rootTitle') 522 sync_title = helpers.get_xml_attr(item, 'title') 523 sync_metadata_type = helpers.get_xml_attr(item, 'metadataType') 524 sync_content_type = helpers.get_xml_attr(item, 'contentType') 525 526 for status in item.getElementsByTagName('Status'): 527 status_failure_code = helpers.get_xml_attr(status, 'failureCode') 528 status_failure = helpers.get_xml_attr(status, 'failure') 529 status_state = helpers.get_xml_attr(status, 'state') 530 status_item_count = helpers.get_xml_attr(status, 'itemsCount') 531 status_item_complete_count = helpers.get_xml_attr(status, 'itemsCompleteCount') 532 status_item_downloaded_count = helpers.get_xml_attr(status, 'itemsDownloadedCount') 533 status_item_ready_count = helpers.get_xml_attr(status, 'itemsReadyCount') 534 status_item_successful_count = helpers.get_xml_attr(status, 'itemsSuccessfulCount') 535 status_total_size = helpers.get_xml_attr(status, 'totalSize') 536 status_item_download_percent_complete = helpers.get_percent( 537 status_item_downloaded_count, status_item_count) 538 539 for settings in item.getElementsByTagName('MediaSettings'): 540 settings_video_bitrate = helpers.get_xml_attr(settings, 'maxVideoBitrate') 541 settings_video_quality = helpers.get_xml_attr(settings, 'videoQuality') 542 settings_video_resolution = helpers.get_xml_attr(settings, 'videoResolution') 543 settings_audio_boost = helpers.get_xml_attr(settings, 'audioBoost') 544 settings_audio_bitrate = helpers.get_xml_attr(settings, 'musicBitrate') 545 settings_photo_quality = helpers.get_xml_attr(settings, 'photoQuality') 546 settings_photo_resolution = helpers.get_xml_attr(settings, 'photoResolution') 547 548 sync_details = {"device_name": device_name, 549 "platform": device_platform, 550 "user_id": device_user_id, 551 "user": device_friendly_name, 552 "username": device_username, 553 "root_title": sync_root_title, 554 "sync_title": sync_title, 555 "metadata_type": sync_metadata_type, 556 "content_type": sync_content_type, 557 "rating_key": rating_key, 558 "state": status_state, 559 "item_count": status_item_count, 560 "item_complete_count": status_item_complete_count, 561 "item_downloaded_count": status_item_downloaded_count, 562 "item_downloaded_percent_complete": status_item_download_percent_complete, 563 "video_bitrate": settings_video_bitrate, 564 "audio_bitrate": settings_audio_bitrate, 565 "photo_quality": settings_photo_quality, 566 "video_quality": settings_video_quality, 567 "total_size": status_total_size, 568 "failure": status_failure, 569 "client_id": client_id, 570 "sync_id": sync_id, 571 "sync_media_type": sync_media_type 572 } 573 574 synced_items.append(sync_details) 575 576 return session.filter_session_info(synced_items, filter_key='user_id') 577 578 def delete_sync(self, client_id, sync_id): 579 logger.info("Tautulli PlexTV :: Deleting sync item '%s'." % sync_id) 580 response = self.delete_plextv_sync(client_id=client_id, sync_id=sync_id) 581 return response.ok 582 583 def get_server_connections(self, pms_identifier='', pms_ip='', pms_port=32400, include_https=True): 584 585 if not pms_identifier: 586 logger.error("Tautulli PlexTV :: Unable to retrieve server connections: no pms_identifier provided.") 587 return {} 588 589 plextv_resources = self.get_plextv_resources(include_https=include_https, 590 output_format='xml') 591 try: 592 xml_head = plextv_resources.getElementsByTagName('Device') 593 except Exception as e: 594 logger.warn("Tautulli PlexTV :: Unable to parse XML for get_server_urls: %s." % e) 595 return {} 596 597 # Function to get all connections for a device 598 def get_connections(device): 599 conn = [] 600 connections = device.getElementsByTagName('Connection') 601 602 server = {'pms_identifier': helpers.get_xml_attr(device, 'clientIdentifier'), 603 'pms_name': helpers.get_xml_attr(device, 'name'), 604 'pms_version': helpers.get_xml_attr(device, 'productVersion'), 605 'pms_platform': helpers.get_xml_attr(device, 'platform'), 606 'pms_presence': helpers.get_xml_attr(device, 'presence'), 607 'pms_is_cloud': 1 if helpers.get_xml_attr(device, 'platform') == 'Cloud' else 0 608 } 609 610 for c in connections: 611 server_details = {'protocol': helpers.get_xml_attr(c, 'protocol'), 612 'address': helpers.get_xml_attr(c, 'address'), 613 'port': helpers.get_xml_attr(c, 'port'), 614 'uri': helpers.get_xml_attr(c, 'uri'), 615 'local': helpers.get_xml_attr(c, 'local') 616 } 617 conn.append(server_details) 618 619 server['connections'] = conn 620 return server 621 622 server = {} 623 624 # Try to match the device 625 for a in xml_head: 626 if helpers.get_xml_attr(a, 'clientIdentifier') == pms_identifier: 627 server = get_connections(a) 628 break 629 630 # Else no device match found 631 if not server: 632 # Try to match the PMS_IP and PMS_PORT 633 for a in xml_head: 634 if helpers.get_xml_attr(a, 'provides') == 'server': 635 connections = a.getElementsByTagName('Connection') 636 637 for connection in connections: 638 if helpers.get_xml_attr(connection, 'address') == pms_ip and \ 639 helpers.get_xml_attr(connection, 'port') == str(pms_port): 640 server = get_connections(a) 641 break 642 643 if server.get('connections'): 644 break 645 646 return server 647 648 def get_server_times(self): 649 servers = self.get_plextv_server_list(output_format='xml') 650 server_times = {} 651 652 try: 653 xml_head = servers.getElementsByTagName('Server') 654 except Exception as e: 655 logger.warn("Tautulli PlexTV :: Unable to parse XML for get_server_times: %s." % e) 656 return {} 657 658 for a in xml_head: 659 if helpers.get_xml_attr(a, 'machineIdentifier') == plexpy.CONFIG.PMS_IDENTIFIER: 660 server_times = {"created_at": helpers.get_xml_attr(a, 'createdAt'), 661 "updated_at": helpers.get_xml_attr(a, 'updatedAt'), 662 "version": helpers.get_xml_attr(a, 'version') 663 } 664 break 665 666 return server_times 667 668 def discover(self, include_cloud=True, all_servers=False): 669 """ Query plex for all servers online. Returns the ones you own in a selectize format """ 670 671 # Try to discover localhost server 672 local_machine_identifier = None 673 request_handler = http_handler.HTTPHandler(urls='http://127.0.0.1:32400', timeout=1, 674 ssl_verify=False, silent=True) 675 request = request_handler.make_request(uri='/identity', request_type='GET', output_format='xml') 676 if request: 677 xml_head = request.getElementsByTagName('MediaContainer')[0] 678 local_machine_identifier = xml_head.getAttribute('machineIdentifier') 679 680 local_server = {'httpsRequired': '0', 681 'clientIdentifier': local_machine_identifier, 682 'label': 'Local', 683 'ip': '127.0.0.1', 684 'port': '32400', 685 'uri': 'http://127.0.0.1:32400', 686 'local': '1', 687 'value': '127.0.0.1:32400', 688 'is_cloud': False 689 } 690 691 servers = self.get_plextv_resources(include_https=True, output_format='xml') 692 clean_servers = [] 693 694 try: 695 xml_head = servers.getElementsByTagName('MediaContainer') 696 except Exception as e: 697 logger.warn("Tautulli PlexTV :: Failed to get servers from plex: %s." % e) 698 return [] 699 700 for a in xml_head: 701 if a.getAttribute('size'): 702 if a.getAttribute('size') == '0': 703 return [] 704 705 if a.getElementsByTagName('Device'): 706 devices = a.getElementsByTagName('Device') 707 708 for d in devices: 709 if helpers.get_xml_attr(d, 'presence') == '1' and \ 710 helpers.get_xml_attr(d, 'owned') == '1' and \ 711 helpers.get_xml_attr(d, 'provides') == 'server': 712 713 is_cloud = (helpers.get_xml_attr(d, 'platform').lower() == 'cloud') 714 if not include_cloud and is_cloud: 715 continue 716 717 connections = d.getElementsByTagName('Connection') 718 719 for c in connections: 720 if not all_servers: 721 # If this is a remote server don't show any local IPs. 722 if helpers.get_xml_attr(d, 'publicAddressMatches') == '0' and \ 723 helpers.get_xml_attr(c, 'local') == '1': 724 continue 725 726 # If this is a local server don't show any remote IPs. 727 if helpers.get_xml_attr(d, 'publicAddressMatches') == '1' and \ 728 helpers.get_xml_attr(c, 'local') == '0': 729 continue 730 731 if helpers.get_xml_attr(d, 'clientIdentifier') == local_machine_identifier: 732 local_server['httpsRequired'] = helpers.get_xml_attr(d, 'httpsRequired') 733 local_server['label'] = helpers.get_xml_attr(d, 'name') 734 clean_servers.append(local_server) 735 local_machine_identifier = None 736 737 server = {'httpsRequired': '1' if is_cloud else helpers.get_xml_attr(d, 'httpsRequired'), 738 'clientIdentifier': helpers.get_xml_attr(d, 'clientIdentifier'), 739 'label': helpers.get_xml_attr(d, 'name'), 740 'ip': helpers.get_xml_attr(c, 'address'), 741 'port': helpers.get_xml_attr(c, 'port'), 742 'uri': helpers.get_xml_attr(c, 'uri'), 743 'local': helpers.get_xml_attr(c, 'local'), 744 'value': helpers.get_xml_attr(c, 'address') + ':' + helpers.get_xml_attr(c, 'port'), 745 'is_cloud': is_cloud 746 } 747 clean_servers.append(server) 748 749 if local_machine_identifier: 750 clean_servers.append(local_server) 751 752 clean_servers.sort(key=lambda s: (s['label'], -int(s['local']), s['ip'])) 753 754 return clean_servers 755 756 def get_plex_downloads(self): 757 logger.debug("Tautulli PlexTV :: Retrieving current server version.") 758 759 pms_connect = pmsconnect.PmsConnect() 760 pms_connect.set_server_version() 761 762 update_channel = pms_connect.get_server_update_channel() 763 764 logger.debug("Tautulli PlexTV :: Plex update channel is %s." % update_channel) 765 plex_downloads = self.get_plextv_downloads(plexpass=(update_channel == 'beta')) 766 767 try: 768 available_downloads = json.loads(plex_downloads) 769 except Exception as e: 770 logger.warn("Tautulli PlexTV :: Unable to load JSON for get_plex_updates.") 771 return {} 772 773 # Get the updates for the platform 774 pms_platform = common.PMS_PLATFORM_NAME_OVERRIDES.get(plexpy.CONFIG.PMS_PLATFORM, plexpy.CONFIG.PMS_PLATFORM) 775 platform_downloads = available_downloads.get('computer').get(pms_platform) or \ 776 available_downloads.get('nas').get(pms_platform) 777 778 if not platform_downloads: 779 logger.error("Tautulli PlexTV :: Unable to retrieve Plex updates: Could not match server platform: %s." 780 % pms_platform) 781 return {} 782 783 v_old = helpers.cast_to_int("".join(v.zfill(4) for v in plexpy.CONFIG.PMS_VERSION.split('-')[0].split('.')[:4])) 784 v_new = helpers.cast_to_int("".join(v.zfill(4) for v in platform_downloads.get('version', '').split('-')[0].split('.')[:4])) 785 786 if not v_old: 787 logger.error("Tautulli PlexTV :: Unable to retrieve Plex updates: Invalid current server version: %s." 788 % plexpy.CONFIG.PMS_VERSION) 789 return {} 790 if not v_new: 791 logger.error("Tautulli PlexTV :: Unable to retrieve Plex updates: Invalid new server version: %s." 792 % platform_downloads.get('version')) 793 return {} 794 795 # Get proper download 796 releases = platform_downloads.get('releases', [{}]) 797 release = next((r for r in releases if r['distro'] == plexpy.CONFIG.PMS_UPDATE_DISTRO and 798 r['build'] == plexpy.CONFIG.PMS_UPDATE_DISTRO_BUILD), releases[0]) 799 800 download_info = {'update_available': v_new > v_old, 801 'platform': platform_downloads.get('name'), 802 'release_date': platform_downloads.get('release_date'), 803 'version': platform_downloads.get('version'), 804 'requirements': platform_downloads.get('requirements'), 805 'extra_info': platform_downloads.get('extra_info'), 806 'changelog_added': platform_downloads.get('items_added'), 807 'changelog_fixed': platform_downloads.get('items_fixed'), 808 'label': release.get('label'), 809 'distro': release.get('distro'), 810 'distro_build': release.get('build'), 811 'download_url': release.get('url'), 812 } 813 814 return download_info 815 816 def get_plexpass_status(self): 817 account_data = self.get_plextv_user_details(output_format='xml') 818 819 try: 820 subscription = account_data.getElementsByTagName('subscription') 821 except Exception as e: 822 logger.warn("Tautulli PlexTV :: Unable to parse XML for get_plexpass_status: %s." % e) 823 return False 824 825 if subscription and helpers.get_xml_attr(subscription[0], 'active') == '1': 826 plexpy.CONFIG.__setattr__('PMS_PLEXPASS', 1) 827 plexpy.CONFIG.write() 828 return True 829 else: 830 logger.debug("Tautulli PlexTV :: Plex Pass subscription not found.") 831 plexpy.CONFIG.__setattr__('PMS_PLEXPASS', 0) 832 plexpy.CONFIG.write() 833 return False 834 835 def get_devices_list(self): 836 devices = self.get_plextv_devices_list(output_format='xml') 837 838 try: 839 xml_head = devices.getElementsByTagName('Device') 840 except Exception as e: 841 logger.warn("Tautulli PlexTV :: Unable to parse XML for get_devices_list: %s." % e) 842 return [] 843 844 devices_list = [] 845 for a in xml_head: 846 device = {"device_name": helpers.get_xml_attr(a, 'name'), 847 "product": helpers.get_xml_attr(a, 'product'), 848 "product_version": helpers.get_xml_attr(a, 'productVersion'), 849 "platform": helpers.get_xml_attr(a, 'platform'), 850 "platform_version": helpers.get_xml_attr(a, 'platformVersion'), 851 "device": helpers.get_xml_attr(a, 'device'), 852 "model": helpers.get_xml_attr(a, 'model'), 853 "vendor": helpers.get_xml_attr(a, 'vendor'), 854 "provides": helpers.get_xml_attr(a, 'provides'), 855 "device_identifier": helpers.get_xml_attr(a, 'clientIdentifier'), 856 "device_id": helpers.get_xml_attr(a, 'id'), 857 "token": helpers.get_xml_attr(a, 'token') 858 } 859 devices_list.append(device) 860 861 return devices_list 862 863 def get_cloud_server_status(self): 864 cloud_status = self.cloud_server_status(output_format='xml') 865 866 try: 867 status_info = cloud_status.getElementsByTagName('info') 868 except Exception as e: 869 logger.warn("Tautulli PlexTV :: Unable to parse XML for get_cloud_server_status: %s." % e) 870 return False 871 872 for info in status_info: 873 servers = info.getElementsByTagName('server') 874 for s in servers: 875 if helpers.get_xml_attr(s, 'address') == plexpy.CONFIG.PMS_IP: 876 if helpers.get_xml_attr(info, 'running') == '1': 877 return True 878 else: 879 return False 880 881 def get_plex_account_details(self): 882 account_data = self.get_plextv_user_details(output_format='xml') 883 884 try: 885 xml_head = account_data.getElementsByTagName('user') 886 except Exception as e: 887 logger.warn("Tautulli PlexTV :: Unable to parse XML for get_plex_account_details: %s." % e) 888 return None 889 890 for a in xml_head: 891 account_details = {"user_id": helpers.get_xml_attr(a, 'id'), 892 "username": helpers.get_xml_attr(a, 'username'), 893 "thumb": helpers.get_xml_attr(a, 'thumb'), 894 "email": helpers.get_xml_attr(a, 'email'), 895 "is_home_user": helpers.get_xml_attr(a, 'home'), 896 "is_restricted": helpers.get_xml_attr(a, 'restricted'), 897 "filter_all": helpers.get_xml_attr(a, 'filterAll'), 898 "filter_movies": helpers.get_xml_attr(a, 'filterMovies'), 899 "filter_tv": helpers.get_xml_attr(a, 'filterTelevision'), 900 "filter_music": helpers.get_xml_attr(a, 'filterMusic'), 901 "filter_photos": helpers.get_xml_attr(a, 'filterPhotos'), 902 "user_token": helpers.get_xml_attr(a, 'authToken') 903 } 904 return account_details 905 906 def get_geoip_lookup(self, ip_address=''): 907 if not ip_address or not helpers.is_valid_ip(ip_address): 908 return 909 910 geoip_data = self.get_plextv_geoip(ip_address=ip_address, output_format='xml') 911 912 try: 913 xml_head = geoip_data.getElementsByTagName('location') 914 except Exception as e: 915 logger.warn(u"Tautulli PlexTV :: Unable to parse XML for get_geoip_lookup: %s." % e) 916 return None 917 918 for a in xml_head: 919 coordinates = helpers.get_xml_attr(a, 'coordinates').split(',') 920 latitude = longitude = None 921 if len(coordinates) == 2: 922 latitude, longitude = [helpers.cast_to_float(c) for c in coordinates] 923 924 geo_info = {"code": helpers.get_xml_attr(a, 'code') or None, 925 "country": helpers.get_xml_attr(a, 'country') or None, 926 "region": helpers.get_xml_attr(a, 'subdivisions') or None, 927 "city": helpers.get_xml_attr(a, 'city') or None, 928 "postal_code": helpers.get_xml_attr(a, 'postal_code') or None, 929 "timezone": helpers.get_xml_attr(a, 'time_zone') or None, 930 "latitude": latitude, 931 "longitude": longitude, 932 "continent": None, # keep for backwards compatibility with GeoLite2 933 "accuracy": None # keep for backwards compatibility with GeoLite2 934 } 935 936 return geo_info 937