Merge pull request #282 from JonnyWong16/miscellaneous-fixes

IPv6 support, fix recently added, get server name, notify server down
This commit is contained in:
JonnyWong16 2015-11-20 06:28:02 -08:00
commit 9e5334ac81
20 changed files with 576 additions and 150 deletions

View file

@ -7,7 +7,7 @@ from plexpy import version
<html lang="en">
<head>
<meta charset="utf-8">
<title>PlexPy - ${title}</title>
<title>PlexPy - ${title} | ${server_name}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="">
<meta name="author" content="">

View file

@ -341,6 +341,24 @@ input[type="color"],
border-radius: 3px;
transition: background-color .3s;
}
textarea.form-control {
height: initial;
margin: 5px 0 5px 0;
color: #fff;
border: 0px solid #444;
background: #555;
padding: 6px 12px;
background-color: #555;
border-radius: 3px;
transition: background-color .3s;
resize: none;
}
textarea.form-control:focus {
outline: 0;
color: #555;
background-color: #fff;
transition: background-color .3s;
}
.pagination > li > a,
.pagination > li > span {
position: relative;

View file

@ -33,7 +33,7 @@
<div class="row">
<div class="col-md-12">
<div class="padded-header" id="library-statistics-header">
<h3>Library Statistics</h3>
<h3>Library Statistics <small>${config['pms_name']}</small></h3>
</div>
<div id="library-stats" class="library-platforms">
<div class='text-muted'><i class="fa fa-refresh fa-spin"></i> Loading stats...</div>
@ -98,27 +98,6 @@
});
}
function getLibraryStatsHeader() {
$.ajax({
"url": "get_servers_info",
type: "post",
cache: false,
async: true,
data: { },
complete: function (xhr, status) {
server_info = $.parseJSON(xhr.responseText);
var server_name = 'Server name not found';
for (var i in server_info) {
if (server_info[i].machine_identifier == '${config['pms_identifier']}') {
server_name = server_info[i].name
break;
}
}
$('#library-statistics-header h3').append(' <small>' + server_name + '</small>')
}
});
}
function getLibraryStats() {
$.ajax({
url: 'library_stats',
@ -170,7 +149,6 @@
});
getHomeStats();
getLibraryStatsHeader();
getLibraryStats();

View file

@ -232,7 +232,9 @@ function getPlatformImagePath(platformName) {
function isPrivateIP(ip_address) {
if (ip_address.indexOf(".") > -1) {
var parts = ip_address.split('.');
// get IPv4 mapped address (xxx.xxx.xxx.xxx) from IPv6 addresss (::ffff:xxx.xxx.xxx.xxx)
var parts = ip_address.split(":");
var parts = parts[parts.length - 1].split('.');
if (parts[0] === '10' ||
(parts[0] === '172' && (parseInt(parts[1], 10) >= 16 && parseInt(parts[1], 10) <= 31)) ||
(parts[0] === '192' && parts[1] === '168')) {

View file

@ -64,6 +64,20 @@ from plexpy import helpers
</label>
<p class="help-block">Trigger notification when a media item is added to the Plex Media Server.</p>
</div>
<div class="checkbox">
<label>
<input type="checkbox" data-size="small" data-id="${data['id']}" data-config-name="${data['config_prefix']}_on_extdown" ${helpers.checked(data['on_extdown'])} class="toggle-switches">
Notify on Plex remote access down
</label>
<p class="help-block">Trigger notification when the Plex Media Server cannot be reached externally.</p>
</div>
<div class="checkbox">
<label>
<input type="checkbox" data-size="small" data-id="${data['id']}" data-config-name="${data['config_prefix']}_on_intdown" ${helpers.checked(data['on_intdown'])} class="toggle-switches">
Notify on Plex server down
</label>
<p class="help-block">Trigger notification when the Plex Media Server cannot be reached internally.</p>
</div>
</div>
</div>
</div>

View file

@ -403,6 +403,12 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</label>
<p class="help-block">Instead of polling the server at regular intervals let the server tell us when something happens. This is currently experimental. Encrypted websocket is not currently supported.</p>
</div>
<div class="checkbox">
<label>
<input type="checkbox" id="monitor_remote_access" name="monitor_remote_access" value="1" ${config['monitor_remote_access']}> Monitor Plex Remote Access
</label>
<p class="help-block">Enable to have PlexPy check if remote access to the Plex Media Server goes down. (Must have remote access set up in Plex.)</p>
</div>
<div class="padded-header">
<h3>History Logging</h3>
@ -483,7 +489,7 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</div>
<div class="padded-header">
<h3>Notification Tuning</h3>
<h3>Current Activity Notifications</h3>
</div>
<div class="form-group">
@ -502,12 +508,27 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</label>
<p class="help-block">Disable to prevent consecutive notifications (i.e. both watched &amp; stopped notifications).</p>
</div>
<div class="padded-header">
<h3>Recently Added Notifications</h3>
</div>
<div class="checkbox">
<label>
<input type="checkbox" name="notify_recently_added_grandparent" id="notify_recently_added_grandparent" value="1" ${config['notify_recently_added_grandparent']}> Group notifications for recently added TV Shows or Music
</label>
<p class="help-block">Enable to only get one notification for recently added Episodes or Tracks. Movies are unaffected.</p>
</div>
<div class="form-group">
<label for="notify_recently_added_delay">Notification Delay</label>
<div class="row">
<div class="col-md-2">
<input type="text" class="form-control" data-parsley-type="integer" id="notify_recently_added_delay" name="notify_recently_added_delay" value="${config['notify_recently_added_delay']}" size="5" data-parsley-min="60" data-parsley-trigger="change" data-parsley-errors-container="#notify_recently_added_delay_error" required>
</div>
<div id="notify_recently_added_delay_error" class="alert alert-danger settings-alert" role="alert"></div>
</div>
<p class="help-block">Set the delay for recently added notifications to allow metadata to be processes. Minimum 60 seconds.</p>
</div>
<div class="padded-header">
<h3>Custom Notification Messages</h3>
@ -523,7 +544,7 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
<br/>
<ul id="accordion-session" class="accordion list-unstyled">
<li>
<div class="link"><i class="fa fa-play"></i>Playback Start<i class="fa fa-chevron-down"></i></div>
<div class="link"><i class="fa fa-play fa-fw"></i>&nbsp;Playback Start<i class="fa fa-chevron-down"></i></div>
<ul class="submenu">
<li>
<div class="form-group">
@ -533,14 +554,14 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</div>
<div class="form-group">
<label for="notify_on_start_body_text">Message Body</label>
<input class="form-control" type="text" id="notify_on_start_body_text" name="notify_on_start_body_text" value="${config['notify_on_start_body_text']}" data-parsley-trigger="change" required>
<textarea class="form-control" id="notify_on_start_body_text" name="notify_on_start_body_text" data-parsley-trigger="change" data-autoresize required>${config['notify_on_start_body_text']}</textarea>
<p class="help-block">Set a custom body.</p>
</div>
</li>
</ul>
</li>
<li>
<div class="link"><i class="fa fa-stop"></i>Playback Stop<i class="fa fa-chevron-down"></i></div>
<div class="link"><i class="fa fa-stop fa-fw"></i>&nbsp;Playback Stop<i class="fa fa-chevron-down"></i></div>
<ul class="submenu">
<li>
<div class="form-group">
@ -550,14 +571,14 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</div>
<div class="form-group">
<label for="notify_on_stop_body_text">Message Body</label>
<input class="form-control" type="text" id="notify_on_stop_body_text" name="notify_on_stop_body_text" value="${config['notify_on_stop_body_text']}" data-parsley-trigger="change" required>
<textarea class="form-control" id="notify_on_stop_body_text" name="notify_on_stop_body_text" data-parsley-trigger="change" data-autoresize required>${config['notify_on_stop_body_text']}</textarea>
<p class="help-block">Set a custom body.</p>
</div>
</li>
</ul>
</li>
<li>
<div class="link"><i class="fa fa-pause"></i>Playback Pause<i class="fa fa-chevron-down"></i></div>
<div class="link"><i class="fa fa-pause fa-fw"></i>&nbsp;Playback Pause<i class="fa fa-chevron-down"></i></div>
<ul class="submenu">
<li>
<div class="form-group">
@ -567,14 +588,14 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</div>
<div class="form-group">
<label for="notify_on_pause_body_text">Message Body</label>
<input class="form-control" type="text" id="notify_on_pause_body_text" name="notify_on_pause_body_text" value="${config['notify_on_pause_body_text']}" data-parsley-trigger="change" required>
<textarea class="form-control" id="notify_on_pause_body_text" name="notify_on_pause_body_text" data-parsley-trigger="change" data-autoresize required>${config['notify_on_pause_body_text']}</textarea>
<p class="help-block">Set a custom body.</p>
</div>
</li>
</ul>
</li>
<li>
<div class="link"><i class="fa fa-play"></i>Playback Resume<i class="fa fa-chevron-down"></i></div>
<div class="link"><i class="fa fa-play fa-fw"></i>&nbsp;Playback Resume<i class="fa fa-chevron-down"></i></div>
<ul class="submenu">
<li>
<div class="form-group">
@ -584,14 +605,14 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</div>
<div class="form-group">
<label for="notify_on_resume_body_text">Message Body</label>
<input class="form-control" type="text" id="notify_on_resume_body_text" name="notify_on_resume_body_text" value="${config['notify_on_resume_body_text']}" data-parsley-trigger="change" required>
<textarea class="form-control" id="notify_on_resume_body_text" name="notify_on_resume_body_text" data-parsley-trigger="change" data-autoresize required>${config['notify_on_resume_body_text']}</textarea>
<p class="help-block">Set a custom body.</p>
</div>
</li>
</ul>
</li>
<li>
<div class="link"><i class="fa fa-eye"></i>Watched<i class="fa fa-chevron-down"></i></div>
<div class="link"><i class="fa fa-eye fa-fw"></i>&nbsp;Watched<i class="fa fa-chevron-down"></i></div>
<ul class="submenu">
<li>
<div class="form-group">
@ -601,14 +622,14 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</div>
<div class="form-group">
<label for="notify_on_watched_body_text">Message Body</label>
<input class="form-control" type="text" id="notify_on_watched_body_text" name="notify_on_watched_body_text" value="${config['notify_on_watched_body_text']}" data-parsley-trigger="change" required>
<textarea class="form-control" id="notify_on_watched_body_text" name="notify_on_watched_body_text" data-parsley-trigger="change" data-autoresize required>${config['notify_on_watched_body_text']}</textarea>
<p class="help-block">Set a custom body.</p>
</div>
</li>
</ul>
</li>
<li>
<div class="link"><i class="fa fa-spinner"></i>Buffer Warnings<i class="fa fa-chevron-down"></i></div>
<div class="link"><i class="fa fa-spinner fa-fw"></i>&nbsp;Buffer Warnings<i class="fa fa-chevron-down"></i></div>
<ul class="submenu">
<li>
<div class="form-group">
@ -618,7 +639,7 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</div>
<div class="form-group">
<label for="notify_on_buffer_body_text">Message Body</label>
<input class="form-control" type="text" id="notify_on_buffer_body_text" name="notify_on_buffer_body_text" value="${config['notify_on_buffer_body_text']}" data-parsley-trigger="change" required>
<textarea class="form-control" id="notify_on_buffer_body_text" name="notify_on_buffer_body_text" data-parsley-trigger="change" data-autoresize required>${config['notify_on_buffer_body_text']}</textarea>
<p class="help-block">Set a custom body.</p>
</div>
</li>
@ -627,7 +648,7 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</ul>
<ul id="accordion-timeline" class="accordion list-unstyled">
<li>
<div class="link"><i class="fa fa-download"></i>Recently Added<i class="fa fa-chevron-down"></i></div>
<div class="link"><i class="fa fa-download fa-fw"></i>&nbsp;Recently Added<i class="fa fa-chevron-down"></i></div>
<ul class="submenu">
<li>
<div class="form-group">
@ -637,7 +658,41 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</div>
<div class="form-group">
<label for="notify_on_created_body_text">Message Body</label>
<input class="form-control" type="text" id="notify_on_created_body_text" name="notify_on_created_body_text" value="${config['notify_on_created_body_text']}" data-parsley-trigger="change" required>
<textarea class="form-control" id="notify_on_created_body_text" name="notify_on_created_body_text" data-parsley-trigger="change" data-autoresize required>${config['notify_on_created_body_text']}</textarea>
<p class="help-block">Set a custom body.</p>
</div>
</li>
</ul>
</li>
<li>
<div class="link"><i class="fa fa-server fa-fw"></i>&nbsp;Plex Remote Access Down<i class="fa fa-chevron-down"></i></div>
<ul class="submenu">
<li>
<div class="form-group">
<label for="notify_on_extdown_subject_text">Subject Line</label>
<input class="form-control" type="text" id="notify_on_extdown_subject_text" name="notify_on_extdown_subject_text" value="${config['notify_on_extdown_subject_text']}" data-parsley-trigger="change" required>
<p class="help-block">Set a custom subject line.</p>
</div>
<div class="form-group">
<label for="notify_on_extdown_body_text">Message Body</label>
<textarea class="form-control" id="notify_on_extdown_body_text" name="notify_on_extdown_body_text" data-parsley-trigger="change" data-autoresize required>${config['notify_on_extdown_body_text']}</textarea>
<p class="help-block">Set a custom body.</p>
</div>
</li>
</ul>
</li>
<li>
<div class="link"><i class="fa fa-server fa-fw"></i>&nbsp;Plex Server Down<i class="fa fa-chevron-down"></i></div>
<ul class="submenu">
<li>
<div class="form-group">
<label for="notify_on_intdown_subject_text">Subject Line</label>
<input class="form-control" type="text" id="notify_on_intdown_subject_text" name="notify_on_intdown_subject_text" value="${config['notify_on_intdown_subject_text']}" data-parsley-trigger="change" required>
<p class="help-block">Set a custom subject line.</p>
</div>
<div class="form-group">
<label for="notify_on_intdown_body_text">Message Body</label>
<textarea class="form-control" id="notify_on_intdown_body_text" name="notify_on_intdown_body_text" data-parsley-trigger="change" data-autoresize required>${config['notify_on_intdown_body_text']}</textarea>
<p class="help-block">Set a custom body.</p>
</div>
</li>
@ -660,7 +715,7 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
% for agent in available_notification_agents:
<li>
<span>
% if agent['on_play'] or agent['on_stop'] or agent['on_pause'] or agent['on_resume'] or agent['on_buffer'] or agent['on_watched'] or agent['on_created']:
% if agent['on_play'] or agent['on_stop'] or agent['on_pause'] or agent['on_resume'] or agent['on_buffer'] or agent['on_watched'] or agent['on_created'] or agent['on_extdown'] or agent['on_intdown']:
<a href="javascript:void(0)" data-target="#notification-triggers-modal" data-id="${agent['id']}" class="toggle-notification-triggers-modal toggle-left active" data-toggle="modal"><i class="fa fa-lg fa-bell"></i></a>
% else:
<a href="javascript:void(0)" data-target="#notification-triggers-modal" data-id="${agent['id']}" class="toggle-notification-triggers-modal toggle-left" data-toggle="modal"><i class="fa fa-lg fa-bell"></i></a>
@ -870,6 +925,10 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
<td width="150"><strong>{server_name}</strong></td>
<td>The name of your Plex Server.</td>
</tr>
<tr>
<td width="150"><strong>{server_uptime}</strong></td>
<td>The uptime (in days, hours, mins, secs) of your Plex Server.</td>
</tr>
<tr>
<td width="150"><strong>{user}</strong></td>
<td>The username of the person streaming.</td>
@ -1252,7 +1311,7 @@ $(document).ready(function() {
'X-Plex-Version': '${common.VERSION_NUMBER}',
'X-Plex-Platform': '${common.PLATFORM}',
'X-Plex-Platform-Version': '${common.PLATFORM_VERSION}',
'X-Plex-Client-Identifier': '${config['pms_uuid']}',
'X-Plex-Client-Identifier': '${config["pms_uuid"]}',
'Authorization': 'Basic ' + btoa($("#pms_username").val() + ':' + $("#pms_password").val())
},
error: function(jqXHR, textStatus, errorThrown) {
@ -1388,6 +1447,14 @@ $(document).ready(function() {
e.preventDefault()
});
// auto resizing textarea for custom notification message body
$('textarea[data-autoresize]').each(function() {
var offset = this.offsetHeight - this.clientHeight;
var resizeTextarea = function(el) {
$(el).css('height', 'auto').css('height', el.scrollHeight + offset);
};
$(this).on('focus keyup input', function() { resizeTextarea(this); }).removeAttr('data-autoresize');
});
});
</script>
</%def>
</%def>

View file

@ -31,7 +31,7 @@ except ImportError:
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.interval import IntervalTrigger
from plexpy import versioncheck, logger, activity_pinger, plextv
from plexpy import versioncheck, logger, activity_pinger, plextv, pmsconnect
import plexpy.config
PROG_DIR = None
@ -169,6 +169,7 @@ def initialize(config_file):
# Get the real PMS urls for SSL and remote access
if CONFIG.PMS_TOKEN and CONFIG.PMS_IP and CONFIG.PMS_PORT:
plextv.get_real_pms_url()
pmsconnect.get_server_friendly_name()
# Refresh the users list on startup
if CONFIG.PMS_TOKEN and CONFIG.REFRESH_USERS_ON_STARTUP:
@ -280,14 +281,19 @@ def initialize_scheduler():
seconds = 0
if CONFIG.PMS_IP and CONFIG.PMS_TOKEN:
schedule_job(plextv.get_real_pms_url, 'Refresh Plex Server URLs', hours=12, minutes=0, seconds=0)
schedule_job(plextv.get_real_pms_url, 'Refresh Plex Server URLs',
hours=12, minutes=0, seconds=0)
schedule_job(pmsconnect.get_server_friendly_name, 'Refresh Plex Server Name',
hours=12, minutes=0, seconds=0)
schedule_job(activity_pinger.check_recently_added, 'Check for recently added items',
hours=0, minutes=0, seconds=seconds)
schedule_job(activity_pinger.check_server_response, 'Check for server response',
hours=0, minutes=0, seconds=seconds)
# If we're not using websockets then fall back to polling
if not CONFIG.MONITORING_USE_WEBSOCKET or POLLING_FAILOVER:
schedule_job(activity_pinger.check_active_sessions, 'Check for active sessions',
hours=0, minutes=0, seconds=seconds)
schedule_job(activity_pinger.check_recently_added, 'Check for recently added items',
hours=0, minutes=0, seconds=seconds)
# Refresh the users list
if CONFIG.REFRESH_USERS_INTERVAL:

View file

@ -218,7 +218,7 @@ class TimelineHandler(object):
def __init__(self, timeline):
self.timeline = timeline
logger.debug(timeline)
#logger.debug(timeline)
def is_item(self):
if 'itemID' in self.timeline:

View file

@ -13,13 +13,16 @@
# You should have received a copy of the GNU General Public License
# along with PlexPy. If not, see <http://www.gnu.org/licenses/>.
from plexpy import logger, pmsconnect, notification_handler, database, helpers, activity_processor
from plexpy import logger, pmsconnect, plextv, notification_handler, database, helpers, activity_processor
import threading
import plexpy
import time
import urllib2
monitor_lock = threading.Lock()
ext_ping_count = 0
int_ping_count = 0
def check_active_sessions(ws_request=False):
@ -162,12 +165,14 @@ def check_active_sessions(ws_request=False):
monitor_process.write_session(session)
else:
logger.debug(u"PlexPy Monitor :: Unable to read session list.")
def check_recently_added():
with monitor_lock:
current_time = int(time.time())
# add delay to allow for metadata processing
delay = plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_DELAY
time_threshold = int(time.time()) - delay
time_interval = plexpy.CONFIG.MONITORING_INTERVAL
pms_connect = pmsconnect.PmsConnect()
recently_added_list = pms_connect.get_recently_added_details(count='10')
@ -176,36 +181,91 @@ def check_recently_added():
recently_added = recently_added_list['recently_added']
for item in recently_added:
if int(item['added_at']) >= current_time - plexpy.CONFIG.MONITORING_INTERVAL:
if item['media_type'] == 'movie':
metadata_list = pms_connect.get_metadata_details(item['rating_key'])
if metadata_list:
metadata = [metadata_list['metadata']]
else:
logger.error(u"PlexPy Monitor :: Unable to retrieve metadata for rating_key %s" % str(item['rating_key']))
elif plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_GRANDPARENT:
metadata_list = pms_connect.get_metadata_details(item['parent_rating_key'])
if metadata_list:
metadata = [metadata_list['metadata']]
else:
logger.error(u"PlexPy Monitor :: Unable to retrieve metadata for parent_rating_key %s" % str(item['parent_rating_key']))
if item['media_type'] == 'movie':
metadata_list = pms_connect.get_metadata_details(item['rating_key'])
if metadata_list:
metadata = [metadata_list['metadata']]
else:
metadata_list = pms_connect.get_metadata_children_details(item['rating_key'])
if metadata_list:
metadata = metadata_list['metadata']
else:
logger.error(u"PlexPy Monitor :: Unable to retrieve children metadata for rating_key" % str(item['rating_key']))
logger.error(u"PlexPy Monitor :: Unable to retrieve metadata for rating_key %s" \
% str(item['rating_key']))
if metadata:
else:
metadata_list = pms_connect.get_metadata_children_details(item['rating_key'])
if metadata_list:
metadata = metadata_list['metadata']
else:
logger.error(u"PlexPy Monitor :: Unable to retrieve children metadata for rating_key %s" \
% str(item['rating_key']))
if metadata:
if not plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_GRANDPARENT:
for item in metadata:
if (plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_GRANDPARENT \
and int(item['updated_at']) >= current_time - plexpy.CONFIG.MONITORING_INTERVAL) \
or (not plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_GRANDPARENT \
and int(item['added_at']) >= current_time - plexpy.CONFIG.MONITORING_INTERVAL):
logger.debug(u"PlexPy Monitor :: Library item %s has been added to Plex." % str(item['rating_key']))
if 0 < int(item['added_at']) - time_threshold <= time_interval:
# Fire off notifications
threading.Thread(target=notification_handler.notify_timeline,
kwargs=dict(timeline_data=item, notify_action='created')).start()
else:
item = max(metadata, key=lambda x:x['added_at'])
if 0 < int(item['added_at']) - time_threshold <= time_interval:
if item['media_type'] == 'episode' or item['media_type'] == 'track':
metadata_list = pms_connect.get_metadata_details(item['grandparent_rating_key'])
if metadata_list:
item = metadata_list['metadata']
else:
logger.error(u"PlexPy Monitor :: Unable to retrieve grandparent metadata for grandparent_rating_key %s" \
% str(item['rating_key']))
# Fire off notifications
threading.Thread(target=notification_handler.notify_timeline,
kwargs=dict(timeline_data=item, notify_action='created')).start()
def check_server_response():
with monitor_lock:
pms_connect = pmsconnect.PmsConnect()
internal_response = pms_connect.get_server_response()
global int_ping_count
if not internal_response:
int_ping_count += 1
logger.warn(u"PlexPy Monitor :: Unable to get an internal response from the server, ping attempt %s." \
% str(int_ping_count))
else:
int_ping_count = 0
if plexpy.CONFIG.MONITOR_REMOTE_ACCESS:
plex_tv = plextv.PlexTV()
external_response = plex_tv.get_server_response()
global ext_ping_count
if not external_response:
ext_ping_count += 1
logger.warn(u"PlexPy Monitor :: Plex remote access port mapping failed, ping attempt %s." \
% str(ext_ping_count))
else:
host = external_response[0]['host']
port = external_response[0]['port']
try:
http_response = urllib2.urlopen('http://' + host + ':' + port)
except urllib2.HTTPError, e:
ext_ping_count = 0
except urllib2.URLError, e:
ext_ping_count += 1
logger.warn(u"PlexPy Monitor :: Unable to get an external response from the server, ping attempt %s." \
% str(ext_ping_count))
else:
ext_ping_count = 0
if int_ping_count == 3:
# Fire off notifications
threading.Thread(target=notification_handler.notify_timeline,
kwargs=dict(notify_action='intdown')).start()
if ext_ping_count == 3:
# Fire off notifications
threading.Thread(target=notification_handler.notify_timeline,
kwargs=dict(notify_action='extdown')).start()

View file

@ -279,10 +279,17 @@ class ActivityProcessor(object):
if ipv4:
# The logged IP will always be the first match and we don't want localhost entries
if ipv4[0] != '127.0.0.1':
logger.debug(u"PlexPy ActivityProcessor :: Matched IP address (%s) for stream ratingKey %s "
u"and machineIdentifier %s."
% (ipv4[0], rating_key, machine_id))
return ipv4[0]
# check if IPv4 mapped IPv6 address (::ffff:xxx.xxx.xxx.xxx)
if '::ffff:' + ipv4[0] in line:
logger.debug(u"PlexPy ActivityProcessor :: Matched IP address (%s) for stream ratingKey %s "
u"and machineIdentifier %s."
% ('::ffff:' + ipv4[0], rating_key, machine_id))
return '::ffff:' + ipv4[0]
else:
logger.debug(u"PlexPy ActivityProcessor :: Matched IP address (%s) for stream ratingKey %s "
u"and machineIdentifier %s."
% (ipv4[0], rating_key, machine_id))
return ipv4[0]
logger.debug(u"PlexPy ActivityProcessor :: Unable to find IP address on first pass. "
u"Attempting fallback check in 5 seconds...")
@ -302,9 +309,14 @@ class ActivityProcessor(object):
if ipv4:
# The logged IP will always be the first match and we don't want localhost entries
if ipv4[0] != '127.0.0.1':
logger.debug(u"PlexPy ActivityProcessor :: Matched IP address (%s) for stream ratingKey %s." %
(ipv4[0], rating_key))
return ipv4[0]
if '::ffff:' + ipv4[0] in line:
logger.debug(u"PlexPy ActivityProcessor :: Matched IP address (%s) for stream ratingKey %s." %
('::ffff:' + ipv4[0], rating_key))
return '::ffff:' + ipv4[0]
else:
logger.debug(u"PlexPy ActivityProcessor :: Matched IP address (%s) for stream ratingKey %s." %
(ipv4[0], rating_key))
return ipv4[0]
logger.debug(u"PlexPy ActivityProcessor :: Unable to find IP address on fallback search. Not logging IP address.")

View file

@ -26,6 +26,7 @@ _CONFIG_DEFINITIONS = {
'PMS_IP': (str, 'PMS', '127.0.0.1'),
'PMS_IS_REMOTE': (int, 'PMS', 0),
'PMS_LOGS_FOLDER': (str, 'PMS', ''),
'PMS_NAME': (str, 'PMS', ''),
'PMS_PORT': (int, 'PMS', 32400),
'PMS_TOKEN': (str, 'PMS', ''),
'PMS_SSL': (int, 'General', 0),
@ -45,6 +46,8 @@ _CONFIG_DEFINITIONS = {
'BOXCAR_ON_BUFFER': (int, 'Boxcar', 0),
'BOXCAR_ON_WATCHED': (int, 'Boxcar', 0),
'BOXCAR_ON_CREATED': (int, 'Boxcar', 0),
'BOXCAR_ON_EXTDOWN': (int, 'Boxcar', 0),
'BOXCAR_ON_INTDOWN': (int, 'Boxcar', 0),
'BUFFER_THRESHOLD': (int, 'Monitoring', 3),
'BUFFER_WAIT': (int, 'Monitoring', 900),
'CACHE_DIR': (str, 'General', ''),
@ -70,6 +73,8 @@ _CONFIG_DEFINITIONS = {
'EMAIL_ON_BUFFER': (int, 'Email', 0),
'EMAIL_ON_WATCHED': (int, 'Email', 0),
'EMAIL_ON_CREATED': (int, 'Email', 0),
'EMAIL_ON_EXTDOWN': (int, 'Email', 0),
'EMAIL_ON_INTDOWN': (int, 'Email', 0),
'ENABLE_HTTPS': (int, 'General', 0),
'FIRST_RUN_COMPLETE': (int, 'General', 0),
'FREEZE_DB': (int, 'General', 0),
@ -87,6 +92,8 @@ _CONFIG_DEFINITIONS = {
'GROWL_ON_BUFFER': (int, 'Growl', 0),
'GROWL_ON_WATCHED': (int, 'Growl', 0),
'GROWL_ON_CREATED': (int, 'Growl', 0),
'GROWL_ON_EXTDOWN': (int, 'Growl', 0),
'GROWL_ON_INTDOWN': (int, 'Growl', 0),
'HOME_LIBRARY_CARDS': (str, 'General', 'library_statistics_first'),
'HOME_STATS_LENGTH': (int, 'General', 30),
'HOME_STATS_TYPE': (int, 'General', 0),
@ -112,6 +119,8 @@ _CONFIG_DEFINITIONS = {
'IFTTT_ON_BUFFER': (int, 'IFTTT', 0),
'IFTTT_ON_WATCHED': (int, 'IFTTT', 0),
'IFTTT_ON_CREATED': (int, 'IFTTT', 0),
'IFTTT_ON_EXTDOWN': (int, 'IFTTT', 0),
'IFTTT_ON_INTDOWN': (int, 'IFTTT', 0),
'JOURNAL_MODE': (str, 'Advanced', 'wal'),
'LAUNCH_BROWSER': (int, 'General', 1),
'LOG_DIR': (str, 'General', ''),
@ -125,6 +134,7 @@ _CONFIG_DEFINITIONS = {
'MUSIC_NOTIFY_ON_STOP': (int, 'Monitoring', 0),
'MUSIC_NOTIFY_ON_PAUSE': (int, 'Monitoring', 0),
'MUSIC_LOGGING_ENABLE': (int, 'Monitoring', 0),
'MONITOR_REMOTE_ACCESS': (int, 'Monitoring', 0),
'MONITORING_INTERVAL': (int, 'Monitoring', 60),
'MONITORING_USE_WEBSOCKET': (int, 'Monitoring', 0),
'NMA_APIKEY': (str, 'NMA', ''),
@ -137,8 +147,11 @@ _CONFIG_DEFINITIONS = {
'NMA_ON_BUFFER': (int, 'NMA', 0),
'NMA_ON_WATCHED': (int, 'NMA', 0),
'NMA_ON_CREATED': (int, 'NMA', 0),
'NMA_ON_EXTDOWN': (int, 'NMA', 0),
'NMA_ON_INTDOWN': (int, 'NMA', 0),
'NOTIFY_CONSECUTIVE': (int, 'Monitoring', 1),
'NOTIFY_RECENTLY_ADDED_GRANDPARENT': (int, 'Monitoring', 0),
'NOTIFY_RECENTLY_ADDED_DELAY': (int, 'Monitoring', 60),
'NOTIFY_WATCHED_PERCENT': (int, 'Monitoring', 85),
'NOTIFY_ON_START_SUBJECT_TEXT': (str, 'Monitoring', 'PlexPy ({server_name})'),
'NOTIFY_ON_START_BODY_TEXT': (str, 'Monitoring', '{user} ({player}) started playing {title}.'),
@ -154,6 +167,10 @@ _CONFIG_DEFINITIONS = {
'NOTIFY_ON_WATCHED_BODY_TEXT': (str, 'Monitoring', '{user} ({player}) has watched {title}.'),
'NOTIFY_ON_CREATED_SUBJECT_TEXT': (str, 'Monitoring', 'PlexPy ({server_name})'),
'NOTIFY_ON_CREATED_BODY_TEXT': (str, 'Monitoring', '{title} was recently added to Plex.'),
'NOTIFY_ON_EXTDOWN_SUBJECT_TEXT': (str, 'Monitoring', 'PlexPy ({server_name})'),
'NOTIFY_ON_EXTDOWN_BODY_TEXT': (str, 'Monitoring', 'The Plex Media Server remote access is down.'),
'NOTIFY_ON_INTDOWN_SUBJECT_TEXT': (str, 'Monitoring', 'PlexPy ({server_name})'),
'NOTIFY_ON_INTDOWN_BODY_TEXT': (str, 'Monitoring', 'The Plex Media Server is down.'),
'OSX_NOTIFY_APP': (str, 'OSX_Notify', '/Applications/PlexPy'),
'OSX_NOTIFY_ENABLED': (int, 'OSX_Notify', 0),
'OSX_NOTIFY_ON_PLAY': (int, 'OSX_Notify', 0),
@ -163,6 +180,8 @@ _CONFIG_DEFINITIONS = {
'OSX_NOTIFY_ON_BUFFER': (int, 'OSX_Notify', 0),
'OSX_NOTIFY_ON_WATCHED': (int, 'OSX_Notify', 0),
'OSX_NOTIFY_ON_CREATED': (int, 'OSX_Notify', 0),
'OSX_NOTIFY_ON_EXTDOWN': (int, 'OSX_Notify', 0),
'OSX_NOTIFY_ON_INTDOWN': (int, 'OSX_Notify', 0),
'PLEX_CLIENT_HOST': (str, 'Plex', ''),
'PLEX_ENABLED': (int, 'Plex', 0),
'PLEX_PASSWORD': (str, 'Plex', ''),
@ -174,6 +193,8 @@ _CONFIG_DEFINITIONS = {
'PLEX_ON_BUFFER': (int, 'Plex', 0),
'PLEX_ON_WATCHED': (int, 'Plex', 0),
'PLEX_ON_CREATED': (int, 'Plex', 0),
'PLEX_ON_EXTDOWN': (int, 'Plex', 0),
'PLEX_ON_INTDOWN': (int, 'Plex', 0),
'PROWL_ENABLED': (int, 'Prowl', 0),
'PROWL_KEYS': (str, 'Prowl', ''),
'PROWL_PRIORITY': (int, 'Prowl', 0),
@ -184,6 +205,8 @@ _CONFIG_DEFINITIONS = {
'PROWL_ON_BUFFER': (int, 'Prowl', 0),
'PROWL_ON_WATCHED': (int, 'Prowl', 0),
'PROWL_ON_CREATED': (int, 'Prowl', 0),
'PROWL_ON_EXTDOWN': (int, 'Prowl', 0),
'PROWL_ON_INTDOWN': (int, 'Prowl', 0),
'PUSHALOT_APIKEY': (str, 'Pushalot', ''),
'PUSHALOT_ENABLED': (int, 'Pushalot', 0),
'PUSHALOT_ON_PLAY': (int, 'Pushalot', 0),
@ -193,6 +216,8 @@ _CONFIG_DEFINITIONS = {
'PUSHALOT_ON_BUFFER': (int, 'Pushalot', 0),
'PUSHALOT_ON_WATCHED': (int, 'Pushalot', 0),
'PUSHALOT_ON_CREATED': (int, 'Pushalot', 0),
'PUSHALOT_ON_EXTDOWN': (int, 'Pushalot', 0),
'PUSHALOT_ON_INTDOWN': (int, 'Pushalot', 0),
'PUSHBULLET_APIKEY': (str, 'PushBullet', ''),
'PUSHBULLET_DEVICEID': (str, 'PushBullet', ''),
'PUSHBULLET_CHANNEL_TAG': (str, 'PushBullet', ''),
@ -204,6 +229,8 @@ _CONFIG_DEFINITIONS = {
'PUSHBULLET_ON_BUFFER': (int, 'PushBullet', 0),
'PUSHBULLET_ON_WATCHED': (int, 'PushBullet', 0),
'PUSHBULLET_ON_CREATED': (int, 'PushBullet', 0),
'PUSHBULLET_ON_EXTDOWN': (int, 'PushBullet', 0),
'PUSHBULLET_ON_INTDOWN': (int, 'PushBullet', 0),
'PUSHOVER_APITOKEN': (str, 'Pushover', ''),
'PUSHOVER_ENABLED': (int, 'Pushover', 0),
'PUSHOVER_KEYS': (str, 'Pushover', ''),
@ -216,6 +243,8 @@ _CONFIG_DEFINITIONS = {
'PUSHOVER_ON_BUFFER': (int, 'Pushover', 0),
'PUSHOVER_ON_WATCHED': (int, 'Pushover', 0),
'PUSHOVER_ON_CREATED': (int, 'Pushover', 0),
'PUSHOVER_ON_EXTDOWN': (int, 'Pushover', 0),
'PUSHOVER_ON_INTDOWN': (int, 'Pushover', 0),
'REFRESH_USERS_INTERVAL': (int, 'Monitoring', 12),
'REFRESH_USERS_ON_STARTUP': (int, 'Monitoring', 1),
'TELEGRAM_BOT_TOKEN': (str, 'Telegram', ''),
@ -228,6 +257,8 @@ _CONFIG_DEFINITIONS = {
'TELEGRAM_ON_BUFFER': (int, 'Telegram', 0),
'TELEGRAM_ON_WATCHED': (int, 'Telegram', 0),
'TELEGRAM_ON_CREATED': (int, 'Telegram', 0),
'TELEGRAM_ON_EXTDOWN': (int, 'Telegram', 0),
'TELEGRAM_ON_INTDOWN': (int, 'Telegram', 0),
'TV_NOTIFY_ENABLE': (int, 'Monitoring', 0),
'TV_NOTIFY_ON_START': (int, 'Monitoring', 1),
'TV_NOTIFY_ON_STOP': (int, 'Monitoring', 0),
@ -243,6 +274,8 @@ _CONFIG_DEFINITIONS = {
'TWITTER_ON_BUFFER': (int, 'Twitter', 0),
'TWITTER_ON_WATCHED': (int, 'Twitter', 0),
'TWITTER_ON_CREATED': (int, 'Twitter', 0),
'TWITTER_ON_EXTDOWN': (int, 'Twitter', 0),
'TWITTER_ON_INTDOWN': (int, 'Twitter', 0),
'UPDATE_DB_INTERVAL': (int, 'General', 24),
'VERIFY_SSL_CERT': (bool_int, 'Advanced', 1),
'VIDEO_LOGGING_ENABLE': (int, 'Monitoring', 1),
@ -256,7 +289,9 @@ _CONFIG_DEFINITIONS = {
'XBMC_ON_RESUME': (int, 'XBMC', 0),
'XBMC_ON_BUFFER': (int, 'XBMC', 0),
'XBMC_ON_WATCHED': (int, 'XBMC', 0),
'XBMC_ON_CREATED': (int, 'XBMC', 0)
'XBMC_ON_CREATED': (int, 'XBMC', 0),
'XBMC_ON_EXTDOWN': (int, 'XBMC', 0),
'XBMC_ON_INTDOWN': (int, 'XBMC', 0)
}
# pylint:disable=R0902
# it might be nice to refactor for fewer instance variables

View file

@ -41,7 +41,7 @@ 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 user ELSE users.friendly_name END) as friendly_name',
'(CASE WHEN users.friendly_name IS NULL THEN users.username ELSE users.friendly_name END) as friendly_name',
'platform',
'player',
'ip_address',
@ -437,7 +437,7 @@ class DataFactory(object):
top_users = []
try:
query = 'SELECT session_history.user, ' \
'(case when users.friendly_name is null then session_history.user else ' \
'(case when users.friendly_name is null then users.username else ' \
'users.friendly_name end) as friendly_name,' \
'COUNT(session_history.id) as total_plays, ' \
'SUM(case when session_history.stopped > 0 ' \
@ -536,7 +536,7 @@ class DataFactory(object):
try:
query = 'SELECT session_history_metadata.id, ' \
'session_history.user, ' \
'(case when users.friendly_name is null then session_history.user else ' \
'(case when users.friendly_name is null then users.username else ' \
'users.friendly_name end) as friendly_name,' \
'users.user_id, ' \
'users.custom_avatar_url as user_thumb, ' \

View file

@ -410,7 +410,7 @@ class Graphs(object):
if y_axis == 'plays':
query = 'SELECT ' \
'(case when users.friendly_name is null then session_history.user else ' \
'(case when users.friendly_name is null 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, ' \
@ -427,7 +427,7 @@ class Graphs(object):
result = monitor_db.select(query)
else:
query = 'SELECT ' \
'(case when users.friendly_name is null then session_history.user else ' \
'(case when users.friendly_name is null 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_duration, ' \

View file

@ -1,4 +1,4 @@
# This file is part of PlexPy.
# This file is part of PlexPy.
#
# PlexPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -144,6 +144,31 @@ def now():
now = datetime.datetime.now()
return now.strftime("%Y-%m-%d %H:%M:%S")
def human_duration(s):
hd = ''
if str(s).isdigit():
d = int(s / 84600)
h = int((s % 84600) / 3600)
m = int(((s % 84600) % 3600) / 60)
s = int(((s % 84600) % 3600) % 60)
hd_list = []
if d > 0:
hd_list.append(str(d) + ' days')
if h > 0:
hd_list.append(str(h) + ' hrs')
if m > 0:
hd_list.append(str(m) + ' mins')
if s > 0:
hd_list.append(str(s) + ' secs')
hd = ' '.join(hd_list)
return hd
else:
return hd
def get_age(date):

View file

@ -13,7 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with PlexPy. If not, see <http://www.gnu.org/licenses/>.
from plexpy import logger, config, notifiers, database, helpers
from plexpy import logger, config, notifiers, database, helpers, plextv, pmsconnect
import plexpy
import time
@ -172,17 +172,27 @@ def notify_timeline(timeline_data=None, notify_action=None):
if timeline_data and notify_action:
for agent in notifiers.available_notification_agents():
if agent['on_created'] and notify_action == 'created':
if (plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_GRANDPARENT \
and (timeline_data['media_type'] == 'movie' or timeline_data['media_type'] == 'show' or timeline_data['media_type'] == 'artist')) \
or (not plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_GRANDPARENT \
and (timeline_data['media_type'] == 'movie' or timeline_data['media_type'] == 'episode' or timeline_data['media_type'] == 'track')):
# Build and send notification
notify_strings = build_notify_text(timeline=timeline_data, state=notify_action)
notifiers.send_notification(config_id=agent['id'],
subject=notify_strings[0],
body=notify_strings[1])
# Set the notification state in the db
set_notify_state(session=timeline_data, state=notify_action, agent_info=agent)
# Build and send notification
notify_strings = build_notify_text(timeline=timeline_data, state=notify_action)
notifiers.send_notification(config_id=agent['id'],
subject=notify_strings[0],
body=notify_strings[1])
# Set the notification state in the db
set_notify_state(session=timeline_data, state=notify_action, agent_info=agent)
elif not timeline_data and notify_action:
for agent in notifiers.available_notification_agents():
if agent['on_extdown'] and notify_action == 'extdown':
# Build and send notification
notify_strings = build_server_notify_text(state=notify_action)
notifiers.send_notification(config_id=agent['id'],
subject=notify_strings[0],
body=notify_strings[1])
if agent['on_intdown'] and notify_action == 'intdown':
# Build and send notification
notify_strings = build_server_notify_text(state=notify_action)
notifiers.send_notification(config_id=agent['id'],
subject=notify_strings[0],
body=notify_strings[1])
else:
logger.debug(u"PlexPy Notifier :: Notify timeline called but incomplete data received.")
@ -265,19 +275,21 @@ def set_notify_state(session, state, agent_info):
def build_notify_text(session=None, timeline=None, state=None):
from plexpy import pmsconnect, helpers
import re
# Get the server name
pms_connect = pmsconnect.PmsConnect()
server_name = pms_connect.get_server_pref(pref='FriendlyName')
# If friendly name is blank
if not server_name:
servers_info = pms_connect.get_servers_info()
for server in servers_info:
if server['machine_identifier'] == plexpy.CONFIG.PMS_IDENTIFIER:
server_name = server['name']
break
server_name = plexpy.CONFIG.PMS_NAME
# Get the server uptime
plex_tv = plextv.PlexTV()
server_times = plex_tv.get_server_times()
if server_times:
updated_at = server_times[0]['updated_at']
server_uptime = helpers.human_duration(int(time.time() - helpers.cast_to_float(updated_at)))
else:
logger.error(u"PlexPy Notifier :: Unable to retrieve server uptime.")
server_uptime = 'N/A'
# Get metadata feed for item
if session:
@ -285,6 +297,7 @@ def build_notify_text(session=None, timeline=None, state=None):
elif timeline:
rating_key = timeline['rating_key']
pms_connect = pmsconnect.PmsConnect()
metadata_list = pms_connect.get_metadata_details(rating_key=rating_key)
if metadata_list:
@ -294,20 +307,22 @@ def build_notify_text(session=None, timeline=None, state=None):
return []
# Check for exclusion tags
if metadata['media_type'] == 'episode':
# Regex pattern to remove the text in the tags we don't want
pattern = re.compile('<movie>[^>]+.</movie>|<music>[^>]+.</music>', re.IGNORECASE)
elif metadata['media_type'] == 'movie':
if metadata['media_type'] == 'movie':
# Regex pattern to remove the text in the tags we don't want
pattern = re.compile('<tv>[^>]+.</tv>|<music>[^>]+.</music>', re.IGNORECASE)
elif metadata['media_type'] == 'track':
elif metadata['media_type'] == 'show' or metadata['media_type'] == 'episode':
# Regex pattern to remove the text in the tags we don't want
pattern = re.compile('<movie>[^>]+.</movie>|<music>[^>]+.</music>', re.IGNORECASE)
elif metadata['media_type'] == 'artist' or metadata['media_type'] == 'track':
# Regex pattern to remove the text in the tags we don't want
pattern = re.compile('<tv>[^>]+.</tv>|<movie>[^>]+.</movie>', re.IGNORECASE)
else:
pattern = None
if metadata['media_type'] == 'episode' or metadata['media_type'] == 'movie' or metadata['media_type'] == 'track' \
and pattern:
if metadata['media_type'] == 'movie' \
or metadata['media_type'] == 'show' or metadata['media_type'] == 'episode' \
or metadata['media_type'] == 'artist' or metadata['media_type'] == 'track' \
and pattern:
# Remove the unwanted tags and strip any unmatch tags too.
on_start_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT))
on_start_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT))
@ -389,6 +404,7 @@ def build_notify_text(session=None, timeline=None, state=None):
progress_percent = helpers.get_percent(view_offset, duration)
available_params = {'server_name': server_name,
'server_uptime': server_uptime,
'user': user,
'platform': platform,
'player': player,
@ -594,6 +610,78 @@ def build_notify_text(session=None, timeline=None, state=None):
else:
return None
def build_server_notify_text(state=None):
# Get the server name
server_name = plexpy.CONFIG.PMS_NAME
# Get the server uptime
plex_tv = plextv.PlexTV()
server_times = plex_tv.get_server_times()
if server_times:
updated_at = server_times[0]['updated_at']
server_uptime = helpers.human_duration(int(time.time() - helpers.cast_to_float(updated_at)))
else:
logger.error(u"PlexPy Notifier :: Unable to retrieve server uptime.")
server_uptime = 'N/A'
on_extdown_subject = plexpy.CONFIG.NOTIFY_ON_EXTDOWN_SUBJECT_TEXT
on_extdown_body = plexpy.CONFIG.NOTIFY_ON_EXTDOWN_BODY_TEXT
on_intdown_subject = plexpy.CONFIG.NOTIFY_ON_INTDOWN_SUBJECT_TEXT
on_intdown_body = plexpy.CONFIG.NOTIFY_ON_INTDOWN_BODY_TEXT
available_params = {'server_name': server_name,
'server_uptime': server_uptime}
# Default text
subject_text = 'PlexPy (%s)' % server_name
if state == 'extdown':
# Default body text
body_text = 'The Plex Media Server remote access is down.'
if on_extdown_subject and on_extdown_body:
try:
subject_text = unicode(on_extdown_subject).format(**available_params)
except LookupError, e:
logger.error(u"PlexPy Notifier :: Unable to parse field %s in notification subject. Using fallback." % e)
except:
logger.error(u"PlexPy Notifier :: Unable to parse custom notification subject. Using fallback.")
try:
body_text = unicode(on_extdown_body).format(**available_params)
except LookupError, e:
logger.error(u"PlexPy Notifier :: Unable to parse field %s in notification body. Using fallback." % e)
except:
logger.error(u"PlexPy Notifier :: Unable to parse custom notification body. Using fallback.")
return [subject_text, body_text]
else:
return [subject_text, body_text]
elif state == 'intdown':
# Default body text
body_text = 'The Plex Media Server is down.'
if on_intdown_subject and on_intdown_body:
try:
subject_text = unicode(on_intdown_subject).format(**available_params)
except LookupError, e:
logger.error(u"PlexPy Notifier :: Unable to parse field %s in notification subject. Using fallback." % e)
except:
logger.error(u"PlexPy Notifier :: Unable to parse custom notification subject. Using fallback.")
try:
body_text = unicode(on_intdown_body).format(**available_params)
except LookupError, e:
logger.error(u"PlexPy Notifier :: Unable to parse field %s in notification body. Using fallback." % e)
except:
logger.error(u"PlexPy Notifier :: Unable to parse custom notification body. Using fallback.")
return [subject_text, body_text]
else:
return [subject_text, body_text]
else:
return None
def strip_tag(data):
import re

View file

@ -66,7 +66,9 @@ def available_notification_agents():
'on_resume': plexpy.CONFIG.GROWL_ON_RESUME,
'on_buffer': plexpy.CONFIG.GROWL_ON_BUFFER,
'on_watched': plexpy.CONFIG.GROWL_ON_WATCHED,
'on_created': plexpy.CONFIG.GROWL_ON_CREATED
'on_created': plexpy.CONFIG.GROWL_ON_CREATED,
'on_extdown': plexpy.CONFIG.GROWL_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.GROWL_ON_INTDOWN
},
{'name': 'Prowl',
'id': AGENT_IDS['Prowl'],
@ -79,7 +81,9 @@ def available_notification_agents():
'on_resume': plexpy.CONFIG.PROWL_ON_RESUME,
'on_buffer': plexpy.CONFIG.PROWL_ON_BUFFER,
'on_watched': plexpy.CONFIG.PROWL_ON_WATCHED,
'on_created': plexpy.CONFIG.PROWL_ON_CREATED
'on_created': plexpy.CONFIG.PROWL_ON_CREATED,
'on_extdown': plexpy.CONFIG.PROWL_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.PROWL_ON_INTDOWN
},
{'name': 'XBMC',
'id': AGENT_IDS['XBMC'],
@ -92,7 +96,9 @@ def available_notification_agents():
'on_resume': plexpy.CONFIG.XBMC_ON_RESUME,
'on_buffer': plexpy.CONFIG.XBMC_ON_BUFFER,
'on_watched': plexpy.CONFIG.XBMC_ON_WATCHED,
'on_created': plexpy.CONFIG.XBMC_ON_CREATED
'on_created': plexpy.CONFIG.XBMC_ON_CREATED,
'on_extdown': plexpy.CONFIG.XBMC_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.XBMC_ON_INTDOWN
},
{'name': 'Plex',
'id': AGENT_IDS['Plex'],
@ -105,7 +111,9 @@ def available_notification_agents():
'on_resume': plexpy.CONFIG.PLEX_ON_RESUME,
'on_buffer': plexpy.CONFIG.PLEX_ON_BUFFER,
'on_watched': plexpy.CONFIG.PLEX_ON_WATCHED,
'on_created': plexpy.CONFIG.PLEX_ON_CREATED
'on_created': plexpy.CONFIG.PLEX_ON_CREATED,
'on_extdown': plexpy.CONFIG.PLEX_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.PLEX_ON_INTDOWN
},
{'name': 'NotifyMyAndroid',
'id': AGENT_IDS['NMA'],
@ -118,7 +126,9 @@ def available_notification_agents():
'on_resume': plexpy.CONFIG.NMA_ON_RESUME,
'on_buffer': plexpy.CONFIG.NMA_ON_BUFFER,
'on_watched': plexpy.CONFIG.NMA_ON_WATCHED,
'on_created': plexpy.CONFIG.NMA_ON_CREATED
'on_created': plexpy.CONFIG.NMA_ON_CREATED,
'on_extdown': plexpy.CONFIG.NMA_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.NMA_ON_INTDOWN
},
{'name': 'Pushalot',
'id': AGENT_IDS['Pushalot'],
@ -131,7 +141,9 @@ def available_notification_agents():
'on_resume': plexpy.CONFIG.PUSHALOT_ON_RESUME,
'on_buffer': plexpy.CONFIG.PUSHALOT_ON_BUFFER,
'on_watched': plexpy.CONFIG.PUSHALOT_ON_WATCHED,
'on_created': plexpy.CONFIG.PUSHALOT_ON_CREATED
'on_created': plexpy.CONFIG.PUSHALOT_ON_CREATED,
'on_extdown': plexpy.CONFIG.PUSHALOT_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.PUSHALOT_ON_INTDOWN
},
{'name': 'Pushbullet',
'id': AGENT_IDS['Pushbullet'],
@ -144,7 +156,9 @@ def available_notification_agents():
'on_resume': plexpy.CONFIG.PUSHBULLET_ON_RESUME,
'on_buffer': plexpy.CONFIG.PUSHBULLET_ON_BUFFER,
'on_watched': plexpy.CONFIG.PUSHBULLET_ON_WATCHED,
'on_created': plexpy.CONFIG.PUSHBULLET_ON_CREATED
'on_created': plexpy.CONFIG.PUSHBULLET_ON_CREATED,
'on_extdown': plexpy.CONFIG.PUSHBULLET_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.PUSHBULLET_ON_INTDOWN
},
{'name': 'Pushover',
'id': AGENT_IDS['Pushover'],
@ -157,7 +171,9 @@ def available_notification_agents():
'on_resume': plexpy.CONFIG.PUSHOVER_ON_RESUME,
'on_buffer': plexpy.CONFIG.PUSHOVER_ON_BUFFER,
'on_watched': plexpy.CONFIG.PUSHOVER_ON_WATCHED,
'on_created': plexpy.CONFIG.PUSHOVER_ON_CREATED
'on_created': plexpy.CONFIG.PUSHOVER_ON_CREATED,
'on_extdown': plexpy.CONFIG.PUSHOVER_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.PUSHOVER_ON_INTDOWN
},
{'name': 'Boxcar2',
'id': AGENT_IDS['Boxcar2'],
@ -170,7 +186,9 @@ def available_notification_agents():
'on_resume': plexpy.CONFIG.BOXCAR_ON_RESUME,
'on_buffer': plexpy.CONFIG.BOXCAR_ON_BUFFER,
'on_watched': plexpy.CONFIG.BOXCAR_ON_WATCHED,
'on_created': plexpy.CONFIG.BOXCAR_ON_CREATED
'on_created': plexpy.CONFIG.BOXCAR_ON_CREATED,
'on_extdown': plexpy.CONFIG.BOXCAR_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.BOXCAR_ON_INTDOWN
},
{'name': 'E-mail',
'id': AGENT_IDS['Email'],
@ -183,7 +201,9 @@ def available_notification_agents():
'on_resume': plexpy.CONFIG.EMAIL_ON_RESUME,
'on_buffer': plexpy.CONFIG.EMAIL_ON_BUFFER,
'on_watched': plexpy.CONFIG.EMAIL_ON_WATCHED,
'on_created': plexpy.CONFIG.EMAIL_ON_CREATED
'on_created': plexpy.CONFIG.EMAIL_ON_CREATED,
'on_extdown': plexpy.CONFIG.EMAIL_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.EMAIL_ON_INTDOWN
},
{'name': 'Twitter',
'id': AGENT_IDS['Twitter'],
@ -196,7 +216,9 @@ def available_notification_agents():
'on_resume': plexpy.CONFIG.TWITTER_ON_RESUME,
'on_buffer': plexpy.CONFIG.TWITTER_ON_BUFFER,
'on_watched': plexpy.CONFIG.TWITTER_ON_WATCHED,
'on_created': plexpy.CONFIG.TWITTER_ON_CREATED
'on_created': plexpy.CONFIG.TWITTER_ON_CREATED,
'on_extdown': plexpy.CONFIG.TWITTER_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.TWITTER_ON_INTDOWN
},
{'name': 'IFTTT',
'id': AGENT_IDS['IFTTT'],
@ -209,7 +231,9 @@ def available_notification_agents():
'on_resume': plexpy.CONFIG.IFTTT_ON_RESUME,
'on_buffer': plexpy.CONFIG.IFTTT_ON_BUFFER,
'on_watched': plexpy.CONFIG.IFTTT_ON_WATCHED,
'on_created': plexpy.CONFIG.IFTTT_ON_CREATED
'on_created': plexpy.CONFIG.IFTTT_ON_CREATED,
'on_extdown': plexpy.CONFIG.IFTTT_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.IFTTT_ON_INTDOWN
},
{'name': 'Telegram',
'id': AGENT_IDS['Telegram'],
@ -222,7 +246,9 @@ def available_notification_agents():
'on_resume': plexpy.CONFIG.TELEGRAM_ON_RESUME,
'on_buffer': plexpy.CONFIG.TELEGRAM_ON_BUFFER,
'on_watched': plexpy.CONFIG.TELEGRAM_ON_WATCHED,
'on_created': plexpy.CONFIG.TELEGRAM_ON_CREATED
'on_created': plexpy.CONFIG.TELEGRAM_ON_CREATED,
'on_extdown': plexpy.CONFIG.TELEGRAM_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.TELEGRAM_ON_INTDOWN
}
]
@ -240,7 +266,9 @@ def available_notification_agents():
'on_resume': plexpy.CONFIG.OSX_NOTIFY_ON_RESUME,
'on_buffer': plexpy.CONFIG.OSX_NOTIFY_ON_BUFFER,
'on_watched': plexpy.CONFIG.OSX_NOTIFY_ON_WATCHED,
'on_created': plexpy.CONFIG.OSX_NOTIFY_ON_CREATED
'on_created': plexpy.CONFIG.OSX_NOTIFY_ON_CREATED,
'on_extdown': plexpy.CONFIG.OSX_NOTIFY_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.OSX_NOTIFY_ON_INTDOWN
})
return agents
@ -1320,6 +1348,8 @@ class Email(object):
mailserver.sendmail(plexpy.CONFIG.EMAIL_FROM, plexpy.CONFIG.EMAIL_TO, message.as_string())
mailserver.quit()
logger.info(u"Email notifications sent.")
return True
except Exception, e:

View file

@ -444,3 +444,41 @@ class PlexTV(object):
return clean_servers
return json.dumps(clean_servers, indent=4)
def get_server_times(self):
servers = self.get_plextv_server_list(output_format='xml')
server_times = []
try:
xml_head = servers.getElementsByTagName('Server')
except:
logger.warn("Error parsing XML for Plex servers.")
return []
for a in xml_head:
if helpers.get_xml_attr(a, 'machineIdentifier') == plexpy.CONFIG.PMS_IDENTIFIER:
server_times.append({"created_at": helpers.get_xml_attr(a, 'createdAt'),
"updated_at": helpers.get_xml_attr(a, 'updatedAt')
})
break
return server_times
def get_server_response(self):
response = self.get_plextv_server_list(output_format='xml')
server_url = []
try:
xml_head = response.getElementsByTagName('Server')
except:
return False
for a in xml_head:
if helpers.get_xml_attr(a, 'machineIdentifier') == plexpy.CONFIG.PMS_IDENTIFIER:
server_url.append({"host": helpers.get_xml_attr(a, 'host'),
"port": helpers.get_xml_attr(a, 'port')
})
break
return server_url

View file

@ -19,6 +19,23 @@ from urlparse import urlparse
import plexpy
import urllib2
def get_server_friendly_name():
logger.info("Requesting name from server...")
server_name = PmsConnect().get_server_pref(pref='FriendlyName')
# If friendly name is blank
if not server_name:
servers_info = PmsConnect().get_servers_info()
for server in servers_info:
if server['machine_identifier'] == plexpy.CONFIG.PMS_IDENTIFIER:
server_name = server['name']
break
if server_name and server_name != plexpy.CONFIG.PMS_NAME:
plexpy.CONFIG.__setattr__('PMS_NAME', server_name)
plexpy.CONFIG.write()
return server_name
class PmsConnect(object):
"""
@ -1631,4 +1648,17 @@ class PmsConnect(object):
'children': parents}
}
return key_list
return key_list
"""
Check for a server response.
Output: bool
"""
def get_server_response(self):
response = self.get_server_list()
if not response:
return False
else:
return True

View file

@ -139,14 +139,14 @@ def process(opcode, data):
activity = activity_handler.ActivityHandler(timeline=time_line[0])
activity.process()
if type == 'timeline':
try:
time_line = info.get('_children')
except:
logger.debug(u"PlexPy WebSocket :: Timeline event found but unable to get timeline data.")
return False
#if type == 'timeline':
# try:
# time_line = info.get('_children')
# except:
# logger.debug(u"PlexPy WebSocket :: Timeline event found but unable to get timeline data.")
# return False
activity = activity_handler.TimelineHandler(timeline=time_line[0])
activity.process()
# activity = activity_handler.TimelineHandler(timeline=time_line[0])
# activity.process()
return True

View file

@ -46,9 +46,11 @@ def serve_template(templatename, **kwargs):
_hplookup = TemplateLookup(directories=[template_dir])
server_name = plexpy.CONFIG.PMS_NAME
try:
template = _hplookup.get_template(templatename)
return template.render(**kwargs)
return template.render(server_name=server_name, **kwargs)
except:
return exceptions.html_error_template().render()
@ -71,7 +73,8 @@ class WebInterface(object):
"home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH,
"home_stats_cards": plexpy.CONFIG.HOME_STATS_CARDS,
"home_library_cards": plexpy.CONFIG.HOME_LIBRARY_CARDS,
"pms_identifier": plexpy.CONFIG.PMS_IDENTIFIER
"pms_identifier": plexpy.CONFIG.PMS_IDENTIFIER,
"pms_name": plexpy.CONFIG.PMS_NAME
}
return serve_template(templatename="index.html", title="Home", config=config)
@ -432,6 +435,7 @@ class WebInterface(object):
"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),
"monitor_remote_access": checked(plexpy.CONFIG.MONITOR_REMOTE_ACCESS),
"monitoring_interval": plexpy.CONFIG.MONITORING_INTERVAL,
"monitoring_use_websocket": checked(plexpy.CONFIG.MONITORING_USE_WEBSOCKET),
"refresh_users_interval": plexpy.CONFIG.REFRESH_USERS_INTERVAL,
@ -443,6 +447,7 @@ class WebInterface(object):
"pms_is_remote": checked(plexpy.CONFIG.PMS_IS_REMOTE),
"notify_consecutive": checked(plexpy.CONFIG.NOTIFY_CONSECUTIVE),
"notify_recently_added_grandparent": checked(plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_GRANDPARENT),
"notify_recently_added_delay": plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_DELAY,
"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,
@ -458,6 +463,10 @@ class WebInterface(object):
"notify_on_watched_body_text": plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT,
"notify_on_created_subject_text": plexpy.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT,
"notify_on_created_body_text": plexpy.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT,
"notify_on_extdown_subject_text": plexpy.CONFIG.NOTIFY_ON_EXTDOWN_SUBJECT_TEXT,
"notify_on_extdown_body_text": plexpy.CONFIG.NOTIFY_ON_EXTDOWN_BODY_TEXT,
"notify_on_intdown_subject_text": plexpy.CONFIG.NOTIFY_ON_INTDOWN_SUBJECT_TEXT,
"notify_on_intdown_body_text": plexpy.CONFIG.NOTIFY_ON_INTDOWN_BODY_TEXT,
"home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH,
"home_stats_type": checked(plexpy.CONFIG.HOME_STATS_TYPE),
"home_stats_count": plexpy.CONFIG.HOME_STATS_COUNT,
@ -482,7 +491,7 @@ class WebInterface(object):
"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", "video_logging_enable", "music_logging_enable", "pms_is_remote", "home_stats_type",
"group_history_tables", "notify_consecutive", "notify_recently_added_grandparent"
"group_history_tables", "notify_consecutive", "notify_recently_added_grandparent", "monitor_remote_access"
]
for checked_config in checked_configs:
if checked_config not in kwargs:
@ -1137,10 +1146,24 @@ class WebInterface(object):
logger.warn('Unable to retrieve data.')
@cherrypy.expose
def get_server_prefs(self, **kwargs):
def get_server_friendly_name(self, **kwargs):
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_server_prefs(output_format='json')
result = pmsconnect.get_server_friendly_name()
if result:
cherrypy.response.headers['Content-type'] = 'application/json'
return result
else:
logger.warn('Unable to retrieve data.')
@cherrypy.expose
def get_server_prefs(self, pref=None, **kwargs):
if pref:
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_server_pref(pref=pref)
else:
result = None
if result:
cherrypy.response.headers['Content-type'] = 'application/json'