From 319d521773aaad214aeea1c1237e9352d628192d Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Tue, 14 Jun 2016 18:47:14 -0700 Subject: [PATCH 01/55] Update Facebook instructions --- plexpy/notifiers.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py index 983b34a2..28c5cbaa 100644 --- a/plexpy/notifiers.py +++ b/plexpy/notifiers.py @@ -2425,13 +2425,14 @@ class FacebookNotifier(object): config_option = [{'label': 'Instructions', 'description': 'Step 1: Visit \ Facebook Developers to add a new app using basic setup.
\ - Step 2: Go to Settings > Advanced and fill in \ - Valid OAuth redirect URIs with your PlexPy URL (e.g. http://localhost:8181).
\ - Step 3: Go to App Review and toggle public to Yes.
\ - Step 4: Fill in the PlexPy URL below with the exact same URL from Step 3.
\ - Step 5: Fill in the App ID and App Secret below.
\ - Step 6: Click the Request Authorization button below.
\ - Step 7: Fill in your Group ID below.', + Step 2: Click Add Product on the left, then Get Started \ + for Facebook Login.
\ + Step 3: Fill in Valid OAuth redirect URIs with your PlexPy URL (e.g. http://localhost:8181).
\ + Step 4: Click App Review on the left and toggle "make public" to Yes.
\ + Step 5: Fill in the PlexPy URL below with the exact same URL from Step 3.
\ + Step 6: Fill in the App ID and App Secret below.
\ + Step 7: Click the Request Authorization button below.
\ + Step 8: Fill in your Group ID below.', 'input_type': 'help' }, {'label': 'PlexPy URL', From 8706e72f6abca60517a66da0e30d2f4c9ae9be2b Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Tue, 14 Jun 2016 20:49:27 -0700 Subject: [PATCH 02/55] Make sure fallback to username if friendly name is blank --- plexpy/datafactory.py | 4 ++-- plexpy/graphs.py | 12 ++++++++---- plexpy/libraries.py | 5 +++-- plexpy/users.py | 8 ++++---- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py index b90e1b76..84a1b337 100644 --- a/plexpy/datafactory.py +++ b/plexpy/datafactory.py @@ -69,8 +69,8 @@ class DataFactory(object): 'SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) AS paused_counter', 'session_history.user_id', 'session_history.user', - '(CASE WHEN users.friendly_name IS NULL THEN users.username ELSE users.friendly_name END) \ - AS friendly_name', + '(CASE WHEN users.friendly_name IS NULL OR TRIM(users.friendly_name) = "" \ + THEN users.username ELSE users.friendly_name END) AS friendly_name', 'platform', 'player', 'ip_address', diff --git a/plexpy/graphs.py b/plexpy/graphs.py index 0ed17392..3666a471 100644 --- a/plexpy/graphs.py +++ b/plexpy/graphs.py @@ -463,7 +463,8 @@ class Graphs(object): if y_axis == 'plays': query = 'SELECT ' \ 'users.user_id, users.username, ' \ - '(CASE WHEN users.friendly_name IS NULL THEN users.username ELSE users.friendly_name END) AS friendly_name,' \ + '(CASE WHEN users.friendly_name IS NULL OR TRIM(users.friendly_name) = "" ' \ + ' THEN users.username ELSE users.friendly_name END) AS friendly_name,' \ 'SUM(CASE WHEN media_type = "episode" THEN 1 ELSE 0 END) AS tv_count, ' \ 'SUM(CASE WHEN media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \ 'SUM(CASE WHEN media_type = "track" THEN 1 ELSE 0 END) AS music_count, ' \ @@ -479,7 +480,8 @@ class Graphs(object): else: query = 'SELECT ' \ 'users.user_id, users.username, ' \ - '(CASE WHEN users.friendly_name IS NULL THEN users.username ELSE users.friendly_name END) AS friendly_name,' \ + '(CASE WHEN users.friendly_name IS NULL OR TRIM(users.friendly_name) = "" ' \ + ' THEN users.username ELSE users.friendly_name END) AS friendly_name,' \ 'SUM(CASE WHEN media_type = "episode" AND stopped > 0 THEN (stopped - started) ' \ ' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS tv_count, ' \ 'SUM(CASE WHEN media_type = "movie" AND stopped > 0 THEN (stopped - started) ' \ @@ -904,7 +906,8 @@ class Graphs(object): if y_axis == 'plays': query = 'SELECT ' \ 'users.user_id, users.username, ' \ - '(CASE WHEN users.friendly_name IS NULL THEN users.username ELSE users.friendly_name END) AS friendly_name, ' \ + '(CASE WHEN users.friendly_name IS NULL OR TRIM(users.friendly_name) = "" ' \ + ' THEN users.username ELSE users.friendly_name END) AS friendly_name,' \ 'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \ 'THEN 1 ELSE 0 END) AS dp_count, ' \ 'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \ @@ -925,7 +928,8 @@ class Graphs(object): else: query = 'SELECT ' \ 'users.user_id, users.username, ' \ - '(CASE WHEN users.friendly_name IS NULL THEN users.username ELSE users.friendly_name END) AS friendly_name, ' \ + '(CASE WHEN users.friendly_name IS NULL OR TRIM(users.friendly_name) = "" ' \ + ' THEN users.username ELSE users.friendly_name END) AS friendly_name,' \ 'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \ 'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \ ' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS dp_count, ' \ diff --git a/plexpy/libraries.py b/plexpy/libraries.py index c942b768..649d454a 100644 --- a/plexpy/libraries.py +++ b/plexpy/libraries.py @@ -753,8 +753,9 @@ class Libraries(object): try: if str(section_id).isdigit(): - query = 'SELECT (CASE WHEN users.friendly_name IS NULL THEN users.username ' \ - 'ELSE users.friendly_name END) AS friendly_name, users.user_id, users.thumb, COUNT(user) AS user_count ' \ + query = 'SELECT (CASE WHEN users.friendly_name IS NULL OR TRIM(users.friendly_name) = "" ' \ + 'THEN users.username ELSE users.friendly_name END) AS friendly_name, ' \ + 'users.user_id, users.thumb, COUNT(user) AS user_count ' \ 'FROM session_history ' \ 'JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \ 'JOIN users ON users.user_id = session_history.user_id ' \ diff --git a/plexpy/users.py b/plexpy/users.py index ef530e55..949c9f44 100644 --- a/plexpy/users.py +++ b/plexpy/users.py @@ -181,8 +181,8 @@ class Users(object): 'session_history_media_info.transcode_decision', 'session_history.user', 'session_history.user_id as custom_user_id', - '(CASE WHEN users.friendly_name IS NULL THEN users.username ELSE \ - users.friendly_name END) AS friendly_name' + '(CASE WHEN users.friendly_name IS NULL OR TRIM(users.friendly_name) = "" \ + THEN users.username ELSE users.friendly_name END) AS friendly_name' ] try: @@ -717,8 +717,8 @@ class Users(object): 'user_login.host', 'user_login.user_agent', 'user_login.timestamp', - '(CASE WHEN users.friendly_name IS NULL THEN user_login.user ELSE users.friendly_name END) \ - AS friendly_name' + '(CASE WHEN users.friendly_name IS NULL OR TRIM(users.friendly_name) = "" \ + THEN users.username ELSE users.friendly_name END) AS friendly_name' ] try: From 652ca73126d81e99a7c2ca7d8a04771599ada25a Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Sat, 18 Jun 2016 09:33:37 -0700 Subject: [PATCH 03/55] Make "Enable Posters in Notifications" a global toggle --- plexpy/notification_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py index b64afab5..9319d9a1 100644 --- a/plexpy/notification_handler.py +++ b/plexpy/notification_handler.py @@ -624,7 +624,7 @@ def build_notify_text(session=None, timeline=None, notify_action=None, agent_id= else: thumb = None - if thumb: + if plexpy.CONFIG.NOTIFY_UPLOAD_POSTERS and thumb: # Try to retrieve a poster_url from the database data_factory = datafactory.DataFactory() poster_url = data_factory.get_poster_url(rating_key=poster_key) From 1120aa3841a482f25b11f40c932ecf54fe983f02 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Sat, 18 Jun 2016 16:36:13 -0700 Subject: [PATCH 04/55] Fix direct stream count in current activity header --- plexpy/webserve.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plexpy/webserve.py b/plexpy/webserve.py index f4ff17c2..667af5af 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -269,7 +269,7 @@ class WebInterface(object): else: if s['video_decision'] == 'transcode' or s['audio_decision'] == 'transcode': data['transcode'] += 1 - elif s['video_decision'] == 'direct copy' or s['audio_decision'] == 'copy play': + elif s['video_decision'] == 'copy' or s['audio_decision'] == 'copy': data['direct_stream'] += 1 else: data['direct_play'] += 1 From b12bde4f79bd4e6784b1a18f6c0852ceb9fe6e29 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Sun, 19 Jun 2016 23:31:52 -0700 Subject: [PATCH 05/55] Fix apostrophe in Arnold quote --- plexpy/webserve.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 667af5af..21011577 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -4202,7 +4202,7 @@ class WebInterface(object): 'Can you hurry up. My horse is getting tired.', 'What killed the dinosaurs? The Ice Age!', 'That\'s for sleeping with my wife!', - 'Remember when I said I’d kill you last... I lied!', + 'Remember when I said I\'d kill you last... I lied!', 'You want to be a farmer? Here\'s a couple of acres', 'Now, this is the plan. Get your ass to Mars.', 'I just had a terrible thought... What if this is a dream?' From 8c6e142314e3c409b619e92834b90b7a5bb1e272 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Mon, 20 Jun 2016 19:09:02 -0700 Subject: [PATCH 06/55] Hide poster refresh icon in guest mode --- data/interfaces/default/current_activity.html | 2 ++ .../default/current_activity_instance.html | 2 ++ data/interfaces/default/home_stats.html | 28 +++++++++++++++++++ data/interfaces/default/info.html | 8 ++++++ .../default/info_children_list.html | 6 ++++ .../default/info_search_results_list.html | 14 ++++++++++ data/interfaces/default/library.html | 2 ++ .../default/library_recently_added.html | 2 ++ data/interfaces/default/recently_added.html | 6 ++++ .../default/user_recently_watched.html | 2 ++ 10 files changed, 72 insertions(+) diff --git a/data/interfaces/default/current_activity.html b/data/interfaces/default/current_activity.html index 8446d7c6..1e309645 100644 --- a/data/interfaces/default/current_activity.html +++ b/data/interfaces/default/current_activity.html @@ -106,7 +106,9 @@ DOCUMENTATION :: END % else:
% endif + % if _session['user_group'] == 'admin': + % endif
@@ -42,25 +43,26 @@ \ No newline at end of file diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index be3d66c9..bba6fdf3 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -3004,4 +3004,7 @@ a:hover .overlay-refresh-image { } a:hover .overlay-refresh-image:hover { opacity: .9; +} +#ip_error a { + color: #e9a049; } \ No newline at end of file diff --git a/data/interfaces/default/js/script.js b/data/interfaces/default/js/script.js index 74be6f51..4ad59f45 100644 --- a/data/interfaces/default/js/script.js +++ b/data/interfaces/default/js/script.js @@ -54,6 +54,32 @@ function showMsg(msg, loader, timeout, ms, error) { } } +function confirmAjaxCall(url, msg, loader_msg, callback) { + $("#confirm-message").html(msg); + $('#confirm-modal').modal(); + $('#confirm-modal').one('click', '#confirm-button', function () { + if (loader_msg) { + showMsg(loader_msg, true, false) + } + $.ajax({ + url: url, + type: 'POST', + complete: function (xhr, status) { + result = $.parseJSON(xhr.responseText); + msg = result.message; + if (result.result == 'success') { + showMsg(' ' + msg, false, true, 5000) + } else { + showMsg(' ' + msg, false, true, 5000, true) + } + if (typeof callback === "function") { + callback(); + } + } + }); + }); +} + function doAjaxCall(url, elem, reload, form, callback) { // Set Message feedback = $("#ajaxMsg"); diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index 4df58467..b6222be8 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -4,7 +4,7 @@ import sys import plexpy - from plexpy import common, logger, notifiers, versioncheck + from plexpy import common, notifiers, versioncheck from plexpy.helpers import anon_url available_notification_agents = sorted(notifiers.available_notification_agents(), key=lambda k: k['name']) @@ -62,86 +62,10 @@

PlexPy Configuration

- - - % if plexpy.CURRENT_VERSION: - - - - - - - - - % endif - - - - - - - - - - - - - - - - - - - - - - - % if config['geoip_db']: - - % else: - - % endif - - % if plexpy.ARGS: - - - - - % endif - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Git Branch:${plexpy.CONFIG.GIT_BRANCH}
Git Commit Hash:${plexpy.CURRENT_VERSION}
Configuration File:${plexpy.CONFIG_FILE}
Database File:${plexpy.DB_FILE}
Log File:${os.path.join(config['log_dir'], logger.FILENAME)}
Backup Directory:${config['backup_dir']}
Cache Directory:${config['cache_dir']}
GeoLite2 Database:${config['geoip_db']} | Reinstall / UpdateClick here to install the database.
Arguments:${plexpy.ARGS}
Platform:${common.PLATFORM} ${common.PLATFORM_VERSION}
Python Version:${sys.version}
Plex Forums:https://forums.plex.tv/discussion/169591/plexpy-another-plex-monitoring-program
Source:https://github.com/drzoidberg33/plexpy
Wiki:https://github.com/drzoidberg33/plexpy/wiki
Issues:https://github.com/drzoidberg33/plexpy/issues
Feature Requests:http://feathub.com/drzoidberg33/plexpy
Gitter Chat:https://gitter.im/drzoidberg33/plexpy
+
+
Loading configuration table...
+
+

PlexPy Scheduled Tasks

@@ -2134,6 +2058,31 @@ \ No newline at end of file diff --git a/plexpy/helpers.py b/plexpy/helpers.py index a36d99c8..28142e92 100644 --- a/plexpy/helpers.py +++ b/plexpy/helpers.py @@ -577,6 +577,19 @@ def install_geoip_db(): return True +def uninstall_geoip_db(): + logger.debug(u"PlexPy Helpers :: Uninstalling the GeoLite2 database...") + try: + os.remove(plexpy.CONFIG.GEOIP_DB) + plexpy.CONFIG.__setattr__('GEOIP_DB', '') + plexpy.CONFIG.write() + except Exception as e: + logger.error(u"PlexPy Helpers :: Failed to uninstall the GeoLite2 database: %s" % e) + return False + + logger.debug(u"PlexPy Helpers :: GeoLite2 database uninstalled successfully.") + return True + def geoip_lookup(ip_address): if not plexpy.CONFIG.GEOIP_DB: return 'GeoLite2 database not installed. Please install from the ' \ diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 4ff7eeaf..538d361c 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -2802,15 +2802,30 @@ class WebInterface(object): @cherrypy.expose @cherrypy.tools.json_out() @requireAuth(member_of("admin")) + @addtoapi() def install_geoip_db(self): - """ Downloads and installs the GeoIP database """ + """ Downloads and installs the GeoLite2 database """ result = helpers.install_geoip_db() if result: - return {'result': 'success', 'message': 'GeoIP database installed successful.'} + return {'result': 'success', 'message': 'GeoLite2 database installed successful.'} else: - return {'result': 'error', 'message': 'GeoIP database install failed.'} + return {'result': 'error', 'message': 'GeoLite2 database install failed.'} + + @cherrypy.expose + @cherrypy.tools.json_out() + @requireAuth(member_of("admin")) + @addtoapi() + def uninstall_geoip_db(self): + """ Uninstalls the GeoLite2 database """ + + result = helpers.uninstall_geoip_db() + + if result: + return {'result': 'success', 'message': 'GeoLite2 database uninstalled successfully.'} + else: + return {'result': 'error', 'message': 'GeoLite2 database uninstall failed.'} @cherrypy.expose @requireAuth(member_of("admin")) From 4d28e4603f536264438bc768c3f544f180a03821 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Fri, 1 Jul 2016 10:16:24 -0700 Subject: [PATCH 26/55] Respect custom GeoLite2 path on reinstalling database --- plexpy/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plexpy/helpers.py b/plexpy/helpers.py index 28142e92..37b1aa5e 100644 --- a/plexpy/helpers.py +++ b/plexpy/helpers.py @@ -525,7 +525,7 @@ def install_geoip_db(): md5_checksum = '' temp_gz = os.path.join(plexpy.CONFIG.CACHE_DIR, geolite2_gz) - geolite2_db = os.path.join(plexpy.DATA_DIR, geolite2_db) + geolite2_db = plexpy.CONFIG.GEOIP_DB or os.path.join(plexpy.DATA_DIR, geolite2_db) # Retrieve the GeoLite2 gzip file logger.debug(u"PlexPy Helpers :: Downloading GeoLite2 gzip file from MaxMind...") From f7810f7f95683207eb9d81f08c81a8d052dd6eb9 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Sat, 2 Jul 2016 16:05:37 -0700 Subject: [PATCH 27/55] Reword GeoLite2 install confirmation message --- data/interfaces/default/configuration_table.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/data/interfaces/default/configuration_table.html b/data/interfaces/default/configuration_table.html index b7da81a3..0d14f7c2 100644 --- a/data/interfaces/default/configuration_table.html +++ b/data/interfaces/default/configuration_table.html @@ -102,8 +102,9 @@ DOCUMENTATION :: END $(document).ready(function () { $("#install_geoip_db, #reinstall_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 uses ~65MB in your PlexPy directory.'; + '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 PlexPy directory.
' var url = 'install_geoip_db'; confirmAjaxCall(url, msg, 'Installing GeoLite2 database.', getConfigurationTable); }); From f77538f179db4676904fed16110792f32a5cd00c Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Sat, 2 Jul 2016 16:05:57 -0700 Subject: [PATCH 28/55] Fix guidelines modal for separated configuration table --- data/interfaces/default/configuration_table.html | 10 ++++++++++ data/interfaces/default/settings.html | 10 ---------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/data/interfaces/default/configuration_table.html b/data/interfaces/default/configuration_table.html index 0d14f7c2..4dd14b6e 100644 --- a/data/interfaces/default/configuration_table.html +++ b/data/interfaces/default/configuration_table.html @@ -115,5 +115,15 @@ DOCUMENTATION :: END var url = 'uninstall_geoip_db'; confirmAjaxCall(url, msg, 'Uninstalling GeoLite2 database.', getConfigurationTable); }); + + $('.guidelines-modal-link').on('click', function (e) { + e.preventDefault(); + $('#guidelines-link').attr('href', $('#source-link').attr('href')); + $('#guidelines-type').text($(this).data('id')) + $('#guidelines-modal').modal(); + $('#guidelines-continue').attr('href', $(this).attr('href')).on('click', function () { + $('#guidelines-modal').modal('hide'); + }); + }); }); \ No newline at end of file diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index f46d2343..7744e4b7 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -2560,16 +2560,6 @@ $(document).ready(function() { $('#notify_recently_added_grandparent_note').css('color', c); }); - $('.guidelines-modal-link').on('click', function (e) { - e.preventDefault(); - $('#guidelines-link').attr('href', $('#source-link').attr('href')); - $('#guidelines-type').text($(this).data('id')) - $('#guidelines-modal').modal(); - $('#guidelines-continue').attr('href', $(this).attr('href')).on('click', function () { - $('#guidelines-modal').modal('hide'); - }); - }); - function allowGuestAccessCheck () { if ($("#http_basic_auth").is(":checked")) { $("#allow_guest_access").attr("disabled", true); From 0b085b6d035ac8ff477d6855b09ee73bfb81c216 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Mon, 4 Jul 2016 22:47:59 -0700 Subject: [PATCH 29/55] Notifications for user concurrent streams --- .../default/notification_triggers_modal.html | 7 +++ data/interfaces/default/settings.html | 41 +++++++++++++- plexpy/activity_handler.py | 21 ++++++-- plexpy/activity_processor.py | 20 +++++++ plexpy/config.py | 26 ++++++++- plexpy/notification_handler.py | 53 +++++++++++++++++- plexpy/notifiers.py | 54 ++++++++++++------- plexpy/webserve.py | 4 ++ 8 files changed, 200 insertions(+), 26 deletions(-) diff --git a/data/interfaces/default/notification_triggers_modal.html b/data/interfaces/default/notification_triggers_modal.html index 5de454a7..fc0a0a57 100644 --- a/data/interfaces/default/notification_triggers_modal.html +++ b/data/interfaces/default/notification_triggers_modal.html @@ -57,6 +57,13 @@

Trigger notification when a media item triggers the defined buffer threshold.

+
+ +

Trigger notification when a user has concurrent streams.

+
-

Specify the number of items to show in the top lists for the watch statistics on the home page. Max is 10 items, default is 5 items, 0 to disable.

+

Specify the number of items to show in the top lists for the watch statistics on the home page. Maximum 10 items, default 5 items, 0 to disable.

-

The interval (in seconds) PlexPy will ping your Plex Server. Min 30 seconds, recommended 60 seconds.

+

The interval (in seconds) PlexPy will ping your Plex Server. Minimum 30 seconds, recommended 60 seconds.

Disable to prevent consecutive notifications (i.e. both watched & stopped notifications).

+
+ +

Enable to only get notified of concurrent streams by a single user from different IP addresses.

+
+
+ +
+
+ +
+ +
+

The number of concurrent streams by a single user for PlexPy to trigger a notification. Minimum 2.

+

Recently Added Notifications

@@ -1026,6 +1042,23 @@ +
  • + + +
    • @@ -1562,6 +1595,10 @@ {streams} The number of concurrent streams. + + {user_streams} + The number of concurrent streams by the person streaming. + {user} The friendly name of the person streaming. diff --git a/plexpy/activity_handler.py b/plexpy/activity_handler.py index 08bb2327..40008f39 100644 --- a/plexpy/activity_handler.py +++ b/plexpy/activity_handler.py @@ -55,23 +55,36 @@ class ActivityHandler(object): return None - def update_db_session(self): + def update_db_session(self, session=None): # Update our session temp table values monitor_proc = activity_processor.ActivityProcessor() - monitor_proc.write_session(session=self.get_live_session(), notify=False) + monitor_proc.write_session(session=session, notify=False) def on_start(self): if self.is_valid_session() and self.get_live_session(): logger.debug(u"PlexPy ActivityHandler :: Session %s has started." % str(self.get_session_key())) + session = self.get_live_session() + # Check if any notification agents have notifications enabled if any(d['on_play'] for d in notifiers.available_notification_agents()): # Fire off notifications threading.Thread(target=notification_handler.notify, - kwargs=dict(stream_data=self.get_live_session(), notify_action='play')).start() + kwargs=dict(stream_data=session, notify_action='play')).start() # Write the new session to our temp session table - self.update_db_session() + self.update_db_session(session=session) + + # Check if any notification agents have notifications enabled + if any(d['on_concurrent'] for d in notifiers.available_notification_agents()): + # Check if any concurrent streams by the user + ip = True if plexpy.CONFIG.NOTIFY_CONCURRENT_BY_IP else None + ap = activity_processor.ActivityProcessor() + user_sessions = ap.get_session_by_user_id(user_id=session['user_id'], ip_address=ip) + if len(user_sessions) >= plexpy.CONFIG.NOTIFY_CONCURRENT_THRESHOLD: + # Push any notifications - Push it on it's own thread so we don't hold up our db actions + threading.Thread(target=notification_handler.notify, + kwargs=dict(stream_data=session, notify_action='concurrent')).start() def on_stop(self, force_stop=False): if self.is_valid_session(): diff --git a/plexpy/activity_processor.py b/plexpy/activity_processor.py index 1687d098..0a290b9a 100644 --- a/plexpy/activity_processor.py +++ b/plexpy/activity_processor.py @@ -106,6 +106,16 @@ class ActivityProcessor(object): ip_address = {'ip_address': ip_address} self.db.upsert('sessions', ip_address, keys) + # Check if any notification agents have notifications enabled + if notify and any(d['on_concurrent'] for d in notifiers.available_notification_agents()): + # Check if any concurrent streams by the user + ip = True if plexpy.CONFIG.NOTIFY_CONCURRENT_BY_IP else None + user_sessions = self.get_session_by_user_id(user_id=session['user_id'], ip_address=ip) + if len(user_sessions) >= plexpy.CONFIG.NOTIFY_CONCURRENT_THRESHOLD: + # Push any notifications - Push it on it's own thread so we don't hold up our db actions + threading.Thread(target=notification_handler.notify, + kwargs=dict(stream_data=values, notify_action='concurrent')).start() + return True def write_session_history(self, session=None, import_metadata=None, is_import=False, import_ignore_interval=0): @@ -470,3 +480,13 @@ class ActivityProcessor(object): return last_time['buffer_last_triggered'] return None + + def get_session_by_user_id(self, user_id=None, ip_address=None): + sessions = [] + if str(user_id).isdigit(): + ip = 'GROUP BY ip_address' if ip_address else '' + sessions = self.db.select('SELECT * ' + 'FROM sessions ' + 'WHERE user_id = ? %s' % ip, + [user_id]) + return sessions \ No newline at end of file diff --git a/plexpy/config.py b/plexpy/config.py index 0123d02c..f0b133b1 100644 --- a/plexpy/config.py +++ b/plexpy/config.py @@ -80,6 +80,7 @@ _CONFIG_DEFINITIONS = { 'BOXCAR_ON_EXTUP': (int, 'Boxcar', 0), 'BOXCAR_ON_INTUP': (int, 'Boxcar', 0), 'BOXCAR_ON_PMSUPDATE': (int, 'Boxcar', 0), + 'BOXCAR_ON_CONCURRENT': (int, 'Boxcar', 0), 'BROWSER_ENABLED': (int, 'Boxcar', 0), 'BROWSER_AUTO_HIDE_DELAY': (int, 'Boxcar', 5), 'BROWSER_ON_PLAY': (int, 'BROWSER', 0), @@ -94,6 +95,7 @@ _CONFIG_DEFINITIONS = { 'BROWSER_ON_EXTUP': (int, 'BROWSER', 0), 'BROWSER_ON_INTUP': (int, 'BROWSER', 0), 'BROWSER_ON_PMSUPDATE': (int, 'BROWSER', 0), + 'BROWSER_ON_CONCURRENT': (int, 'BROWSER', 0), 'BUFFER_THRESHOLD': (int, 'Monitoring', 3), 'BUFFER_WAIT': (int, 'Monitoring', 900), 'BACKUP_DIR': (str, 'General', ''), @@ -130,6 +132,7 @@ _CONFIG_DEFINITIONS = { 'EMAIL_ON_EXTUP': (int, 'Email', 0), 'EMAIL_ON_INTUP': (int, 'Email', 0), 'EMAIL_ON_PMSUPDATE': (int, 'Email', 0), + 'EMAIL_ON_CONCURRENT': (int, 'Email', 0), 'ENABLE_HTTPS': (int, 'General', 0), 'FACEBOOK_ENABLED': (int, 'Facebook', 0), 'FACEBOOK_REDIRECT_URI': (str, 'Facebook', ''), @@ -152,6 +155,7 @@ _CONFIG_DEFINITIONS = { 'FACEBOOK_ON_EXTUP': (int, 'Facebook', 0), 'FACEBOOK_ON_INTUP': (int, 'Facebook', 0), 'FACEBOOK_ON_PMSUPDATE': (int, 'Facebook', 0), + 'FACEBOOK_ON_CONCURRENT': (int, 'Facebook', 0), 'FIRST_RUN_COMPLETE': (int, 'General', 0), 'FREEZE_DB': (int, 'General', 0), 'GEOIP_DB': (str, 'General', ''), @@ -180,6 +184,7 @@ _CONFIG_DEFINITIONS = { 'GROWL_ON_EXTUP': (int, 'Growl', 0), 'GROWL_ON_INTUP': (int, 'Growl', 0), 'GROWL_ON_PMSUPDATE': (int, 'Growl', 0), + 'GROWL_ON_CONCURRENT': (int, 'Growl', 0), 'HOME_SECTIONS': (list, 'General', ['current_activity','watch_stats','library_stats','recently_added']), 'HOME_LIBRARY_CARDS': (list, 'General', ['first_run']), 'HOME_STATS_LENGTH': (int, 'General', 30), @@ -219,6 +224,7 @@ _CONFIG_DEFINITIONS = { 'IFTTT_ON_EXTUP': (int, 'IFTTT', 0), 'IFTTT_ON_INTUP': (int, 'IFTTT', 0), 'IFTTT_ON_PMSUPDATE': (int, 'IFTTT', 0), + 'IFTTT_ON_CONCURRENT': (int, 'IFTTT', 0), 'IMGUR_CLIENT_ID': (str, 'Monitoring', ''), 'JOIN_APIKEY': (str, 'Join', ''), 'JOIN_DEVICEID': (str, 'Join', ''), @@ -236,6 +242,7 @@ _CONFIG_DEFINITIONS = { 'JOIN_ON_EXTUP': (int, 'Join', 0), 'JOIN_ON_INTUP': (int, 'Join', 0), 'JOIN_ON_PMSUPDATE': (int, 'Join', 0), + 'JOIN_ON_CONCURRENT': (int, 'Join', 0), 'JOURNAL_MODE': (str, 'Advanced', 'wal'), 'LAUNCH_BROWSER': (int, 'General', 1), 'LOG_BLACKLIST': (int, 'General', 1), @@ -270,11 +277,14 @@ _CONFIG_DEFINITIONS = { 'NMA_ON_EXTUP': (int, 'NMA', 0), 'NMA_ON_INTUP': (int, 'NMA', 0), 'NMA_ON_PMSUPDATE': (int, 'NMA', 0), + 'NMA_ON_CONCURRENT': (int, 'NMA', 0), 'NOTIFY_CONSECUTIVE': (int, 'Monitoring', 1), 'NOTIFY_UPLOAD_POSTERS': (int, 'Monitoring', 0), 'NOTIFY_RECENTLY_ADDED': (int, 'Monitoring', 0), 'NOTIFY_RECENTLY_ADDED_GRANDPARENT': (int, 'Monitoring', 0), 'NOTIFY_RECENTLY_ADDED_DELAY': (int, 'Monitoring', 60), + 'NOTIFY_CONCURRENT_BY_IP': (int, 'Monitoring', 0), + 'NOTIFY_CONCURRENT_THRESHOLD': (int, 'Monitoring', 2), 'NOTIFY_WATCHED_PERCENT': (int, 'Monitoring', 85), 'NOTIFY_ON_START_SUBJECT_TEXT': (unicode, 'Monitoring', 'PlexPy ({server_name})'), 'NOTIFY_ON_START_BODY_TEXT': (unicode, 'Monitoring', '{user} ({player}) started playing {title}.'), @@ -300,6 +310,8 @@ _CONFIG_DEFINITIONS = { 'NOTIFY_ON_INTUP_BODY_TEXT': (unicode, 'Monitoring', 'The Plex Media Server is back up.'), 'NOTIFY_ON_PMSUPDATE_SUBJECT_TEXT': (unicode, 'Monitoring', 'PlexPy ({server_name})'), 'NOTIFY_ON_PMSUPDATE_BODY_TEXT': (unicode, 'Monitoring', 'An update is available for the Plex Media Server (version {update_version}).'), + 'NOTIFY_ON_CONCURRENT_SUBJECT_TEXT': (unicode, 'Monitoring', 'PlexPy ({server_name})'), + 'NOTIFY_ON_CONCURRENT_BODY_TEXT': (unicode, 'Monitoring', '{user} has {user_streams} concurrent streams.'), 'NOTIFY_SCRIPTS_ARGS_TEXT': (unicode, 'Monitoring', ''), 'OSX_NOTIFY_APP': (str, 'OSX_Notify', '/Applications/PlexPy'), 'OSX_NOTIFY_ENABLED': (int, 'OSX_Notify', 0), @@ -315,6 +327,7 @@ _CONFIG_DEFINITIONS = { 'OSX_NOTIFY_ON_EXTUP': (int, 'OSX_Notify', 0), 'OSX_NOTIFY_ON_INTUP': (int, 'OSX_Notify', 0), 'OSX_NOTIFY_ON_PMSUPDATE': (int, 'OSX_Notify', 0), + 'OSX_NOTIFY_ON_CONCURRENT': (int, 'OSX_Notify', 0), 'PLEX_CLIENT_HOST': (str, 'Plex', ''), 'PLEX_ENABLED': (int, 'Plex', 0), 'PLEX_PASSWORD': (str, 'Plex', ''), @@ -331,6 +344,7 @@ _CONFIG_DEFINITIONS = { 'PLEX_ON_EXTUP': (int, 'Plex', 0), 'PLEX_ON_INTUP': (int, 'Plex', 0), 'PLEX_ON_PMSUPDATE': (int, 'Plex', 0), + 'PLEX_ON_CONCURRENT': (int, 'Plex', 0), 'PROWL_ENABLED': (int, 'Prowl', 0), 'PROWL_KEYS': (str, 'Prowl', ''), 'PROWL_PRIORITY': (int, 'Prowl', 0), @@ -346,6 +360,7 @@ _CONFIG_DEFINITIONS = { 'PROWL_ON_EXTUP': (int, 'Prowl', 0), 'PROWL_ON_INTUP': (int, 'Prowl', 0), 'PROWL_ON_PMSUPDATE': (int, 'Prowl', 0), + 'PROWL_ON_CONCURRENT': (int, 'Prowl', 0), 'PUSHALOT_APIKEY': (str, 'Pushalot', ''), 'PUSHALOT_ENABLED': (int, 'Pushalot', 0), 'PUSHALOT_ON_PLAY': (int, 'Pushalot', 0), @@ -360,6 +375,7 @@ _CONFIG_DEFINITIONS = { 'PUSHALOT_ON_EXTUP': (int, 'Pushalot', 0), 'PUSHALOT_ON_INTUP': (int, 'Pushalot', 0), 'PUSHALOT_ON_PMSUPDATE': (int, 'Pushalot', 0), + 'PUSHALOT_ON_CONCURRENT': (int, 'Pushalot', 0), 'PUSHBULLET_APIKEY': (str, 'PushBullet', ''), 'PUSHBULLET_DEVICEID': (str, 'PushBullet', ''), 'PUSHBULLET_CHANNEL_TAG': (str, 'PushBullet', ''), @@ -376,6 +392,7 @@ _CONFIG_DEFINITIONS = { 'PUSHBULLET_ON_EXTUP': (int, 'PushBullet', 0), 'PUSHBULLET_ON_INTUP': (int, 'PushBullet', 0), 'PUSHBULLET_ON_PMSUPDATE': (int, 'PushBullet', 0), + 'PUSHBULLET_ON_CONCURRENT': (int, 'PushBullet', 0), 'PUSHOVER_APITOKEN': (str, 'Pushover', ''), 'PUSHOVER_ENABLED': (int, 'Pushover', 0), 'PUSHOVER_HTML_SUPPORT': (int, 'Pushover', 1), @@ -394,6 +411,7 @@ _CONFIG_DEFINITIONS = { 'PUSHOVER_ON_EXTUP': (int, 'Pushover', 0), 'PUSHOVER_ON_INTUP': (int, 'Pushover', 0), 'PUSHOVER_ON_PMSUPDATE': (int, 'Pushover', 0), + 'PUSHOVER_ON_CONCURRENT': (int, 'Pushover', 0), 'REFRESH_LIBRARIES_INTERVAL': (int, 'Monitoring', 12), 'REFRESH_LIBRARIES_ON_STARTUP': (int, 'Monitoring', 1), 'REFRESH_USERS_INTERVAL': (int, 'Monitoring', 12), @@ -419,6 +437,7 @@ _CONFIG_DEFINITIONS = { 'SLACK_ON_EXTUP': (int, 'Slack', 0), 'SLACK_ON_INTUP': (int, 'Slack', 0), 'SLACK_ON_PMSUPDATE': (int, 'Slack', 0), + 'SLACK_ON_CONCURRENT': (int, 'Slack', 0), 'SCRIPTS_ENABLED': (int, 'Scripts', 0), 'SCRIPTS_FOLDER': (unicode, 'Scripts', ''), 'SCRIPTS_ON_PLAY': (int, 'Scripts', 0), @@ -433,6 +452,7 @@ _CONFIG_DEFINITIONS = { 'SCRIPTS_ON_INTDOWN': (int, 'Scripts', 0), 'SCRIPTS_ON_INTUP': (int, 'Scripts', 0), 'SCRIPTS_ON_PMSUPDATE': (int, 'Scripts', 0), + 'SCRIPTS_ON_CONCURRENT': (int, 'Scripts', 0), 'SCRIPTS_ON_PLAY_SCRIPT': (unicode, 'Scripts', ''), 'SCRIPTS_ON_STOP_SCRIPT': (unicode, 'Scripts', ''), 'SCRIPTS_ON_PAUSE_SCRIPT': (unicode, 'Scripts', ''), @@ -445,6 +465,7 @@ _CONFIG_DEFINITIONS = { 'SCRIPTS_ON_INTDOWN_SCRIPT': (unicode, 'Scripts', ''), 'SCRIPTS_ON_INTUP_SCRIPT': (unicode, 'Scripts', ''), 'SCRIPTS_ON_PMSUPDATE_SCRIPT': (unicode, 'Scripts', ''), + 'SCRIPTS_ON_CONCURRENT_SCRIPT': (unicode, 'Scripts', ''), 'TELEGRAM_BOT_TOKEN': (str, 'Telegram', ''), 'TELEGRAM_ENABLED': (int, 'Telegram', 0), 'TELEGRAM_CHAT_ID': (str, 'Telegram', ''), @@ -463,6 +484,7 @@ _CONFIG_DEFINITIONS = { 'TELEGRAM_ON_EXTUP': (int, 'Telegram', 0), 'TELEGRAM_ON_INTUP': (int, 'Telegram', 0), 'TELEGRAM_ON_PMSUPDATE': (int, 'Telegram', 0), + 'TELEGRAM_ON_CONCURRENT': (int, 'Telegram', 0), 'TV_LOGGING_ENABLE': (int, 'Monitoring', 1), 'TV_NOTIFY_ENABLE': (int, 'Monitoring', 0), 'TV_NOTIFY_ON_START': (int, 'Monitoring', 1), @@ -487,6 +509,7 @@ _CONFIG_DEFINITIONS = { 'TWITTER_ON_EXTUP': (int, 'Twitter', 0), 'TWITTER_ON_INTUP': (int, 'Twitter', 0), 'TWITTER_ON_PMSUPDATE': (int, 'Twitter', 0), + 'TWITTER_ON_CONCURRENT': (int, 'Twitter', 0), 'UPDATE_DB_INTERVAL': (int, 'General', 24), 'UPDATE_SECTION_IDS': (int, 'General', 1), 'UPDATE_LABELS': (int, 'General', 1), @@ -507,7 +530,8 @@ _CONFIG_DEFINITIONS = { 'XBMC_ON_INTDOWN': (int, 'XBMC', 0), 'XBMC_ON_EXTUP': (int, 'XBMC', 0), 'XBMC_ON_INTUP': (int, 'XBMC', 0), - 'XBMC_ON_PMSUPDATE': (int, 'XBMC', 0) + 'XBMC_ON_PMSUPDATE': (int, 'XBMC', 0), + 'XBMC_ON_CONCURRENT': (int, 'XBMC', 0) } _BLACKLIST_KEYS = ['_APITOKEN', '_TOKEN', '_KEY', '_SECRET', '_PASSWORD', '_APIKEY', '_ID'] diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py index c87b394f..be9707ee 100644 --- a/plexpy/notification_handler.py +++ b/plexpy/notification_handler.py @@ -182,6 +182,26 @@ def notify(stream_data=None, notify_action=None): notify_strings=notify_strings, metadata=metadata) + elif agent['on_concurrent'] and notify_action == 'concurrent': + # Build and send notification + notify_strings, metadata = build_notify_text(session=stream_data, + notify_action=notify_action, + agent_id=agent['id']) + + notifiers.send_notification(agent_id=agent['id'], + subject=notify_strings[0], + body=notify_strings[1], + script_args=notify_strings[2], + notify_action=notify_action, + metadata=metadata) + + # Set the notification state in the db + set_notify_state(session=stream_data, + notify_action=notify_action, + agent_info=agent, + notify_strings=notify_strings, + metadata=metadata) + elif (stream_data['media_type'] == 'track' and plexpy.CONFIG.MUSIC_NOTIFY_ENABLE): for agent in notifiers.available_notification_agents(): @@ -485,7 +505,10 @@ def build_notify_text(session=None, timeline=None, notify_action=None, agent_id= pms_connect = pmsconnect.PmsConnect() metadata_list = pms_connect.get_metadata_details(rating_key=rating_key) - stream_count = pms_connect.get_current_activity().get('stream_count', '') + current_activity = pms_connect.get_current_activity() + sessions = current_activity.get('sessions', []) + stream_count = current_activity.get('stream_count', '') + user_stream_count = sum(1 for d in sessions if d['user_id'] == session['user_id']) if metadata_list: metadata = metadata_list['metadata'] @@ -525,6 +548,8 @@ def build_notify_text(session=None, timeline=None, notify_action=None, agent_id= on_watched_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT), agent_id) on_created_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT), agent_id) on_created_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT), agent_id) + on_concurrent_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_CONCURRENT_SUBJECT_TEXT), agent_id) + on_concurrent_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_CONCURRENT_BODY_TEXT), agent_id) script_args_text = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT), agent_id) else: on_start_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT, agent_id) @@ -541,6 +566,8 @@ def build_notify_text(session=None, timeline=None, notify_action=None, agent_id= on_watched_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT, agent_id) on_created_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT, agent_id) on_created_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT, agent_id) + on_concurrent_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_CONCURRENT_SUBJECT_TEXT, agent_id) + on_concurrent_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_CONCURRENT_BODY_TEXT, agent_id) script_args_text = strip_tag(plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT, agent_id) # Create a title @@ -676,6 +703,7 @@ def build_notify_text(session=None, timeline=None, notify_action=None, agent_id= 'timestamp': arrow.now().format(time_format), # Stream parameters 'streams': stream_count, + 'user_streams': user_stream_count, 'user': session.get('friendly_name',''), 'username': session.get('user',''), 'platform': session.get('platform',''), @@ -940,6 +968,29 @@ def build_notify_text(session=None, timeline=None, notify_action=None, agent_id= except: logger.error(u"PlexPy NotificationHandler :: Unable to parse custom notification body. Using fallback.") + return [subject_text, body_text, script_args], metadata + else: + return [subject_text, body_text, script_args], metadata + elif notify_action == 'concurrent': + # Default body text + body_text = '%s has %s concurrent streams.' % (session['friendly_name'], + user_stream_count) + + if on_concurrent_subject and on_concurrent_body: + try: + subject_text = unicode(on_concurrent_subject).format(**available_params) + except LookupError as e: + logger.error(u"PlexPy NotificationHandler :: Unable to parse field %s in notification subject. Using fallback." % e) + except: + logger.error(u"PlexPy NotificationHandler :: Unable to parse custom notification subject. Using fallback.") + + try: + body_text = unicode(on_concurrent_body).format(**available_params) + except LookupError as e: + logger.error(u"PlexPy NotificationHandler :: Unable to parse field %s in notification body. Using fallback." % e) + except: + logger.error(u"PlexPy NotificationHandler :: Unable to parse custom notification body. Using fallback.") + return [subject_text, body_text, script_args], metadata else: return [subject_text, body_text, script_args], metadata diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py index fffdbcec..25404ea5 100644 --- a/plexpy/notifiers.py +++ b/plexpy/notifiers.py @@ -82,7 +82,8 @@ def available_notification_agents(): 'on_intdown': plexpy.CONFIG.GROWL_ON_INTDOWN, 'on_extup': plexpy.CONFIG.GROWL_ON_EXTUP, 'on_intup': plexpy.CONFIG.GROWL_ON_INTUP, - 'on_pmsupdate': plexpy.CONFIG.GROWL_ON_PMSUPDATE + 'on_pmsupdate': plexpy.CONFIG.GROWL_ON_PMSUPDATE, + 'on_concurrent': plexpy.CONFIG.GROWL_ON_CONCURRENT }, {'name': 'Prowl', 'id': AGENT_IDS['Prowl'], @@ -100,7 +101,8 @@ def available_notification_agents(): 'on_intdown': plexpy.CONFIG.PROWL_ON_INTDOWN, 'on_extup': plexpy.CONFIG.PROWL_ON_EXTUP, 'on_intup': plexpy.CONFIG.PROWL_ON_INTUP, - 'on_pmsupdate': plexpy.CONFIG.PROWL_ON_PMSUPDATE + 'on_pmsupdate': plexpy.CONFIG.PROWL_ON_PMSUPDATE, + 'on_concurrent': plexpy.CONFIG.PROWL_ON_CONCURRENT }, {'name': 'XBMC', 'id': AGENT_IDS['XBMC'], @@ -118,7 +120,8 @@ def available_notification_agents(): 'on_intdown': plexpy.CONFIG.XBMC_ON_INTDOWN, 'on_extup': plexpy.CONFIG.XBMC_ON_EXTUP, 'on_intup': plexpy.CONFIG.XBMC_ON_INTUP, - 'on_pmsupdate': plexpy.CONFIG.XBMC_ON_PMSUPDATE + 'on_pmsupdate': plexpy.CONFIG.XBMC_ON_PMSUPDATE, + 'on_concurrent': plexpy.CONFIG.XBMC_ON_CONCURRENT }, {'name': 'Plex Home Theater', 'id': AGENT_IDS['Plex'], @@ -136,7 +139,8 @@ def available_notification_agents(): 'on_intdown': plexpy.CONFIG.PLEX_ON_INTDOWN, 'on_extup': plexpy.CONFIG.PLEX_ON_EXTUP, 'on_intup': plexpy.CONFIG.PLEX_ON_INTUP, - 'on_pmsupdate': plexpy.CONFIG.PLEX_ON_PMSUPDATE + 'on_pmsupdate': plexpy.CONFIG.PLEX_ON_PMSUPDATE, + 'on_concurrent': plexpy.CONFIG.PLEX_ON_CONCURRENT }, {'name': 'NotifyMyAndroid', 'id': AGENT_IDS['NMA'], @@ -154,7 +158,8 @@ def available_notification_agents(): 'on_intdown': plexpy.CONFIG.NMA_ON_INTDOWN, 'on_extup': plexpy.CONFIG.NMA_ON_EXTUP, 'on_intup': plexpy.CONFIG.NMA_ON_INTUP, - 'on_pmsupdate': plexpy.CONFIG.NMA_ON_PMSUPDATE + 'on_pmsupdate': plexpy.CONFIG.NMA_ON_PMSUPDATE, + 'on_concurrent': plexpy.CONFIG.NMA_ON_CONCURRENT }, {'name': 'Pushalot', 'id': AGENT_IDS['Pushalot'], @@ -172,7 +177,8 @@ def available_notification_agents(): 'on_intdown': plexpy.CONFIG.PUSHALOT_ON_INTDOWN, 'on_extup': plexpy.CONFIG.PUSHALOT_ON_EXTUP, 'on_intup': plexpy.CONFIG.PUSHALOT_ON_INTUP, - 'on_pmsupdate': plexpy.CONFIG.PUSHALOT_ON_PMSUPDATE + 'on_pmsupdate': plexpy.CONFIG.PUSHALOT_ON_PMSUPDATE, + 'on_concurrent': plexpy.CONFIG.PUSHALOT_ON_CONCURRENT }, {'name': 'Pushbullet', 'id': AGENT_IDS['Pushbullet'], @@ -190,7 +196,8 @@ def available_notification_agents(): 'on_intdown': plexpy.CONFIG.PUSHBULLET_ON_INTDOWN, 'on_extup': plexpy.CONFIG.PUSHBULLET_ON_EXTUP, 'on_intup': plexpy.CONFIG.PUSHBULLET_ON_INTUP, - 'on_pmsupdate': plexpy.CONFIG.PUSHBULLET_ON_PMSUPDATE + 'on_pmsupdate': plexpy.CONFIG.PUSHBULLET_ON_PMSUPDATE, + 'on_concurrent': plexpy.CONFIG.PUSHBULLET_ON_CONCURRENT }, {'name': 'Pushover', 'id': AGENT_IDS['Pushover'], @@ -208,7 +215,8 @@ def available_notification_agents(): 'on_intdown': plexpy.CONFIG.PUSHOVER_ON_INTDOWN, 'on_extup': plexpy.CONFIG.PUSHOVER_ON_EXTUP, 'on_intup': plexpy.CONFIG.PUSHOVER_ON_INTUP, - 'on_pmsupdate': plexpy.CONFIG.PUSHOVER_ON_PMSUPDATE + 'on_pmsupdate': plexpy.CONFIG.PUSHOVER_ON_PMSUPDATE, + 'on_concurrent': plexpy.CONFIG.PUSHOVER_ON_CONCURRENT }, {'name': 'Boxcar2', 'id': AGENT_IDS['Boxcar2'], @@ -226,7 +234,8 @@ def available_notification_agents(): 'on_intdown': plexpy.CONFIG.BOXCAR_ON_INTDOWN, 'on_extup': plexpy.CONFIG.BOXCAR_ON_EXTUP, 'on_intup': plexpy.CONFIG.BOXCAR_ON_INTUP, - 'on_pmsupdate': plexpy.CONFIG.BOXCAR_ON_PMSUPDATE + 'on_pmsupdate': plexpy.CONFIG.BOXCAR_ON_PMSUPDATE, + 'on_concurrent': plexpy.CONFIG.BOXCAR_ON_CONCURRENT }, {'name': 'E-mail', 'id': AGENT_IDS['Email'], @@ -244,7 +253,8 @@ def available_notification_agents(): 'on_intdown': plexpy.CONFIG.EMAIL_ON_INTDOWN, 'on_extup': plexpy.CONFIG.EMAIL_ON_EXTUP, 'on_intup': plexpy.CONFIG.EMAIL_ON_INTUP, - 'on_pmsupdate': plexpy.CONFIG.EMAIL_ON_PMSUPDATE + 'on_pmsupdate': plexpy.CONFIG.EMAIL_ON_PMSUPDATE, + 'on_concurrent': plexpy.CONFIG.EMAIL_ON_CONCURRENT }, {'name': 'Twitter', 'id': AGENT_IDS['Twitter'], @@ -262,7 +272,8 @@ def available_notification_agents(): 'on_intdown': plexpy.CONFIG.TWITTER_ON_INTDOWN, 'on_extup': plexpy.CONFIG.TWITTER_ON_EXTUP, 'on_intup': plexpy.CONFIG.TWITTER_ON_INTUP, - 'on_pmsupdate': plexpy.CONFIG.TWITTER_ON_PMSUPDATE + 'on_pmsupdate': plexpy.CONFIG.TWITTER_ON_PMSUPDATE, + 'on_concurrent': plexpy.CONFIG.TWITTER_ON_CONCURRENT }, {'name': 'IFTTT', 'id': AGENT_IDS['IFTTT'], @@ -280,7 +291,8 @@ def available_notification_agents(): 'on_intdown': plexpy.CONFIG.IFTTT_ON_INTDOWN, 'on_extup': plexpy.CONFIG.IFTTT_ON_EXTUP, 'on_intup': plexpy.CONFIG.IFTTT_ON_INTUP, - 'on_pmsupdate': plexpy.CONFIG.IFTTT_ON_PMSUPDATE + 'on_pmsupdate': plexpy.CONFIG.IFTTT_ON_PMSUPDATE, + 'on_concurrent': plexpy.CONFIG.IFTTT_ON_CONCURRENT }, {'name': 'Telegram', 'id': AGENT_IDS['Telegram'], @@ -298,7 +310,8 @@ def available_notification_agents(): 'on_intdown': plexpy.CONFIG.TELEGRAM_ON_INTDOWN, 'on_extup': plexpy.CONFIG.TELEGRAM_ON_EXTUP, 'on_intup': plexpy.CONFIG.TELEGRAM_ON_INTUP, - 'on_pmsupdate': plexpy.CONFIG.TELEGRAM_ON_PMSUPDATE + 'on_pmsupdate': plexpy.CONFIG.TELEGRAM_ON_PMSUPDATE, + 'on_concurrent': plexpy.CONFIG.TELEGRAM_ON_CONCURRENT }, {'name': 'Slack', 'id': AGENT_IDS['Slack'], @@ -316,7 +329,8 @@ def available_notification_agents(): 'on_intdown': plexpy.CONFIG.SLACK_ON_INTDOWN, 'on_extup': plexpy.CONFIG.SLACK_ON_EXTUP, 'on_intup': plexpy.CONFIG.SLACK_ON_INTUP, - 'on_pmsupdate': plexpy.CONFIG.SLACK_ON_PMSUPDATE + 'on_pmsupdate': plexpy.CONFIG.SLACK_ON_PMSUPDATE, + 'on_concurrent': plexpy.CONFIG.SLACK_ON_CONCURRENT }, {'name': 'Scripts', 'id': AGENT_IDS['Scripts'], @@ -334,7 +348,8 @@ def available_notification_agents(): 'on_extup': plexpy.CONFIG.SCRIPTS_ON_EXTUP, 'on_intdown': plexpy.CONFIG.SCRIPTS_ON_INTDOWN, 'on_intup': plexpy.CONFIG.SCRIPTS_ON_INTUP, - 'on_pmsupdate': plexpy.CONFIG.SCRIPTS_ON_PMSUPDATE + 'on_pmsupdate': plexpy.CONFIG.SCRIPTS_ON_PMSUPDATE, + 'on_concurrent': plexpy.CONFIG.SCRIPTS_ON_CONCURRENT }, {'name': 'Facebook', 'id': AGENT_IDS['Facebook'], @@ -352,7 +367,8 @@ def available_notification_agents(): 'on_intdown': plexpy.CONFIG.FACEBOOK_ON_INTDOWN, 'on_extup': plexpy.CONFIG.FACEBOOK_ON_EXTUP, 'on_intup': plexpy.CONFIG.FACEBOOK_ON_INTUP, - 'on_pmsupdate': plexpy.CONFIG.FACEBOOK_ON_PMSUPDATE + 'on_pmsupdate': plexpy.CONFIG.FACEBOOK_ON_PMSUPDATE, + 'on_concurrent': plexpy.CONFIG.FACEBOOK_ON_CONCURRENT }, {'name': 'Browser', 'id': AGENT_IDS['Browser'], @@ -370,7 +386,8 @@ def available_notification_agents(): 'on_intdown': plexpy.CONFIG.BROWSER_ON_INTDOWN, 'on_extup': plexpy.CONFIG.BROWSER_ON_EXTUP, 'on_intup': plexpy.CONFIG.BROWSER_ON_INTUP, - 'on_pmsupdate': plexpy.CONFIG.BROWSER_ON_PMSUPDATE + 'on_pmsupdate': plexpy.CONFIG.BROWSER_ON_PMSUPDATE, + 'on_concurrent': plexpy.CONFIG.BROWSER_ON_CONCURRENT }, {'name': 'Join', 'id': AGENT_IDS['Join'], @@ -388,7 +405,8 @@ def available_notification_agents(): 'on_intdown': plexpy.CONFIG.JOIN_ON_INTDOWN, 'on_extup': plexpy.CONFIG.JOIN_ON_EXTUP, 'on_intup': plexpy.CONFIG.JOIN_ON_INTUP, - 'on_pmsupdate': plexpy.CONFIG.JOIN_ON_PMSUPDATE + 'on_pmsupdate': plexpy.CONFIG.JOIN_ON_PMSUPDATE, + 'on_concurrent': plexpy.CONFIG.JOIN_ON_CONCURRENT } ] diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 538d361c..2f7870bd 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -2569,6 +2569,8 @@ class WebInterface(object): "notify_recently_added": checked(plexpy.CONFIG.NOTIFY_RECENTLY_ADDED), "notify_recently_added_grandparent": checked(plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_GRANDPARENT), "notify_recently_added_delay": plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_DELAY, + "notify_concurrent_by_ip": plexpy.CONFIG.NOTIFY_CONCURRENT_BY_IP, + "notify_concurrent_threshold": plexpy.CONFIG.NOTIFY_CONCURRENT_THRESHOLD, "notify_watched_percent": plexpy.CONFIG.NOTIFY_WATCHED_PERCENT, "notify_on_start_subject_text": plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT, "notify_on_start_body_text": plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT, @@ -2594,6 +2596,8 @@ class WebInterface(object): "notify_on_intup_body_text": plexpy.CONFIG.NOTIFY_ON_INTUP_BODY_TEXT, "notify_on_pmsupdate_subject_text": plexpy.CONFIG.NOTIFY_ON_PMSUPDATE_SUBJECT_TEXT, "notify_on_pmsupdate_body_text": plexpy.CONFIG.NOTIFY_ON_PMSUPDATE_BODY_TEXT, + "notify_on_concurrent_subject_text": plexpy.CONFIG.NOTIFY_ON_CONCURRENT_SUBJECT_TEXT, + "notify_on_concurrent_body_text": plexpy.CONFIG.NOTIFY_ON_CONCURRENT_BODY_TEXT, "notify_scripts_args_text": plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT, "home_sections": json.dumps(plexpy.CONFIG.HOME_SECTIONS), "home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH, From 9d9103a83bf4d440bd287969ffe3a7cab4255972 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Tue, 5 Jul 2016 20:48:11 -0700 Subject: [PATCH 30/55] Add missing user concurrent stream script --- .../default/notification_triggers_modal.html | 2 +- plexpy/notifiers.py | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/data/interfaces/default/notification_triggers_modal.html b/data/interfaces/default/notification_triggers_modal.html index fc0a0a57..aefb5595 100644 --- a/data/interfaces/default/notification_triggers_modal.html +++ b/data/interfaces/default/notification_triggers_modal.html @@ -60,7 +60,7 @@

      Trigger notification when a user has concurrent streams.

      diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py index 25404ea5..1a00a4f9 100644 --- a/plexpy/notifiers.py +++ b/plexpy/notifiers.py @@ -2108,6 +2108,9 @@ class Scripts(object): elif notify_action == 'pmsupdate': script = plexpy.CONFIG.SCRIPTS_ON_PMSUPDATE_SCRIPT + elif notify_action == 'concurrent': + script = plexpy.CONFIG.SCRIPTS_ON_CONCURRENT_SCRIPT + else: # For manual scripts script = kwargs.get('script', '') @@ -2284,6 +2287,13 @@ class Scripts(object): 'description': 'Choose the script for Plex update available.', 'input_type': 'select', 'select_options': self.list_scripts() + }, + {'label': 'User Concurrent Streams', + 'value': plexpy.CONFIG.SCRIPTS_ON_CONCURRENT_SCRIPT, + 'name': 'scripts_on_concurrent_script', + 'description': 'Choose the script for user concurrent streams.', + 'input_type': 'select', + 'select_options': self.list_scripts() } ] From 59d63f61d9d473f573572dcc9d313323c50a4856 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Tue, 5 Jul 2016 20:55:11 -0700 Subject: [PATCH 31/55] Add missing concurrent notification to OSX notify --- plexpy/notifiers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py index 1a00a4f9..65b46a1a 100644 --- a/plexpy/notifiers.py +++ b/plexpy/notifiers.py @@ -429,7 +429,8 @@ def available_notification_agents(): 'on_intdown': plexpy.CONFIG.OSX_NOTIFY_ON_INTDOWN, 'on_extup': plexpy.CONFIG.OSX_NOTIFY_ON_EXTUP, 'on_intup': plexpy.CONFIG.OSX_NOTIFY_ON_INTUP, - 'on_pmsupdate': plexpy.CONFIG.OSX_NOTIFY_ON_PMSUPDATE + 'on_pmsupdate': plexpy.CONFIG.OSX_NOTIFY_ON_PMSUPDATE, + 'on_concurrent': plexpy.CONFIG.OSX_NOTIFY_ON_CONCURRENT }) return agents From f4273cafb67f1d1324a19e73204d8e199d229fab Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Tue, 5 Jul 2016 20:59:07 -0700 Subject: [PATCH 32/55] Add missing concurrent notifications for tracks --- plexpy/notification_handler.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py index be9707ee..cc627eb8 100644 --- a/plexpy/notification_handler.py +++ b/plexpy/notification_handler.py @@ -305,6 +305,26 @@ def notify(stream_data=None, notify_action=None): notify_strings=notify_strings, metadata=metadata) + elif agent['on_concurrent'] and notify_action == 'concurrent': + # Build and send notification + notify_strings, metadata = build_notify_text(session=stream_data, + notify_action=notify_action, + agent_id=agent['id']) + + notifiers.send_notification(agent_id=agent['id'], + subject=notify_strings[0], + body=notify_strings[1], + script_args=notify_strings[2], + notify_action=notify_action, + metadata=metadata) + + # Set the notification state in the db + set_notify_state(session=stream_data, + notify_action=notify_action, + agent_info=agent, + notify_strings=notify_strings, + metadata=metadata) + elif stream_data['media_type'] == 'clip': pass else: From 3ccc82f343ce623d5afa4d3e3fb189c2470473c4 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Tue, 5 Jul 2016 21:30:47 -0700 Subject: [PATCH 33/55] Add notification for user streaming from a new device --- .../default/notification_triggers_modal.html | 7 ++ data/interfaces/default/settings.html | 17 +++++ plexpy/activity_handler.py | 13 +++- plexpy/activity_processor.py | 11 +++ plexpy/config.py | 24 +++++- plexpy/datafactory.py | 17 ++++- plexpy/notification_handler.py | 75 ++++++++++++++++++- plexpy/notifiers.py | 67 ++++++++++++----- plexpy/webserve.py | 2 + 9 files changed, 207 insertions(+), 26 deletions(-) diff --git a/data/interfaces/default/notification_triggers_modal.html b/data/interfaces/default/notification_triggers_modal.html index aefb5595..d14c1e7b 100644 --- a/data/interfaces/default/notification_triggers_modal.html +++ b/data/interfaces/default/notification_triggers_modal.html @@ -64,6 +64,13 @@

      Trigger notification when a user has concurrent streams.

    +
    + +

    Trigger notification when a user streams from a new device.

    +