Merge branch 'dev'

This commit is contained in:
JonnyWong16 2016-03-12 15:04:40 -08:00
commit 08537c1d69
50 changed files with 1904 additions and 784 deletions

View file

@ -1,9 +1,39 @@
# Changelog
## v1.3.10 (2016-03-12)
* Fix: Actually allow HTML tags for Pushover.
* Fix: PlexPy not restarting on Windows if there is a space in the folder path.
* Fix: Reconnect websocket when changing PMS SSL setting.
* Fix: Datatables not loading when view_offset or duration is blank.
* Fix: Bug when checking the PMS version in the settings.
* Fix: Auto-refreshing of log tables.
* Fix: Logging of IPv6 addresses. (PMS version >0.9.14 only.)
* Fix: Hide days selection from the Play Totals graph page.
* Fix: PlexPy overwriting user's own SSL certificate/key.
* Fix: Multiple watched notifications when using websocket.
* Fix: Some missing python library imports.
* Fix: Some typos in settings and PlexWatch importer.
* New: Ability to get notified of PMS updates.
* New: Ability to disable the link to Plex Web with Facebook notifications and use IMDB, TVDB, TMDb, or Last.fm instead.
* New: Ability to reset Imgur poster url from the info page if the poster is changed.
* New: Tooltips on the current activity progress bars.
* New: Side scrolling of Recently Added/Recently Played items.
* New: Document all date/time format options.
* New: Button to clear notification logs.
* New: Customizable backup, cache, and log directories.
* Change: Retry writing sessions to history if it fails, so sessions don't get lost. (Activity pinger only, not availble for websocket.)
* Change: Save any unknown sessions to the "Local" user.
* Change: History table modal is filtered depending on which graph series is clicked.
* Change: Revert back to saving the state of datatables (search, sorting, entries per page, etc.).
* Change: Newlines are not longer stripped from notification text which allows for finer control of how notifications look.
* Change: Updated FreeNAS/FreeBSD init scripts. (Must have updated jails.) (Thanks @chiviak)
## v1.3.9 (2016-02-21)
* Fix: Recently added notification not sent to all notification agents.
* Add: Pushover HTML support. (Thanks @elseym)
* New: Pushover HTML support. (Thanks @elseym)
## v1.3.8 (2016-02-21)
@ -12,8 +42,8 @@
* Fix: Remove media tags from script arguments for server notifications.
* Fix: Encode poster titles to UTF-8 for Imgur upload.
* Fix: Allow notifications to send without poster if Imgur upload fails.
* Add: Notification Logs table in the Logs tab.
* Add: Toggle in settings to enable posters in notifications. (Disabled by default.)
* New: Notification Logs table in the Logs tab.
* New: Toggle in settings to enable posters in notifications. (Disabled by default.)
* Change: Save Imgur poster URL to database so upload is not needed every time.
* Change: Notify log in database to log each event as a separate entry.
* Change: Monitor remote access is unchecked if remote access is disabled on server.
@ -26,18 +56,18 @@
* Fix: Video metadata flags showing up for track info.
* Fix: Custom library icons not applied to Library Statistics.
* Fix: Typos in the Web UI.
* Add: ETA to Current Activity overlay.
* Add: Total duration to Libraries and Users tables.
* Add: {machine_id} to notification options.
* Add: IMDB, TVDB, TMDb, Last.fm, and Trackt IDs/URLs to notification options.
* Add: {poster_url} to notification options using Imgur.
* Add: Poster and link for Facebook notifications.
* Add: Log javascript errors from the Web UI.
* Add: Configuration and Scheduler info to the settings page.
* Add: Schedule background task to backup the PlexPy database.
* Add: URL anonymizer for external links.
* Add: Plex Media Scanner log file to Log viewer.
* Add: API v2 (sill very experimental). (Thanks @Hellowlol)
* New: ETA to Current Activity overlay.
* New: Total duration to Libraries and Users tables.
* New: {machine_id} to notification options.
* New: IMDB, TVDB, TMDb, Last.fm, and Trackt IDs/URLs to notification options.
* New: {poster_url} to notification options using Imgur.
* New: Poster and link for Facebook notifications.
* New: Log javascript errors from the Web UI.
* New: Configuration and Scheduler info to the settings page.
* New: Schedule background task to backup the PlexPy database.
* New: URL anonymizer for external links.
* New: Plex Media Scanner log file to Log viewer.
* New: API v2 (sill very experimental). (Thanks @Hellowlol)
* Change: Allow secure websocket connections.
* Change: History grouping now accounts for the view offset.
* Change: Subject line can be toggled off for Facebook, Slack, Telegram, and Twitter.
@ -60,9 +90,9 @@
* Fix: Unable to expand media info table when missing "Added At" date.
* Fix: Server verification for unpublished servers.
* Fix: Updating PMS identifier for server change.
* Add: {stream_time}, {remaining_time}, and {progress_time} to notification options.
* Add: Powershell script support. (Thanks @Hellowlol)
* Add: Method to delete duplicate libraries.
* New: {stream_time}, {remaining_time}, and {progress_time} to notification options.
* New: Powershell script support. (Thanks @Hellowlol)
* New: Method to delete duplicate libraries.
* Change: Daemonize before running start up tasks.
@ -74,7 +104,7 @@
* Fix: Libraries and Users lists not refreshing.
* Fix: Server verification in settings.
* Fix: Empty libraries not added to database.
* Add: Unique identifiers to notification options.
* New: Unique identifiers to notification options.
* Remove: Requirement of media type toggles for recently added notifications.
* Remove: Built in Twitter key and secret.
* Change: Unnecessary quoting of script arguments.
@ -107,21 +137,21 @@
## v1.3.0 (2016-01-23)
* Add: Brand new Libraries section.
* Add: Lots of new library statistics.
* Add: Media info table for libraries.
* Add: Web app for Android and iOS. (Thanks @zobe123)
* Add: Slack notification agent. (Thanks @richipargo)
* Add: Facebook notification agent.
* Add: Custom script notification agent. (Thanks @Hellowlol)
* Add: Custom "From Name" to email notification agent.
* Add: Ability to test notifications / send custom one-off notifications.
* Add: 'datestamp' and 'timestamp' notification options.
* Add: More concurrent stream statistics.
* Add: Media info flags on the info pages.
* Add: Ability to fix broken metadata if the item has been moved in Plex.
* Add: Ability to rearrange the homepage statistics cards.
* Add: CentOS startup script (Thanks @PHoSawyer)
* New: Brand new Libraries section.
* New: Lots of new library statistics.
* New: Media info table for libraries.
* New: Web app for Android and iOS. (Thanks @zobe123)
* New: Slack notification agent. (Thanks @richipargo)
* New: Facebook notification agent.
* New: Custom script notification agent. (Thanks @Hellowlol)
* New: Custom "From Name" to email notification agent.
* New: Ability to test notifications / send custom one-off notifications.
* New: 'datestamp' and 'timestamp' notification options.
* New: More concurrent stream statistics.
* New: Media info flags on the info pages.
* New: Ability to fix broken metadata if the item has been moved in Plex.
* New: Ability to rearrange the homepage statistics cards.
* New: CentOS startup script (Thanks @PHoSawyer)
* Fix: Server name blank after first run wizard.
* Fix: Incorrect duration for grouped home stats.
* Fix: Allow SSL when verifying server in settings.

View file

@ -1,6 +1,7 @@
<%
import plexpy
from plexpy import version
from plexpy.helpers import anon_url
%>
<!doctype html>
@ -136,15 +137,15 @@ from plexpy import version
<div id="ajaxMsg" class="ajaxMsg"></div>
% if plexpy.CONFIG.CHECK_GITHUB and not plexpy.CURRENT_VERSION:
<div id="updatebar" style="display: none;">
You're running an unknown version of PlexPy. <a href="update">Update</a> or
<a href="#" id="updateDismiss">Close</a>
You're running an unknown version of PlexPy.<br />
<a href="update">Update</a> or <a href="#" id="updateDismiss">Close</a>
</div>
% elif plexpy.CONFIG.CHECK_GITHUB and plexpy.CURRENT_VERSION != plexpy.LATEST_VERSION and plexpy.COMMITS_BEHIND > 0 and plexpy.INSTALL_TYPE != 'win':
<div id="updatebar" style="display: none;">
A <a
href="https://github.com/${plexpy.CONFIG.GIT_USER}/plexpy/compare/${plexpy.CURRENT_VERSION}...${plexpy.LATEST_VERSION}" target="_blank">
newer version</a> is available. You're ${plexpy.COMMITS_BEHIND} commits behind. <a href="update">Update</a> or
<a href="#" id="updateDismiss">Close</a>
A <a href="${anon_url('https://github.com/%s/plexpy/compare/%s...%s' % (plexpy.CONFIG.GIT_USER, plexpy.CURRENT_VERSION, plexpy.LATEST_VERSION))}" target="_blank">
newer version</a> is available.<br />
You're ${plexpy.COMMITS_BEHIND} commits behind.<br />
<a href="update">Update</a> or <a href="#" id="updateDismiss">Close</a>
</div>
% endif
<nav class="navbar navbar-fixed-top">

View file

@ -882,10 +882,11 @@ a .dashboard-activity-metadata-user-thumb:hover {
}
.dashboard-recent-media-row {
width: 100%;
margin:0 auto;
margin: 0 auto;
text-align: center;
position: relative;
z-index: 0;
overflow: hidden;
}
.dashboard-recent-media {
width: 100%;
@ -2407,6 +2408,9 @@ a .home-platforms-instance-list-oval:hover,
padding: 0;
border: 0;
}
.history-thumbnail-popover.popover.left {
margin-left: -15px;
}
.history-thumbnail-popover.popover.right {
margin-left: 15px;
}
@ -2655,6 +2659,7 @@ table[id^='media_info_child'] table[id^='media_info_child'] thead th {
margin: 5px 0 0 0.5em;
}
.notification-params {
width: 100%;
margin-top: 10px;
background-color: #282828;
}
@ -2669,6 +2674,14 @@ table[id^='media_info_child'] table[id^='media_info_child'] thead th {
padding-left: 10px;
width: 200px;
}
.notification-params.time-options td:first-child {
padding-left: 10px;
width: 125px;
}
.notification-params.time-options td:nth-child(2) {
padding-left: 10px;
width: 275px;
}
.notification-params td:not(:first-child) {
padding-right: 10px;
}
@ -2758,4 +2771,22 @@ a.no-highlight:hover {
}
.save-button {
margin-top: 15px;
}
.nav-dashboard > li {
float: left;
}
.btn-gray.disabled,
.btn-gray.disabled:focus,
.btn-gray.disabled:hover,
.btn-gray.disabled:active {
color: #323232;
cursor: default;
}
.nav-header > li > a:focus,
.nav-header > li > a:hover {
background-color: transparent;
}
#recently-added-row-scroller,
#recently-watched-row-scroller {
position: relative;
}

View file

@ -217,8 +217,8 @@ DOCUMENTATION :: END
% endif
<div class="dashboard-activity-progress">
<div class="dashboard-activity-progress-bar">
<div class="bufferbar" style="width: ${a['transcode_progress']}%">${a['transcode_progress']}%</div>
<div class="bar" style="width: ${a['progress_percent']}%">${a['progress_percent']}%</div>
<div class="bufferbar" style="width: ${a['transcode_progress']}%" data-toggle="tooltip" title="Transcoder Progress">${a['transcode_progress']}%</div>
<div class="bar" style="width: ${a['progress_percent']}%" data-toggle="tooltip" title="Stream Progress">${a['progress_percent']}%</div>
</div>
</div>
<div class="dashboard-activity-metadata-wrapper">
@ -261,11 +261,7 @@ DOCUMENTATION :: END
% endif
</div>
<div class="dashboard-activity-metadata-user">
% if a['user_id']:
<a href="user?user_id=${a['user_id']}" title="${a['friendly_name']}">${a['friendly_name']}</a>
% else:
<a href="user?user=${a['user']}" title="${a['friendly_name']}">${a['friendly_name']}</a>
% endif
</div>
</div>
</div>
@ -289,11 +285,13 @@ DOCUMENTATION :: END
});
// Add hover class to dashboard-instance
$('.dashboard-activity-poster').hover(function() {
$('.dashboard-activity-poster, .dashboard-activity-progress-bar').hover(function() {
$(this).closest('.dashboard-instance').addClass('hover');
}, function() {
$(this).closest('.dashboard-instance').removeClass('hover');
});
$('.bar, .bufferbar').tooltip({container: 'body', placement: 'right', delay: 50});
</script>
% else:
<div class="text-muted">Nothing is currently being watched.</div><br>

View file

@ -249,7 +249,7 @@
<script>
// Modal popup dialog
function selectHandler(selectedDate) {
function selectHandler(selectedDate, selectedSeries) {
try
{
@ -259,10 +259,25 @@
var y = dateValue.getFullYear();
var dateString = '' + y + '-' + (m<=9 ? '0' + m : m) + '-' + (d <= 9 ? '0' + d : d);
var media_type = 'all';
var transcode_decision = null;
switch(selectedSeries) {
case "TV": media_type = 'episode'; break;
case "Movies": media_type = 'movie'; break;
case "Music": media_type = 'track'; break;
case "Direct Play": transcode_decision = 'direct play'; break;
case "Direct Stream": transcode_decision = 'copy'; break;
case "Transcode": transcode_decision = 'transcode'; break;
}
$.ajax({
"url": "history_table_modal",
url: "history_table_modal",
type: 'post',
data: { 'start_date': dateString },
data: {
start_date: dateString,
media_type: media_type,
transcode_decision: transcode_decision
},
complete: function(xhr, status) {
$('#history-modal').modal('show');
$("#history-modal").html(xhr.responseText);
@ -271,7 +286,7 @@
}
catch(err)
{
console.log("Failed to retrieve data");
console.log("Failed to retrieve history modal data.");
}
}
</script>
@ -299,6 +314,8 @@
var music_visible = (${config['music_logging_enable']} == 1 ? true : false);
function loadGraphsTab1(time_range, yaxis) {
$('#days-selection').show();
setGraphFormat(yaxis);
$.ajax({
@ -382,6 +399,8 @@
}
function loadGraphsTab2(time_range, yaxis) {
$('#days-selection').show();
setGraphFormat(yaxis);
$.ajax({
@ -460,6 +479,8 @@
}
function loadGraphsTab3(yaxis) {
$('#days-selection').hide();
setGraphFormat(yaxis);
$.ajax({

View file

@ -22,7 +22,7 @@
</div>
</div>
<div class='table-card-back'>
<table class="display" id="history_table" width="100%">
<table class="display history_table" id="history_table" width="100%">
<thead>
<tr>
<th align="left" id="delete_row">Delete</th>

View file

@ -5,12 +5,12 @@
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-remove"></i></button>
<h4 class="modal-title" id="myModalLabel">
<strong><span id="modal_header_ip_address">
<i class="fa fa-history"></i> History for <span id="date-header">${data}</span>
<i class="fa fa-history"></i> History for <span id="date-header">${data['start_date']}</span>
</span></strong>
</h4>
</div>
<div class="modal-body" id="modal-text">
<table class="display" id="history_table" width="100%">
<table class="display history_table" id="history_table_modal" width="100%">
<thead>
<tr>
<th align="left" id="started">Started</th>
@ -32,7 +32,7 @@
<script src="interfaces/default/js/tables/history_table_modal.js"></script>
<script>
$(document).ready(function() {
$('#date-header').html(moment('${data}','YYYY-MM-DD').format('ddd MMM Do YYYY'));
$('#date-header').html(moment('${data["start_date"]}','YYYY-MM-DD').format('ddd MMM Do YYYY'));
history_table_modal_options.ajax = {
url: 'get_history',
type: 'post',
@ -40,14 +40,16 @@
return {
json_data: JSON.stringify(d),
grouping: false,
start_date: '${data}'
};
start_date: "${data['start_date']}",
media_type: "${data.get('media_type')}",
transcode_decision: "${data.get('transcode_decision')}"
};
}
}
history_table = $('#history_table').DataTable(history_table_modal_options);
history_table = $('#history_table_modal').DataTable(history_table_modal_options);
clearSearchButton('history_table', history_table);
clearSearchButton('history_table_modal', history_table);
// Move #info-modal to parent container
if (!($('#history-modal').next().is('#info-modal'))) {

View file

@ -23,7 +23,7 @@
<h3>Watch Statistics <small>Last ${config['home_stats_length']} days</small></h3>
</div>
<div id="home-stats" class="home-platforms">
<div class='text-muted'><i class="fa fa-refresh fa-spin"></i> Loading stats...</div>
<div class="text-muted"><i class="fa fa-refresh fa-spin"></i> Loading stats...</div>
<br>
</div>
</div>
@ -36,19 +36,27 @@
<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>
<div class="text-muted"><i class="fa fa-refresh fa-spin"></i> Loading stats...</div>
<br>
</div>
</div>
</div>
% endif
<div class='row'>
<div class="row">
<div class="col-md-12">
<div class="padded-header">
<ul class="nav nav-header nav-dashboard pull-right">
<li>
<a href="#" id="recently-added-page-left" class="paginate btn-gray disabled" data-id="+1"><i class="fa fa-lg fa-chevron-left"></i></a>
</li>
<li>
<a href="#" id="recently-added-page-right" class="paginate btn-gray disabled" data-id="-1"><i class="fa fa-lg fa-chevron-right"></i></a>
</li>
</ul>
<h3>Recently Added</h3>
</div>
<div id='recentlyAdded'>
<div class='text-muted'><i class="fa fa-refresh fa-spin"></i> Looking for new items...</div>
<div id="recentlyAdded" style="margin-right: -15px;">
<div class="text-muted"><i class="fa fa-refresh fa-spin"></i> Looking for new items...</div>
<br>
</div>
</div>
@ -59,7 +67,6 @@
<%def name="javascriptIncludes()">
<script src="interfaces/default/js/moment-with-locale.js"></script>
<script>
function currentActivityHeader() {
$.ajax({
url: 'get_current_activity_header',
@ -71,7 +78,6 @@
});
}
currentActivityHeader();
setInterval(currentActivityHeader, 15000);
function currentActivity() {
$.ajax({
@ -84,7 +90,12 @@
});
}
currentActivity();
setInterval(currentActivity, 15000);
setInterval(function () {
$('.bar, .bufferbar').tooltip('destroy');
currentActivityHeader();
currentActivity();
}, 15000);
function getHomeStats(days) {
$.ajax({
@ -97,6 +108,7 @@
}
});
}
getHomeStats();
function getLibraryStats() {
$.ajax({
@ -109,33 +121,21 @@
}
});
}
getLibraryStats();
function recentlyAdded() {
var widthVal = $('body').find(".container-fluid").width();
var tmp = (widthVal-20) / 182;
if (tmp > 0) {
containerSize = parseInt(tmp);
} else {
containerSize = 1;
}
$.ajax({
url: 'get_recently_added',
type: "GET",
async: true,
data: { count : containerSize },
data: { count : 50 },
complete: function(xhr, status) {
$("#recentlyAdded").html(xhr.responseText);
highlightAddedScrollerButton();
}
});
}
$(document).ready(function () {
recentlyAdded();
$(window).resize(function() {
recentlyAdded();
});
});
recentlyAdded();
var date_format = 'YYYY-MM-DD';
var time_format = 'hh:mm a';
@ -148,10 +148,44 @@
}
});
getHomeStats();
getLibraryStats();
function highlightAddedScrollerButton() {
var scroller = $("#recently-added-row-scroller");
var numElems = scroller.find("li").length;
scroller.width(numElems * 175);
if (scroller.width() > $("body").find(".container-fluid").width()) {
$("#recently-added-page-right").removeClass("disabled");
} else {
$("#recently-added-page-right").addClass("disabled");
}
}
$(window).resize(function () {
highlightAddedScrollerButton();
});
var leftTotal = 0;
$(".paginate").click(function (e) {
e.preventDefault();
var scroller = $("#recently-added-row-scroller");
var containerWidth = $("body").find(".container-fluid").width();
var scrollAmount = $(this).data("id") * parseInt((containerWidth - 15) / 175) * 175;
var leftMax = Math.min(-parseInt(scroller.width()) + Math.abs(scrollAmount), 0);
leftTotal = Math.max(Math.min(leftTotal + scrollAmount, 0), leftMax);
scroller.animate({ left: leftTotal }, 250);
if (leftTotal == 0) {
$("#recently-added-page-left").addClass("disabled").blur();
} else {
$("#recently-added-page-left").removeClass("disabled");
}
if (leftTotal == leftMax) {
$("#recently-added-page-right").addClass("disabled").blur();
} else {
$("#recently-added-page-right").removeClass("disabled");
}
});
</script>
</%def>

View file

@ -344,25 +344,41 @@ DOCUMENTATION :: END
<button class="btn btn-danger btn-edit" data-toggle="button" aria-pressed="false" autocomplete="off" id="row-edit-mode">
<i class="fa fa-trash-o"></i> Delete mode
</button>
% if source == 'history':
<a href="update_metadata?rating_key=${data['rating_key']}&update=True" class="btn btn-danger btn-edit" id="fix-metadata">
<i class="fa fa-wrench"></i> Fix Metadata
</a>
% endif
% if data.get('poster_url'):
% if data['media_type'] == 'artist' or data['media_type'] == 'album' or data['media_type'] == 'track':
<span class="imgur-poster-tooltip" data-toggle="popover" data-img="${data['poster_url']}" data-height="80" data-width="80" style="display: inline-flex;">
% else:
<span class="imgur-poster-tooltip" data-toggle="popover" data-img="${data['poster_url']}" data-height="120" data-width="80" style="display: inline-flex;">
% endif
<button class="btn btn-danger btn-edit" data-toggle="button" aria-pressed="false" autocomplete="off" id="delete-imgur-poster">
<i class="fa fa-picture-o"></i> Reset Imgur Poster
</button>
</span>
% endif
<div class="alert alert-danger alert-edit" role="alert" id="row-edit-mode-alert"><i class="fa fa-exclamation-triangle"></i>&nbspSelect rows to delete. Data is deleted upon exiting delete mode.</div>
</div>
</div>
<div class="table-card-back">
<table class="display" id="history_table" width="100%">
<table class="display history_table" id="history_table-RK-${data['rating_key']}" width="100%">
<thead>
<tr>
<th align='left' id="delete">Delete</th>
<th align='left' id="time">Time</th>
<th align='left' id="friendly_name">User</th>
<th align='left' id="ip_address">IP Address</th>
<th align='left' id="platform">Platform</th>
<th align='left' id="player">Player</th>
<th align='left' id="title">Title</th>
<th align='left' id="started">Started</th>
<th align='left' id="paused_counter">Paused</th>
<th align='left' id="stopped">Stopped</th>
<th align='left' id="duration">Duration</th>
<th align='left' id="percent_complete"></th>
<th align="left" id="delete">Delete</th>
<th align="left" id="time">Time</th>
<th align="left" id="friendly_name">User</th>
<th align="left" id="ip_address">IP Address</th>
<th align="left" id="platform">Platform</th>
<th align="left" id="player">Player</th>
<th align="left" id="title">Title</th>
<th align="left" id="started">Started</th>
<th align="left" id="paused_counter">Paused</th>
<th align="left" id="stopped">Stopped</th>
<th align="left" id="duration">Duration</th>
<th align="left" id="percent_complete"></th>
</tr>
</thead>
<tbody></tbody>
@ -458,11 +474,11 @@ DOCUMENTATION :: END
<script>
$(document).ready(function () {
get_history();
history_table = $('#history_table').DataTable(history_table_options);
history_table = $('#history_table-RK-${data["rating_key"]}').DataTable(history_table_options);
var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 11] });
$(colvis.button()).appendTo('div.colvis-button-bar');
clearSearchButton('history_table', history_table);
clearSearchButton('history_table-RK-${data["rating_key"]}', history_table);
$('#row-edit-mode').on('click', function() {
$('#row-edit-mode-alert').fadeIn(200);
@ -519,10 +535,33 @@ DOCUMENTATION :: END
$("#runtime").html(millisecondsToMinutes($("#runtime").text(), true));
$('div.art-face').animate({ opacity: 0.2 }, { duration: 1000 });
</script>
% if source == 'history':
% if data.get('poster_url'):
<script>
$('#row-edit-mode').after('<a href="update_metadata?rating_key=${data['rating_key']}&update=True" class="btn btn-danger btn-edit" id="fix-metadata"> \
<i class="fa fa-wrench"></i> Fix Metadata</a>');
$('.imgur-poster-tooltip').popover({
html: true,
container: 'body',
trigger: 'hover',
placement: 'left',
template: '<div class="popover history-thumbnail-popover" role="tooltip"><div class="arrow" style="top: 50%;"></div><div class="popover-content"></div></div>',
content: function () {
return '<div class="history-thumbnail" style="background-image: url(' + $(this).data('img') + '); height: ' + $(this).data('height') + 'px; width: ' + $(this).data('width') + 'px;" />';
}
});
$('#delete-imgur-poster').on('click', function() {
$.ajax({
url: 'delete_poster_url',
type: 'POST',
async: true,
data: { poster_url : "${data['poster_url']}" },
success: function (data) {
var msg = '<i class="fa fa-check"></i>&nbsp; Imgur poster reset';
showMsg(msg, false, true, 2000);
$('.imgur-poster-tooltip').popover('destroy');
$('#delete-imgur-poster').closest('span').remove();
}
});
});
</script>
% endif
% endif

View file

@ -32,7 +32,8 @@
</div>
</div>
<div class="modal-footer">
<span class="text-muted">Telize service written by <a href="https://github.com/fcambus/telize" target="_blank">Frederic Cambus</a>.</span>
<% from plexpy.helpers import anon_url %>
<span class="text-muted">Telize service written by <a href="${anon_url('https://github.com/fcambus/telize')}" target="_blank">Frederic Cambus</a>.</span>
</div>
</div>
</div>

View file

@ -28,8 +28,8 @@ var hc_plays_by_day_options = {
cursor: 'pointer',
point: {
events: {
click: function() {
selectHandler(this.category);
click: function () {
selectHandler(this.category, this.series.name);
}
}
}
@ -60,7 +60,8 @@ var hc_plays_by_day_options = {
}
},
tooltip: {
shared: true
shared: true,
crosshairs: true
},
series: [{}]
};

View file

@ -29,7 +29,7 @@ var hc_plays_by_stream_type_options = {
point: {
events: {
click: function() {
selectHandler(this.category);
selectHandler(this.category, this.series.name);
}
}
}
@ -60,7 +60,8 @@ var hc_plays_by_stream_type_options = {
}
},
tooltip: {
shared: true
shared: true,
crosshairs: true
},
series: [{}]
};

View file

@ -22,7 +22,7 @@ history_table_options = {
"emptyTable": "No data in table"
},
"pagingType": "bootstrap",
"stateSave": false,
"stateSave": true,
"processing": false,
"serverSide": true,
"pageLength": 25,
@ -107,7 +107,7 @@ history_table_options = {
}
},
"width": "10%",
"className": "no-wrap hidden-md hidden-sm hidden-xs modal-control"
"className": "no-wrap hidden-md hidden-sm hidden-xs"
},
{
"targets": [5],
@ -115,11 +115,11 @@ history_table_options = {
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
var transcode_dec = '';
if (rowData['video_decision'] === 'transcode' || rowData['audio_decision'] === 'transcode') {
if (rowData['transcode_decision'] === 'transcode') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Transcode"><i class="fa fa-server fa-fw"></i></span>';
} else if (rowData['video_decision'] === 'copy' || rowData['audio_decision'] === 'copy') {
} else if (rowData['transcode_decision'] === 'copy') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Stream"><i class="fa fa-video-camera fa-fw"></i></span>';
} else if (rowData['video_decision'] === 'direct play' || rowData['audio_decision'] === 'direct play') {
} else if (rowData['transcode_decision'] === 'direct play') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Play"><i class="fa fa-play-circle fa-fw"></i></span>';
}
$(td).html('<div><a href="#" data-target="#info-modal" data-toggle="modal"><div style="float: left;">' + transcode_dec + '&nbsp;' + cellData + '</div></a></div>');
@ -307,7 +307,7 @@ history_table_options = {
}
// Parent table platform modal
$('#history_table').on('click', '> tbody > tr > td.modal-control', function () {
$('.history_table').on('click', '> tbody > tr > td.modal-control', function () {
var tr = $(this).closest('tr');
var row = history_table.row( tr );
var rowData = row.data();
@ -327,7 +327,7 @@ $('#history_table').on('click', '> tbody > tr > td.modal-control', function () {
});
// Parent table ip address modal
$('#history_table').on('click', '> tbody > tr > td.modal-control-ip', function () {
$('.history_table').on('click', '> tbody > tr > td.modal-control-ip', function () {
var tr = $(this).closest('tr');
var row = history_table.row( tr );
var rowData = row.data();
@ -350,7 +350,7 @@ $('#history_table').on('click', '> tbody > tr > td.modal-control-ip', function (
});
// Parent table delete mode
$('#history_table').on('click', '> tbody > tr > td.delete-control > button', function () {
$('.history_table').on('click', '> tbody > tr > td.delete-control > button', function () {
var tr = $(this).closest('tr');
var row = history_table.row( tr );
var rowData = row.data();
@ -399,7 +399,7 @@ $('#history_table').on('click', '> tbody > tr > td.delete-control > button', fun
});
// Parent table expand detailed history
$('#history_table').on('click', '> tbody > tr > td.expand-history a', function () {
$('.history_table').on('click', '> tbody > tr > td.expand-history a', function () {
var tr = $(this).closest('tr');
var row = history_table.row(tr);
var rowData = row.data();

View file

@ -79,11 +79,11 @@ history_table_modal_options = {
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
var transcode_dec = '';
if (rowData['video_decision'] === 'transcode' || rowData['audio_decision'] === 'transcode') {
if (rowData['transcode_decision'] === 'transcode') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Transcode"><i class="fa fa-server fa-fw"></i></span>';
} else if (rowData['video_decision'] === 'copy' || rowData['audio_decision'] === 'copy') {
} else if (rowData['transcode_decision'] === 'copy') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Stream"><i class="fa fa-video-camera fa-fw"></i></span>';
} else if (rowData['video_decision'] === 'direct play' || rowData['audio_decision'] === 'direct play') {
} else if (rowData['transcode_decision'] === 'direct play') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Play"><i class="fa fa-play-circle fa-fw"></i></span>';
}
$(td).html('<div><a href="#" data-target="#info-modal" data-toggle="modal"><div style="float: left;">' + transcode_dec + '&nbsp' + cellData + '</div></a></div>');
@ -148,7 +148,7 @@ history_table_modal_options = {
}
}
$('#history_table').on('click', 'td.modal-control', function () {
$('.history_table').on('click', 'td.modal-control', function () {
var tr = $(this).parents('tr');
var row = history_table.row(tr);
var rowData = row.data();

View file

@ -16,7 +16,7 @@ libraries_list_table_options = {
"pageLength": 10,
"order": [ 2, 'asc'],
"autoWidth": true,
"stateSave": false,
"stateSave": true,
"pagingType": "bootstrap",
"columnDefs": [
{

View file

@ -5,7 +5,7 @@ var log_table_options = {
"pagingType": "bootstrap",
"order": [ 0, 'desc'],
"pageLength": 50,
"stateSave": false,
"stateSave": true,
"language": {
"search":"Search: ",
"lengthMenu":"Show _MENU_ lines per page",

View file

@ -23,7 +23,7 @@ media_info_table_options = {
"emptyTable": "No data in table"
},
"pagingType": "bootstrap",
"stateSave": false,
"stateSave": true,
"processing": false,
"serverSide": true,
"pageLength": 25,
@ -294,7 +294,7 @@ media_info_table_options = {
}
// Parent table expand detailed media info
$('#media_info_table').on('click', '> tbody > tr > td.expand-media-info a', function () {
$('.media_info_table').on('click', '> tbody > tr > td.expand-media-info a', function () {
var tr = $(this).closest('tr');
var row = media_info_table.row(tr);
var rowData = row.data();

View file

@ -5,7 +5,7 @@ notification_log_table_options = {
"pagingType": "bootstrap",
"order": [ 0, 'desc'],
"pageLength": 50,
"stateSave": false,
"stateSave": true,
"language": {
"search":"Search: ",
"lengthMenu":"Show _MENU_ lines per page",

View file

@ -5,7 +5,7 @@ var plex_log_table_options = {
"pagingType": "bootstrap",
"order": [ 0, 'desc'],
"pageLength": 50,
"stateSave": false,
"stateSave": true,
"language": {
"search":"Search: ",
"lengthMenu":"Show _MENU_ lines per page",

View file

@ -4,7 +4,7 @@ sync_table_options = {
"pagingType": "bootstrap",
"order": [ [ 0, 'desc'], [ 1, 'asc'], [2, 'asc'] ],
"pageLength": 25,
"stateSave": false,
"stateSave": true,
"language": {
"search":"Search: ",
"lengthMenu":"Show _MENU_ lines per page",

View file

@ -8,7 +8,7 @@ user_ip_table_options = {
"infoFiltered":"(filtered from _MAX_ total entries)",
"emptyTable": "No data in table",
},
"stateSave": false,
"stateSave": true,
"pagingType": "bootstrap",
"processing": false,
"serverSide": true,
@ -56,7 +56,7 @@ user_ip_table_options = {
}
},
"width": "15%",
"className": "no-wrap hidden-md hidden-sm hidden-xs modal-control"
"className": "no-wrap hidden-md hidden-sm hidden-xs"
},
{
"targets": [3],
@ -64,11 +64,11 @@ user_ip_table_options = {
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData) {
var transcode_dec = '';
if (rowData['video_decision'] === 'transcode' || rowData['audio_decision'] === 'transcode') {
if (rowData['transcode_decision'] === 'transcode') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Transcode"><i class="fa fa-server fa-fw"></i></span>';
} else if (rowData['video_decision'] === 'copy' || rowData['audio_decision'] === 'copy') {
} else if (rowData['transcode_decision'] === 'copy') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Stream"><i class="fa fa-video-camera fa-fw"></i></span>';
} else if (rowData['video_decision'] === 'direct play' || rowData['audio_decision'] === 'direct play') {
} else if (rowData['transcode_decision'] === 'direct play') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Play"><i class="fa fa-play-circle fa-fw"></i></span>';
}
$(td).html('<div><a href="#" data-target="#info-modal" data-toggle="modal"><div style="float: left;">' + transcode_dec + '&nbsp' + cellData + '</div></a></div>');
@ -146,11 +146,11 @@ user_ip_table_options = {
}
}
$('#user_ip_table').on('mouseenter', 'td.modal-control span', function () {
$('.user_ip_table').on('mouseenter', 'td.modal-control span', function () {
$(this).tooltip();
});
$('#user_ip_table').on('click', 'td.modal-control', function () {
$('.user_ip_table').on('click', 'td.modal-control', function () {
var tr = $(this).parents('tr');
var row = user_ip_table.row(tr);
var rowData = row.data();
@ -169,7 +169,7 @@ $('#user_ip_table').on('click', 'td.modal-control', function () {
showStreamDetails();
});
$('#user_ip_table').on('click', 'td.modal-control-ip', function () {
$('.user_ip_table').on('click', 'td.modal-control-ip', function () {
var tr = $(this).parents('tr');
var row = user_ip_table.row( tr );
var rowData = row.data();

View file

@ -16,7 +16,7 @@ users_list_table_options = {
"pageLength": 10,
"order": [ 2, 'asc'],
"autoWidth": true,
"stateSave": false,
"stateSave": true,
"pagingType": "bootstrap",
"columnDefs": [
{
@ -120,11 +120,11 @@ users_list_table_options = {
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== null && cellData !== '') {
var transcode_dec = '';
if (rowData['video_decision'] === 'transcode' || rowData['audio_decision'] === 'transcode') {
if (rowData['transcode_decision'] === 'transcode') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Transcode"><i class="fa fa-server fa-fw"></i></span>';
} else if (rowData['video_decision'] === 'copy' || rowData['audio_decision'] === 'copy') {
} else if (rowData['transcode_decision'] === 'copy') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Stream"><i class="fa fa-video-camera fa-fw"></i></span>';
} else if (rowData['video_decision'] === 'direct play' || rowData['audio_decision'] === 'direct play') {
} else if (rowData['transcode_decision'] === 'direct play') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Play"><i class="fa fa-play-circle fa-fw"></i></span>';
}
$(td).html('<div><a href="#" data-target="#info-modal" data-toggle="modal"><div style="float: left;">' + transcode_dec + '&nbsp;' + cellData + '</div></a></div>');

View file

@ -118,8 +118,16 @@ DOCUMENTATION :: END
<div class="row">
<div class="col-md-12">
<div class="table-card-header">
<ul class="nav nav-header nav-dashboard pull-right">
<li>
<a href="#" id="recently-watched-page-left" class="paginate-watched btn-gray disabled" data-id="+1"><i class="fa fa-lg fa-chevron-left"></i></a>
</li>
<li>
<a href="#" id="recently-watched-page-right" class="paginate-watched btn-gray disabled" data-id="-1"><i class="fa fa-lg fa-chevron-right"></i></a>
</li>
</ul>
<div class="header-bar">
<span><i class="fa fa-history"></i> Recently Watched</span>
<span><i class="fa fa-history"></i> Recently Played</span>
</div>
</div>
<div class="table-card-back">
@ -135,6 +143,14 @@ DOCUMENTATION :: END
<div class="row">
<div class="col-md-12">
<div class="table-card-header">
<ul class="nav nav-header nav-dashboard pull-right">
<li>
<a href="#" id="recently-added-page-left" class="paginate-added btn-gray disabled" data-id="+1"><i class="fa fa-lg fa-chevron-left"></i></a>
</li>
<li>
<a href="#" id="recently-added-page-right" class="paginate-added btn-gray disabled" data-id="-1"><i class="fa fa-lg fa-chevron-right"></i></a>
</li>
</ul>
<div class="header-bar">
<span><i class="fa fa-history"></i> Recently Added</span>
</div>
@ -170,7 +186,7 @@ DOCUMENTATION :: END
</div>
</div>
<div class="table-card-back">
<table class="display" id="history_table" width="100%">
<table class="display history_table" id="history_table-SID-${data['section_id']}" width="100%">
<thead>
<tr>
<th align="left" id="delete">Delete</th>
@ -229,7 +245,7 @@ DOCUMENTATION :: END
</div>
</div>
<div class="table-card-back">
<table class="display" id="media_info_table" width="100%">
<table class="display media_info_table" id="media_info_table-SID-${data['section_id']}" width="100%">
<thead>
<tr>
<th align="left" id="added_at">Added At</th>
@ -364,12 +380,12 @@ DOCUMENTATION :: END
};
}
}
history_table = $('#history_table').DataTable(history_table_options);
history_table = $('#history_table-SID-${data["section_id"]}').DataTable(history_table_options);
var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 11] });
$(colvis.button()).appendTo('#button-bar-history');
clearSearchButton('history_table', history_table);
clearSearchButton('history_table-SID-${data["section_id"]}', history_table);
}
function loadMediaInfoTable() {
@ -385,12 +401,12 @@ DOCUMENTATION :: END
};
}
}
media_info_table = $('#media_info_table').DataTable(media_info_table_options);
media_info_table = $('#media_info_table-SID-${data["section_id"]}').DataTable(media_info_table_options);
var colvis = new $.fn.dataTable.ColVis(media_info_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark' });
$(colvis.button()).appendTo('#button-bar-media-info');
clearSearchButton('media_info_table', media_info_table);
clearSearchButton('media_info_table-SID-${data["section_id"]}', media_info_table);
}
$( "#history-tab-btn" ).one( "click", function() {
@ -460,61 +476,116 @@ DOCUMENTATION :: END
});
function recentlyWatched() {
var widthVal = $("#library-recently-watched").width();
var tmp = (widthVal-25) / 175;
if (tmp > 0) {
var containerSize = parseInt(tmp);
} else {
var containerSize = 1;
}
// Populate recently watched
$.ajax({
url: 'get_library_recently_watched',
async: true,
data: {
section_id: section_id,
limit: containerSize
limit: 50
},
complete: function(xhr, status) {
$("#library-recently-watched").html(xhr.responseText);
highlightWatchedScrollerButton();
}
});
}
function recentlyAdded() {
var widthVal = $("#library-recently-added").width();
var tmp = (widthVal-25) / 175;
if (tmp > 0) {
var containerSize = parseInt(tmp);
} else {
var containerSize = 1;
}
// Populate recently added
$.ajax({
url: 'get_library_recently_added',
async: true,
data: {
section_id: section_id,
limit: containerSize
limit: 50
},
complete: function(xhr, status) {
$("#library-recently-added").html(xhr.responseText);
highlightAddedScrollerButton();
}
});
}
recentlyWatched();
recentlyAdded();
function highlightWatchedScrollerButton() {
var scroller = $("#recently-watched-row-scroller");
var numElems = scroller.find("li").length;
scroller.width(numElems * 175);
if (scroller.width() > $("#library-recently-watched").width()) {
$("#recently-watched-page-right").removeClass("disabled");
} else {
$("#recently-watched-page-right").addClass("disabled");
}
}
function highlightAddedScrollerButton() {
var scroller = $("#recently-added-row-scroller");
var numElems = scroller.find("li").length;
scroller.width(numElems * 175);
if (scroller.width() > $("#library-recently-added").width()) {
$("#recently-added-page-right").removeClass("disabled");
} else {
$("#recently-added-page-right").addClass("disabled");
}
}
$(window).resize(function() {
recentlyWatched();
recentlyAdded();
highlightWatchedScrollerButton();
highlightAddedScrollerButton();
});
$('div.art-face').animate({ opacity: 0.2 }, { duration: 1000 });
var leftTotalWatched = 0;
$(".paginate-watched").click(function (e) {
e.preventDefault();
var scroller = $("#recently-watched-row-scroller");
var containerWidth = $("#library-recently-watched").width();
var scrollAmount = $(this).data("id") * parseInt(containerWidth / 175) * 175;
var leftMax = Math.min(-parseInt(scroller.width()) + Math.abs(scrollAmount), 0);
leftTotalWatched = Math.max(Math.min(leftTotalWatched + scrollAmount, 0), leftMax);
scroller.animate({ left: leftTotalWatched }, 250);
if (leftTotalWatched == 0) {
$("#recently-watched-page-left").addClass("disabled").blur();
} else {
$("#recently-watched-page-left").removeClass("disabled");
}
if (leftTotalWatched == leftMax) {
$("#recently-watched-page-right").addClass("disabled").blur();
} else {
$("#recently-watched-page-right").removeClass("disabled");
}
});
var leftTotalAdded = 0;
$(".paginate-added").click(function (e) {
e.preventDefault();
var scroller = $("#recently-added-row-scroller");
var containerWidth = $("#library-recently-added").width();
var scrollAmount = $(this).data("id") * parseInt(containerWidth / 175) * 175;
var leftMax = Math.min(-parseInt(scroller.width()) + Math.abs(scrollAmount), 0);
leftTotalAdded = Math.max(Math.min(leftTotalAdded + scrollAmount, 0), leftMax);
scroller.animate({ left: leftTotalAdded }, 250);
if (leftTotalAdded == 0) {
$("#recently-added-page-left").addClass("disabled").blur();
} else {
$("#recently-added-page-left").removeClass("disabled");
}
if (leftTotalAdded == leftMax) {
$("#recently-added-page-right").addClass("disabled").blur();
} else {
$("#recently-added-page-right").removeClass("disabled");
}
});
});
</script>
% endif

View file

@ -32,63 +32,65 @@ DOCUMENTATION :: END
% if data:
<div class="dashboard-recent-media-row">
<ul class="dashboard-recent-media list-unstyled">
% for item in data:
<li>
% if item['media_type'] == 'episode' or item['media_type'] == 'movie':
<a href="info?rating_key=${item['rating_key']}">
<div class="dashboard-recent-media-poster">
% if item['media_type'] == 'episode':
% if item['parent_thumb']:
<div class="dashboard-recent-media-poster-face" style="background-image: url(pms_image_proxy?img=${item['parent_thumb']}&width=300&height=450&fallback=poster);">
% else:
<div class="dashboard-recent-media-poster-face" style="background-image: url(pms_image_proxy?img=${item['grandparent_thumb']}&width=300&height=450&fallback=poster);">
% endif
% elif item['media_type'] == 'movie':
<div class="dashboard-recent-media-poster-face" style="background-image: url(pms_image_proxy?img=${item['thumb']}&width=300&height=450&fallback=poster);">
% endif
<div class="dashboard-recent-media-overlay">
<div class="dashboard-recent-media-overlay-text" id="added_at-${item['rating_key']}">
<script>
<div id="recently-added-row-scroller" style="left: 0;">
<ul class="dashboard-recent-media list-unstyled">
% for item in data:
<li>
% if item['media_type'] == 'episode' or item['media_type'] == 'movie':
<a href="info?rating_key=${item['rating_key']}">
<div class="dashboard-recent-media-poster">
% if item['media_type'] == 'episode':
% if item['parent_thumb']:
<div class="dashboard-recent-media-poster-face" style="background-image: url(pms_image_proxy?img=${item['parent_thumb']}&width=300&height=450&fallback=poster);">
% else:
<div class="dashboard-recent-media-poster-face" style="background-image: url(pms_image_proxy?img=${item['grandparent_thumb']}&width=300&height=450&fallback=poster);">
% endif
% elif item['media_type'] == 'movie':
<div class="dashboard-recent-media-poster-face" style="background-image: url(pms_image_proxy?img=${item['thumb']}&width=300&height=450&fallback=poster);">
% endif
<div class="dashboard-recent-media-overlay">
<div class="dashboard-recent-media-overlay-text" id="added_at-${item['rating_key']}">
<script>
$('#added_at-${item['rating_key']}').text('Added ' + moment(${item['added_at']}, "X").fromNow())
</script>
</div>
</div>
</div>
</div>
<div class="dashboard-recent-media-metacontainer">
% if item['media_type'] == 'episode':
<h3 title="${item['grandparent_title']}">${item['grandparent_title']}</h3>
<h3 title="${item['title']}">${item['title']}</h3>
<h3 class="text-muted">S${item['parent_media_index']} &middot; E${item['media_index']}</h3>
% elif item['media_type'] == 'movie':
<h3 title="${item['title']}">${item['title']}</h3>
<h3 class="text-muted">${item['year']}</h3>
% endif
</div>
</a>
% elif item['media_type'] == 'album':
<a href="info?rating_key=${item['rating_key']}">
<div class="dashboard-recent-media-cover">
<div class="dashboard-recent-media-cover-face" style="background-image: url(pms_image_proxy?img=${item['thumb']}&width=300&height=300&fallback=cover);">
<div class="dashboard-recent-media-overlay">
<div class="dashboard-recent-media-overlay-text" id="added_at-${item['rating_key']}">
<script>
$('#added_at-${item['rating_key']}').text('Added ' + moment(${item['added_at']}, "X").fromNow())
</script>
</script>
</div>
</div>
</div>
</div>
</div>
<div class="dashboard-recent-media-metacontainer">
% if item['media_type'] == 'episode':
<h3 title="${item['grandparent_title']}">${item['grandparent_title']}</h3>
<h3 title="${item['title']}">${item['title']}</h3>
<h3 class="text-muted">S${item['parent_media_index']} &middot; E${item['media_index']}</h3>
% elif item['media_type'] == 'movie':
<h3 title="${item['title']}">${item['title']}</h3>
<h3 class="text-muted">${item['year']}</h3>
% endif
</div>
</a>
% elif item['media_type'] == 'album':
<a href="info?rating_key=${item['rating_key']}">
<div class="dashboard-recent-media-cover">
<div class="dashboard-recent-media-cover-face" style="background-image: url(pms_image_proxy?img=${item['thumb']}&width=300&height=300&fallback=cover);">
<div class="dashboard-recent-media-overlay">
<div class="dashboard-recent-media-overlay-text" id="added_at-${item['rating_key']}">
<script>
$('#added_at-${item['rating_key']}').text('Added ' + moment(${item['added_at']}, "X").fromNow())
</script>
</div>
</div>
<div class="dashboard-recent-media-metacontainer">
<h3 title="${item['parent_title']}">${item['parent_title']}</h3>
<h3 class="text-muted">${item['title']}</h3>
</div>
</div>
<div class="dashboard-recent-media-metacontainer">
<h3 title="${item['parent_title']}">${item['parent_title']}</h3>
<h3 class="text-muted">${item['title']}</h3>
</div>
</a>
% endif
</li>
% endfor
</ul>
</a>
% endif
</li>
% endfor
</ul>
</div>
</div>
% else:
<div class="text-muted">No stats to show.

View file

@ -22,6 +22,7 @@ from plexpy import helpers
</div>
<div class="button-bar">
<button class="btn btn-dark" id="clear-logs"><i class="fa fa-trash-o"></i> Clear log</button>
<button class="btn btn-dark" id="clear-notify-logs" style="display: none;"><i class="fa fa-trash-o"></i> Clear log</button>
</div>
</div>
<div class='table-card-back'>
@ -156,24 +157,28 @@ from plexpy import helpers
$("#plexpy-logs-btn").click(function () {
$("#clear-logs").show();
$("#clear-notify-logs").hide();
LoadPlexPyLogs();
clearSearchButton('log_table', log_table);
});
$("#plex-logs-btn").click(function () {
$("#clear-logs").hide();
$("#clear-notify-logs").hide();
LoadPlexLogs();
clearSearchButton('plex_log_table', plex_log_table);
});
$("#plex-scanner-logs-btn").click(function () {
$("#clear-logs").hide();
$("#clear-notify-logs").hide();
LoadPlexScannerLogs();
clearSearchButton('plex_scanner_log_table', plex_scanner_log_table);
});
$("#notification-logs-btn").click(function () {
$("#clear-logs").hide();
$("#clear-notify-logs").show();
LoadNotificationLogs();
clearSearchButton('notification_log_table', notification_log_table);
});
@ -185,6 +190,19 @@ from plexpy import helpers
}
});
$("#clear-notify-logs").click(function () {
var r = confirm("Are you sure you want to clear the PlexPy notification log?");
if (r == true) {
$.ajax({
url: 'clearNotifyLogs',
type: 'POST',
success: function (data) {
notification_log_table.draw();
}
});
}
});
var timer;
function setRefresh()
{
@ -200,8 +218,12 @@ from plexpy import helpers
timer = setInterval(function() {
if ($("#tabs-1").hasClass("active")) {
log_table.ajax.reload();
} else {
} else if ($("#tabs-2").hasClass("active")) {
plex_log_table.ajax.reload();
} else if ($("#tabs-3").hasClass("active")) {
plex_scanner_log_table.ajax.reload();
} else if ($("#tabs-4").hasClass("active")) {
notification_log_table.ajax.reload();
}
}, 1000*refreshrate.value);
}

View file

@ -92,6 +92,13 @@ from plexpy import helpers
</label>
<p class="help-block">Trigger notification when the Plex Media Server can be reached externally after being down.</p>
</div>
<div class="checkbox">
<label>
<input type="checkbox" data-size="small" data-id="${data['id']}" data-config-name="${data['config_prefix']}_on_pmsupdate" ${helpers.checked(data['on_pmsupdate'])} class="toggle-switches">
Notify on Plex update available
</label>
<p class="help-block">Trigger notification when an update for the Plex Media Server is available.</p>
</div>
</div>
</div>
</div>

View file

@ -32,56 +32,58 @@ DOCUMENTATION :: END
% if data != None:
<div class="dashboard-recent-media-row">
<ul class="dashboard-recent-media list-unstyled">
% for item in data:
<div class="dashboard-recent-media-instance">
<li>
% if item['media_type'] == 'season' or item['media_type'] == 'movie':
<a href="info?rating_key=${item['rating_key']}">
<div class="dashboard-recent-media-poster">
<div class="dashboard-recent-media-poster-face" style="background-image: url(pms_image_proxy?img=${item['thumb']}&width=300&height=450&fallback=poster);">
<div class="dashboard-recent-media-overlay">
<div class="dashboard-recent-media-overlay-text" id="added_at-${item['rating_key']}">
<script>
$('#added_at-${item['rating_key']}').text('Added ' + moment(${item['added_at']}, "X").fromNow())
</script>
<div id="recently-added-row-scroller" style="left: 0;">
<ul class="dashboard-recent-media list-unstyled">
% for item in data:
<div class="dashboard-recent-media-instance">
<li>
% if item['media_type'] == 'season' or item['media_type'] == 'movie':
<a href="info?rating_key=${item['rating_key']}">
<div class="dashboard-recent-media-poster">
<div class="dashboard-recent-media-poster-face" style="background-image: url(pms_image_proxy?img=${item['thumb']}&width=300&height=450&fallback=poster);">
<div class="dashboard-recent-media-overlay">
<div class="dashboard-recent-media-overlay-text" id="added_at-${item['rating_key']}">
<script>
$('#added_at-${item['rating_key']}').text('Added ' + moment(${item['added_at']}, "X").fromNow())
</script>
</div>
</div>
</div>
</div>
</div>
<div class="dashboard-recent-media-metacontainer">
% if item['media_type'] == 'season':
<h3 title="${item['parent_title']}">${item['parent_title']}</h3>
<h3 class="text-muted">${item['title']}</h3>
% elif item['media_type'] == 'movie':
<h3 title="${item['title']}">${item['title']}</h3>
<h3 class="text-muted">${item['year']}</h3>
% endif
</div>
</a>
% elif item['media_type'] == 'album':
<a href="info?rating_key=${item['rating_key']}">
<div class="dashboard-recent-media-cover">
<div class="dashboard-recent-media-cover-face" style="background-image: url(pms_image_proxy?img=${item['thumb']}&width=300&height=300&fallback=cover);">
<div class="dashboard-recent-media-overlay">
<div class="dashboard-recent-media-overlay-text" id="added_at-${item['rating_key']}">
<script>
$('#added_at-${item['rating_key']}').text('Added ' + moment(${item['added_at']}, "X").fromNow())
</script>
<div class="dashboard-recent-media-metacontainer">
% if item['media_type'] == 'season':
<h3 title="${item['parent_title']}">${item['parent_title']}</h3>
<h3 class="text-muted">${item['title']}</h3>
% elif item['media_type'] == 'movie':
<h3 title="${item['title']}">${item['title']}</h3>
<h3 class="text-muted">${item['year']}</h3>
% endif
</div>
</a>
% elif item['media_type'] == 'album':
<a href="info?rating_key=${item['rating_key']}">
<div class="dashboard-recent-media-cover">
<div class="dashboard-recent-media-cover-face" style="background-image: url(pms_image_proxy?img=${item['thumb']}&width=300&height=300&fallback=cover);">
<div class="dashboard-recent-media-overlay">
<div class="dashboard-recent-media-overlay-text" id="added_at-${item['rating_key']}">
<script>
$('#added_at-${item['rating_key']}').text('Added ' + moment(${item['added_at']}, "X").fromNow())
</script>
</div>
</div>
</div>
</div>
</div>
<div class="dashboard-recent-media-metacontainer">
<h3 title="${item['parent_title']}">${item['parent_title']}</h3>
<h3 class="text-muted">${item['title']}</h3>
</div>
</a>
% endif
</li>
</div>
% endfor
</ul>
<div class="dashboard-recent-media-metacontainer">
<h3 title="${item['parent_title']}">${item['parent_title']}</h3>
<h3 class="text-muted">${item['title']}</h3>
</div>
</a>
% endif
</li>
</div>
% endfor
</ul>
</div>
</div>
% else:
<div class="text-muted">There was an error communicating with your Plex Server. Please check your <a href="settings">settings</a>.

View file

@ -43,7 +43,7 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
<li role="presentation"><a href="#tabs-5" aria-controls="tabs-5" role="tab" data-toggle="tab">Plex Media Server</a></li>
<li role="presentation"><a href="#tabs-6" aria-controls="tabs-6" role="tab" data-toggle="tab">Plex.tv Account</a></li>
<li role="presentation"><a href="#tabs-7" aria-controls="tabs-7" role="tab" data-toggle="tab">Extra Settings</a></li>
<li role="presentation"><a href="#tabs-8" aria-controls="tabs-8" role="tab" data-toggle="tab">Monitoring</a></li>
<li role="presentation"><a href="#tabs-8" aria-controls="tabs-8" role="tab" data-toggle="tab">Activity Monitoring</a></li>
<li role="presentation"><a href="#tabs-9" aria-controls="tabs-9" role="tab" data-toggle="tab">Notifications</a></li>
<li role="presentation"><a href="#tabs-10" aria-controls="tabs-10" role="tab" data-toggle="tab">Notification Agents</a></li>
</ul>
@ -82,15 +82,15 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</tr>
<tr>
<td>Backup Directory:</td>
<td>${plexpy.CONFIG.BACKUP_DIR}</td>
<td>${config['backup_dir']}</td>
</tr>
<tr>
<td>Cache Directory:</td>
<td>${plexpy.CONFIG.CACHE_DIR}</td>
<td>${config['cache_dir']}</td>
</tr>
<tr>
<td>Log Directory:</td>
<td>${plexpy.CONFIG.LOG_DIR}</td>
<td>${config['log_dir']}</td>
</tr>
% if plexpy.ARGS:
<tr>
@ -169,6 +169,35 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</label>
<p class="help-block">Group successive play history by the same user as a single entry in the tables and watch statistics.</p>
</div>
<div class="padded-header">
<h3>Directories</h3>
</div>
<div class="form-group">
<label for="backup_dir">Backup Directory</label>
<div class="row">
<div class="col-md-6">
<input type="text" class="form-control directory-settings" id="backup_dir" name="backup_dir" value="${config['backup_dir']}">
</div>
</div>
</div>
<div class="form-group">
<label for="cache_dir">Cache Directory</label>
<div class="row">
<div class="col-md-6">
<input type="text" class="form-control directory-settings" id="cache_dir" name="cache_dir" value="${config['cache_dir']}">
</div>
</div>
</div>
<div class="form-group">
<label for="log_dir">Log Directory</label>
<div class="row">
<div class="col-md-6">
<input type="text" class="form-control directory-settings" id="log_dir" name="log_dir" value="${config['log_dir']}">
</div>
</div>
</div>
<p><input type="button" class="btn btn-bright save-button" value="Save" data-success="Changes saved successfully"></p>
</div>
<div role="tabpanel" class="tab-pane" id="tabs-2">
@ -437,9 +466,24 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
<div role="tabpanel" class="tab-pane" id="tabs-5">
<div class="padded-header">
<h3>Plex Media Server</h3>
<h3>Plex Media Server <small style="color: #fff;">Version <span id="pms_version">unknown</span></small></h3>
</div>
<p class="help-block">If you're using websocket monitoring, any server changes require a restart of PlexPy.</p>
<div class="checkbox">
<label>
<input type="checkbox" id="monitor_pms_updates" name="monitor_pms_updates" value="1" ${config['monitor_pms_updates']}> Monitor Plex Updates
</label>
<p class="help-block">Enable to have PlexPy check if updates are available for the Plex Media Server.<br />
Note: The Plex updater is broken on certain Plex Pass version of Plex Media Server. PlexPy will automatically disable checking for Plex updates if one of these versions is found.</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>
<span id="remoteAccessCheck" style="color: #eb8600; padding-left: 10px;"></span>
<p class="help-block">Enable to have PlexPy check if remote access to the Plex Media Server goes down.</p>
</div>
<div class="form-group has-feedback" id="pms-ip-group">
<label for="pms_ip">Plex IP or Hostname</label>
<div class="row">
@ -485,8 +529,9 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
<label for="pms_logs_folder">Logs Folder</label>
<div class="row">
<div class="col-md-6">
<input type="text" class="form-control" id="pms_logs_folder" name="pms_logs_folder" value="${config['pms_logs_folder']}" size="30" data-parsley-trigger="change">
<input type="text" class="form-control" id="pms_logs_folder" name="pms_logs_folder" value="${config['pms_logs_folder']}" size="30" data-parsley-trigger="change" data-parsley-pattern="^[^\~\%]" data-parsley-errors-container="#pms_logs_folder_error" data-parsley-error-message="Shortcuts are not recognized.">
</div>
<div id="pms_logs_folder_error" class="alert alert-danger settings-alert" role="alert"></div>
</div>
<p class="help-block">Set the complete folder path where your Plex Server logs are, shortcuts are not recognized.<br />
<a href="${anon_url('https://support.plex.tv/hc/en-us/articles/200250417-Plex-Media-Server-Log-Files')}" target="_blank">Click here</a> for help. This is required if you enable IP logging (for PMS 0.9.12 and below). </p>
@ -613,13 +658,6 @@ 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 PlexPy when something happens.</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>
<span id="remoteAccessCheck" style="color: #eb8600; padding-left: 10px;"></span>
<p class="help-block">Enable to have PlexPy check if remote access to the Plex Media Server goes down.</p>
</div>
<div class="padded-header">
<h3>History Logging</h3>
@ -749,8 +787,14 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
<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 TV Show or Artist notification for a batch of recently added Episodes or Tracks. Movies are unaffected.<br />
Note: No Season/Episode or Album/Track metadata will be available.</p>
<p class="help-block">
Enable to only get one TV Show or Artist notification for a batch of recently added Episodes or Tracks. Movies are unaffected.<br />
% if config['notify_recently_added_grandparent'] == 'Checked':
<span id="notify_recently_added_grandparent_note" style="color: #eb8600;">Note: No Season/Episode or Album/Track metadata will be available.</span>
% else:
<span id="notify_recently_added_grandparent_note">Note: No Season/Episode or Album/Track metadata will be available.</span>
% endif
</p>
</div>
<div class="form-group">
<label for="notify_recently_added_delay">Notification Delay</label>
@ -965,6 +1009,23 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</li>
</ul>
</li>
<li>
<div class="link"><i class="fa fa-refresh fa-fw"></i>&nbsp;Plex Update Available<i class="fa fa-chevron-down"></i></div>
<ul class="submenu">
<li>
<div class="form-group">
<label for="notify_on_pmsupdate_subject_text">Subject Line</label>
<input class="form-control" type="text" id="notify_on_pmsupdate_subject_text" name="notify_on_pmsupdate_subject_text" value="${config['notify_on_pmsupdate_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_extup_body_text">Message Body</label>
<textarea class="form-control" id="notify_on_pmsupdate_body_text" name="notify_on_pmsupdate_body_text" data-parsley-trigger="change" data-autoresize required>${config['notify_on_pmsupdate_body_text']}</textarea>
<p class="help-block">Set a custom body.</p>
</div>
</li>
</ul>
</li>
</ul>
<ul id="accordion-scripts" class="accordion list-unstyled">
<li>
@ -1025,24 +1086,45 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
<h4 class="modal-title">Date &amp; Time Format Options</h4>
</div>
<div class="modal-body">
<table>
<table class="notification-params time-options">
<thead>
<tr>
<th>
Year
</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center" colspan="3"><h5>Year</h5></td>
</tr>
<tr>
<td><strong>YYYY</strong></td>
<td>Numeric, 4 digits</td>
<td>Numeric, four digits</td>
<td>Eg., 1999, 2003</td>
</tr>
<tr>
<td><strong>YY</strong></td>
<td>Numeric, 2 digits</td>
<td>Numeric, two digits</td>
<td>Eg., 99, 03</td>
</tr>
</tbody>
</table>
<table class="notification-params time-options">
<thead>
<tr>
<td align="center" colspan="3"><h5>Month</h5></td>
<th>
Month
</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>MMMM</strong></td>
<td>Textual, full</td>
<td>January-December</td>
</tr>
<tr>
<td><strong>MMM</strong></td>
<td>Textual, three letters</td>
<td>Jan-Dec</td>
</tr>
<tr>
<td><strong>MM</strong></td>
@ -1054,23 +1136,41 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
<td>Numeric, without leading zeros</td>
<td>1-12</td>
</tr>
</tbody>
</table>
<table class="notification-params time-options">
<thead>
<tr>
<td><strong>MMMM</strong></td>
<td>Textual full</td>
<td>January-December</td>
<th>
Day of the Year
</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>DDDD</strong></td>
<td>Numeric, with leading zeros</td>
<td>001-365</td>
</tr>
<tr>
<td><strong>MMM</strong></td>
<td>Textual three letters</td>
<td>Jan-Dec</td>
<td><strong>DDD</strong></td>
<td>Numeric, without leading zeros</td>
<td>1-365</td>
</tr>
</tbody>
</table>
<table class="notification-params time-options">
<thead>
<tr>
<td align="center" colspan="3"><h5>Day</h5></td>
<th>
Day of the Month
</th>
</tr>
</thead>
<tbody>
<tr>
<td width="100"><strong>DD</strong></td>
<td width="300">Numeric, with leading zeros</td>
<td><strong>DD</strong></td>
<td>Numeric, with leading zeros</td>
<td>01-31</td>
</tr>
<tr>
@ -1080,57 +1180,165 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</tr>
<tr>
<td><strong>Do</strong></td>
<td>The English suffix for the day of the month</td>
<td>st, nd or th in the 1st, 2nd or 15th.</td>
<td>Numeric, with suffix</td>
<td>Eg., 1st, 2nd ... 31st.</td>
</tr>
</tbody>
</table>
<table class="notification-params time-options">
<thead>
<tr>
<td align="center" colspan="3"><h5>Time</h5></td>
<th>
Day of the Week
</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>dddd</strong></td>
<td>Textual, full</td>
<td>Sunday-Saturday</td>
</tr>
<tr>
<td><strong>a</strong></td>
<td width="300">am/pm Lowercase</td>
<td>am, pm</td>
<td><strong>ddd</strong></td>
<td>Textual, three letters</td>
<td>Sun-Sat</td>
</tr>
<tr>
<td><strong>A</strong></td>
<td>AM/PM Uppercase</td>
<td>AM, PM</td>
<td><strong>d</strong></td>
<td>Numeric</td>
<td>0-6</td>
</tr>
</tbody>
</table>
<table class="notification-params time-options">
<thead>
<tr>
<td><strong>h</strong></td>
<td>Hour, 12-hour, without leading zeros</td>
<td>1-12</td>
</tr>
<tr>
<td><strong>hh</strong></td>
<td>Hour, 12-hour, with leading zeros</td>
<td>01-12</td>
</tr>
<tr>
<td><strong>H</strong></td>
<td>Hour, 24-hour, without leading zeros</td>
<td>0-23</td>
<th>
Hour
</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>HH</strong></td>
<td>Hour, 24-hour, with leading zeros</td>
<td>24-hour, with leading zeros</td>
<td>00-23</td>
</tr>
<tr>
<td><strong>H</strong></td>
<td>24-hour, without leading zeros</td>
<td>0-23</td>
</tr>
<tr>
<td><strong>hh</strong></td>
<td>12-hour, with leading zeros</td>
<td>01-12</td>
</tr>
<tr>
<td><strong>h</strong></td>
<td>12-hour, without leading zeros</td>
<td>1-12</td>
</tr>
</tbody>
</table>
<table class="notification-params time-options">
<thead>
<tr>
<th>
Minute
</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>mm</strong></td>
<td>Minutes, with leading zeros</td>
<td>Numeric, with leading zeros</td>
<td>00-59</td>
</tr>
<tr>
<td><strong>m</strong></td>
<td>Numeric, without leading zeros</td>
<td>0-59</td>
</tr>
</tbody>
</table>
<table class="notification-params time-options">
<thead>
<tr>
<th>
Second
</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>ss</strong></td>
<td>Seconds, with leading zeros</td>
<td>Numeric, with leading zeros</td>
<td>00-59</td>
</tr>
<tr>
<td><strong>zz</strong></td>
<td>Timezone abbreviation</td>
<td>Eg., EST, MDT ...</td>
<td><strong>s</strong></td>
<td>Numeric, without leading zeros</td>
<td>0-59</td>
</tr>
</tbody>
</table>
<table class="notification-params time-options">
<thead>
<tr>
<th>
AM / PM
</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>A</strong></td>
<td>AM/PM uppercase</td>
<td>AM, PM</td>
</tr>
<tr>
<td><strong>a</strong></td>
<td width="300">am/pm lowercase</td>
<td>am, pm</td>
</tr>
</tbody>
</table>
<table class="notification-params time-options">
<thead>
<tr>
<th>
Timezone
</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>ZZ</strong></td>
<td>UTC offset</td>
<td>Eg., +0100, -0700</td>
</tr>
<tr>
<td><strong>Z</strong></td>
<td>UTC offset</td>
<td>Eg., +01:00, -07:00</td>
</tr>
</tbody>
</table>
<table class="notification-params time-options">
<thead>
<tr>
<th>
Timestamp
</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>X</strong></td>
<td>Unix timestamp</td>
<td>Eg., 1456887825</td>
</tr>
</tbody>
</table>
@ -1215,6 +1423,10 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
<td><strong>{server_uptime}</strong></td>
<td>The uptime (in days, hours, mins, secs) of your Plex Server.</td>
</tr>
<tr>
<td><strong>{server_version}</strong></td>
<td>The current version of your Plex Server.</td>
</tr>
<tr>
<td><strong>{action}</strong></td>
<td>The action that triggered the notification.</td>
@ -1407,7 +1619,7 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</tr>
<tr>
<td><strong>{artist_name}</strong></td>
<td>The name of the artistd.</td>
<td>The name of the artist.</td>
</tr>
<tr>
<td><strong>{album_name}</strong></td>
@ -1521,7 +1733,7 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</tr>
<tr>
<td><strong>{lastfm_url}</strong></td>
<td>The last.fm URL for the album.
<td>The Last.fm URL for the album.
<p class="small-muted">(PMS agent must be Last.fm)</p></td>
</tr>
<tr>
@ -1546,6 +1758,29 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</tr>
</tbody>
</table>
<table class="notification-params">
<thead>
<tr>
<th>
Plex Update Available
</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>{update_version}</strong></td>
<td>The available update version for your Plex Server.</td>
</tr>
<tr>
<td><strong>{update_url}</strong></td>
<td>The available update download URL.</td>
</tr>
<tr>
<td><strong>{update_changelog}</strong></td>
<td>The changelog for the available update.</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="modal-footer"></div>
@ -1650,10 +1885,11 @@ $(document).ready(function() {
authChanged = false;
httpChanged = false;
monitorChanged = false;
directoryChanged = false;
// Alert the user that their changes require a restart.
function postSaveChecks() {
if ((serverChanged && $('#monitoring_use_websocket').is(":checked")) || authChanged || httpChanged || monitorChanged) {
if ((serverChanged && $('#monitoring_use_websocket').is(":checked")) || authChanged || httpChanged || monitorChanged || directoryChanged) {
$('#restart-modal').modal('show');
}
}
@ -1766,6 +2002,10 @@ $(document).ready(function() {
monitorChanged = true;
});
$( ".directory-settings" ).change(function() {
directoryChanged = true;
});
$( ".pms-settings" ).change(function() {
serverChanged = true;
$("#pms_identifier").val("");
@ -1899,50 +2139,62 @@ $(document).ready(function() {
$.get("/osxnotifyregister", { 'app': osx_notify_app }, function (data) { showMsg("<div class='msg'><span class='ui-icon ui-icon-check'></span>" + data + "</div>", false, true, 3000); });
})
pms_version = false;
pms_logs_debug = false;
pms_logs = false;
// Checks to see if PMS server version is >= 0.9.14 with automaatically logged IP addresses
$.ajax({
url: 'get_server_identity',
async: true,
success: function(data) {
var version = data.version.split('.')
if (parseInt(version[0]) >= 0 && parseInt(version[1]) >= 9 && parseInt(version[2]) >= 14) {
if (data.version){ $("#pms_version").text(data.version); }
var version = (data.version ? data.version.split('.') : null);
if (version && parseInt(version[0]) >= 0 && parseInt(version[1]) >= 9 && parseInt(version[2]) >= 14) {
$("#debugLogCheck").html("IP address is automatically logged for PMS version 0.9.14 and above.");
$("#ip_logging_enable").attr("disabled", true);
$("#ip_logging_enable").attr("checked", true);
pms_version = true;
checkLogsPath();
} else {
// Check to see if debug logs are enabled on the PMS.
$.ajax({
url: 'get_server_pref',
data: { pref: 'logDebug' },
async: true,
success: function(data) {
if (data !== 'true') {
$("#debugLogCheck").html("Debug logging must be enabled on your Plex Server. <a target='_blank' href='${anon_url('https://support.plex.tv/hc/en-us/articles/201643703-Reporting-issues-with-Plex-Media-Server')}'>Click here</a> for help.");
$("#ip_logging_enable").attr("disabled", true);
$("#ip_logging_enable").attr("checked", false);
}
pms_logs_debug = (data == 'true' ? true : false);
// Check to see if our logs folder is set before allowing IP logging to be enabled.
checkLogsPath();
}
});
// Check to see if our logs folder is set before allowing IP logging to be enabled.
checkLogsPath();
$("#pms_logs_folder").change(function() {
checkLogsPath();
});
function checkLogsPath() {
if ($("#pms_logs_folder").val() == '') {
$("#debugLogCheck").html("You must first define your Plex Server Logs folder path under the Plex Media Server tab.");
$("#ip_logging_enable").attr("disabled", true);
$("#ip_logging_enable").attr("checked", false);
} else {
$("#ip_logging_enable").attr("disabled", false);
$("#debugLogCheck").html("");
}
}
}
}
});
$("#pms_logs_folder").change(function() {
checkLogsPath();
});
function checkLogsPath() {
pms_logs = ($("#pms_logs_folder").val() == '' ? false : true);
// Toggle IP logging checkbox depending on debug logs, and logs path
if (!(pms_version)) {
if (pms_logs_debug && pms_logs) {
$("#ip_logging_enable").attr("disabled", false);
$("#debugLogCheck").html("");
} else if (!(pms_logs_debug)) {
$("#debugLogCheck").html("Debug logging must be enabled on your Plex Server. <a target='_blank' href='${anon_url('https://support.plex.tv/hc/en-us/articles/201643703-Reporting-issues-with-Plex-Media-Server')}'>Click here</a> for help.");
$("#ip_logging_enable").attr("disabled", true);
$("#ip_logging_enable").attr("checked", false);
} else {
$("#debugLogCheck").html("You must first define your Plex Server Logs folder path under the Plex Media Server tab.");
$("#ip_logging_enable").attr("disabled", true);
$("#ip_logging_enable").attr("checked", false);
}
}
}
$.ajax({
url: 'get_server_pref',
data: { pref: 'PublishServerOnPlexOnlineKey' },
@ -2055,6 +2307,10 @@ $(document).ready(function() {
}
getSchedulerTable();
$("#notify_recently_added_grandparent").change(function () {
var c = this.checked ? '#eb8600' : '#737373';
$('#notify_recently_added_grandparent_note').css('color', c);
});
});
</script>
</%def>

View file

@ -112,8 +112,16 @@ from plexpy import helpers
<div class="row">
<div class="col-md-12">
<div class="table-card-header">
<ul class="nav nav-header nav-dashboard pull-right">
<li>
<a href="#" id="recently-watched-page-left" class="paginate btn-gray disabled" data-id="+1"><i class="fa fa-lg fa-chevron-left"></i></a>
</li>
<li>
<a href="#" id="recently-watched-page-right" class="paginate btn-gray" data-id="-1"><i class="fa fa-lg fa-chevron-right"></i></a>
</li>
</ul>
<div class="header-bar">
<span><i class="fa fa-history"></i> Recently Watched</span>
<span><i class="fa fa-history"></i> Recently Played</span>
</div>
</div>
<div class="table-card-back">
@ -140,7 +148,7 @@ from plexpy import helpers
</div>
</div>
<div class="table-card-back">
<table id="user_ip_table" class="display" width="100%">
<table class="display user_ip_table" id="user_ip_table-UID-${data['user_id']}" width="100%">
<thead>
<tr>
<th align="left">Last Seen</th>
@ -178,7 +186,7 @@ from plexpy import helpers
</div>
</div>
<div class="table-card-back">
<table class="display" id="history_table" width="100%">
<table class="display history_table" id="history_table-UID-${data['user_id']}" width="100%">
<thead>
<tr>
<th align="left" id="delete">Delete</th>
@ -218,7 +226,7 @@ from plexpy import helpers
</div>
</div>
<div class="table-card-back">
<table class="display" id="sync_table" width="100%">
<table class="display" id="sync_table-UID-${data['user_id']}" width="100%">
<thead>
<tr>
<th align="left" id="state">State</th>
@ -348,13 +356,13 @@ from plexpy import helpers
};
}
}
history_table = $('#history_table').DataTable(history_table_options);
history_table = $('#history_table-UID-${data["user_id"]}').DataTable(history_table_options);
history_table.column(2).visible(false);
var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 11] });
$(colvis.button()).appendTo('#button-bar-history');
clearSearchButton('history_table', history_table);
clearSearchButton('history_table-UID-${data["user_id"]}', history_table);
$('#history_table_filter').prepend('<div class="btn-group" data-toggle="buttons" id="media_type-selection" style="padding-right: 15px;"> \
<label class="btn btn-dark active"> \
@ -397,9 +405,9 @@ from plexpy import helpers
};
}
}
user_ip_table = $('#user_ip_table').DataTable(user_ip_table_options);
user_ip_table = $('#user_ip_table-UID-${data["user_id"]}').DataTable(user_ip_table_options);
clearSearchButton('user_ip_table', user_ip_table);
clearSearchButton('user_ip_table-UID-${data["user_id"]}', user_ip_table);
});
$( "#sync-tab-btn" ).one( "click", function() {
@ -410,13 +418,13 @@ from plexpy import helpers
d.user_id = user_id;
}
}
sync_table = $('#sync_table').DataTable(sync_table_options);
sync_table = $('#sync_table-UID-${data["user_id"]}').DataTable(sync_table_options);
sync_table.column(1).visible(false);
var colvis_sync = new $.fn.dataTable.ColVis( sync_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark' } );
$( colvis_sync.button() ).appendTo('#button-bar-sync');
clearSearchButton('sync_table', sync_table);
clearSearchButton('sync_table-UID-${data["user_id"]}', sync_table);
});
// Load edit user modal
@ -471,32 +479,60 @@ from plexpy import helpers
});
function recentlyWatched() {
var widthVal = $("#user-recently-watched").width();
var tmp = (widthVal-25) / 175;
if (tmp > 0) {
var containerSize = parseInt(tmp);
} else {
var containerSize = 1;
}
// Populate recently watched
$.ajax({
url: 'get_user_recently_watched',
async: true,
data: {
user_id: user_id,
limit: containerSize
limit: 50
},
complete: function(xhr, status) {
$("#user-recently-watched").html(xhr.responseText);
highlightWatchedScrollerButton();
}
});
}
recentlyWatched();
function highlightWatchedScrollerButton() {
var scroller = $("#recently-watched-row-scroller");
var numElems = scroller.find("li").length;
scroller.width(numElems * 175);
if (scroller.width() > $("#user-recently-watched").width()) {
$("#recently-watched-page-right").removeClass("disabled");
} else {
$("#recently-watched-page-right").addClass("disabled");
}
}
$(window).resize(function() {
recentlyWatched();
highlightWatchedScrollerButton();
});
var leftTotal = 0;
$(".paginate").click(function (e) {
e.preventDefault();
var scroller = $("#recently-watched-row-scroller");
var containerWidth = $("#user-recently-watched").width();
var scrollAmount = $(this).data("id") * parseInt(containerWidth / 175) * 175;
var leftMax = Math.min(-parseInt(scroller.width()) + Math.abs(scrollAmount), 0);
leftTotal = Math.max(Math.min(leftTotal + scrollAmount, 0), leftMax);
scroller.animate({ left: leftTotal }, 250);
if (leftTotal == 0) {
$("#recently-watched-page-left").addClass("disabled").blur();
} else {
$("#recently-watched-page-left").removeClass("disabled");
}
if (leftTotal == leftMax) {
$("#recently-watched-page-right").addClass("disabled").blur();
} else {
$("#recently-watched-page-right").removeClass("disabled");
}
});
});
</script>

View file

@ -28,56 +28,58 @@ DOCUMENTATION :: END
% if data:
<div class="dashboard-recent-media-row">
<ul class="dashboard-recent-media list-unstyled">
% for item in data:
<li>
% if item['media_type'] == 'episode' or item['media_type'] == 'movie':
<a href="info?source=history&rating_key=${item['rating_key']}">
<div class="dashboard-recent-media-poster">
<div class="dashboard-recent-media-poster-face" style="background-image: url(pms_image_proxy?img=${item['thumb']}&width=300&height=450&fallback=poster);">
<div class="dashboard-recent-media-overlay">
<div class="dashboard-recent-media-overlay-text" id="time-${item['time']}">
<script>
$('#time-${item['time']}').text('Watched ' + moment(${item['time']}, "X").fromNow())
</script>
<div id="recently-watched-row-scroller" style="left: 0;">
<ul class="dashboard-recent-media list-unstyled">
% for item in data:
<li>
% if item['media_type'] == 'episode' or item['media_type'] == 'movie':
<a href="info?source=history&rating_key=${item['rating_key']}">
<div class="dashboard-recent-media-poster">
<div class="dashboard-recent-media-poster-face" style="background-image: url(pms_image_proxy?img=${item['thumb']}&width=300&height=450&fallback=poster);">
<div class="dashboard-recent-media-overlay">
<div class="dashboard-recent-media-overlay-text" id="time-${item['time']}">
<script>
$('#time-${item['time']}').text('Watched ' + moment(${item['time']}, "X").fromNow())
</script>
</div>
</div>
</div>
</div>
</div>
<div class="dashboard-recent-media-metacontainer">
% if item['media_type'] == 'episode':
<h3 title="${item['grandparent_title']}">${item['grandparent_title']}</h3>
<h3 title="${item['title']}">${item['title']}</h3>
<h3 class="text-muted">S${item['parent_media_index']} &middot; E${item['media_index']}</h3>
% elif item['media_type'] == 'movie':
<h3 title="${item['title']}">${item['title']}</h3>
<h3 class="text-muted">${item['year']}</h3>
% endif
</div>
</a>
% elif item['media_type'] == 'track':
<a href="info?source=history&rating_key=${item['rating_key']}">
<div class="dashboard-recent-media-cover">
<div class="dashboard-recent-media-cover-face" style="background-image: url(pms_image_proxy?img=${item['thumb']}&width=300&height=300&fallback=cover);">
<div class="dashboard-recent-media-overlay">
<div class="dashboard-recent-media-overlay-text" id="time-${item['time']}">
<script>
$('#time-${item['time']}').text('Watched ' + moment(${item['time']}, "X").fromNow())
</script>
<div class="dashboard-recent-media-metacontainer">
% if item['media_type'] == 'episode':
<h3 title="${item['grandparent_title']}">${item['grandparent_title']}</h3>
<h3 title="${item['title']}">${item['title']}</h3>
<h3 class="text-muted">S${item['parent_media_index']} &middot; E${item['media_index']}</h3>
% elif item['media_type'] == 'movie':
<h3 title="${item['title']}">${item['title']}</h3>
<h3 class="text-muted">${item['year']}</h3>
% endif
</div>
</a>
% elif item['media_type'] == 'track':
<a href="info?source=history&rating_key=${item['rating_key']}">
<div class="dashboard-recent-media-cover">
<div class="dashboard-recent-media-cover-face" style="background-image: url(pms_image_proxy?img=${item['thumb']}&width=300&height=300&fallback=cover);">
<div class="dashboard-recent-media-overlay">
<div class="dashboard-recent-media-overlay-text" id="time-${item['time']}">
<script>
$('#time-${item['time']}').text('Watched ' + moment(${item['time']}, "X").fromNow())
</script>
</div>
</div>
</div>
</div>
</div>
<div class="dashboard-recent-media-metacontainer">
<h3 title="${item['grandparent_title']}">${item['grandparent_title']}</h3>
<h3 title="${item['title']}">${item['title']}</h3>
<h3 class="text-muted">${item['parent_title']}</h3>
</div>
</a>
% endif
</li>
% endfor
</ul>
<div class="dashboard-recent-media-metacontainer">
<h3 title="${item['grandparent_title']}">${item['grandparent_title']}</h3>
<h3 title="${item['title']}">${item['title']}</h3>
<h3 class="text-muted">${item['parent_title']}</h3>
</div>
</a>
% endif
</li>
% endfor
</ul>
</div>
</div>
% else:
<div class="text-muted">No stats to show.</div><br>

View file

@ -33,12 +33,13 @@ load_rc_config ${name}
: ${plexpy_dir:="/usr/local/plexpy"}
: ${plexpy_chdir:="${plexpy_dir}"}
: ${plexpy_pid:="${plexpy_dir}/plexpy.pid"}
: ${plexpy_flags:=""}
status_cmd="${name}_status"
stop_cmd="${name}_stop"
command="/usr/sbin/daemon"
command_args="python2 ${plexpy_dir}/PlexPy.py --daemon --pidfile ${plexpy_pid} --quiet --nolaunch"
command="${plexpy_dir}/PlexPy.py"
command_args="--daemon --pidfile ${plexpy_pid} --quiet --nolaunch ${plexpy_flags}"
# Ensure user is root when running this script.
if [ `id -u` != "0" ]; then

5
init-scripts/init.freenas Normal file → Executable file
View file

@ -33,12 +33,13 @@ load_rc_config ${name}
: ${plexpy_dir:="/usr/local/share/plexpy"}
: ${plexpy_chdir:="${plexpy_dir}"}
: ${plexpy_pid:="${plexpy_dir}/plexpy.pid"}
: ${plexpy_flags:=""}
status_cmd="${name}_status"
stop_cmd="${name}_stop"
command="/usr/sbin/daemon"
command_args="python2 ${plexpy_dir}/PlexPy.py --daemon --pidfile ${plexpy_pid} --quiet --nolaunch"
command="${plexpy_dir}/PlexPy.py"
command_args="--daemon --pidfile ${plexpy_pid} --quiet --nolaunch ${plexpy_flags}"
# Ensure user is root when running this script.
if [ `id -u` != "0" ]; then

View file

@ -103,7 +103,7 @@ def initialize(config_file):
if not CONFIG.HTTPS_KEY:
CONFIG.HTTPS_KEY = os.path.join(DATA_DIR, 'server.key')
if not CONFIG.LOG_DIR.startswith(os.path.abspath(DATA_DIR)):
if not CONFIG.LOG_DIR:
CONFIG.LOG_DIR = os.path.join(DATA_DIR, 'logs')
if not os.path.exists(CONFIG.LOG_DIR):
@ -120,8 +120,7 @@ def initialize(config_file):
logger.initLogger(console=not QUIET, log_dir=CONFIG.LOG_DIR,
verbose=VERBOSE)
if not CONFIG.BACKUP_DIR.startswith(os.path.abspath(DATA_DIR)):
# Put the backup dir in the data dir for now
if not CONFIG.BACKUP_DIR:
CONFIG.BACKUP_DIR = os.path.join(DATA_DIR, 'backups')
if not os.path.exists(CONFIG.BACKUP_DIR):
try:
@ -129,14 +128,13 @@ def initialize(config_file):
except OSError as e:
logger.error("Could not create backup dir '%s': %s", BACKUP_DIR, e)
if not CONFIG.CACHE_DIR.startswith(os.path.abspath(DATA_DIR)):
# Put the cache dir in the data dir for now
if not CONFIG.CACHE_DIR:
CONFIG.CACHE_DIR = os.path.join(DATA_DIR, 'cache')
if not os.path.exists(CONFIG.CACHE_DIR):
try:
os.makedirs(CONFIG.CACHE_DIR)
except OSError as e:
logger.error("Could not create cache dir '%s': %s", DATA_DIR, e)
logger.error("Could not create cache dir '%s': %s", CACHE_DIR, e)
# Initialize the database
logger.info('Checking to see if the database has all tables....')
@ -306,6 +304,13 @@ def initialize_scheduler():
schedule_job(activity_pinger.check_recently_added, 'Check for recently added items',
hours=0, minutes=0, seconds=0)
if CONFIG.MONITOR_PMS_UPDATES:
schedule_job(activity_pinger.check_server_updates, 'Check for Plex updates',
hours=12, minutes=0, seconds=0)
else:
schedule_job(activity_pinger.check_server_updates, 'Check for Plex updates',
hours=0, minutes=0, seconds=0)
if CONFIG.MONITOR_REMOTE_ACCESS:
schedule_job(activity_pinger.check_server_response, 'Check for Plex remote access',
hours=0, minutes=0, seconds=seconds)
@ -390,7 +395,7 @@ def dbcheck():
# sessions table :: This is a temp table that logs currently active sessions
c_db.execute(
'CREATE TABLE IF NOT EXISTS sessions (id INTEGER PRIMARY KEY AUTOINCREMENT, '
'session_key INTEGER, rating_key INTEGER, section_id INTEGER, media_type TEXT, started INTEGER, '
'session_key INTEGER, rating_key INTEGER, section_id INTEGER, media_type TEXT, started INTEGER, stopped INTEGER, '
'paused_counter INTEGER DEFAULT 0, state TEXT, user_id INTEGER, user TEXT, friendly_name TEXT, '
'ip_address TEXT, machine_id TEXT, player TEXT, platform TEXT, title TEXT, parent_title TEXT, '
'grandparent_title TEXT, parent_rating_key INTEGER, grandparent_rating_key INTEGER, '
@ -413,8 +418,8 @@ def dbcheck():
# session_history_media_info table :: This is a table which logs each session's media info
c_db.execute(
'CREATE TABLE IF NOT EXISTS session_history_media_info (id INTEGER PRIMARY KEY, '
'rating_key INTEGER, video_decision TEXT, audio_decision TEXT, duration INTEGER DEFAULT 0, width INTEGER, '
'CREATE TABLE IF NOT EXISTS session_history_media_info (id INTEGER PRIMARY KEY, rating_key INTEGER, '
'video_decision TEXT, audio_decision TEXT, transcode_decision TEXT, duration INTEGER DEFAULT 0, width INTEGER, '
'height INTEGER, container TEXT, video_codec TEXT, audio_codec TEXT, bitrate INTEGER, video_resolution TEXT, '
'video_framerate TEXT, aspect_ratio TEXT, audio_channels INTEGER, transcode_protocol TEXT, '
'transcode_container TEXT, transcode_video_codec TEXT, transcode_audio_codec TEXT, '
@ -614,6 +619,15 @@ def dbcheck():
'ALTER TABLE sessions ADD COLUMN section_id INTEGER'
)
# Upgrade sessions table from earlier versions
try:
c_db.execute('SELECT stopped FROM sessions')
except sqlite3.OperationalError:
logger.debug(u"Altering database. Updating database table sessions.")
c_db.execute(
'ALTER TABLE sessions ADD COLUMN stopped INTEGER'
)
# Upgrade session_history table from earlier versions
try:
c_db.execute('SELECT reference_id FROM session_history')
@ -664,6 +678,21 @@ def dbcheck():
'ALTER TABLE session_history_metadata ADD COLUMN section_id INTEGER'
)
# Upgrade session_history_media_info table from earlier versions
try:
c_db.execute('SELECT transcode_decision FROM session_history_media_info')
except sqlite3.OperationalError:
logger.debug(u"Altering database. Updating database table session_history_media_info.")
c_db.execute(
'ALTER TABLE session_history_media_info ADD COLUMN transcode_decision TEXT'
)
c_db.execute(
'UPDATE session_history_media_info SET transcode_decision = (CASE '
'WHEN video_decision = "transcode" OR audio_decision = "transcode" THEN "transcode" '
'WHEN video_decision = "copy" OR audio_decision = "copy" THEN "copy" '
'WHEN video_decision = "direct play" OR audio_decision = "direct play" THEN "direct play" END)'
)
# Upgrade users table from earlier versions
try:
c_db.execute('SELECT do_notify FROM users')
@ -890,7 +919,13 @@ def shutdown(restart=False, update=False):
if '--nolaunch' not in args:
args += ['--nolaunch']
logger.info('Restarting PlexPy with %s', args)
os.execv(exe, args)
# os.execv fails with spaced names on Windows
# https://bugs.python.org/issue19066
if os.name == 'nt':
subprocess.Popen(args, cwd=os.getcwd())
else:
os.execv(exe, args)
os._exit(0)

View file

@ -16,7 +16,7 @@
import time
import plexpy
from plexpy import logger, pmsconnect, activity_processor, threading, notification_handler, helpers
from plexpy import logger, pmsconnect, activity_processor, threading, notification_handler, helpers, notifiers
class ActivityHandler(object):
@ -57,9 +57,11 @@ class ActivityHandler(object):
if self.is_valid_session() and self.get_live_session():
logger.debug(u"PlexPy ActivityHandler :: Session %s has started." % str(self.get_session_key()))
# Fire off notifications
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=self.get_live_session(), notify_action='play')).start()
# 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()
# Write the new session to our temp session table
self.update_db_session()
@ -77,20 +79,24 @@ class ActivityHandler(object):
if not force_stop:
ap.set_session_state(session_key=self.get_session_key(),
state=self.timeline['state'],
view_offset=self.timeline['viewOffset'])
view_offset=self.timeline['viewOffset'],
stopped=int(time.time()))
# Retrieve the session data from our temp table
db_session = ap.get_session_by_key(session_key=self.get_session_key())
# Fire off notifications
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=db_session, notify_action='stop')).start()
# Check if any notification agents have notifications enabled
if any(d['on_stop'] for d in notifiers.available_notification_agents()):
# Fire off notifications
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=db_session, notify_action='stop')).start()
# Write it to the history table
monitor_proc = activity_processor.ActivityProcessor()
monitor_proc.write_session_history(session=db_session)
# Remove the session from our temp session table
logger.debug(u"PlexPy ActivityHandler :: Removing session %s from session queue" % str(self.get_session_key()))
ap.delete_session(session_key=self.get_session_key())
def on_pause(self):
@ -109,9 +115,11 @@ class ActivityHandler(object):
# Retrieve the session data from our temp table
db_session = ap.get_session_by_key(session_key=self.get_session_key())
# Fire off notifications
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=db_session, notify_action='pause')).start()
# Check if any notification agents have notifications enabled
if any(d['on_pause'] for d in notifiers.available_notification_agents()):
# Fire off notifications
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=db_session, notify_action='pause')).start()
def on_resume(self):
if self.is_valid_session():
@ -129,9 +137,11 @@ class ActivityHandler(object):
# Retrieve the session data from our temp table
db_session = ap.get_session_by_key(session_key=self.get_session_key())
# Fire off notifications
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=db_session, notify_action='resume')).start()
# Check if any notification agents have notifications enabled
if any(d['on_resume'] for d in notifiers.available_notification_agents()):
# Fire off notifications
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=db_session, notify_action='resume')).start()
def on_buffer(self):
if self.is_valid_session():
@ -159,8 +169,11 @@ class ActivityHandler(object):
if plexpy.CONFIG.BUFFER_THRESHOLD > 0 and (current_buffer_count >= plexpy.CONFIG.BUFFER_THRESHOLD and \
time_since_last_trigger == 0 or time_since_last_trigger >= plexpy.CONFIG.BUFFER_WAIT):
ap.set_session_buffer_trigger_time(session_key=self.get_session_key())
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=db_stream, notify_action='buffer')).start()
# Check if any notification agents have notifications enabled
if any(d['on_buffer'] for d in notifiers.available_notification_agents()):
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=db_stream, notify_action='buffer')).start()
# This function receives events from our websocket connection
def process(self):
@ -202,10 +215,17 @@ class ActivityHandler(object):
# Monitor if the stream has reached the watch percentage for notifications
# The only purpose of this is for notifications
progress_percent = helpers.get_percent(self.timeline['viewOffset'], db_session['duration'])
if progress_percent >= plexpy.CONFIG.NOTIFY_WATCHED_PERCENT and this_state != 'buffering':
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=db_session, notify_action='watched')).start()
# Check if any notification agents have notifications enabled
notify_agents = [d['id'] for d in notifiers.available_notification_agents() if d['on_watched']]
# Get the current states for notifications from our db
notified_agents = [d['agent_id'] for d in notification_handler.get_notify_state(session=db_session)
if d['notify_action'] == 'watched'] if notify_agents else []
if any(a not in notified_agents for a in notify_agents):
progress_percent = helpers.get_percent(self.timeline['viewOffset'], db_session['duration'])
if progress_percent >= plexpy.CONFIG.NOTIFY_WATCHED_PERCENT and this_state != 'buffering':
# Rather not put this on it's own thread so we know it completes before our next event.
notification_handler.notify(stream_data=db_session, notify_action='watched')
else:
# We don't have this session in our table yet, start a new one.

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, pmsconnect, plextv, notification_handler, database, helpers, activity_processor, libraries
from plexpy import logger, pmsconnect, plextv, notification_handler, database, helpers, activity_processor, libraries, notifiers
import threading
import plexpy
@ -38,9 +38,12 @@ def check_active_sessions(ws_request=False):
if session_list:
if int_ping_count >= 3:
logger.info(u"PlexPy Monitor :: The Plex Media Server is back up.")
# Fire off notifications
threading.Thread(target=notification_handler.notify_timeline,
kwargs=dict(notify_action='intup')).start()
# Check if any notification agents have notifications enabled
if any(d['on_intup'] for d in notifiers.available_notification_agents()):
# Fire off notifications
threading.Thread(target=notification_handler.notify_timeline,
kwargs=dict(notify_action='intup')).start()
int_ping_count = 0
media_container = session_list['sessions']
@ -58,16 +61,24 @@ def check_active_sessions(ws_request=False):
# Here we can check the play states
if session['state'] != stream['state']:
if session['state'] == 'paused':
# 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()
logger.debug(u"PlexPy Monitor :: Session %s has been paused." % stream['session_key'])
# Check if any notification agents have notifications enabled
if any(d['on_pause'] for d in notifiers.available_notification_agents()):
# 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 session['state'] == 'playing' and stream['state'] == 'paused':
# 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='resume')).start()
logger.debug(u"PlexPy Monitor :: Session %s has been resumed." % stream['session_key'])
# Check if any notification agents have notifications enabled
if any(d['on_resume'] for d in notifiers.available_notification_agents()):
# 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='resume')).start()
if stream['state'] == 'paused' and not ws_request:
# The stream is still paused so we need to increment the paused_counter
@ -105,8 +116,12 @@ def check_active_sessions(ws_request=False):
'WHERE session_key = ? AND rating_key = ?',
[stream['session_key'], stream['rating_key']])
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=stream, notify_action='buffer')).start()
# Check if any notification agents have notifications enabled
if any(d['on_buffer'] for d in notifiers.available_notification_agents()):
# 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='buffer')).start()
else:
# Subsequent buffer notifications after wait time
if int(time.time()) > buffer_values[0]['buffer_last_triggered'] + \
@ -119,11 +134,16 @@ def check_active_sessions(ws_request=False):
'WHERE session_key = ? AND rating_key = ?',
[stream['session_key'], stream['rating_key']])
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=stream, notify_action='buffer')).start()
# Check if any notification agents have notifications enabled
if any(d['on_buffer'] for d in notifiers.available_notification_agents()):
# 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='buffer')).start()
logger.debug(u"PlexPy Monitor :: Stream buffering. Count is now %s. Last triggered %s."
% (buffer_values[0]['buffer_count'],
logger.debug(u"PlexPy Monitor :: Session %s is buffering. Count is now %s. Last triggered %s."
% (stream['session_key'],
buffer_values[0]['buffer_count'],
buffer_values[0]['buffer_last_triggered']))
# Check if the user has reached the offset in the media we defined as the "watched" percent
@ -132,37 +152,61 @@ def check_active_sessions(ws_request=False):
if session['view_offset'] and session['duration'] and session['state'] != 'buffering':
if helpers.get_percent(session['view_offset'],
session['duration']) > plexpy.CONFIG.NOTIFY_WATCHED_PERCENT:
# Check if any notification agents have notifications enabled
if any(d['on_watched'] for d in notifiers.available_notification_agents()):
# 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
if stream['state'] != 'stopped':
logger.debug(u"PlexPy Monitor :: Session %s has stopped." % stream['session_key'])
# Set the stream stop time
stream['stopped'] = int(time.time())
monitor_db.action('UPDATE sessions SET stopped = ?, state = ? '
'WHERE session_key = ? AND rating_key = ?',
[stream['stopped'], 'stopped', stream['session_key'], stream['rating_key']])
# 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:
# Check if any notification agents have notifications enabled
if any(d['on_watched'] for d in notifiers.available_notification_agents()):
# 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']])
# 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
# Check if any notification agents have notifications enabled
if any(d['on_stop'] for d in notifiers.available_notification_agents()):
# 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()
kwargs=dict(stream_data=stream, notify_action='stop')).start()
# Write the item history on playback stop
monitor_process.write_session_history(session=stream)
success = monitor_process.write_session_history(session=stream)
if success:
# If session is written to the databaase successfully, remove the session from the session table
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']])
else:
logger.warn(u"PlexPy Monitor :: Failed to write sessionKey %s ratingKey %s to the database. " \
"Will try again on the next pass." % (stream['session_key'], stream['rating_key']))
# Process the newly received session data
for session in media_container:
monitor_process.write_session(session)
new_session = monitor_process.write_session(session)
if new_session:
logger.debug(u"PlexPy Monitor :: Session %s has started." % session['session_key'])
else:
logger.debug(u"PlexPy Monitor :: Unable to read session list.")
@ -171,9 +215,11 @@ def check_active_sessions(ws_request=False):
% str(int_ping_count))
if int_ping_count == 3:
# Fire off notifications
threading.Thread(target=notification_handler.notify_timeline,
kwargs=dict(notify_action='intdown')).start()
# Check if any notification agents have notifications enabled
if any(d['on_intdown'] for d in notifiers.available_notification_agents()):
# Fire off notifications
threading.Thread(target=notification_handler.notify_timeline,
kwargs=dict(notify_action='intdown')).start()
def check_recently_added():
@ -225,9 +271,12 @@ def check_recently_added():
if 0 < time_threshold - int(item['added_at']) <= time_interval:
logger.debug(u"PlexPy Monitor :: Library item %s has been added to Plex." % str(item['rating_key']))
# Fire off notifications
threading.Thread(target=notification_handler.notify_timeline,
kwargs=dict(timeline_data=item, notify_action='created')).start()
# Check if any notification agents have notifications enabled
if any(d['on_created'] for d in notifiers.available_notification_agents()):
# 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'])
@ -243,9 +292,12 @@ def check_recently_added():
% str(item['rating_key']))
logger.debug(u"PlexPy Monitor :: Library item %s has been added to Plex." % str(item['rating_key']))
# Fire off notifications
threading.Thread(target=notification_handler.notify_timeline,
kwargs=dict(timeline_data=item, notify_action='created')).start()
# Check if any notification agents have notifications enabled
if any(d['on_created'] for d in notifiers.available_notification_agents()):
# Fire off notifications
threading.Thread(target=notification_handler.notify_timeline,
kwargs=dict(timeline_data=item, notify_action='created')).start()
def check_server_response():
@ -275,12 +327,44 @@ def check_server_response():
else:
if ext_ping_count >= 3:
logger.info(u"PlexPy Monitor :: Plex remote access is back up.")
# Fire off notifications
threading.Thread(target=notification_handler.notify_timeline,
kwargs=dict(notify_action='extup')).start()
# Check if any notification agents have notifications enabled
if any(d['on_extup'] for d in notifiers.available_notification_agents()):
# Fire off notifications
threading.Thread(target=notification_handler.notify_timeline,
kwargs=dict(notify_action='extup')).start()
ext_ping_count = 0
if ext_ping_count == 3:
# Fire off notifications
threading.Thread(target=notification_handler.notify_timeline,
kwargs=dict(notify_action='extdown')).start()
# Check if any notification agents have notifications enabled
if any(d['on_extdown'] for d in notifiers.available_notification_agents()):
# Fire off notifications
threading.Thread(target=notification_handler.notify_timeline,
kwargs=dict(notify_action='extdown')).start()
def check_server_updates():
with monitor_lock:
logger.info(u"PlexPy Monitor :: Checking for PMS updates...")
pms_connect = pmsconnect.PmsConnect()
server_identity = pms_connect.get_server_identity()
update_status = pms_connect.get_update_staus()
if server_identity and update_status:
version = server_identity['version']
logger.info(u"PlexPy Monitor :: Current PMS version: %s", version)
if update_status['state'] == 'available':
update_version = update_status['version']
logger.info(u"PlexPy Monitor :: PMS update available version: %s", update_version)
# Check if any notification agents have notifications enabled
if any(d['on_pmsupdate'] for d in notifiers.available_notification_agents()):
# Fire off notifications
threading.Thread(target=notification_handler.notify_timeline,
kwargs=dict(notify_action='pmsupdate')).start()
else:
logger.info(u"PlexPy Monitor :: No PMS update available.")

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, pmsconnect, notification_handler, log_reader, database
from plexpy import logger, pmsconnect, notification_handler, log_reader, database, notifiers
import threading
import plexpy
@ -78,9 +78,10 @@ class ActivityProcessor(object):
result = self.db.upsert('sessions', values, keys)
if result == 'insert':
# Push any notifications - Push it on it's own thread so we don't hold up our db actions
if notify:
# Check if any notification agents have notifications enabled
if notify and any(d['on_play'] for d in notifiers.available_notification_agents()):
values.update({'ip_address': session['ip_address']})
# 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()
@ -97,16 +98,23 @@ class ActivityProcessor(object):
ip_address = {'ip_address': ip_address}
self.db.upsert('sessions', ip_address, keys)
return True
def write_session_history(self, session=None, import_metadata=None, is_import=False, import_ignore_interval=0):
from plexpy import users, libraries
user_data = users.Users()
user_details = user_data.get_details(user_id=session['user_id'])
section_id = session['section_id'] if not is_import else import_metadata['section_id']
library_data = libraries.Libraries()
library_details = library_data.get_details(section_id=section_id)
if not is_import:
user_data = users.Users()
user_details = user_data.get_details(user_id=session['user_id'])
library_data = libraries.Libraries()
library_details = library_data.get_details(section_id=section_id)
# Return false if failed to retrieve user or library details
if not user_details or not library_details:
return False
if session:
logging_enabled = False
@ -116,8 +124,14 @@ class ActivityProcessor(object):
stopped = int(session['stopped'])
else:
stopped = int(time.time())
elif session['stopped']:
stopped = int(session['stopped'])
else:
stopped = int(time.time())
self.set_session_state(session_key=session['session_key'],
state='stopped',
view_offset=session['viewOffset'],
stopped=stopped)
if plexpy.CONFIG.MOVIE_LOGGING_ENABLE and str(session['rating_key']).isdigit() and \
session['media_type'] == 'movie':
@ -137,14 +151,14 @@ class ActivityProcessor(object):
else:
real_play_time = stopped - session['started']
if plexpy.CONFIG.LOGGING_IGNORE_INTERVAL and not is_import:
if not is_import and plexpy.CONFIG.LOGGING_IGNORE_INTERVAL:
if (session['media_type'] == 'movie' or session['media_type'] == 'episode') and \
(real_play_time < int(plexpy.CONFIG.LOGGING_IGNORE_INTERVAL)):
logging_enabled = False
logger.debug(u"PlexPy ActivityProcessor :: Play duration for ratingKey %s is %s secs which is less than %s "
u"seconds, so we're not logging it." %
(session['rating_key'], str(real_play_time), plexpy.CONFIG.LOGGING_IGNORE_INTERVAL))
if session['media_type'] == 'track' and not is_import:
if not is_import and session['media_type'] == 'track':
if real_play_time < 15 and session['duration'] >= 30:
logging_enabled = False
logger.debug(u"PlexPy ActivityProcessor :: Play duration for ratingKey %s is %s secs, "
@ -156,17 +170,29 @@ class ActivityProcessor(object):
logging_enabled = False
logger.debug(u"PlexPy ActivityProcessor :: Play duration for ratingKey %s is %s secs which is less than %s "
u"seconds, so we're not logging it." %
(session['rating_key'], str(real_play_time),
import_ignore_interval))
(session['rating_key'], str(real_play_time), import_ignore_interval))
if not user_details['keep_history'] and not is_import:
if not is_import and not user_details['keep_history']:
logging_enabled = False
logger.debug(u"PlexPy ActivityProcessor :: History logging for user '%s' is disabled." % user_details['username'])
elif not library_details['keep_history'] and not is_import:
elif not is_import and not library_details['keep_history']:
logging_enabled = False
logger.debug(u"PlexPy ActivityProcessor :: History logging for library '%s' is disabled." % library_details['section_name'])
if logging_enabled:
# Fetch metadata first so we can return false if it fails
if not is_import:
logger.debug(u"PlexPy ActivityProcessor :: Fetching metadata for item ratingKey %s" % session['rating_key'])
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_metadata_details(rating_key=str(session['rating_key']))
if result:
metadata = result['metadata']
else:
return False
else:
metadata = import_metadata
# logger.debug(u"PlexPy ActivityProcessor :: Attempting to write to session_history table...")
query = 'INSERT INTO session_history (started, stopped, rating_key, parent_rating_key, ' \
'grandparent_rating_key, media_type, user_id, user, ip_address, paused_counter, player, ' \
@ -218,13 +244,22 @@ class ActivityProcessor(object):
# % last_id)
# Write the session_history_media_info table
# Generate a combined transcode decision value
if session['video_decision'] == 'transcode' or session['audio_decision'] == 'transcode':
transcode_decision = 'transcode'
elif session['video_decision'] == 'copy' or session['audio_decision'] == 'copy':
transcode_decision = 'copy'
else:
transcode_decision = 'direct play'
# logger.debug(u"PlexPy ActivityProcessor :: Attempting to write to session_history_media_info table...")
query = 'INSERT INTO session_history_media_info (id, rating_key, video_decision, audio_decision, ' \
'duration, width, height, container, video_codec, audio_codec, bitrate, video_resolution, ' \
'video_framerate, aspect_ratio, audio_channels, transcode_protocol, transcode_container, ' \
'transcode_video_codec, transcode_audio_codec, transcode_audio_channels, transcode_width, ' \
'transcode_height) VALUES ' \
'(last_insert_rowid(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
'transcode_height, transcode_decision) VALUES ' \
'(last_insert_rowid(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
args = [session['rating_key'], session['video_decision'], session['audio_decision'],
session['duration'], session['width'], session['height'], session['container'],
@ -232,19 +267,12 @@ class ActivityProcessor(object):
session['video_resolution'], session['video_framerate'], session['aspect_ratio'],
session['audio_channels'], session['transcode_protocol'], session['transcode_container'],
session['transcode_video_codec'], session['transcode_audio_codec'],
session['transcode_audio_channels'], session['transcode_width'], session['transcode_height']]
session['transcode_audio_channels'], session['transcode_width'], session['transcode_height'],
transcode_decision]
# logger.debug(u"PlexPy ActivityProcessor :: Writing session_history_media_info transaction...")
self.db.action(query=query, args=args)
if not is_import:
logger.debug(u"PlexPy ActivityProcessor :: Fetching metadata for item ratingKey %s" % session['rating_key'])
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_metadata_details(rating_key=str(session['rating_key']))
metadata = result['metadata']
else:
metadata = import_metadata
# Write the session_history_metadata table
directors = ";".join(metadata['directors'])
writers = ";".join(metadata['writers'])
@ -280,6 +308,9 @@ class ActivityProcessor(object):
# logger.debug(u"PlexPy ActivityProcessor :: Writing session_history_metadata transaction...")
self.db.action(query=query, args=args)
# Return true when the session is successfully written to the database
return True
def find_session_ip(self, rating_key=None, machine_id=None):
logger.debug(u"PlexPy ActivityProcessor :: Requesting log lines...")
@ -351,12 +382,15 @@ class ActivityProcessor(object):
return None
def set_session_state(self, session_key=None, state=None, view_offset=0):
def set_session_state(self, session_key=None, state=None, view_offset=0, **kwargs):
if str(session_key).isdigit() and str(view_offset).isdigit():
values = {'view_offset': int(view_offset)}
if state:
values['state'] = state
for k,v in kwargs.iteritems():
values[k] = v
keys = {'session_key': session_key}
result = self.db.upsert('sessions', values, keys)

View file

@ -62,6 +62,7 @@ MEDIA_FLAGS_VIDEO = {'avc1': 'h264',
SCHEDULER_LIST = ['Check GitHub for updates',
'Check for active sessions',
'Check for recently added items',
'Check for Plex updates',
'Check for Plex remote access',
'Refresh users list',
'Refresh libraries list',

View file

@ -48,6 +48,7 @@ _CONFIG_DEFINITIONS = {
'BOXCAR_ON_INTDOWN': (int, 'Boxcar', 0),
'BOXCAR_ON_EXTUP': (int, 'Boxcar', 0),
'BOXCAR_ON_INTUP': (int, 'Boxcar', 0),
'BOXCAR_ON_PMSUPDATE': (int, 'Boxcar', 0),
'BUFFER_THRESHOLD': (int, 'Monitoring', 3),
'BUFFER_WAIT': (int, 'Monitoring', 900),
'BACKUP_DIR': (str, 'General', ''),
@ -81,6 +82,7 @@ _CONFIG_DEFINITIONS = {
'EMAIL_ON_INTDOWN': (int, 'Email', 0),
'EMAIL_ON_EXTUP': (int, 'Email', 0),
'EMAIL_ON_INTUP': (int, 'Email', 0),
'EMAIL_ON_PMSUPDATE': (int, 'Email', 0),
'ENABLE_HTTPS': (int, 'General', 0),
'FACEBOOK_ENABLED': (int, 'Facebook', 0),
'FACEBOOK_REDIRECT_URI': (str, 'Facebook', ''),
@ -88,6 +90,7 @@ _CONFIG_DEFINITIONS = {
'FACEBOOK_APP_SECRET': (str, 'Facebook', ''),
'FACEBOOK_TOKEN': (str, 'Facebook', ''),
'FACEBOOK_GROUP': (str, 'Facebook', ''),
'FACEBOOK_INCL_PMSLINK': (int, 'Facebook', 0),
'FACEBOOK_INCL_POSTER': (int, 'Facebook', 1),
'FACEBOOK_INCL_SUBJECT': (int, 'Facebook', 1),
'FACEBOOK_ON_PLAY': (int, 'Facebook', 0),
@ -101,6 +104,7 @@ _CONFIG_DEFINITIONS = {
'FACEBOOK_ON_INTDOWN': (int, 'Facebook', 0),
'FACEBOOK_ON_EXTUP': (int, 'Facebook', 0),
'FACEBOOK_ON_INTUP': (int, 'Facebook', 0),
'FACEBOOK_ON_PMSUPDATE': (int, 'Facebook', 0),
'FIRST_RUN_COMPLETE': (int, 'General', 0),
'FREEZE_DB': (int, 'General', 0),
'GET_FILE_SIZES': (int, 'General', 0),
@ -126,6 +130,7 @@ _CONFIG_DEFINITIONS = {
'GROWL_ON_INTDOWN': (int, 'Growl', 0),
'GROWL_ON_EXTUP': (int, 'Growl', 0),
'GROWL_ON_INTUP': (int, 'Growl', 0),
'GROWL_ON_PMSUPDATE': (int, 'Growl', 0),
'HOME_LIBRARY_CARDS': (list, 'General', ['first_run']),
'HOME_STATS_LENGTH': (int, 'General', 30),
'HOME_STATS_TYPE': (int, 'General', 0),
@ -159,6 +164,7 @@ _CONFIG_DEFINITIONS = {
'IFTTT_ON_INTDOWN': (int, 'IFTTT', 0),
'IFTTT_ON_EXTUP': (int, 'IFTTT', 0),
'IFTTT_ON_INTUP': (int, 'IFTTT', 0),
'IFTTT_ON_PMSUPDATE': (int, 'IFTTT', 0),
'JOURNAL_MODE': (str, 'Advanced', 'wal'),
'LAUNCH_BROWSER': (int, 'General', 1),
'LOG_DIR': (str, 'General', ''),
@ -173,6 +179,7 @@ _CONFIG_DEFINITIONS = {
'MUSIC_NOTIFY_ON_START': (int, 'Monitoring', 1),
'MUSIC_NOTIFY_ON_STOP': (int, 'Monitoring', 0),
'MUSIC_NOTIFY_ON_PAUSE': (int, 'Monitoring', 0),
'MONITOR_PMS_UPDATES': (int, 'Monitoring', 0),
'MONITOR_REMOTE_ACCESS': (int, 'Monitoring', 0),
'MONITORING_INTERVAL': (int, 'Monitoring', 60),
'MONITORING_USE_WEBSOCKET': (int, 'Monitoring', 0),
@ -190,6 +197,7 @@ _CONFIG_DEFINITIONS = {
'NMA_ON_INTDOWN': (int, 'NMA', 0),
'NMA_ON_EXTUP': (int, 'NMA', 0),
'NMA_ON_INTUP': (int, 'NMA', 0),
'NMA_ON_PMSUPDATE': (int, 'NMA', 0),
'NOTIFY_CONSECUTIVE': (int, 'Monitoring', 1),
'NOTIFY_UPLOAD_POSTERS': (int, 'Monitoring', 0),
'NOTIFY_RECENTLY_ADDED': (int, 'Monitoring', 0),
@ -218,6 +226,8 @@ _CONFIG_DEFINITIONS = {
'NOTIFY_ON_EXTUP_BODY_TEXT': (unicode, 'Monitoring', 'The Plex Media Server remote access is back up.'),
'NOTIFY_ON_INTUP_SUBJECT_TEXT': (unicode, 'Monitoring', 'PlexPy ({server_name})'),
'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_SCRIPTS_ARGS_TEXT': (unicode, 'Monitoring', ''),
'OSX_NOTIFY_APP': (str, 'OSX_Notify', '/Applications/PlexPy'),
'OSX_NOTIFY_ENABLED': (int, 'OSX_Notify', 0),
@ -232,6 +242,7 @@ _CONFIG_DEFINITIONS = {
'OSX_NOTIFY_ON_INTDOWN': (int, 'OSX_Notify', 0),
'OSX_NOTIFY_ON_EXTUP': (int, 'OSX_Notify', 0),
'OSX_NOTIFY_ON_INTUP': (int, 'OSX_Notify', 0),
'OSX_NOTIFY_ON_PMSUPDATE': (int, 'OSX_Notify', 0),
'PLEX_CLIENT_HOST': (str, 'Plex', ''),
'PLEX_ENABLED': (int, 'Plex', 0),
'PLEX_PASSWORD': (str, 'Plex', ''),
@ -247,6 +258,7 @@ _CONFIG_DEFINITIONS = {
'PLEX_ON_INTDOWN': (int, 'Plex', 0),
'PLEX_ON_EXTUP': (int, 'Plex', 0),
'PLEX_ON_INTUP': (int, 'Plex', 0),
'PLEX_ON_PMSUPDATE': (int, 'Plex', 0),
'PROWL_ENABLED': (int, 'Prowl', 0),
'PROWL_KEYS': (str, 'Prowl', ''),
'PROWL_PRIORITY': (int, 'Prowl', 0),
@ -261,6 +273,7 @@ _CONFIG_DEFINITIONS = {
'PROWL_ON_INTDOWN': (int, 'Prowl', 0),
'PROWL_ON_EXTUP': (int, 'Prowl', 0),
'PROWL_ON_INTUP': (int, 'Prowl', 0),
'PROWL_ON_PMSUPDATE': (int, 'Prowl', 0),
'PUSHALOT_APIKEY': (str, 'Pushalot', ''),
'PUSHALOT_ENABLED': (int, 'Pushalot', 0),
'PUSHALOT_ON_PLAY': (int, 'Pushalot', 0),
@ -274,6 +287,7 @@ _CONFIG_DEFINITIONS = {
'PUSHALOT_ON_INTDOWN': (int, 'Pushalot', 0),
'PUSHALOT_ON_EXTUP': (int, 'Pushalot', 0),
'PUSHALOT_ON_INTUP': (int, 'Pushalot', 0),
'PUSHALOT_ON_PMSUPDATE': (int, 'Pushalot', 0),
'PUSHBULLET_APIKEY': (str, 'PushBullet', ''),
'PUSHBULLET_DEVICEID': (str, 'PushBullet', ''),
'PUSHBULLET_CHANNEL_TAG': (str, 'PushBullet', ''),
@ -289,6 +303,7 @@ _CONFIG_DEFINITIONS = {
'PUSHBULLET_ON_INTDOWN': (int, 'PushBullet', 0),
'PUSHBULLET_ON_EXTUP': (int, 'PushBullet', 0),
'PUSHBULLET_ON_INTUP': (int, 'PushBullet', 0),
'PUSHBULLET_ON_PMSUPDATE': (int, 'PushBullet', 0),
'PUSHOVER_APITOKEN': (str, 'Pushover', ''),
'PUSHOVER_ENABLED': (int, 'Pushover', 0),
'PUSHOVER_HTML_SUPPORT': (int, 'Pushover', 1),
@ -306,6 +321,7 @@ _CONFIG_DEFINITIONS = {
'PUSHOVER_ON_INTDOWN': (int, 'Pushover', 0),
'PUSHOVER_ON_EXTUP': (int, 'Pushover', 0),
'PUSHOVER_ON_INTUP': (int, 'Pushover', 0),
'PUSHOVER_ON_PMSUPDATE': (int, 'Pushover', 0),
'REFRESH_LIBRARIES_INTERVAL': (int, 'Monitoring', 12),
'REFRESH_LIBRARIES_ON_STARTUP': (int, 'Monitoring', 1),
'REFRESH_USERS_INTERVAL': (int, 'Monitoring', 12),
@ -327,6 +343,7 @@ _CONFIG_DEFINITIONS = {
'SLACK_ON_INTDOWN': (int, 'Slack', 0),
'SLACK_ON_EXTUP': (int, 'Slack', 0),
'SLACK_ON_INTUP': (int, 'Slack', 0),
'SLACK_ON_PMSUPDATE': (int, 'Slack', 0),
'SCRIPTS_ENABLED': (int, 'Scripts', 0),
'SCRIPTS_FOLDER': (unicode, 'Scripts', ''),
'SCRIPTS_ON_PLAY': (int, 'Scripts', 0),
@ -340,6 +357,7 @@ _CONFIG_DEFINITIONS = {
'SCRIPTS_ON_EXTUP': (int, 'Scripts', 0),
'SCRIPTS_ON_INTDOWN': (int, 'Scripts', 0),
'SCRIPTS_ON_INTUP': (int, 'Scripts', 0),
'SCRIPTS_ON_PMSUPDATE': (int, 'Scripts', 0),
'SCRIPTS_ON_PLAY_SCRIPT': (unicode, 'Scripts', ''),
'SCRIPTS_ON_STOP_SCRIPT': (unicode, 'Scripts', ''),
'SCRIPTS_ON_PAUSE_SCRIPT': (unicode, 'Scripts', ''),
@ -351,6 +369,7 @@ _CONFIG_DEFINITIONS = {
'SCRIPTS_ON_EXTUP_SCRIPT': (unicode, 'Scripts', ''),
'SCRIPTS_ON_INTDOWN_SCRIPT': (unicode, 'Scripts', ''),
'SCRIPTS_ON_INTUP_SCRIPT': (unicode, 'Scripts', ''),
'SCRIPTS_ON_PMSUPDATE_SCRIPT': (unicode, 'Scripts', ''),
'TELEGRAM_BOT_TOKEN': (str, 'Telegram', ''),
'TELEGRAM_ENABLED': (int, 'Telegram', 0),
'TELEGRAM_CHAT_ID': (str, 'Telegram', ''),
@ -366,6 +385,7 @@ _CONFIG_DEFINITIONS = {
'TELEGRAM_ON_INTDOWN': (int, 'Telegram', 0),
'TELEGRAM_ON_EXTUP': (int, 'Telegram', 0),
'TELEGRAM_ON_INTUP': (int, 'Telegram', 0),
'TELEGRAM_ON_PMSUPDATE': (int, 'Telegram', 0),
'TV_LOGGING_ENABLE': (int, 'Monitoring', 1),
'TV_NOTIFY_ENABLE': (int, 'Monitoring', 0),
'TV_NOTIFY_ON_START': (int, 'Monitoring', 1),
@ -388,6 +408,7 @@ _CONFIG_DEFINITIONS = {
'TWITTER_ON_INTDOWN': (int, 'Twitter', 0),
'TWITTER_ON_EXTUP': (int, 'Twitter', 0),
'TWITTER_ON_INTUP': (int, 'Twitter', 0),
'TWITTER_ON_PMSUPDATE': (int, 'Twitter', 0),
'UPDATE_DB_INTERVAL': (int, 'General', 24),
'UPDATE_SECTION_IDS': (int, 'General', 1),
'VERIFY_SSL_CERT': (bool_int, 'Advanced', 1),
@ -406,7 +427,8 @@ _CONFIG_DEFINITIONS = {
'XBMC_ON_EXTDOWN': (int, 'XBMC', 0),
'XBMC_ON_INTDOWN': (int, 'XBMC', 0),
'XBMC_ON_EXTUP': (int, 'XBMC', 0),
'XBMC_ON_INTUP': (int, 'XBMC', 0)
'XBMC_ON_INTUP': (int, 'XBMC', 0),
'XBMC_ON_PMSUPDATE': (int, 'XBMC', 0)
}

View file

@ -18,6 +18,7 @@ import os
import sqlite3
import shutil
import threading
import time
import logger
import plexpy
@ -37,9 +38,21 @@ def clear_history_tables():
monitor_db.action('DELETE FROM session_history')
monitor_db.action('DELETE FROM session_history_media_info')
monitor_db.action('DELETE FROM session_history_metadata')
monitor_db.action('VACUUM;')
monitor_db.action('VACUUM')
def delete_sessions():
logger.debug(u"PlexPy Database :: Clearing temporary sessions from database.")
monitor_db = MonitorDatabase()
try:
monitor_db.action('DELETE FROM sessions')
monitor_db.action('VACUUM')
return 'Cleared temporary sessions.'
except Exception as e:
logger.warn(u"PlexPy Database :: Unable to clear temporary sessions from database: %s." % e)
return 'Unable to clear temporary sessions.'
def db_filename(filename="plexpy.db"):
""" Returns the filepath to the db """

View file

@ -58,11 +58,10 @@ class DataFactory(object):
'session_history_metadata.thumb',
'session_history_metadata.parent_thumb',
'session_history_metadata.grandparent_thumb',
'MAX((CASE WHEN view_offset IS NULL THEN 0.1 ELSE view_offset * 1.0 END) / \
(CASE WHEN session_history_metadata.duration IS NULL THEN 1.0 \
ELSE session_history_metadata.duration * 1.0 END) * 100) AS percent_complete',
'session_history_media_info.video_decision',
'session_history_media_info.audio_decision',
'MAX((CASE WHEN (view_offset IS NULL OR view_offset = "") THEN 0.1 ELSE view_offset * 1.0 END) / \
(CASE WHEN (session_history_metadata.duration IS NULL OR session_history_metadata.duration = "") \
THEN 1.0 ELSE session_history_metadata.duration * 1.0 END) * 100) AS percent_complete',
'session_history_media_info.transcode_decision',
'COUNT(*) AS group_count',
'GROUP_CONCAT(session_history.id) AS group_ids'
]
@ -138,8 +137,7 @@ class DataFactory(object):
'media_index': item['media_index'],
'parent_media_index': item['parent_media_index'],
'thumb': thumb,
'video_decision': item['video_decision'],
'audio_decision': item['audio_decision'],
'transcode_decision': item['transcode_decision'],
'percent_complete': int(round(item['percent_complete'])),
'watched_status': watched_status,
'group_count': item['group_count'],
@ -626,24 +624,21 @@ class DataFactory(object):
title = 'Concurrent Transcodes'
query = base_query \
+ 'AND (session_history_media_info.video_decision = "transcode" ' \
'OR session_history_media_info.audio_decision = "transcode") '
+ 'AND session_history_media_info.transcode_decision = "transcode" '
result = monitor_db.select(query)
if result:
most_concurrent.append(calc_most_concurrent(title, result))
title = 'Concurrent Direct Streams'
query = base_query \
+ 'AND (session_history_media_info.video_decision != "transcode" ' \
'AND session_history_media_info.audio_decision = "copy") '
+ 'AND session_history_media_info.transcode_decision = "copy" '
result = monitor_db.select(query)
if result:
most_concurrent.append(calc_most_concurrent(title, result))
title = 'Concurrent Direct Plays'
query = base_query \
+ 'AND (session_history_media_info.video_decision = "direct play" ' \
'OR session_history_media_info.audio_decision = "direct play") '
+ 'AND session_history_media_info.transcode_decision = "direct play" '
result = monitor_db.select(query)
if result:
most_concurrent.append(calc_most_concurrent(title, result))
@ -828,6 +823,7 @@ class DataFactory(object):
'SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) AS total_duration ' \
'FROM session_history ' \
'JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \
'JOIN session_history_media_info ON session_history_media_info.id = session_history.id ' \
'%s ' % where
result = monitor_db.select(query)
except Exception as e:
@ -860,16 +856,28 @@ class DataFactory(object):
return ip_address
def get_poster_url(self, rating_key=''):
def get_poster_url(self, rating_key='', metadata=None):
monitor_db = database.MonitorDatabase()
poster_url = ''
poster_key = ''
if rating_key:
poster_key = rating_key
elif metadata:
if metadata['media_type'] == 'movie' or metadata['media_type'] == 'show' or \
metadata['media_type'] == 'artist' or metadata['media_type'] == 'album':
poster_key = metadata['rating_key']
elif metadata['media_type'] == 'episode':
poster_key = metadata['grandparent_rating_key']
elif metadata['media_type'] == 'season' or metadata['media_type'] == 'track':
poster_key = metadata['parent_rating_key']
if poster_key:
try:
query = 'SELECT id, poster_url FROM notify_log ' \
'WHERE rating_key = %d OR parent_rating_key = %d OR grandparent_rating_key = %d ' \
'ORDER BY id DESC LIMIT 1' % (int(rating_key), int(rating_key), int(rating_key))
'ORDER BY id DESC LIMIT 1' % (int(poster_key), int(poster_key), int(poster_key))
result = monitor_db.select(query)
except Exception as e:
logger.warn(u"PlexPy DataFactory :: Unable to execute database query for get_poster_url: %s." % e)
@ -882,6 +890,16 @@ class DataFactory(object):
return poster_url
def delete_poster_url(self, poster_url=''):
monitor_db = database.MonitorDatabase()
if poster_url:
logger.info(u"PlexPy DataFactory :: Deleting poster_url %s from the notify log database." % poster_url)
monitor_db.upsert('notify_log', {'poster_url': None}, {'poster_url': poster_url})
return 'Deleted poster_url %s.' % poster_url
else:
return 'Unable to delete poster_url.'
def get_search_query(self, rating_key=''):
monitor_db = database.MonitorDatabase()
@ -1188,4 +1206,16 @@ class DataFactory(object):
'draw': query['draw']
}
return dict
return dict
def delete_notification_log(self):
monitor_db = database.MonitorDatabase()
try:
logger.info(u"PlexPy DataFactory :: Clearing notification logs from database.")
monitor_db.action('DELETE FROM notify_log')
monitor_db.action('VACUUM')
return 'Cleared notification logs.'
except Exception as e:
logger.warn(u"PlexPy DataFactory :: Unable to execute database query for delete_notification_log: %s." % e)
return 'Unable to clear notification logs.'

View file

@ -490,40 +490,39 @@ class Graphs(object):
try:
if y_axis == 'plays':
query = 'SELECT date(session_history.started, "unixepoch", "localtime") AS date_played, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \
'OR session_history_media_info.audio_decision = "direct play") THEN 1 ELSE 0 END) AS dp_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \
'AND session_history_media_info.audio_decision = "copy") THEN 1 ELSE 0 END) AS ds_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \
'OR session_history_media_info.audio_decision = "transcode") THEN 1 ELSE 0 END) AS tc_count ' \
'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" ' \
'THEN 1 ELSE 0 END) AS ds_count, ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \
'THEN 1 ELSE 0 END) AS tc_count ' \
'FROM session_history ' \
'JOIN session_history_media_info ON session_history.id = session_history_media_info.id ' \
'WHERE (datetime(session_history.stopped, "unixepoch", "localtime") >= ' \
'datetime("now", "-%s days", "localtime")) AND ' \
'(session_history.media_type = "episode" OR session_history.media_type = "movie" OR session_history.media_type = "track") ' \
'(session_history.media_type = "episode" OR session_history.media_type = "movie" OR ' \
'session_history.media_type = "track") ' \
'GROUP BY date_played ' \
'ORDER BY started ASC' % time_range
result = monitor_db.select(query)
else:
query = 'SELECT date(session_history.started, "unixepoch", "localtime") AS date_played, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \
'OR session_history_media_info.audio_decision = "direct play") ' \
'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, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \
'AND session_history_media_info.audio_decision = "copy") ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \
'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 ds_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \
'OR session_history_media_info.audio_decision = "transcode") ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \
'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 tc_count ' \
'FROM session_history ' \
'JOIN session_history_media_info ON session_history.id = session_history_media_info.id ' \
'WHERE datetime(session_history.stopped, "unixepoch", "localtime") >= ' \
'datetime("now", "-%s days", "localtime") AND ' \
'(session_history.media_type = "episode" OR session_history.media_type = "movie" OR session_history.media_type = "track") ' \
'(session_history.media_type = "episode" OR session_history.media_type = "movie" OR ' \
'session_history.media_type = "track") ' \
'GROUP BY date_played ' \
'ORDER BY started ASC' % time_range
@ -583,12 +582,12 @@ class Graphs(object):
try:
if y_axis == 'plays':
query = 'SELECT session_history_media_info.video_resolution AS resolution, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \
'OR session_history_media_info.audio_decision = "direct play") THEN 1 ELSE 0 END) AS dp_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \
'AND session_history_media_info.audio_decision = "copy") THEN 1 ELSE 0 END) AS ds_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \
'OR session_history_media_info.audio_decision = "transcode") THEN 1 ELSE 0 END) AS tc_count, ' \
'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" ' \
'THEN 1 ELSE 0 END) AS ds_count, ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \
'THEN 1 ELSE 0 END) AS tc_count, ' \
'COUNT(session_history.id) AS total_count ' \
'FROM session_history ' \
'JOIN session_history_media_info ON session_history.id = session_history_media_info.id ' \
@ -602,16 +601,13 @@ class Graphs(object):
result = monitor_db.select(query)
else:
query = 'SELECT session_history_media_info.video_resolution AS resolution,' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \
'OR session_history_media_info.audio_decision = "direct play") ' \
'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, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \
'AND session_history_media_info.audio_decision = "copy") ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \
'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 ds_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \
'OR session_history_media_info.audio_decision = "transcode") ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \
'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 tc_count, ' \
'SUM(CASE WHEN stopped > 0 THEN (stopped - started) ' \
@ -671,12 +667,12 @@ class Graphs(object):
'WHEN session_history_media_info.transcode_height <= 1440 THEN "QHD" ' \
'WHEN session_history_media_info.transcode_height <= 2160 THEN "4K" ' \
'ELSE "unknown" END) ELSE session_history_media_info.video_resolution END) AS resolution, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \
'OR session_history_media_info.audio_decision = "direct play") THEN 1 ELSE 0 END) AS dp_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \
'AND session_history_media_info.audio_decision = "copy") THEN 1 ELSE 0 END) AS ds_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" '\
'OR session_history_media_info.audio_decision = "transcode") THEN 1 ELSE 0 END) AS tc_count, ' \
'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" ' \
'THEN 1 ELSE 0 END) AS ds_count, ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" '\
'THEN 1 ELSE 0 END) AS tc_count, ' \
'COUNT(session_history.id) AS total_count ' \
'FROM session_history ' \
'JOIN session_history_media_info ON session_history.id = session_history_media_info.id ' \
@ -700,16 +696,13 @@ class Graphs(object):
'WHEN session_history_media_info.transcode_height <= 1440 THEN "QHD" ' \
'WHEN session_history_media_info.transcode_height <= 2160 THEN "4K" ' \
'ELSE "unknown" END) ELSE session_history_media_info.video_resolution END) AS resolution, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \
'OR session_history_media_info.audio_decision = "direct play") ' \
'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, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \
'AND session_history_media_info.audio_decision = "copy") ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \
'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 ds_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \
'OR session_history_media_info.audio_decision = "transcode") ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \
'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 tc_count, ' \
'SUM(CASE WHEN stopped > 0 THEN (stopped - started) ' \
@ -759,12 +752,12 @@ class Graphs(object):
try:
if y_axis == 'plays':
query = 'SELECT session_history.platform AS platform, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \
'OR session_history_media_info.audio_decision = "direct play") THEN 1 ELSE 0 END) AS dp_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \
'AND session_history_media_info.audio_decision = "copy") THEN 1 ELSE 0 END) AS ds_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \
'OR session_history_media_info.audio_decision = "transcode") THEN 1 ELSE 0 END) AS tc_count, ' \
'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" ' \
'THEN 1 ELSE 0 END) AS ds_count, ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \
'THEN 1 ELSE 0 END) AS tc_count, ' \
'COUNT(session_history.id) AS total_count ' \
'FROM session_history ' \
'JOIN session_history_media_info ON session_history.id = session_history_media_info.id ' \
@ -777,16 +770,13 @@ class Graphs(object):
result = monitor_db.select(query)
else:
query = 'SELECT session_history.platform AS platform, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \
'OR session_history_media_info.audio_decision = "direct play") ' \
'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, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \
'AND session_history_media_info.audio_decision = "copy") ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \
'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 ds_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \
'AND session_history_media_info.audio_decision = "transcode") ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \
'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 tc_count, ' \
'SUM(CASE WHEN session_history.stopped > 0 ' \
@ -838,12 +828,12 @@ class Graphs(object):
if y_axis == 'plays':
query = 'SELECT ' \
'(CASE WHEN users.friendly_name IS NULL THEN users.username ELSE users.friendly_name END) AS username, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \
'OR session_history_media_info.audio_decision = "direct play") THEN 1 ELSE 0 END) AS dp_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \
'AND session_history_media_info.audio_decision = "copy") THEN 1 ELSE 0 END) AS ds_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \
'OR session_history_media_info.audio_decision = "transcode") THEN 1 ELSE 0 END) AS tc_count, ' \
'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" ' \
'THEN 1 ELSE 0 END) AS ds_count, ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \
'THEN 1 ELSE 0 END) AS tc_count, ' \
'COUNT(session_history.id) AS total_count ' \
'FROM session_history ' \
'JOIN users ON session_history.user_id = users.user_id ' \
@ -858,16 +848,13 @@ class Graphs(object):
else:
query = 'SELECT ' \
'(CASE WHEN users.friendly_name IS NULL THEN users.username ELSE users.friendly_name END) AS username, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \
'OR session_history_media_info.audio_decision = "direct play") ' \
'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, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \
'AND session_history_media_info.audio_decision = "copy") ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \
'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 ds_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \
'AND session_history_media_info.audio_decision = "transcode") ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \
'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 tc_count, ' \
'SUM(CASE WHEN session_history.stopped > 0 ' \

View file

@ -148,7 +148,6 @@ class Libraries(object):
'session_history_metadata.year',
'session_history_metadata.media_index',
'session_history_metadata.parent_media_index',
'session_history_media_info.video_decision',
'library_sections.do_notify',
'library_sections.do_notify_created',
'library_sections.keep_history'
@ -540,7 +539,7 @@ class Libraries(object):
def get_details(self, section_id=None):
from plexpy import pmsconnect
default_return = {'section_id': None,
default_return = {'section_id': 0,
'section_name': 'Local',
'section_type': '',
'library_thumb': common.DEFAULT_COVER_THUMB,
@ -550,7 +549,7 @@ class Libraries(object):
'child_count': 0,
'do_notify': 0,
'do_notify_created': 0,
'keep_history': 0
'keep_history': 1
}
if not section_id:
@ -603,7 +602,8 @@ class Libraries(object):
return library_details
else:
logger.warn(u"PlexPy Libraries :: Unable to retrieve library from local database. Requesting library list refresh.")
logger.warn(u"PlexPy Libraries :: Unable to retrieve library %s from database. Requesting library list refresh."
% section_id)
# Let's first refresh the libraries list to make sure the library isn't newly added and not in the db yet
pmsconnect.refresh_libraries()
@ -613,9 +613,9 @@ class Libraries(object):
return library_details
else:
logger.warn(u"PlexPy Users :: Unable to retrieve user from local database. Returning 'Local' library.")
logger.warn(u"PlexPy Users :: Unable to retrieve library %s from database. Returning 'Local' library."
% section_id)
# If there is no library data we must return something
# Use "Local" library to retain compatibility with PlexWatch database value
return default_return
def get_watch_time_stats(self, section_id=None):

View file

@ -50,7 +50,10 @@ def notify(stream_data=None, notify_action=None):
for agent in notifiers.available_notification_agents():
if agent['on_play'] and notify_action == 'play':
# Build and send notification
notify_strings, metadata = build_notify_text(session=stream_data, notify_action=notify_action)
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],
@ -68,7 +71,10 @@ def notify(stream_data=None, notify_action=None):
elif agent['on_stop'] and notify_action == 'stop' \
and (plexpy.CONFIG.NOTIFY_CONSECUTIVE or progress_percent < plexpy.CONFIG.NOTIFY_WATCHED_PERCENT):
# Build and send notification
notify_strings, metadata = build_notify_text(session=stream_data, notify_action=notify_action)
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],
@ -86,7 +92,10 @@ def notify(stream_data=None, notify_action=None):
elif agent['on_pause'] and notify_action == 'pause' \
and (plexpy.CONFIG.NOTIFY_CONSECUTIVE or progress_percent < 99):
# Build and send notification
notify_strings, metadata = build_notify_text(session=stream_data, notify_action=notify_action)
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],
@ -104,7 +113,10 @@ def notify(stream_data=None, notify_action=None):
elif agent['on_resume'] and notify_action == 'resume' \
and (plexpy.CONFIG.NOTIFY_CONSECUTIVE or progress_percent < 99):
# Build and send notification
notify_strings, metadata = build_notify_text(session=stream_data, notify_action=notify_action)
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],
@ -121,7 +133,10 @@ def notify(stream_data=None, notify_action=None):
elif agent['on_buffer'] and notify_action == 'buffer':
# Build and send notification
notify_strings, metadata = build_notify_text(session=stream_data, notify_action=notify_action)
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],
@ -143,7 +158,10 @@ def notify(stream_data=None, notify_action=None):
# 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'] and d['notify_action'] == notify_action for d in notify_states):
# Build and send notification
notify_strings, metadata = build_notify_text(session=stream_data, notify_action=notify_action)
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],
@ -163,7 +181,10 @@ def notify(stream_data=None, notify_action=None):
for agent in notifiers.available_notification_agents():
if agent['on_play'] and notify_action == 'play':
# Build and send notification
notify_strings, metadata = build_notify_text(session=stream_data, notify_action=notify_action)
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],
@ -180,7 +201,10 @@ def notify(stream_data=None, notify_action=None):
elif agent['on_stop'] and notify_action == 'stop':
# Build and send notification
notify_strings, metadata = build_notify_text(session=stream_data, notify_action=notify_action)
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],
@ -197,7 +221,10 @@ def notify(stream_data=None, notify_action=None):
elif agent['on_pause'] and notify_action == 'pause':
# Build and send notification
notify_strings, metadata = build_notify_text(session=stream_data, notify_action=notify_action)
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],
@ -214,7 +241,10 @@ def notify(stream_data=None, notify_action=None):
elif agent['on_resume'] and notify_action == 'resume':
# Build and send notification
notify_strings, metadata = build_notify_text(session=stream_data, notify_action=notify_action)
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],
@ -231,7 +261,10 @@ def notify(stream_data=None, notify_action=None):
elif agent['on_buffer'] and notify_action == 'buffer':
# Build and send notification
notify_strings, metadata = build_notify_text(session=stream_data, notify_action=notify_action)
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],
@ -260,7 +293,10 @@ def notify_timeline(timeline_data=None, notify_action=None):
for agent in notifiers.available_notification_agents():
if agent['on_created'] and notify_action == 'created':
# Build and send notification
notify_strings, metadata = build_notify_text(timeline=timeline_data, notify_action=notify_action)
notify_strings, metadata = build_notify_text(timeline=timeline_data,
notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0],
body=notify_strings[1],
@ -279,7 +315,9 @@ def notify_timeline(timeline_data=None, notify_action=None):
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(notify_action=notify_action)
notify_strings = build_server_notify_text(notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0],
body=notify_strings[1],
@ -295,7 +333,9 @@ def notify_timeline(timeline_data=None, notify_action=None):
if agent['on_intdown'] and notify_action == 'intdown':
# Build and send notification
notify_strings = build_server_notify_text(notify_action=notify_action)
notify_strings = build_server_notify_text(notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0],
body=notify_strings[1],
@ -311,7 +351,9 @@ def notify_timeline(timeline_data=None, notify_action=None):
if agent['on_extup'] and notify_action == 'extup':
# Build and send notification
notify_strings = build_server_notify_text(notify_action=notify_action)
notify_strings = build_server_notify_text(notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0],
body=notify_strings[1],
@ -327,7 +369,27 @@ def notify_timeline(timeline_data=None, notify_action=None):
if agent['on_intup'] and notify_action == 'intup':
# Build and send notification
notify_strings = build_server_notify_text(notify_action=notify_action)
notify_strings = build_server_notify_text(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)
# Set the notification state in the db
set_notify_state(session={},
notify_action=notify_action,
agent_info=agent,
notify_strings=notify_strings,
metadata={})
if agent['on_pmsupdate'] and notify_action == 'pmsupdate':
# Build and send notification
notify_strings = build_server_notify_text(notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0],
body=notify_strings[1],
@ -395,11 +457,11 @@ def set_notify_state(session, notify_action, agent_info, notify_strings, metadat
logger.error(u"PlexPy NotificationHandler :: Unable to set notify state.")
def build_notify_text(session=None, timeline=None, notify_action=None):
def build_notify_text(session=None, timeline=None, notify_action=None, agent_id=None):
# Get time formats
date_format = plexpy.CONFIG.DATE_FORMAT.replace('Do','').replace('zz','')
time_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('zz','')
duration_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('zz','').replace('a','').replace('A','')
date_format = plexpy.CONFIG.DATE_FORMAT.replace('Do','')
time_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','')
duration_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('a','').replace('A','')
# Get the server name
server_name = plexpy.CONFIG.PMS_NAME
@ -408,6 +470,10 @@ def build_notify_text(session=None, timeline=None, notify_action=None):
plex_tv = plextv.PlexTV()
server_times = plex_tv.get_server_times()
# Get the server version
pms_connect = pmsconnect.PmsConnect()
server_identity = pms_connect.get_server_identity()
if server_times:
updated_at = server_times[0]['updated_at']
server_uptime = helpers.human_duration(int(time.time() - helpers.cast_to_int(updated_at)))
@ -435,13 +501,13 @@ def build_notify_text(session=None, timeline=None, notify_action=None):
# Check for exclusion tags
if metadata['media_type'] == 'movie':
# Regex pattern to remove the text in the tags we don't want
pattern = re.compile('\n*<tv>[^>]+.</tv>\n*|\n*<music>[^>]+.</music>\n*', re.IGNORECASE | re.DOTALL)
pattern = re.compile(r'<tv>[^>]+.<\/tv>|<music>[^>]+.<\/music>', re.IGNORECASE | re.DOTALL)
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('\n*<movie>[^>]+.</movie>\n*|\n*?<music>[^>]+.</music>\n*', re.IGNORECASE | re.DOTALL)
pattern = re.compile(r'<movie>[^>]+.<\/movie>|<music>[^>]+.<\/music>', re.IGNORECASE | re.DOTALL)
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('\n*<tv>[^>]+.</tv>\n*|\n*<movie>[^>]+.</movie>\n*', re.IGNORECASE | re.DOTALL)
pattern = re.compile(r'<tv>[^>]+.<\/tv>|<movie>[^>]+.<\/movie>', re.IGNORECASE | re.DOTALL)
else:
pattern = None
@ -450,37 +516,37 @@ def build_notify_text(session=None, timeline=None, notify_action=None):
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))
on_stop_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT))
on_stop_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT))
on_pause_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT))
on_pause_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT))
on_resume_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT))
on_resume_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT))
on_buffer_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT))
on_buffer_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT))
on_watched_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT))
on_watched_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT))
on_created_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT))
on_created_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT))
script_args_text = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT))
on_start_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT), agent_id)
on_start_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT), agent_id)
on_stop_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT), agent_id)
on_stop_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT), agent_id)
on_pause_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT), agent_id)
on_pause_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT), agent_id)
on_resume_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT), agent_id)
on_resume_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT), agent_id)
on_buffer_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT), agent_id)
on_buffer_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT), agent_id)
on_watched_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT), 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)
script_args_text = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT), agent_id)
else:
on_start_subject = plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT
on_start_body = plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT
on_stop_subject = plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT
on_stop_body = plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT
on_pause_subject = plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT
on_pause_body = plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT
on_resume_subject = plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT
on_resume_body = plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT
on_buffer_subject = plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT
on_buffer_body = plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT
on_watched_subject = plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT
on_watched_body = plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT
on_created_subject = plexpy.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT
on_created_body = plexpy.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT
script_args_text = plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT
on_start_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT, agent_id)
on_start_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT, agent_id)
on_stop_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT, agent_id)
on_stop_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT, agent_id)
on_pause_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT, agent_id)
on_pause_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT, agent_id)
on_resume_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT, agent_id)
on_resume_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT, agent_id)
on_buffer_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT, agent_id)
on_buffer_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT, agent_id)
on_watched_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT, 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)
script_args_text = strip_tag(plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT, agent_id)
# Create a title
if metadata['media_type'] == 'episode' or metadata['media_type'] == 'track':
@ -591,6 +657,7 @@ def build_notify_text(session=None, timeline=None, notify_action=None):
available_params = {# Global paramaters
'server_name': server_name,
'server_uptime': server_uptime,
'server_version': server_identity.get('version',''),
'action': notify_action.title(),
'datestamp': arrow.now().format(date_format),
'timestamp': arrow.now().format(time_format),
@ -854,10 +921,10 @@ def build_notify_text(session=None, timeline=None, notify_action=None):
return None
def build_server_notify_text(notify_action=None):
def build_server_notify_text(notify_action=None, agent_id=None):
# Get time formats
date_format = plexpy.CONFIG.DATE_FORMAT.replace('Do','').replace('zz','')
time_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('zz','')
date_format = plexpy.CONFIG.DATE_FORMAT.replace('Do','')
time_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','')
# Get the server name
server_name = plexpy.CONFIG.PMS_NAME
@ -866,6 +933,14 @@ def build_server_notify_text(notify_action=None):
plex_tv = plextv.PlexTV()
server_times = plex_tv.get_server_times()
# Get the server version
pms_connect = pmsconnect.PmsConnect()
server_identity = pms_connect.get_server_identity()
update_status = {}
if notify_action == 'pmsupdate':
update_status = pms_connect.get_update_staus()
if server_times:
updated_at = server_times[0]['updated_at']
server_uptime = helpers.human_duration(int(time.time() - helpers.cast_to_int(updated_at)))
@ -875,22 +950,29 @@ def build_server_notify_text(notify_action=None):
pattern = re.compile('\n*<tv>[^>]+.</tv>\n*|\n*<movie>[^>]+.</movie>\n*|\n*?<music>[^>]+.</music>\n*', re.IGNORECASE | re.DOTALL)
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
on_extup_subject = plexpy.CONFIG.NOTIFY_ON_EXTUP_SUBJECT_TEXT
on_extup_body = plexpy.CONFIG.NOTIFY_ON_EXTUP_BODY_TEXT
on_intup_subject = plexpy.CONFIG.NOTIFY_ON_INTUP_SUBJECT_TEXT
on_intup_body = plexpy.CONFIG.NOTIFY_ON_INTUP_BODY_TEXT
script_args_text = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT))
on_extdown_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_EXTDOWN_SUBJECT_TEXT, agent_id)
on_extdown_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_EXTDOWN_BODY_TEXT, agent_id)
on_intdown_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_INTDOWN_SUBJECT_TEXT, agent_id)
on_intdown_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_INTDOWN_BODY_TEXT, agent_id)
on_extup_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_EXTUP_SUBJECT_TEXT, agent_id)
on_extup_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_EXTUP_BODY_TEXT, agent_id)
on_intup_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_INTUP_SUBJECT_TEXT, agent_id)
on_intup_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_INTUP_BODY_TEXT, agent_id)
on_pmsupdate_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_PMSUPDATE_SUBJECT_TEXT, agent_id)
on_pmsupdate_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_PMSUPDATE_BODY_TEXT, agent_id)
script_args_text = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT), agent_id)
available_params = {# Global paramaters
'server_name': server_name,
'server_uptime': server_uptime,
'server_version': server_identity.get('version',''),
'action': notify_action.title(),
'datestamp': arrow.now().format(date_format),
'timestamp': arrow.now().format(time_format)}
'timestamp': arrow.now().format(time_format),
# Update parameters
'update_version': update_status.get('version',''),
'update_url': update_status.get('download_url',''),
'update_changelog': update_status.get('changelog','')}
# Default text
subject_text = 'PlexPy (%s)' % server_name
@ -996,10 +1078,39 @@ def build_server_notify_text(notify_action=None):
else:
return [subject_text, body_text, script_args]
elif notify_action == 'pmsupdate':
# Default body text
body_text = 'An update is available for the Plex Media Server (version {update_version}).'
if on_pmsupdate_subject and on_pmsupdate_body:
try:
subject_text = unicode(on_pmsupdate_subject).format(**available_params)
except LookupError, 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_pmsupdate_body).format(**available_params)
except LookupError, 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]
else:
return [subject_text, body_text, script_args]
else:
return None
def strip_tag(data):
p = re.compile(r'<.*?>')
def strip_tag(data, agent_id=None):
# Allow tags b, i, u, a[href], font[color] for Pushover
if agent_id == 7:
p = re.compile(r'<(?!/?(b>|i>|u>)|(a\shref=\"[^\"\'\s]+\"|/a>|font\scolor=\"[^\"\'\s]+\"|/font>)).*?>',
re.IGNORECASE | re.DOTALL)
else:
p = re.compile(r'<.*?>', re.IGNORECASE | re.DOTALL)
return p.sub('', data)

View file

@ -75,7 +75,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.GROWL_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.GROWL_ON_INTDOWN,
'on_extup': plexpy.CONFIG.GROWL_ON_EXTUP,
'on_intup': plexpy.CONFIG.GROWL_ON_INTUP
'on_intup': plexpy.CONFIG.GROWL_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.GROWL_ON_PMSUPDATE
},
{'name': 'Prowl',
'id': AGENT_IDS['Prowl'],
@ -92,7 +93,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.PROWL_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.PROWL_ON_INTDOWN,
'on_extup': plexpy.CONFIG.PROWL_ON_EXTUP,
'on_intup': plexpy.CONFIG.PROWL_ON_INTUP
'on_intup': plexpy.CONFIG.PROWL_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.PROWL_ON_PMSUPDATE
},
{'name': 'XBMC',
'id': AGENT_IDS['XBMC'],
@ -109,7 +111,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.XBMC_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.XBMC_ON_INTDOWN,
'on_extup': plexpy.CONFIG.XBMC_ON_EXTUP,
'on_intup': plexpy.CONFIG.XBMC_ON_INTUP
'on_intup': plexpy.CONFIG.XBMC_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.XBMC_ON_PMSUPDATE
},
{'name': 'Plex',
'id': AGENT_IDS['Plex'],
@ -126,7 +129,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.PLEX_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.PLEX_ON_INTDOWN,
'on_extup': plexpy.CONFIG.PLEX_ON_EXTUP,
'on_intup': plexpy.CONFIG.PLEX_ON_INTUP
'on_intup': plexpy.CONFIG.PLEX_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.PLEX_ON_PMSUPDATE
},
{'name': 'NotifyMyAndroid',
'id': AGENT_IDS['NMA'],
@ -143,7 +147,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.NMA_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.NMA_ON_INTDOWN,
'on_extup': plexpy.CONFIG.NMA_ON_EXTUP,
'on_intup': plexpy.CONFIG.NMA_ON_INTUP
'on_intup': plexpy.CONFIG.NMA_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.NMA_ON_PMSUPDATE
},
{'name': 'Pushalot',
'id': AGENT_IDS['Pushalot'],
@ -160,7 +165,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.PUSHALOT_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.PUSHALOT_ON_INTDOWN,
'on_extup': plexpy.CONFIG.PUSHALOT_ON_EXTUP,
'on_intup': plexpy.CONFIG.PUSHALOT_ON_INTUP
'on_intup': plexpy.CONFIG.PUSHALOT_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.PUSHALOT_ON_PMSUPDATE
},
{'name': 'Pushbullet',
'id': AGENT_IDS['Pushbullet'],
@ -177,7 +183,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.PUSHBULLET_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.PUSHBULLET_ON_INTDOWN,
'on_extup': plexpy.CONFIG.PUSHBULLET_ON_EXTUP,
'on_intup': plexpy.CONFIG.PUSHBULLET_ON_INTUP
'on_intup': plexpy.CONFIG.PUSHBULLET_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.PUSHBULLET_ON_PMSUPDATE
},
{'name': 'Pushover',
'id': AGENT_IDS['Pushover'],
@ -194,7 +201,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.PUSHOVER_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.PUSHOVER_ON_INTDOWN,
'on_extup': plexpy.CONFIG.PUSHOVER_ON_EXTUP,
'on_intup': plexpy.CONFIG.PUSHOVER_ON_INTUP
'on_intup': plexpy.CONFIG.PUSHOVER_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.PUSHOVER_ON_PMSUPDATE
},
{'name': 'Boxcar2',
'id': AGENT_IDS['Boxcar2'],
@ -211,7 +219,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.BOXCAR_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.BOXCAR_ON_INTDOWN,
'on_extup': plexpy.CONFIG.BOXCAR_ON_EXTUP,
'on_intup': plexpy.CONFIG.BOXCAR_ON_INTUP
'on_intup': plexpy.CONFIG.BOXCAR_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.BOXCAR_ON_PMSUPDATE
},
{'name': 'E-mail',
'id': AGENT_IDS['Email'],
@ -228,7 +237,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.EMAIL_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.EMAIL_ON_INTDOWN,
'on_extup': plexpy.CONFIG.EMAIL_ON_EXTUP,
'on_intup': plexpy.CONFIG.EMAIL_ON_INTUP
'on_intup': plexpy.CONFIG.EMAIL_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.EMAIL_ON_PMSUPDATE
},
{'name': 'Twitter',
'id': AGENT_IDS['Twitter'],
@ -245,7 +255,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.TWITTER_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.TWITTER_ON_INTDOWN,
'on_extup': plexpy.CONFIG.TWITTER_ON_EXTUP,
'on_intup': plexpy.CONFIG.TWITTER_ON_INTUP
'on_intup': plexpy.CONFIG.TWITTER_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.TWITTER_ON_PMSUPDATE
},
{'name': 'IFTTT',
'id': AGENT_IDS['IFTTT'],
@ -262,7 +273,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.IFTTT_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.IFTTT_ON_INTDOWN,
'on_extup': plexpy.CONFIG.IFTTT_ON_EXTUP,
'on_intup': plexpy.CONFIG.IFTTT_ON_INTUP
'on_intup': plexpy.CONFIG.IFTTT_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.IFTTT_ON_PMSUPDATE
},
{'name': 'Telegram',
'id': AGENT_IDS['Telegram'],
@ -279,7 +291,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.TELEGRAM_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.TELEGRAM_ON_INTDOWN,
'on_extup': plexpy.CONFIG.TELEGRAM_ON_EXTUP,
'on_intup': plexpy.CONFIG.TELEGRAM_ON_INTUP
'on_intup': plexpy.CONFIG.TELEGRAM_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.TELEGRAM_ON_PMSUPDATE
},
{'name': 'Slack',
'id': AGENT_IDS['Slack'],
@ -296,7 +309,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.SLACK_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.SLACK_ON_INTDOWN,
'on_extup': plexpy.CONFIG.SLACK_ON_EXTUP,
'on_intup': plexpy.CONFIG.SLACK_ON_INTUP
'on_intup': plexpy.CONFIG.SLACK_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.SLACK_ON_PMSUPDATE
},
{'name': 'Scripts',
'id': AGENT_IDS['Scripts'],
@ -313,7 +327,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.SCRIPTS_ON_EXTDOWN,
'on_extup': plexpy.CONFIG.SCRIPTS_ON_EXTUP,
'on_intdown': plexpy.CONFIG.SCRIPTS_ON_INTDOWN,
'on_intup': plexpy.CONFIG.SCRIPTS_ON_INTUP
'on_intup': plexpy.CONFIG.SCRIPTS_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.SCRIPTS_ON_PMSUPDATE
},
{'name': 'Facebook',
'id': AGENT_IDS['Facebook'],
@ -330,7 +345,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.FACEBOOK_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.FACEBOOK_ON_INTDOWN,
'on_extup': plexpy.CONFIG.FACEBOOK_ON_EXTUP,
'on_intup': plexpy.CONFIG.FACEBOOK_ON_INTUP
'on_intup': plexpy.CONFIG.FACEBOOK_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.FACEBOOK_ON_PMSUPDATE
}
]
@ -352,7 +368,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.OSX_NOTIFY_ON_EXTDOWN,
'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_intup': plexpy.CONFIG.OSX_NOTIFY_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.OSX_NOTIFY_ON_PMSUPDATE
})
return agents
@ -1266,7 +1283,7 @@ class TwitterNotifier(object):
def return_config_options(self):
config_option = [{'label': 'Instructions',
'description': 'Step 1: Visit <a href="https://apps.twitter.com/" target="_blank"> \
'description': 'Step 1: Visit <a href="' + helpers.anon_url('https://apps.twitter.com') + '" target="_blank"> \
Twitter Apps</a> to <strong>Create New App</strong>. A vaild "Website" is not required.<br>\
Step 2: Go to <strong>Keys and Access Tokens</strong> and click \
<strong>Create my access token</strong>.<br>\
@ -1301,7 +1318,7 @@ class TwitterNotifier(object):
{'label': 'Include Subject Line',
'value': self.incl_subject,
'name': 'twitter_incl_subject',
'description': 'Include the subject line in the notifications.',
'description': 'Include the subject line with the notifications.',
'input_type': 'checkbox'
}
]
@ -1625,7 +1642,8 @@ class IFTTT(object):
config_option = [{'label': 'Ifttt Maker Channel Key',
'value': self.apikey,
'name': 'ifttt_key',
'description': 'Your Ifttt key. You can get a key from <a href="https://ifttt.com/maker" target="_blank">here</a>.',
'description': 'Your Ifttt key. You can get a key from'
' <a href="' + helpers.anon_url('https://ifttt.com/maker') + '" target="_blank">here</a>.',
'input_type': 'text'
},
{'label': 'Ifttt Event',
@ -1699,19 +1717,23 @@ class TELEGRAM(object):
config_option = [{'label': 'Telegram Bot Token',
'value': self.bot_token,
'name': 'telegram_bot_token',
'description': 'Your Telegram bot token. Contact <a href="http://telegram.me/BotFather" target="_blank">@BotFather</a> on Telegram to get one.',
'description': 'Your Telegram bot token. '
'Contact <a href="' + helpers.anon_url('https://telegram.me/BotFather') + '" target="_blank">@BotFather</a>'
' on Telegram to get one.',
'input_type': 'text'
},
{'label': 'Telegram Chat ID, Group ID, or Channel Username',
'value': self.chat_id,
'name': 'telegram_chat_id',
'description': 'Your Telegram Chat ID, Group ID, or @channelusername. Contact <a href="http://telegram.me/myidbot" target="_blank">@myidbot</a> on Telegram to get an ID.',
'description': 'Your Telegram Chat ID, Group ID, or @channelusername. '
'Contact <a href="' + helpers.anon_url('https://telegram.me/myidbot') + '" target="_blank">@myidbot</a>'
' on Telegram to get an ID.',
'input_type': 'text'
},
{'label': 'Include Subject Line',
'value': self.incl_subject,
'name': 'telegram_incl_subject',
'description': 'Include the subject line in the notifications.',
'description': 'Include the subject line with the notifications.',
'input_type': 'checkbox'
}
]
@ -1809,7 +1831,7 @@ class SLACK(object):
{'label': 'Include Subject Line',
'value': self.incl_subject,
'name': 'slack_incl_subject',
'description': 'Include the subject line in the notifications.',
'description': 'Include the subject line with the notifications.',
'input_type': 'checkbox'
}
]
@ -1880,14 +1902,14 @@ class Scripts(object):
elif notify_action == 'resume':
script = plexpy.CONFIG.SCRIPTS_ON_RESUME_SCRIPT
elif notify_action == 'watched':
script = plexpy.CONFIG.SCRIPTS_ON_WATCHED_SCRIPT
elif notify_action == 'buffer':
script = plexpy.CONFIG.SCRIPTS_ON_BUFFER_SCRIPT
elif notify_action == 'extdown':
script = plexpy.CONFIG.SCRIPTS_ON_EXTDOWN_SCRIPT
elif notify_action == 'extup':
script = plexpy.CONFIG.SCRIPTS_ON_EXTUP_SCRIPT
elif notify_action == 'created':
script = plexpy.CONFIG.SCRIPTS_ON_CREATED_SCRIPT
elif notify_action == 'intdown':
script = plexpy.CONFIG.SCRIPTS_ON_INTDOWN_SCRIPT
@ -1895,11 +1917,14 @@ class Scripts(object):
elif notify_action == 'intup':
script = plexpy.CONFIG.SCRIPTS_ON_INTUP_SCRIPT
elif notify_action == 'created':
script = plexpy.CONFIG.SCRIPTS_ON_CREATED_SCRIPT
elif notify_action == 'extdown':
script = plexpy.CONFIG.SCRIPTS_ON_EXTDOWN_SCRIPT
elif notify_action == 'watched':
script = plexpy.CONFIG.SCRIPTS_ON_WATCHED_SCRIPT
elif notify_action == 'extup':
script = plexpy.CONFIG.SCRIPTS_ON_EXTUP_SCRIPT
elif notify_action == 'pmsupdate':
script = plexpy.CONFIG.SCRIPTS_ON_PMSUPDATE_SCRIPT
else:
# For manual scripts
@ -2041,13 +2066,6 @@ class Scripts(object):
'input_type': 'select',
'select_options': self.list_scripts()
},
{'label': 'Plex Remote Access Down',
'value': plexpy.CONFIG.SCRIPTS_ON_EXTDOWN_SCRIPT,
'name': 'scripts_on_extdown_script',
'description': 'Choose the script for Plex remote access down.',
'input_type': 'select',
'select_options': self.list_scripts()
},
{'label': 'Plex Server Down',
'value': plexpy.CONFIG.SCRIPTS_ON_INTDOWN_SCRIPT,
'name': 'scripts_on_intdown_script',
@ -2055,6 +2073,20 @@ class Scripts(object):
'input_type': 'select',
'select_options': self.list_scripts()
},
{'label': 'Plex Server Back Up',
'value': plexpy.CONFIG.SCRIPTS_ON_INTUP_SCRIPT,
'name': 'scripts_on_intup_script',
'description': 'Choose the script for Plex server back up.',
'input_type': 'select',
'select_options': self.list_scripts()
},
{'label': 'Plex Remote Access Down',
'value': plexpy.CONFIG.SCRIPTS_ON_EXTDOWN_SCRIPT,
'name': 'scripts_on_extdown_script',
'description': 'Choose the script for Plex remote access down.',
'input_type': 'select',
'select_options': self.list_scripts()
},
{'label': 'Plex Remote Access Back Up',
'value': plexpy.CONFIG.SCRIPTS_ON_EXTUP_SCRIPT,
'name': 'scripts_on_extup_script',
@ -2062,10 +2094,10 @@ class Scripts(object):
'input_type': 'select',
'select_options': self.list_scripts()
},
{'label': 'Plex Server Back Up',
'value': plexpy.CONFIG.SCRIPTS_ON_INTUP_SCRIPT,
'name': 'scripts_on_intup_script',
'description': 'Choose the script for Plex server back up.',
{'label': 'Plex Update Available',
'value': plexpy.CONFIG.SCRIPTS_ON_PMSUPDATE_SCRIPT,
'name': 'scripts_on_pmsupdate_script',
'description': 'Choose the script for Plex update available.',
'input_type': 'select',
'select_options': self.list_scripts()
}
@ -2082,6 +2114,7 @@ class FacebookNotifier(object):
self.app_id = plexpy.CONFIG.FACEBOOK_APP_ID
self.app_secret = plexpy.CONFIG.FACEBOOK_APP_SECRET
self.group_id = plexpy.CONFIG.FACEBOOK_GROUP
self.incl_pmslink = plexpy.CONFIG.FACEBOOK_INCL_PMSLINK
self.incl_poster = plexpy.CONFIG.FACEBOOK_INCL_POSTER
self.incl_subject = plexpy.CONFIG.FACEBOOK_INCL_SUBJECT
@ -2139,10 +2172,27 @@ class FacebookNotifier(object):
poster_url = metadata.get('poster_url','')
if poster_url:
if metadata['media_type'] == 'movie' or metadata['media_type'] == 'show':
if metadata['media_type'] == 'movie':
title = metadata['title']
subtitle = metadata['year']
rating_key = metadata['rating_key']
if metadata.get('imdb_url',''):
poster_link = metadata.get('imdb_url', '')
caption = 'View on IMDB.'
elif metadata.get('themoviedb_url',''):
poster_link = metadata.get('themoviedb_url', '')
caption = 'View on The Movie Database.'
elif metadata['media_type'] == 'show':
title = metadata['title']
subtitle = metadata['year']
rating_key = metadata['rating_key']
if metadata.get('thetvdb_url',''):
poster_link = metadata.get('thetvdb_url', '')
caption = 'View on TheTVDB.'
elif metadata.get('themoviedb_url',''):
poster_link = metadata.get('themoviedb_url', '')
caption = 'View on The Movie Database.'
elif metadata['media_type'] == 'episode':
title = '%s - %s' % (metadata['grandparent_title'], metadata['title'])
@ -2150,26 +2200,44 @@ class FacebookNotifier(object):
'\xc2\xb7'.decode('utf8'),
metadata['media_index'])
rating_key = metadata['rating_key']
if metadata.get('thetvdb_url',''):
poster_link = metadata.get('thetvdb_url', '')
caption = 'View on TheTVDB.'
elif metadata.get('themoviedb_url',''):
poster_link = metadata.get('themoviedb_url', '')
caption = 'View on The Movie Database.'
elif metadata['media_type'] == 'artist':
title = metadata['title']
subtitle = ''
rating_key = metadata['rating_key']
if metadata.get('lastfm_url',''):
poster_link = metadata.get('lastfm_url', '')
caption = 'View on Last.fm.'
elif metadata['media_type'] == 'track':
title = '%s - %s' % (metadata['grandparent_title'], metadata['title'])
subtitle = metadata['parent_title']
rating_key = metadata['parent_rating_key']
caption = 'View in Plex Web.'
if metadata.get('lastfm_url',''):
poster_link = metadata.get('lastfm_url', '')
caption = 'View on Last.fm.'
# Build Facebook post attachment
attachment['link'] = 'http://app.plex.tv/web/app#!/server/' + plexpy.CONFIG.PMS_IDENTIFIER + \
'/details/%2Flibrary%2Fmetadata%2F' + rating_key
if self.incl_pmslink:
caption = 'View on Plex Web.'
attachment['link'] = 'http://app.plex.tv/web/app#!/server/' + plexpy.CONFIG.PMS_IDENTIFIER + \
'/details/%2Flibrary%2Fmetadata%2F' + rating_key
attachment['caption'] = caption
elif poster_link:
attachment['link'] = poster_link
attachment['caption'] = caption
else:
attachment['link'] = poster_url
attachment['picture'] = poster_url
attachment['name'] = title
attachment['description'] = subtitle
attachment['caption'] = caption
try:
api.put_wall_post(profile_id=self.group_id, message=message, attachment=attachment)
@ -2186,7 +2254,7 @@ class FacebookNotifier(object):
def return_config_options(self):
config_option = [{'label': 'Instructions',
'description': '<strong>Facebook notifications are currently experimental!</strong><br><br> \
Step 1: Visit <a href="https://developers.facebook.com/apps/" target="_blank"> \
Step 1: Visit <a href="' + helpers.anon_url('https://developers.facebook.com/apps') + '" target="_blank"> \
Facebook Developers</a> to add a new app using <strong>basic setup</strong>.<br>\
Step 2: Go to <strong>Settings > Basic</strong> and fill in a \
<strong>Contact Email</strong>.<br>\
@ -2195,7 +2263,7 @@ class FacebookNotifier(object):
Step 4: Go to <strong>App Review</strong> and toggle public to <strong>Yes</strong>.<br>\
Step 5: Fill in the <strong>PlexPy URL</strong> below with the exact same URL from Step 3.<br>\
Step 6: Fill in the <strong>App ID</strong> and <strong>App Secret</strong> below.<br>\
Step 7: Click the <strong>Request Authorization</strong> button below.<br> \
Step 7: Click the <strong>Request Authorization</strong> button below.<br>\
Step 8: Fill in the <strong>Group ID</strong> below.',
'input_type': 'help'
},
@ -2232,13 +2300,20 @@ class FacebookNotifier(object):
{'label': 'Include Poster Image',
'value': self.incl_poster,
'name': 'facebook_incl_poster',
'description': 'Include a poster and link in the notifications.',
'description': 'Include a poster with the notifications.',
'input_type': 'checkbox'
},
{'label': 'Include Link to Plex Web',
'value': self.incl_pmslink,
'name': 'facebook_incl_pmslink',
'description': 'Include a link to the media in Plex Web with the notifications.<br>'
'If disabled, the link will go to IMDB, TVDB, TMDb, or Last.fm instead, if available.',
'input_type': 'checkbox'
},
{'label': 'Include Subject Line',
'value': self.incl_subject,
'name': 'facebook_incl_subject',
'description': 'Include the subject line in the notifications.',
'description': 'Include the subject line with the notifications.',
'input_type': 'checkbox'
}
]

View file

@ -251,7 +251,7 @@ def import_from_plexwatch(database=None, table_name=None, import_ignore_interval
hours=0, minutes=0, seconds=0)
plexpy.schedule_job(activity_pinger.check_recently_added, 'Check for recently added items',
hours=0, minutes=0, seconds=0)
plexpy.schedule_job(activity_pinger.check_server_response, 'Check for server response',
plexpy.schedule_job(activity_pinger.check_server_response, 'Check for Plex remote access',
hours=0, minutes=0, seconds=0)
ap = activity_processor.ActivityProcessor()
@ -302,7 +302,7 @@ def import_from_plexwatch(database=None, table_name=None, import_ignore_interval
# Skip line if we don't have a ratingKey to work with
if not row['rating_key']:
logger.error(u"PlexPy Importer :: Skipping record due to null ratingRey.")
logger.error(u"PlexPy Importer :: Skipping record due to null ratingKey.")
continue
# If the user_id no longer exists in the friends list, pull it from the xml.

View file

@ -389,6 +389,38 @@ class PmsConnect(object):
return request
def put_updater(self, output_format=''):
"""
Refresh updater status.
Optional parameters: output_format { dict, json }
Output: array
"""
uri = '/updater/check?download=0'
request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='PUT',
output_format=output_format)
return request
def get_updater(self, output_format=''):
"""
Return updater status.
Optional parameters: output_format { dict, json }
Output: array
"""
uri = '/updater/status'
request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET',
output_format=output_format)
return request
def get_recently_added_details(self, section_id='', count='0'):
"""
Return processed and validated list of recently added items.
@ -1008,7 +1040,7 @@ class PmsConnect(object):
'user_id': user_details['user_id'],
'friendly_name': user_details['friendly_name'],
'user_thumb': user_details['user_thumb'],
'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split(':')[-1],
'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split('::ffff:')[-1],
'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'),
'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'),
'machine_id': machine_id,
@ -1130,7 +1162,7 @@ class PmsConnect(object):
'user_id': user_details['user_id'],
'friendly_name': user_details['friendly_name'],
'user_thumb': user_details['user_thumb'],
'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split(':')[-1],
'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split('::ffff:')[-1],
'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'),
'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'),
'machine_id': machine_id,
@ -1188,7 +1220,7 @@ class PmsConnect(object):
'user_id': user_details['user_id'],
'friendly_name': user_details['friendly_name'],
'user_thumb': user_details['user_thumb'],
'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split(':')[-1],
'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split('::ffff:')[-1],
'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'),
'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'),
'machine_id': machine_id,
@ -1246,7 +1278,7 @@ class PmsConnect(object):
'user_id': user_details['user_id'],
'friendly_name': user_details['friendly_name'],
'user_thumb': user_details['user_thumb'],
'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split(':')[-1],
'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split('::ffff:')[-1],
'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'),
'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'),
'machine_id': machine_id,
@ -1337,7 +1369,7 @@ class PmsConnect(object):
'user_id': user_details['user_id'],
'friendly_name': user_details['friendly_name'],
'user_thumb': user_details['user_thumb'],
'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split(':')[-1],
'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split('::ffff:')[-1],
'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'),
'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'),
'machine_id': machine_id,
@ -1479,7 +1511,7 @@ class PmsConnect(object):
xml_head = identity.getElementsByTagName('MediaContainer')
except Exception as e:
logger.warn(u"PlexPy Pmsconnect :: Unable to parse XML for get_local_server_identity: %s." % e)
return []
return {}
server_identity = {}
for a in xml_head:
@ -1995,3 +2027,40 @@ class PmsConnect(object):
}
return server_response
def get_update_staus(self):
# Refresh the Plex updater status first
self.put_updater()
updater_status = self.get_updater(output_format='xml')
try:
xml_head = updater_status.getElementsByTagName('MediaContainer')
except Exception as e:
logger.warn(u"PlexPy Pmsconnect :: Unable to parse XML for get_update_staus: %s." % e)
# Catch the malformed XML on certain PMX version.
# XML parser helper returns empty list if there is an error parsing XML
if updater_status == []:
logger.warn(u"Plex API updater XML is broken on the current PMS version. Please update your PMS manually.")
logger.info(u"PlexPy is unable to check for Plex updates. Disabling check for Plex updates.")
# Disable check for Plex updates
plexpy.CONFIG.MONITOR_PMS_UPDATES = 0
plexpy.initialize_scheduler()
plexpy.CONFIG.write()
return {}
updater_info = {}
for a in xml_head:
if a.getElementsByTagName('Release'):
release = a.getElementsByTagName('Release')
for item in release:
updater_info = {'can_install': helpers.get_xml_attr(a, 'canInstall'),
'download_url': helpers.get_xml_attr(a, 'downloadURL'),
'version': helpers.get_xml_attr(item, 'version'),
'state': helpers.get_xml_attr(item, 'state'),
'changelog': helpers.get_xml_attr(item, 'fixed')
}
return updater_info

View file

@ -50,8 +50,7 @@ class Users(object):
'session_history_metadata.year',
'session_history_metadata.media_index',
'session_history_metadata.parent_media_index',
'session_history_media_info.video_decision',
'session_history_media_info.audio_decision',
'session_history_media_info.transcode_decision',
'users.do_notify as do_notify',
'users.keep_history as keep_history'
]
@ -117,8 +116,7 @@ class Users(object):
'year': item['year'],
'media_index': item['media_index'],
'parent_media_index': item['parent_media_index'],
'video_decision': item['video_decision'],
'audio_decision': item['audio_decision'],
'transcode_decision': item['transcode_decision'],
'do_notify': helpers.checked(item['do_notify']),
'keep_history': helpers.checked(item['keep_history'])
}
@ -154,8 +152,7 @@ class Users(object):
'session_history_metadata.year',
'session_history_metadata.media_index',
'session_history_metadata.parent_media_index',
'session_history_media_info.video_decision',
'session_history_media_info.audio_decision',
'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 \
@ -213,8 +210,7 @@ class Users(object):
'year': item['year'],
'media_index': item['media_index'],
'parent_media_index': item['parent_media_index'],
'video_decision': item['video_decision'],
'audio_decision': item['audio_decision'],
'transcode_decision': item['transcode_decision'],
'friendly_name': item['friendly_name']
}
@ -245,7 +241,7 @@ class Users(object):
def get_details(self, user_id=None, user=None):
from plexpy import plextv
default_return = {'user_id': None,
default_return = {'user_id': 0,
'username': 'Local',
'friendly_name': 'Local',
'user_thumb': common.DEFAULT_USER_THUMB,
@ -254,7 +250,7 @@ class Users(object):
'is_allow_sync': 0,
'is_restricted': 0,
'do_notify': 0,
'keep_history': 0
'keep_history': 1
}
if not user_id and not user:
@ -316,7 +312,8 @@ class Users(object):
return user_details
else:
logger.warn(u"PlexPy Users :: Unable to retrieve user from local database. Requesting user list refresh.")
logger.warn(u"PlexPy Users :: Unable to retrieve user %s from database. Requesting user list refresh."
% user_id if user_id else user)
# Let's first refresh the user list to make sure the user isn't newly added and not in the db yet
plextv.refresh_users()
@ -326,7 +323,8 @@ class Users(object):
return user_details
else:
logger.warn(u"PlexPy Users :: Unable to retrieve user from local database. Returning 'Local' user.")
logger.warn(u"PlexPy Users :: Unable to retrieve user %s from database. Returning 'Local' user."
% user_id if user_id else user)
# If there is no user data we must return something
# Use "Local" user to retain compatibility with PlexWatch database value
return default_return

View file

@ -1,2 +1,2 @@
PLEXPY_VERSION = "master"
PLEXPY_RELEASE_VERSION = "1.3.9"
PLEXPY_RELEASE_VERSION = "1.3.10"

View file

@ -25,6 +25,7 @@ import websocket
name = 'websocket'
opcode_data = (websocket.ABNF.OPCODE_TEXT, websocket.ABNF.OPCODE_BINARY)
ws_reconnect = False
def start_thread():
@ -34,6 +35,11 @@ def start_thread():
threading.Thread(target=run).start()
def reconnect():
global ws_reconnect
ws_reconnect = True
def run():
from websocket import create_connection
@ -51,19 +57,21 @@ def run():
if plexpy.CONFIG.PMS_TOKEN:
uri += '?X-Plex-Token=' + plexpy.CONFIG.PMS_TOKEN
global ws_reconnect
ws_reconnect = False
ws_connected = False
reconnects = 0
# Try an open the websocket connection - if it fails after 15 retries fallback to polling
while not ws_connected and reconnects <= 15:
try:
logger.info(u'PlexPy WebSocket :: Opening%s websocket, connection attempt %s.' % (secure, str(reconnects + 1)))
logger.info(u"PlexPy WebSocket :: Opening%s websocket, connection attempt %s." % (secure, str(reconnects + 1)))
ws = create_connection(uri)
reconnects = 0
ws_connected = True
logger.info(u'PlexPy WebSocket :: Ready')
logger.info(u"PlexPy WebSocket :: Ready")
except IOError, e:
logger.error(u'PlexPy WebSocket :: %s.' % e)
logger.error(u"PlexPy WebSocket :: %s." % e)
reconnects += 1
time.sleep(5)
@ -81,22 +89,30 @@ def run():
if reconnects > 1:
time.sleep(5)
logger.warn(u'PlexPy WebSocket :: Connection has closed, reconnecting...')
logger.warn(u"PlexPy WebSocket :: Connection has closed, reconnecting...")
try:
ws = create_connection(uri)
except IOError, e:
logger.info(u'PlexPy WebSocket :: %s.' % e)
logger.info(u"PlexPy WebSocket :: %s." % e)
else:
ws.shutdown()
ws_connected = False
break
if not ws_connected:
logger.error(u'PlexPy WebSocket :: Connection unavailable, falling back to polling.')
# Check if we recieved a restart notification and close websocket connection cleanly
if ws_reconnect:
logger.info(u"PlexPy WebSocket :: Reconnecting websocket...")
ws.shutdown()
ws_connected = False
start_thread()
if not ws_connected and not ws_reconnect:
logger.error(u"PlexPy WebSocket :: Connection unavailable, falling back to polling.")
plexpy.POLLING_FAILOVER = True
plexpy.initialize_scheduler()
logger.debug(u'PlexPy WebSocket :: Leaving thread.')
logger.debug(u"PlexPy WebSocket :: Leaving thread.")
def receive(ws):
@ -124,7 +140,7 @@ def process(opcode, data):
try:
info = json.loads(data)
except Exception as ex:
logger.warn(u'PlexPy WebSocket :: Error decoding message from websocket: %s' % ex)
logger.warn(u"PlexPy WebSocket :: Error decoding message from websocket: %s" % ex)
logger.debug(data)
return False

View file

@ -13,7 +13,8 @@
# 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, notifiers, plextv, pmsconnect, common, log_reader, datafactory, graphs, users, libraries, database
from plexpy import logger, notifiers, plextv, pmsconnect, common, log_reader, \
datafactory, graphs, users, libraries, database, web_socket
from plexpy.helpers import checked, addtoapi, get_ip, create_https_certificates
from mako.lookup import TemplateLookup
@ -253,6 +254,18 @@ class WebInterface(object):
logger.warn(u"Unable to retrieve data for get_recently_added.")
return serve_template(templatename="recently_added.html", data=None)
@cherrypy.expose
def delete_temp_sessions(self):
result = database.delete_sessions()
if result:
cherrypy.response.headers['Content-type'] = 'application/json'
return json.dumps({'message': result})
else:
cherrypy.response.headers['Content-type'] = 'application/json'
return json.dumps({'message': 'no data received'})
##### Libraries #####
@ -776,6 +789,10 @@ class WebInterface(object):
media_type = kwargs.get('media_type', "")
if media_type != 'all':
custom_where.append(['session_history.media_type', media_type])
if 'transcode_decision' in kwargs:
transcode_decision = kwargs.get('transcode_decision', "")
if transcode_decision:
custom_where.append(['session_history_media_info.transcode_decision', transcode_decision])
data_factory = datafactory.DataFactory()
history = data_factory.get_datatables_history(kwargs=kwargs, custom_where=custom_where, grouping=grouping, watched_percent=watched_percent)
@ -989,9 +1006,9 @@ class WebInterface(object):
logger.warn(u"Unable to retrieve data for get_stream_type_by_top_10_platforms.")
@cherrypy.expose
def history_table_modal(self, start_date=None, **kwargs):
def history_table_modal(self, **kwargs):
return serve_template(templatename="history_table_modal.html", title="History Data", data=start_date)
return serve_template(templatename="history_table_modal.html", title="History Data", data=kwargs)
##### Sync #####
@ -1091,6 +1108,19 @@ class WebInterface(object):
cherrypy.response.headers['Content-type'] = 'application/json'
return json.dumps(notifications)
@cherrypy.expose
@addtoapi()
def clearNotifyLogs(self, **kwargs):
data_factory = datafactory.DataFactory()
result = data_factory.delete_notification_log()
if result:
cherrypy.response.headers['Content-type'] = 'application/json'
return json.dumps({'message': result})
else:
cherrypy.response.headers['Content-type'] = 'application/json'
return json.dumps({'message': 'no data received'})
@cherrypy.expose
def clearLogs(self):
plexpy.LOG_LIST = []
@ -1147,8 +1177,9 @@ class WebInterface(object):
"api_key": plexpy.CONFIG.API_KEY,
"update_db_interval": plexpy.CONFIG.UPDATE_DB_INTERVAL,
"freeze_db": checked(plexpy.CONFIG.FREEZE_DB),
"log_dir": plexpy.CONFIG.LOG_DIR,
"backup_dir": plexpy.CONFIG.BACKUP_DIR,
"cache_dir": plexpy.CONFIG.CACHE_DIR,
"log_dir": plexpy.CONFIG.LOG_DIR,
"check_github": checked(plexpy.CONFIG.CHECK_GITHUB),
"interface_list": interface_list,
"cache_sizemb": plexpy.CONFIG.CACHE_SIZEMB,
@ -1178,6 +1209,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_pms_updates": checked(plexpy.CONFIG.MONITOR_PMS_UPDATES),
"monitor_remote_access": checked(plexpy.CONFIG.MONITOR_REMOTE_ACCESS),
"monitoring_interval": plexpy.CONFIG.MONITORING_INTERVAL,
"monitoring_use_websocket": checked(plexpy.CONFIG.MONITORING_USE_WEBSOCKET),
@ -1219,6 +1251,8 @@ class WebInterface(object):
"notify_on_extup_body_text": plexpy.CONFIG.NOTIFY_ON_EXTUP_BODY_TEXT,
"notify_on_intup_subject_text": plexpy.CONFIG.NOTIFY_ON_INTUP_SUBJECT_TEXT,
"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_scripts_args_text": plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT,
"home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH,
"home_stats_type": checked(plexpy.CONFIG.HOME_STATS_TYPE),
@ -1246,12 +1280,15 @@ class WebInterface(object):
"refresh_libraries_on_startup", "refresh_users_on_startup",
"ip_logging_enable", "movie_logging_enable", "tv_logging_enable", "music_logging_enable",
"pms_is_remote", "home_stats_type", "group_history_tables", "notify_consecutive", "notify_upload_posters",
"notify_recently_added", "notify_recently_added_grandparent", "monitor_remote_access", "get_file_sizes"
"notify_recently_added", "notify_recently_added_grandparent",
"monitor_pms_updates", "monitor_remote_access", "get_file_sizes"
]
for checked_config in checked_configs:
if checked_config not in kwargs:
# checked items should be zero or one. if they were not sent then the item was not checked
kwargs[checked_config] = '0'
kwargs[checked_config] = 0
else:
kwargs[checked_config] = 1
# If http password exists in config, do not overwrite when blank value received
if kwargs.get('http_password'):
@ -1274,13 +1311,14 @@ class WebInterface(object):
if kwargs.get('monitoring_interval') != str(plexpy.CONFIG.MONITORING_INTERVAL) or \
kwargs.get('refresh_libraries_interval') != str(plexpy.CONFIG.REFRESH_LIBRARIES_INTERVAL) or \
kwargs.get('refresh_users_interval') != str(plexpy.CONFIG.REFRESH_USERS_INTERVAL) or \
kwargs.get('notify_recently_added') != str(plexpy.CONFIG.NOTIFY_RECENTLY_ADDED) or \
kwargs.get('monitor_remote_access') != str(plexpy.CONFIG.MONITOR_REMOTE_ACCESS):
kwargs.get('notify_recently_added') != plexpy.CONFIG.NOTIFY_RECENTLY_ADDED or \
kwargs.get('monitor_pms_updates') != plexpy.CONFIG.MONITOR_PMS_UPDATES or \
kwargs.get('monitor_remote_access') != plexpy.CONFIG.MONITOR_REMOTE_ACCESS:
reschedule = True
# If we change the SSL setting for PMS or PMS remote setting, make sure we grab the new url.
if kwargs.get('pms_ssl') != str(plexpy.CONFIG.PMS_SSL) or \
kwargs.get('pms_is_remote') != str(plexpy.CONFIG.PMS_IS_REMOTE):
if kwargs.get('pms_ssl') != plexpy.CONFIG.PMS_SSL or \
kwargs.get('pms_is_remote') != plexpy.CONFIG.PMS_IS_REMOTE:
server_changed = True
# If we change the HTTPS setting, make sure we generate a new certificate.
@ -1327,6 +1365,7 @@ class WebInterface(object):
if server_changed:
plextv.get_real_pms_url()
pmsconnect.get_server_friendly_name()
web_socket.reconnect()
# Reconfigure scheduler if intervals changed
if reschedule:
@ -1630,11 +1669,16 @@ class WebInterface(object):
if source == 'history':
data_factory = datafactory.DataFactory()
metadata = data_factory.get_metadata_details(rating_key=rating_key)
poster_url = data_factory.get_poster_url(metadata=metadata)
metadata['poster_url'] = poster_url
else:
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_metadata_details(rating_key=rating_key, get_media_info=True)
if result:
metadata = result['metadata']
data_factory = datafactory.DataFactory()
poster_url = data_factory.get_poster_url(metadata=metadata)
metadata['poster_url'] = poster_url
if metadata:
return serve_template(templatename="info.html", data=metadata, title="Info", config=config, source=source)
@ -1681,6 +1725,22 @@ class WebInterface(object):
return None
@cherrypy.expose
def delete_poster_url(self, poster_url=''):
if poster_url:
data_factory = datafactory.DataFactory()
result = data_factory.delete_poster_url(poster_url=poster_url)
else:
result = None
if result:
cherrypy.response.headers['Content-type'] = 'application/json'
return json.dumps({'message': result})
else:
cherrypy.response.headers['Content-type'] = 'application/json'
return json.dumps({'message': 'no data received'})
##### Search #####
@ -2183,3 +2243,9 @@ class WebInterface(object):
a = Api()
a.checkParams(*args, **kwargs)
return a.fetchData()
@cherrypy.expose
def check_pms_updater(self):
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_update_staus()
return json.dumps(result)