diff --git a/data/interfaces/default/config.html b/data/interfaces/default/config.html index 5c6a016c..65776a28 100644 --- a/data/interfaces/default/config.html +++ b/data/interfaces/default/config.html @@ -225,10 +225,48 @@
+
+

Monitoring Settings

+
-
-

Global Notifications

+
+ + +

The interval (in seconds) PlexPy will ping your Plex Server. Min 30 seconds, Recommended 60 seconds. 0 to disable.

+

Disabling monitoring will disable ALL notifications.

+
+
+

User List

+
+
+
+ + +

The interval (in hours) PlexPy will request an updated friends list from Plex.tv. 0 to disable.

+
+
+ Refresh user list on startup +

Refresh the user list when PlexPy starts.

+
+
+
+
+
+

IP Logging

+
+
+
+ Enable IP Logging +

Enable this to attempt to log the IP address of the user. This currently does nothing useful.

+
+
+
+
+
+

Global Notifications

+
+
@@ -238,9 +276,14 @@
- Notify on playback start + Notify on playback start +
+
+ Notify on playback stop +
+
+ Notify on playback pause
-
@@ -253,9 +296,14 @@
- Notify on playback start + Notify on playback start +
+
+ Notify on playback stop +
+
+ Notify on playback pause
-
@@ -268,7 +316,13 @@
- Notify on playback start + Notify on playback start +
+
+ Notify on playback stop +
+
+ Notify on playback pause
diff --git a/plexpy/__init__.py b/plexpy/__init__.py index 3d9f5a51..530aca1a 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -163,7 +163,7 @@ def initialize(config_file): LATEST_VERSION = CURRENT_VERSION # Refresh the users list on startup - if CONFIG.PMS_TOKEN: + if CONFIG.PMS_TOKEN and CONFIG.REFRESH_USERS_ON_STARTUP: plextv.refresh_users() # Store the original umask @@ -261,12 +261,26 @@ def initialize_scheduler(): schedule_job(versioncheck.checkGithub, 'Check GitHub for updates', hours=0, minutes=minutes) # Start checking for new sessions every minute - if CONFIG.PMS_IP: - schedule_job(monitor.check_active_sessions, 'Check for active sessions', hours=0, minutes=0, seconds=60) + if CONFIG.MONITORING_INTERVAL: + # Our interval should never be less than 30 seconds + if CONFIG.MONITORING_INTERVAL > 30: + seconds = CONFIG.MONITORING_INTERVAL + else: + seconds = 30 + else: + seconds = 0 + + if CONFIG.PMS_IP: + schedule_job(monitor.check_active_sessions, 'Check for active sessions', hours=0, minutes=0, seconds=seconds) + + # Refresh the users list + if CONFIG.REFRESH_USERS_INTERVAL: + hours = CONFIG.REFRESH_USERS_INTERVAL + else: + hours = 0 - # Refresh the users list every 12 hours (we will make this configurable later) if CONFIG.PMS_TOKEN: - schedule_job(plextv.refresh_users, 'Refresh users list', hours=12, minutes=0, seconds=0) + schedule_job(plextv.refresh_users, 'Refresh users list', hours=hours, minutes=0, seconds=0) # Start scheduler if start_jobs and len(SCHED.get_jobs()): @@ -356,7 +370,8 @@ def dbcheck(): c_db.execute( 'CREATE TABLE IF NOT EXISTS sessions (id INTEGER PRIMARY KEY AUTOINCREMENT, ' 'session_key INTEGER, rating_key INTEGER, media_type TEXT, started INTEGER, ' - 'paused_counter INTEGER, state TEXT, user TEXT, machine_id TEXT)' + 'paused_counter INTEGER, state TEXT, user_id INTEGER, user TEXT, friendly_name TEXT, ' + 'machine_id TEXT, player TEXT, title TEXT, parent_title TEXT, grandparent_title TEXT)' ) # Upgrade sessions table from earlier versions @@ -380,6 +395,30 @@ def dbcheck(): 'ALTER TABLE sessions ADD COLUMN machine_id TEXT' ) + # Upgrade sessions table from earlier versions + try: + c_db.execute('SELECT title from sessions') + except sqlite3.OperationalError: + logger.debug(u"Altering database. Updating database table sessions.") + c_db.execute( + 'ALTER TABLE sessions ADD COLUMN title TEXT' + ) + c_db.execute( + 'ALTER TABLE sessions ADD COLUMN parent_title TEXT' + ) + c_db.execute( + 'ALTER TABLE sessions ADD COLUMN grandparent_title TEXT' + ) + c_db.execute( + 'ALTER TABLE sessions ADD COLUMN friendly_name TEXT' + ) + c_db.execute( + 'ALTER TABLE sessions ADD COLUMN player TEXT' + ) + c_db.execute( + 'ALTER TABLE sessions ADD COLUMN user_id INTEGER' + ) + conn_db.commit() c_db.close() diff --git a/plexpy/config.py b/plexpy/config.py index b04834ba..7120a2e8 100644 --- a/plexpy/config.py +++ b/plexpy/config.py @@ -69,13 +69,19 @@ _CONFIG_DEFINITIONS = { 'HTTP_ROOT': (str, 'General', '/'), 'HTTP_USERNAME': (str, 'General', ''), 'INTERFACE': (str, 'General', 'default'), + 'IP_LOGGING_ENABLE': (int, 'General', 0), 'JOURNAL_MODE': (str, 'Advanced', 'wal'), 'LAUNCH_BROWSER': (int, 'General', 1), 'LOG_DIR': (str, 'General', ''), 'MOVIE_NOTIFY_ENABLE': (int, 'Monitoring', 0), 'MOVIE_NOTIFY_ON_START': (int, 'Monitoring', 1), + 'MOVIE_NOTIFY_ON_STOP': (int, 'Monitoring', 0), + 'MOVIE_NOTIFY_ON_PAUSE': (int, 'Monitoring', 0), 'MUSIC_NOTIFY_ENABLE': (int, 'Monitoring', 0), 'MUSIC_NOTIFY_ON_START': (int, 'Monitoring', 1), + 'MUSIC_NOTIFY_ON_STOP': (int, 'Monitoring', 0), + 'MUSIC_NOTIFY_ON_PAUSE': (int, 'Monitoring', 0), + 'MONITORING_INTERVAL': (int, 'Monitoring', 60), 'NMA_APIKEY': (str, 'NMA', ''), 'NMA_ENABLED': (int, 'NMA', 0), 'NMA_PRIORITY': (int, 'NMA', 0), @@ -97,8 +103,12 @@ _CONFIG_DEFINITIONS = { 'PUSHOVER_ENABLED': (int, 'Pushover', 0), 'PUSHOVER_KEYS': (str, 'Pushover', ''), 'PUSHOVER_PRIORITY': (int, 'Pushover', 0), + 'REFRESH_USERS_INTERVAL': (int, 'Monitoring', 12), + 'REFRESH_USERS_ON_STARTUP': (int, 'Monitoring', 1), 'TV_NOTIFY_ENABLE': (int, 'Monitoring', 0), 'TV_NOTIFY_ON_START': (int, 'Monitoring', 1), + 'TV_NOTIFY_ON_STOP': (int, 'Monitoring', 0), + 'TV_NOTIFY_ON_PAUSE': (int, 'Monitoring', 0), 'TWITTER_ENABLED': (int, 'Twitter', 0), 'TWITTER_PASSWORD': (str, 'Twitter', ''), 'TWITTER_PREFIX': (str, 'Twitter', 'Headphones'), diff --git a/plexpy/monitor.py b/plexpy/monitor.py index 1c0060f0..261f93a1 100644 --- a/plexpy/monitor.py +++ b/plexpy/monitor.py @@ -41,24 +41,44 @@ def check_active_sessions(): media_container = session_list['sessions'] # Check our temp table for what we must do with the new streams - db_streams = monitor_db.select('SELECT session_key, rating_key, media_type FROM sessions') + db_streams = monitor_db.select('SELECT session_key, rating_key, media_type, title, parent_title, ' + 'grandparent_title, user, friendly_name, player, state ' + 'FROM sessions') for result in db_streams: - if any(d['session_key'] == str(result[0]) for d in media_container): + # Build a result dictionary for easier referencing + stream = {'session_key': result[0], + 'rating_key': result[1], + 'media_type': result[2], + 'title': result[3], + 'parent_title': result[4], + 'grandparent_title': result[5], + 'user': result[6], + 'friendly_name': result[7], + 'player': result[8], + 'state': result[9] + } + + if any(d['session_key'] == str(stream['session_key']) for d in media_container): # The user's session is still active - pass - if any(d['rating_key'] == str(result[1]) for d in media_container): - # The user is still playing the same media item - # Here we can check the play states - # logger.debug(u"PlexPy Monitor :: Session key %s :: Rating key %s is still active." % (result[0], result[1])) - pass - else: - # The user has stopped playing a stream - monitor_db.action('DELETE FROM sessions WHERE session_key = ? AND rating_key = ?', [result[0], result[1]]) - # logger.debug(u"PlexPy Monitor :: Session key %s :: Rating key %s has been stopped." % (result[0], result[1])) + for session in media_container: + if session['rating_key'] == str(stream['rating_key']): + # The user is still playing the same media item + # Here we can check the play states + if session['state'] != stream['state']: + if session['state'] == 'paused': + # Push any notifications + notify(stream_data=stream, notify_action='pause') + else: + # The user has stopped playing a stream + monitor_db.action('DELETE FROM sessions WHERE session_key = ? AND rating_key = ?', + [stream['session_key'], stream['rating_key']]) + # Push any notifications + notify(stream_data=stream, notify_action='stop') else: # The user's session is no longer active - monitor_db.action('DELETE FROM sessions WHERE session_key = ?', [result[0]]) - # logger.debug(u"PlexPy Monitor :: Session key %s :: Rating key %s has been stopped." % (result[0], result[1])) + monitor_db.action('DELETE FROM sessions WHERE session_key = ?', [stream['session_key']]) + # Push any notifications + notify(stream_data=stream, notify_action='stop') # Process the newly received session data for session in media_container: @@ -169,7 +189,13 @@ class MonitorProcessing(object): 'media_type': session['type'], 'state': session['state'], 'user': session['user'], - 'machine_id': session['machine_id']} + 'machine_id': session['machine_id'], + 'title': session['title'], + 'parent_title': session['parent_title'], + 'grandparent_title': session['grandparent_title'], + 'friendly_name': session['friendly_name'], + 'player': session['player'] + } timestamp = {'started': int(time.time())} @@ -182,23 +208,11 @@ class MonitorProcessing(object): # If it's our first write then time stamp it. self.db.upsert('sessions', timestamp, keys) - # User started playing a stream :: We notify here - if session['type'] == 'track' or session['type'] == 'episode': - item_title = session['grandparent_title'] + ' - ' + session['title'] - elif session['type'] == 'movie': - item_title = session['title'] - else: - item_title = session['title'] - - logger.info('%s (%s) starting playing %s' % (session['friendly_name'], session['platform'], item_title)) - pushmessage = '%s (%s) starting playing %s' % (session['friendly_name'], session['platform'], item_title) - # Push any notifications - monitor_notifications = MonitorNotifications(media_type=session['type'], user=session['user']) - monitor_notifications.notify(pushmessage) + notify(stream_data=values, notify_action='play') # Try and grab IP address from logs - if plexpy.CONFIG.PMS_LOGS_FOLDER: + if plexpy.CONFIG.IP_LOGGING_ENABLE and plexpy.CONFIG.PMS_LOGS_FOLDER: ip_address = self.find_session_ip(rating_key=session['rating_key'], machine_id=session['machine_id']) @@ -247,31 +261,78 @@ class MonitorProcessing(object): return None +def notify(stream_data=None, notify_action=None): -class MonitorNotifications(object): - - def __init__(self, media_type, user=None): - self.media_type = media_type - self.user = user - self.tv_notify_enabled = plexpy.CONFIG.TV_NOTIFY_ENABLE - self.movie_notify_enabled = plexpy.CONFIG.MOVIE_NOTIFY_ENABLE - self.music_notify_enabled = plexpy.CONFIG.MUSIC_NOTIFY_ENABLE - - def notify(self, message=None): - if message: - if self.media_type == 'movie': - if self.movie_notify_enabled: - notification_handler.push_nofitications(message, 'PlexPy', common.notify_strings[1]) - elif self.media_type == 'episode': - if self.tv_notify_enabled: - notification_handler.push_nofitications(message, 'PlexPy', common.notify_strings[1]) - elif self.media_type == 'track': - if self.music_notify_enabled: - notification_handler.push_nofitications(message, 'PlexPy', common.notify_strings[1]) - elif self.media_type == 'clip': - pass - else: - logger.debug(u"Notify called with unsupported media type.") - pass + if stream_data and notify_action: + # Build media item title + if stream_data['media_type'] == 'episode' or stream_data['media_type'] == 'track': + item_title = '%s - %s' % (stream_data['grandparent_title'], stream_data['title']) + elif stream_data['media_type'] == 'movie': + item_title = stream_data['title'] else: - logger.debug(u"Notify called without a message.") + item_title = stream_data['title'] + + if notify_action == 'play': + logger.info('%s (%s) started playing %s.' % (stream_data['friendly_name'], stream_data['player'], item_title)) + + if stream_data['media_type'] == 'movie': + if plexpy.CONFIG.MOVIE_NOTIFY_ENABLE: + + if plexpy.CONFIG.MOVIE_NOTIFY_ON_START and notify_action == 'play': + message = '%s (%s) started playing %s.' % \ + (stream_data['friendly_name'], stream_data['player'], item_title) + notification_handler.push_nofitications(message, 'PlexPy', common.notify_strings[1]) + + elif plexpy.CONFIG.MOVIE_NOTIFY_ON_PAUSE and notify_action == 'pause': + message = '%s (%s) has paused %s.' % \ + (stream_data['friendly_name'], stream_data['player'], item_title) + notification_handler.push_nofitications(message, 'PlexPy', common.notify_strings[1]) + + elif plexpy.CONFIG.MOVIE_NOTIFY_ON_STOP and notify_action == 'stop': + message = '%s (%s) stopped playing %s.' % \ + (stream_data['friendly_name'], stream_data['player'], item_title) + notification_handler.push_nofitications(message, 'PlexPy', common.notify_strings[1]) + + elif stream_data['media_type'] == 'episode': + if plexpy.CONFIG.TV_NOTIFY_ENABLE: + + if plexpy.CONFIG.TV_NOTIFY_ON_START and notify_action == 'play': + message = '%s (%s) started playing %s.' % \ + (stream_data['friendly_name'], stream_data['player'], item_title) + notification_handler.push_nofitications(message, 'PlexPy', common.notify_strings[1]) + + elif plexpy.CONFIG.TV_NOTIFY_ON_PAUSE and notify_action == 'pause': + message = '%s (%s) has paused %s.' % \ + (stream_data['friendly_name'], stream_data['player'], item_title) + notification_handler.push_nofitications(message, 'PlexPy', common.notify_strings[1]) + + elif plexpy.CONFIG.TV_NOTIFY_ON_STOP and notify_action == 'stop': + message = '%s (%s) stopped playing %s.' % \ + (stream_data['friendly_name'], stream_data['player'], item_title) + notification_handler.push_nofitications(message, 'PlexPy', common.notify_strings[1]) + + elif stream_data['media_type'] == 'track': + if plexpy.CONFIG.MUSIC_NOTIFY_ENABLE: + + if plexpy.CONFIG.MUSIC_NOTIFY_ON_START and notify_action == 'play': + message = '%s (%s) started playing %s.' % \ + (stream_data['friendly_name'], stream_data['player'], item_title) + notification_handler.push_nofitications(message, 'PlexPy', common.notify_strings[1]) + + elif plexpy.CONFIG.MUSIC_NOTIFY_ON_PAUSE and notify_action == 'pause': + message = '%s (%s) has paused %s.' % \ + (stream_data['friendly_name'], stream_data['player'], item_title) + notification_handler.push_nofitications(message, 'PlexPy', common.notify_strings[1]) + + elif plexpy.CONFIG.MUSIC_NOTIFY_ON_STOP and notify_action == 'stop': + message = '%s (%s) stopped playing %s.' % \ + (stream_data['friendly_name'], stream_data['player'], item_title) + notification_handler.push_nofitications(message, 'PlexPy', common.notify_strings[1]) + + elif stream_data['media_type'] == 'clip': + pass + else: + logger.debug(u"Notify called with unsupported media type.") + pass + else: + logger.debug(u"Notify called but incomplete data received.") diff --git a/plexpy/webserve.py b/plexpy/webserve.py index e298aa3d..961c3c09 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -335,7 +335,17 @@ class WebInterface(object): "music_notify_enable": checked(plexpy.CONFIG.MUSIC_NOTIFY_ENABLE), "tv_notify_on_start": checked(plexpy.CONFIG.TV_NOTIFY_ON_START), "movie_notify_on_start": checked(plexpy.CONFIG.MOVIE_NOTIFY_ON_START), - "music_notify_on_start": checked(plexpy.CONFIG.MUSIC_NOTIFY_ON_START) + "music_notify_on_start": checked(plexpy.CONFIG.MUSIC_NOTIFY_ON_START), + "tv_notify_on_stop": checked(plexpy.CONFIG.TV_NOTIFY_ON_STOP), + "movie_notify_on_stop": checked(plexpy.CONFIG.MOVIE_NOTIFY_ON_STOP), + "music_notify_on_stop": checked(plexpy.CONFIG.MUSIC_NOTIFY_ON_STOP), + "tv_notify_on_pause": checked(plexpy.CONFIG.TV_NOTIFY_ON_PAUSE), + "movie_notify_on_pause": checked(plexpy.CONFIG.MOVIE_NOTIFY_ON_PAUSE), + "music_notify_on_pause": checked(plexpy.CONFIG.MUSIC_NOTIFY_ON_PAUSE), + "monitoring_interval": plexpy.CONFIG.MONITORING_INTERVAL, + "refresh_users_interval": plexpy.CONFIG.REFRESH_USERS_INTERVAL, + "refresh_users_on_startup": checked(plexpy.CONFIG.REFRESH_USERS_ON_STARTUP), + "ip_logging_enable": checked(plexpy.CONFIG.IP_LOGGING_ENABLE) } return serve_template(templatename="config.html", title="Settings", config=config) @@ -353,7 +363,10 @@ class WebInterface(object): "boxcar_enabled", "email_enabled", "email_tls", "grouping_global_history", "grouping_user_history", "grouping_charts", "pms_use_bif", "tv_notify_enable", "movie_notify_enable", "music_notify_enable", - "tv_notify_on_start", "movie_notify_on_start", "music_notify_on_start" + "tv_notify_on_start", "movie_notify_on_start", "music_notify_on_start", + "tv_notify_on_stop", "movie_notify_on_stop", "music_notify_on_stop", + "tv_notify_on_pause", "movie_notify_on_pause", "music_notify_on_pause", "refresh_users_on_startup", + "ip_logging_enable" ] for checked_config in checked_configs: if checked_config not in kwargs: