@@ -299,12 +311,12 @@
-
-
-
+
+
+
${agent['name']}
% if agent['has_config']:
-
+
% endif
@@ -661,10 +673,7 @@
$('.notify-toggle-icon').each(function() {
if ($(this).data('config-value') == 1) {
- $(this).css("color", "#eb8600");
$(this).addClass("active");
- } else {
- $(this).css("color", "#444");
}
});
@@ -679,7 +688,6 @@
data: data,
async: true,
success: function(data) {
- toggle.css("color", "#444");
toggle.removeClass("active");
}
});
@@ -691,7 +699,6 @@
data: data,
async: true,
success: function(data) {
- toggle.css("color", "#eb8600");
toggle.addClass("active");
}
});
diff --git a/plexpy/__init__.py b/plexpy/__init__.py
index 5840120d..947d10fd 100644
--- a/plexpy/__init__.py
+++ b/plexpy/__init__.py
@@ -524,6 +524,13 @@ def dbcheck():
'ALTER TABLE session_history_metadata ADD COLUMN full_title TEXT'
)
+ # notify_log table :: This is a table which logs notifications sent
+ c_db.execute(
+ 'CREATE TABLE IF NOT EXISTS notify_log (id INTEGER PRIMARY KEY AUTOINCREMENT, '
+ 'session_key INTEGER, rating_key INTEGER, user_id INTEGER, user TEXT, '
+ 'agent_id INTEGER, agent_name TEXT, on_play INTEGER, on_stop INTEGER, on_watched INTEGER)'
+ )
+
conn_db.commit()
c_db.close()
diff --git a/plexpy/config.py b/plexpy/config.py
index 3d4d32b7..bcd3ed81 100644
--- a/plexpy/config.py
+++ b/plexpy/config.py
@@ -103,6 +103,7 @@ _CONFIG_DEFINITIONS = {
'NMA_ON_PLAY': (int, 'NMA', 0),
'NMA_ON_STOP': (int, 'NMA', 0),
'NMA_ON_WATCHED': (int, 'NMA', 0),
+ 'NOTIFY_WATCHED_PERCENT': (int, 'Monitoring', 85),
'OSX_NOTIFY_APP': (str, 'OSX_Notify', '/Applications/PlexPy'),
'OSX_NOTIFY_ENABLED': (int, 'OSX_Notify', 0),
'OSX_NOTIFY_ON_PLAY': (int, 'OSX_Notify', 0),
@@ -128,6 +129,7 @@ _CONFIG_DEFINITIONS = {
'PUSHALOT_ON_WATCHED': (int, 'Pushalot', 0),
'PUSHBULLET_APIKEY': (str, 'PushBullet', ''),
'PUSHBULLET_DEVICEID': (str, 'PushBullet', ''),
+ 'PUSHBULLET_CHANNEL_TAG': (str, 'PushBullet', ''),
'PUSHBULLET_ENABLED': (int, 'PushBullet', 0),
'PUSHBULLET_ON_PLAY': (int, 'PushBullet', 0),
'PUSHBULLET_ON_STOP': (int, 'PushBullet', 0),
diff --git a/plexpy/helpers.py b/plexpy/helpers.py
index 4a5767c1..f864ebc2 100644
--- a/plexpy/helpers.py
+++ b/plexpy/helpers.py
@@ -99,7 +99,7 @@ def latinToAscii(unicrap):
pass
else:
r += str(i)
- return r
+ return r.encode('utf-8')
def convert_milliseconds(ms):
@@ -352,7 +352,7 @@ def convert_xml_to_dict(xml):
def get_percent(value1, value2):
- if value1.isdigit() and value2.isdigit():
+ if str(value1).isdigit() and str(value2).isdigit():
value1 = cast_to_float(value1)
value2 = cast_to_float(value2)
else:
diff --git a/plexpy/monitor.py b/plexpy/monitor.py
index ea5fcd2c..fd662d94 100644
--- a/plexpy/monitor.py
+++ b/plexpy/monitor.py
@@ -13,7 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with PlexPy. If not, see
.
-from plexpy import logger, pmsconnect, notification_handler, log_reader, common, database
+from plexpy import logger, pmsconnect, notification_handler, log_reader, common, database, helpers
import threading
import plexpy
@@ -97,8 +97,10 @@ def check_active_sessions():
# Here we can check the play states
if session['state'] != stream['state']:
if session['state'] == 'paused':
- # Push any notifications
- notification_handler.notify(stream_data=stream, notify_action='pause')
+ # 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=stream, notify_action='pause')).start()
if stream['state'] == 'paused':
# The stream is still paused so we need to increment the paused_counter
# Using the set config parameter as the interval, probably not the most accurate but
@@ -107,14 +109,33 @@ def check_active_sessions():
monitor_db.action('UPDATE sessions SET paused_counter = ? '
'WHERE session_key = ? AND rating_key = ?',
[paused_counter, stream['session_key'], stream['rating_key']])
+ # Check if the user has reached the offset in the media we defined as the "watched" percent
+ if session['progress'] and session['duration']:
+ if helpers.get_percent(session['progress'], session['duration']) > plexpy.CONFIG.NOTIFY_WATCHED_PERCENT:
+ # 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=stream, notify_action='watched')).start()
+
else:
# The user has stopped playing a stream
logger.debug(u"PlexPy Monitor :: Removing sessionKey %s ratingKey %s from session queue"
% (stream['session_key'], stream['rating_key']))
monitor_db.action('DELETE FROM sessions WHERE session_key = ? AND rating_key = ?',
[stream['session_key'], stream['rating_key']])
- # Push any notifications
- notification_handler.notify(stream_data=stream, notify_action='stop')
+
+ # Check if the user has reached the offset in the media we defined as the "watched" percent
+ if stream['view_offset'] and stream['duration']:
+ if helpers.get_percent(stream['view_offset'], stream['duration']) > plexpy.CONFIG.NOTIFY_WATCHED_PERCENT:
+ # 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=stream, notify_action='watched')).start()
+
+ # 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=stream, notify_action='stop')).start()
+
# Write the item history on playback stop
monitor_process.write_session_history(session=stream)
@@ -132,7 +153,8 @@ class MonitorProcessing(object):
def write_session(self, session=None):
- values = {'rating_key': session['rating_key'],
+ values = {'session_key': session['session_key'],
+ 'rating_key': session['rating_key'],
'media_type': session['type'],
'state': session['state'],
'user_id': session['user_id'],
@@ -175,8 +197,10 @@ class MonitorProcessing(object):
result = self.db.upsert('sessions', values, keys)
if result == 'insert':
- # Push any notifications
- notification_handler.notify(stream_data=values, notify_action='play')
+ # 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='play')).start()
+
started = int(time.time())
# Try and grab IP address from logs
diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py
index 3b099789..66dab406 100644
--- a/plexpy/notification_handler.py
+++ b/plexpy/notification_handler.py
@@ -13,9 +13,10 @@
# You should have received a copy of the GNU General Public License
# along with PlexPy. If not, see
.
-from plexpy import logger, config, notifiers
+from plexpy import logger, config, notifiers, database
import plexpy
+import time
def notify(stream_data=None, notify_action=None):
from plexpy import pmsconnect, common
@@ -37,7 +38,7 @@ def notify(stream_data=None, notify_action=None):
item_title = stream_data['title']
if notify_action == 'play':
- logger.info('PlexPy Monitor :: %s (%s) started playing %s.' % (stream_data['friendly_name'],
+ logger.info('PlexPy Notifier :: %s (%s) started playing %s.' % (stream_data['friendly_name'],
stream_data['player'], item_title))
if stream_data['media_type'] == 'movie' or stream_data['media_type'] == 'episode':
@@ -45,35 +46,101 @@ def notify(stream_data=None, notify_action=None):
for agent in notifiers.available_notification_agents():
if agent['on_play'] and notify_action == 'play':
- logger.debug("%s agent is configured to notify on playback start." % agent['name'])
+ logger.debug("PlexPy Notifier :: %s agent is configured to notify on playback start." % agent['name'])
message = '%s (%s) started playing %s.' % \
(stream_data['friendly_name'], stream_data['player'], item_title)
notifiers.send_notification(config_id=agent['id'], subject=notify_header, body=message)
+ set_notify_state(session=stream_data, state='play', agent_info=agent)
elif agent['on_stop'] and notify_action == 'stop':
- logger.debug("%s agent is configured to notify on playback stop." % agent['name'])
+ logger.debug("PlexPy Notifier :: %s agent is configured to notify on playback stop." % agent['name'])
message = '%s (%s) has stopped %s.' % \
(stream_data['friendly_name'], stream_data['player'], item_title)
notifiers.send_notification(config_id=agent['id'], subject=notify_header, body=message)
+ set_notify_state(session=stream_data, state='stop', agent_info=agent)
+ elif agent['on_watched'] and notify_action == 'watched':
+ notify_states = get_notify_state(session=stream_data)
+ # If there is nothing in the notify_log for our agent id but it is enabled we should notify
+ if not any(d['agent_id'] == agent['id'] for d in notify_states):
+ logger.debug("PlexPy Notifier :: %s agent is configured to notify on watched." % agent['name'])
+ message = '%s (%s) has watched %s.' % \
+ (stream_data['friendly_name'], stream_data['player'], item_title)
+ notifiers.send_notification(config_id=agent['id'], subject=notify_header, body=message)
+ set_notify_state(session=stream_data, state='watched', agent_info=agent)
+ else:
+ # Check in our notify log if the notification has already been sent
+ for notify_state in notify_states:
+ if not notify_state['on_watched'] and (notify_state['agent_id'] == agent['id']):
+ logger.debug("PlexPy Notifier :: %s agent is configured to notify on watched." % agent['name'])
+ message = '%s (%s) has watched %s.' % \
+ (stream_data['friendly_name'], stream_data['player'], item_title)
+ notifiers.send_notification(config_id=agent['id'], subject=notify_header, body=message)
+ set_notify_state(session=stream_data, state='watched', agent_info=agent)
elif stream_data['media_type'] == 'track':
if plexpy.CONFIG.MUSIC_NOTIFY_ENABLE:
for agent in notifiers.available_notification_agents():
if agent['on_play'] and notify_action == 'play':
- logger.debug("%s agent is configured to notify on playback start." % agent['name'])
+ logger.debug("PlexPy Notifier :: %s agent is configured to notify on playback start." % agent['name'])
message = '%s (%s) started playing %s.' % \
(stream_data['friendly_name'], stream_data['player'], item_title)
notifiers.send_notification(config_id=agent['id'], subject=notify_header, body=message)
+ set_notify_state(session=stream_data, state='play', agent_info=agent)
elif agent['on_stop'] and notify_action == 'stop':
- logger.debug("%s agent is configured to notify on playback stop." % agent['name'])
+ logger.debug("PlexPy Notifier :: %s agent is configured to notify on playback stop." % agent['name'])
message = '%s (%s) has stopped %s.' % \
(stream_data['friendly_name'], stream_data['player'], item_title)
notifiers.send_notification(config_id=agent['id'], subject=notify_header, body=message)
+ set_notify_state(session=stream_data, state='stop', agent_info=agent)
elif stream_data['media_type'] == 'clip':
pass
else:
- logger.debug(u"PlexPy Monitor :: Notify called with unsupported media type.")
+ logger.debug(u"PlexPy Notifier :: Notify called with unsupported media type.")
pass
else:
- logger.debug(u"PlexPy Monitor :: Notify called but incomplete data received.")
+ logger.debug(u"PlexPy Notifier :: Notify called but incomplete data received.")
+
+def get_notify_state(session):
+ monitor_db = database.MonitorDatabase()
+ result = monitor_db.select('SELECT on_play, on_stop, on_watched, agent_id '
+ 'FROM notify_log '
+ 'WHERE session_key = ? '
+ 'AND rating_key = ? '
+ 'AND user = ? '
+ 'ORDER BY id DESC',
+ args=[session['session_key'], session['rating_key'], session['user']])
+ notify_states = []
+ for item in result:
+ notify_state = {'on_play': item[0],
+ 'on_stop': item[1],
+ 'on_watched': item[2],
+ 'agent_id': item[3]}
+ notify_states.append(notify_state)
+
+ return notify_states
+
+def set_notify_state(session, state, agent_info):
+
+ if session and state and agent_info:
+ monitor_db = database.MonitorDatabase()
+
+ if state == 'play':
+ values = {'on_play': int(time.time())}
+ elif state == 'stop':
+ values = {'on_stop': int(time.time())}
+ elif state == 'watched':
+ values = {'on_watched': int(time.time())}
+ else:
+ return
+
+ keys = {'session_key': session['session_key'],
+ 'rating_key': session['rating_key'],
+ 'user_id': session['user_id'],
+ 'user': session['user'],
+ 'agent_id': agent_info['id'],
+ 'agent_name': agent_info['name']}
+
+ monitor_db.upsert(table_name='notify_log', key_dict=keys, value_dict=values)
+ else:
+ logger.error('PlexPy Notifier :: Unable to set notify state.')
diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py
index 855380de..dae66bb4 100644
--- a/plexpy/notifiers.py
+++ b/plexpy/notifiers.py
@@ -44,9 +44,9 @@ AGENT_IDS = {"Growl": 0,
"XBMC": 2,
"Plex": 3,
"NMA": 4,
- "PushAlot": 5,
- "PushBullet": 6,
- "PushOver": 7,
+ "Pushalot": 5,
+ "Pushbullet": 6,
+ "Pushover": 7,
"OSX Notify": 8,
"Boxcar2": 9,
"Email": 10}
@@ -97,8 +97,8 @@ def available_notification_agents():
'on_stop': plexpy.CONFIG.NMA_ON_STOP,
'on_watched': plexpy.CONFIG.NMA_ON_WATCHED
},
- {'name': 'PushAlot',
- 'id': AGENT_IDS['PushAlot'],
+ {'name': 'Pushalot',
+ 'id': AGENT_IDS['Pushalot'],
'config_prefix': 'pushalot',
'has_config': True,
'state': checked(plexpy.CONFIG.PUSHALOT_ENABLED),
@@ -106,8 +106,8 @@ def available_notification_agents():
'on_stop': plexpy.CONFIG.PUSHALOT_ON_STOP,
'on_watched': plexpy.CONFIG.PUSHALOT_ON_WATCHED
},
- {'name': 'PushBullet',
- 'id': AGENT_IDS['PushBullet'],
+ {'name': 'Pushbullet',
+ 'id': AGENT_IDS['Pushbullet'],
'config_prefix': 'pushbullet',
'has_config': True,
'state': checked(plexpy.CONFIG.PUSHBULLET_ENABLED),
@@ -115,8 +115,8 @@ def available_notification_agents():
'on_stop': plexpy.CONFIG.PUSHBULLET_ON_STOP,
'on_watched': plexpy.CONFIG.PUSHBULLET_ON_WATCHED
},
- {'name': 'PushOver',
- 'id': AGENT_IDS['PushOver'],
+ {'name': 'Pushover',
+ 'id': AGENT_IDS['Pushover'],
'config_prefix': 'pushover',
'has_config': True,
'state': checked(plexpy.CONFIG.PUSHOVER_ENABLED),
@@ -257,7 +257,7 @@ class GROWL(object):
return cherrypy.config['config'].get('Growl', options)
def notify(self, message, event):
- if not self.enabled:
+ if not message or not event:
return
# Split host and port
@@ -362,7 +362,7 @@ class PROWL(object):
return cherrypy.config['config'].get('Prowl', options)
def notify(self, message, event):
- if not plexpy.CONFIG.PROWL_ENABLED:
+ if not message or not event:
return
http_handler = HTTPSConnection("api.prowlapp.com")
@@ -596,18 +596,21 @@ class NMA(object):
self.on_watched = plexpy.CONFIG.NMA_ON_WATCHED
def notify(self, subject=None, message=None):
+ if not subject or not message:
+ return
+
title = 'PlexPy'
api = plexpy.CONFIG.NMA_APIKEY
nma_priority = plexpy.CONFIG.NMA_PRIORITY
- logger.debug(u"NMA title: " + title)
- logger.debug(u"NMA API: " + api)
- logger.debug(u"NMA Priority: " + str(nma_priority))
+ # logger.debug(u"NMA title: " + title)
+ # logger.debug(u"NMA API: " + api)
+ # logger.debug(u"NMA Priority: " + str(nma_priority))
event = subject
- logger.debug(u"NMA event: " + event)
- logger.debug(u"NMA message: " + message)
+ # logger.debug(u"NMA event: " + event)
+ # logger.debug(u"NMA message: " + message)
batch = False
@@ -648,6 +651,7 @@ class PUSHBULLET(object):
def __init__(self):
self.apikey = plexpy.CONFIG.PUSHBULLET_APIKEY
self.deviceid = plexpy.CONFIG.PUSHBULLET_DEVICEID
+ self.channel_tag = plexpy.CONFIG.PUSHBULLET_CHANNEL_TAG
self.on_play = plexpy.CONFIG.PUSHBULLET_ON_PLAY
self.on_stop = plexpy.CONFIG.PUSHBULLET_ON_STOP
self.on_watched = plexpy.CONFIG.PUSHBULLET_ON_WATCHED
@@ -665,16 +669,22 @@ class PUSHBULLET(object):
'title': subject.encode("utf-8"),
'body': message.encode("utf-8")}
+ # Can only send to a device or channel, not both.
+ if self.deviceid:
+ data['device_iden'] = self.deviceid
+ elif self.channel_tag:
+ data['channel_tag'] = self.channel_tag
+
http_handler.request("POST",
"/v2/pushes",
headers={'Content-type': "application/json",
- 'Authorization': 'Basic %s' % base64.b64encode(plexpy.CONFIG.PUSHBULLET_APIKEY + ":")},
+ 'Authorization': 'Basic %s' % base64.b64encode(plexpy.CONFIG.PUSHBULLET_APIKEY + ":")},
body=json.dumps(data))
response = http_handler.getresponse()
request_status = response.status
- logger.debug(u"PushBullet response status: %r" % request_status)
- logger.debug(u"PushBullet response headers: %r" % response.getheaders())
- logger.debug(u"PushBullet response body: %r" % response.read())
+ # logger.debug(u"PushBullet response status: %r" % request_status)
+ # logger.debug(u"PushBullet response headers: %r" % response.getheaders())
+ # logger.debug(u"PushBullet response body: %r" % response.read())
if request_status == 200:
logger.info(u"PushBullet notifications sent.")
@@ -704,7 +714,13 @@ class PUSHBULLET(object):
{'label': 'Device ID',
'value': self.deviceid,
'name': 'pushbullet_deviceid',
- 'description': 'A device ID (optional).',
+ 'description': 'A device ID (optional). If set, will override channel tag.',
+ 'input_type': 'text'
+ },
+ {'label': 'Channel',
+ 'value': self.channel_tag,
+ 'name': 'pushbullet_channel_tag',
+ 'description': 'A channel tag (optional).',
'input_type': 'text'
}
]
@@ -720,7 +736,7 @@ class PUSHALOT(object):
self.on_watched = plexpy.CONFIG.PUSHALOT_ON_WATCHED
def notify(self, message, event):
- if not plexpy.CONFIG.PUSHALOT_ENABLED:
+ if not message or not event:
return
pushalot_authorizationtoken = plexpy.CONFIG.PUSHALOT_APIKEY
@@ -786,7 +802,7 @@ class PUSHOVER(object):
return cherrypy.config['config'].get('Pushover', options)
def notify(self, message, event):
- if not plexpy.CONFIG.PUSHOVER_ENABLED:
+ if not message or not event:
return
http_handler = HTTPSConnection("api.pushover.net")
@@ -1037,11 +1053,11 @@ class BOXCAR(object):
self.on_stop = plexpy.CONFIG.BOXCAR_ON_STOP
self.on_watched = plexpy.CONFIG.BOXCAR_ON_WATCHED
- def notify(self, title, message, rgid=None):
- try:
- if rgid:
- message += '
MusicBrainz' % rgid
+ def notify(self, title, message):
+ if not title or not message:
+ return
+ try:
data = urllib.urlencode({
'user_credentials': plexpy.CONFIG.BOXCAR_TOKEN,
'notification[title]': title.encode('utf-8'),
@@ -1077,6 +1093,8 @@ class Email(object):
self.on_watched = plexpy.CONFIG.EMAIL_ON_WATCHED
def notify(self, subject, message):
+ if not subject or not message:
+ return
message = MIMEText(message, 'plain', "utf-8")
message['Subject'] = subject
diff --git a/plexpy/webserve.py b/plexpy/webserve.py
index d644434e..679d788b 100644
--- a/plexpy/webserve.py
+++ b/plexpy/webserve.py
@@ -510,7 +510,8 @@ class WebInterface(object):
"video_logging_enable": checked(plexpy.CONFIG.VIDEO_LOGGING_ENABLE),
"music_logging_enable": checked(plexpy.CONFIG.MUSIC_LOGGING_ENABLE),
"logging_ignore_interval": plexpy.CONFIG.LOGGING_IGNORE_INTERVAL,
- "pms_is_remote": checked(plexpy.CONFIG.PMS_IS_REMOTE)
+ "pms_is_remote": checked(plexpy.CONFIG.PMS_IS_REMOTE),
+ "notify_watched_percent": plexpy.CONFIG.NOTIFY_WATCHED_PERCENT
}
return serve_template(templatename="settings.html", title="Settings", config=config)