Compare commits

..

24 Commits

Author SHA1 Message Date
JonnyWong16
14b98a32e0 v2.5.6 2020-10-02 20:35:06 -07:00
JonnyWong16
a985cec9c2 Fix loading synced items for guest access 2020-10-02 11:16:49 -07:00
JonnyWong16
5dc0d5536d Also add username hover to most active card 2020-09-29 21:00:47 -07:00
JonnyWong16
e3eca5af46 Change friendly name hover title text to username 2020-09-29 20:07:23 -07:00
JonnyWong16
d9ece291b7 Fix 1px off dropdown menus 2020-09-28 18:23:07 -07:00
JonnyWong16
221d6e136a Added remote access down notification threshold setting 2020-09-27 19:31:26 -07:00
JonnyWong16
ad6e314343 Don't floor newsletter start date 2020-09-27 17:44:11 -07:00
JonnyWong16
2a4b48d0fa Clean up Telegram send poster 2020-09-26 19:03:24 -07:00
JonnyWong16
a8e0502b41 Merge pull request #1377 from JohnnyKing94/master
Added Silent Notification option for Telegram Agent
2020-09-26 18:44:58 -07:00
JonnyWong16
ccf0e0dae7 Add default thumb and art to Live TV library 2020-09-26 18:32:13 -07:00
JonnyWong16
bfa4d3dfec Add library name to fix metadata modal 2020-09-26 18:03:29 -07:00
JonnyWong16
93997c11dc Add playback error notification trigger 2020-09-21 18:31:19 -07:00
JonnyWong16
7ce92d5f17 Add error state icon to activity card and history table 2020-09-21 18:30:41 -07:00
JonnyWong16
9740010368 Add container_decision to notification parameters 2020-09-21 18:06:40 -07:00
JonnyWong16
e4e0b765b6 Rename container transcoding to converting on activity cards 2020-09-21 17:57:01 -07:00
Gianfranco
721cf5c930 Renamed 'silent_message' to 'silent_notification.'
Signed-off-by: Gianfranco <gianfry94@hotmail.it>
2020-09-14 20:46:22 +02:00
Gianfranco
f07bcca96a Wording changes
Signed-off-by: Gianfranco <gianfry94@hotmail.it>
2020-09-14 20:26:27 +02:00
JonnyWong16
056d0d81ac Improve activity monitor session start log message 2020-09-14 09:19:33 -07:00
JonnyWong16
38ccd37867 Fix QR code not showing up for localhost address 2020-09-14 08:51:22 -07:00
Gianfranco
21799116c5 Reworked the Telegram Agent code to include the "silent_message" option. Both cases are now managed and the alerts are being respected.
Signed-off-by: Gianfranco <gianfry94@hotmail.it>
2020-09-14 12:46:45 +02:00
JonnyWong16
60bdf1d1ce Schedule database pragma optimize 2020-09-12 14:18:31 -07:00
JonnyWong16
02658759ea Fix purge library from the edit modal 2020-09-10 08:35:26 -07:00
JonnyWong16
68946ceede Add uninstall before installing to Windows installer 2020-09-06 19:03:47 -07:00
Gianfranco
81ee44b60f Added "silent_message" option for Telegram Agent
Added a new checkbox in the notification telegram config in order to send new messages silently. In this way the telegram users will receive a notification with no sound.
2020-08-21 22:49:26 +02:00
30 changed files with 271 additions and 101 deletions

12
API.md
View File

@@ -85,10 +85,10 @@ Delete all Tautulli history for a specific library.
```
Required parameters:
server_id (str): The Plex server identifier of the library section
section_id (str): The id of the Plex library section
Optional parameters:
server_id (str): The Plex server identifier of the library section
row_ids (str): Comma separated row ids to delete, e.g. "2,3,8"
Returns:
@@ -159,10 +159,10 @@ Delete a library section from Tautulli. Also erases all history for the library.
```
Required parameters:
server_id (str): The Plex server identifier of the library section
section_id (str): The id of the Plex library section
Optional parameters:
server_id (str): The Plex server identifier of the library section
row_ids (str): Comma separated row ids to delete, e.g. "2,3,8"
Returns:
@@ -427,6 +427,7 @@ Returns:
"children_count": "",
"collections": [],
"container": "mkv",
"container_decision": "direct play",
"content_rating": "TV-MA",
"deleted_user": 0,
"device": "Windows",
@@ -1065,12 +1066,14 @@ Returns:
[{"friendly_name": "Jon Snow",
"total_plays": 170,
"user_id": 133788,
"user_thumb": "https://plex.tv/users/k10w42309cynaopq/avatar"
"user_thumb": "https://plex.tv/users/k10w42309cynaopq/avatar",
"username": "LordCommanderSnow"
},
{"platform_type": "DanyKhaleesi69",
"total_plays": 42,
"user_id": 8008135,
"user_thumb": "https://plex.tv/users/568gwwoib5t98a3a/avatar"
"user_thumb": "https://plex.tv/users/568gwwoib5t98a3a/avatar",
"username: "DanyKhaleesi69"
},
{...},
{...}
@@ -2544,6 +2547,7 @@ Returns:
"transcode_decision": "transcode",
"user_id": 133788,
"user_thumb": "https://plex.tv/users/568gwwoib5t98a3a/avatar",
"username": "LordCommanderSnow",
"year": 2016
},
{...},

View File

@@ -1,5 +1,28 @@
# Changelog
## v2.5.6 (2020-10-02)
* Activity:
* Change: Renamed container "Transcode" to "Converting" on activity cards.
* Notifications:
* New: Added a silent notification option for Telegram. (Thanks @JohnnyKing94)
* New: Added container_decision notification parameter.
* New: Added notification trigger for Playback Error.
* New: Added remote access down notification threshold setting.
* Newsletter:
* Change: Stop flooring newsletter start date.
* UI:
* Fix: Unable to purge history from the library edit modal.
* Fix: QR code not showing up for localhost address when trying to register a device.
* New: Added library name to the fix metadata modal.
* API:
* New: Added default thumb and art to the Live TV library.
* Other:
* Fix: Synced items not loading for guest access.
* New: Schedule some more automatic database optimizations.
* Change: Added automatic uninstall before installing to the Windows installer.
## v2.5.5 (2020-09-06)
* Activity:

View File

@@ -475,6 +475,10 @@ fieldset[disabled] .btn-bright.active {
}
.btn-group select {
margin-top: 0;
height: 34px;
}
.btn-group label {
margin-bottom: 0;
}
.input-group-addon-form {
display: inline-block;
@@ -488,9 +492,6 @@ fieldset[disabled] .btn-bright.active {
width: 100%;
margin-top: 5px;
}
#user-selection label {
margin-bottom: 0;
}
.alert-edit {
display: none;
float: left;
@@ -2112,7 +2113,7 @@ a:hover .item-children-poster {
margin-right: 20px;
}
#new_title h3 {
color: #f9be03;
color: #E5A00D;
font-size: 14px;
line-height: 1.42857143;
font-weight: bold;
@@ -2361,9 +2362,6 @@ a .library-user-instance-box:hover {
-moz-box-shadow: inset 0 0 0 2px #e9a049;
box-shadow: inset 0 0 0 2px #e9a049;
}
#watched-stats-days-selection label {
margin-bottom: 0;
}
.home-padded-header {
margin: 25px 0;
height: 34px;
@@ -3454,10 +3452,6 @@ pre::-webkit-scrollbar-thumb {
.activity-queue tr:nth-child(even) td {
background-color: rgba(255,255,255,0.010);
}
#days-selection label,
#months-selection label {
margin-bottom: 0;
}
.card-sortable {
height: 36px;
padding: 0 20px 0 0;
@@ -3720,10 +3714,6 @@ a:hover .overlay-refresh-image:hover {
padding-top: 10px;
padding-bottom: 10px;
}
#plexpy-log-levels label,
#plex-log-levels label {
margin-bottom: 0;
}
#plexpy-notifiers-table .friendly_name,
#notifier-config-modal span.notifier_id,
#plexpy-newsletters-table .friendly_name,

View File

@@ -218,7 +218,7 @@ DOCUMENTATION :: END
<div class="sub-heading">Container</div>
<div class="sub-value" id="transcode_container-${sk}">
% if data['stream_container_decision'] == 'transcode':
Transcode (${data['container'].upper()} <i class="fa fa-long-arrow-right"></i> ${data['stream_container'].upper()})
Converting (${data['container'].upper()} <i class="fa fa-long-arrow-right"></i> ${data['stream_container'].upper()})
% else:
Direct Play (${data['stream_container'].upper()})
% endif
@@ -397,7 +397,7 @@ DOCUMENTATION :: END
</div>
</div>
<div class="dashboard-activity-metadata-wrapper">
<a href="${user_href}" title="${data['friendly_name']}">
<a href="${user_href}" title="${data['username']}">
<div class="dashboard-activity-metadata-user-thumb" style="background-image: url(${data['user_thumb']});"></div>
</a>
<div class="dashboard-activity-metadata-title-container">
@@ -408,6 +408,8 @@ DOCUMENTATION :: END
<i class="fa fa-fw fa-pause"></i>&nbsp;
% elif data['state'] == 'buffering':
<i class="fa fa-fw fa-spinner"></i>&nbsp;
% elif data['state'] == 'error':
<i class="fa fa-fw fa-exclamation-triangle"></i>&nbsp;
% endif
</div>
<div class="dashboard-activity-metadata-title">
@@ -519,7 +521,7 @@ DOCUMENTATION :: END
% endif
</div>
<div class="dashboard-activity-metadata-user">
<a href="${user_href}" title="${data['friendly_name']}">${data['friendly_name']}</a>
<a href="${user_href}" title="${data['username']}">${data['friendly_name']}</a>
</div>
</div>
</div>

View File

@@ -115,21 +115,13 @@ DOCUMENTATION :: END
var msg = 'Are you REALLY sure you want to purge all history for the <strong>${data["section_name"]}</strong> library?<br>' +
'This is permanent and cannot be undone!';
var url = 'delete_all_library_history';
confirmAjaxCall(url, msg, { section_id: '${data["section_id"]}' }, null, function () { location.reload(); });
confirmAjaxCall(url, msg, { server_id: '${server_id}', section_id: '${data["section_id"]}' }, null, function () { location.reload(); });
});
$('#undelete-library').click(function () {
var msg = 'Are you sure you want to undelete this user?';
var msg = 'Are you sure you want to undelete this library?';
var url = 'undelete_library';
confirmAjaxCall(url, msg, { section_id: '${data["section_id"]}' }, null, function () { location.reload(); });
});
$(document).ready(function() {
// Move #confirm-modal to parent container
if (!($('#edit-library-modal').next().is('#confirm-modal-purge'))) {
$('#confirm-modal-purge').appendTo($('#edit-library-modal').parent());
}
$('#edit-library-modal > #confirm-modal-purge').remove();
});
</script>
% endif

View File

@@ -134,13 +134,5 @@ DOCUMENTATION :: END
var url = 'undelete_user';
confirmAjaxCall(url, msg, { user_id: '${data["user_id"]}' }, null, function () { location.reload(); });
});
$(document).ready(function() {
// Move #confirm-modal-purge to parent container
if (!($('#edit-user-modal').next().is('#confirm-modal-purge'))) {
$('#confirm-modal-purge').appendTo($('#edit-user-modal').parent());
}
$('#edit-user-modal > #confirm-modal-purge').remove();
});
</script>
% endif

View File

@@ -104,7 +104,7 @@ DOCUMENTATION :: END
</div>
% elif stat_id == 'top_users':
<% user_href = page('user', row0['user_id']) if row0['user_id'] else '#' %>
<a id="stats-thumb-url-${stat_id}" href="${user_href}" title="${row0['friendly_name']}" class="hidden-xs">
<a id="stats-thumb-url-${stat_id}" href="${user_href}" title="${row0['user']}" class="hidden-xs">
<div id="stats-thumb-${stat_id}" class="dashboard-stats-circle" style="background-image: url(${row0['user_thumb'] or 'images/gravatar-default.png'})"></div>
</a>
% elif stat_id == 'top_platforms':
@@ -122,7 +122,7 @@ DOCUMENTATION :: END
% elif stat_id.startswith('popular'):
<span class="dashboard-stats-stats-units">users</span>
% elif stat_id == 'last_watched':
<span class="dashboard-stats-stats-units" id="last-watched-header-info">${row0['friendly_name']}</span>
<span class="dashboard-stats-stats-units" id="last-watched-header-info" title="${row0['user']}">${row0['friendly_name']}</span>
% elif stat_id == 'most_concurrent':
<span class="dashboard-stats-stats-units" id="most-concurrent-header-info">streams</span>
% endif
@@ -134,7 +134,7 @@ DOCUMENTATION :: END
<li class="dashboard-stats-info-item ${'expanded' if loop.index == 0 else ''}" data-stat_id="${stat_id}"
data-rating_key="${row.get('rating_key')}" data-guid="${row.get('guid')}" data-title="${row.get('title')}"
data-art="${row.get('art')}" data-thumb="${row.get('thumb')}" data-platform="${row.get('platform_name')}"
data-user_id="${row.get('user_id')}" data-friendly_name="${row.get('friendly_name')}" data-user_thumb="${row.get('user_thumb')}"
data-user_id="${row.get('user_id')}" data-user="${row.get('user')}" data-friendly_name="${row.get('friendly_name')}" data-user_thumb="${row.get('user_thumb')}"
data-last_watch="${row.get('last_watch')}" data-started="${row.get('started')}" data-live="${row.get('live')}">
<div class="sub-list">${loop.index + 1}</div>
<div class="sub-value">
@@ -152,7 +152,7 @@ DOCUMENTATION :: END
</a>
% elif stat_id == 'top_users':
<% user_href = page('user', row['user_id']) if row['user_id'] else '#' %>
<a href="${user_href}" title="${row['friendly_name']}">
<a href="${user_href}" title="${row['user']}">
${row['friendly_name']}
</a>
% elif stat_id == 'top_platforms':

View File

@@ -377,6 +377,9 @@
case 'buffering':
state_icon = '<i class="fa fa-fw fa-spinner"></i>&nbsp;';
break;
case 'error':
state_icon = '<i class="fa fa-fw fa-exclamation-triangle"></i>&nbsp;';
break;
default:
state_icon = '<i class="fa fa-fw fa-question-circle"></i>&nbsp;';
}
@@ -431,7 +434,7 @@
var transcode_container = '';
if (s.stream_container_decision === 'transcode') {
transcode_container = 'Transcode (' + s.container.toUpperCase() + ' <i class="fa fa-long-arrow-right"></i> ' + s.stream_container.toUpperCase() + ')';
transcode_container = 'Converting (' + s.container.toUpperCase() + ' <i class="fa fa-long-arrow-right"></i> ' + s.stream_container.toUpperCase() + ')';
} else {
transcode_container = 'Direct Play (' + s.stream_container.toUpperCase() + ')';
}
@@ -756,7 +759,7 @@
if (user_id) {
href = page('user', user_id);
}
$('#stats-thumb-url-' + stat_id).attr('href', href).prop('title', $(elem).data('friendly_name'));
$('#stats-thumb-url-' + stat_id).attr('href', href).prop('title', $(elem).data('user'));
} else if (stat_id === 'top_platforms') {
$('#stats-thumb-' + stat_id).removeClass(function (index, className) {
return (className.match (/(^|\s)platform-\S+/g) || []).join(' ');

View File

@@ -65,7 +65,7 @@ DOCUMENTATION :: END
<ul class="item-children-instance list-unstyled">
% for child in data['results_list']['collection']:
<li>
<a href="${page('info', child['rating_key'])}" id="${child['rating_key']}">
<a href="${page('info', child['rating_key'])}" data-rating_key="${child['rating_key']}" data-library_name="${child['library_name']}">
<div class="item-children-poster">
<div class="item-children-poster-face poster-item" style="background-image: url(${page('pms_image_proxy', child['thumb'], child['rating_key'], 300, 450, fallback='poster')});"></div>
% if _session['user_group'] == 'admin':
@@ -90,7 +90,7 @@ DOCUMENTATION :: END
<ul class="item-children-instance list-unstyled">
% for child in data['results_list']['movie']:
<li>
<a href="${page('info', child['rating_key'])}" id="${child['rating_key']}">
<a href="${page('info', child['rating_key'])}" data-rating_key="${child['rating_key']}" data-library_name="${child['library_name']}">
<div class="item-children-poster">
<div class="item-children-poster-face poster-item" style="background-image: url(${page('pms_image_proxy', child['thumb'], child['rating_key'], 300, 450, fallback='poster')});"></div>
% if _session['user_group'] == 'admin':
@@ -115,7 +115,7 @@ DOCUMENTATION :: END
<ul class="item-children-instance list-unstyled">
% for child in data['results_list']['show']:
<li>
<a href="${page('info', child['rating_key'])}" id="${child['rating_key']}">
<a href="${page('info', child['rating_key'])}" data-rating_key="${child['rating_key']}" data-library_name="${child['library_name']}">
<div class="item-children-poster">
<div class="item-children-poster-face poster-item" style="background-image: url(${page('pms_image_proxy', child['thumb'], child['rating_key'], 300, 450, fallback='poster')});"></div>
% if _session['user_group'] == 'admin':
@@ -140,7 +140,7 @@ DOCUMENTATION :: END
<ul class="item-children-instance list-unstyled">
% for child in data['results_list']['season']:
<li>
<a href="${page('info', child['rating_key'])}" id="${child['rating_key']}">
<a href="${page('info', child['rating_key'])}" data-rating_key="${child['rating_key']}" data-library_name="${child['library_name']}">
<div class="item-children-poster">
<div class="item-children-poster-face poster-item" style="background-image: url(${page('pms_image_proxy', child['thumb'], child['rating_key'], 300, 450, fallback='poster')});"></div>
% if _session['user_group'] == 'admin':
@@ -165,7 +165,7 @@ DOCUMENTATION :: END
<ul class="item-children-instance list-unstyled">
% for child in data['results_list']['episode']:
<li>
<a href="${page('info', child['rating_key'])}" id="${child['rating_key']}">
<a href="${page('info', child['rating_key'])}" data-rating_key="${child['rating_key']}" data-library_name="${child['library_name']}">
<div class="item-children-poster">
<div class="item-children-poster-face episode-item" style="background-image: url(${page('pms_image_proxy', child['thumb'], child['rating_key'], 500, 280, fallback='art')});"></div>
% if _session['user_group'] == 'admin':
@@ -191,7 +191,7 @@ DOCUMENTATION :: END
<ul class="item-children-instance list-unstyled">
% for child in data['results_list']['artist']:
<li>
<a href="${page('info', child['rating_key'])}" id="${child['rating_key']}">
<a href="${page('info', child['rating_key'])}" data-rating_key="${child['rating_key']}" data-library_name="${child['library_name']}">
<div class="item-children-poster">
<div class="item-children-poster-face cover-item" style="background-image: url(${page('pms_image_proxy', child['thumb'], child['rating_key'], 300, 300, fallback='cover')});"></div>
% if _session['user_group'] == 'admin':
@@ -215,7 +215,7 @@ DOCUMENTATION :: END
<ul class="item-children-instance list-unstyled">
% for child in data['results_list']['album']:
<li>
<a href="${page('info', child['rating_key'])}" id="${child['rating_key']}">
<a href="${page('info', child['rating_key'])}" data-rating_key="${child['rating_key']}" data-library_name="${child['library_name']}">
<div class="item-children-poster">
<div class="item-children-poster-face cover-item" style="background-image: url(${page('pms_image_proxy', child['thumb'], child['rating_key'], 300, 300, fallback='cover')});"></div>
% if _session['user_group'] == 'admin':
@@ -240,7 +240,7 @@ DOCUMENTATION :: END
<ul class="item-children-instance list-unstyled">
% for child in data['results_list']['track']:
<li>
<a href="${page('info', child['rating_key'])}" id="${child['rating_key']}">
<a href="${page('info', child['rating_key'])}" data-rating_key="${child['rating_key']}" data-library_name="${child['library_name']}">
<div class="item-children-poster">
<div class="item-children-poster-face cover-item" style="background-image: url(${page('pms_image_proxy', child['parent_thumb'], child['parent_rating_key'], 300, 300, fallback='cover')});">
<div class="item-children-card-overlay">

View File

@@ -60,6 +60,8 @@ history_table_options = {
state = '<span class="current-activity-tooltip" data-toggle="tooltip" title="Currently Paused"><i class="fa fa-pause fa-fw"></i></span>';
} else if (rowData['state'] === 'buffering') {
state = '<span class="current-activity-tooltip" data-toggle="tooltip" title="Currently Buffering"><i class="fa fa-spinner fa-fw"></i></span>';
} else if (rowData['state'] === 'error') {
state = '<span class="current-activity-tooltip" data-toggle="tooltip" title="Playback Error"><i class="fa fa-exclamation-triangle fa-fw"></i></span>';
} else if (rowData['state'] === 'stopped') {
state = '<span class="current-activity-tooltip" data-toggle="tooltip" title="Currently Stopped"><i class="fa fa-stop fa-fw"></i></span>';
}
@@ -81,9 +83,9 @@ history_table_options = {
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
if (rowData['user_id']) {
$(td).html('<a href="' + page('user', rowData['user_id']) + '">' + cellData + '</a>');
$(td).html('<a href="' + page('user', rowData['user_id']) + '" title="' + rowData['user'] + '">' + cellData + '</a>');
} else {
$(td).html('<a href="' + page('user', null, rowData['user']) + '">' + cellData + '</a>');
$(td).html('<a href="' + page('user', null, rowData['user']) + '" title="' + rowData['user'] + '">' + cellData + '</a>');
}
} else {
$(td).html(cellData);

View File

@@ -51,9 +51,9 @@ sync_table_options = {
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
if (rowData['user_id']) {
$(td).html('<a href="' + page('user', rowData['user_id']) + '">' + cellData + '</a>');
$(td).html('<a href="' + page('user', rowData['user_id']) + '" title="' + rowData['username'] + '">' + cellData + '</a>');
} else {
$(td).html('<a href="' + page('user', null, rowData['user']) + '">' + cellData + '</a>');
$(td).html('<a href="' + page('user', null, rowData['user']) + '" title="' + rowData['username'] + '">' + cellData + '</a>');
}
} else {
$(td).html(cellData);

View File

@@ -62,9 +62,9 @@ users_list_table_options = {
var inactive = '';
if (!rowData['is_active']) { inactive = '<span class="inactive-user-tooltip" data-toggle="tooltip" title="User not on Plex server"><i class="fa fa-exclamation-triangle"></i></span>'; }
if (cellData === '') {
$(td).html('<a href="' + page('user', rowData['user_id']) + '"><div class="users-poster-face" style="background-image: url(../../images/gravatar-default-80x80.png);">' + inactive + '</div></a>');
$(td).html('<a href="' + page('user', rowData['user_id']) + '"" title="' + rowData['username'] + '"><div class="users-poster-face" style="background-image: url(../../images/gravatar-default-80x80.png);">' + inactive + '</div></a>');
} else {
$(td).html('<a href="' + page('user', rowData['user_id']) + '"><div class="users-poster-face" style="background-image: url(' + rowData['user_thumb'] + ');">' + inactive + '</div></a>');
$(td).html('<a href="' + page('user', rowData['user_id']) + '"" title="' + rowData['username'] + '"><div class="users-poster-face" style="background-image: url(' + rowData['user_thumb'] + ');">' + inactive + '</div></a>');
}
},
"orderable": false,
@@ -78,7 +78,7 @@ users_list_table_options = {
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== null && cellData !== '') {
$(td).html('<div class="edit-user-name" data-id="' + rowData['row_id'] + '">' +
'<a href="' + page('user', rowData['user_id']) + '">' + cellData + '</a>' +
'<a href="' + page('user', rowData['user_id']) + '" title="' + rowData['username'] + '">' + cellData + '</a>' +
'<input type="text" class="hidden" value="' + cellData + '">' +
'</div>');
} else {

View File

@@ -25,11 +25,11 @@ DOCUMENTATION :: END
<div class="user-player-instance">
<li>
% if a['user_id']:
<a href="${page('user', a['user_id'])}" title="${a['friendly_name']}">
<a href="${page('user', a['user_id'])}" title="${a['username']}">
<div class="library-user-instance-box" style="background-image: url(${a['user_thumb']});"></div>
</a>
<div class=" user-player-instance-name">
<a href="${page('user', a['user_id'])}" title="${a['friendly_name']}">${a['friendly_name']}</a>
<a href="${page('user', a['user_id'])}" title="${a['username']}">${a['friendly_name']}</a>
</div>
% else:
<div class="library-user-instance-box" style="background-image: url(${a['user_thumb']});"></div>

View File

@@ -1037,7 +1037,7 @@
</p>
</div>
<div class="form-group">
<label for="notify_recently_added_delay">Notification Delay</label>
<label for="notify_recently_added_delay">Recently Added Notification Delay</label>
<div class="row">
<div class="col-md-2">
<input type="text" class="form-control" data-parsley-type="integer" id="notify_recently_added_delay" name="notify_recently_added_delay" value="${config['notify_recently_added_delay']}" size="5" data-parsley-min="60" data-parsley-trigger="change" data-parsley-errors-container="#notify_recently_added_delay_error" required>
@@ -1070,6 +1070,21 @@
</p>
</div>-->
<div class="padded-header">
<h3>Remote Access Notifications</h3>
</div>
<div class="form-group">
<label for="notify_remote_access_threshold">Remote Access Down Threshold</label>
<div class="row">
<div class="col-md-2">
<input type="text" class="form-control" data-parsley-type="integer" id="notify_remote_access_threshold" name="notify_remote_access_threshold" value="${config['notify_remote_access_threshold']}" size="5" data-parsley-min="60" data-parsley-trigger="change" data-parsley-errors-container="#notify_remote_access_threshold_error" required>
</div>
<div id="notify_remote_access_threshold_error" class="alert alert-danger settings-alert" role="alert"></div>
</div>
<p class="help-block">The duration (in seconds) for Plex remote access to be down before sending a notification. Minimum 60, default 60.</p>
</div>
<div class="padded-header">
<h3>Newsletters</h3>
</div>

View File

@@ -171,6 +171,7 @@ DOCUMENTATION :: END
</p>
<p> with </p>
<p><span id="new_title"></span></p>
<p>from the <strong><span id="new_library"></span></strong> library?</p>
% if query['media_type'] != 'movie':
<p>All items for <strong>${query['grandparent_title']}</strong> will also be updated.</p>
% endif
@@ -211,10 +212,12 @@ DOCUMENTATION :: END
$(document).on('click', '#search-results-list a', function (e) {
e.preventDefault();
var new_rating_key = $(this).attr('id');
var new_rating_key = $(this).data('rating_key');
var new_library_section = $(this).data('library_name');
var new_href = $(this).attr('href');
$('#new_title').html($(this).find('.item-children-instance-text-wrapper').html());
$('#new_library').text(new_library_section);
$('#confirm-modal-update').modal();
$('#confirm-modal-update').one('click', '#confirm-update', function () {

View File

@@ -7,7 +7,7 @@
!define APP_NAME "Tautulli"
!define COMP_NAME "Tautulli"
!define WEB_SITE "https://tautulli.com"
!define COPYRIGHT "Tautulli <EFBFBD> 2020"
!define COPYRIGHT "Tautulli © 2020"
!define DESCRIPTION "Monitor your Plex Media Server"
!define APP_ICON "..\dist\Tautulli\data\interfaces\default\images\logo-circle.ico"
!define LICENSE_TXT "..\dist\Tautulli\LICENSE"
@@ -116,6 +116,8 @@ Var /GLOBAL nolaunch
######################################################################
Section -MainProgram
Call UninstallPrevious
${INSTALL_TYPE}
SetOverwrite ifnewer
SetOutPath "$INSTDIR"
@@ -238,3 +240,17 @@ Function un.onInit
FunctionEnd
######################################################################
Function UninstallPrevious
; Check for uninstaller.
ReadRegStr $R0 "${REG_ROOT}" "${UNINSTALL_PATH}" "UninstallString"
${If} $R0 == ""
Goto Done
${EndIf}
DetailPrint "Removing previous installation."
; Run the uninstaller silently.
ExecWait '"$R0" /S _?=$INSTDIR'
Done:
FunctionEnd
######################################################################

View File

@@ -439,7 +439,7 @@ def initialize_scheduler():
backup_hours = CONFIG.BACKUP_INTERVAL if 1 <= CONFIG.BACKUP_INTERVAL <= 24 else 6
schedule_job(database.optimize, 'Optimize Tautulli database',
schedule_job(database.optimize_db, 'Optimize Tautulli database',
hours=24, minutes=0, seconds=0)
schedule_job(database.make_backup, 'Backup Tautulli database',
hours=backup_hours, minutes=0, seconds=0, args=(True, True))
@@ -687,20 +687,21 @@ def dbcheck():
'CREATE TABLE IF NOT EXISTS notifiers (id INTEGER PRIMARY KEY AUTOINCREMENT, '
'agent_id INTEGER, agent_name TEXT, agent_label TEXT, friendly_name TEXT, notifier_config TEXT, '
'on_play INTEGER DEFAULT 0, on_stop INTEGER DEFAULT 0, on_pause INTEGER DEFAULT 0, '
'on_resume INTEGER DEFAULT 0, on_change INTEGER DEFAULT 0, on_buffer INTEGER DEFAULT 0, on_watched INTEGER DEFAULT 0, '
'on_created INTEGER DEFAULT 0, on_extdown INTEGER DEFAULT 0, on_intdown INTEGER DEFAULT 0, '
'on_resume INTEGER DEFAULT 0, on_change INTEGER DEFAULT 0, on_buffer INTEGER DEFAULT 0, '
'on_error INTEGER DEFAULT 0, on_watched INTEGER DEFAULT 0, on_created INTEGER DEFAULT 0, '
'on_extdown INTEGER DEFAULT 0, on_intdown INTEGER DEFAULT 0, '
'on_extup INTEGER DEFAULT 0, on_intup INTEGER DEFAULT 0, on_pmsupdate INTEGER DEFAULT 0, '
'on_concurrent INTEGER DEFAULT 0, on_newdevice INTEGER DEFAULT 0, on_plexpyupdate INTEGER DEFAULT 0, '
'on_plexpydbcorrupt INTEGER DEFAULT 0, '
'on_play_subject TEXT, on_stop_subject TEXT, on_pause_subject TEXT, '
'on_resume_subject TEXT, on_change_subject TEXT, on_buffer_subject TEXT, on_watched_subject TEXT, '
'on_created_subject TEXT, on_extdown_subject TEXT, on_intdown_subject TEXT, '
'on_resume_subject TEXT, on_change_subject TEXT, on_buffer_subject TEXT, on_error_subject TEXT, '
'on_watched_subject TEXT, on_created_subject TEXT, on_extdown_subject TEXT, on_intdown_subject TEXT, '
'on_extup_subject TEXT, on_intup_subject TEXT, on_pmsupdate_subject TEXT, '
'on_concurrent_subject TEXT, on_newdevice_subject TEXT, on_plexpyupdate_subject TEXT, '
'on_plexpydbcorrupt_subject TEXT, '
'on_play_body TEXT, on_stop_body TEXT, on_pause_body TEXT, '
'on_resume_body TEXT, on_change_body TEXT, on_buffer_body TEXT, on_watched_body TEXT, '
'on_created_body TEXT, on_extdown_body TEXT, on_intdown_body TEXT, '
'on_resume_body TEXT, on_change_body TEXT, on_buffer_body TEXT, on_error_body TEXT, '
'on_watched_body TEXT, on_created_body TEXT, on_extdown_body TEXT, on_intdown_body TEXT, '
'on_extup_body TEXT, on_intup_body TEXT, on_pmsupdate_body TEXT, '
'on_concurrent_body TEXT, on_newdevice_body TEXT, on_plexpyupdate_body TEXT, '
'on_plexpydbcorrupt_body TEXT, '
@@ -1942,6 +1943,19 @@ def dbcheck():
'ALTER TABLE library_sections ADD COLUMN is_active INTEGER DEFAULT 1'
)
# Upgrade library_sections table from earlier versions
try:
result = c_db.execute('SELECT thumb, art FROM library_sections WHERE section_id = ?',
[common.LIVE_TV_SECTION_ID]).fetchone()
if result and (not result[0] or not result[1]):
logger.debug("Altering database. Updating database table library_sections.")
c_db.execute('UPDATE library_sections SET thumb = ?, art =? WHERE section_id = ?',
[common.DEFAULT_LIVE_TV_THUMB,
common.DEFAULT_LIVE_TV_ART_FULL,
common.LIVE_TV_SECTION_ID])
except sqlite3.OperationalError:
pass
# Upgrade users table from earlier versions (remove UNIQUE constraint on username)
try:
result = c_db.execute('SELECT SQL FROM sqlite_master WHERE type="table" AND name="users"').fetchone()
@@ -2065,6 +2079,21 @@ def dbcheck():
'ALTER TABLE notifiers ADD COLUMN on_plexpydbcorrupt_body TEXT'
)
# Upgrade notifiers table from earlier versions
try:
c_db.execute('SELECT on_error FROM notifiers')
except sqlite3.OperationalError:
logger.debug("Altering database. Updating database table notifiers.")
c_db.execute(
'ALTER TABLE notifiers ADD COLUMN on_error INTEGER DEFAULT 0'
)
c_db.execute(
'ALTER TABLE notifiers ADD COLUMN on_error_subject TEXT'
)
c_db.execute(
'ALTER TABLE notifiers ADD COLUMN on_error_body TEXT'
)
# Upgrade tvmaze_lookup table from earlier versions
try:
c_db.execute('SELECT rating_key FROM tvmaze_lookup')

View File

@@ -264,6 +264,19 @@ class ActivityHandler(object):
plexpy.NOTIFY_QUEUE.put({'stream_data': db_session.copy(), 'notify_action': 'on_buffer'})
def on_error(self):
if self.is_valid_session():
logger.debug("Tautulli ActivityHandler :: Session %s encountered an error." % str(self.get_session_key()))
# Update the session state and viewOffset
self.update_db_session()
# Retrieve the session data from our temp table
ap = activity_processor.ActivityProcessor()
db_session = ap.get_session_by_key(session_key=self.get_session_key())
plexpy.NOTIFY_QUEUE.put({'stream_data': db_session.copy(), 'notify_action': 'on_error'})
# This function receives events from our websocket connection
def process(self):
if self.is_valid_session():
@@ -321,6 +334,8 @@ class ActivityHandler(object):
self.on_resume()
elif this_state == 'stopped':
self.on_stop()
elif this_state == 'error':
self.on_error()
elif this_state == 'paused':
# Update the session last_paused timestamp
@@ -520,6 +535,12 @@ class ReachabilityHandler(object):
pref = pms_connect.get_server_pref(pref='PublishServerOnPlexOnlineKey')
return helpers.bool_true(pref)
def on_down(self, server_response):
plexpy.NOTIFY_QUEUE.put({'notify_action': 'on_extdown', 'remote_access_info': server_response})
def on_up(self, server_response):
plexpy.NOTIFY_QUEUE.put({'notify_action': 'on_extup', 'remote_access_info': server_response})
def process(self):
# Check if remote access is enabled
if not self.remote_access_enabled():
@@ -535,20 +556,30 @@ class ReachabilityHandler(object):
if server_response:
# Waiting for port mapping
if server_response['mapping_state'] == 'waiting':
logger.warn("Tautulli Monitor :: Remote access waiting for port mapping.")
logger.warn("Tautulli ReachabilityHandler :: Remote access waiting for port mapping.")
elif plexpy.PLEX_REMOTE_ACCESS_UP is not False and server_response['reason']:
logger.warn("Tautulli Monitor :: Remote access failed: %s" % server_response['reason'])
logger.info("Tautulli Monitor :: Plex remote access is down.")
logger.warn("Tautulli ReachabilityHandler :: Remote access failed: %s" % server_response['reason'])
logger.info("Tautulli ReachabilityHandler :: Plex remote access is down.")
plexpy.PLEX_REMOTE_ACCESS_UP = False
plexpy.NOTIFY_QUEUE.put({'notify_action': 'on_extdown', 'remote_access_info': server_response})
if not ACTIVITY_SCHED.get_job('on_extdown'):
logger.debug("Tautulli ReachabilityHandler :: Schedule remote access down callback in %d seconds.",
plexpy.CONFIG.NOTIFY_REMOTE_ACCESS_THRESHOLD)
schedule_callback('on_extdown', func=self.on_down, args=[server_response],
seconds=plexpy.CONFIG.NOTIFY_REMOTE_ACCESS_THRESHOLD)
elif plexpy.PLEX_REMOTE_ACCESS_UP is False and not server_response['reason']:
logger.info("Tautulli Monitor :: Plex remote access is back up.")
logger.info("Tautulli ReachabilityHandler :: Plex remote access is back up.")
plexpy.PLEX_REMOTE_ACCESS_UP = True
plexpy.NOTIFY_QUEUE.put({'notify_action': 'on_extup', 'remote_access_info': server_response})
if ACTIVITY_SCHED.get_job('on_extdown'):
logger.debug("Tautulli ReachabilityHandler :: Cancelling scheduled remote access down callback.")
schedule_callback('on_extdown', remove_job=True)
else:
self.on_up(server_response)
elif plexpy.PLEX_REMOTE_ACCESS_UP is None:
plexpy.PLEX_REMOTE_ACCESS_UP = self.is_reachable()

View File

@@ -89,6 +89,11 @@ def check_active_sessions(ws_request=False):
plexpy.NOTIFY_QUEUE.put({'stream_data': stream.copy(), 'notify_action': 'on_resume'})
if session['state'] == 'error':
logger.debug("Tautulli Monitor :: Session %s encountered an error." % stream['session_key'])
plexpy.NOTIFY_QUEUE.put({'stream_data': stream.copy(), 'notify_action': 'on_error'})
if stream['state'] == 'paused' and not ws_request:
# The stream is still paused so we need to increment the paused_counter
# Using the set config parameter as the interval, probably not the most accurate but
@@ -209,8 +214,9 @@ def check_active_sessions(ws_request=False):
new_session = monitor_process.write_session(session)
if new_session:
logger.debug("Tautulli Monitor :: Session %s started by user %s with ratingKey %s."
% (session['session_key'], session['user_id'], session['rating_key']))
logger.debug("Tautulli Monitor :: Session %s started by user %s (%s) with ratingKey %s (%s)%s."
% (str(session['session_key']), str(session['user_id']), session['username'],
str(session['rating_key']), session['full_title'], '[Live TV]' if session['live'] else ''))
else:
logger.debug("Tautulli Monitor :: Unable to read session list.")

View File

@@ -48,6 +48,7 @@ DEFAULT_ART = "interfaces/default/images/art.png"
DEFAULT_LIVE_TV_POSTER_THUMB = "interfaces/default/images/poster-live.png"
DEFAULT_LIVE_TV_ART = "interfaces/default/images/art-live.png"
DEFAULT_LIVE_TV_ART_FULL = "interfaces/default/images/art-live-full.png"
DEFAULT_LIVE_TV_THUMB = "interfaces/default/images/libraries/live.png"
ONLINE_POSTER_THUMB = "https://tautulli.com/images/poster.png"
ONLINE_COVER_THUMB = "https://tautulli.com/images/cover.png"
@@ -382,10 +383,11 @@ NOTIFICATION_PARAMETERS = [
{'name': 'Progress Duration', 'type': 'int', 'value': 'progress_duration', 'description': 'The last reported offset (in minutes) of the stream.'},
{'name': 'Progress Time', 'type': 'str', 'value': 'progress_time', 'description': 'The last reported offset (in time format) of the stream.'},
{'name': 'Progress Percent', 'type': 'int', 'value': 'progress_percent', 'description': 'The last reported progress percent of the stream.'},
{'name': 'Transcode Decision', 'type': 'str', 'value': 'transcode_decision', 'description': 'The transcode decisions of the stream.'},
{'name': 'Video Decision', 'type': 'str', 'value': 'video_decision', 'description': 'The video transcode decisions of the stream.'},
{'name': 'Audio Decision', 'type': 'str', 'value': 'audio_decision', 'description': 'The audio transcode decisions of the stream.'},
{'name': 'Subtitle Decision', 'type': 'str', 'value': 'subtitle_decision', 'description': 'The subtitle transcode decisions of the stream.'},
{'name': 'Transcode Decision', 'type': 'str', 'value': 'transcode_decision', 'description': 'The transcode decision of the stream.'},
{'name': 'Container Decision', 'type': 'str', 'value': 'container_decision', 'description': 'The container transcode decision of the stream.'},
{'name': 'Video Decision', 'type': 'str', 'value': 'video_decision', 'description': 'The video transcode decision of the stream.'},
{'name': 'Audio Decision', 'type': 'str', 'value': 'audio_decision', 'description': 'The audio transcode decision of the stream.'},
{'name': 'Subtitle Decision', 'type': 'str', 'value': 'subtitle_decision', 'description': 'The subtitle transcode decision of the stream.'},
{'name': 'Quality Profile', 'type': 'str', 'value': 'quality_profile', 'description': 'The Plex quality profile of the stream.', 'example': 'e.g. Original, 4 Mbps 720p, etc.'},
{'name': 'Optimized Version', 'type': 'int', 'value': 'optimized_version', 'description': 'If the stream is an optimized version.', 'example': '0 or 1'},
{'name': 'Optimized Version Profile', 'type': 'str', 'value': 'optimized_version_profile', 'description': 'The optimized version profile of the stream.'},

View File

@@ -161,6 +161,7 @@ _CONFIG_DEFINITIONS = {
'NOTIFY_RECENTLY_ADDED_DELAY': (int, 'Monitoring', 300),
'NOTIFY_RECENTLY_ADDED_GRANDPARENT': (int, 'Monitoring', 0),
'NOTIFY_RECENTLY_ADDED_UPGRADE': (int, 'Monitoring', 0),
'NOTIFY_REMOTE_ACCESS_THRESHOLD': (int, 'Monitoring', 60),
'NOTIFY_CONCURRENT_BY_IP': (int, 'Monitoring', 0),
'NOTIFY_CONCURRENT_THRESHOLD': (int, 'Monitoring', 2),
'PLEXPY_AUTO_UPDATE': (int, 'General', 0),

View File

@@ -286,7 +286,18 @@ def vacuum():
def optimize():
monitor_db = MonitorDatabase()
logger.info("Tautulli Database :: Optimizing database.")
try:
monitor_db.action('PRAGMA optimize')
except Exception as e:
logger.error("Tautulli Database :: Failed to optimize database: %s" % e)
def optimize_db():
vacuum()
optimize()
def db_filename(filename=FILENAME):

View File

@@ -126,6 +126,8 @@ def add_live_tv_library(refresh=False):
'section_id': common.LIVE_TV_SECTION_ID,
'section_name': common.LIVE_TV_SECTION_NAME,
'section_type': 'live',
'thumb': common.DEFAULT_LIVE_TV_THUMB,
'art': common.DEFAULT_LIVE_TV_ART_FULL,
'is_active': 1
}
@@ -789,7 +791,7 @@ class Libraries(object):
if str(section_id).isdigit():
query = 'SELECT (CASE WHEN users.friendly_name IS NULL OR TRIM(users.friendly_name) = "" ' \
'THEN users.username ELSE users.friendly_name END) AS friendly_name, ' \
'users.user_id, users.thumb, users.custom_avatar_url AS custom_thumb, ' \
'users.user_id, users.username, users.thumb, users.custom_avatar_url AS custom_thumb, ' \
'COUNT(DISTINCT %s) AS user_count ' \
'FROM session_history ' \
'JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \
@@ -815,6 +817,7 @@ class Libraries(object):
row = {'friendly_name': item['friendly_name'],
'user_id': item['user_id'],
'user_thumb': user_thumb,
'username': item['username'],
'total_plays': item['user_count']
}
user_stats.append(row)

View File

@@ -403,9 +403,9 @@ class Newsletter(object):
if self.start_date is None:
if self.config['time_frame_units'] == 'days':
self.start_date = self.end_date.shift(days=-self.config['time_frame']+1).floor('day')
self.start_date = self.end_date.shift(days=-self.config['time_frame'])
else:
self.start_date = self.end_date.shift(hours=-self.config['time_frame']).floor('hour')
self.start_date = self.end_date.shift(hours=-self.config['time_frame'])
self.end_time = self.end_date.timestamp
self.start_time = self.start_date.timestamp

View File

@@ -899,6 +899,7 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
'progress_percent': helpers.get_percent(view_offset, duration),
'initial_stream': notify_params['initial_stream'],
'transcode_decision': transcode_decision,
'container_decision': notify_params['container_decision'],
'video_decision': notify_params['video_decision'],
'audio_decision': notify_params['audio_decision'],
'subtitle_decision': notify_params['subtitle_decision'],

View File

@@ -295,6 +295,14 @@ def available_notification_actions(agent_id=None):
'icon': 'fa-play',
'media_types': ('movie', 'episode', 'track')
},
{'label': 'Playback Error',
'name': 'on_error',
'description': 'Trigger a notification when a stream encounters an error.',
'subject': 'Tautulli ({server_name})',
'body': '{user} ({player}) encountered an error trying to play {title}.',
'icon': 'fa-exclamation-triangle',
'media_types': ('movie', 'episode', 'track')
},
{'label': 'Transcode Decision Change',
'name': 'on_change',
'description': 'Trigger a notification when a stream changes transcode decision.',
@@ -3365,6 +3373,7 @@ class TELEGRAM(Notifier):
_DEFAULT_CONFIG = {'bot_token': '',
'chat_id': '',
'disable_web_preview': 0,
'silent_notification': 0,
'html_support': 1,
'incl_subject': 1,
'incl_poster': 0
@@ -3401,18 +3410,25 @@ class TELEGRAM(Notifier):
data['disable_notification'] = True
else:
data['caption'] = text.encode('utf-8')
if self.config['silent_notification']:
data['disable_notification'] = True
r = self.make_request('https://api.telegram.org/bot{}/sendPhoto'.format(self.config['bot_token']),
self.make_request('https://api.telegram.org/bot{}/sendPhoto'.format(self.config['bot_token']),
data=data, files=files)
if not data.pop('disable_notification', None):
return r
if 'caption' in data:
return
data.pop('disable_notification', None)
data['text'] = (text[:4093] + (text[4093:] and '...')).encode('utf-8')
if self.config['disable_web_preview']:
data['disable_web_page_preview'] = True
if self.config['silent_notification']:
data['disable_notification'] = True
headers = {'Content-type': 'application/x-www-form-urlencoded'}
return self.make_request('https://api.telegram.org/bot{}/sendMessage'.format(self.config['bot_token']),
@@ -3460,6 +3476,12 @@ class TELEGRAM(Notifier):
'name': 'telegram_disable_web_preview',
'description': 'Disables automatic link previews for links in the message',
'input_type': 'checkbox'
},
{'label': 'Enable Silent Notifications',
'value': self.config['silent_notification'],
'name': 'telegram_silent_notification',
'description': 'Send notifications silently without any alert sounds.',
'input_type': 'checkbox'
}
]

View File

@@ -2070,6 +2070,7 @@ class PmsConnect(object):
transcode_decision = 'direct play'
stream_details['transcode_decision'] = transcode_decision
stream_details['container_decision'] = stream_details['stream_container_decision']
# Override * in audio codecs
if stream_details['stream_audio_codec'] == '*':

View File

@@ -118,6 +118,7 @@ class Users(object):
columns = ['users.id AS row_id',
'users.user_id',
'users.username',
'(CASE WHEN users.friendly_name IS NULL OR TRIM(users.friendly_name) = "" \
THEN users.username ELSE users.friendly_name END) AS friendly_name',
'users.thumb AS user_thumb',
@@ -193,6 +194,7 @@ class Users(object):
row = {'row_id': item['row_id'],
'user_id': item['user_id'],
'username': item['username'],
'friendly_name': item['friendly_name'],
'user_thumb': user_thumb,
'plays': item['plays'],

View File

@@ -18,4 +18,4 @@
from __future__ import unicode_literals
PLEXPY_BRANCH = "master"
PLEXPY_RELEASE_VERSION = "v2.5.5"
PLEXPY_RELEASE_VERSION = "v2.5.6"

View File

@@ -628,7 +628,8 @@ class WebInterface(object):
result = None
status_message = 'An error occured.'
return serve_template(templatename="edit_library.html", title="Edit Library", data=result, status_message=status_message)
return serve_template(templatename="edit_library.html", title="Edit Library",
data=result, server_id=plexpy.CONFIG.PMS_IDENTIFIER, status_message=status_message)
@cherrypy.expose
@requireAuth(member_of("admin"))
@@ -981,12 +982,14 @@ class WebInterface(object):
[{"friendly_name": "Jon Snow",
"total_plays": 170,
"user_id": 133788,
"user_thumb": "https://plex.tv/users/k10w42309cynaopq/avatar"
"user_thumb": "https://plex.tv/users/k10w42309cynaopq/avatar",
"username": "LordCommanderSnow"
},
{"platform_type": "DanyKhaleesi69",
{"friendly_name": "DanyKhaleesi69",
"total_plays": 42,
"user_id": 8008135,
"user_thumb": "https://plex.tv/users/568gwwoib5t98a3a/avatar"
"user_thumb": "https://plex.tv/users/568gwwoib5t98a3a/avatar",
"username: "DanyKhaleesi69"
},
{...},
{...}
@@ -1015,10 +1018,10 @@ class WebInterface(object):
```
Required parameters:
server_id (str): The Plex server identifier of the library section
section_id (str): The id of the Plex library section
Optional parameters:
server_id (str): The Plex server identifier of the library section
row_ids (str): Comma separated row ids to delete, e.g. "2,3,8"
Returns:
@@ -1044,10 +1047,10 @@ class WebInterface(object):
```
Required parameters:
server_id (str): The Plex server identifier of the library section
section_id (str): The id of the Plex library section
Optional parameters:
server_id (str): The Plex server identifier of the library section
row_ids (str): Comma separated row ids to delete, e.g. "2,3,8"
Returns:
@@ -1199,6 +1202,7 @@ class WebInterface(object):
"transcode_decision": "transcode",
"user_id": 133788,
"user_thumb": "https://plex.tv/users/568gwwoib5t98a3a/avatar",
"username": "LordCommanderSnow",
"year": 2016
},
{...},
@@ -2545,7 +2549,10 @@ class WebInterface(object):
if user_id == 'null':
user_id = None
plex_tv = plextv.PlexTV()
if get_session_user_id():
user_id = get_session_user_id()
plex_tv = plextv.PlexTV(token=plexpy.CONFIG.PMS_TOKEN)
result = plex_tv.get_synced_items(machine_id=machine_id, user_id_filter=user_id)
if result:
@@ -3025,6 +3032,7 @@ class WebInterface(object):
"notify_group_recently_added_grandparent": checked(plexpy.CONFIG.NOTIFY_GROUP_RECENTLY_ADDED_GRANDPARENT),
"notify_group_recently_added_parent": checked(plexpy.CONFIG.NOTIFY_GROUP_RECENTLY_ADDED_PARENT),
"notify_recently_added_delay": plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_DELAY,
"notify_remote_access_threshold": plexpy.CONFIG.NOTIFY_REMOTE_ACCESS_THRESHOLD,
"notify_concurrent_by_ip": checked(plexpy.CONFIG.NOTIFY_CONCURRENT_BY_IP),
"notify_concurrent_threshold": plexpy.CONFIG.NOTIFY_CONCURRENT_THRESHOLD,
"notify_continued_session_threshold": plexpy.CONFIG.NOTIFY_CONTINUED_SESSION_THRESHOLD,
@@ -4438,6 +4446,10 @@ class WebInterface(object):
None
```
"""
if isinstance(img, str) and img.startswith('interfaces/default/images'):
fp = os.path.join(plexpy.PROG_DIR, 'data', img)
return serve_file(path=fp, content_type='image/png')
if not img and not rating_key:
if fallback in common.DEFAULT_IMAGES:
fbi = common.DEFAULT_IMAGES[fallback]
@@ -5396,6 +5408,7 @@ class WebInterface(object):
"children_count": "",
"collections": [],
"container": "mkv",
"container_decision": "direct play",
"content_rating": "TV-MA",
"deleted_user": 0,
"device": "Windows",
@@ -6074,6 +6087,12 @@ class WebInterface(object):
whois_info = helpers.whois_lookup(ip_address)
return whois_info
@cherrypy.expose
@cherrypy.tools.json_out()
@requireAuth(member_of("admin"))
def get_plexpy_url(self, **kwargs):
return helpers.get_plexpy_url()
@cherrypy.expose
@cherrypy.tools.json_out()
@requireAuth(member_of("admin"))