From 554be92c3944d48280e4db5aff817f6289ff6998 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Mon, 23 Dec 2019 22:30:49 -0800 Subject: [PATCH 01/39] Update go to setting links --- .../interfaces/default/newsletter_config.html | 2 +- data/interfaces/default/notifier_config.html | 2 +- data/interfaces/default/settings.html | 36 +++++++++++-------- plexpy/notifiers.py | 32 ++++++++--------- 4 files changed, 39 insertions(+), 33 deletions(-) diff --git a/data/interfaces/default/newsletter_config.html b/data/interfaces/default/newsletter_config.html index 54ccfdf1..67f56302 100644 --- a/data/interfaces/default/newsletter_config.html +++ b/data/interfaces/default/newsletter_config.html @@ -271,7 +271,7 @@

Select an existing notification agent where the subject and body text will be sent.
- Note: Self-hosted newsletters must be enabled under Newsletters to include a link to the newsletter. + Note: Self-hosted newsletters must be enabled under Newsletters to include a link to the newsletter.

diff --git a/data/interfaces/default/notifier_config.html b/data/interfaces/default/notifier_config.html index 43bf3a91..e38e4d09 100644 --- a/data/interfaces/default/notifier_config.html +++ b/data/interfaces/default/notifier_config.html @@ -485,7 +485,7 @@ '
' + '' + '

Facebook requires HTTPS for authorization. ' + - 'Please enable HTTPS for Tautulli under Web Interface.

' + + 'Please enable HTTPS for Tautulli under Web Interface.

' + '
' ); $('#facebook_redirect_uri').val('HTTPS not enabled'); diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index 9e6fd230..bda52397 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -1008,7 +1008,7 @@

Note: The ${http_root}newsletter endpoint on your domain must be publicly accessible from the internet.

-

Warning: Public Tautulli domain not set under Web Interface.

+

Warning: Public Tautulli domain not set under Web Interface.

@@ -1022,7 +1022,7 @@

Select the authentication method to use for self-hosted newsletters.

-

Warning: Guest Access is not enabled under Web Interface.

+

Warning: Guest Access is not enabled under Web Interface.

@@ -1204,7 +1204,7 @@ Add a new newsletter agent, or configure an existing newsletter agent by clicking the settings icon on the right.

- Warning: The Image Hosting setting must be enabled for images to display on the newsletter. + Warning: The Image Hosting setting must be enabled for images to display on the newsletter.


@@ -1316,7 +1316,7 @@

Register a new device using a QR code, or configure an existing device by clicking the settings icon on the right.

-

The API must be enabled under Web Interface to use the app.

+

Warning: The API must be enabled under Web Interface to use the app.

Loading registered devices...
@@ -2773,20 +2773,26 @@ $(document).ready(function() { $('#allow_guest_access').click(function () { newsletterPasswordEnabled(); - }) + }); + + function gotoSetting(tab, setting){ + $("a[href=#tabs-" + tab + "]").click(); + if (setting) { + _setting = '#' + setting; + if ($(_setting).closest('.advanced-setting').length && !$('#menu_link_show_advanced_settings').hasClass('active')) { + $('#menu_link_show_advanced_settings').click() + } + var body_container = $('.body-container'); + var scroll_pos = setting ? body_container.scrollTop() + $(_setting).offset().top - 100 : 0; + body_container.animate({scrollTop: scroll_pos}); + $(_setting).closest('.form-group, .checkbox').delay(500).fadeOut().fadeIn('slow').fadeOut().fadeIn('slow'); + } + } $('body').on('click', 'a[data-tab-destination]', function () { var tab = $(this).data('tab-destination'); - $("a[href=#" + tab + "]").click(); - var scroll_destination = $(this).data('target'); - if (scroll_destination) { - if ($(scroll_destination).closest('.advanced-setting').length && !$('#menu_link_show_advanced_settings').hasClass('active')) { - $('#menu_link_show_advanced_settings').click() - } - var body_container = $('.body-container') - var scroll_pos = scroll_destination ? body_container.scrollTop() + $(scroll_destination).offset().top - 100 : 0; - body_container.animate({scrollTop: scroll_pos}); - } + var setting = $(this).data('target'); + gotoSetting(tab, setting) }); $('#resources-xml').on('tripleclick', function () { diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py index e2dad604..c7d61379 100644 --- a/plexpy/notifiers.py +++ b/plexpy/notifiers.py @@ -999,8 +999,8 @@ class ANDROIDAPP(Notifier): config_option.append({ 'label': 'Device', 'description': 'No devices registered. ' - 'Get the Android App and register a device.', + '' + 'Get the Android App and register a device.', 'input_type': 'help' }) else: @@ -1009,8 +1009,8 @@ class ANDROIDAPP(Notifier): 'value': self.config['device_id'], 'name': 'androidapp_device_id', 'description': 'Set your Android app device or ' - 'register a new device with Tautulli.', + '' + 'register a new device with Tautulli.', 'input_type': 'select', 'select_options': devices }) @@ -1264,8 +1264,8 @@ class DISCORD(Notifier): 'value': self.config['incl_card'], 'name': 'discord_incl_card', 'description': 'Include an info card with a poster and metadata with the notifications.
' - 'Note: Image Hosting ' + 'Note: Image Hosting ' 'must be enabled under the notifications settings tab.', 'input_type': 'checkbox' }, @@ -1639,8 +1639,8 @@ class FACEBOOK(Notifier): 'value': self.config['incl_card'], 'name': 'facebook_incl_card', 'description': 'Include an info card with a poster and metadata with the notifications.
' - 'Note: Image Hosting ' + 'Note: Image Hosting ' 'must be enabled under the notifications settings tab.', 'input_type': 'checkbox' }, @@ -1962,8 +1962,8 @@ class HIPCHAT(Notifier): 'value': self.config['incl_card'], 'name': 'hipchat_incl_card', 'description': 'Include an info card with a poster and metadata with the notifications.
' - 'Note: Image Hosting ' + 'Note: Image Hosting ' 'must be enabled under the notifications settings tab.
' 'Note: This will change the notification type to HTML and emoticons will no longer work.', 'input_type': 'checkbox' @@ -2184,8 +2184,8 @@ class JOIN(Notifier): 'value': self.config['incl_poster'], 'name': 'join_incl_poster', 'description': 'Include a poster with the notifications.
' - 'Note: Image Hosting ' + 'Note: Image Hosting ' 'must be enabled under the notifications settings tab.', 'input_type': 'checkbox' }, @@ -3348,8 +3348,8 @@ class SLACK(Notifier): 'value': self.config['incl_card'], 'name': 'slack_incl_card', 'description': 'Include an info card with a poster and metadata with the notifications.
' - 'Note: Image Hosting ' + 'Note: Image Hosting ' 'must be enabled under the notifications settings tab.', 'input_type': 'checkbox' }, @@ -3593,8 +3593,8 @@ class TWITTER(Notifier): 'value': self.config['incl_poster'], 'name': 'twitter_incl_poster', 'description': 'Include a poster with the notifications.
' - 'Note: Image Hosting ' + 'Note: Image Hosting ' 'must be enabled under the notifications settings tab.', 'input_type': 'checkbox' } From 41c9369b43409499fbef7aa1213118e7e9c56571 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Mon, 23 Dec 2019 23:58:36 -0800 Subject: [PATCH 02/39] Change GeoIP database install using license key --- .../default/configuration_table.html | 24 ------- data/interfaces/default/settings.html | 49 +++++++++++-- plexpy/config.py | 9 +++ plexpy/helpers.py | 71 ++++++++++++------- plexpy/webserve.py | 4 +- 5 files changed, 101 insertions(+), 56 deletions(-) diff --git a/data/interfaces/default/configuration_table.html b/data/interfaces/default/configuration_table.html index ed0fa2c3..989b1766 100644 --- a/data/interfaces/default/configuration_table.html +++ b/data/interfaces/default/configuration_table.html @@ -53,14 +53,6 @@ DOCUMENTATION :: END Newsletter Directory: ${plexpy.CONFIG.NEWSLETTER_DIR} - - GeoLite2 Database: - % if plexpy.CONFIG.GEOIP_DB: - ${plexpy.CONFIG.GEOIP_DB} | Reinstall / Update | Uninstall - % else: - Click here to install the GeoLite2 database. - % endif - % if plexpy.ARGS: Arguments: @@ -102,22 +94,6 @@ DOCUMENTATION :: END diff --git a/plexpy/config.py b/plexpy/config.py index 1b431e3c..104e3553 100644 --- a/plexpy/config.py +++ b/plexpy/config.py @@ -175,6 +175,7 @@ _CONFIG_DEFINITIONS = { 'FIRST_RUN_COMPLETE': (int, 'General', 0), 'FREEZE_DB': (int, 'General', 0), 'GEOIP_DB': (str, 'General', ''), + 'GEOIP_DB_INSTALLED': (int, 'General', 0), 'GET_FILE_SIZES': (int, 'General', 0), 'GET_FILE_SIZES_HOLD': (dict, 'General', {'section_ids': [], 'rating_keys': []}), 'GIT_BRANCH': (str, 'General', 'master'), @@ -289,6 +290,7 @@ _CONFIG_DEFINITIONS = { 'LOG_BLACKLIST': (int, 'General', 1), 'LOG_DIR': (str, 'General', ''), 'LOGGING_IGNORE_INTERVAL': (int, 'Monitoring', 120), + 'MAXMIND_LICENSE_KEY': (str, 'General', ''), 'METADATA_CACHE_SECONDS': (int, 'Advanced', 1800), 'MOVIE_LOGGING_ENABLE': (int, 'Monitoring', 1), 'MOVIE_NOTIFY_ENABLE': (int, 'Monitoring', 0), @@ -923,3 +925,10 @@ class Config(object): self.BUFFER_THRESHOLD = max(self.BUFFER_THRESHOLD, 10) self.CONFIG_VERSION = 13 + + if self.CONFIG_VERSION == 13: + self.GEOIP_DB_INSTALLED = int(bool(self.GEOIP_DB)) + if not self.GEOIP_DB: + self.GEOIP_DB = os.path.join(plexpy.DATA_DIR, 'GeoLite2-City.mmdb') + + self.CONFIG_VERSION = 14 diff --git a/plexpy/helpers.py b/plexpy/helpers.py index 4b831439..0d7be965 100644 --- a/plexpy/helpers.py +++ b/plexpy/helpers.py @@ -36,6 +36,7 @@ import re import shlex import socket import sys +import tarfile import time import unicodedata import urllib, urllib2 @@ -584,61 +585,79 @@ def is_valid_ip(address): def install_geoip_db(): - maxmind_url = 'http://geolite.maxmind.com/download/geoip/database/' - geolite2_gz = 'GeoLite2-City.mmdb.gz' - geolite2_md5 = 'GeoLite2-City.md5' - geolite2_db = geolite2_gz[:-3] - md5_checksum = '' + if not plexpy.CONFIG.MAXMIND_LICENSE_KEY: + logger.error(u"Tautulli Helpers :: Failed to download GeoLite2 database file from MaxMind: Missing MaxMindLicense Key") + return False + + maxmind_db = 'GeoLite2-City' + maxmind_url = 'https://download.maxmind.com/app/geoip_download?edition_id={db}&suffix={{suffix}}&license_key={key}'.format( + db=maxmind_db, key=plexpy.CONFIG.MAXMIND_LICENSE_KEY) + geolite2_db_url = maxmind_url.format(suffix='tar.gz') + geolite2_md5_url = maxmind_url.format(suffix='tar.gz.md5') + geolite2_gz = maxmind_db + '.tar.gz' + geolite2_md5 = geolite2_gz + '.md5' + geolite2_db = maxmind_db + '.mmdb' + geolite2_db_path = plexpy.CONFIG.GEOIP_DB or os.path.join(plexpy.DATA_DIR, geolite2_db) temp_gz = os.path.join(plexpy.CONFIG.CACHE_DIR, geolite2_gz) - geolite2_db = plexpy.CONFIG.GEOIP_DB or os.path.join(plexpy.DATA_DIR, geolite2_db) + temp_md5 = os.path.join(plexpy.CONFIG.CACHE_DIR, geolite2_md5) # Retrieve the GeoLite2 gzip file logger.debug(u"Tautulli Helpers :: Downloading GeoLite2 gzip file from MaxMind...") try: maxmind = urllib.URLopener() - maxmind.retrieve(maxmind_url + geolite2_gz, temp_gz) - md5_checksum = urllib2.urlopen(maxmind_url + geolite2_md5).read() + maxmind.retrieve(geolite2_db_url, temp_gz) + maxmind.retrieve(geolite2_md5_url, temp_md5) except Exception as e: logger.error(u"Tautulli Helpers :: Failed to download GeoLite2 gzip file from MaxMind: %s" % e) return False - # Extract the GeoLite2 database file - logger.debug(u"Tautulli Helpers :: Extracting GeoLite2 database...") - try: - with gzip.open(temp_gz, 'rb') as gz: - with open(geolite2_db, 'wb') as db: - db.write(gz.read()) - except Exception as e: - logger.error(u"Tautulli Helpers :: Failed to extract the GeoLite2 database: %s" % e) - return False - - # Check MD5 hash for GeoLite2 database file - logger.debug(u"Tautulli Helpers :: Checking MD5 checksum for GeoLite2 database...") + # Check MD5 hash for GeoLite2 tar.gz file + logger.debug(u"Tautulli Helpers :: Checking MD5 checksum for GeoLite2 gzip file...") try: hash_md5 = hashlib.md5() - with open(geolite2_db, 'rb') as f: + with open(temp_gz, 'rb') as f: for chunk in iter(lambda: f.read(4096), b""): hash_md5.update(chunk) md5_hash = hash_md5.hexdigest() + with open(temp_md5, 'r') as f: + md5_checksum = f.read() + if md5_hash != md5_checksum: logger.error(u"Tautulli Helpers :: MD5 checksum doesn't match for GeoLite2 database. " "Checksum: %s, file hash: %s" % (md5_checksum, md5_hash)) return False except Exception as e: - logger.error(u"Tautulli Helpers :: Failed to generate MD5 checksum for GeoLite2 database: %s" % e) + logger.error(u"Tautulli Helpers :: Failed to generate MD5 checksum for GeoLite2 gzip file: %s" % e) + return False + + # Extract the GeoLite2 database file + logger.debug(u"Tautulli Helpers :: Extracting GeoLite2 database...") + try: + with tarfile.open(temp_gz, 'r:gz') as tar: + for tarinfo in tar: + if tarinfo.isdir(): + member = tar.getmember(os.path.join(tarinfo.name, geolite2_db)) + mmdb = tar.extractfile(member) + with open(geolite2_db_path, 'wb') as db: + db.write(mmdb.read()) + break + except Exception as e: + logger.error(u"Tautulli Helpers :: Failed to extract the GeoLite2 database: %s" % e) return False # Delete temportary GeoLite2 gzip file logger.debug(u"Tautulli Helpers :: Deleting temporary GeoLite2 gzip file...") try: os.remove(temp_gz) + os.remove(temp_md5) except Exception as e: logger.warn(u"Tautulli Helpers :: Failed to remove temporary GeoLite2 gzip file: %s" % e) logger.debug(u"Tautulli Helpers :: GeoLite2 database installed successfully.") - plexpy.CONFIG.__setattr__('GEOIP_DB', geolite2_db) + plexpy.CONFIG.__setattr__('GEOIP_DB', geolite2_db_path) + plexpy.CONFIG.__setattr__('GEOIP_DB_INSTALLED', int(time.time())) plexpy.CONFIG.write() return True @@ -648,7 +667,7 @@ def uninstall_geoip_db(): logger.debug(u"Tautulli Helpers :: Uninstalling the GeoLite2 database...") try: os.remove(plexpy.CONFIG.GEOIP_DB) - plexpy.CONFIG.__setattr__('GEOIP_DB', '') + plexpy.CONFIG.__setattr__('GEOIP_DB_INSTALLED', 0) plexpy.CONFIG.write() except Exception as e: logger.error(u"Tautulli Helpers :: Failed to uninstall the GeoLite2 database: %s" % e) @@ -659,7 +678,7 @@ def uninstall_geoip_db(): def geoip_lookup(ip_address): - if not plexpy.CONFIG.GEOIP_DB: + if not plexpy.CONFIG.GEOIP_DB_INSTALLED: return 'GeoLite2 database not installed. Please install from the ' \ 'Settings page.' @@ -677,7 +696,7 @@ def geoip_lookup(ip_address): 'Settings page.' except maxminddb.InvalidDatabaseError as e: return 'Invalid GeoLite2 database. Please reinstall from the ' \ - 'Settings page.' + 'Settings page.' except geoip2.errors.AddressNotFoundError as e: return '%s' % e except Exception as e: diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 27e4ed36..272da3bd 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -2819,7 +2819,9 @@ class WebInterface(object): "newsletter_password": plexpy.CONFIG.NEWSLETTER_PASSWORD, "newsletter_inline_styles": checked(plexpy.CONFIG.NEWSLETTER_INLINE_STYLES), "newsletter_custom_dir": plexpy.CONFIG.NEWSLETTER_CUSTOM_DIR, - "win_sys_tray": checked(plexpy.CONFIG.WIN_SYS_TRAY) + "win_sys_tray": checked(plexpy.CONFIG.WIN_SYS_TRAY), + "geoip_db": plexpy.CONFIG.GEOIP_DB, + "maxmind_license_key": plexpy.CONFIG.MAXMIND_LICENSE_KEY } return serve_template(templatename="settings.html", title="Settings", config=config, kwargs=kwargs) From b83eb2e76322510af08c539cf44f63cfd0e4b0fe Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Wed, 25 Dec 2019 11:17:45 -0800 Subject: [PATCH 03/39] Add auto-updating of GeoLite2 database --- data/interfaces/default/settings.html | 46 ++++++++++++++++++++------- plexpy/__init__.py | 3 ++ plexpy/common.py | 3 +- plexpy/helpers.py | 27 +++++++++++++--- plexpy/webserve.py | 11 ++++--- 5 files changed, 69 insertions(+), 21 deletions(-) diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index 3e503579..3695f215 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -1178,20 +1178,25 @@

- Enter your MaxMind License Key. You can register for a license key here. + Enter your MaxMind License Key to install the GeoLite2 database. You can register for a license key here.

- ${docker_msg | n} + ${docker_msg | n}
-
- -
- - +
+
+ + + + +
+

+ The GeoLite2 database is used to geolocate IP addresses. Database last updated never. +

@@ -2818,24 +2823,43 @@ $(document).ready(function() { openPlexXML('/api/resources', true, {includeHttps: 1}); }); - if ("${kwargs.get('install_geoip')}" == 'true') { + if ("${kwargs.get('install_geoip')}" === 'true') { gotoSetting('notifications', 'geoip_db') } + if ("${config['geoip_db_installed']}" > "0") { + $("#geoip_db_updated").text(moment("${config['geoip_db_installed']}", "X").fromNow()); + } + $("#install_geoip_db").click(function () { var msg = 'Are you sure you want to install the GeoLite2 database?

' + 'The database is used to lookup IP address geolocation info.
' + 'The database will be downloaded from MaxMind,
' + - 'and requires 100MB of free space to install in your Tautulli directory.
' + 'and requires 100MB of free space to install in your Tautulli directory.
'; var url = 'install_geoip_db'; - confirmAjaxCall(url, msg, null, 'Installing GeoLite2 database.', function () {$('#install_geoip_db').text('Update');}); + if ($(this).text() === 'Update') { + url += '?update=true'; + } + confirmAjaxCall(url, msg, null, 'Installing GeoLite2 database.', function (result) { + if (result.result === "success") { + $('#install_geoip_db').text('Update'); + $('#geoip_db_updated').text(moment(result.updated, "X").fromNow()); + } + getSchedulerTable(); + }); }); $("#uninstall_geoip_db").click(function () { var msg = 'Are you sure you want to uninstall the GeoLite2 database?

' + 'You will not be able to lookup IP address geolocation info.'; var url = 'uninstall_geoip_db'; - confirmAjaxCall(url, msg, null, 'Uninstalling GeoLite2 database.', function () {$('#install_geoip_db').text('Install');}); + confirmAjaxCall(url, msg, null, 'Uninstalling GeoLite2 database.', function (result) { + if (result.result === "success") { + $('#install_geoip_db').text('Install'); + $('#geoip_db_updated').text('never'); + } + getSchedulerTable(); + }); }); }); diff --git a/plexpy/__init__.py b/plexpy/__init__.py index c2e47ef7..70eaa19b 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -39,6 +39,7 @@ import activity_pinger import common import database import datafactory +import helpers import libraries import logger import mobile_app @@ -444,6 +445,8 @@ def initialize_scheduler(): hours=backup_hours, minutes=0, seconds=0, args=(True, True)) schedule_job(config.make_backup, 'Backup Tautulli config', hours=backup_hours, minutes=0, seconds=0, args=(True, True)) + schedule_job(helpers.update_geoip_db, 'Update GeoLite2 database', + hours=12 * bool(CONFIG.GEOIP_DB_INSTALLED > 1), minutes=0, seconds=0) if WS_CONNECTED and CONFIG.PMS_IP and CONFIG.PMS_TOKEN: schedule_job(plextv.get_server_resources, 'Refresh Plex server URLs', diff --git a/plexpy/common.py b/plexpy/common.py index fcd80536..cc4ca6a0 100644 --- a/plexpy/common.py +++ b/plexpy/common.py @@ -197,7 +197,8 @@ SCHEDULER_LIST = [ 'Refresh libraries list', 'Refresh Plex server URLs', 'Backup Tautulli database', - 'Backup Tautulli config' + 'Backup Tautulli config', + 'Update GeoLite2 database' ] DATE_TIME_FORMATS = [ diff --git a/plexpy/helpers.py b/plexpy/helpers.py index 0d7be965..099be9c4 100644 --- a/plexpy/helpers.py +++ b/plexpy/helpers.py @@ -584,7 +584,16 @@ def is_valid_ip(address): return False -def install_geoip_db(): +def update_geoip_db(): + if plexpy.CONFIG.GEOIP_DB_INSTALLED > 1: + logger.info(u"Tautulli Helpers :: Checking for GeoLite2 database updates.") + now = int(time.time()) + if now - plexpy.CONFIG.GEOIP_DB_INSTALLED >= 2592000: # 30 days + return install_geoip_db(update=True) + logger.info(u"Tautulli Helpers :: GeoLite2 database already updated within the last 30 days.") + + +def install_geoip_db(update=False): if not plexpy.CONFIG.MAXMIND_LICENSE_KEY: logger.error(u"Tautulli Helpers :: Failed to download GeoLite2 database file from MaxMind: Missing MaxMindLicense Key") return False @@ -655,25 +664,33 @@ def install_geoip_db(): except Exception as e: logger.warn(u"Tautulli Helpers :: Failed to remove temporary GeoLite2 gzip file: %s" % e) - logger.debug(u"Tautulli Helpers :: GeoLite2 database installed successfully.") plexpy.CONFIG.__setattr__('GEOIP_DB', geolite2_db_path) plexpy.CONFIG.__setattr__('GEOIP_DB_INSTALLED', int(time.time())) plexpy.CONFIG.write() - return True + logger.debug(u"Tautulli Helpers :: GeoLite2 database installed successfully.") + + if not update: + plexpy.schedule_job(update_geoip_db, 'Update GeoLite2 database', hours=12, minutes=0, seconds=0) + + return plexpy.CONFIG.GEOIP_DB_INSTALLED def uninstall_geoip_db(): logger.debug(u"Tautulli Helpers :: Uninstalling the GeoLite2 database...") try: os.remove(plexpy.CONFIG.GEOIP_DB) - plexpy.CONFIG.__setattr__('GEOIP_DB_INSTALLED', 0) - plexpy.CONFIG.write() except Exception as e: logger.error(u"Tautulli Helpers :: Failed to uninstall the GeoLite2 database: %s" % e) return False + plexpy.CONFIG.__setattr__('GEOIP_DB_INSTALLED', 0) + plexpy.CONFIG.write() + logger.debug(u"Tautulli Helpers :: GeoLite2 database uninstalled successfully.") + + plexpy.schedule_job(update_geoip_db, 'Update GeoLite2 database', hours=0, minutes=0, seconds=0) + return True diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 272da3bd..427b1025 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -2821,6 +2821,7 @@ class WebInterface(object): "newsletter_custom_dir": plexpy.CONFIG.NEWSLETTER_CUSTOM_DIR, "win_sys_tray": checked(plexpy.CONFIG.WIN_SYS_TRAY), "geoip_db": plexpy.CONFIG.GEOIP_DB, + "geoip_db_installed": plexpy.CONFIG.GEOIP_DB_INSTALLED, "maxmind_license_key": plexpy.CONFIG.MAXMIND_LICENSE_KEY } @@ -3057,15 +3058,17 @@ class WebInterface(object): @cherrypy.tools.json_out() @requireAuth(member_of("admin")) @addtoapi() - def install_geoip_db(self, **kwargs): + def install_geoip_db(self, update=False, **kwargs): """ Downloads and installs the GeoLite2 database """ - result = helpers.install_geoip_db() + update = True if update == 'true' else False + + result = helpers.install_geoip_db(update=update) if result: - return {'result': 'success', 'message': 'GeoLite2 database installed successful.'} + return {'result': 'success', 'message': 'GeoLite2 database installed successful.', 'updated': result} else: - return {'result': 'error', 'message': 'GeoLite2 database install failed.'} + return {'result': 'error', 'message': 'GeoLite2 database install failed.', 'updated': 0} @cherrypy.expose @cherrypy.tools.json_out() From f958de2de646116fca7b1cd5d76e9ade899a289a Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Wed, 25 Dec 2019 11:41:53 -0800 Subject: [PATCH 04/39] Move 3rd Party API settings to new tab --- data/interfaces/default/settings.html | 110 +++++++++++++++----------- plexpy/notifiers.py | 12 +-- 2 files changed, 72 insertions(+), 50 deletions(-) diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index 3695f215..08e805e7 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -56,6 +56,7 @@
  • Notifications & Newsletters
  • Notification Agents
  • Newsletter Agents
  • +
  • 3rd Party APIs
  • Import & Backups
  • Tautulli Remote Android App beta
  • @@ -1063,12 +1064,60 @@

    Enter the full path to where newsletter files will be saved.

    +

    + +
    + +
    +
    -

    3rd Party APIs

    +

    Notification Agents

    +

    + Add a new notification agent, or configure an existing notification agent by clicking the settings icon on the right. +

    +

    + Please see the Notification Agents Guide for instructions on setting up each notification agent. +

    +
    +
    +
    Loading notification agents...
    +
    +
    + +
    + +
    + +
    +

    Newsletter Agents

    +
    + +

    + Add a new newsletter agent, or configure an existing newsletter agent by clicking the settings icon on the right. +

    +

    + Warning: The Image Hosting setting must be enabled for images to display on the newsletter. +

    +
    +
    +
    Loading newsletter agents...
    +
    +
    + +
    + +
    + +
    +

    Image Hosting

    +
    + +

    Image hosting is used to provide posters and artwork for some notification agents and newsletters.

    +
    - +
    @@ -1108,7 +1157,7 @@

    Note: The ${http_root}image endpoint on your domain must be publicly accessible from the internet.

    -

    Warning: Public Tautulli domain not set under Web Interface.

    +

    Warning: Public Tautulli domain not set under Web Interface.

    @@ -1151,6 +1200,13 @@

    + +
    +

    Metadata Lookups

    +
    + +

    Metadata lookups are used to provide additional metadata for notifications when available.

    +
    +
    +

    Geolocation Database

    +
    + +

    TThe GeoLite2 database is used to geolocate IP addresses.

    +
    @@ -1195,7 +1257,7 @@

    - The GeoLite2 database is used to geolocate IP addresses. Database last updated never. + GeoLite2 Database last updated never.

    @@ -1203,46 +1265,6 @@
    -
    - -
    -

    Notification Agents

    -
    - -

    - Add a new notification agent, or configure an existing notification agent by clicking the settings icon on the right. -

    -

    - Please see the Notification Agents Guide for instructions on setting up each notification agent. -

    -
    -
    -
    Loading notification agents...
    -
    -
    - -
    - -
    - -
    -

    Newsletter Agents

    -
    - -

    - Add a new newsletter agent, or configure an existing newsletter agent by clicking the settings icon on the right. -

    -

    - Warning: The Image Hosting setting must be enabled for images to display on the newsletter. -

    -
    -
    -
    Loading newsletter agents...
    -
    -
    - -
    -
    diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py index c7d61379..ce095db4 100644 --- a/plexpy/notifiers.py +++ b/plexpy/notifiers.py @@ -1264,7 +1264,7 @@ class DISCORD(Notifier): 'value': self.config['incl_card'], 'name': 'discord_incl_card', 'description': 'Include an info card with a poster and metadata with the notifications.
    ' - 'Note: Image Hosting ' 'must be enabled under the notifications settings tab.', 'input_type': 'checkbox' @@ -1639,7 +1639,7 @@ class FACEBOOK(Notifier): 'value': self.config['incl_card'], 'name': 'facebook_incl_card', 'description': 'Include an info card with a poster and metadata with the notifications.
    ' - 'Note: Image Hosting ' 'must be enabled under the notifications settings tab.', 'input_type': 'checkbox' @@ -1962,7 +1962,7 @@ class HIPCHAT(Notifier): 'value': self.config['incl_card'], 'name': 'hipchat_incl_card', 'description': 'Include an info card with a poster and metadata with the notifications.
    ' - 'Note: Image Hosting ' 'must be enabled under the notifications settings tab.
    ' 'Note: This will change the notification type to HTML and emoticons will no longer work.', @@ -2184,7 +2184,7 @@ class JOIN(Notifier): 'value': self.config['incl_poster'], 'name': 'join_incl_poster', 'description': 'Include a poster with the notifications.
    ' - 'Note: Image Hosting ' 'must be enabled under the notifications settings tab.', 'input_type': 'checkbox' @@ -3348,7 +3348,7 @@ class SLACK(Notifier): 'value': self.config['incl_card'], 'name': 'slack_incl_card', 'description': 'Include an info card with a poster and metadata with the notifications.
    ' - 'Note: Image Hosting ' 'must be enabled under the notifications settings tab.', 'input_type': 'checkbox' @@ -3593,7 +3593,7 @@ class TWITTER(Notifier): 'value': self.config['incl_poster'], 'name': 'twitter_incl_poster', 'description': 'Include a poster with the notifications.
    ' - 'Note: Image Hosting ' 'must be enabled under the notifications settings tab.', 'input_type': 'checkbox' From 7ee1c51810047a8010b665c95b197b226bc3febf Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Wed, 25 Dec 2019 12:40:58 -0800 Subject: [PATCH 05/39] Go to correct setting for installing GeoLite2 database --- data/interfaces/default/settings.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index 08e805e7..c60ceff0 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -2846,7 +2846,7 @@ $(document).ready(function() { }); if ("${kwargs.get('install_geoip')}" === 'true') { - gotoSetting('notifications', 'geoip_db') + gotoSetting('3rd_party_apis', 'geoip_db') } if ("${config['geoip_db_installed']}" > "0") { From 3b44a3afd20f0a8ed507506644ad3ae838c053b5 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Wed, 25 Dec 2019 12:51:59 -0800 Subject: [PATCH 06/39] Add setting for GeoLite2 database update interval --- data/interfaces/default/settings.html | 10 ++++++++++ plexpy/config.py | 1 + plexpy/helpers.py | 2 +- plexpy/webserve.py | 3 ++- 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index c60ceff0..01ab8280 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -1260,6 +1260,16 @@ GeoLite2 Database last updated never.

    +
    + +
    +
    + +
    + +
    +

    The interval (in days) Tautulli will automatically update the GeoLite2 database. Minimum 7, default 30.

    +

    diff --git a/plexpy/config.py b/plexpy/config.py index 104e3553..58e57f2e 100644 --- a/plexpy/config.py +++ b/plexpy/config.py @@ -176,6 +176,7 @@ _CONFIG_DEFINITIONS = { 'FREEZE_DB': (int, 'General', 0), 'GEOIP_DB': (str, 'General', ''), 'GEOIP_DB_INSTALLED': (int, 'General', 0), + 'GEOIP_DB_UPDATE_DAYS': (int, 'General', 30), 'GET_FILE_SIZES': (int, 'General', 0), 'GET_FILE_SIZES_HOLD': (dict, 'General', {'section_ids': [], 'rating_keys': []}), 'GIT_BRANCH': (str, 'General', 'master'), diff --git a/plexpy/helpers.py b/plexpy/helpers.py index 099be9c4..6597b12e 100644 --- a/plexpy/helpers.py +++ b/plexpy/helpers.py @@ -588,7 +588,7 @@ def update_geoip_db(): if plexpy.CONFIG.GEOIP_DB_INSTALLED > 1: logger.info(u"Tautulli Helpers :: Checking for GeoLite2 database updates.") now = int(time.time()) - if now - plexpy.CONFIG.GEOIP_DB_INSTALLED >= 2592000: # 30 days + if now - plexpy.CONFIG.GEOIP_DB_INSTALLED >= plexpy.CONFIG.GEOIP_DB_UPDATE_DAYS * 24 * 60 * 60: return install_geoip_db(update=True) logger.info(u"Tautulli Helpers :: GeoLite2 database already updated within the last 30 days.") diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 427b1025..7c82ef21 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -2820,9 +2820,10 @@ class WebInterface(object): "newsletter_inline_styles": checked(plexpy.CONFIG.NEWSLETTER_INLINE_STYLES), "newsletter_custom_dir": plexpy.CONFIG.NEWSLETTER_CUSTOM_DIR, "win_sys_tray": checked(plexpy.CONFIG.WIN_SYS_TRAY), + "maxmind_license_key": plexpy.CONFIG.MAXMIND_LICENSE_KEY, "geoip_db": plexpy.CONFIG.GEOIP_DB, "geoip_db_installed": plexpy.CONFIG.GEOIP_DB_INSTALLED, - "maxmind_license_key": plexpy.CONFIG.MAXMIND_LICENSE_KEY + "geoip_db_update_days": plexpy.CONFIG.GEOIP_DB_UPDATE_DAYS } return serve_template(templatename="settings.html", title="Settings", config=config, kwargs=kwargs) From d4fee1d701f009f9e899fe6104f544adb39cace0 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Wed, 25 Dec 2019 13:23:33 -0800 Subject: [PATCH 07/39] Fix GeoLite2 last updated time for previous installs --- data/interfaces/default/settings.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index 01ab8280..0f7de5d7 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -2859,7 +2859,7 @@ $(document).ready(function() { gotoSetting('3rd_party_apis', 'geoip_db') } - if ("${config['geoip_db_installed']}" > "0") { + if ("${config['geoip_db_installed']}" > "1") { $("#geoip_db_updated").text(moment("${config['geoip_db_installed']}", "X").fromNow()); } From 0428df8e3f2bdc19a866ad0c83b51549d44414ea Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Wed, 25 Dec 2019 13:31:46 -0800 Subject: [PATCH 08/39] Fix GeoLite2 update button for previous installs --- data/interfaces/default/settings.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index 0f7de5d7..d912f9d8 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -1250,7 +1250,7 @@
    - +
    From ee91da2ff13b6e9701187b5e6675c76a7c8d1c03 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Wed, 25 Dec 2019 13:43:12 -0800 Subject: [PATCH 09/39] Force reinstall of GeoLite2 database --- data/interfaces/default/settings.html | 4 ++-- plexpy/config.py | 1 - plexpy/helpers.py | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index d912f9d8..01ab8280 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -1250,7 +1250,7 @@
    - +
    @@ -2859,7 +2859,7 @@ $(document).ready(function() { gotoSetting('3rd_party_apis', 'geoip_db') } - if ("${config['geoip_db_installed']}" > "1") { + if ("${config['geoip_db_installed']}" > "0") { $("#geoip_db_updated").text(moment("${config['geoip_db_installed']}", "X").fromNow()); } diff --git a/plexpy/config.py b/plexpy/config.py index 58e57f2e..469f3f47 100644 --- a/plexpy/config.py +++ b/plexpy/config.py @@ -928,7 +928,6 @@ class Config(object): self.CONFIG_VERSION = 13 if self.CONFIG_VERSION == 13: - self.GEOIP_DB_INSTALLED = int(bool(self.GEOIP_DB)) if not self.GEOIP_DB: self.GEOIP_DB = os.path.join(plexpy.DATA_DIR, 'GeoLite2-City.mmdb') diff --git a/plexpy/helpers.py b/plexpy/helpers.py index 6597b12e..f8256ce1 100644 --- a/plexpy/helpers.py +++ b/plexpy/helpers.py @@ -585,7 +585,7 @@ def is_valid_ip(address): def update_geoip_db(): - if plexpy.CONFIG.GEOIP_DB_INSTALLED > 1: + if plexpy.CONFIG.GEOIP_DB_INSTALLED: logger.info(u"Tautulli Helpers :: Checking for GeoLite2 database updates.") now = int(time.time()) if now - plexpy.CONFIG.GEOIP_DB_INSTALLED >= plexpy.CONFIG.GEOIP_DB_UPDATE_DAYS * 24 * 60 * 60: From e7b3d768ce7b0092ef0c11b2c074a62df243a3f4 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Wed, 25 Dec 2019 14:07:59 -0800 Subject: [PATCH 10/39] Prevent installing GeoLite2 database without license key --- data/interfaces/default/settings.html | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index 01ab8280..0ea552d2 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -1250,7 +1250,7 @@
    - +
    @@ -1999,6 +1999,7 @@ $(document).ready(function() { getNewslettersTable(); getMobileDevicesTable(); loadUpdateDistros(); + enableGeoLiteInstallButton(); settingsChanged = false; } @@ -2859,6 +2860,11 @@ $(document).ready(function() { gotoSetting('3rd_party_apis', 'geoip_db') } + function enableGeoLiteInstallButton() { + $('#install_geoip_db').prop('disabled', !(Boolean($("#maxmind_license_key").val()))); + } + enableGeoLiteInstallButton(); + if ("${config['geoip_db_installed']}" > "0") { $("#geoip_db_updated").text(moment("${config['geoip_db_installed']}", "X").fromNow()); } From c358693fb247b946aa4bf31044d0ae182590f9a1 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Wed, 25 Dec 2019 14:10:16 -0800 Subject: [PATCH 11/39] Fix GeoLite2 update scheduler --- plexpy/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plexpy/__init__.py b/plexpy/__init__.py index 70eaa19b..82667f65 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -1,4 +1,4 @@ -# This file is part of Tautulli. +# 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 @@ -446,7 +446,7 @@ def initialize_scheduler(): schedule_job(config.make_backup, 'Backup Tautulli config', hours=backup_hours, minutes=0, seconds=0, args=(True, True)) schedule_job(helpers.update_geoip_db, 'Update GeoLite2 database', - hours=12 * bool(CONFIG.GEOIP_DB_INSTALLED > 1), minutes=0, seconds=0) + hours=12 * bool(CONFIG.GEOIP_DB_INSTALLED), minutes=0, seconds=0) if WS_CONNECTED and CONFIG.PMS_IP and CONFIG.PMS_TOKEN: schedule_job(plextv.get_server_resources, 'Refresh Plex server URLs', From 9450a1434dc88dcc2c94654481aac00e7154dfdf Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Wed, 25 Dec 2019 14:10:35 -0800 Subject: [PATCH 12/39] Remove line break from Python version in startup logs --- plexpy/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plexpy/__init__.py b/plexpy/__init__.py index 82667f65..4f4e1293 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -1,4 +1,4 @@ -# This file is part of Tautulli. +# 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 @@ -167,7 +167,7 @@ def initialize(config_file): plexpy.SYS_TIMEZONE.zone, plexpy.SYS_UTC_OFFSET )) logger.info(u"Python {}".format( - sys.version + sys.version.replace('\n', '') )) logger.info(u"Program Dir: {}".format( PROG_DIR From 02220209e3a4c23c2ffc98f228175023314712de Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Fri, 27 Dec 2019 09:48:38 -0800 Subject: [PATCH 13/39] Add default activity refresh interval to setting help text --- data/interfaces/default/settings.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index 0ea552d2..7f593bcc 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -285,7 +285,7 @@
    -

    Set the interval (in seconds) to refresh the current activity on the homepage. Minimum 2.

    +

    Set the interval (in seconds) to refresh the current activity on the homepage. Minimum 2, default 10.

    From a81ad6d73e5f104804ee7fe605b62451df73d7ee Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Sat, 28 Dec 2019 11:06:04 -0800 Subject: [PATCH 14/39] Set maximum GeoLite2 database update interval to 30 days --- data/interfaces/default/settings.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index 7f593bcc..8bdc5aba 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -1264,11 +1264,11 @@
    - +
    -

    The interval (in days) Tautulli will automatically update the GeoLite2 database. Minimum 7, default 30.

    +

    The interval (in days) Tautulli will automatically update the GeoLite2 database. Minimum 7, maximum 30, default 30.

    From 8bf876f88c04c789456b0f5b5781c2dec79e4caf Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Sat, 28 Dec 2019 11:09:52 -0800 Subject: [PATCH 15/39] Add note to save the MaxMind license key --- data/interfaces/default/settings.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index 8bdc5aba..fa83ecb5 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -1240,7 +1240,7 @@

    - Enter your MaxMind License Key to install the GeoLite2 database. You can register for a license key here. + Enter and save your MaxMind License Key to install the GeoLite2 database. You can register for a license key here.

    From 629800c2398de564e2703028cdf4b22d8470e9a9 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Sun, 29 Dec 2019 11:05:21 -0800 Subject: [PATCH 16/39] Change metadata to links for lookup help text --- data/interfaces/default/settings.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index fa83ecb5..62505dd3 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -1205,7 +1205,7 @@

    Metadata Lookups

    -

    Metadata lookups are used to provide additional metadata for notifications when available.

    +

    Metadata lookups are used to provide additional links for notifications when available.

    -

    - You can register a new Imgur application here.
    +

    + Please see the 3rd Party APIs Guide for instructions on setting up Imgur.
    Warning: Imgur uploads are rate-limited and newsletters may exceed the limit. Please use Cloudinary for newsletters instead.

    @@ -1162,8 +1162,8 @@
    -

    - You can sign up for Cloudinary here.
    +

    + Please see the 3rd Party APIs Guide for instructions on setting up Cloudinary.

    @@ -1230,8 +1230,10 @@

    Geolocation Database

    -

    TThe GeoLite2 database is used to geolocate IP addresses.

    - +

    The GeoLite2 database is used to geolocate IP addresses.

    +

    + Please see the 3rd Party APIs Guide for instructions on setting up MaxMind.
    +

    @@ -1240,7 +1242,7 @@

    - Enter and save your MaxMind License Key to install the GeoLite2 database. You can register for a license key here. + Enter and save your MaxMind License Key to install the GeoLite2 database.

    From b8ef56574a6149e9e5799a41d94a9bd7ee442544 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Mon, 30 Dec 2019 10:24:46 -0800 Subject: [PATCH 18/39] v2.1.40 --- CHANGELOG.md | 12 ++++++++++++ plexpy/version.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af4f670e..500b7803 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## v2.1.40 (2019-12-30) + +* UI: + * Change: Moved 3rd Party API settings to new tab in the settings. +* Graphs: + * Change: Improve calculating month ranges for Play Totals graphs. +* Other: + * Fix: Failing to verify a Plex Media Server using a hostname. + * Change: A license key is now required to install the MaxMind GeoLite2 database for IP geolocation. Please follow the guide in the wiki to reinstall the GeoLite2 database. + * Change: The GeoLite2 database will now automatically update periodically if installed. + + ## v2.1.39 (2019-12-08) * UI: diff --git a/plexpy/version.py b/plexpy/version.py index 04cb9911..1941ef61 100644 --- a/plexpy/version.py +++ b/plexpy/version.py @@ -1,2 +1,2 @@ PLEXPY_BRANCH = "master" -PLEXPY_RELEASE_VERSION = "v2.1.39" +PLEXPY_RELEASE_VERSION = "v2.1.40" From 54bbbb36a6022154720dd152114a22c69529586c Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Mon, 30 Dec 2019 13:58:56 -0800 Subject: [PATCH 19/39] Fix Geolite2 db extraction on Windows --- plexpy/helpers.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plexpy/helpers.py b/plexpy/helpers.py index f8256ce1..9f7c764c 100644 --- a/plexpy/helpers.py +++ b/plexpy/helpers.py @@ -644,14 +644,16 @@ def install_geoip_db(update=False): # Extract the GeoLite2 database file logger.debug(u"Tautulli Helpers :: Extracting GeoLite2 database...") try: + mmdb = None with tarfile.open(temp_gz, 'r:gz') as tar: - for tarinfo in tar: - if tarinfo.isdir(): - member = tar.getmember(os.path.join(tarinfo.name, geolite2_db)) + for member in tar.getmembers(): + if geolite2_db in member.name: mmdb = tar.extractfile(member) with open(geolite2_db_path, 'wb') as db: db.write(mmdb.read()) break + if not mmdb: + raise Exception("{} not found in gzip file.".format(geolite2_db)) except Exception as e: logger.error(u"Tautulli Helpers :: Failed to extract the GeoLite2 database: %s" % e) return False From e334a0fc8b0c443b7355a52dea14aa52045808bc Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Mon, 30 Dec 2019 14:19:12 -0800 Subject: [PATCH 20/39] Change GeoLite2 tar extraction --- plexpy/helpers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plexpy/helpers.py b/plexpy/helpers.py index 9f7c764c..6da6e362 100644 --- a/plexpy/helpers.py +++ b/plexpy/helpers.py @@ -648,9 +648,9 @@ def install_geoip_db(update=False): with tarfile.open(temp_gz, 'r:gz') as tar: for member in tar.getmembers(): if geolite2_db in member.name: - mmdb = tar.extractfile(member) - with open(geolite2_db_path, 'wb') as db: - db.write(mmdb.read()) + member.name = os.path.basename(member.name) + tar.extract(member, os.path.dirname(geolite2_db_path)) + mmdb = True break if not mmdb: raise Exception("{} not found in gzip file.".format(geolite2_db)) From 6af96332fa1e9b52758f3e209facf9215e36959e Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Mon, 30 Dec 2019 14:29:19 -0800 Subject: [PATCH 21/39] Use tar.extractall instead of tar.extract --- plexpy/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plexpy/helpers.py b/plexpy/helpers.py index 6da6e362..a5ba22a2 100644 --- a/plexpy/helpers.py +++ b/plexpy/helpers.py @@ -649,7 +649,7 @@ def install_geoip_db(update=False): for member in tar.getmembers(): if geolite2_db in member.name: member.name = os.path.basename(member.name) - tar.extract(member, os.path.dirname(geolite2_db_path)) + tar.extractall(path=os.path.dirname(geolite2_db_path), members=[member]) mmdb = True break if not mmdb: From a7a9ed86280d87cded8930a2cb15b8909db2354e Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Mon, 30 Dec 2019 14:33:10 -0800 Subject: [PATCH 22/39] v2.1.41 --- CHANGELOG.md | 6 ++++++ plexpy/version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 500b7803..64519511 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v2.1.41 (2019-12-30) + +* Other: + * Fix: Failing to extract the GeoLite2 database on Windows. + + ## v2.1.40 (2019-12-30) * UI: diff --git a/plexpy/version.py b/plexpy/version.py index 1941ef61..2a5a57ff 100644 --- a/plexpy/version.py +++ b/plexpy/version.py @@ -1,2 +1,2 @@ PLEXPY_BRANCH = "master" -PLEXPY_RELEASE_VERSION = "v2.1.40" +PLEXPY_RELEASE_VERSION = "v2.1.41" From 0cba3988aa0935ded97ef52ee8af8b3f99ee2068 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Wed, 1 Jan 2020 10:10:05 -0800 Subject: [PATCH 23/39] Update certifi to 2019.11.28 --- lib/certifi/__init__.py | 2 +- lib/certifi/cacert.pem | 144 ++++++++++++---------------------------- 2 files changed, 45 insertions(+), 101 deletions(-) diff --git a/lib/certifi/__init__.py b/lib/certifi/__init__.py index 632db8e1..0d59a056 100644 --- a/lib/certifi/__init__.py +++ b/lib/certifi/__init__.py @@ -1,3 +1,3 @@ from .core import where -__version__ = "2019.03.09" +__version__ = "2019.11.28" diff --git a/lib/certifi/cacert.pem b/lib/certifi/cacert.pem index 84636dde..a4758ef3 100644 --- a/lib/certifi/cacert.pem +++ b/lib/certifi/cacert.pem @@ -771,36 +771,6 @@ vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep +OkuE6N36B9K -----END CERTIFICATE----- -# Issuer: CN=Class 2 Primary CA O=Certplus -# Subject: CN=Class 2 Primary CA O=Certplus -# Label: "Certplus Class 2 Primary CA" -# Serial: 177770208045934040241468760488327595043 -# MD5 Fingerprint: 88:2c:8c:52:b8:a2:3c:f3:f7:bb:03:ea:ae:ac:42:0b -# SHA1 Fingerprint: 74:20:74:41:72:9c:dd:92:ec:79:31:d8:23:10:8d:c2:81:92:e2:bb -# SHA256 Fingerprint: 0f:99:3c:8a:ef:97:ba:af:56:87:14:0e:d5:9a:d1:82:1b:b4:af:ac:f0:aa:9a:58:b5:d5:7a:33:8a:3a:fb:cb ------BEGIN CERTIFICATE----- -MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAw -PTELMAkGA1UEBhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRswGQYDVQQDExJDbGFz -cyAyIFByaW1hcnkgQ0EwHhcNOTkwNzA3MTcwNTAwWhcNMTkwNzA2MjM1OTU5WjA9 -MQswCQYDVQQGEwJGUjERMA8GA1UEChMIQ2VydHBsdXMxGzAZBgNVBAMTEkNsYXNz -IDIgUHJpbWFyeSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANxQ -ltAS+DXSCHh6tlJw/W/uz7kRy1134ezpfgSN1sxvc0NXYKwzCkTsA18cgCSR5aiR -VhKC9+Ar9NuuYS6JEI1rbLqzAr3VNsVINyPi8Fo3UjMXEuLRYE2+L0ER4/YXJQyL -kcAbmXuZVg2v7tK8R1fjeUl7NIknJITesezpWE7+Tt9avkGtrAjFGA7v0lPubNCd -EgETjdyAYveVqUSISnFOYFWe2yMZeVYHDD9jC1yw4r5+FfyUM1hBOHTE4Y+L3yas -H7WLO7dDWWuwJKZtkIvEcupdM5i3y95ee++U8Rs+yskhwcWYAqqi9lt3m/V+llU0 -HGdpwPFC40es/CgcZlUCAwEAAaOBjDCBiTAPBgNVHRMECDAGAQH/AgEKMAsGA1Ud -DwQEAwIBBjAdBgNVHQ4EFgQU43Mt38sOKAze3bOkynm4jrvoMIkwEQYJYIZIAYb4 -QgEBBAQDAgEGMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly93d3cuY2VydHBsdXMu -Y29tL0NSTC9jbGFzczIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQCnVM+IRBnL39R/ -AN9WM2K191EBkOvDP9GIROkkXe/nFL0gt5o8AP5tn9uQ3Nf0YtaLcF3n5QRIqWh8 -yfFC82x/xXp8HVGIutIKPidd3i1RTtMTZGnkLuPT55sJmabglZvOGtd/vjzOUrMR -FcEPF80Du5wlFbqidon8BvEY0JNLDnyCt6X09l/+7UCmnYR0ObncHoUW2ikbhiMA -ybuJfm6AiB4vFLQDJKgybwOaRywwvlbGp0ICcBvqQNi6BQNwB6SW//1IMwrh3KWB -kJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7 -l7+ijrRU ------END CERTIFICATE----- - # Issuer: CN=DST Root CA X3 O=Digital Signature Trust Co. # Subject: CN=DST Root CA X3 O=Digital Signature Trust Co. # Label: "DST Root CA X3" @@ -1219,36 +1189,6 @@ t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== -----END CERTIFICATE----- -# Issuer: CN=Deutsche Telekom Root CA 2 O=Deutsche Telekom AG OU=T-TeleSec Trust Center -# Subject: CN=Deutsche Telekom Root CA 2 O=Deutsche Telekom AG OU=T-TeleSec Trust Center -# Label: "Deutsche Telekom Root CA 2" -# Serial: 38 -# MD5 Fingerprint: 74:01:4a:91:b1:08:c4:58:ce:47:cd:f0:dd:11:53:08 -# SHA1 Fingerprint: 85:a4:08:c0:9c:19:3e:5d:51:58:7d:cd:d6:13:30:fd:8c:de:37:bf -# SHA256 Fingerprint: b6:19:1a:50:d0:c3:97:7f:7d:a9:9b:cd:aa:c8:6a:22:7d:ae:b9:67:9e:c7:0b:a3:b0:c9:d9:22:71:c1:70:d3 ------BEGIN CERTIFICATE----- -MIIDnzCCAoegAwIBAgIBJjANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJERTEc -MBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2Vj -IFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290IENB -IDIwHhcNOTkwNzA5MTIxMTAwWhcNMTkwNzA5MjM1OTAwWjBxMQswCQYDVQQGEwJE -RTEcMBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxl -U2VjIFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290 -IENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrC6M14IspFLEU -ha88EOQ5bzVdSq7d6mGNlUn0b2SjGmBmpKlAIoTZ1KXleJMOaAGtuU1cOs7TuKhC -QN/Po7qCWWqSG6wcmtoIKyUn+WkjR/Hg6yx6m/UTAtB+NHzCnjwAWav12gz1Mjwr -rFDa1sPeg5TKqAyZMg4ISFZbavva4VhYAUlfckE8FQYBjl2tqriTtM2e66foai1S -NNs671x1Udrb8zH57nGYMsRUFUQM+ZtV7a3fGAigo4aKSe5TBY8ZTNXeWHmb0moc -QqvF1afPaA+W5OFhmHZhyJF81j4A4pFQh+GdCuatl9Idxjp9y7zaAzTVjlsB9WoH -txa2bkp/AgMBAAGjQjBAMB0GA1UdDgQWBBQxw3kbuvVT1xfgiXotF2wKsyudMzAP -BgNVHRMECDAGAQH/AgEFMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOC -AQEAlGRZrTlk5ynrE/5aw4sTV8gEJPB0d8Bg42f76Ymmg7+Wgnxu1MM9756Abrsp -tJh6sTtU6zkXR34ajgv8HzFZMQSyzhfzLMdiNlXiItiJVbSYSKpk+tYcNthEeFpa -IzpXl/V6ME+un2pMSyuOoAPjPuCp1NJ70rOo4nI8rZ7/gFnkm0W09juwzTkZmDLl -6iFhkOQxIY40sfcvNUqFENrnijchvllj4PKFiDFT1FQUhXB59C4Gdyd1Lx+4ivn+ -xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU -Cm26OWMohpLzGITY+9HPBVZkVw== ------END CERTIFICATE----- - # Issuer: CN=Cybertrust Global Root O=Cybertrust, Inc # Subject: CN=Cybertrust Global Root O=Cybertrust, Inc # Label: "Cybertrust Global Root" @@ -3453,46 +3393,6 @@ AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ 5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su -----END CERTIFICATE----- -# Issuer: CN=Certinomis - Root CA O=Certinomis OU=0002 433998903 -# Subject: CN=Certinomis - Root CA O=Certinomis OU=0002 433998903 -# Label: "Certinomis - Root CA" -# Serial: 1 -# MD5 Fingerprint: 14:0a:fd:8d:a8:28:b5:38:69:db:56:7e:61:22:03:3f -# SHA1 Fingerprint: 9d:70:bb:01:a5:a4:a0:18:11:2e:f7:1c:01:b9:32:c5:34:e7:88:a8 -# SHA256 Fingerprint: 2a:99:f5:bc:11:74:b7:3c:bb:1d:62:08:84:e0:1c:34:e5:1c:cb:39:78:da:12:5f:0e:33:26:88:83:bf:41:58 ------BEGIN CERTIFICATE----- -MIIFkjCCA3qgAwIBAgIBATANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJGUjET -MBEGA1UEChMKQ2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxHTAb -BgNVBAMTFENlcnRpbm9taXMgLSBSb290IENBMB4XDTEzMTAyMTA5MTcxOFoXDTMz -MTAyMTA5MTcxOFowWjELMAkGA1UEBhMCRlIxEzARBgNVBAoTCkNlcnRpbm9taXMx -FzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMR0wGwYDVQQDExRDZXJ0aW5vbWlzIC0g -Um9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANTMCQosP5L2 -fxSeC5yaah1AMGT9qt8OHgZbn1CF6s2Nq0Nn3rD6foCWnoR4kkjW4znuzuRZWJfl -LieY6pOod5tK8O90gC3rMB+12ceAnGInkYjwSond3IjmFPnVAy//ldu9n+ws+hQV -WZUKxkd8aRi5pwP5ynapz8dvtF4F/u7BUrJ1Mofs7SlmO/NKFoL21prbcpjp3vDF -TKWrteoB4owuZH9kb/2jJZOLyKIOSY008B/sWEUuNKqEUL3nskoTuLAPrjhdsKkb -5nPJWqHZZkCqqU2mNAKthH6yI8H7KsZn9DS2sJVqM09xRLWtwHkziOC/7aOgFLSc -CbAK42C++PhmiM1b8XcF4LVzbsF9Ri6OSyemzTUK/eVNfaoqoynHWmgE6OXWk6Ri -wsXm9E/G+Z8ajYJJGYrKWUM66A0ywfRMEwNvbqY/kXPLynNvEiCL7sCCeN5LLsJJ -wx3tFvYk9CcbXFcx3FXuqB5vbKziRcxXV4p1VxngtViZSTYxPDMBbRZKzbgqg4SG -m/lg0h9tkQPTYKbVPZrdd5A9NaSfD171UkRpucC63M9933zZxKyGIjK8e2uR73r4 -F2iw4lNVYC2vPsKD2NkJK/DAZNuHi5HMkesE/Xa0lZrmFAYb1TQdvtj/dBxThZng -WVJKYe2InmtJiUZ+IFrZ50rlau7SZRFDAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIB -BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTvkUz1pcMw6C8I6tNxIqSSaHh0 -2TAfBgNVHSMEGDAWgBTvkUz1pcMw6C8I6tNxIqSSaHh02TANBgkqhkiG9w0BAQsF -AAOCAgEAfj1U2iJdGlg+O1QnurrMyOMaauo++RLrVl89UM7g6kgmJs95Vn6RHJk/ -0KGRHCwPT5iVWVO90CLYiF2cN/z7ZMF4jIuaYAnq1fohX9B0ZedQxb8uuQsLrbWw -F6YSjNRieOpWauwK0kDDPAUwPk2Ut59KA9N9J0u2/kTO+hkzGm2kQtHdzMjI1xZS -g081lLMSVX3l4kLr5JyTCcBMWwerx20RoFAXlCOotQqSD7J6wWAsOMwaplv/8gzj -qh8c3LigkyfeY+N/IZ865Z764BNqdeuWXGKRlI5nU7aJ+BIJy29SWwNyhlCVCNSN -h4YVH5Uk2KRvms6knZtt0rJ2BobGVgjF6wnaNsIbW0G+YSrjcOa4pvi2WsS9Iff/ -ql+hbHY5ZtbqTFXhADObE5hjyW/QASAJN1LnDE8+zbz1X5YnpyACleAu6AdBBR8V -btaw5BngDwKTACdyxYvRVB9dSsNAl35VpnzBMwQUAR1JIGkLGZOdblgi90AMRgwj -Y/M50n92Uaf0yKHxDHYiI0ZSKS3io0EHVmmY0gUJvGnHWmHNj4FgFU2A3ZDifcRQ -8ow7bkrHxuaAKzyBvBGAFhAn1/DNP3nMcyrDflOR1m749fPH0FFNjkulW+YZFzvW -gQncItzujrnEj1PhZ7szuIgVRs/taTX/dQ1G885x4cVrhkIGuUE= ------END CERTIFICATE----- - # Issuer: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed # Subject: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed # Label: "OISTE WISeKey Global Root GB CA" @@ -4656,3 +4556,47 @@ L5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB60PZ2Pierc+xYw5F9KBa LJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fqdBb9HxEG mpv0 -----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority - G4 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2015 Entrust, Inc. - for authorized use only +# Subject: CN=Entrust Root Certification Authority - G4 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2015 Entrust, Inc. - for authorized use only +# Label: "Entrust Root Certification Authority - G4" +# Serial: 289383649854506086828220374796556676440 +# MD5 Fingerprint: 89:53:f1:83:23:b7:7c:8e:05:f1:8c:71:38:4e:1f:88 +# SHA1 Fingerprint: 14:88:4e:86:26:37:b0:26:af:59:62:5c:40:77:ec:35:29:ba:96:01 +# SHA256 Fingerprint: db:35:17:d1:f6:73:2a:2d:5a:b9:7c:53:3e:c7:07:79:ee:32:70:a6:2f:b4:ac:42:38:37:24:60:e6:f0:1e:88 +-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIRANm1Q3+vqTkPAAAAAFVlrVgwDQYJKoZIhvcNAQELBQAw +gb4xCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQL +Ex9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykg +MjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMjAw +BgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEc0 +MB4XDTE1MDUyNzExMTExNloXDTM3MTIyNzExNDExNlowgb4xCzAJBgNVBAYTAlVT +MRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1 +c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJ +bmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3Qg +Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEc0MIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAsewsQu7i0TD/pZJH4i3DumSXbcr3DbVZwbPLqGgZ +2K+EbTBwXX7zLtJTmeH+H17ZSK9dE43b/2MzTdMAArzE+NEGCJR5WIoV3imz/f3E +T+iq4qA7ec2/a0My3dl0ELn39GjUu9CH1apLiipvKgS1sqbHoHrmSKvS0VnM1n4j +5pds8ELl3FFLFUHtSUrJ3hCX1nbB76W1NhSXNdh4IjVS70O92yfbYVaCNNzLiGAM +C1rlLAHGVK/XqsEQe9IFWrhAnoanw5CGAlZSCXqc0ieCU0plUmr1POeo8pyvi73T +DtTUXm6Hnmo9RR3RXRv06QqsYJn7ibT/mCzPfB3pAqoEmh643IhuJbNsZvc8kPNX +wbMv9W3y+8qh+CmdRouzavbmZwe+LGcKKh9asj5XxNMhIWNlUpEbsZmOeX7m640A +2Vqq6nPopIICR5b+W45UYaPrL0swsIsjdXJ8ITzI9vF01Bx7owVV7rtNOzK+mndm +nqxpkCIHH2E6lr7lmk/MBTwoWdPBDFSoWWG9yHJM6Nyfh3+9nEg2XpWjDrk4JFX8 +dWbrAuMINClKxuMrLzOg2qOGpRKX/YAr2hRC45K9PvJdXmd0LhyIRyk0X+IyqJwl +N4y6mACXi0mWHv0liqzc2thddG5msP9E36EYxr5ILzeUePiVSj9/E15dWf10hkNj +c0kCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0OBBYEFJ84xFYjwznooHFs6FRM5Og6sb9nMA0GCSqGSIb3DQEBCwUAA4ICAQAS +5UKme4sPDORGpbZgQIeMJX6tuGguW8ZAdjwD+MlZ9POrYs4QjbRaZIxowLByQzTS +Gwv2LFPSypBLhmb8qoMi9IsabyZIrHZ3CL/FmFz0Jomee8O5ZDIBf9PD3Vht7LGr +hFV0d4QEJ1JrhkzO3bll/9bGXp+aEJlLdWr+aumXIOTkdnrG0CSqkM0gkLpHZPt/ +B7NTeLUKYvJzQ85BK4FqLoUWlFPUa19yIqtRLULVAJyZv967lDtX/Zr1hstWO1uI +AeV8KEsD+UmDfLJ/fOPtjqF/YFOOVZ1QNBIPt5d7bIdKROf1beyAN/BYGW5KaHbw +H5Lk6rWS02FREAutp9lfx1/cH6NcjKF+m7ee01ZvZl4HliDtC3T7Zk6LERXpgUl+ +b7DUUH8i119lAg2m9IUe2K4GS0qn0jFmwvjO5QimpAKWRGhXxNUzzxkvFMSUHHuk +2fCfDrGA4tGeEWSpiBE6doLlYsKA2KSD7ZPvfC+QsDJMlhVoSFLUmQjAJOgc47Ol +IQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk +5F6G+TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuY +n/PIjhs4ViFqUZPTkcpG2om3PVODLAgfi49T3f+sHw== +-----END CERTIFICATE----- From c31d3ffd6c8550b514eb7e11a4c692516df30880 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Wed, 1 Jan 2020 10:10:52 -0800 Subject: [PATCH 24/39] Download from MaxMind using urllib3 using certifi --- plexpy/helpers.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/plexpy/helpers.py b/plexpy/helpers.py index a5ba22a2..630f6e4d 100644 --- a/plexpy/helpers.py +++ b/plexpy/helpers.py @@ -14,6 +14,7 @@ # along with Tautulli. If not, see . import base64 +import certifi import cloudinary from cloudinary.api import delete_resources_by_tag from cloudinary.uploader import upload @@ -21,7 +22,6 @@ from cloudinary.utils import cloudinary_url import datetime from functools import wraps import geoip2.database, geoip2.errors -import gzip import hashlib import imghdr from itertools import izip_longest @@ -34,12 +34,13 @@ from operator import itemgetter import os import re import shlex +import shutil import socket import sys import tarfile import time import unicodedata -import urllib, urllib2 +import urllib3 from xml.dom import minidom import xmltodict @@ -614,9 +615,11 @@ def install_geoip_db(update=False): # Retrieve the GeoLite2 gzip file logger.debug(u"Tautulli Helpers :: Downloading GeoLite2 gzip file from MaxMind...") try: - maxmind = urllib.URLopener() - maxmind.retrieve(geolite2_db_url, temp_gz) - maxmind.retrieve(geolite2_md5_url, temp_md5) + maxmind = urllib3.PoolManager(cert_reqs='CERT_REQUIRED', ca_certs=certifi.where()) + with maxmind.request('GET', geolite2_db_url, preload_content=False) as r_db, open(temp_gz, 'wb') as f_db: + shutil.copyfileobj(r_db, f_db) + with maxmind.request('GET', geolite2_md5_url, preload_content=False) as r_md5, open(temp_md5, 'wb') as f_md5: + shutil.copyfileobj(r_md5, f_md5) except Exception as e: logger.error(u"Tautulli Helpers :: Failed to download GeoLite2 gzip file from MaxMind: %s" % e) return False From 0e1764755ada57d0cac02410317939512cc043db Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Thu, 2 Jan 2020 21:25:15 -0800 Subject: [PATCH 25/39] Change save setting callbacks --- data/interfaces/default/settings.html | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index 4ec134f9..fd50522a 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -1979,12 +1979,10 @@ $(document).ready(function() { } function preSaveChecks(_callback) { - if (serverChanged) { - verifyServer(); - } verifyPMSWebURL(); - - if (_callback) { + if (serverChanged) { + verifyServer(_callback); + } else if (typeof _callback === "function") { _callback(); } } @@ -2010,12 +2008,13 @@ $(document).ready(function() { settingsChanged = true; }); - function saveSettings() { + function saveSettings(_callback) { if (configForm.parsley().validate()) { - doAjaxCall('configUpdate', $(this), 'tabs', true, true, postSaveChecks); - return false; + doAjaxCall('configUpdate', $(this), 'tabs', true, true, _callback); + return true; } else { - showMsg(' Please verify your settings.', false, true, 5000, true) + showMsg(' Please verify your settings.', false, true, 5000, true); + return false; } } @@ -2029,7 +2028,7 @@ $(document).ready(function() { } $('.save-button').click(function() { - preSaveChecks(function () { saveSettings() }); + preSaveChecks(function () { saveSettings(postSaveChecks) }); }); initConfigCheckbox('#api_enabled'); @@ -2338,6 +2337,7 @@ $(document).ready(function() { if (($("#pms_ip").val() !== '') || ($("#pms_port").val() !== '')) { $("#pms_verify").html('').fadeIn('fast'); + showMsg('Verifying Plex server...', true, true, 10000, false); $.ajax({ url: 'get_server_id', data: { @@ -2375,10 +2375,11 @@ $(document).ready(function() { } else { $("#pms_verify").html('').fadeIn('fast'); $("#pms_ip_group").removeClass("has-error"); + showMsg(' Server verified.', false, true, 5000); serverChanged = false; } - if (_callback) { + if (typeof _callback === "function") { _callback(); } } else { From 7d71086a41e675efd38dee7a5764177a9dee2de2 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Thu, 2 Jan 2020 21:28:11 -0800 Subject: [PATCH 26/39] Verify MaxMind license key and GeoLite2 database path before allowing install --- data/interfaces/default/settings.html | 22 ++++++++++++---------- plexpy/helpers.py | 4 ++++ 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index fd50522a..7ca4b323 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -1242,7 +1242,7 @@

    - Enter and save your MaxMind License Key to install the GeoLite2 database. + Enter your MaxMind License Key to install the GeoLite2 database.

    @@ -1250,13 +1250,14 @@
    - + - +
    +

    GeoLite2 Database last updated never. @@ -1999,7 +2000,6 @@ $(document).ready(function() { getNewslettersTable(); getMobileDevicesTable(); loadUpdateDistros(); - enableGeoLiteInstallButton(); settingsChanged = false; } @@ -2863,20 +2863,22 @@ $(document).ready(function() { gotoSetting('3rd_party_apis', 'geoip_db') } - function enableGeoLiteInstallButton() { - $('#install_geoip_db').prop('disabled', !(Boolean($("#maxmind_license_key").val()))); - } - enableGeoLiteInstallButton(); - if ("${config['geoip_db_installed']}" > "0") { $("#geoip_db_updated").text(moment("${config['geoip_db_installed']}", "X").fromNow()); } $("#install_geoip_db").click(function () { + if ($.trim($("#maxmind_license_key").val()) === "") { + $("#maxmind_license_key").focus(); + showMsg(' Maxmind License Key cannot be blank.', false, true, 5000, true); + return false; + } else if (!(saveSettings())){ + return false; + } var msg = 'Are you sure you want to install the GeoLite2 database?

    ' + 'The database is used to lookup IP address geolocation info.
    ' + 'The database will be downloaded from MaxMind,
    ' + - 'and requires 100MB of free space to install in your Tautulli directory.
    '; + 'and requires 100MB of free space to install.
    '; var url = 'install_geoip_db'; if ($(this).text() === 'Update') { url += '?update=true'; diff --git a/plexpy/helpers.py b/plexpy/helpers.py index 630f6e4d..bc12a773 100644 --- a/plexpy/helpers.py +++ b/plexpy/helpers.py @@ -609,6 +609,10 @@ def install_geoip_db(update=False): geolite2_db = maxmind_db + '.mmdb' geolite2_db_path = plexpy.CONFIG.GEOIP_DB or os.path.join(plexpy.DATA_DIR, geolite2_db) + # Check path ends with .mmdb + if os.path.splitext(geolite2_db_path)[1] != os.path.splitext(geolite2_db)[1]: + geolite2_db_path = os.path.join(geolite2_db_path, geolite2_db) + temp_gz = os.path.join(plexpy.CONFIG.CACHE_DIR, geolite2_gz) temp_md5 = os.path.join(plexpy.CONFIG.CACHE_DIR, geolite2_md5) From badbfdc4c109d8122f8b77c091d107213ae5f649 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Sat, 4 Jan 2020 10:52:11 -0800 Subject: [PATCH 27/39] Disable uninstall GeoLite2 database button when not installed --- data/interfaces/default/settings.html | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index 7ca4b323..5d912db4 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -1253,7 +1253,7 @@ - +

    @@ -2008,9 +2008,9 @@ $(document).ready(function() { settingsChanged = true; }); - function saveSettings(_callback) { + function saveSettings(showMsg, _callback) { if (configForm.parsley().validate()) { - doAjaxCall('configUpdate', $(this), 'tabs', true, true, _callback); + doAjaxCall('configUpdate', $(this), 'tabs', true, showMsg, _callback); return true; } else { showMsg(' Please verify your settings.', false, true, 5000, true); @@ -2028,7 +2028,7 @@ $(document).ready(function() { } $('.save-button').click(function() { - preSaveChecks(function () { saveSettings(postSaveChecks) }); + preSaveChecks(function () { saveSettings(true, postSaveChecks) }); }); initConfigCheckbox('#api_enabled'); @@ -2870,7 +2870,7 @@ $(document).ready(function() { $("#install_geoip_db").click(function () { if ($.trim($("#maxmind_license_key").val()) === "") { $("#maxmind_license_key").focus(); - showMsg(' Maxmind License Key cannot be blank.', false, true, 5000, true); + showMsg(' Maxmind License Key is required.', false, true, 5000, true); return false; } else if (!(saveSettings())){ return false; @@ -2886,6 +2886,7 @@ $(document).ready(function() { confirmAjaxCall(url, msg, null, 'Installing GeoLite2 database.', function (result) { if (result.result === "success") { $('#install_geoip_db').text('Update'); + $('#uninstall_geoip_db').prop('disabled', false); $('#geoip_db_updated').text(moment(result.updated, "X").fromNow()); } getSchedulerTable(); @@ -2899,6 +2900,7 @@ $(document).ready(function() { confirmAjaxCall(url, msg, null, 'Uninstalling GeoLite2 database.', function (result) { if (result.result === "success") { $('#install_geoip_db').text('Install'); + $('#uninstall_geoip_db').prop('disabled', true); $('#geoip_db_updated').text('never'); } getSchedulerTable(); From e321c5b197da9fd4097a5fa93c6cf242b7fd48f8 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Sat, 4 Jan 2020 11:50:48 -0800 Subject: [PATCH 28/39] Fix GeoLite2 update log message to correct number of days from setting --- plexpy/helpers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plexpy/helpers.py b/plexpy/helpers.py index bc12a773..ae47f5cd 100644 --- a/plexpy/helpers.py +++ b/plexpy/helpers.py @@ -591,7 +591,8 @@ def update_geoip_db(): now = int(time.time()) if now - plexpy.CONFIG.GEOIP_DB_INSTALLED >= plexpy.CONFIG.GEOIP_DB_UPDATE_DAYS * 24 * 60 * 60: return install_geoip_db(update=True) - logger.info(u"Tautulli Helpers :: GeoLite2 database already updated within the last 30 days.") + logger.info(u"Tautulli Helpers :: GeoLite2 database already updated within the last %s days." + % plexpy.CONFIG.GEOIP_DB_UPDATE_DAYS) def install_geoip_db(update=False): From 4d0c4bf4f44ed3f96aa86393ed07699dd123cad3 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Sat, 4 Jan 2020 12:03:44 -0800 Subject: [PATCH 29/39] Update GeoLite2 database help text to leave blank for default --- data/interfaces/default/settings.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index 5d912db4..bee08d84 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -1260,7 +1260,7 @@

    - GeoLite2 Database last updated never. + Leave blank to install in the default location. GeoLite2 database last updated never.

    From c8f132a750c81444e2b4e725908067703e3d7f58 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Sat, 4 Jan 2020 17:46:03 -0800 Subject: [PATCH 30/39] v2.1.42 --- CHANGELOG.md | 8 ++++++++ plexpy/version.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64519511..b1343139 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## v2.1.42 (2020-01-04) + +* Other: + * Fix: SSL certificate error when installing GeoLite2 database. + * Change: Verify MaxMind license key and GeoLite2 database path before installing. + * Change: Disable Geolite2 database uninstall button when it is not installed. + + ## v2.1.41 (2019-12-30) * Other: diff --git a/plexpy/version.py b/plexpy/version.py index 2a5a57ff..591119a4 100644 --- a/plexpy/version.py +++ b/plexpy/version.py @@ -1,2 +1,2 @@ PLEXPY_BRANCH = "master" -PLEXPY_RELEASE_VERSION = "v2.1.41" +PLEXPY_RELEASE_VERSION = "v2.1.42" From 2a03be1905c4ecd73581b67a7e27d2182c6c95d7 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Sat, 4 Jan 2020 17:49:27 -0800 Subject: [PATCH 31/39] Fix typo in CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1343139..41f2eeed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ * Other: * Fix: SSL certificate error when installing GeoLite2 database. * Change: Verify MaxMind license key and GeoLite2 database path before installing. - * Change: Disable Geolite2 database uninstall button when it is not installed. + * Change: Disable GeoLite2 database uninstall button when it is not installed. ## v2.1.41 (2019-12-30) From 13374c9ded52a403582d9e01e94f1933517fb6a6 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Tue, 14 Jan 2020 17:35:35 -0800 Subject: [PATCH 32/39] Fix Windows and macOS platform capitalization --- plexpy/common.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plexpy/common.py b/plexpy/common.py index cc4ca6a0..ca87f838 100644 --- a/plexpy/common.py +++ b/plexpy/common.py @@ -54,7 +54,9 @@ PLATFORM_NAME_OVERRIDES = { 'Mystery 3': 'Playstation 3', 'Mystery 4': 'Playstation 4', 'Mystery 5': 'Xbox 360', - 'WebMAF': 'Playstation 4' + 'WebMAF': 'Playstation 4', + 'windows': 'Windows', + 'osx': 'macOS' } PMS_PLATFORM_NAME_OVERRIDES = { From 4740d0fbf31024c2983e0942b0b491527b120867 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Tue, 14 Jan 2020 17:36:25 -0800 Subject: [PATCH 33/39] Change webhook request data if not Content-Type header is not application/json --- plexpy/notifiers.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py index ce095db4..adadc3f6 100644 --- a/plexpy/notifiers.py +++ b/plexpy/notifiers.py @@ -3636,7 +3636,12 @@ class WEBHOOK(Notifier): if webhook_headers: headers.update(webhook_headers) - return self.make_request(self.config['hook'], method=self.config['method'], headers=headers, json=webhook_body) + if headers['Content-Type'] == 'application/json': + data = {'json': webhook_body} + else: + data = {'data': webhook_body} + + return self.make_request(self.config['hook'], method=self.config['method'], headers=headers, **data) def _return_config_options(self): config_option = [{'label': 'Webhook URL', From 90056bcce2025260d4685033ad8da28782ad2267 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Wed, 15 Jan 2020 11:38:50 -0800 Subject: [PATCH 34/39] Indicate HDR content on activity cards --- .../default/current_activity_instance.html | 29 +++++++++------ data/interfaces/default/index.html | 8 +++-- plexpy/pmsconnect.py | 35 +++++++++++++++++++ 3 files changed, 59 insertions(+), 13 deletions(-) diff --git a/data/interfaces/default/current_activity_instance.html b/data/interfaces/default/current_activity_instance.html index f02b39f7..c69b295e 100644 --- a/data/interfaces/default/current_activity_instance.html +++ b/data/interfaces/default/current_activity_instance.html @@ -226,17 +226,24 @@ DOCUMENTATION :: END
  • Video
    - % if data['media_type'] in ('movie', 'episode', 'clip'): + % if data['media_type'] in ('movie', 'episode', 'clip') and data['stream_video_decision']: + <% + if data['video_dynamic_range'] == 'HDR': + video_dynamic_range = ' ' + data['video_dynamic_range'] + stream_video_dynamic_range = ' ' + data['stream_video_dynamic_range'] + else: + video_dynamic_range = stream_video_dynamic_range = '' + %> % if data['stream_video_decision'] == 'transcode': <% hw_d = ' (HW)' if data['transcode_hw_decoding'] else '' hw_e = ' (HW)' if data['transcode_hw_encoding'] else '' %> - Transcode (${data['video_codec'].upper()}${hw_d} ${data['video_full_resolution']} ${data['stream_video_codec'].upper()}${hw_e} ${data['stream_video_full_resolution']}) + Transcode (${data['video_codec'].upper()}${hw_d} ${data['video_full_resolution']}${video_dynamic_range} ${data['stream_video_codec'].upper()}${hw_e} ${data['stream_video_full_resolution']}${stream_video_dynamic_range}) % elif data['stream_video_decision'] == 'copy': - Direct Stream (${data['stream_video_codec'].upper()} ${data['stream_video_full_resolution']}) + Direct Stream (${data['stream_video_codec'].upper()} ${data['stream_video_full_resolution']}${stream_video_dynamic_range}) % else: - Direct Play (${data['stream_video_codec'].upper()} ${data['stream_video_full_resolution']}) + Direct Play (${data['stream_video_codec'].upper()} ${data['stream_video_full_resolution']}${stream_video_dynamic_range}) % endif % elif data['media_type'] == 'photo': Direct Play (${data['width']}x${data['height']}) @@ -248,12 +255,14 @@ DOCUMENTATION :: END
  • Audio
    - % if data['stream_audio_decision'] == 'transcode': - Transcode (${AUDIO_CODEC_OVERRIDES.get(data['audio_codec'], data['audio_codec'].upper())} ${data['audio_channel_layout'].split('(')[0].capitalize()} ${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()}) - % elif data['stream_audio_decision'] == 'copy': - Direct Stream (${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()}) - % else: - Direct Play (${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()}) + % if data['stream_audio_decision']: + % if data['stream_audio_decision'] == 'transcode': + Transcode (${AUDIO_CODEC_OVERRIDES.get(data['audio_codec'], data['audio_codec'].upper())} ${data['audio_channel_layout'].split('(')[0].capitalize()} ${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()}) + % elif data['stream_audio_decision'] == 'copy': + Direct Stream (${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()}) + % else: + Direct Play (${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()}) + % endif % endif
  • diff --git a/data/interfaces/default/index.html b/data/interfaces/default/index.html index 24e9ae5d..d6b479d5 100644 --- a/data/interfaces/default/index.html +++ b/data/interfaces/default/index.html @@ -437,6 +437,8 @@ var video_decision = ''; if (['movie', 'episode', 'clip'].indexOf(s.media_type) > -1 && s.stream_video_decision) { + var v_bd = (s.video_dynamic_range === 'HDR') ? ' ' + s.video_dynamic_range : ''; + var sv_bd = (s.video_dynamic_range === 'HDR') ? ' ' + s.stream_video_dynamic_range : ''; var v_res= ''; switch (s.video_resolution.toLowerCase()) { case 'sd': @@ -462,11 +464,11 @@ if (s.stream_video_decision === 'transcode') { var hw_d = (s.transcode_hw_decoding === 1) ? ' (HW)' : ''; var hw_e = (s.transcode_hw_encoding === 1) ? ' (HW)' : ''; - video_decision = 'Transcode (' + s.video_codec.toUpperCase() + hw_d + ' ' + v_res + ' ' + s.stream_video_codec.toUpperCase() + hw_e + ' ' + sv_res + ')'; + video_decision = 'Transcode (' + s.video_codec.toUpperCase() + hw_d + ' ' + v_res + v_bd + ' ' + s.stream_video_codec.toUpperCase() + hw_e + ' ' + sv_res + sv_bd + ')'; } else if (s.stream_video_decision === 'copy') { - video_decision = 'Direct Stream (' + s.stream_video_codec.toUpperCase() + ' ' + sv_res + ')'; + video_decision = 'Direct Stream (' + s.stream_video_codec.toUpperCase() + ' ' + sv_res + sv_bd + ')'; } else { - video_decision = 'Direct Play (' + s.stream_video_codec.toUpperCase() + ' ' + sv_res + ')'; + video_decision = 'Direct Play (' + s.stream_video_codec.toUpperCase() + ' ' + sv_res + sv_bd + ')'; } } else if (s.media_type === 'photo') { video_decision = 'Direct Play (' + s.width + 'x' + s.height + ')'; diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py index 485b378e..f81f35e3 100644 --- a/plexpy/pmsconnect.py +++ b/plexpy/pmsconnect.py @@ -1262,6 +1262,11 @@ class PmsConnect(object): 'video_codec_level': helpers.get_xml_attr(stream, 'level'), 'video_bitrate': helpers.get_xml_attr(stream, 'bitrate'), 'video_bit_depth': helpers.get_xml_attr(stream, 'bitDepth'), + 'video_chroma_subsampling': helpers.get_xml_attr(stream, 'chromaSubsampling'), + 'video_color_primaries': helpers.get_xml_attr(stream, 'colorPrimaries'), + 'video_color_range': helpers.get_xml_attr(stream, 'colorRange'), + 'video_color_space': helpers.get_xml_attr(stream, 'colorSpace'), + 'video_color_trc': helpers.get_xml_attr(stream, 'colorTrc'), 'video_frame_rate': helpers.get_xml_attr(stream, 'frameRate'), 'video_ref_frames': helpers.get_xml_attr(stream, 'refFrames'), 'video_height': helpers.get_xml_attr(stream, 'height'), @@ -1698,6 +1703,11 @@ class PmsConnect(object): video_id = helpers.get_xml_attr(video_stream_info, 'id') video_details = {'stream_video_bitrate': helpers.get_xml_attr(video_stream_info, 'bitrate'), 'stream_video_bit_depth': helpers.get_xml_attr(video_stream_info, 'bitDepth'), + 'stream_video_chroma_subsampling': helpers.get_xml_attr(video_stream_info, 'chromaSubsampling'), + 'stream_video_color_primaries': helpers.get_xml_attr(video_stream_info, 'colorPrimaries'), + 'stream_video_color_range': helpers.get_xml_attr(video_stream_info, 'colorRange'), + 'stream_video_color_space': helpers.get_xml_attr(video_stream_info, 'colorSpace'), + 'stream_video_color_trc': helpers.get_xml_attr(video_stream_info, 'colorTrc'), 'stream_video_codec_level': helpers.get_xml_attr(video_stream_info, 'level'), 'stream_video_ref_frames': helpers.get_xml_attr(video_stream_info, 'refFrames'), 'stream_video_language': helpers.get_xml_attr(video_stream_info, 'language'), @@ -1708,6 +1718,11 @@ class PmsConnect(object): else: video_details = {'stream_video_bitrate': '', 'stream_video_bit_depth': '', + 'stream_video_chroma_subsampling': '', + 'stream_video_color_primaries': '', + 'stream_video_color_range': '', + 'stream_video_color_space': '', + 'stream_video_color_trc': '', 'stream_video_codec_level': '', 'stream_video_ref_frames': '', 'stream_video_language': '', @@ -1886,6 +1901,11 @@ class PmsConnect(object): 'video_codec_level': '', 'video_bitrate': '', 'video_bit_depth': '', + 'video_chroma_subsampling': '', + 'video_color_primaries': '', + 'video_color_range': '', + 'video_color_space': '', + 'video_color_trc': '', 'video_frame_rate': '', 'video_ref_frames': '', 'video_height': '', @@ -1969,6 +1989,21 @@ class PmsConnect(object): stream_details['stream_video_resolution'], stream_details['stream_video_resolution'] + (video_details['stream_video_scan_type'][:1] or 'p')) + if helpers.cast_to_int(source_video_details['video_bit_depth']) > 8 \ + and source_video_details['video_color_space'] == 'bt2020nc': + stream_details['video_dynamic_range'] = 'HDR' + else: + stream_details['video_dynamic_range'] = 'SDR' + + if helpers.cast_to_int(video_details['stream_video_bit_depth']) > 8 \ + and video_details['stream_video_color_space'] == 'bt2020nc': + stream_details['stream_video_dynamic_range'] = 'HDR' + else: + stream_details['stream_video_dynamic_range'] = 'SDR' + else: + stream_details['video_dynamic_range'] = '' + stream_details['stream_video_dynamic_range'] = '' + # Get the quality profile if media_type in ('movie', 'episode', 'clip') and 'stream_bitrate' in stream_details: if sync_id: From 10e421b9d43fec02ab75746de5446e26dbc678f3 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Wed, 15 Jan 2020 12:19:43 -0800 Subject: [PATCH 35/39] Add color and dynamic range to notification parameters --- plexpy/common.py | 12 ++++++++++++ plexpy/notification_handler.py | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/plexpy/common.py b/plexpy/common.py index ca87f838..c3c5c547 100644 --- a/plexpy/common.py +++ b/plexpy/common.py @@ -363,6 +363,12 @@ NOTIFICATION_PARAMETERS = [ {'name': 'Stream Video Codec Level', 'type': 'int', 'value': 'stream_video_codec_level', 'description': 'The video codec level of the stream.'}, {'name': 'Stream Video Bitrate', 'type': 'int', 'value': 'stream_video_bitrate', 'description': 'The video bitrate (in kbps) of the stream.'}, {'name': 'Stream Video Bit Depth', 'type': 'int', 'value': 'stream_video_bit_depth', 'description': 'The video bit depth of the stream.'}, + {'name': 'Stream Video Chroma Subsampling', 'type': 'str', 'value': 'stream_video_chroma_subsampling', 'description': 'The video chroma subsampling of the stream.'}, + {'name': 'Stream Video Color Primaries', 'type': 'srt', 'value': 'stream_video_color_primaries', 'description': 'The video color primaries of the stream.'}, + {'name': 'Stream Video Color Range', 'type': 'srt', 'value': 'stream_video_color_range', 'description': 'The video color range of the stream.'}, + {'name': 'Stream Video Color Space', 'type': 'str', 'value': 'stream_video_color_space', 'description': 'The video color space of the stream.'}, + {'name': 'Stream Video Color Transfer Function', 'type': 'str', 'value': 'stream_video_color_trc', 'description': 'The video transfer function of the stream.'}, + {'name': 'Stream Video Dynamic Range', 'type': 'str', 'value': 'stream_video_dynamic_range', 'description': 'The video dynamic range of the stream.', 'example': 'HDR or SDR'}, {'name': 'Stream Video Framerate', 'type': 'str', 'value': 'stream_video_framerate', 'description': 'The video framerate of the stream.'}, {'name': 'Stream Video Full Resolution', 'type': 'str', 'value': 'stream_video_full_resolution', 'description': 'The video resolution of the stream with scan type.'}, {'name': 'Stream Video Ref Frames', 'type': 'int', 'value': 'stream_video_ref_frames', 'description': 'The video reference frames of the stream.'}, @@ -470,6 +476,12 @@ NOTIFICATION_PARAMETERS = [ {'name': 'Video Codec Level', 'type': 'int', 'value': 'video_codec_level', 'description': 'The video codec level of the original media.'}, {'name': 'Video Bitrate', 'type': 'int', 'value': 'video_bitrate', 'description': 'The video bitrate of the original media.'}, {'name': 'Video Bit Depth', 'type': 'int', 'value': 'video_bit_depth', 'description': 'The video bit depth of the original media.'}, + {'name': 'Video Chroma Subsampling', 'type': 'str', 'value': 'video_chroma_subsampling', 'description': 'The video chroma subsampling of the original media.'}, + {'name': 'Video Color Primaries', 'type': 'srt', 'value': 'video_color_primaries', 'description': 'The video color primaries of the original media.'}, + {'name': 'Video Color Range', 'type': 'srt', 'value': 'video_color_range', 'description': 'The video color range of the original media.'}, + {'name': 'Video Color Space', 'type': 'str', 'value': 'video_color_space', 'description': 'The video color space of the original media.'}, + {'name': 'Video Color Transfer Function', 'type': 'str', 'value': 'video_color_trc', 'description': 'The video transfer function of the original media.'}, + {'name': 'Video Dynamic Range', 'type': 'str', 'value': 'video_dynamic_range', 'description': 'The video dynamic range of the original media.', 'example': 'HDR or SDR'}, {'name': 'Video Framerate', 'type': 'str', 'value': 'video_framerate', 'description': 'The video framerate of the original media.'}, {'name': 'Video Full Resolution', 'type': 'str', 'value': 'video_full_resolution', 'description': 'The video resolution of the original media with scan type.'}, {'name': 'Video Ref Frames', 'type': 'int', 'value': 'video_ref_frames', 'description': 'The video reference frames of the original media.'}, diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py index 9c14a447..2901bb80 100644 --- a/plexpy/notification_handler.py +++ b/plexpy/notification_handler.py @@ -830,6 +830,12 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m 'stream_video_codec_level': notify_params['stream_video_codec_level'], 'stream_video_bitrate': notify_params['stream_video_bitrate'], 'stream_video_bit_depth': notify_params['stream_video_bit_depth'], + 'stream_video_chroma_subsampling': notify_params['stream_video_chroma_subsampling'], + 'stream_video_color_primaries': notify_params['stream_video_color_primaries'], + 'stream_video_color_range': notify_params['stream_video_color_range'], + 'stream_video_color_space': notify_params['stream_video_color_space'], + 'stream_video_color_trc': notify_params['stream_video_color_trc'], + 'stream_video_dynamic_range': notify_params['stream_video_dynamic_range'], 'stream_video_framerate': notify_params['stream_video_framerate'], 'stream_video_full_resolution': notify_params['stream_video_full_resolution'], 'stream_video_ref_frames': notify_params['stream_video_ref_frames'], @@ -940,6 +946,12 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m 'video_codec_level': notify_params['video_codec_level'], 'video_bitrate': notify_params['video_bitrate'], 'video_bit_depth': notify_params['video_bit_depth'], + 'video_chroma_subsampling': notify_params['video_chroma_subsampling'], + 'video_color_primaries': notify_params['video_color_primaries'], + 'video_color_range': notify_params['video_color_range'], + 'video_color_space': notify_params['video_color_space'], + 'video_color_trc': notify_params['video_color_trc'], + 'video_dynamic_range': notify_params['video_dynamic_range'], 'video_framerate': notify_params['video_framerate'], 'video_full_resolution': notify_params['video_full_resolution'], 'video_ref_frames': notify_params['video_ref_frames'], From d6220a921a048cbd3c595d611a33c25aee7496a9 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Sat, 18 Jan 2020 11:46:43 -0800 Subject: [PATCH 36/39] Log dynamic range to history and show in stream info modal --- data/interfaces/default/stream_data.html | 5 ++++ plexpy/__init__.py | 31 +++++++++++++++++++++--- plexpy/activity_processor.py | 4 +++ plexpy/datafactory.py | 12 ++++++--- 4 files changed, 45 insertions(+), 7 deletions(-) diff --git a/data/interfaces/default/stream_data.html b/data/interfaces/default/stream_data.html index a6e9099e..3165723c 100644 --- a/data/interfaces/default/stream_data.html +++ b/data/interfaces/default/stream_data.html @@ -178,6 +178,11 @@ DOCUMENTATION :: END ${data['stream_video_framerate']} ${data['video_framerate']} + + Dynamic Range + ${data['stream_video_dynamic_range']} + ${data['video_dynamic_range']} + Aspect Ratio - diff --git a/plexpy/__init__.py b/plexpy/__init__.py index 4f4e1293..ea41a3fc 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -585,12 +585,14 @@ def dbcheck(): 'view_offset INTEGER DEFAULT 0, duration INTEGER, video_decision TEXT, audio_decision TEXT, ' 'transcode_decision TEXT, container TEXT, bitrate INTEGER, width INTEGER, height INTEGER, ' 'video_codec TEXT, video_bitrate INTEGER, video_resolution TEXT, video_width INTEGER, video_height INTEGER, ' - 'video_framerate TEXT, video_scan_type TEXT, video_full_resolution TEXT, aspect_ratio TEXT, ' + 'video_framerate TEXT, video_scan_type TEXT, video_full_resolution TEXT, ' + 'video_dynamic_range TEXT, aspect_ratio TEXT, ' 'audio_codec TEXT, audio_bitrate INTEGER, audio_channels INTEGER, subtitle_codec TEXT, ' 'stream_bitrate INTEGER, stream_video_resolution TEXT, quality_profile TEXT, ' 'stream_container_decision TEXT, stream_container TEXT, ' 'stream_video_decision TEXT, stream_video_codec TEXT, stream_video_bitrate INTEGER, stream_video_width INTEGER, ' 'stream_video_height INTEGER, stream_video_framerate TEXT, stream_video_scan_type TEXT, stream_video_full_resolution TEXT, ' + 'stream_video_dynamic_range TEXT, ' 'stream_audio_decision TEXT, stream_audio_codec TEXT, stream_audio_bitrate INTEGER, stream_audio_channels INTEGER, ' 'subtitles INTEGER, stream_subtitle_decision TEXT, stream_subtitle_codec TEXT, ' 'transcode_protocol TEXT, transcode_container TEXT, ' @@ -620,7 +622,7 @@ def dbcheck(): 'video_decision TEXT, audio_decision TEXT, transcode_decision TEXT, duration INTEGER DEFAULT 0, ' 'container TEXT, bitrate INTEGER, width INTEGER, height INTEGER, video_bitrate INTEGER, video_bit_depth INTEGER, ' 'video_codec TEXT, video_codec_level TEXT, video_width INTEGER, video_height INTEGER, video_resolution TEXT, ' - 'video_framerate TEXT, video_scan_type TEXT, video_full_resolution TEXT, aspect_ratio TEXT, ' + 'video_framerate TEXT, video_scan_type TEXT, video_full_resolution TEXT, video_dynamic_range TEXT, aspect_ratio TEXT, ' 'audio_bitrate INTEGER, audio_codec TEXT, audio_channels INTEGER, transcode_protocol TEXT, ' 'transcode_container TEXT, transcode_video_codec TEXT, transcode_audio_codec TEXT, ' 'transcode_audio_channels INTEGER, transcode_width INTEGER, transcode_height INTEGER, ' @@ -630,7 +632,7 @@ def dbcheck(): 'stream_container TEXT, stream_container_decision TEXT, stream_bitrate INTEGER, ' 'stream_video_decision TEXT, stream_video_bitrate INTEGER, stream_video_codec TEXT, stream_video_codec_level TEXT, ' 'stream_video_bit_depth INTEGER, stream_video_height INTEGER, stream_video_width INTEGER, stream_video_resolution TEXT, ' - 'stream_video_framerate TEXT, stream_video_scan_type TEXT, stream_video_full_resolution TEXT, ' + 'stream_video_framerate TEXT, stream_video_scan_type TEXT, stream_video_full_resolution TEXT, stream_video_dynamic_range TEXT, ' 'stream_audio_decision TEXT, stream_audio_codec TEXT, stream_audio_bitrate INTEGER, stream_audio_channels INTEGER, ' 'stream_subtitle_decision TEXT, stream_subtitle_codec TEXT, stream_subtitle_container TEXT, stream_subtitle_forced INTEGER, ' 'subtitles INTEGER, subtitle_codec TEXT, synced_version INTEGER, synced_version_profile TEXT, ' @@ -1203,6 +1205,18 @@ def dbcheck(): 'ALTER TABLE sessions ADD COLUMN stream_video_full_resolution TEXT' ) + # Upgrade sessions table from earlier versions + try: + c_db.execute('SELECT video_dynamic_range FROM sessions') + except sqlite3.OperationalError: + logger.debug(u"Altering database. Updating database table sessions.") + c_db.execute( + 'ALTER TABLE sessions ADD COLUMN video_dynamic_range TEXT' + ) + c_db.execute( + 'ALTER TABLE sessions ADD COLUMN stream_video_dynamic_range TEXT' + ) + # Upgrade session_history table from earlier versions try: c_db.execute('SELECT reference_id FROM session_history') @@ -1541,6 +1555,17 @@ def dbcheck(): 'ELSE stream_video_resolution || "p" END)' ) + # Upgrade session_history_media_info table from earlier versions + try: + c_db.execute('SELECT video_dynamic_range FROM session_history_media_info') + except sqlite3.OperationalError: + logger.debug(u"Altering database. Updating database table session_history_media_info.") + c_db.execute( + 'ALTER TABLE session_history_media_info ADD COLUMN video_dynamic_range TEXT ' + ) + c_db.execute( + 'ALTER TABLE session_history_media_info ADD COLUMN stream_video_dynamic_range TEXT ' + ) # Upgrade users table from earlier versions try: c_db.execute('SELECT do_notify FROM users') diff --git a/plexpy/activity_processor.py b/plexpy/activity_processor.py index 7bccda02..03df9fb5 100644 --- a/plexpy/activity_processor.py +++ b/plexpy/activity_processor.py @@ -78,6 +78,7 @@ class ActivityProcessor(object): 'video_framerate': session.get('video_framerate', ''), 'video_scan_type': session.get('video_scan_type', ''), 'video_full_resolution': session.get('video_full_resolution', ''), + 'video_dynamic_range': session.get('video_dynamic_range', ''), 'aspect_ratio': session.get('aspect_ratio', ''), 'audio_codec': session.get('audio_codec', ''), 'audio_bitrate': session.get('audio_bitrate', ''), @@ -110,6 +111,7 @@ class ActivityProcessor(object): 'stream_video_framerate': session.get('stream_video_framerate', ''), 'stream_video_scan_type': session.get('stream_video_scan_type', ''), 'stream_video_full_resolution': session.get('stream_video_full_resolution', ''), + 'stream_video_dynamic_range': session.get('stream_video_dynamic_range', ''), 'stream_audio_decision': session.get('stream_audio_decision', ''), 'stream_audio_codec': session.get('stream_audio_codec', ''), 'stream_audio_bitrate': session.get('stream_audio_bitrate', ''), @@ -353,6 +355,7 @@ class ActivityProcessor(object): 'video_framerate': session['video_framerate'], 'video_scan_type': session['video_scan_type'], 'video_full_resolution': session['video_full_resolution'], + 'video_dynamic_range': session['video_dynamic_range'], 'aspect_ratio': session['aspect_ratio'], 'audio_codec': session['audio_codec'], 'audio_bitrate': session['audio_bitrate'], @@ -387,6 +390,7 @@ class ActivityProcessor(object): 'stream_video_framerate': session['stream_video_framerate'], 'stream_video_scan_type': session['stream_video_scan_type'], 'stream_video_full_resolution': session['stream_video_full_resolution'], + 'stream_video_dynamic_range': session['stream_video_dynamic_range'], 'stream_audio_decision': session['stream_audio_decision'], 'stream_audio_codec': session['stream_audio_codec'], 'stream_audio_bitrate': session['stream_audio_bitrate'], diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py index ef925eb7..1e635e0f 100644 --- a/plexpy/datafactory.py +++ b/plexpy/datafactory.py @@ -878,11 +878,12 @@ class DataFactory(object): query = 'SELECT bitrate, video_full_resolution, ' \ 'optimized_version, optimized_version_profile, optimized_version_title, ' \ 'synced_version, synced_version_profile, ' \ - 'container, video_codec, video_bitrate, video_width, video_height, video_framerate, aspect_ratio, ' \ + 'container, video_codec, video_bitrate, video_width, video_height, video_framerate, ' \ + 'video_dynamic_range, aspect_ratio, ' \ 'audio_codec, audio_bitrate, audio_channels, subtitle_codec, ' \ 'stream_bitrate, stream_video_full_resolution, quality_profile, stream_container_decision, stream_container, ' \ 'stream_video_decision, stream_video_codec, stream_video_bitrate, stream_video_width, stream_video_height, ' \ - 'stream_video_framerate, ' \ + 'stream_video_framerate, stream_video_dynamic_range, ' \ 'stream_audio_decision, stream_audio_codec, stream_audio_bitrate, stream_audio_channels, ' \ 'subtitles, stream_subtitle_decision, stream_subtitle_codec, ' \ 'transcode_hw_decoding, transcode_hw_encoding, ' \ @@ -899,11 +900,12 @@ class DataFactory(object): query = 'SELECT bitrate, video_full_resolution, ' \ 'optimized_version, optimized_version_profile, optimized_version_title, ' \ 'synced_version, synced_version_profile, ' \ - 'container, video_codec, video_bitrate, video_width, video_height, video_framerate, aspect_ratio, ' \ + 'container, video_codec, video_bitrate, video_width, video_height, video_framerate, ' \ + 'video_dynamic_range, aspect_ratio, ' \ 'audio_codec, audio_bitrate, audio_channels, subtitle_codec, ' \ 'stream_bitrate, stream_video_full_resolution, quality_profile, stream_container_decision, stream_container, ' \ 'stream_video_decision, stream_video_codec, stream_video_bitrate, stream_video_width, stream_video_height, ' \ - 'stream_video_framerate, ' \ + 'stream_video_framerate, stream_video_dynamic_range, ' \ 'stream_audio_decision, stream_audio_codec, stream_audio_bitrate, stream_audio_channels, ' \ 'subtitles, stream_subtitle_decision, stream_subtitle_codec, ' \ 'transcode_hw_decoding, transcode_hw_encoding, ' \ @@ -950,6 +952,7 @@ class DataFactory(object): 'video_width': item['video_width'], 'video_height': item['video_height'], 'video_framerate': item['video_framerate'], + 'video_dynamic_range': item['video_dynamic_range'], 'aspect_ratio': item['aspect_ratio'], 'audio_codec': item['audio_codec'], 'audio_bitrate': item['audio_bitrate'], @@ -966,6 +969,7 @@ class DataFactory(object): 'stream_video_width': item['stream_video_width'], 'stream_video_height': item['stream_video_height'], 'stream_video_framerate': item['stream_video_framerate'], + 'stream_video_dynamic_range': item['stream_video_dynamic_range'], 'stream_audio_decision': item['stream_audio_decision'], 'stream_audio_codec': item['stream_audio_codec'], 'stream_audio_bitrate': item['stream_audio_bitrate'], From 6380de3e6c0a29c078220907214f204ad4c81cdd Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Sun, 19 Jan 2020 16:00:36 -0800 Subject: [PATCH 37/39] Match session user using user_id instead of username --- plexpy/pmsconnect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py index f81f35e3..21efbbf2 100644 --- a/plexpy/pmsconnect.py +++ b/plexpy/pmsconnect.py @@ -1528,7 +1528,7 @@ class PmsConnect(object): # Get the user details user_info = session.getElementsByTagName('User')[0] - user_details = users.Users().get_details(user=helpers.get_xml_attr(user_info, 'title')) + user_details = users.Users().get_details(user_id=helpers.get_xml_attr(user_info, 'id')) # Get the player details player_info = session.getElementsByTagName('Player')[0] From 55ffd68023b5d3788e72f4705515d974a39b86d1 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Sun, 19 Jan 2020 16:02:56 -0800 Subject: [PATCH 38/39] Refactor log filter --- plexpy/logger.py | 67 ++++++++++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/plexpy/logger.py b/plexpy/logger.py index 2345e139..19bd9c5c 100644 --- a/plexpy/logger.py +++ b/plexpy/logger.py @@ -80,7 +80,7 @@ class BlacklistFilter(logging.Filter): Log filter for blacklisted tokens and passwords """ def __init__(self): - pass + super(BlacklistFilter, self).__init__() def filter(self, record): if not plexpy.CONFIG.LOG_BLACKLIST: @@ -98,30 +98,29 @@ class BlacklistFilter(logging.Filter): return True -class PublicIPFilter(logging.Filter): +class RegexFilter(logging.Filter): """ - Log filter for public IP addresses + Base class for regex log filter """ def __init__(self): - pass + super(RegexFilter, self).__init__() + + self.regex = re.compile(r'') def filter(self, record): if not plexpy.CONFIG.LOG_BLACKLIST: return True try: - # Currently only checking for ipv4 addresses - ipv4 = re.findall(r'[0-9]+(?:\.[0-9]+){3}(?!\d*-[a-z0-9]{6})', record.msg) - for ip in ipv4: - if helpers.is_public_ip(ip): - record.msg = record.msg.replace(ip, ip.partition('.')[0] + '.***.***.***') + matches = self.regex.findall(record.msg) + for match in matches: + record.msg = self.replace(record.msg, match) args = [] for arg in record.args: - ipv4 = re.findall(r'[0-9]+(?:\.[0-9]+){3}(?!\d*-[a-z0-9]{6})', arg) if isinstance(arg, basestring) else [] - for ip in ipv4: - if helpers.is_public_ip(ip): - arg = arg.replace(ip, ip.partition('.')[0] + '.***.***.***') + matches = self.regex.findall(arg) if isinstance(arg, basestring) else [] + for match in matches: + arg = self.replace(arg, match) args.append(arg) record.args = tuple(args) except: @@ -129,31 +128,37 @@ class PublicIPFilter(logging.Filter): return True + def replace(self, text, match): + return text -class PlexTokenFilter(logging.Filter): + +class PublicIPFilter(RegexFilter): + """ + Log filter for public IP addresses + """ + def __init__(self): + super(PublicIPFilter, self).__init__() + + # Currently only checking for ipv4 addresses + self.regex = re.compile(r'[0-9]+(?:\.[0-9]+){3}(?!\d*-[a-z0-9]{6})') + + def replace(self, text, ip): + if helpers.is_public_ip(ip): + return text.replace(ip, ip.partition('.')[0] + '.***.***.***') + return text + + +class PlexTokenFilter(RegexFilter): """ Log filter for X-Plex-Token """ def __init__(self): - pass + super(PlexTokenFilter, self).__init__() - def filter(self, record): - try: - tokens = re.findall(r'X-Plex-Token(?:=|%3D)([a-zA-Z0-9]+)', record.msg) - for token in tokens: - record.msg = record.msg.replace(token, 8 * '*' + token[-2:]) + self.regex = re.compile(r'X-Plex-Token(?:=|%3D)([a-zA-Z0-9]+)') - args = [] - for arg in record.args: - tokens = re.findall(r'X-Plex-Token(?:=|%3D)([a-zA-Z0-9]+)', arg) if isinstance(arg, basestring) else [] - for token in tokens: - arg = arg.replace(token, 8 * '*' + token[-2:]) - args.append(arg) - record.args = tuple(args) - except: - pass - - return True + def replace(self, text, token): + return text.replace(token, 8 * '*' + token[-2:]) @contextlib.contextmanager From 0b6d9a489046c0b007f73d6d6f5137335fdaa7ce Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Sun, 19 Jan 2020 16:03:10 -0800 Subject: [PATCH 39/39] Add email address log filter --- plexpy/logger.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/plexpy/logger.py b/plexpy/logger.py index 19bd9c5c..b8a6ec3f 100644 --- a/plexpy/logger.py +++ b/plexpy/logger.py @@ -148,6 +148,22 @@ class PublicIPFilter(RegexFilter): return text +class EmailFilter(RegexFilter): + """ + Log filter for email addresses + """ + def __init__(self): + super(EmailFilter, self).__init__() + + self.regex = re.compile(r'([a-z0-9!#$%&\'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&\'*+/=?^_`{|}~-]+)*@' + r'(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)', + re.IGNORECASE) + + def replace(self, text, email): + email_parts = email.partition('@') + return text.replace(email, email_parts[0][:2] + 8 * '*' + email_parts[1] + 8 * '*') + + class PlexTokenFilter(RegexFilter): """ Log filter for X-Plex-Token @@ -299,6 +315,7 @@ def initLogger(console=False, log_dir=False, verbose=False): for handler in logger.handlers + logger_api.handlers + logger_plex_websocket.handlers: handler.addFilter(BlacklistFilter()) handler.addFilter(PublicIPFilter()) + handler.addFilter(EmailFilter()) handler.addFilter(PlexTokenFilter()) # Install exception hooks