Compare commits
147 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
baeb744a7c | ||
![]() |
d07add383f | ||
![]() |
a1f18bc133 | ||
![]() |
e00c23bc49 | ||
![]() |
0228a356e4 | ||
![]() |
b0fa0d534e | ||
![]() |
1157fda96c | ||
![]() |
bbf774379d | ||
![]() |
24c8c4319d | ||
![]() |
3b8f9f5892 | ||
![]() |
b47ccd06f9 | ||
![]() |
8c4292f9ac | ||
![]() |
a8fbf8ab1d | ||
![]() |
38e9938666 | ||
![]() |
93c4a0652e | ||
![]() |
2fff6647fd | ||
![]() |
36f0f60c49 | ||
![]() |
d12b57e1de | ||
![]() |
deb16428ed | ||
![]() |
a75aba4aee | ||
![]() |
bb5aa2be3d | ||
![]() |
ef6ef98541 | ||
![]() |
89f581f63e | ||
![]() |
6081fa329b | ||
![]() |
112811f3e2 | ||
![]() |
ede07595c3 | ||
![]() |
08d65623dd | ||
![]() |
7540b5fb34 | ||
![]() |
10c54c2d10 | ||
![]() |
b8d9c8cc47 | ||
![]() |
bac5018b27 | ||
![]() |
c65d9898c8 | ||
![]() |
d7284c40bd | ||
![]() |
1c00f82097 | ||
![]() |
c501923f2b | ||
![]() |
81b22a8c36 | ||
![]() |
beb66396fe | ||
![]() |
aaf3de68cf | ||
![]() |
c827c9e825 | ||
![]() |
5100fdbc96 | ||
![]() |
fae3f38a88 | ||
![]() |
a8236222fb | ||
![]() |
2be4d9b6c9 | ||
![]() |
00934b04d2 | ||
![]() |
1e28b22c70 | ||
![]() |
776061605f | ||
![]() |
683e5663e1 | ||
![]() |
090011c9a5 | ||
![]() |
30b11bce98 | ||
![]() |
2a91ec1560 | ||
![]() |
908ce1ff8d | ||
![]() |
fac47ee68b | ||
![]() |
0f8c122ee3 | ||
![]() |
893c91a15d | ||
![]() |
6d152cf308 | ||
![]() |
af2d0446da | ||
![]() |
244b03ba3e | ||
![]() |
50e0629890 | ||
![]() |
c296b38b78 | ||
![]() |
f7cdfd3f30 | ||
![]() |
44cb2400d0 | ||
![]() |
d297597fa6 | ||
![]() |
168e74aa23 | ||
![]() |
35fa8a749b | ||
![]() |
21a1870884 | ||
![]() |
1d86187f79 | ||
![]() |
558f7873f5 | ||
![]() |
a20a52f5f1 | ||
![]() |
17bb57d5f5 | ||
![]() |
e6aef01508 | ||
![]() |
013d957e47 | ||
![]() |
99e064e040 | ||
![]() |
9e5334ac81 | ||
![]() |
fd43cf5dd4 | ||
![]() |
9aea663754 | ||
![]() |
223e2b2b32 | ||
![]() |
ca91adbd53 | ||
![]() |
4fffbf8a0c | ||
![]() |
c3ea35806e | ||
![]() |
1c4df69e61 | ||
![]() |
707c30b0af | ||
![]() |
4d87666a42 | ||
![]() |
b28ac1543a | ||
![]() |
1983597cf1 | ||
![]() |
69a3b5134f | ||
![]() |
53044c75dd | ||
![]() |
12056ac2ba | ||
![]() |
7c8fb58600 | ||
![]() |
da2e7635bd | ||
![]() |
f8b75eadc6 | ||
![]() |
e9bc767c3b | ||
![]() |
1644bbd4b7 | ||
![]() |
18b328a387 | ||
![]() |
98411f0715 | ||
![]() |
fcb3474312 | ||
![]() |
6362b51902 | ||
![]() |
d79d5d5b39 | ||
![]() |
80df8f6191 | ||
![]() |
fd9cf7017b | ||
![]() |
2eed2d54ca | ||
![]() |
0c33e7492a | ||
![]() |
dd137e5c36 | ||
![]() |
dea9663adf | ||
![]() |
767dd20bdc | ||
![]() |
c350943041 | ||
![]() |
c60340d88b | ||
![]() |
276c0e5c7d | ||
![]() |
054f116017 | ||
![]() |
e9017a8342 | ||
![]() |
9cff20ca16 | ||
![]() |
6cbfacaeae | ||
![]() |
8ebfa20db0 | ||
![]() |
5beb4876fb | ||
![]() |
c723d33d38 | ||
![]() |
f75fca12c8 | ||
![]() |
8671707e4d | ||
![]() |
a9316ebea1 | ||
![]() |
ef86740466 | ||
![]() |
57cb755668 | ||
![]() |
aa75cf2b73 | ||
![]() |
3f8224fec5 | ||
![]() |
0b67abb2a2 | ||
![]() |
872ef2771e | ||
![]() |
3b457304e9 | ||
![]() |
974c672a87 | ||
![]() |
b9f47df930 | ||
![]() |
4c388f60d6 | ||
![]() |
d6b31dc542 | ||
![]() |
539cd60e92 | ||
![]() |
056bcd1488 | ||
![]() |
1c58a47073 | ||
![]() |
32cf1ada8b | ||
![]() |
968132099e | ||
![]() |
7b3874dcaa | ||
![]() |
0302b2412a | ||
![]() |
4ac5329019 | ||
![]() |
37bc68573c | ||
![]() |
dc8996c4d2 | ||
![]() |
1ef9d72534 | ||
![]() |
db7225fbad | ||
![]() |
2c354ad783 | ||
![]() |
b96abc8853 | ||
![]() |
c4dc81e8fb | ||
![]() |
be753983fe | ||
![]() |
1bcb34d7eb | ||
![]() |
2243cd1de9 | ||
![]() |
1ff58a85dc |
63
CHANGELOG.md
63
CHANGELOG.md
@@ -1,5 +1,68 @@
|
||||
# Changelog
|
||||
|
||||
## v1.2.9 (2015-12-06)
|
||||
|
||||
* Fix and improve text sanitization.
|
||||
|
||||
|
||||
## v1.2.8 (2015-12-06)
|
||||
|
||||
* Fix sanitize player names
|
||||
* Fix recently added notification delay
|
||||
* Fix recently added metadata queries
|
||||
* Fix multiple lines in notification body text
|
||||
* Fix UTF-8 encoding in Prowl notifications subject line
|
||||
* Change to only log IPv4 addresses
|
||||
* Add global toggle for recently added notifcations
|
||||
* Add feature to delete users
|
||||
* Add channel support for Telegram notification agent
|
||||
* Add icon for Apple tvOS
|
||||
* Add icon for Microsoft Edge
|
||||
|
||||
|
||||
## v1.2.7 (2015-11-27)
|
||||
|
||||
* Fix IP address option in notifications
|
||||
|
||||
|
||||
## v1.2.6 (2015-11-27)
|
||||
|
||||
* Fixes for IP logging in PMS < 0.9.14.x.
|
||||
* Fix issue in plexWatch importer when trying to import item with no ratingKey.
|
||||
|
||||
|
||||
## v1.2.5 (2015-11-25)
|
||||
|
||||
* Add video_decision and audio_decision to notification options
|
||||
* Fix IP address logging
|
||||
* Fix log spam if notifications disabled
|
||||
|
||||
|
||||
## v1.2.4 (2015-11-24)
|
||||
|
||||
* Add filtering by media type in the history table
|
||||
* Add IFTTT notification agent
|
||||
* Add Telegram notification agent
|
||||
* Add notifications for recently added media
|
||||
* Add notifications for server down and remote access down
|
||||
* Add more metadata to notifications options
|
||||
* Add IP address to notification options (for PMS 0.9.14 and above)
|
||||
* Add server uptime to notification options
|
||||
* Add IP address to current activity
|
||||
* Add IPv6 address logging
|
||||
* Add PMS server name to the page title
|
||||
* Fix bug in "Last Watched" statistic
|
||||
* Fix bug in search query
|
||||
* Fix bug on user pages for usernames with single quotes
|
||||
* Fix name for new Plex Media Center
|
||||
* Fix Pushover notifications with unicode characters
|
||||
* Fix bug with showing old usernames in datatables
|
||||
* Fix bug with "Please verify your server" in settings
|
||||
* Change IP lookup provider
|
||||
* Change notifications custom body text to larger text box
|
||||
* Change movie/tv logging and notifications into individual options
|
||||
|
||||
|
||||
## v1.2.3 (2015-10-18)
|
||||
|
||||
* Added "remaining time" as notification substitution.
|
||||
|
@@ -6,11 +6,7 @@ A python based web application for monitoring, analytics and notifications for P
|
||||
|
||||
This project is based on code from Headphones (https://github.com/rembo10/headphones) and PlexWatchWeb (https://github.com/ecleese/plexWatchWeb).
|
||||
|
||||
* plexPy forum thread: https://forums.plex.tv/discussion/169591/plexpy-another-plex-monitoring-program
|
||||
|
||||
If you'd like to buy me a beer, hit the donate button below. All donations go to the project maintainer (primarily for the procurement of liquid refreshment).
|
||||
|
||||
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=G9HZK9BDJLKT6)
|
||||
* PlexPy forum thread: https://forums.plex.tv/discussion/169591/plexpy-another-plex-monitoring-program
|
||||
|
||||
|
||||
###Support
|
||||
|
@@ -7,7 +7,7 @@ from plexpy import version
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>PlexPy - ${title}</title>
|
||||
<title>PlexPy - ${title} | ${server_name}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
@@ -61,7 +61,7 @@ from plexpy import version
|
||||
<form action="search" method="post" class="form" id="search_form">
|
||||
<div class="input-group">
|
||||
<span class="input-textbox">
|
||||
<input type="text" class="form-control" name="search_query" id="search_query" aria-label="Search" placeholder="Search..."/>
|
||||
<input type="text" class="form-control" name="query" id="query" aria-label="Search" placeholder="Search..."/>
|
||||
</span>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-dark btn-inactive" type="submit" id="search_button"><i class="fa fa-search"></i></button>
|
||||
@@ -131,19 +131,19 @@ ${next.headerIncludes()}
|
||||
</script>
|
||||
<script>
|
||||
$('#search_form').submit(function (e) {
|
||||
if ($('#search_query').hasClass('active') && $('#search_query').val().trim() != '') {
|
||||
if ($('#query').hasClass('active') && $('#query').val().trim() != '') {
|
||||
$.ajax({
|
||||
type: 'post',
|
||||
url: 'search',
|
||||
data: { 'query': $('#search_query').val() }
|
||||
data: { 'query': $('#query').val() }
|
||||
})
|
||||
} else {
|
||||
e.preventDefault();
|
||||
$('#search_button').removeClass('btn-inactive');
|
||||
$('#search_query').clearQueue().val('').animate({ right: '0', width: '250px' }).addClass('active').focus();
|
||||
$('#query').clearQueue().val('').animate({ right: '0', width: '250px' }).addClass('active').focus();
|
||||
}
|
||||
})
|
||||
$('#search_query').on('blur', function (e) {
|
||||
$('#query').on('blur', function (e) {
|
||||
if ($(this).val().trim() == '') {
|
||||
$(this).delay(200).animate({ right: '-250px', width: '0' }, function () {
|
||||
$('#search_button').addClass('btn-inactive');
|
||||
|
@@ -341,6 +341,24 @@ input[type="color"],
|
||||
border-radius: 3px;
|
||||
transition: background-color .3s;
|
||||
}
|
||||
textarea.form-control {
|
||||
height: initial;
|
||||
margin: 5px 0 5px 0;
|
||||
color: #fff;
|
||||
border: 0px solid #444;
|
||||
background: #555;
|
||||
padding: 6px 12px;
|
||||
background-color: #555;
|
||||
border-radius: 3px;
|
||||
transition: background-color .3s;
|
||||
resize: none;
|
||||
}
|
||||
textarea.form-control:focus {
|
||||
outline: 0;
|
||||
color: #555;
|
||||
background-color: #fff;
|
||||
transition: background-color .3s;
|
||||
}
|
||||
.pagination > li > a,
|
||||
.pagination > li > span {
|
||||
position: relative;
|
||||
@@ -688,6 +706,14 @@ a:hover .dashboard-activity-poster {
|
||||
white-space: nowrap;
|
||||
width: 150px;
|
||||
}
|
||||
.dashboard-activity-poster-info-ip-address {
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
left: 10px;
|
||||
text-align: left;
|
||||
font-size: 12px;
|
||||
color: #eee;
|
||||
}
|
||||
.dashboard-activity-poster-info-time {
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
@@ -2348,7 +2374,8 @@ a .home-platforms-instance-list-oval:hover,
|
||||
top: 5px;
|
||||
left: 12px;
|
||||
}
|
||||
#users-to-delete > li {
|
||||
#users-to-delete > li,
|
||||
#users-to-purge > li {
|
||||
color: #e9a049;
|
||||
}
|
||||
#updatebar {
|
||||
@@ -2472,7 +2499,7 @@ table[id^='history_child'] thead th {
|
||||
display: inline-flex;
|
||||
float: right;
|
||||
}
|
||||
#search_form #search_query {
|
||||
#search_form #query {
|
||||
width: 0;
|
||||
height: 34px;
|
||||
margin-top: 0;
|
||||
@@ -2481,7 +2508,7 @@ table[id^='history_child'] thead th {
|
||||
right: -250px;
|
||||
border-radius: 3px 0 0 3px;
|
||||
}
|
||||
#search_form #search_query.active {
|
||||
#search_form #query.active {
|
||||
width: 250px;
|
||||
right: 0px;
|
||||
}
|
||||
|
@@ -32,6 +32,7 @@ user_thumb Returns the profile picture of the user owning the s
|
||||
state Returns the state of the current session. Either 'playing', 'paused' or 'buffering'.
|
||||
title Returns the name of the episode, movie or music track.
|
||||
year Returns the year of the episode, movie, or clip.
|
||||
ip_address Returns the ip address of the stream.
|
||||
player Returns the name of the platform used to play the stream.
|
||||
platform Returns the type of platform used to play the stream.
|
||||
throttled Returns true if the transcode session is throttled.
|
||||
@@ -191,6 +192,13 @@ DOCUMENTATION :: END
|
||||
</div>
|
||||
% if a['media_type'] != 'photo':
|
||||
<div class="dashboard-activity-poster-info-bar">
|
||||
<div class="dashboard-activity-poster-info-ip-address">
|
||||
% if a['ip_address']:
|
||||
<span>IP: ${a['ip_address']}</span>
|
||||
% else:
|
||||
<span>IP: N/A</span>
|
||||
% endif
|
||||
</div>
|
||||
<div class="dashboard-activity-poster-info-time">
|
||||
<span class="progress_time">${a['view_offset']}</span>/<span class="progress_time">${a['duration']}</span>
|
||||
</div>
|
||||
|
@@ -115,7 +115,7 @@ DOCUMENTATION :: END
|
||||
success: function(data) {
|
||||
$("#edit-user-status-message").html(data);
|
||||
if ($.trim(friendly_name) !== '') {
|
||||
$(".set-username").html(friendly_name);
|
||||
$('.set-username').html(document.createTextNode(friendly_name));
|
||||
}
|
||||
$("#user-profile-thumb").attr('src', thumb);
|
||||
}
|
||||
|
@@ -76,19 +76,50 @@
|
||||
<script src="interfaces/default/js/moment-with-locale.js"></script>
|
||||
<script src="interfaces/default/js/tables/history_table.js"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
history_table_options.ajax = {
|
||||
"url": "get_history",
|
||||
type: "post",
|
||||
data: function ( d ) {
|
||||
return { 'json_data': JSON.stringify( d ) };
|
||||
$(document).ready(function () {
|
||||
function loadHistoryTable(media_type) {
|
||||
history_table_options.ajax = {
|
||||
url: 'get_history',
|
||||
type: 'post',
|
||||
data: function (d) {
|
||||
return {
|
||||
'json_data': JSON.stringify(d),
|
||||
'media_type': media_type
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
history_table = $('#history_table').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');
|
||||
history_table = $('#history_table').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', 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"> \
|
||||
<input type="radio" name="media_type-filter" id="history-all" value="all" autocomplete="off"> All \
|
||||
</label> \
|
||||
<label class="btn btn-dark"> \
|
||||
<input type="radio" name="media_type-filter" id="history-movies" value="movie" autocomplete="off"> Movies \
|
||||
</label> \
|
||||
<label class="btn btn-dark"> \
|
||||
<input type="radio" name="media_type-filter" id="history-tv_shows" value="episode" autocomplete="off"> TV Shows \
|
||||
</label> \
|
||||
<label class="btn btn-dark"> \
|
||||
<input type="radio" name="media_type-filter" id="history-music" value="track" autocomplete="off"> Music \
|
||||
</label> \
|
||||
</div>');
|
||||
|
||||
$('#media_type-selection').on('change', function () {
|
||||
$('#media_type-selection > label').removeClass('active');
|
||||
selected_filter = $('input[name=media_type-filter]:checked', '#media_type-selection');
|
||||
$(selected_filter).closest('label').addClass('active');
|
||||
media_type = $(selected_filter).val();
|
||||
history_table.draw();
|
||||
});
|
||||
}
|
||||
|
||||
var media_type = 'all';
|
||||
loadHistoryTable(media_type);
|
||||
|
||||
$('#row-edit-mode').on('click', function() {
|
||||
$('#row-edit-mode-alert').fadeIn(200);
|
||||
|
@@ -39,7 +39,7 @@ user_id Returns the user id for the associated stat.
|
||||
friendly_name Returns the friendly name of the user for the associated stat.
|
||||
|
||||
== Only if 'stat_id' is 'top_platform' or 'last_watched' ==
|
||||
platform_type Returns the platform name for the associated stat.
|
||||
player Returns the player name for the associated stat.
|
||||
|
||||
== Only if 'stat_id' is 'last_watched' ==
|
||||
last_watch Returns the time the media item was last watched.
|
||||
@@ -709,7 +709,7 @@ DOCUMENTATION :: END
|
||||
<script>
|
||||
$('#last-watch-stat').text(moment(${top_stat['rows'][0]['last_watch']},"X").format(date_format));
|
||||
</script>
|
||||
</span> - ${top_stat['rows'][0]['platform_type']}
|
||||
</span> - ${top_stat['rows'][0]['player']}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -755,7 +755,7 @@ DOCUMENTATION :: END
|
||||
<script>
|
||||
$('#home-platforms-instance-list-last-watch-${loop.index + 1}').text(moment(${top_stat['rows'][loop.index]['last_watch']},"X").format(date_format));
|
||||
</script>
|
||||
</span> - ${top_stat['rows'][loop.index]['platform_type']}
|
||||
</span> - ${top_stat['rows'][loop.index]['player']}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
BIN
data/interfaces/default/images/platforms/msedge.png
Normal file
BIN
data/interfaces/default/images/platforms/msedge.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.6 KiB |
BIN
data/interfaces/default/images/platforms/pmp.png
Normal file
BIN
data/interfaces/default/images/platforms/pmp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.0 KiB |
@@ -33,7 +33,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="padded-header" id="library-statistics-header">
|
||||
<h3>Library Statistics</h3>
|
||||
<h3>Library Statistics <small>${config['pms_name']}</small></h3>
|
||||
</div>
|
||||
<div id="library-stats" class="library-platforms">
|
||||
<div class='text-muted'><i class="fa fa-refresh fa-spin"></i> Loading stats...</div>
|
||||
@@ -98,27 +98,6 @@
|
||||
});
|
||||
}
|
||||
|
||||
function getLibraryStatsHeader() {
|
||||
$.ajax({
|
||||
"url": "get_servers_info",
|
||||
type: "post",
|
||||
cache: false,
|
||||
async: true,
|
||||
data: { },
|
||||
complete: function (xhr, status) {
|
||||
server_info = $.parseJSON(xhr.responseText);
|
||||
var server_name = 'Server name not found';
|
||||
for (var i in server_info) {
|
||||
if (server_info[i].machine_identifier == '${config['pms_identifier']}') {
|
||||
server_name = server_info[i].name
|
||||
break;
|
||||
}
|
||||
}
|
||||
$('#library-statistics-header h3').append(' <small>' + server_name + '</small>')
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getLibraryStats() {
|
||||
$.ajax({
|
||||
url: 'library_stats',
|
||||
@@ -170,7 +149,6 @@
|
||||
});
|
||||
|
||||
getHomeStats();
|
||||
getLibraryStatsHeader();
|
||||
getLibraryStats();
|
||||
|
||||
|
||||
|
@@ -11,7 +11,7 @@ data :: Usable parameters (if not applicable for media type, blank value will be
|
||||
|
||||
== Global keys ==
|
||||
rating_key Returns the unique identifier for the media item.
|
||||
type Returns the type of media. Either 'movie', 'show', 'season', 'episode', 'artist', 'album', or 'track'.
|
||||
media_type Returns the type of media. Either 'movie', 'show', 'season', 'episode', 'artist', 'album', or 'track'.
|
||||
art Returns the location of the item's artwork
|
||||
title Returns the name of the movie, show, episode, artist, album, or track.
|
||||
duration Returns the standard runtime of the media.
|
||||
@@ -60,7 +60,7 @@ DOCUMENTATION :: END
|
||||
% if data:
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
% if data['type'] != 'library':
|
||||
% if data['media_type'] != 'library':
|
||||
<div class="art-face" style="background-image:url(pms_image_proxy?img=${data['art']}&width=1920&height=1080)"></div>
|
||||
% endif
|
||||
<div class="summary-container">
|
||||
@@ -68,7 +68,7 @@ DOCUMENTATION :: END
|
||||
<div class="col-md-12">
|
||||
<div class="summary-navbar-list">
|
||||
<ul class="list-unstyled breadcrumb">
|
||||
% if data['type'] == 'library':
|
||||
% if data['media_type'] == 'library':
|
||||
% if data['library'] == 'movie':
|
||||
<li class="active">Movies</li>
|
||||
% elif data['library'] == 'show':
|
||||
@@ -76,29 +76,29 @@ DOCUMENTATION :: END
|
||||
% elif data['library'] == 'artist':
|
||||
<li class="active">Music</li>
|
||||
% endif
|
||||
% elif data['type'] == 'movie':
|
||||
% elif data['media_type'] == 'movie':
|
||||
<li><a href="info?item_id=movie">Movies</a></li>
|
||||
<li class="active">${data['title']}</li>
|
||||
% elif data['type'] == 'show':
|
||||
% elif data['media_type'] == 'show':
|
||||
<li><a href="info?item_id=show">TV Shows</a></li>
|
||||
<li class="active">${data['title']}</li>
|
||||
% elif data['type'] == 'season':
|
||||
% elif data['media_type'] == 'season':
|
||||
<li class="hidden-xs hidden-sm"><a href="info?item_id=show">TV Shows</a></li>
|
||||
<li><a href="info?item_id=${data['parent_rating_key']}">${data['parent_title']}</a></li>
|
||||
<li class="active">Season ${data['index']}</li>
|
||||
% elif data['type'] == 'episode':
|
||||
% elif data['media_type'] == 'episode':
|
||||
<li class="hidden-xs hidden-sm"><a href="info?item_id=show">TV Shows</a></li>
|
||||
<li class="hidden-xs hidden-sm"><a href="info?item_id=${data['grandparent_rating_key']}">${data['grandparent_title']}</a></li>
|
||||
<li><a href="info?item_id=${data['parent_rating_key']}">Season ${data['parent_index']}</a></li>
|
||||
<li class="active">Episode ${data['index']} - ${data['title']}</li>
|
||||
% elif data['type'] == 'artist':
|
||||
% elif data['media_type'] == 'artist':
|
||||
<li><a href="info?item_id=artist">Music</a></li>
|
||||
<li class="active">${data['title']}</li>
|
||||
% elif data['type'] == 'album':
|
||||
% elif data['media_type'] == 'album':
|
||||
<li class="hidden-xs hidden-sm"><a href="info?item_id=artist">Music</a></li>
|
||||
<li><a href="info?item_id=${data['parent_rating_key']}">${data['parent_title']}</a></li>
|
||||
<li class="active">${data['title']}</li>
|
||||
% elif data['type'] == 'track':
|
||||
% elif data['media_type'] == 'track':
|
||||
<li class="hidden-xs hidden-sm"><a href="info?item_id=artist">Music</a></li>
|
||||
<li class="hidden-xs hidden-sm"><a href="info?item_id=${data['grandparent_rating_key']}">${data['grandparent_title']}</a></li>
|
||||
<li><a href="info?item_id=${data['parent_rating_key']}">${data['parent_title']}</a></li>
|
||||
@@ -108,28 +108,28 @@ DOCUMENTATION :: END
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
% if data['type'] != 'library':
|
||||
% if data['media_type'] != 'library':
|
||||
<div class="summary-content-title-wrapper">
|
||||
<div class="col-md-9">
|
||||
<div class="summary-content-poster hidden-xs hidden-sm">
|
||||
% if data['type'] == 'track':
|
||||
% if data['media_type'] == 'track':
|
||||
<a href="http://app.plex.tv/web/app#!/server/${config['pms_identifier']}/details/%2Flibrary%2Fmetadata%2F${data['parent_rating_key']}" target="Plex/Web" title="View in Plex/Web">
|
||||
% elif data['type'] != 'library':
|
||||
% elif data['media_type'] != 'library':
|
||||
<a href="http://app.plex.tv/web/app#!/server/${config['pms_identifier']}/details/%2Flibrary%2Fmetadata%2F${data['rating_key']}" target="Plex/Web" title="View in Plex/Web">
|
||||
% endif
|
||||
% if data['type'] == 'episode':
|
||||
% if data['media_type'] == 'episode':
|
||||
<div class="summary-poster-face-episode" style="background-image: url(pms_image_proxy?img=${data['thumb']}&width=500&height=280&fallback=poster);">
|
||||
<div class="summary-poster-face-overlay">
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
% elif data['type'] == 'artist' or data['type'] == 'album' or data['type'] == 'track':
|
||||
% elif data['media_type'] == 'artist' or data['media_type'] == 'album' or data['media_type'] == 'track':
|
||||
<div class="summary-poster-face-track" style="background-image: url(pms_image_proxy?img=${data['thumb']}&width=500&height=500&fallback=poster);">
|
||||
<div class="summary-poster-face-overlay">
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
% elif data['type'] != 'library':
|
||||
% elif data['media_type'] != 'library':
|
||||
<div class="summary-poster-face" style="background-image: url(pms_image_proxy?img=${data['thumb']}&width=300&height=450&fallback=poster);">
|
||||
<div class="summary-poster-face-overlay">
|
||||
<span></span>
|
||||
@@ -139,19 +139,19 @@ DOCUMENTATION :: END
|
||||
</a>
|
||||
</div>
|
||||
<div class="summary-content-title">
|
||||
% if data['type'] == 'movie' or data['type'] == 'show' or data['type'] == 'artist':
|
||||
% if data['media_type'] == 'movie' or data['media_type'] == 'show' or data['media_type'] == 'artist':
|
||||
<h1> </h1><h1>${data['title']}</h1>
|
||||
% elif data['type'] == 'season':
|
||||
% elif data['media_type'] == 'season':
|
||||
<h1> </h1><h1><a href="info?item_id=${data['parent_rating_key']}">${data['parent_title']}</a></h1>
|
||||
<h3 class="hidden-xs">S${data['index']}</h3>
|
||||
% elif data['type'] == 'episode':
|
||||
% elif data['media_type'] == 'episode':
|
||||
<h1><a href="info?item_id=${data['grandparent_rating_key']}">${data['grandparent_title']}</a></h1>
|
||||
<h2>${data['title']}</h2>
|
||||
<h3 class="hidden-xs">S${data['parent_index']} · E${data['index']}</h3>
|
||||
% elif data['type'] == 'album':
|
||||
% elif data['media_type'] == 'album':
|
||||
<h1><a href="info?item_id=${data['parent_rating_key']}">${data['parent_title']}</a></h1>
|
||||
<h2>${data['title']}</h2>
|
||||
% elif data['type'] == 'track':
|
||||
% elif data['media_type'] == 'track':
|
||||
<h1><a href="info?item_id=${data['grandparent_rating_key']}">${data['grandparent_title']}</a></h1>
|
||||
<h2><a href="info?item_id=${data['parent_rating_key']}">${data['parent_title']}</a> - ${data['title']}</h2>
|
||||
<h3 class="hidden-xs">T${data['index']}</h3>
|
||||
@@ -161,13 +161,13 @@ DOCUMENTATION :: END
|
||||
</div>
|
||||
% endif
|
||||
<div class="summary-content-wrapper">
|
||||
% if data['type'] != 'library':
|
||||
% if data['media_type'] != 'library':
|
||||
<div class="col-md-9">
|
||||
% if data['type'] == 'movie' or data['type'] == 'show' or data['type'] == 'season':
|
||||
% if data['media_type'] == 'movie' or data['media_type'] == 'show' or data['media_type'] == 'season':
|
||||
<div class="summary-content-padding hidden-xs hidden-sm" style="height: 275px;"></div>
|
||||
% elif data['type'] == 'episode':
|
||||
% elif data['media_type'] == 'episode':
|
||||
<div class="summary-content-padding hidden-xs hidden-sm" style="height: 40px;"></div>
|
||||
% elif data['type'] == 'artist' or data['type'] == 'album' or data['type'] == 'track':
|
||||
% elif data['media_type'] == 'artist' or data['media_type'] == 'album' or data['media_type'] == 'track':
|
||||
<div class="summary-content-padding hidden-xs hidden-sm" style="height: 150px;"></div>
|
||||
% else:
|
||||
<div class="summary-content-padding hidden-xs hidden-sm"></div>
|
||||
@@ -189,13 +189,13 @@ DOCUMENTATION :: END
|
||||
% endif
|
||||
</div>
|
||||
<div class="summary-content-details-tag">
|
||||
% if data['type'] == 'movie':
|
||||
% if data['media_type'] == 'movie':
|
||||
Year <strong> ${data['year']}</strong>
|
||||
% elif data['type'] == 'show':
|
||||
% elif data['media_type'] == 'show':
|
||||
Aired <strong> ${data['year']}</strong>
|
||||
% elif data['type'] == 'episode':
|
||||
% elif data['media_type'] == 'episode':
|
||||
Aired <strong> <span id="airdate">${data['originally_available_at']}</span></strong>
|
||||
% elif data['type'] == 'album' or data['type'] == 'track':
|
||||
% elif data['media_type'] == 'album' or data['media_type'] == 'track':
|
||||
Released <strong> ${data['year']}</strong>
|
||||
% endif
|
||||
</div>
|
||||
@@ -268,7 +268,7 @@ DOCUMENTATION :: END
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
% if data['type'] == 'show':
|
||||
% if data['media_type'] == 'show':
|
||||
<div class='col-md-12'>
|
||||
<div class='table-card-header'>
|
||||
<div class="header-bar">
|
||||
@@ -279,7 +279,7 @@ DOCUMENTATION :: END
|
||||
<div id="children-list"><i class="fa fa-refresh fa-spin"></i> Loading season list...</div>
|
||||
</div>
|
||||
</div>
|
||||
% elif data['type'] == 'season':
|
||||
% elif data['media_type'] == 'season':
|
||||
<div class='col-md-12'>
|
||||
<div class='table-card-header'>
|
||||
<div class="header-bar">
|
||||
@@ -290,7 +290,7 @@ DOCUMENTATION :: END
|
||||
<div id="children-list"><i class="fa fa-refresh fa-spin"></i> Loading episode list...</div>
|
||||
</div>
|
||||
</div>
|
||||
% elif data['type'] == 'artist':
|
||||
% elif data['media_type'] == 'artist':
|
||||
<div class='col-md-12'>
|
||||
<div class='table-card-header'>
|
||||
<div class="header-bar">
|
||||
@@ -301,7 +301,7 @@ DOCUMENTATION :: END
|
||||
<div id="children-list"><i class="fa fa-refresh fa-spin"></i> Loading album list...</div>
|
||||
</div>
|
||||
</div>
|
||||
% elif data['type'] == 'album':
|
||||
% elif data['media_type'] == 'album':
|
||||
<div class='col-md-12'>
|
||||
<div class='table-card-header'>
|
||||
<div class="header-bar">
|
||||
@@ -382,49 +382,38 @@ DOCUMENTATION :: END
|
||||
<div class="summary-navbar">
|
||||
<div class="col-md-12">
|
||||
<div class="summary-navbar-list">
|
||||
% if query:
|
||||
% if query['media_type'] == 'movie':
|
||||
<span>Movies</span>
|
||||
<span class="hidden-xs hidden-sm"><i class="fa fa-chevron-right"></i></span>
|
||||
<span>${query['title']}</span>
|
||||
% elif query['media_type'] == 'show':
|
||||
<span>TV Shows</span>
|
||||
<span class="hidden-xs hidden-sm"><i class="fa fa-chevron-right"></i></span>
|
||||
<span>${query['grandparent_title']}</span>
|
||||
% elif query['media_type'] == 'season':
|
||||
<span class="hidden-xs hidden-sm">TV Shows</span>
|
||||
<span class="hidden-xs hidden-sm"><i class="fa fa-chevron-right"></i></span>
|
||||
<span class="hidden-xs hidden-sm">${query['grandparent_title']}</span>
|
||||
<span class="hidden-xs hidden-sm"><i class="fa fa-chevron-right"></i></span>
|
||||
<span>Season ${query['parent_media_index']}</span>
|
||||
% elif query['media_type'] == 'episode':
|
||||
<span class="hidden-xs hidden-sm">TV Shows</span>
|
||||
<span class="hidden-xs hidden-sm"><i class="fa fa-chevron-right"></i></span>
|
||||
<span class="hidden-xs hidden-sm">${query['grandparent_title']}</span>
|
||||
<span class="hidden-xs hidden-sm"><i class="fa fa-chevron-right"></i></span>
|
||||
<span>Season ${query['parent_media_index']}</span>
|
||||
<span><i class="fa fa-chevron-right"></i></span>
|
||||
<span>Episode ${query['media_index']} - ${query['title']}</span>
|
||||
% elif query['media_type'] == 'artist':
|
||||
<span>Music</span>
|
||||
<span><i class="fa fa-chevron-right"></i></span>
|
||||
<span>${query['grandparent_title']}</span>
|
||||
% elif query['media_type'] == 'album':
|
||||
<span class="hidden-xs hidden-sm">Music</span>
|
||||
<span class="hidden-xs hidden-sm"><i class="fa fa-chevron-right"></i></span>
|
||||
<span>${query['grandparent_title']}</span>
|
||||
<span><i class="fa fa-chevron-right"></i></span>
|
||||
<span>${query['parent_title']}</span>
|
||||
% elif query['media_type'] == 'track':
|
||||
<span class="hidden-xs hidden-sm">Music</span>
|
||||
<span class="hidden-xs hidden-sm"><i class="fa fa-chevron-right"></i></span>
|
||||
<span class="hidden-xs hidden-sm">${query['grandparent_title']}</span>
|
||||
<span class="hidden-xs hidden-sm"><i class="fa fa-chevron-right"></i></span>
|
||||
<span>${query['parent_title']}</span>
|
||||
<span><i class="fa fa-chevron-right"></i></span>
|
||||
<span>Track ${query['media_index']} - ${query['title']}</span>
|
||||
% endif
|
||||
% endif
|
||||
<ul class="list-unstyled breadcrumb">
|
||||
% if query:
|
||||
% if query['media_type'] == 'movie':
|
||||
<li><a href="info?item_id=movie">Movies</a></li>
|
||||
<li class="active">${query['title']}</li>
|
||||
% elif query['media_type'] == 'show':
|
||||
<li><a href="info?item_id=show">TV Shows</a></li>
|
||||
<li class="active">${query['grandparent_title']}</li>
|
||||
% elif query['media_type'] == 'season':
|
||||
<li class="hidden-xs hidden-sm"><a href="info?item_id=show">TV Shows</a></li>
|
||||
<li class="hidden-xs hidden-sm">${query['grandparent_title']}</li>
|
||||
<li class="active">Season ${query['parent_media_index']}</li>
|
||||
% elif query['media_type'] == 'episode':
|
||||
<li class="hidden-xs hidden-sm"><a href="info?item_id=show">TV Shows</a></li>
|
||||
<li class="hidden-xs hidden-sm">${query['grandparent_title']}</li>
|
||||
<li>Season ${query['parent_media_index']}</li>
|
||||
<li class="active">Episode ${query['media_index']} - ${query['title']}</li>
|
||||
% elif query['media_type'] == 'artist':
|
||||
<li><a href="info?item_id=artist">Music</a></li>
|
||||
<li class="active">${query['grandparent_title']}</li>
|
||||
% elif query['media_type'] == 'album':
|
||||
<li class="hidden-xs hidden-sm"><a href="info?item_id=artist">Music</a></li>
|
||||
<li>${query['grandparent_title']}</li>
|
||||
<li class="active">${query['parent_title']}</li>
|
||||
% elif query['media_type'] == 'track':
|
||||
<li class="hidden-xs hidden-sm"><a href="info?item_id=artist">Music</a></li>
|
||||
<li class="hidden-xs hidden-sm">${query['grandparent_title']}</li>
|
||||
<li>${query['parent_title']}</li>
|
||||
<li class="active">Track ${query['media_index']} - ${query['title']}</li>
|
||||
% endif
|
||||
% endif
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -510,54 +499,54 @@ DOCUMENTATION :: END
|
||||
|
||||
% if data:
|
||||
<script src="interfaces/default/js/tables/history_table.js"></script>
|
||||
% if data['type'] == 'library':
|
||||
% if data['media_type'] == 'library':
|
||||
<script>
|
||||
function get_history() {
|
||||
history_table_options.ajax = {
|
||||
"url": "get_history",
|
||||
url: 'get_history',
|
||||
type: 'post',
|
||||
data: function ( d ) {
|
||||
return { 'json_data': JSON.stringify( d ),
|
||||
'media_type': '${data['media_type']}' };
|
||||
'media_type': "${data['media_type_filter']}" };
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
% elif data['type'] == 'show' or data['type'] == 'artist':
|
||||
% elif data['media_type'] == 'show' or data['media_type'] == 'artist':
|
||||
<script>
|
||||
function get_history() {
|
||||
history_table_options.ajax = {
|
||||
"url": "get_history",
|
||||
url: 'get_history',
|
||||
type: 'post',
|
||||
data: function ( d ) {
|
||||
return { 'json_data': JSON.stringify( d ),
|
||||
'grandparent_rating_key': ${data['rating_key']} };
|
||||
'grandparent_rating_key': "${data['rating_key']}" };
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
% elif data['type'] == 'season' or data['type'] == 'album':
|
||||
% elif data['media_type'] == 'season' or data['media_type'] == 'album':
|
||||
<script>
|
||||
function get_history() {
|
||||
history_table_options.ajax = {
|
||||
"url": "get_history",
|
||||
url: 'get_history',
|
||||
type: 'post',
|
||||
data: function ( d ) {
|
||||
return { 'json_data': JSON.stringify( d ),
|
||||
'parent_rating_key': ${data['rating_key']} };
|
||||
'parent_rating_key': "${data['rating_key']}" };
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
% elif data['type'] == 'episode' or data['type'] == 'track' or data['type'] == 'movie':
|
||||
% elif data['media_type'] == 'episode' or data['media_type'] == 'track' or data['media_type'] == 'movie':
|
||||
<script>
|
||||
function get_history() {
|
||||
history_table_options.ajax = {
|
||||
"url": "get_history",
|
||||
url: 'get_history',
|
||||
type: 'post',
|
||||
data: function ( d ) {
|
||||
return { 'json_data': JSON.stringify( d ),
|
||||
'rating_key': ${data['rating_key']} };
|
||||
'rating_key': "${data['rating_key']}" };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -610,11 +599,11 @@ DOCUMENTATION :: END
|
||||
});
|
||||
});
|
||||
</script>
|
||||
% if data['type'] == 'show' or data['type'] == 'season' or data['type'] == 'artist' or data['type'] == 'album':
|
||||
% if data['media_type'] == 'show' or data['media_type'] == 'season' or data['media_type'] == 'artist' or data['media_type'] == 'album':
|
||||
<script>
|
||||
$.ajax({
|
||||
url: 'get_item_children',
|
||||
type: "GET",
|
||||
type: 'GET',
|
||||
async: true,
|
||||
data: { rating_key : ${data['rating_key']} },
|
||||
complete: function(xhr, status) {
|
||||
@@ -622,7 +611,16 @@ DOCUMENTATION :: END
|
||||
});
|
||||
</script>
|
||||
% endif
|
||||
% if data['type'] != 'library' and data['rating']:
|
||||
% if data['media_type'] != 'library':
|
||||
<!--
|
||||
<script>
|
||||
$('#row-edit-mode').after('<a href="info?item_id=${data['rating_key']}" class="btn btn-danger btn-edit" id="fix-metadata" \
|
||||
data-toggle="tooltip" data-placement="left" title="Fix metadata if the item was moved in Plex"><i class="fa fa-wrench"></i> Fix Metadata</a>');
|
||||
$('#fix-metadata').tooltip();
|
||||
</script>
|
||||
-->
|
||||
% endif
|
||||
% if data['media_type'] != 'library' and data['rating']:
|
||||
<script>
|
||||
// Convert rating to 5 star rating type
|
||||
var starRating = Math.round(${data['rating']} / 2);
|
||||
@@ -638,9 +636,9 @@ DOCUMENTATION :: END
|
||||
<script>
|
||||
$.ajax({
|
||||
url: 'get_search_results_children',
|
||||
type: "GET",
|
||||
type: 'GET',
|
||||
async: true,
|
||||
data: {'query': "${query['query_string']}",
|
||||
data: {'query': "${query['query_string'].replace('"','\\"')}",
|
||||
'media_type': "${query['media_type']}",
|
||||
'season_index': "${query['parent_media_index']}"
|
||||
},
|
||||
|
@@ -17,8 +17,8 @@
|
||||
<h4><strong>Location Details</strong></h4>
|
||||
<ul class="list-unstyled">
|
||||
<li>Country: <strong><span id="country"></span></strong></li>
|
||||
<li>City: <strong><span id="city"></span></strong></li>
|
||||
<li>Region: <strong><span id="region"></span></strong></li>
|
||||
<li>City: <strong><span id="city"></span></strong></li>
|
||||
<li>Timezone: <strong><span id="timezone"></span></strong></li>
|
||||
<li>Latitude: <strong><span id="lat"></span></strong></li>
|
||||
<li>Longitude: <strong><span id="lon"></span></strong></li>
|
||||
@@ -27,14 +27,12 @@
|
||||
<div class="col-md-6">
|
||||
<h4><strong>Connection Details</strong></h4>
|
||||
<ul class="list-unstyled">
|
||||
<li>ISP: <strong><span id="isp"></span></strong></li>
|
||||
<li>Organization: <strong><span id="org"></span></strong></li>
|
||||
<li>AS: <strong><span id="as"></span></strong></li>
|
||||
<li>Organization: <strong><span id="organization"></span></strong></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<span class="text-muted">Service provided by ip-api.com.</span>
|
||||
<span class="text-muted">Telize service written by <a href="https://github.com/fcambus/telize" target="_blank">Frederic Cambus</a>.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -43,7 +41,7 @@
|
||||
<script>
|
||||
function getUserLocation(ip_address) {
|
||||
$.ajax({
|
||||
url: 'http://ip-api.com/json/' + ip_address,
|
||||
url: 'https://telize.myhtpc.co.za/geoip/' + ip_address,
|
||||
cache: true,
|
||||
async: true,
|
||||
type: 'GET',
|
||||
@@ -55,13 +53,11 @@
|
||||
$('#modal_header_ip_address').html('<i class="fa fa-map-marker"></i> IP Address: ' + ip_address);
|
||||
$('#country').html(data.country);
|
||||
$('#city').html(data.city);
|
||||
$('#region').html(data.regionName);
|
||||
$('#region').html(data.region);
|
||||
$('#timezone').html(data.timezone);
|
||||
$('#lat').html(data.lat);
|
||||
$('#lon').html(data.lon);
|
||||
$('#isp').html(data.isp);
|
||||
$('#org').html(data.org);
|
||||
$('#as').html(data.as);
|
||||
$('#lat').html(data.latitude);
|
||||
$('#lon').html(data.longitude);
|
||||
$('#organization').html(data.organization);
|
||||
},
|
||||
timeout: 5000
|
||||
});
|
||||
|
@@ -176,7 +176,9 @@ function getPlatformImagePath(platformName) {
|
||||
if (platformName.indexOf("Roku") > -1) {
|
||||
return 'interfaces/default/images/platforms/roku.png';
|
||||
} else if (platformName.indexOf("Apple TV") > -1) {
|
||||
return 'interfaces/default/images/platforms/appletv.png';
|
||||
return 'interfaces/default/images/platforms/atv.png';
|
||||
} else if (platformName.indexOf("tvOS") > -1) {
|
||||
return 'interfaces/default/images/platforms/atv.png';
|
||||
} else if (platformName.indexOf("Firefox") > -1) {
|
||||
return 'interfaces/default/images/platforms/firefox.png';
|
||||
} else if (platformName.indexOf("Chromecast") > -1) {
|
||||
@@ -201,6 +203,8 @@ function getPlatformImagePath(platformName) {
|
||||
return 'interfaces/default/images/platforms/safari.png';
|
||||
} else if (platformName.indexOf("Internet Explorer") > -1) {
|
||||
return 'interfaces/default/images/platforms/ie.png';
|
||||
} else if (platformName.indexOf("Microsoft Edge") > -1) {
|
||||
return 'interfaces/default/images/platforms/msedge.png';
|
||||
} else if (platformName.indexOf("Unknown Browser") > -1) {
|
||||
return 'interfaces/default/images/platforms/dafault.png';
|
||||
} else if (platformName.indexOf("Windows-XBMC") > -1) {
|
||||
@@ -223,6 +227,8 @@ function getPlatformImagePath(platformName) {
|
||||
return 'interfaces/default/images/platforms/win8.png';
|
||||
} else if (platformName.indexOf("Windows phone") > -1) {
|
||||
return 'interfaces/default/images/platforms/wp.png';
|
||||
} else if (platformName.indexOf("Plex Media Player") > -1) {
|
||||
return 'interfaces/default/images/platforms/pmp.png';
|
||||
} else {
|
||||
return 'interfaces/default/images/platforms/default.png';
|
||||
}
|
||||
@@ -230,7 +236,9 @@ function getPlatformImagePath(platformName) {
|
||||
|
||||
function isPrivateIP(ip_address) {
|
||||
if (ip_address.indexOf(".") > -1) {
|
||||
var parts = ip_address.split('.');
|
||||
// get IPv4 mapped address (xxx.xxx.xxx.xxx) from IPv6 addresss (::ffff:xxx.xxx.xxx.xxx)
|
||||
var parts = ip_address.split(":");
|
||||
var parts = parts[parts.length - 1].split('.');
|
||||
if (parts[0] === '10' ||
|
||||
(parts[0] === '172' && (parseInt(parts[1], 10) >= 16 && parseInt(parts[1], 10) <= 31)) ||
|
||||
(parts[0] === '192' && parts[1] === '168')) {
|
||||
|
@@ -1,3 +1,4 @@
|
||||
var users_to_delete = [];
|
||||
var users_to_purge = [];
|
||||
|
||||
users_list_table_options = {
|
||||
@@ -22,7 +23,8 @@ users_list_table_options = {
|
||||
"targets": [0],
|
||||
"data": null,
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
$(td).html('<div class="edit-user-toggles"><button class="btn btn-xs btn-warning" data-id="' + rowData['user_id'] + '" data-toggle="button"><i class="fa fa-eraser fa-fw"></i> Purge</button>   ' +
|
||||
$(td).html('<div class="edit-user-toggles"><button class="btn btn-xs btn-warning delete-user" data-id="' + rowData['user_id'] + '" data-toggle="button"><i class="fa fa-trash-o fa-fw"></i> Delete</button> ' +
|
||||
'<button class="btn btn-xs btn-warning purge-user" data-id="' + rowData['user_id'] + '" data-toggle="button"><i class="fa fa-eraser fa-fw"></i> Purge</button>   ' +
|
||||
'<input type="checkbox" id="do_notify-' + rowData['user_id'] + '" name="do_notify" value="1" ' + rowData['do_notify'] + '><label class="edit-tooltip" for="do_notify-' + rowData['user_id'] + '" data-toggle="tooltip" title="Toggle Notifications"><i class="fa fa-bell fa-lg fa-fw"></i></label> ' +
|
||||
'<input type="checkbox" id="keep_history-' + rowData['user_id'] + '" name="keep_history" value="1" ' + rowData['keep_history'] + '><label class="edit-tooltip" for="keep_history-' + rowData['user_id'] + '" data-toggle="tooltip" title="Toggle History"><i class="fa fa-history fa-lg fa-fw"></i></label> ');
|
||||
// Show/hide user currently doesn't work
|
||||
@@ -286,16 +288,44 @@ $('#users_list_table').on('change', 'td.edit-control > .edit-user-toggles > inpu
|
||||
});
|
||||
});
|
||||
|
||||
$('#users_list_table').on('click', 'td.edit-control > .edit-user-toggles > button', function () {
|
||||
$('#users_list_table').on('click', 'td.edit-control > .edit-user-toggles > button.delete-user', function () {
|
||||
var tr = $(this).parents('tr');
|
||||
var row = users_list_table.row(tr);
|
||||
var rowData = row.data();
|
||||
|
||||
var index = $.inArray(rowData['user_id'], users_to_purge);
|
||||
if (index === -1) {
|
||||
var index_delete = $.inArray(rowData['user_id'], users_to_delete);
|
||||
var index_purge = $.inArray(rowData['user_id'], users_to_purge);
|
||||
|
||||
if (index_delete === -1) {
|
||||
users_to_delete.push(rowData['user_id']);
|
||||
if (index_purge === -1) {
|
||||
tr.find('button.purge-user').click();
|
||||
}
|
||||
} else {
|
||||
users_to_delete.splice(index_delete, 1);
|
||||
if (index_purge != -1) {
|
||||
tr.find('button.purge-user').click();
|
||||
}
|
||||
}
|
||||
$(this).toggleClass('btn-warning').toggleClass('btn-danger');
|
||||
|
||||
});
|
||||
|
||||
$('#users_list_table').on('click', 'td.edit-control > .edit-user-toggles > button.purge-user', function () {
|
||||
var tr = $(this).parents('tr');
|
||||
var row = users_list_table.row(tr);
|
||||
var rowData = row.data();
|
||||
|
||||
var index_delete = $.inArray(rowData['user_id'], users_to_delete);
|
||||
var index_purge = $.inArray(rowData['user_id'], users_to_purge);
|
||||
|
||||
if (index_purge === -1) {
|
||||
users_to_purge.push(rowData['user_id']);
|
||||
} else {
|
||||
users_to_purge.splice(index, 1);
|
||||
users_to_purge.splice(index_purge, 1);
|
||||
if (index_delete != -1) {
|
||||
tr.find('button.delete-user').click();
|
||||
}
|
||||
}
|
||||
$(this).toggleClass('btn-warning').toggleClass('btn-danger');
|
||||
});
|
@@ -121,6 +121,12 @@ from plexpy import helpers
|
||||
$('#ajaxMsg').addClass('success').fadeIn().delay(3000).fadeOut();
|
||||
});
|
||||
|
||||
$('#testIFTTT').click(function () {
|
||||
$.get("/test_ifttt",
|
||||
function (data) { $('#ajaxMsg').html("<i class='fa fa-check'></i> " + data); });
|
||||
$('#ajaxMsg').addClass('success').fadeIn().delay(3000).fadeOut();
|
||||
});
|
||||
|
||||
// Never send checkbox values directly, always substitute value in hidden input.
|
||||
$('.checkboxes').click(function() {
|
||||
var configToggle = $(this).data('id');
|
||||
|
@@ -57,6 +57,27 @@ from plexpy import helpers
|
||||
</label>
|
||||
<p class="help-block">Trigger notification when a media item triggers the defined buffer threshold.</p>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" data-size="small" data-id="${data['id']}" data-config-name="${data['config_prefix']}_on_created" ${helpers.checked(data['on_created'])} class="toggle-switches">
|
||||
Notify on recently added
|
||||
</label>
|
||||
<p class="help-block">Trigger notification when a media item is added to the Plex Media Server.</p>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" data-size="small" data-id="${data['id']}" data-config-name="${data['config_prefix']}_on_extdown" ${helpers.checked(data['on_extdown'])} class="toggle-switches">
|
||||
Notify on Plex remote access down
|
||||
</label>
|
||||
<p class="help-block">Trigger notification when the Plex Media Server cannot be reached externally.</p>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" data-size="small" data-id="${data['id']}" data-config-name="${data['config_prefix']}_on_intdown" ${helpers.checked(data['on_intdown'])} class="toggle-switches">
|
||||
Notify on Plex server down
|
||||
</label>
|
||||
<p class="help-block">Trigger notification when the Plex Media Server cannot be reached internally.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -81,6 +102,7 @@ from plexpy import helpers
|
||||
console.log('success');
|
||||
}
|
||||
});
|
||||
$('.toggle-notification-triggers-modal[data-id=' + configToggle + ']').addClass('active');
|
||||
} else {
|
||||
var data = {};
|
||||
data[$(this).data('config-name')] = 0;
|
||||
@@ -92,6 +114,9 @@ from plexpy import helpers
|
||||
console.log('success');
|
||||
}
|
||||
});
|
||||
if (!($('.toggle-switches').is(":checked"))) {
|
||||
$('.toggle-notification-triggers-modal[data-id=' + configToggle + ']').removeClass('active');
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@@ -11,13 +11,13 @@ data[array_index] :: Usable parameters
|
||||
|
||||
== Global keys ==
|
||||
rating_key Returns the unique identifier for the media item.
|
||||
type Returns the type of media. Either 'movie' or 'season'.
|
||||
media_type Returns the media type of media. Either 'movie' or 'season' or 'album'.
|
||||
thumb Returns the location of the item's thumbnail. Use with pms_image_proxy.
|
||||
added_at Returns the time when the media was added to the library.
|
||||
title Returns the name of the movie or season.
|
||||
parent_title Returns the name of the TV Show a season belongs too.
|
||||
|
||||
== Only if 'type' is 'movie' ==
|
||||
== Only if 'media_type' is 'movie' ==
|
||||
year Returns the movie release year.
|
||||
|
||||
DOCUMENTATION :: END
|
||||
@@ -29,7 +29,7 @@ DOCUMENTATION :: END
|
||||
% for item in data:
|
||||
<div class="dashboard-recent-media-instance">
|
||||
<li>
|
||||
% if item['type'] == 'season' or item['type'] == 'movie':
|
||||
% if item['media_type'] == 'season' or item['media_type'] == 'movie':
|
||||
<a href="info?item_id=${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);">
|
||||
@@ -43,16 +43,16 @@ DOCUMENTATION :: END
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard-recent-media-metacontainer">
|
||||
% if item['type'] == 'season':
|
||||
% if item['media_type'] == 'season':
|
||||
<h3 title="${item['parent_title']}">${item['parent_title']}</h3>
|
||||
<h3 class="text-muted">${item['title']}</h3>
|
||||
% elif item['type'] == 'movie':
|
||||
% elif item['media_type'] == 'movie':
|
||||
<h3 title="${item['title']}">${item['title']}</h3>
|
||||
<h3 class="text-muted">${item['year']}</h3>
|
||||
% endif
|
||||
</div>
|
||||
</a>
|
||||
% elif item['type'] == 'album':
|
||||
% elif item['media_type'] == 'album':
|
||||
<a href="info?item_id=${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);">
|
||||
|
@@ -26,13 +26,13 @@
|
||||
<%def name="javascriptIncludes()">
|
||||
<script>
|
||||
$('#search_button').removeClass('btn-inactive');
|
||||
$('#search_query').val('${query}').css({ right: '0', width: '250px' }).addClass('active');
|
||||
$('#query').val("${query.replace('"','\\"')}").css({ right: '0', width: '250px' }).addClass('active');
|
||||
|
||||
$.ajax({
|
||||
url: 'get_search_results_children',
|
||||
type: "GET",
|
||||
async: true,
|
||||
data: {'query': "${query}"},
|
||||
data: {'query': "${query.replace('"','\\"')}"},
|
||||
complete: function (xhr, status) {
|
||||
$("#search-results-list").html(xhr.responseText);
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@
|
||||
import plexpy
|
||||
from plexpy import notifiers, common, versioncheck
|
||||
|
||||
available_notification_agents = notifiers.available_notification_agents()
|
||||
available_notification_agents = sorted(notifiers.available_notification_agents(), key=lambda k: k['name'])
|
||||
%>
|
||||
<%def name="headIncludes()">
|
||||
</%def>
|
||||
@@ -273,9 +273,10 @@ available_notification_agents = notifiers.available_notification_agents()
|
||||
<label for="pms_ip">Plex IP or Hostname</label>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<input type="text" class="pms-settings form-control" id="pms_ip" name="pms_ip" value="${config['pms_ip']}" size="30" data-parsley-trigger="change" aria-describedby="server-verified" required>
|
||||
<input type="text" class="pms-settings form-control" id="pms_ip" name="pms_ip" value="${config['pms_ip']}" size="30" data-parsley-trigger="change" aria-describedby="server-verified" data-parsley-errors-container="#pms_ip_error" required>
|
||||
<span class="form-control-feedback" id="pms-verify" aria-hidden="true" style="display: none;"></span>
|
||||
</div>
|
||||
<div id="pms_ip_error" class="alert alert-danger settings-alert" role="alert"></div>
|
||||
</div>
|
||||
<p class="help-block">IP Address or hostname for Plex Media Server.</p>
|
||||
</div>
|
||||
@@ -312,7 +313,8 @@ available_notification_agents = notifiers.available_notification_agents()
|
||||
<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">
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">Set the complete folder path where your Plex Server logs are, shortcuts are not recognized.<br /><a href="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. </p>
|
||||
<p class="help-block">Set the complete folder path where your Plex Server logs are, shortcuts are not recognized.<br />
|
||||
<a href="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>
|
||||
</div>
|
||||
|
||||
<input type="hidden" id="pms_identifier" name="pms_identifier" value="${config['pms_identifier']}">
|
||||
@@ -403,21 +405,31 @@ available_notification_agents = notifiers.available_notification_agents()
|
||||
</label>
|
||||
<p class="help-block">Instead of polling the server at regular intervals let the server tell us when something happens. This is currently experimental. Encrypted websocket is not currently supported.</p>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" id="monitor_remote_access" name="monitor_remote_access" value="1" ${config['monitor_remote_access']}> Monitor Plex Remote Access
|
||||
</label>
|
||||
<p class="help-block">Enable to have PlexPy check if remote access to the Plex Media Server goes down. Your server needs to have remote access enabled.</p>
|
||||
</div>
|
||||
|
||||
<div class="padded-header">
|
||||
<h3>History Logging</h3>
|
||||
</div>
|
||||
<p class="help-block">Keep records of all movie, TV show, or music items played from your Plex Media Server.</p>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" id="video_logging_enable" name="video_logging_enable" value="1" ${config['video_logging_enable']}> Log Movies and TV
|
||||
<input type="checkbox" id="movie_logging_enable" name="movie_logging_enable" value="1" ${config['movie_logging_enable']}> Enable Movie Logging
|
||||
</label>
|
||||
<p class="help-block">Keep records of all video items played from your Plex Media Server.</p>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" id="music_logging_enable" name="music_logging_enable" value="1" ${config['music_logging_enable']}> Log Music
|
||||
<input type="checkbox" id="tv_logging_enable" name="tv_logging_enable" value="1" ${config['tv_logging_enable']}> Enable TV Show Logging
|
||||
</label>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" id="music_logging_enable" name="music_logging_enable" value="1" ${config['music_logging_enable']}> Enable Music Logging
|
||||
</label>
|
||||
<p class="help-block">Keep records of all audio items played from your Plex Media Server. VERY experimental.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="logging_ignore_interval">Ignore Interval</label>
|
||||
@@ -435,7 +447,7 @@ available_notification_agents = notifiers.available_notification_agents()
|
||||
</label>
|
||||
<span id="debugLogCheck" style="color: #eb8600; padding-left: 10px;"></span>
|
||||
<p class="help-block">
|
||||
Enable this to attempt to log the IP address of the user.
|
||||
Enable this to attempt to log the IP address of the user (for PMS 0.9.12 and below, IP address is automatically logged for PMS 0.9.14 and above).
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -473,7 +485,12 @@ available_notification_agents = notifiers.available_notification_agents()
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="movie_notify_enable" id="movie_notify_enable" value="1" ${config['movie_notify_enable']}> Enable Movie and TV Notifications
|
||||
<input type="checkbox" name="movie_notify_enable" id="movie_notify_enable" value="1" ${config['movie_notify_enable']}> Enable Movie Notifications
|
||||
</label>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="tv_notify_enable" id="tv_notify_enable" value="1" ${config['tv_notify_enable']}> Enable TV Show Notifications
|
||||
</label>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
@@ -481,9 +498,14 @@ available_notification_agents = notifiers.available_notification_agents()
|
||||
<input type="checkbox" name="music_notify_enable" id="music_notify_enable" value="1" ${config['music_notify_enable']}> Enable Music Notifications
|
||||
</label>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="notify_recently_added" id="notify_recently_added" value="1" ${config['notify_recently_added']}> Enable Recently Added Notifications
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="padded-header">
|
||||
<h3>Notification Tuning</h3>
|
||||
<h3>Current Activity Notifications</h3>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
@@ -503,6 +525,27 @@ available_notification_agents = notifiers.available_notification_agents()
|
||||
<p class="help-block">Disable to prevent consecutive notifications (i.e. both watched & stopped notifications).</p>
|
||||
</div>
|
||||
|
||||
<div class="padded-header">
|
||||
<h3>Recently Added Notifications</h3>
|
||||
</div>
|
||||
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="notify_recently_added_grandparent" id="notify_recently_added_grandparent" value="1" ${config['notify_recently_added_grandparent']}> Group notifications for recently added TV Shows or Music
|
||||
</label>
|
||||
<p class="help-block">Enable to only get one TV Show or Artist notification for recently added Episodes or Tracks. Movies are unaffected.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="notify_recently_added_delay">Notification Delay</label>
|
||||
<div class="row">
|
||||
<div class="col-md-2">
|
||||
<input type="text" class="form-control" data-parsley-type="integer" id="notify_recently_added_delay" name="notify_recently_added_delay" value="${config['notify_recently_added_delay']}" size="5" data-parsley-min="60" data-parsley-trigger="change" data-parsley-errors-container="#notify_recently_added_delay_error" required>
|
||||
</div>
|
||||
<div id="notify_recently_added_delay_error" class="alert alert-danger settings-alert" role="alert"></div>
|
||||
</div>
|
||||
<p class="help-block">Set the delay for recently added notifications to allow metadata to be processed. Minimum 60 seconds.</p>
|
||||
</div>
|
||||
|
||||
<div class="padded-header">
|
||||
<h3>Custom Notification Messages</h3>
|
||||
</div>
|
||||
@@ -515,9 +558,9 @@ available_notification_agents = notifiers.available_notification_agents()
|
||||
<a href="#notify-text-tags-modal" data-toggle="modal">here</a> to view usage information.
|
||||
</p>
|
||||
<br/>
|
||||
<ul id="accordion" class="accordion list-unstyled">
|
||||
<ul id="accordion-session" class="accordion list-unstyled">
|
||||
<li>
|
||||
<div class="link"><i class="fa fa-play"></i>Playback Start<i class="fa fa-chevron-down"></i></div>
|
||||
<div class="link"><i class="fa fa-play fa-fw"></i> Playback Start<i class="fa fa-chevron-down"></i></div>
|
||||
<ul class="submenu">
|
||||
<li>
|
||||
<div class="form-group">
|
||||
@@ -527,14 +570,14 @@ available_notification_agents = notifiers.available_notification_agents()
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="notify_on_start_body_text">Message Body</label>
|
||||
<input class="form-control" type="text" id="notify_on_start_body_text" name="notify_on_start_body_text" value="${config['notify_on_start_body_text']}" data-parsley-trigger="change" required>
|
||||
<textarea class="form-control" id="notify_on_start_body_text" name="notify_on_start_body_text" data-parsley-trigger="change" data-autoresize required>${config['notify_on_start_body_text']}</textarea>
|
||||
<p class="help-block">Set a custom body.</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<div class="link"><i class="fa fa-stop"></i>Playback Stop<i class="fa fa-chevron-down"></i></div>
|
||||
<div class="link"><i class="fa fa-stop fa-fw"></i> Playback Stop<i class="fa fa-chevron-down"></i></div>
|
||||
<ul class="submenu">
|
||||
<li>
|
||||
<div class="form-group">
|
||||
@@ -544,14 +587,14 @@ available_notification_agents = notifiers.available_notification_agents()
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="notify_on_stop_body_text">Message Body</label>
|
||||
<input class="form-control" type="text" id="notify_on_stop_body_text" name="notify_on_stop_body_text" value="${config['notify_on_stop_body_text']}" data-parsley-trigger="change" required>
|
||||
<textarea class="form-control" id="notify_on_stop_body_text" name="notify_on_stop_body_text" data-parsley-trigger="change" data-autoresize required>${config['notify_on_stop_body_text']}</textarea>
|
||||
<p class="help-block">Set a custom body.</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<div class="link"><i class="fa fa-pause"></i>Playback Pause<i class="fa fa-chevron-down"></i></div>
|
||||
<div class="link"><i class="fa fa-pause fa-fw"></i> Playback Pause<i class="fa fa-chevron-down"></i></div>
|
||||
<ul class="submenu">
|
||||
<li>
|
||||
<div class="form-group">
|
||||
@@ -561,14 +604,14 @@ available_notification_agents = notifiers.available_notification_agents()
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="notify_on_pause_body_text">Message Body</label>
|
||||
<input class="form-control" type="text" id="notify_on_pause_body_text" name="notify_on_pause_body_text" value="${config['notify_on_pause_body_text']}" data-parsley-trigger="change" required>
|
||||
<textarea class="form-control" id="notify_on_pause_body_text" name="notify_on_pause_body_text" data-parsley-trigger="change" data-autoresize required>${config['notify_on_pause_body_text']}</textarea>
|
||||
<p class="help-block">Set a custom body.</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<div class="link"><i class="fa fa-play"></i>Playback Resume<i class="fa fa-chevron-down"></i></div>
|
||||
<div class="link"><i class="fa fa-play fa-fw"></i> Playback Resume<i class="fa fa-chevron-down"></i></div>
|
||||
<ul class="submenu">
|
||||
<li>
|
||||
<div class="form-group">
|
||||
@@ -578,14 +621,14 @@ available_notification_agents = notifiers.available_notification_agents()
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="notify_on_resume_body_text">Message Body</label>
|
||||
<input class="form-control" type="text" id="notify_on_resume_body_text" name="notify_on_resume_body_text" value="${config['notify_on_resume_body_text']}" data-parsley-trigger="change" required>
|
||||
<textarea class="form-control" id="notify_on_resume_body_text" name="notify_on_resume_body_text" data-parsley-trigger="change" data-autoresize required>${config['notify_on_resume_body_text']}</textarea>
|
||||
<p class="help-block">Set a custom body.</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<div class="link"><i class="fa fa-eye"></i>Watched<i class="fa fa-chevron-down"></i></div>
|
||||
<div class="link"><i class="fa fa-eye fa-fw"></i> Watched<i class="fa fa-chevron-down"></i></div>
|
||||
<ul class="submenu">
|
||||
<li>
|
||||
<div class="form-group">
|
||||
@@ -595,14 +638,14 @@ available_notification_agents = notifiers.available_notification_agents()
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="notify_on_watched_body_text">Message Body</label>
|
||||
<input class="form-control" type="text" id="notify_on_watched_body_text" name="notify_on_watched_body_text" value="${config['notify_on_watched_body_text']}" data-parsley-trigger="change" required>
|
||||
<textarea class="form-control" id="notify_on_watched_body_text" name="notify_on_watched_body_text" data-parsley-trigger="change" data-autoresize required>${config['notify_on_watched_body_text']}</textarea>
|
||||
<p class="help-block">Set a custom body.</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<div class="link"><i class="fa fa-spinner"></i>Buffer Warnings<i class="fa fa-chevron-down"></i></div>
|
||||
<div class="link"><i class="fa fa-spinner fa-fw"></i> Buffer Warnings<i class="fa fa-chevron-down"></i></div>
|
||||
<ul class="submenu">
|
||||
<li>
|
||||
<div class="form-group">
|
||||
@@ -612,7 +655,60 @@ available_notification_agents = notifiers.available_notification_agents()
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="notify_on_buffer_body_text">Message Body</label>
|
||||
<input class="form-control" type="text" id="notify_on_buffer_body_text" name="notify_on_buffer_body_text" value="${config['notify_on_buffer_body_text']}" data-parsley-trigger="change" required>
|
||||
<textarea class="form-control" id="notify_on_buffer_body_text" name="notify_on_buffer_body_text" data-parsley-trigger="change" data-autoresize required>${config['notify_on_buffer_body_text']}</textarea>
|
||||
<p class="help-block">Set a custom body.</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<ul id="accordion-timeline" class="accordion list-unstyled">
|
||||
<li>
|
||||
<div class="link"><i class="fa fa-download fa-fw"></i> Recently Added<i class="fa fa-chevron-down"></i></div>
|
||||
<ul class="submenu">
|
||||
<li>
|
||||
<div class="form-group">
|
||||
<label for="notify_on_created_subject_text">Subject Line</label>
|
||||
<input class="form-control" type="text" id="notify_on_created_subject_text" name="notify_on_created_subject_text" value="${config['notify_on_created_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_created_body_text">Message Body</label>
|
||||
<textarea class="form-control" id="notify_on_created_body_text" name="notify_on_created_body_text" data-parsley-trigger="change" data-autoresize required>${config['notify_on_created_body_text']}</textarea>
|
||||
<p class="help-block">Set a custom body.</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<div class="link"><i class="fa fa-server fa-fw"></i> Plex Remote Access Down<i class="fa fa-chevron-down"></i></div>
|
||||
<ul class="submenu">
|
||||
<li>
|
||||
<div class="form-group">
|
||||
<label for="notify_on_extdown_subject_text">Subject Line</label>
|
||||
<input class="form-control" type="text" id="notify_on_extdown_subject_text" name="notify_on_extdown_subject_text" value="${config['notify_on_extdown_subject_text']}" data-parsley-trigger="change" required>
|
||||
<p class="help-block">Set a custom subject line.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="notify_on_extdown_body_text">Message Body</label>
|
||||
<textarea class="form-control" id="notify_on_extdown_body_text" name="notify_on_extdown_body_text" data-parsley-trigger="change" data-autoresize required>${config['notify_on_extdown_body_text']}</textarea>
|
||||
<p class="help-block">Set a custom body.</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<div class="link"><i class="fa fa-server fa-fw"></i> Plex Server Down<i class="fa fa-chevron-down"></i></div>
|
||||
<ul class="submenu">
|
||||
<li>
|
||||
<div class="form-group">
|
||||
<label for="notify_on_intdown_subject_text">Subject Line</label>
|
||||
<input class="form-control" type="text" id="notify_on_intdown_subject_text" name="notify_on_intdown_subject_text" value="${config['notify_on_intdown_subject_text']}" data-parsley-trigger="change" required>
|
||||
<p class="help-block">Set a custom subject line.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="notify_on_intdown_body_text">Message Body</label>
|
||||
<textarea class="form-control" id="notify_on_intdown_body_text" name="notify_on_intdown_body_text" data-parsley-trigger="change" data-autoresize required>${config['notify_on_intdown_body_text']}</textarea>
|
||||
<p class="help-block">Set a custom body.</p>
|
||||
</div>
|
||||
</li>
|
||||
@@ -635,7 +731,7 @@ available_notification_agents = notifiers.available_notification_agents()
|
||||
% for agent in available_notification_agents:
|
||||
<li>
|
||||
<span>
|
||||
% if agent['on_play'] or agent['on_stop'] or agent['on_pause'] or agent['on_resume'] or agent['on_buffer'] or agent['on_watched']:
|
||||
% if agent['on_play'] or agent['on_stop'] or agent['on_pause'] or agent['on_resume'] or agent['on_buffer'] or agent['on_watched'] or agent['on_created'] or agent['on_extdown'] or agent['on_intdown']:
|
||||
<a href="javascript:void(0)" data-target="#notification-triggers-modal" data-id="${agent['id']}" class="toggle-notification-triggers-modal toggle-left active" data-toggle="modal"><i class="fa fa-lg fa-bell"></i></a>
|
||||
% else:
|
||||
<a href="javascript:void(0)" data-target="#notification-triggers-modal" data-id="${agent['id']}" class="toggle-notification-triggers-modal toggle-left" data-toggle="modal"><i class="fa fa-lg fa-bell"></i></a>
|
||||
@@ -845,6 +941,10 @@ available_notification_agents = notifiers.available_notification_agents()
|
||||
<td width="150"><strong>{server_name}</strong></td>
|
||||
<td>The name of your Plex Server.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{server_uptime}</strong></td>
|
||||
<td>The uptime (in days, hours, mins, secs) of your Plex Server.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{user}</strong></td>
|
||||
<td>The username of the person streaming.</td>
|
||||
@@ -857,13 +957,17 @@ available_notification_agents = notifiers.available_notification_agents()
|
||||
<td width="150"><strong>{player}</strong></td>
|
||||
<td>The name of the device being used for playback.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{ip_address}</strong></td>
|
||||
<td>The IP address of the device being used for playback. (PMS 0.9.14 and above)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{media_type}</strong></td>
|
||||
<td>The type of media being played (movie, episode, track).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{title}</strong></td>
|
||||
<td>The title of the item being played.</td>
|
||||
<td>The full title of the item being played.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{show_name}</strong></td>
|
||||
@@ -881,6 +985,10 @@ available_notification_agents = notifiers.available_notification_agents()
|
||||
<td width="150"><strong>{album_name}</strong></td>
|
||||
<td>The title of the album being played.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{track_name}</strong></td>
|
||||
<td>The title of the track being played.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{season_num}</strong></td>
|
||||
<td>The season number for the media item if item is episode.</td>
|
||||
@@ -897,9 +1005,17 @@ available_notification_agents = notifiers.available_notification_agents()
|
||||
<td width="150"><strong>{episode_num00}</strong></td>
|
||||
<td>The two digit episode number.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{video_decision}</strong></td>
|
||||
<td>The video transcode decisions for the media item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{audio_decision}</strong></td>
|
||||
<td>The audio transcode decisions for the media item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{transcode_decision}</strong></td>
|
||||
<td>The transcode decisions for the media item.</td>
|
||||
<td>The stream transcode decisions for the media item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{year}</strong></td>
|
||||
@@ -913,10 +1029,30 @@ available_notification_agents = notifiers.available_notification_agents()
|
||||
<td width="150"><strong>{content_rating}</strong></td>
|
||||
<td>The content rating for the media item. (e.g. TV-MA, TV-PG, etc.)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{directors}</strong></td>
|
||||
<td>A list of directors for the media item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{writers}</strong></td>
|
||||
<td>A list of writers for the media item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{actors}</strong></td>
|
||||
<td>A list of actors for the media item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{genres}</strong></td>
|
||||
<td>A list of genres for the media item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{summary}</strong></td>
|
||||
<td>A short plot summary for the media item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{tagline}</strong></td>
|
||||
<td>A tagline for the media item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150"><strong>{rating}</strong></td>
|
||||
<td>The rating (out of 10) for the item.</td>
|
||||
@@ -1051,17 +1187,21 @@ $(document).ready(function() {
|
||||
}
|
||||
|
||||
var configForm = $("#configUpdate");
|
||||
function saveSettings() {
|
||||
if (configForm.parsley().validate()) {
|
||||
doAjaxCall('configUpdate', $(this), 'tabs', true);
|
||||
postSaveChecks();
|
||||
return false;
|
||||
} else {
|
||||
showMsg('<i class="fa fa-exclamation-circle"></i> Please verify your settings.', false, true, 2000, true)
|
||||
}
|
||||
}
|
||||
|
||||
$('.save-button').click(function() {
|
||||
if ($("#pms_identifier").val() == "") {
|
||||
showMsg('<i class="fa fa-exclamation-circle"></i> Please verify your server.',false,true,2000,true)
|
||||
verifyServer(function () { saveSettings() });
|
||||
} else {
|
||||
if (configForm.parsley().validate()) {
|
||||
doAjaxCall('configUpdate',$(this),'tabs',true);
|
||||
postSaveChecks();
|
||||
return false;
|
||||
} else {
|
||||
showMsg('<i class="fa fa-exclamation-circle"></i> Please verify your settings.',false,true,2000,true)
|
||||
}
|
||||
saveSettings();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1147,7 +1287,7 @@ $(document).ready(function() {
|
||||
verifyServer();
|
||||
});
|
||||
|
||||
function verifyServer() {
|
||||
function verifyServer(_callback) {
|
||||
var pms_ip = $("#pms_ip").val()
|
||||
var pms_port = $("#pms_port").val()
|
||||
if (($("#pms_ip").val() !== '') || ($("#pms_port").val() !== '')) {
|
||||
@@ -1170,10 +1310,15 @@ $(document).ready(function() {
|
||||
$("#pms-verify").html('<i class="fa fa-check"></i>');
|
||||
$('#pms-verify').fadeIn('fast');
|
||||
$("#pms-ip-group").removeClass("has-error");
|
||||
|
||||
if (_callback) {
|
||||
_callback();
|
||||
}
|
||||
} else {
|
||||
$("#pms-verify").html('<i class="fa fa-close"></i>');
|
||||
$('#pms-verify').fadeIn('fast');
|
||||
$("#pms-ip-group").addClass("has-error");
|
||||
showMsg('<i class="fa fa-exclamation-circle"></i> Could not verify your server.', false, true, 2000, true)
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1181,6 +1326,7 @@ $(document).ready(function() {
|
||||
$("#pms-verify").html('<i class="fa fa-close"></i>');
|
||||
$('#pms-verify').fadeIn('fast');
|
||||
$("#pms-ip-group").addClass("has-error");
|
||||
showMsg('<i class="fa fa-exclamation-circle"></i> Could not verify your server.', false, true, 2000, true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1199,7 +1345,7 @@ $(document).ready(function() {
|
||||
'X-Plex-Version': '${common.VERSION_NUMBER}',
|
||||
'X-Plex-Platform': '${common.PLATFORM}',
|
||||
'X-Plex-Platform-Version': '${common.PLATFORM_VERSION}',
|
||||
'X-Plex-Client-Identifier': '${config['pms_uuid']}',
|
||||
'X-Plex-Client-Identifier': '${config["pms_uuid"]}',
|
||||
'Authorization': 'Basic ' + btoa($("#pms_username").val() + ':' + $("#pms_password").val())
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
@@ -1292,7 +1438,8 @@ $(document).ready(function() {
|
||||
}
|
||||
}
|
||||
|
||||
var accordion = new Accordion($('#accordion'), false);
|
||||
var accordion_session = new Accordion($('#accordion-session'), false);
|
||||
var accordion_timeline = new Accordion($('#accordion-timeline'), false);
|
||||
|
||||
var cards = "${config['home_stats_cards']}".split(/[\s,]+/);
|
||||
cards.forEach(function (item) {
|
||||
@@ -1334,6 +1481,14 @@ $(document).ready(function() {
|
||||
e.preventDefault()
|
||||
});
|
||||
|
||||
// auto resizing textarea for custom notification message body
|
||||
$('textarea[data-autoresize]').each(function() {
|
||||
var offset = this.offsetHeight - this.clientHeight;
|
||||
var resizeTextarea = function(el) {
|
||||
$(el).css('height', 'auto').css('height', el.scrollHeight + offset);
|
||||
};
|
||||
$(this).on('focus keyup input', function() { resizeTextarea(this); }).removeAttr('data-autoresize');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</%def>
|
||||
</%def>
|
@@ -262,33 +262,6 @@ from plexpy import helpers
|
||||
<script src="interfaces/default/js/tables/user_ips.js"></script>
|
||||
<script src="interfaces/default/js/tables/sync_table.js"></script>
|
||||
<script>
|
||||
function recentlyWatched() {
|
||||
var widthVal = $('body').find("#user-recently-watched").width();
|
||||
var tmp = (widthVal-32) / 180;
|
||||
|
||||
if (tmp > 0) {
|
||||
containerSize = parseInt(tmp);
|
||||
} else {
|
||||
containerSize = 1;
|
||||
}
|
||||
|
||||
% if data['user_id']:
|
||||
var user_id = ${data['user_id']};
|
||||
% else:
|
||||
var user_id = null;
|
||||
% endif
|
||||
|
||||
// Populate recently watched
|
||||
$.ajax({
|
||||
url: 'get_user_recently_watched',
|
||||
async: true,
|
||||
data: { user_id: user_id, user: '${data['username']}', limit: containerSize },
|
||||
complete: function(xhr, status) {
|
||||
$("#user-recently-watched").html(xhr.responseText);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
|
||||
% if data['user_id']:
|
||||
@@ -297,13 +270,15 @@ from plexpy import helpers
|
||||
var user_id = null;
|
||||
% endif
|
||||
|
||||
var username = '${data['username'].replace("'", "\\'")}';
|
||||
|
||||
$("#edit-user-tooltip").tooltip();
|
||||
|
||||
// Populate watch time stats
|
||||
$.ajax({
|
||||
url: 'get_user_watch_time_stats',
|
||||
async: true,
|
||||
data: { user_id: user_id, user: '${data['username']}' },
|
||||
data: { user_id: user_id, user: username },
|
||||
complete: function(xhr, status) {
|
||||
$("#user-time-stats").html(xhr.responseText);
|
||||
}
|
||||
@@ -313,21 +288,23 @@ from plexpy import helpers
|
||||
$.ajax({
|
||||
url: 'get_user_player_stats',
|
||||
async: true,
|
||||
data: { user_id: user_id, user: '${data['username']}' },
|
||||
data: { user_id: user_id, user: username },
|
||||
complete: function(xhr, status) {
|
||||
$("#user-player-stats").html(xhr.responseText);
|
||||
}
|
||||
});
|
||||
|
||||
$( "#history-tab-btn" ).one( "click", function() {
|
||||
function loadHistoryTable(media_type) {
|
||||
// Build watch history table
|
||||
history_table_options.ajax = {
|
||||
"url": "get_history",
|
||||
url: 'get_history',
|
||||
type: 'post',
|
||||
data: function ( d ) {
|
||||
return { 'json_data': JSON.stringify( d ),
|
||||
'user_id': user_id,
|
||||
'user': "${data['username']}"
|
||||
return {
|
||||
'json_data': JSON.stringify( d ),
|
||||
'user_id': user_id,
|
||||
'user': username,
|
||||
'media_type': media_type
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -338,6 +315,34 @@ from plexpy import helpers
|
||||
$(colvis.button()).appendTo('#button-bar-history');
|
||||
|
||||
clearSearchButton('history_table', 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"> \
|
||||
<input type="radio" name="media_type-filter" id="history-all" value="all" autocomplete="off"> All \
|
||||
</label> \
|
||||
<label class="btn btn-dark"> \
|
||||
<input type="radio" name="media_type-filter" id="history-movies" value="movie" autocomplete="off"> Movies \
|
||||
</label> \
|
||||
<label class="btn btn-dark"> \
|
||||
<input type="radio" name="media_type-filter" id="history-tv_shows" value="episode" autocomplete="off"> TV Shows \
|
||||
</label> \
|
||||
<label class="btn btn-dark"> \
|
||||
<input type="radio" name="media_type-filter" id="history-music" value="track" autocomplete="off"> Music \
|
||||
</label> \
|
||||
</div>');
|
||||
|
||||
$('#media_type-selection').on('change', function () {
|
||||
$('#media_type-selection > label').removeClass('active');
|
||||
selected_filter = $('input[name=media_type-filter]:checked', '#media_type-selection');
|
||||
$(selected_filter).closest('label').addClass('active');
|
||||
media_type = $(selected_filter).val();
|
||||
history_table.draw();
|
||||
});
|
||||
}
|
||||
|
||||
$( "#history-tab-btn" ).one( "click", function() {
|
||||
var media_type = 'all';
|
||||
loadHistoryTable(media_type);
|
||||
});
|
||||
|
||||
$( "#ip-tab-btn" ).one( "click", function() {
|
||||
@@ -348,7 +353,7 @@ from plexpy import helpers
|
||||
data: function ( d ) {
|
||||
return { 'json_data': JSON.stringify( d ),
|
||||
'user_id': user_id,
|
||||
'user': "${data['username']}"
|
||||
'user': username
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -363,7 +368,7 @@ from plexpy import helpers
|
||||
"url": "get_sync",
|
||||
"data": function(d) {
|
||||
d.user_id = user_id;
|
||||
d.user = "${data['username']}";
|
||||
d.user = username;
|
||||
}
|
||||
}
|
||||
sync_table = $('#sync_table').DataTable(sync_table_options);
|
||||
@@ -380,7 +385,7 @@ from plexpy import helpers
|
||||
$("#edit-user-tooltip").tooltip('hide');
|
||||
$.ajax({
|
||||
url: 'edit_user_dialog',
|
||||
data: { user_id: user_id, user: '${data['username']}' },
|
||||
data: { user_id: user_id, user: username },
|
||||
cache: false,
|
||||
async: true,
|
||||
complete: function(xhr, status) {
|
||||
@@ -425,6 +430,33 @@ from plexpy import helpers
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function recentlyWatched() {
|
||||
var widthVal = $('body').find("#user-recently-watched").width();
|
||||
var tmp = (widthVal-32) / 180;
|
||||
|
||||
if (tmp > 0) {
|
||||
containerSize = parseInt(tmp);
|
||||
} else {
|
||||
containerSize = 1;
|
||||
}
|
||||
|
||||
% if data['user_id']:
|
||||
var user_id = ${data['user_id']};
|
||||
% else:
|
||||
var user_id = null;
|
||||
% endif
|
||||
|
||||
// Populate recently watched
|
||||
$.ajax({
|
||||
url: 'get_user_recently_watched',
|
||||
async: true,
|
||||
data: { user_id: user_id, user: username, limit: containerSize },
|
||||
complete: function(xhr, status) {
|
||||
$("#user-recently-watched").html(xhr.responseText);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
recentlyWatched();
|
||||
$(window).resize(function() {
|
||||
|
@@ -16,7 +16,7 @@
|
||||
<button class="btn btn-danger btn-edit" data-toggle="button" aria-pressed="false" autocomplete="off" id="row-edit-mode">
|
||||
<i class="fa fa-pencil"></i> Edit mode
|
||||
</button> 
|
||||
<div class="alert alert-danger alert-edit" role="alert" id="row-edit-mode-alert"><i class="fa fa-exclamation-triangle"></i> Select users to purge. Data is purged upon exiting edit mode.</div>
|
||||
<div class="alert alert-danger alert-edit" role="alert" id="row-edit-mode-alert"><i class="fa fa-exclamation-triangle"></i> Select users to delete/purge. Data is deleted/purged upon exiting edit mode.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='table-card-back'>
|
||||
@@ -46,16 +46,16 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-remove"></i></button>
|
||||
<h4 class="modal-title" id="myModalLabel">Confirm Purge</h4>
|
||||
<h4 class="modal-title" id="myModalLabel">Confirm Delete/Purge</h4>
|
||||
</div>
|
||||
<div class="modal-body" style="text-align: center;">
|
||||
<p>Are you REALLY sure you want to purge all history for the following users:</p>
|
||||
<ul id="users-to-delete" class="list-unstyled"></ul>
|
||||
<ul id="users-to-purge" class="list-unstyled"></ul>
|
||||
<p>This is permanent and cannot be undone!</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-dark" data-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-danger btn-ok" data-dismiss="modal" id="confirm-purge">Purge</button>
|
||||
<button type="button" class="btn btn-danger btn-ok" data-dismiss="modal" id="confirm-delete">Confirm</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -74,8 +74,8 @@
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
users_list_table_options.ajax = {
|
||||
"url": "get_user_list",
|
||||
type: "post",
|
||||
url: 'get_user_list',
|
||||
type: 'POST',
|
||||
data: function ( d ) {
|
||||
return { 'json_data': JSON.stringify( d ) };
|
||||
}
|
||||
@@ -88,18 +88,46 @@
|
||||
$('#row-edit-mode').on('click', function () {
|
||||
$('#row-edit-mode-alert').fadeIn(200);
|
||||
$('#users-to-delete').html('');
|
||||
$('#users-to-purge').html('');
|
||||
|
||||
if ($(this).hasClass('active')) {
|
||||
if (users_to_purge.length > 0) {
|
||||
if (users_to_delete.length > 0 || users_to_purge.length > 0) {
|
||||
$('.edit-control').each(function () {
|
||||
$(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger');
|
||||
});
|
||||
|
||||
for (var i = 0; i < users_to_purge.length; i++) {
|
||||
$('#users-to-delete').append('<li>' + $('div[data-id=' + users_to_purge[i] + '] > input').val() + '</li>');
|
||||
users_to_purge = $.grep(users_to_purge, function (value) {
|
||||
return $.inArray(value, users_to_delete) < 0;
|
||||
});
|
||||
|
||||
if (users_to_delete.length > 0) {
|
||||
$('#users-to-delete').prepend('<p>Are you REALLY sure you want to delete the following users:</p>')
|
||||
for (var i = 0; i < users_to_delete.length; i++) {
|
||||
$('#users-to-delete').append('<li>' + $('div[data-id=' + users_to_delete[i] + '] > input').val() + '</li>');
|
||||
}
|
||||
}
|
||||
|
||||
if (users_to_purge.length > 0) {
|
||||
$('#users-to-purge').prepend('<p>Are you REALLY sure you want to purge all history for the following users:</p>')
|
||||
for (var i = 0; i < users_to_purge.length; i++) {
|
||||
$('#users-to-purge').append('<li>' + $('div[data-id=' + users_to_purge[i] + '] > input').val() + '</li>');
|
||||
}
|
||||
}
|
||||
|
||||
$('#confirm-modal').modal();
|
||||
$('#confirm-modal').one('click', '#confirm-purge', function () {
|
||||
$('#confirm-modal').one('click', '#confirm-delete', function () {
|
||||
for (var i = 0; i < users_to_delete.length; i++) {
|
||||
$.ajax({
|
||||
url: 'delete_user',
|
||||
data: { user_id: users_to_delete[i] },
|
||||
cache: false,
|
||||
async: true,
|
||||
success: function (data) {
|
||||
var msg = "User deleted";
|
||||
showMsg(msg, false, true, 2000);
|
||||
}
|
||||
});
|
||||
}
|
||||
for (var i = 0; i < users_to_purge.length; i++) {
|
||||
$.ajax({
|
||||
url: 'delete_all_user_history',
|
||||
@@ -129,6 +157,7 @@
|
||||
});
|
||||
|
||||
} else {
|
||||
users_to_delete = [];
|
||||
users_to_purge = [];
|
||||
$('.edit-control').each(function () {
|
||||
$(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger');
|
||||
@@ -147,12 +176,12 @@
|
||||
url: 'refresh_users_list',
|
||||
cache: false,
|
||||
async: true,
|
||||
success : function(data) {
|
||||
success: function(data) {
|
||||
showMsg('<i class="fa fa-check"></i> User list refresh started...',false,true,2000,false)
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
showMsg('<i class="fa fa-exclamation-circle"></i> Unable to refresh user list.',false,true,2000,true)
|
||||
},
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<%
|
||||
<%
|
||||
import plexpy
|
||||
from plexpy import common
|
||||
%>
|
||||
@@ -104,9 +104,15 @@ from plexpy import common
|
||||
|
||||
<div class="wizard-card" data-cardname="card4">
|
||||
<h3>Monitoring</h3>
|
||||
<p class="help-block">Keep records of all movie, TV show, or music items played from your Plex Media Server.</p>
|
||||
<div class="wizard-input-section">
|
||||
<input type="checkbox" id="video_logging_enable" name="video_logging_enable" value="1" ${config['video_logging_enable']}> Log Movies and TV
|
||||
<p class="help-block">Keep records of all video items played from your Plex Media Server.</p>
|
||||
<input type="checkbox" id="movie_logging_enable" name="movie_logging_enable" value="1" ${config['movie_logging_enable']}> Enable Movie Logging
|
||||
</div>
|
||||
<div class="wizard-input-section">
|
||||
<input type="checkbox" id="tv_logging_enable" name="tv_logging_enable" value="1" ${config['tv_logging_enable']}> Enable TV Show Logging
|
||||
</div>
|
||||
<div class="wizard-input-section">
|
||||
<input type="checkbox" id="music_logging_enable" name="music_logging_enable" value="1" ${config['music_logging_enable']}> Enable Music Logging
|
||||
</div>
|
||||
<div class="wizard-input-section">
|
||||
<label for="logging_ignore_interval">Ignore Interval</label>
|
||||
@@ -118,19 +124,16 @@ from plexpy import common
|
||||
</div>
|
||||
<p class="help-block">The interval (in seconds) PlexPy will wait for a video item to be active before logging it. 0 to disable.</p>
|
||||
</div>
|
||||
<!-- Music logging is still very experimental -- leave this for now.
|
||||
<div class="wizard-input-section">
|
||||
<input type="checkbox" id="music_logging_enable" name="music_logging_enable" value="1"> Log Music
|
||||
<p class="help-block">Keep records of all audio items played from your Plex Media Server. VERY experimental.</p>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
<div class="wizard-card" data-cardname="card5" data-validate="validateNotifications">
|
||||
<h3>Notifications</h3>
|
||||
<p class="help-block">PlexPy supports a wide variety of notification options. To set up a notification agent conifgure this in <strong>Settings -> Notification Agents</strong>
|
||||
after you have completed this setup wizard.</p><br/>
|
||||
<div class="wizard-input-section">
|
||||
<input type="checkbox" name="movie_notify_enable" id="movie_notify_enable" value="1" ${config['movie_notify_enable']}> Enable notifications on Movie and TV playback
|
||||
<input type="checkbox" name="movie_notify_enable" id="movie_notify_enable" value="1" ${config['movie_notify_enable']}> Enable notifications on Movie playback
|
||||
</div>
|
||||
<div class="wizard-input-section">
|
||||
<input type="checkbox" name="tv_notify_enable" id="tv_notify_enable" value="1" ${config['tv_notify_enable']}> Enable notifications on TV Show playback
|
||||
</div>
|
||||
<div class="wizard-input-section">
|
||||
<input type="checkbox" name="music_notify_enable" id="music_notify_enable" value="1" ${config['music_notify_enable']}> Enable notifications on Music playback
|
||||
@@ -262,6 +265,7 @@ from plexpy import common
|
||||
if (ci != "undefined") {
|
||||
// To allow next step in the guide.
|
||||
// servers with clientIdentifier is verified
|
||||
$("#pms_identifier").val(ci);
|
||||
$("#pms_valid").val("valid");
|
||||
$("#pms-verify-status").html('<i class="fa fa-check"></i> Server found!').show();
|
||||
} else {
|
||||
|
@@ -31,7 +31,7 @@ except ImportError:
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from apscheduler.triggers.interval import IntervalTrigger
|
||||
|
||||
from plexpy import versioncheck, logger, activity_pinger, plextv
|
||||
from plexpy import versioncheck, logger, activity_pinger, plextv, pmsconnect
|
||||
import plexpy.config
|
||||
|
||||
PROG_DIR = None
|
||||
@@ -169,6 +169,7 @@ def initialize(config_file):
|
||||
# Get the real PMS urls for SSL and remote access
|
||||
if CONFIG.PMS_TOKEN and CONFIG.PMS_IP and CONFIG.PMS_PORT:
|
||||
plextv.get_real_pms_url()
|
||||
pmsconnect.get_server_friendly_name()
|
||||
|
||||
# Refresh the users list on startup
|
||||
if CONFIG.PMS_TOKEN and CONFIG.REFRESH_USERS_ON_STARTUP:
|
||||
@@ -280,7 +281,24 @@ def initialize_scheduler():
|
||||
seconds = 0
|
||||
|
||||
if CONFIG.PMS_IP and CONFIG.PMS_TOKEN:
|
||||
schedule_job(plextv.get_real_pms_url, 'Refresh Plex Server URLs', hours=12, minutes=0, seconds=0)
|
||||
schedule_job(plextv.get_real_pms_url, 'Refresh Plex Server URLs',
|
||||
hours=12, minutes=0, seconds=0)
|
||||
schedule_job(pmsconnect.get_server_friendly_name, 'Refresh Plex Server Name',
|
||||
hours=12, minutes=0, seconds=0)
|
||||
|
||||
if CONFIG.NOTIFY_RECENTLY_ADDED:
|
||||
schedule_job(activity_pinger.check_recently_added, 'Check for recently added items',
|
||||
hours=0, minutes=0, seconds=seconds)
|
||||
else:
|
||||
schedule_job(activity_pinger.check_recently_added, 'Check for recently added items',
|
||||
hours=0, minutes=0, seconds=0)
|
||||
|
||||
if CONFIG.MONITOR_REMOTE_ACCESS:
|
||||
schedule_job(activity_pinger.check_server_response, 'Check for server response',
|
||||
hours=0, minutes=0, seconds=seconds)
|
||||
else:
|
||||
schedule_job(activity_pinger.check_server_response, 'Check for server response',
|
||||
hours=0, minutes=0, seconds=0)
|
||||
|
||||
# If we're not using websockets then fall back to polling
|
||||
if not CONFIG.MONITORING_USE_WEBSOCKET or POLLING_FAILOVER:
|
||||
@@ -398,9 +416,9 @@ def dbcheck():
|
||||
c_db.execute(
|
||||
'CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, '
|
||||
'user_id INTEGER DEFAULT NULL UNIQUE, username TEXT NOT NULL UNIQUE, '
|
||||
'friendly_name TEXT, thumb TEXT, email TEXT, is_home_user INTEGER DEFAULT NULL, '
|
||||
'friendly_name TEXT, thumb TEXT, email TEXT, custom_avatar_url TEXT, is_home_user INTEGER DEFAULT NULL, '
|
||||
'is_allow_sync INTEGER DEFAULT NULL, is_restricted INTEGER DEFAULT NULL, do_notify INTEGER DEFAULT 1, '
|
||||
'keep_history INTEGER DEFAULT 1, custom_avatar_url TEXT)'
|
||||
'keep_history INTEGER DEFAULT 1, deleted_user INTEGER DEFAULT 0)'
|
||||
)
|
||||
|
||||
# Upgrade sessions table from earlier versions
|
||||
@@ -552,7 +570,7 @@ def dbcheck():
|
||||
'CREATE TABLE IF NOT EXISTS notify_log (id INTEGER PRIMARY KEY AUTOINCREMENT, '
|
||||
'session_key INTEGER, rating_key INTEGER, user_id INTEGER, user TEXT, '
|
||||
'agent_id INTEGER, agent_name TEXT, on_play INTEGER, on_stop INTEGER, on_watched INTEGER, '
|
||||
'on_pause INTEGER, on_resume INTEGER, on_buffer INTEGER)'
|
||||
'on_pause INTEGER, on_resume INTEGER, on_buffer INTEGER, on_created INTEGER)'
|
||||
)
|
||||
|
||||
# Upgrade users table from earlier versions
|
||||
@@ -588,6 +606,15 @@ def dbcheck():
|
||||
'ALTER TABLE notify_log ADD COLUMN on_buffer INTEGER'
|
||||
)
|
||||
|
||||
# Upgrade notify_log table from earlier versions
|
||||
try:
|
||||
c_db.execute('SELECT on_created from notify_log')
|
||||
except sqlite3.OperationalError:
|
||||
logger.debug(u"Altering database. Updating database table notify_log.")
|
||||
c_db.execute(
|
||||
'ALTER TABLE notify_log ADD COLUMN on_created INTEGER'
|
||||
)
|
||||
|
||||
# Upgrade sessions table from earlier versions
|
||||
try:
|
||||
c_db.execute('SELECT buffer_count from sessions')
|
||||
@@ -647,6 +674,15 @@ def dbcheck():
|
||||
'WHERE t1.id = session_history.id) '
|
||||
)
|
||||
|
||||
# Upgrade users table from earlier versions
|
||||
try:
|
||||
c_db.execute('SELECT deleted_user from users')
|
||||
except sqlite3.OperationalError:
|
||||
logger.debug(u"Altering database. Updating database table users.")
|
||||
c_db.execute(
|
||||
'ALTER TABLE users ADD COLUMN deleted_user INTEGER DEFAULT 0'
|
||||
)
|
||||
|
||||
conn_db.commit()
|
||||
c_db.close()
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# This file is part of PlexPy.
|
||||
# This file is part of PlexPy.
|
||||
#
|
||||
# PlexPy is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -212,4 +212,54 @@ class ActivityHandler(object):
|
||||
else:
|
||||
# We don't have this session in our table yet, start a new one.
|
||||
if this_state != 'buffering':
|
||||
self.on_start()
|
||||
self.on_start()
|
||||
|
||||
class TimelineHandler(object):
|
||||
|
||||
def __init__(self, timeline):
|
||||
self.timeline = timeline
|
||||
#logger.debug(timeline)
|
||||
|
||||
def is_item(self):
|
||||
if 'itemID' in self.timeline:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def get_rating_key(self):
|
||||
if self.is_item():
|
||||
return int(self.timeline['itemID'])
|
||||
|
||||
return None
|
||||
|
||||
def get_metadata(self):
|
||||
pms_connect = pmsconnect.PmsConnect()
|
||||
metadata_list = pms_connect.get_metadata_details(self.get_rating_key())
|
||||
|
||||
if metadata_list:
|
||||
return metadata_list['metadata']
|
||||
|
||||
return None
|
||||
|
||||
def on_created(self):
|
||||
if self.is_item():
|
||||
logger.debug(u"PlexPy TimelineHandler :: Library item %s has been added to Plex." % str(self.get_rating_key()))
|
||||
|
||||
# Fire off notifications
|
||||
threading.Thread(target=notification_handler.notify_timeline,
|
||||
kwargs=dict(timeline_data=self.get_metadata(), notify_action='created')).start()
|
||||
|
||||
# This function receives events from our websocket connection
|
||||
def process(self):
|
||||
if self.is_item():
|
||||
|
||||
this_state = self.timeline['state']
|
||||
this_type = self.timeline['type']
|
||||
this_metadataState = self.timeline.get('metadataState', None)
|
||||
this_mediaState = self.timeline.get('mediaState', None)
|
||||
|
||||
# state: 5: done processing metadata
|
||||
# type: 1: movie, 2: tv show, 4: episode, 8: artist, 10: track
|
||||
types = [1, 2, 4, 8, 10]
|
||||
if this_state == 5 and this_type in types and this_metadataState == None and this_mediaState == None:
|
||||
self.on_created()
|
@@ -1,4 +1,4 @@
|
||||
# This file is part of PlexPy.
|
||||
# This file is part of PlexPy.
|
||||
#
|
||||
# PlexPy is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -13,13 +13,15 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with PlexPy. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from plexpy import logger, pmsconnect, notification_handler, database, helpers, activity_processor
|
||||
from plexpy import logger, pmsconnect, plextv, notification_handler, database, helpers, activity_processor
|
||||
|
||||
import threading
|
||||
import plexpy
|
||||
import time
|
||||
|
||||
monitor_lock = threading.Lock()
|
||||
ext_ping_count = 0
|
||||
int_ping_count = 0
|
||||
|
||||
|
||||
def check_active_sessions(ws_request=False):
|
||||
@@ -31,7 +33,11 @@ def check_active_sessions(ws_request=False):
|
||||
monitor_process = activity_processor.ActivityProcessor()
|
||||
# logger.debug(u"PlexPy Monitor :: Checking for active streams.")
|
||||
|
||||
global int_ping_count
|
||||
|
||||
if session_list:
|
||||
int_ping_count = 0
|
||||
|
||||
media_container = session_list['sessions']
|
||||
|
||||
# Check our temp table for what we must do with the new streams
|
||||
@@ -162,3 +168,107 @@ def check_active_sessions(ws_request=False):
|
||||
monitor_process.write_session(session)
|
||||
else:
|
||||
logger.debug(u"PlexPy Monitor :: Unable to read session list.")
|
||||
|
||||
int_ping_count += 1
|
||||
logger.warn(u"PlexPy Monitor :: Unable to get an internal response from the server, ping attempt %s." \
|
||||
% str(int_ping_count))
|
||||
|
||||
if int_ping_count == 3:
|
||||
# Fire off notifications
|
||||
threading.Thread(target=notification_handler.notify_timeline,
|
||||
kwargs=dict(notify_action='intdown')).start()
|
||||
|
||||
|
||||
def check_recently_added():
|
||||
|
||||
with monitor_lock:
|
||||
# add delay to allow for metadata processing
|
||||
delay = plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_DELAY
|
||||
time_threshold = int(time.time()) - delay
|
||||
time_interval = plexpy.CONFIG.MONITORING_INTERVAL
|
||||
|
||||
pms_connect = pmsconnect.PmsConnect()
|
||||
recently_added_list = pms_connect.get_recently_added_details(count='10')
|
||||
|
||||
if recently_added_list:
|
||||
recently_added = recently_added_list['recently_added']
|
||||
|
||||
for item in recently_added:
|
||||
metadata = []
|
||||
|
||||
if 0 < time_threshold - int(item['added_at']) <= time_interval:
|
||||
if item['media_type'] == 'movie':
|
||||
metadata_list = pms_connect.get_metadata_details(item['rating_key'])
|
||||
if metadata_list:
|
||||
metadata = [metadata_list['metadata']]
|
||||
else:
|
||||
logger.error(u"PlexPy Monitor :: Unable to retrieve metadata for rating_key %s" \
|
||||
% str(item['rating_key']))
|
||||
|
||||
else:
|
||||
metadata_list = pms_connect.get_metadata_children_details(item['rating_key'])
|
||||
if metadata_list:
|
||||
metadata = metadata_list['metadata']
|
||||
else:
|
||||
logger.error(u"PlexPy Monitor :: Unable to retrieve children metadata for rating_key %s" \
|
||||
% str(item['rating_key']))
|
||||
|
||||
if metadata:
|
||||
if not plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_GRANDPARENT:
|
||||
for item in metadata:
|
||||
if 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()
|
||||
|
||||
else:
|
||||
item = max(metadata, key=lambda x:x['added_at'])
|
||||
|
||||
if 0 < time_threshold - int(item['added_at']) <= time_interval:
|
||||
if item['media_type'] == 'episode' or item['media_type'] == 'track':
|
||||
metadata_list = pms_connect.get_metadata_details(item['grandparent_rating_key'])
|
||||
|
||||
if metadata_list:
|
||||
item = metadata_list['metadata']
|
||||
else:
|
||||
logger.error(u"PlexPy Monitor :: Unable to retrieve grandparent metadata for grandparent_rating_key %s" \
|
||||
% str(item['rating_key']))
|
||||
|
||||
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()
|
||||
|
||||
def check_server_response():
|
||||
|
||||
with monitor_lock:
|
||||
pms_connect = pmsconnect.PmsConnect()
|
||||
server_response = pms_connect.get_server_response()
|
||||
|
||||
global ext_ping_count
|
||||
|
||||
# Check for remote access
|
||||
if server_response:
|
||||
|
||||
mapping_state = server_response['mapping_state']
|
||||
mapping_error = server_response['mapping_error']
|
||||
|
||||
# Check if the port is mapped
|
||||
if not mapping_state == 'mapped':
|
||||
ext_ping_count += 1
|
||||
logger.warn(u"PlexPy Monitor :: Plex remote access port not mapped, ping attempt %s." \
|
||||
% str(ext_ping_count))
|
||||
# Check if the port is open
|
||||
elif mapping_error == 'unreachable':
|
||||
ext_ping_count += 1
|
||||
logger.warn(u"PlexPy Monitor :: Plex remote access port mapped, but mapping failed, ping attempt %s." \
|
||||
% str(ext_ping_count))
|
||||
# Reset external ping counter
|
||||
else:
|
||||
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()
|
@@ -39,6 +39,7 @@ class ActivityProcessor(object):
|
||||
'parent_title': session['parent_title'],
|
||||
'grandparent_title': session['grandparent_title'],
|
||||
'friendly_name': session['friendly_name'],
|
||||
#'ip_address': session['ip_address'],
|
||||
'player': session['player'],
|
||||
'platform': session['platform'],
|
||||
'parent_rating_key': session['parent_rating_key'],
|
||||
@@ -66,6 +67,10 @@ class ActivityProcessor(object):
|
||||
'transcode_height': session['transcode_height']
|
||||
}
|
||||
|
||||
# Add ip_address back into values
|
||||
if session['ip_address']:
|
||||
values.update({'ip_address': session['ip_address']})
|
||||
|
||||
keys = {'session_key': session['session_key'],
|
||||
'rating_key': session['rating_key']}
|
||||
|
||||
@@ -74,24 +79,23 @@ class ActivityProcessor(object):
|
||||
if result == 'insert':
|
||||
# Push any notifications - Push it on it's own thread so we don't hold up our db actions
|
||||
if notify:
|
||||
values.update({'ip_address': session['ip_address']})
|
||||
threading.Thread(target=notification_handler.notify,
|
||||
kwargs=dict(stream_data=values, notify_action='play')).start()
|
||||
|
||||
started = int(time.time())
|
||||
|
||||
# Try and grab IP address from logs
|
||||
if plexpy.CONFIG.IP_LOGGING_ENABLE and plexpy.CONFIG.PMS_LOGS_FOLDER:
|
||||
ip_address = self.find_session_ip(rating_key=session['rating_key'],
|
||||
machine_id=session['machine_id'])
|
||||
else:
|
||||
ip_address = None
|
||||
|
||||
timestamp = {'started': started,
|
||||
'ip_address': ip_address}
|
||||
|
||||
# If it's our first write then time stamp it.
|
||||
started = int(time.time())
|
||||
timestamp = {'started': started}
|
||||
self.db.upsert('sessions', timestamp, keys)
|
||||
|
||||
# Try and grab IP address from logs (fallback if not on PMS 0.9.14 and above)
|
||||
if not session['ip_address']:
|
||||
if plexpy.CONFIG.IP_LOGGING_ENABLE and plexpy.CONFIG.PMS_LOGS_FOLDER:
|
||||
ip_address = self.find_session_ip(rating_key=session['rating_key'],
|
||||
machine_id=session['machine_id'])
|
||||
ip_address = {'ip_address': ip_address}
|
||||
self.db.upsert('sessions', ip_address, keys)
|
||||
|
||||
def write_session_history(self, session=None, import_metadata=None, is_import=False, import_ignore_interval=0):
|
||||
from plexpy import users
|
||||
|
||||
@@ -109,8 +113,11 @@ class ActivityProcessor(object):
|
||||
else:
|
||||
stopped = int(time.time())
|
||||
|
||||
if plexpy.CONFIG.VIDEO_LOGGING_ENABLE and str(session['rating_key']).isdigit() and \
|
||||
(session['media_type'] == 'movie' or session['media_type'] == 'episode'):
|
||||
if plexpy.CONFIG.MOVIE_LOGGING_ENABLE and str(session['rating_key']).isdigit() and \
|
||||
session['media_type'] == 'movie':
|
||||
logging_enabled = True
|
||||
elif plexpy.CONFIG.TV_LOGGING_ENABLE and str(session['rating_key']).isdigit() and \
|
||||
session['media_type'] == 'episode':
|
||||
logging_enabled = True
|
||||
elif plexpy.CONFIG.MUSIC_LOGGING_ENABLE and str(session['rating_key']).isdigit() and \
|
||||
session['media_type'] == 'track':
|
||||
@@ -278,9 +285,16 @@ class ActivityProcessor(object):
|
||||
if ipv4:
|
||||
# The logged IP will always be the first match and we don't want localhost entries
|
||||
if ipv4[0] != '127.0.0.1':
|
||||
# check if IPv4 mapped IPv6 address (::ffff:xxx.xxx.xxx.xxx)
|
||||
#if '::ffff:' + ipv4[0] in line:
|
||||
# logger.debug(u"PlexPy ActivityProcessor :: Matched IP address (%s) for stream ratingKey %s "
|
||||
# u"and machineIdentifier %s."
|
||||
# % ('::ffff:' + ipv4[0], rating_key, machine_id))
|
||||
# return '::ffff:' + ipv4[0]
|
||||
#else:
|
||||
logger.debug(u"PlexPy ActivityProcessor :: Matched IP address (%s) for stream ratingKey %s "
|
||||
u"and machineIdentifier %s."
|
||||
% (ipv4[0], rating_key, machine_id))
|
||||
u"and machineIdentifier %s."
|
||||
% (ipv4[0], rating_key, machine_id))
|
||||
return ipv4[0]
|
||||
|
||||
logger.debug(u"PlexPy ActivityProcessor :: Unable to find IP address on first pass. "
|
||||
@@ -301,8 +315,13 @@ class ActivityProcessor(object):
|
||||
if ipv4:
|
||||
# The logged IP will always be the first match and we don't want localhost entries
|
||||
if ipv4[0] != '127.0.0.1':
|
||||
#if '::ffff:' + ipv4[0] in line:
|
||||
# logger.debug(u"PlexPy ActivityProcessor :: Matched IP address (%s) for stream ratingKey %s." %
|
||||
# ('::ffff:' + ipv4[0], rating_key))
|
||||
# return '::ffff:' + ipv4[0]
|
||||
#else:
|
||||
logger.debug(u"PlexPy ActivityProcessor :: Matched IP address (%s) for stream ratingKey %s." %
|
||||
(ipv4[0], rating_key))
|
||||
(ipv4[0], rating_key))
|
||||
return ipv4[0]
|
||||
|
||||
logger.debug(u"PlexPy ActivityProcessor :: Unable to find IP address on fallback search. Not logging IP address.")
|
||||
|
@@ -394,8 +394,8 @@ class Api(object):
|
||||
"grouping_global_history": bool(plexpy.CONFIG.GROUPING_GLOBAL_HISTORY),
|
||||
"grouping_user_history": bool(plexpy.CONFIG.GROUPING_USER_HISTORY),
|
||||
"grouping_charts": bool(plexpy.CONFIG.GROUPING_CHARTS),
|
||||
"tv_notify_enable": bool(plexpy.CONFIG.TV_NOTIFY_ENABLE),
|
||||
"movie_notify_enable": bool(plexpy.CONFIG.MOVIE_NOTIFY_ENABLE),
|
||||
"tv_notify_enable": bool(plexpy.CONFIG.TV_NOTIFY_ENABLE),
|
||||
"music_notify_enable": bool(plexpy.CONFIG.MUSIC_NOTIFY_ENABLE),
|
||||
"tv_notify_on_start": bool(plexpy.CONFIG.TV_NOTIFY_ON_START),
|
||||
"movie_notify_on_start": bool(plexpy.CONFIG.MOVIE_NOTIFY_ON_START),
|
||||
@@ -410,7 +410,8 @@ class Api(object):
|
||||
"refresh_users_interval": plexpy.CONFIG.REFRESH_USERS_INTERVAL,
|
||||
"refresh_users_on_startup": bool(plexpy.CONFIG.REFRESH_USERS_ON_STARTUP),
|
||||
"ip_logging_enable": bool(plexpy.CONFIG.IP_LOGGING_ENABLE),
|
||||
"video_logging_enable": bool(plexpy.CONFIG.VIDEO_LOGGING_ENABLE),
|
||||
"movie_logging_enable": bool(plexpy.CONFIG.MOVIE_LOGGING_ENABLE),
|
||||
"tv_logging_enable": bool(plexpy.CONFIG.TV_LOGGING_ENABLE),
|
||||
"music_logging_enable": bool(plexpy.CONFIG.MUSIC_LOGGING_ENABLE),
|
||||
"logging_ignore_interval": plexpy.CONFIG.LOGGING_IGNORE_INTERVAL,
|
||||
"pms_is_remote": bool(plexpy.CONFIG.PMS_IS_REMOTE),
|
||||
|
@@ -41,4 +41,9 @@ notify_strings[NOTIFY_STOPPED] = "Playback stopped"
|
||||
|
||||
DEFAULT_USER_THUMB = "interfaces/default/images/gravatar-default-80x80.png"
|
||||
DEFAULT_POSTER_THUMB = "interfaces/default/images/poster.png"
|
||||
DEFAULT_COVER_THUMB = "interfaces/default/images/cover.png"
|
||||
DEFAULT_COVER_THUMB = "interfaces/default/images/cover.png"
|
||||
|
||||
PLATFORM_NAME_OVERRIDES = {'Konvergo': 'Plex Media Player',
|
||||
'Mystery 3': 'Playstation 3',
|
||||
'Mystery 4': 'Playstation 4',
|
||||
'Mystery 5': 'Xbox 360'}
|
||||
|
118
plexpy/config.py
118
plexpy/config.py
@@ -26,6 +26,7 @@ _CONFIG_DEFINITIONS = {
|
||||
'PMS_IP': (str, 'PMS', '127.0.0.1'),
|
||||
'PMS_IS_REMOTE': (int, 'PMS', 0),
|
||||
'PMS_LOGS_FOLDER': (str, 'PMS', ''),
|
||||
'PMS_NAME': (unicode, 'PMS', ''),
|
||||
'PMS_PORT': (int, 'PMS', 32400),
|
||||
'PMS_TOKEN': (str, 'PMS', ''),
|
||||
'PMS_SSL': (int, 'General', 0),
|
||||
@@ -44,6 +45,9 @@ _CONFIG_DEFINITIONS = {
|
||||
'BOXCAR_ON_RESUME': (int, 'Boxcar', 0),
|
||||
'BOXCAR_ON_BUFFER': (int, 'Boxcar', 0),
|
||||
'BOXCAR_ON_WATCHED': (int, 'Boxcar', 0),
|
||||
'BOXCAR_ON_CREATED': (int, 'Boxcar', 0),
|
||||
'BOXCAR_ON_EXTDOWN': (int, 'Boxcar', 0),
|
||||
'BOXCAR_ON_INTDOWN': (int, 'Boxcar', 0),
|
||||
'BUFFER_THRESHOLD': (int, 'Monitoring', 3),
|
||||
'BUFFER_WAIT': (int, 'Monitoring', 900),
|
||||
'CACHE_DIR': (str, 'General', ''),
|
||||
@@ -68,6 +72,9 @@ _CONFIG_DEFINITIONS = {
|
||||
'EMAIL_ON_RESUME': (int, 'Email', 0),
|
||||
'EMAIL_ON_BUFFER': (int, 'Email', 0),
|
||||
'EMAIL_ON_WATCHED': (int, 'Email', 0),
|
||||
'EMAIL_ON_CREATED': (int, 'Email', 0),
|
||||
'EMAIL_ON_EXTDOWN': (int, 'Email', 0),
|
||||
'EMAIL_ON_INTDOWN': (int, 'Email', 0),
|
||||
'ENABLE_HTTPS': (int, 'General', 0),
|
||||
'FIRST_RUN_COMPLETE': (int, 'General', 0),
|
||||
'FREEZE_DB': (int, 'General', 0),
|
||||
@@ -84,6 +91,9 @@ _CONFIG_DEFINITIONS = {
|
||||
'GROWL_ON_RESUME': (int, 'Growl', 0),
|
||||
'GROWL_ON_BUFFER': (int, 'Growl', 0),
|
||||
'GROWL_ON_WATCHED': (int, 'Growl', 0),
|
||||
'GROWL_ON_CREATED': (int, 'Growl', 0),
|
||||
'GROWL_ON_EXTDOWN': (int, 'Growl', 0),
|
||||
'GROWL_ON_INTDOWN': (int, 'Growl', 0),
|
||||
'HOME_LIBRARY_CARDS': (str, 'General', 'library_statistics_first'),
|
||||
'HOME_STATS_LENGTH': (int, 'General', 30),
|
||||
'HOME_STATS_TYPE': (int, 'General', 0),
|
||||
@@ -99,19 +109,33 @@ _CONFIG_DEFINITIONS = {
|
||||
'HTTP_USERNAME': (str, 'General', ''),
|
||||
'INTERFACE': (str, 'General', 'default'),
|
||||
'IP_LOGGING_ENABLE': (int, 'General', 0),
|
||||
'IFTTT_KEY': (str, 'IFTTT', ''),
|
||||
'IFTTT_EVENT': (str, 'IFTTT', 'plexpy'),
|
||||
'IFTTT_ENABLED': (int, 'IFTTT', 0),
|
||||
'IFTTT_ON_PLAY': (int, 'IFTTT', 0),
|
||||
'IFTTT_ON_STOP': (int, 'IFTTT', 0),
|
||||
'IFTTT_ON_PAUSE': (int, 'IFTTT', 0),
|
||||
'IFTTT_ON_RESUME': (int, 'IFTTT', 0),
|
||||
'IFTTT_ON_BUFFER': (int, 'IFTTT', 0),
|
||||
'IFTTT_ON_WATCHED': (int, 'IFTTT', 0),
|
||||
'IFTTT_ON_CREATED': (int, 'IFTTT', 0),
|
||||
'IFTTT_ON_EXTDOWN': (int, 'IFTTT', 0),
|
||||
'IFTTT_ON_INTDOWN': (int, 'IFTTT', 0),
|
||||
'JOURNAL_MODE': (str, 'Advanced', 'wal'),
|
||||
'LAUNCH_BROWSER': (int, 'General', 1),
|
||||
'LOG_DIR': (str, 'General', ''),
|
||||
'LOGGING_IGNORE_INTERVAL': (int, 'Monitoring', 120),
|
||||
'MOVIE_LOGGING_ENABLE': (int, 'Monitoring', 1),
|
||||
'MOVIE_NOTIFY_ENABLE': (int, 'Monitoring', 0),
|
||||
'MOVIE_NOTIFY_ON_START': (int, 'Monitoring', 1),
|
||||
'MOVIE_NOTIFY_ON_STOP': (int, 'Monitoring', 0),
|
||||
'MOVIE_NOTIFY_ON_PAUSE': (int, 'Monitoring', 0),
|
||||
'MUSIC_LOGGING_ENABLE': (int, 'Monitoring', 1),
|
||||
'MUSIC_NOTIFY_ENABLE': (int, 'Monitoring', 0),
|
||||
'MUSIC_NOTIFY_ON_START': (int, 'Monitoring', 1),
|
||||
'MUSIC_NOTIFY_ON_STOP': (int, 'Monitoring', 0),
|
||||
'MUSIC_NOTIFY_ON_PAUSE': (int, 'Monitoring', 0),
|
||||
'MUSIC_LOGGING_ENABLE': (int, 'Monitoring', 0),
|
||||
'MONITOR_REMOTE_ACCESS': (int, 'Monitoring', 0),
|
||||
'MONITORING_INTERVAL': (int, 'Monitoring', 60),
|
||||
'MONITORING_USE_WEBSOCKET': (int, 'Monitoring', 0),
|
||||
'NMA_APIKEY': (str, 'NMA', ''),
|
||||
@@ -123,20 +147,32 @@ _CONFIG_DEFINITIONS = {
|
||||
'NMA_ON_RESUME': (int, 'NMA', 0),
|
||||
'NMA_ON_BUFFER': (int, 'NMA', 0),
|
||||
'NMA_ON_WATCHED': (int, 'NMA', 0),
|
||||
'NMA_ON_CREATED': (int, 'NMA', 0),
|
||||
'NMA_ON_EXTDOWN': (int, 'NMA', 0),
|
||||
'NMA_ON_INTDOWN': (int, 'NMA', 0),
|
||||
'NOTIFY_CONSECUTIVE': (int, 'Monitoring', 1),
|
||||
'NOTIFY_RECENTLY_ADDED': (int, 'Monitoring', 0),
|
||||
'NOTIFY_RECENTLY_ADDED_GRANDPARENT': (int, 'Monitoring', 0),
|
||||
'NOTIFY_RECENTLY_ADDED_DELAY': (int, 'Monitoring', 60),
|
||||
'NOTIFY_WATCHED_PERCENT': (int, 'Monitoring', 85),
|
||||
'NOTIFY_ON_START_SUBJECT_TEXT': (str, 'Monitoring', 'PlexPy ({server_name})'),
|
||||
'NOTIFY_ON_START_BODY_TEXT': (str, 'Monitoring', '{user} ({player}) started playing {title}.'),
|
||||
'NOTIFY_ON_STOP_SUBJECT_TEXT': (str, 'Monitoring', 'PlexPy ({server_name})'),
|
||||
'NOTIFY_ON_STOP_BODY_TEXT': (str, 'Monitoring', '{user} ({player}) has stopped {title}.'),
|
||||
'NOTIFY_ON_PAUSE_SUBJECT_TEXT': (str, 'Monitoring', 'PlexPy ({server_name})'),
|
||||
'NOTIFY_ON_PAUSE_BODY_TEXT': (str, 'Monitoring', '{user} ({player}) has paused {title}.'),
|
||||
'NOTIFY_ON_RESUME_SUBJECT_TEXT': (str, 'Monitoring', 'PlexPy ({server_name})'),
|
||||
'NOTIFY_ON_RESUME_BODY_TEXT': (str, 'Monitoring', '{user} ({player}) has resumed {title}.'),
|
||||
'NOTIFY_ON_BUFFER_SUBJECT_TEXT': (str, 'Monitoring', 'PlexPy ({server_name})'),
|
||||
'NOTIFY_ON_BUFFER_BODY_TEXT': (str, 'Monitoring', '{user} ({player}) is buffering {title}.'),
|
||||
'NOTIFY_ON_WATCHED_SUBJECT_TEXT': (str, 'Monitoring', 'PlexPy ({server_name})'),
|
||||
'NOTIFY_ON_WATCHED_BODY_TEXT': (str, 'Monitoring', '{user} ({player}) has watched {title}.'),
|
||||
'NOTIFY_ON_START_SUBJECT_TEXT': (unicode, 'Monitoring', 'PlexPy ({server_name})'),
|
||||
'NOTIFY_ON_START_BODY_TEXT': (unicode, 'Monitoring', '{user} ({player}) started playing {title}.'),
|
||||
'NOTIFY_ON_STOP_SUBJECT_TEXT': (unicode, 'Monitoring', 'PlexPy ({server_name})'),
|
||||
'NOTIFY_ON_STOP_BODY_TEXT': (unicode, 'Monitoring', '{user} ({player}) has stopped {title}.'),
|
||||
'NOTIFY_ON_PAUSE_SUBJECT_TEXT': (unicode, 'Monitoring', 'PlexPy ({server_name})'),
|
||||
'NOTIFY_ON_PAUSE_BODY_TEXT': (unicode, 'Monitoring', '{user} ({player}) has paused {title}.'),
|
||||
'NOTIFY_ON_RESUME_SUBJECT_TEXT': (unicode, 'Monitoring', 'PlexPy ({server_name})'),
|
||||
'NOTIFY_ON_RESUME_BODY_TEXT': (unicode, 'Monitoring', '{user} ({player}) has resumed {title}.'),
|
||||
'NOTIFY_ON_BUFFER_SUBJECT_TEXT': (unicode, 'Monitoring', 'PlexPy ({server_name})'),
|
||||
'NOTIFY_ON_BUFFER_BODY_TEXT': (unicode, 'Monitoring', '{user} ({player}) is buffering {title}.'),
|
||||
'NOTIFY_ON_WATCHED_SUBJECT_TEXT': (unicode, 'Monitoring', 'PlexPy ({server_name})'),
|
||||
'NOTIFY_ON_WATCHED_BODY_TEXT': (unicode, 'Monitoring', '{user} ({player}) has watched {title}.'),
|
||||
'NOTIFY_ON_CREATED_SUBJECT_TEXT': (unicode, 'Monitoring', 'PlexPy ({server_name})'),
|
||||
'NOTIFY_ON_CREATED_BODY_TEXT': (unicode, 'Monitoring', '{title} was recently added to Plex.'),
|
||||
'NOTIFY_ON_EXTDOWN_SUBJECT_TEXT': (unicode, 'Monitoring', 'PlexPy ({server_name})'),
|
||||
'NOTIFY_ON_EXTDOWN_BODY_TEXT': (unicode, 'Monitoring', 'The Plex Media Server remote access is down.'),
|
||||
'NOTIFY_ON_INTDOWN_SUBJECT_TEXT': (unicode, 'Monitoring', 'PlexPy ({server_name})'),
|
||||
'NOTIFY_ON_INTDOWN_BODY_TEXT': (unicode, 'Monitoring', 'The Plex Media Server is down.'),
|
||||
'OSX_NOTIFY_APP': (str, 'OSX_Notify', '/Applications/PlexPy'),
|
||||
'OSX_NOTIFY_ENABLED': (int, 'OSX_Notify', 0),
|
||||
'OSX_NOTIFY_ON_PLAY': (int, 'OSX_Notify', 0),
|
||||
@@ -145,6 +181,9 @@ _CONFIG_DEFINITIONS = {
|
||||
'OSX_NOTIFY_ON_RESUME': (int, 'OSX_Notify', 0),
|
||||
'OSX_NOTIFY_ON_BUFFER': (int, 'OSX_Notify', 0),
|
||||
'OSX_NOTIFY_ON_WATCHED': (int, 'OSX_Notify', 0),
|
||||
'OSX_NOTIFY_ON_CREATED': (int, 'OSX_Notify', 0),
|
||||
'OSX_NOTIFY_ON_EXTDOWN': (int, 'OSX_Notify', 0),
|
||||
'OSX_NOTIFY_ON_INTDOWN': (int, 'OSX_Notify', 0),
|
||||
'PLEX_CLIENT_HOST': (str, 'Plex', ''),
|
||||
'PLEX_ENABLED': (int, 'Plex', 0),
|
||||
'PLEX_PASSWORD': (str, 'Plex', ''),
|
||||
@@ -155,6 +194,9 @@ _CONFIG_DEFINITIONS = {
|
||||
'PLEX_ON_RESUME': (int, 'Plex', 0),
|
||||
'PLEX_ON_BUFFER': (int, 'Plex', 0),
|
||||
'PLEX_ON_WATCHED': (int, 'Plex', 0),
|
||||
'PLEX_ON_CREATED': (int, 'Plex', 0),
|
||||
'PLEX_ON_EXTDOWN': (int, 'Plex', 0),
|
||||
'PLEX_ON_INTDOWN': (int, 'Plex', 0),
|
||||
'PROWL_ENABLED': (int, 'Prowl', 0),
|
||||
'PROWL_KEYS': (str, 'Prowl', ''),
|
||||
'PROWL_PRIORITY': (int, 'Prowl', 0),
|
||||
@@ -164,6 +206,9 @@ _CONFIG_DEFINITIONS = {
|
||||
'PROWL_ON_RESUME': (int, 'Prowl', 0),
|
||||
'PROWL_ON_BUFFER': (int, 'Prowl', 0),
|
||||
'PROWL_ON_WATCHED': (int, 'Prowl', 0),
|
||||
'PROWL_ON_CREATED': (int, 'Prowl', 0),
|
||||
'PROWL_ON_EXTDOWN': (int, 'Prowl', 0),
|
||||
'PROWL_ON_INTDOWN': (int, 'Prowl', 0),
|
||||
'PUSHALOT_APIKEY': (str, 'Pushalot', ''),
|
||||
'PUSHALOT_ENABLED': (int, 'Pushalot', 0),
|
||||
'PUSHALOT_ON_PLAY': (int, 'Pushalot', 0),
|
||||
@@ -172,6 +217,9 @@ _CONFIG_DEFINITIONS = {
|
||||
'PUSHALOT_ON_RESUME': (int, 'Pushalot', 0),
|
||||
'PUSHALOT_ON_BUFFER': (int, 'Pushalot', 0),
|
||||
'PUSHALOT_ON_WATCHED': (int, 'Pushalot', 0),
|
||||
'PUSHALOT_ON_CREATED': (int, 'Pushalot', 0),
|
||||
'PUSHALOT_ON_EXTDOWN': (int, 'Pushalot', 0),
|
||||
'PUSHALOT_ON_INTDOWN': (int, 'Pushalot', 0),
|
||||
'PUSHBULLET_APIKEY': (str, 'PushBullet', ''),
|
||||
'PUSHBULLET_DEVICEID': (str, 'PushBullet', ''),
|
||||
'PUSHBULLET_CHANNEL_TAG': (str, 'PushBullet', ''),
|
||||
@@ -182,6 +230,9 @@ _CONFIG_DEFINITIONS = {
|
||||
'PUSHBULLET_ON_RESUME': (int, 'PushBullet', 0),
|
||||
'PUSHBULLET_ON_BUFFER': (int, 'PushBullet', 0),
|
||||
'PUSHBULLET_ON_WATCHED': (int, 'PushBullet', 0),
|
||||
'PUSHBULLET_ON_CREATED': (int, 'PushBullet', 0),
|
||||
'PUSHBULLET_ON_EXTDOWN': (int, 'PushBullet', 0),
|
||||
'PUSHBULLET_ON_INTDOWN': (int, 'PushBullet', 0),
|
||||
'PUSHOVER_APITOKEN': (str, 'Pushover', ''),
|
||||
'PUSHOVER_ENABLED': (int, 'Pushover', 0),
|
||||
'PUSHOVER_KEYS': (str, 'Pushover', ''),
|
||||
@@ -193,8 +244,24 @@ _CONFIG_DEFINITIONS = {
|
||||
'PUSHOVER_ON_RESUME': (int, 'Pushover', 0),
|
||||
'PUSHOVER_ON_BUFFER': (int, 'Pushover', 0),
|
||||
'PUSHOVER_ON_WATCHED': (int, 'Pushover', 0),
|
||||
'PUSHOVER_ON_CREATED': (int, 'Pushover', 0),
|
||||
'PUSHOVER_ON_EXTDOWN': (int, 'Pushover', 0),
|
||||
'PUSHOVER_ON_INTDOWN': (int, 'Pushover', 0),
|
||||
'REFRESH_USERS_INTERVAL': (int, 'Monitoring', 12),
|
||||
'REFRESH_USERS_ON_STARTUP': (int, 'Monitoring', 1),
|
||||
'TELEGRAM_BOT_TOKEN': (str, 'Telegram', ''),
|
||||
'TELEGRAM_ENABLED': (int, 'Telegram', 0),
|
||||
'TELEGRAM_CHAT_ID': (str, 'Telegram', ''),
|
||||
'TELEGRAM_ON_PLAY': (int, 'Telegram', 0),
|
||||
'TELEGRAM_ON_STOP': (int, 'Telegram', 0),
|
||||
'TELEGRAM_ON_PAUSE': (int, 'Telegram', 0),
|
||||
'TELEGRAM_ON_RESUME': (int, 'Telegram', 0),
|
||||
'TELEGRAM_ON_BUFFER': (int, 'Telegram', 0),
|
||||
'TELEGRAM_ON_WATCHED': (int, 'Telegram', 0),
|
||||
'TELEGRAM_ON_CREATED': (int, 'Telegram', 0),
|
||||
'TELEGRAM_ON_EXTDOWN': (int, 'Telegram', 0),
|
||||
'TELEGRAM_ON_INTDOWN': (int, 'Telegram', 0),
|
||||
'TV_LOGGING_ENABLE': (int, 'Monitoring', 1),
|
||||
'TV_NOTIFY_ENABLE': (int, 'Monitoring', 0),
|
||||
'TV_NOTIFY_ON_START': (int, 'Monitoring', 1),
|
||||
'TV_NOTIFY_ON_STOP': (int, 'Monitoring', 0),
|
||||
@@ -209,6 +276,9 @@ _CONFIG_DEFINITIONS = {
|
||||
'TWITTER_ON_RESUME': (int, 'Twitter', 0),
|
||||
'TWITTER_ON_BUFFER': (int, 'Twitter', 0),
|
||||
'TWITTER_ON_WATCHED': (int, 'Twitter', 0),
|
||||
'TWITTER_ON_CREATED': (int, 'Twitter', 0),
|
||||
'TWITTER_ON_EXTDOWN': (int, 'Twitter', 0),
|
||||
'TWITTER_ON_INTDOWN': (int, 'Twitter', 0),
|
||||
'UPDATE_DB_INTERVAL': (int, 'General', 24),
|
||||
'VERIFY_SSL_CERT': (bool_int, 'Advanced', 1),
|
||||
'VIDEO_LOGGING_ENABLE': (int, 'Monitoring', 1),
|
||||
@@ -221,7 +291,10 @@ _CONFIG_DEFINITIONS = {
|
||||
'XBMC_ON_PAUSE': (int, 'XBMC', 0),
|
||||
'XBMC_ON_RESUME': (int, 'XBMC', 0),
|
||||
'XBMC_ON_BUFFER': (int, 'XBMC', 0),
|
||||
'XBMC_ON_WATCHED': (int, 'XBMC', 0)
|
||||
'XBMC_ON_WATCHED': (int, 'XBMC', 0),
|
||||
'XBMC_ON_CREATED': (int, 'XBMC', 0),
|
||||
'XBMC_ON_EXTDOWN': (int, 'XBMC', 0),
|
||||
'XBMC_ON_INTDOWN': (int, 'XBMC', 0)
|
||||
}
|
||||
# pylint:disable=R0902
|
||||
# it might be nice to refactor for fewer instance variables
|
||||
@@ -234,6 +307,7 @@ class Config(object):
|
||||
self._config = ConfigObj(self._config_file, encoding='utf-8')
|
||||
for key in _CONFIG_DEFINITIONS.keys():
|
||||
self.check_setting(key)
|
||||
self._upgrade()
|
||||
|
||||
def _define(self, name):
|
||||
key = name.upper()
|
||||
@@ -322,4 +396,18 @@ class Config(object):
|
||||
"""
|
||||
for name, value in kwargs.items():
|
||||
key, definition_type, section, ini_key, default = self._define(name)
|
||||
self._config[section][ini_key] = definition_type(value)
|
||||
self._config[section][ini_key] = definition_type(value)
|
||||
|
||||
def _upgrade(self):
|
||||
"""
|
||||
Upgrades config file from previous verisions and bumps up config version
|
||||
"""
|
||||
if self.CONFIG_VERSION == '0':
|
||||
# Separate out movie and tv notifications
|
||||
if self.MOVIE_NOTIFY_ENABLE == 1:
|
||||
self.TV_NOTIFY_ENABLE = 1
|
||||
# Separate out movie and tv logging
|
||||
if self.VIDEO_LOGGING_ENABLE == 0:
|
||||
self.MOVIE_LOGGING_ENABLE = 0
|
||||
self.TV_LOGGING_ENABLE = 0
|
||||
self.CONFIG_VERSION = '1'
|
@@ -58,7 +58,14 @@ class MonitorDatabase(object):
|
||||
self.connection.execute("PRAGMA journal_mode = %s" % plexpy.CONFIG.JOURNAL_MODE)
|
||||
# 64mb of cache memory, probably need to make it user configurable
|
||||
self.connection.execute("PRAGMA cache_size=-%s" % (get_cache_size() * 1024))
|
||||
self.connection.row_factory = sqlite3.Row
|
||||
self.connection.row_factory = self.dict_factory
|
||||
|
||||
def dict_factory(self, cursor, row):
|
||||
d = {}
|
||||
for idx, col in enumerate(cursor.description):
|
||||
d[col[0]] = row[idx]
|
||||
|
||||
return d
|
||||
|
||||
def action(self, query, args=None, return_last_id=False):
|
||||
if query is None:
|
||||
|
@@ -28,7 +28,7 @@ class DataFactory(object):
|
||||
|
||||
def get_history(self, kwargs=None, custom_where=None, grouping=0, watched_percent=85):
|
||||
data_tables = datatables.DataTables()
|
||||
|
||||
|
||||
group_by = ['session_history.reference_id'] if grouping else ['session_history.id']
|
||||
|
||||
columns = ['session_history.reference_id',
|
||||
@@ -37,11 +37,11 @@ class DataFactory(object):
|
||||
'MIN(started) AS started',
|
||||
'MAX(stopped) AS stopped',
|
||||
'SUM(CASE WHEN stopped > 0 THEN (stopped - started) ELSE 0 END) - \
|
||||
SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) AS duration',
|
||||
'SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) AS paused_counter',
|
||||
SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) AS duration',
|
||||
'SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) AS paused_counter',
|
||||
'session_history.user_id',
|
||||
'session_history.user',
|
||||
'(CASE WHEN users.friendly_name IS NULL THEN user ELSE users.friendly_name END) as friendly_name',
|
||||
'(CASE WHEN users.friendly_name IS NULL THEN users.username ELSE users.friendly_name END) as friendly_name',
|
||||
'platform',
|
||||
'player',
|
||||
'ip_address',
|
||||
@@ -88,7 +88,7 @@ class DataFactory(object):
|
||||
'error': 'Unable to execute database query.'}
|
||||
|
||||
history = query['result']
|
||||
|
||||
|
||||
rows = []
|
||||
for item in history:
|
||||
if item["media_type"] == 'episode' and item["parent_thumb"]:
|
||||
@@ -106,10 +106,10 @@ class DataFactory(object):
|
||||
watched_status = 0
|
||||
|
||||
# Rename Mystery platform names
|
||||
platform_names = {'Mystery 3': 'Playstation 3',
|
||||
'Mystery 4': 'Playstation 4',
|
||||
'Mystery 5': 'Xbox 360'}
|
||||
platform = platform_names.get(item["platform"], item["platform"])
|
||||
platform = common.PLATFORM_NAME_OVERRIDES.get(item["platform"], item["platform"])
|
||||
|
||||
# Sanitize player name
|
||||
player = helpers.sanitize(item["player"])
|
||||
|
||||
row = {"reference_id": item["reference_id"],
|
||||
"id": item["id"],
|
||||
@@ -122,7 +122,7 @@ class DataFactory(object):
|
||||
"user": item["user"],
|
||||
"friendly_name": item["friendly_name"],
|
||||
"platform": platform,
|
||||
"player": item["player"],
|
||||
"player": player,
|
||||
"ip_address": item["ip_address"],
|
||||
"media_type": item["media_type"],
|
||||
"rating_key": item["rating_key"],
|
||||
@@ -143,7 +143,7 @@ class DataFactory(object):
|
||||
}
|
||||
|
||||
rows.append(row)
|
||||
|
||||
|
||||
dict = {'recordsFiltered': query['filteredCount'],
|
||||
'recordsTotal': query['totalCount'],
|
||||
'data': rows,
|
||||
@@ -182,23 +182,23 @@ class DataFactory(object):
|
||||
'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stats_count)
|
||||
result = monitor_db.select(query)
|
||||
except:
|
||||
logger.warn("Unable to execute database query.")
|
||||
logger.warn("Unable to execute database query for get_home_stats: top_tv.")
|
||||
return None
|
||||
|
||||
for item in result:
|
||||
row = {'title': item[1],
|
||||
'total_plays': item[2],
|
||||
'total_duration': item[3],
|
||||
row = {'title': item['grandparent_title'],
|
||||
'total_plays': item['total_plays'],
|
||||
'total_duration': item['total_duration'],
|
||||
'users_watched': '',
|
||||
'rating_key': item[4],
|
||||
'last_play': item[5],
|
||||
'grandparent_thumb': item[6],
|
||||
'rating_key': item['grandparent_rating_key'],
|
||||
'last_play': item['last_watch'],
|
||||
'grandparent_thumb': item['grandparent_thumb'],
|
||||
'thumb': '',
|
||||
'user': '',
|
||||
'friendly_name': '',
|
||||
'platform_type': '',
|
||||
'platform': '',
|
||||
'row_id': item[0]
|
||||
'row_id': item['id']
|
||||
}
|
||||
top_tv.append(row)
|
||||
|
||||
@@ -230,22 +230,22 @@ class DataFactory(object):
|
||||
'LIMIT %s' % (time_range, sort_type, stats_count)
|
||||
result = monitor_db.select(query)
|
||||
except:
|
||||
logger.warn("Unable to execute database query.")
|
||||
logger.warn("Unable to execute database query for get_home_stats: popular_tv.")
|
||||
return None
|
||||
|
||||
for item in result:
|
||||
row = {'title': item[1],
|
||||
'users_watched': item[2],
|
||||
'rating_key': item[3],
|
||||
'last_play': item[4],
|
||||
'total_plays': item[5],
|
||||
'grandparent_thumb': item[7],
|
||||
row = {'title': item['grandparent_title'],
|
||||
'users_watched': item['users_watched'],
|
||||
'rating_key': item['grandparent_rating_key'],
|
||||
'last_play': item['last_watch'],
|
||||
'total_plays': item['total_plays'],
|
||||
'grandparent_thumb': item['grandparent_thumb'],
|
||||
'thumb': '',
|
||||
'user': '',
|
||||
'friendly_name': '',
|
||||
'platform_type': '',
|
||||
'platform': '',
|
||||
'row_id': item[0]
|
||||
'row_id': item['id']
|
||||
}
|
||||
popular_tv.append(row)
|
||||
|
||||
@@ -274,23 +274,23 @@ class DataFactory(object):
|
||||
'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stats_count)
|
||||
result = monitor_db.select(query)
|
||||
except:
|
||||
logger.warn("Unable to execute database query.")
|
||||
logger.warn("Unable to execute database query for get_home_stats: top_movies.")
|
||||
return None
|
||||
|
||||
for item in result:
|
||||
row = {'title': item[1],
|
||||
'total_plays': item[2],
|
||||
'total_duration': item[3],
|
||||
row = {'title': item['full_title'],
|
||||
'total_plays': item['total_plays'],
|
||||
'total_duration': item['total_duration'],
|
||||
'users_watched': '',
|
||||
'rating_key': item[4],
|
||||
'last_play': item[5],
|
||||
'rating_key': item['rating_key'],
|
||||
'last_play': item['last_watch'],
|
||||
'grandparent_thumb': '',
|
||||
'thumb': item[6],
|
||||
'thumb': item['thumb'],
|
||||
'user': '',
|
||||
'friendly_name': '',
|
||||
'platform_type': '',
|
||||
'platform': '',
|
||||
'row_id': item[0]
|
||||
'row_id': item['id']
|
||||
}
|
||||
top_movies.append(row)
|
||||
|
||||
@@ -322,22 +322,22 @@ class DataFactory(object):
|
||||
'LIMIT %s' % (time_range, sort_type, stats_count)
|
||||
result = monitor_db.select(query)
|
||||
except:
|
||||
logger.warn("Unable to execute database query.")
|
||||
logger.warn("Unable to execute database query for get_home_stats: popular_movies.")
|
||||
return None
|
||||
|
||||
for item in result:
|
||||
row = {'title': item[1],
|
||||
'users_watched': item[2],
|
||||
'rating_key': item[3],
|
||||
'last_play': item[4],
|
||||
'total_plays': item[5],
|
||||
row = {'title': item['full_title'],
|
||||
'users_watched': item['users_watched'],
|
||||
'rating_key': item['rating_key'],
|
||||
'last_play': item['last_watch'],
|
||||
'total_plays': item['total_plays'],
|
||||
'grandparent_thumb': '',
|
||||
'thumb': item[7],
|
||||
'thumb': item['thumb'],
|
||||
'user': '',
|
||||
'friendly_name': '',
|
||||
'platform_type': '',
|
||||
'platform': '',
|
||||
'row_id': item[0]
|
||||
'row_id': item['id']
|
||||
}
|
||||
popular_movies.append(row)
|
||||
|
||||
@@ -366,23 +366,23 @@ class DataFactory(object):
|
||||
'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stats_count)
|
||||
result = monitor_db.select(query)
|
||||
except:
|
||||
logger.warn("Unable to execute database query.")
|
||||
logger.warn("Unable to execute database query for get_home_stats: top_music.")
|
||||
return None
|
||||
|
||||
for item in result:
|
||||
row = {'title': item[1],
|
||||
'total_plays': item[2],
|
||||
'total_duration': item[3],
|
||||
row = {'title': item['grandparent_title'],
|
||||
'total_plays': item['total_plays'],
|
||||
'total_duration': item['total_duration'],
|
||||
'users_watched': '',
|
||||
'rating_key': item[4],
|
||||
'last_play': item[5],
|
||||
'grandparent_thumb': item[6],
|
||||
'rating_key': item['grandparent_rating_key'],
|
||||
'last_play': item['last_watch'],
|
||||
'grandparent_thumb': item['grandparent_thumb'],
|
||||
'thumb': '',
|
||||
'user': '',
|
||||
'friendly_name': '',
|
||||
'platform_type': '',
|
||||
'platform': '',
|
||||
'row_id': item[0]
|
||||
'row_id': item['id']
|
||||
}
|
||||
top_music.append(row)
|
||||
|
||||
@@ -414,22 +414,22 @@ class DataFactory(object):
|
||||
'LIMIT %s' % (time_range, sort_type, stats_count)
|
||||
result = monitor_db.select(query)
|
||||
except:
|
||||
logger.warn("Unable to execute database query.")
|
||||
logger.warn("Unable to execute database query for get_home_stats: popular_music.")
|
||||
return None
|
||||
|
||||
for item in result:
|
||||
row = {'title': item[1],
|
||||
'users_watched': item[2],
|
||||
'rating_key': item[3],
|
||||
'last_play': item[4],
|
||||
'total_plays': item[5],
|
||||
'grandparent_thumb': item[7],
|
||||
row = {'title': item['grandparent_title'],
|
||||
'users_watched': item['users_watched'],
|
||||
'rating_key': item['grandparent_rating_key'],
|
||||
'last_play': item['last_watch'],
|
||||
'total_plays': item['total_plays'],
|
||||
'grandparent_thumb': item['grandparent_thumb'],
|
||||
'thumb': '',
|
||||
'user': '',
|
||||
'friendly_name': '',
|
||||
'platform_type': '',
|
||||
'platform': '',
|
||||
'row_id': item[0]
|
||||
'row_id': item['id']
|
||||
}
|
||||
popular_music.append(row)
|
||||
|
||||
@@ -440,7 +440,7 @@ class DataFactory(object):
|
||||
top_users = []
|
||||
try:
|
||||
query = 'SELECT session_history.user, ' \
|
||||
'(case when users.friendly_name is null then session_history.user else ' \
|
||||
'(case when users.friendly_name is null then users.username else ' \
|
||||
'users.friendly_name end) as friendly_name,' \
|
||||
'COUNT(session_history.id) as total_plays, ' \
|
||||
'SUM(case when session_history.stopped > 0 ' \
|
||||
@@ -459,21 +459,21 @@ class DataFactory(object):
|
||||
'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stats_count)
|
||||
result = monitor_db.select(query)
|
||||
except:
|
||||
logger.warn("Unable to execute database query.")
|
||||
logger.warn("Unable to execute database query for get_home_stats: top_users.")
|
||||
return None
|
||||
|
||||
for item in result:
|
||||
if not item[5] or item[5] == '':
|
||||
if not item['thumb'] or item['thumb'] == '':
|
||||
user_thumb = common.DEFAULT_USER_THUMB
|
||||
else:
|
||||
user_thumb = item[5]
|
||||
user_thumb = item['thumb']
|
||||
|
||||
row = {'user': item[0],
|
||||
'user_id': item[6],
|
||||
'friendly_name': item[1],
|
||||
'total_plays': item[2],
|
||||
'total_duration': item[3],
|
||||
'last_play': item[4],
|
||||
row = {'user': item['user'],
|
||||
'user_id': item['user_id'],
|
||||
'friendly_name': item['friendly_name'],
|
||||
'total_plays': item['total_plays'],
|
||||
'total_duration': item['total_duration'],
|
||||
'last_play': item['last_watch'],
|
||||
'user_thumb': user_thumb,
|
||||
'grandparent_thumb': '',
|
||||
'users_watched': '',
|
||||
@@ -507,20 +507,17 @@ class DataFactory(object):
|
||||
'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stats_count)
|
||||
result = monitor_db.select(query)
|
||||
except:
|
||||
logger.warn("Unable to execute database query.")
|
||||
logger.warn("Unable to execute database query for get_home_stats: top_platforms.")
|
||||
return None
|
||||
|
||||
for item in result:
|
||||
# Rename Mystery platform names
|
||||
platform_names = {'Mystery 3': 'Playstation 3',
|
||||
'Mystery 4': 'Playstation 4',
|
||||
'Mystery 5': 'Xbox 360'}
|
||||
platform_type = platform_names.get(item[0], item[0])
|
||||
platform_type = common.PLATFORM_NAME_OVERRIDES.get(item['platform'], item['platform'])
|
||||
|
||||
row = {'platform': item[0],
|
||||
'total_plays': item[1],
|
||||
'total_duration': item[2],
|
||||
'last_play': item[3],
|
||||
row = {'platform': item['platform'],
|
||||
'total_plays': item['total_plays'],
|
||||
'total_duration': item['total_duration'],
|
||||
'last_play': item['last_watch'],
|
||||
'platform_type': platform_type,
|
||||
'title': '',
|
||||
'thumb': '',
|
||||
@@ -542,7 +539,7 @@ class DataFactory(object):
|
||||
try:
|
||||
query = 'SELECT session_history_metadata.id, ' \
|
||||
'session_history.user, ' \
|
||||
'(case when users.friendly_name is null then session_history.user else ' \
|
||||
'(case when users.friendly_name is null then users.username else ' \
|
||||
'users.friendly_name end) as friendly_name,' \
|
||||
'users.user_id, ' \
|
||||
'users.custom_avatar_url as user_thumb, ' \
|
||||
@@ -551,7 +548,7 @@ class DataFactory(object):
|
||||
'session_history_metadata.thumb, ' \
|
||||
'session_history_metadata.grandparent_thumb, ' \
|
||||
'MAX(session_history.started) as last_watch, ' \
|
||||
'session_history.player as platform, ' \
|
||||
'session_history.player, ' \
|
||||
'((CASE WHEN session_history.view_offset IS NULL THEN 0.1 ELSE \
|
||||
session_history.view_offset * 1.0 END) / \
|
||||
(CASE WHEN session_history_metadata.duration IS NULL THEN 1.0 ELSE \
|
||||
@@ -564,31 +561,34 @@ class DataFactory(object):
|
||||
'AND (session_history_metadata.media_type = "movie" ' \
|
||||
'OR session_history_metadata.media_type = "episode") ' \
|
||||
'AND percent_complete >= %s ' \
|
||||
'GROUP BY session_history_metadata.full_title ' \
|
||||
'GROUP BY session_history.id ' \
|
||||
'ORDER BY last_watch DESC ' \
|
||||
'LIMIT %s' % (time_range, notify_watched_percent, stats_count)
|
||||
result = monitor_db.select(query)
|
||||
except:
|
||||
logger.warn("Unable to execute database query.")
|
||||
logger.warn("Unable to execute database query for get_home_stats: last_watched.")
|
||||
return None
|
||||
|
||||
for item in result:
|
||||
if not item[8] or item[8] == '':
|
||||
thumb = item[7]
|
||||
if not item['grandparent_thumb'] or item['grandparent_thumb'] == '':
|
||||
thumb = item['thumb']
|
||||
else:
|
||||
thumb = item[8]
|
||||
thumb = item['grandparent_thumb']
|
||||
|
||||
row = {'row_id': item[0],
|
||||
'user': item[1],
|
||||
'friendly_name': item[2],
|
||||
'user_id': item[3],
|
||||
'user_thumb': item[4],
|
||||
'title': item[5],
|
||||
'rating_key': item[6],
|
||||
# Sanitize player name
|
||||
player = helpers.sanitize(item["player"])
|
||||
|
||||
row = {'row_id': item['id'],
|
||||
'user': item['user'],
|
||||
'friendly_name': item['friendly_name'],
|
||||
'user_id': item['user_id'],
|
||||
'user_thumb': item['user_thumb'],
|
||||
'title': item['full_title'],
|
||||
'rating_key': item['rating_key'],
|
||||
'thumb': thumb,
|
||||
'grandparent_thumb': item[8],
|
||||
'last_watch': item[9],
|
||||
'platform_type': item[10],
|
||||
'grandparent_thumb': item['grandparent_thumb'],
|
||||
'last_watch': item['last_watch'],
|
||||
'player': player,
|
||||
}
|
||||
last_watched.append(row)
|
||||
|
||||
@@ -615,26 +615,26 @@ class DataFactory(object):
|
||||
stream_output = {}
|
||||
|
||||
for item in result:
|
||||
stream_output = {'container': item[0],
|
||||
'bitrate': item[1],
|
||||
'video_resolution': item[2],
|
||||
'width': item[3],
|
||||
'height': item[4],
|
||||
'aspect_ratio': item[5],
|
||||
'video_framerate': item[6],
|
||||
'video_codec': item[7],
|
||||
'audio_codec': item[8],
|
||||
'audio_channels': item[9],
|
||||
'transcode_video_dec': item[10],
|
||||
'transcode_video_codec': item[11],
|
||||
'transcode_height': item[12],
|
||||
'transcode_width': item[13],
|
||||
'transcode_audio_dec': item[14],
|
||||
'transcode_audio_codec': item[15],
|
||||
'transcode_audio_channels': item[16],
|
||||
'media_type': item[17],
|
||||
'title': item[18],
|
||||
'grandparent_title': item[19]
|
||||
stream_output = {'container': item['container'],
|
||||
'bitrate': item['bitrate'],
|
||||
'video_resolution': item['video_resolution'],
|
||||
'width': item['width'],
|
||||
'height': item['height'],
|
||||
'aspect_ratio': item['aspect_ratio'],
|
||||
'video_framerate': item['video_framerate'],
|
||||
'video_codec': item['video_codec'],
|
||||
'audio_codec': item['audio_codec'],
|
||||
'audio_channels': item['audio_channels'],
|
||||
'transcode_video_dec': item['video_decision'],
|
||||
'transcode_video_codec': item['transcode_video_codec'],
|
||||
'transcode_height': item['transcode_height'],
|
||||
'transcode_width': item['transcode_width'],
|
||||
'transcode_audio_dec': item['audio_decision'],
|
||||
'transcode_audio_codec': item['transcode_audio_codec'],
|
||||
'transcode_audio_channels': item['transcode_audio_channels'],
|
||||
'media_type': item['media_type'],
|
||||
'title': item['title'],
|
||||
'grandparent_title': item['grandparent_title']
|
||||
}
|
||||
|
||||
return stream_output
|
||||
@@ -680,29 +680,29 @@ class DataFactory(object):
|
||||
'ORDER BY started DESC LIMIT ?'
|
||||
result = monitor_db.select(query, args=[limit])
|
||||
except:
|
||||
logger.warn("Unable to execute database query.")
|
||||
logger.warn("Unable to execute database query for get_recently_watched.")
|
||||
return None
|
||||
|
||||
for row in result:
|
||||
if row[1] == 'episode' and row[8]:
|
||||
thumb = row[8]
|
||||
elif row[1] == 'episode':
|
||||
thumb = row[9]
|
||||
if row['media_type'] == 'episode' and row['parent_thumb']:
|
||||
thumb = row['parent_thumb']
|
||||
elif row['media_type'] == 'episode':
|
||||
thumb = row['grandparent_thumb']
|
||||
else:
|
||||
thumb = row[7]
|
||||
thumb = row['thumb']
|
||||
|
||||
recent_output = {'row_id': row[0],
|
||||
'type': row[1],
|
||||
'rating_key': row[2],
|
||||
'title': row[4],
|
||||
'parent_title': row[5],
|
||||
'grandparent_title': row[6],
|
||||
recent_output = {'row_id': row['id'],
|
||||
'type': row['media_type'],
|
||||
'rating_key': row['rating_key'],
|
||||
'title': row['title'],
|
||||
'parent_title': row['parent_title'],
|
||||
'grandparent_title': row['grandparent_title'],
|
||||
'thumb': thumb,
|
||||
'index': row[10],
|
||||
'parent_index': row[11],
|
||||
'year': row[12],
|
||||
'time': row[13],
|
||||
'user': row[14]
|
||||
'index': row['media_index'],
|
||||
'parent_index': row['parent_media_index'],
|
||||
'year': row['year'],
|
||||
'time': row['started'],
|
||||
'user': row['user']
|
||||
}
|
||||
recently_watched.append(recent_output)
|
||||
|
||||
@@ -729,7 +729,7 @@ class DataFactory(object):
|
||||
actors = item['actors'].split(';') if item['actors'] else []
|
||||
genres = item['genres'].split(';') if item['genres'] else []
|
||||
|
||||
metadata = {'type': item['media_type'],
|
||||
metadata = {'media_type': item['media_type'],
|
||||
'rating_key': item['rating_key'],
|
||||
'parent_rating_key': item['parent_rating_key'],
|
||||
'grandparent_rating_key': item['grandparent_rating_key'],
|
||||
@@ -806,6 +806,40 @@ class DataFactory(object):
|
||||
else:
|
||||
return 'Unable to delete items. Input user_id not valid.'
|
||||
|
||||
def delete_user(self, user_id=None):
|
||||
monitor_db = database.MonitorDatabase()
|
||||
|
||||
if user_id.isdigit():
|
||||
self.delete_all_user_history(user_id)
|
||||
logger.info(u"PlexPy DataFactory :: Deleting user with id %s from database." % user_id)
|
||||
monitor_db.action('UPDATE users SET deleted_user = 1 WHERE user_id = ?', [user_id])
|
||||
monitor_db.action('UPDATE users SET keep_history = 0 WHERE user_id = ?', [user_id])
|
||||
monitor_db.action('UPDATE users SET do_notify = 0 WHERE user_id = ?', [user_id])
|
||||
|
||||
return 'Deleted user with id %s.' % user_id
|
||||
else:
|
||||
return 'Unable to delete user. Input user_id not valid.'
|
||||
|
||||
def undelete_user(self, user_id=None, username=None):
|
||||
monitor_db = database.MonitorDatabase()
|
||||
|
||||
if user_id and user_id.isdigit():
|
||||
logger.info(u"PlexPy DataFactory :: Re-adding user with id %s to database." % user_id)
|
||||
monitor_db.action('UPDATE users SET deleted_user = 0 WHERE user_id = ?', [user_id])
|
||||
monitor_db.action('UPDATE users SET keep_history = 1 WHERE user_id = ?', [user_id])
|
||||
monitor_db.action('UPDATE users SET do_notify = 1 WHERE user_id = ?', [user_id])
|
||||
|
||||
return 'Re-added user with id %s.' % user_id
|
||||
elif username:
|
||||
logger.info(u"PlexPy DataFactory :: Re-adding user with username %s to database." % username)
|
||||
monitor_db.action('UPDATE users SET deleted_user = 0 WHERE username = ?', [username])
|
||||
monitor_db.action('UPDATE users SET keep_history = 1 WHERE username = ?', [username])
|
||||
monitor_db.action('UPDATE users SET do_notify = 1 WHERE username = ?', [username])
|
||||
|
||||
return 'Re-added user with username %s.' % username
|
||||
else:
|
||||
return 'Unable to re-add user. Input user_id or username not valid.'
|
||||
|
||||
def get_search_query(self, rating_key=''):
|
||||
monitor_db = database.MonitorDatabase()
|
||||
|
||||
@@ -854,7 +888,7 @@ class DataFactory(object):
|
||||
media_type = 'artist'
|
||||
|
||||
if query_string and media_type:
|
||||
query = {'query_string': query_string.replace('"', ''),
|
||||
query = {'query_string': query_string,
|
||||
'title': title,
|
||||
'parent_title': parent_title,
|
||||
'grandparent_title': grandparent_title,
|
||||
@@ -894,7 +928,7 @@ class DataFactory(object):
|
||||
grandparent_rating_key = result[0]['grandparent_rating_key']
|
||||
|
||||
except:
|
||||
logger.warn("Unable to execute database query.")
|
||||
logger.warn("Unable to execute database query for get_rating_keys_list.")
|
||||
return {}
|
||||
|
||||
query = 'SELECT rating_key, parent_rating_key, grandparent_rating_key, title, parent_title, grandparent_title, ' \
|
||||
@@ -934,7 +968,7 @@ class DataFactory(object):
|
||||
})
|
||||
|
||||
key_list = grandparents
|
||||
|
||||
|
||||
return key_list
|
||||
|
||||
def update_rating_key(self, old_key_list='', new_key_list='', media_type=''):
|
||||
@@ -956,52 +990,68 @@ class DataFactory(object):
|
||||
mapping = {}
|
||||
if old_key_list and new_key_list:
|
||||
mapping = get_pairs(old_key_list, new_key_list)
|
||||
|
||||
|
||||
if mapping:
|
||||
logger.info(u"PlexPy DataFactory :: Updating rating keys in the database.")
|
||||
for old_key, new_key in mapping.iteritems():
|
||||
# check rating_key (3 tables)
|
||||
monitor_db.action('UPDATE session_history SET rating_key = ? WHERE rating_key = ?',
|
||||
monitor_db.action('UPDATE session_history SET rating_key = ? WHERE rating_key = ?',
|
||||
[new_key, old_key])
|
||||
monitor_db.action('UPDATE session_history_media_info SET rating_key = ? WHERE rating_key = ?',
|
||||
monitor_db.action('UPDATE session_history_media_info SET rating_key = ? WHERE rating_key = ?',
|
||||
[new_key, old_key])
|
||||
monitor_db.action('UPDATE session_history_metadata SET rating_key = ? WHERE rating_key = ?',
|
||||
monitor_db.action('UPDATE session_history_metadata SET rating_key = ? WHERE rating_key = ?',
|
||||
[new_key, old_key])
|
||||
|
||||
# check parent_rating_key (2 tables)
|
||||
monitor_db.action('UPDATE session_history SET parent_rating_key = ? WHERE parent_rating_key = ?',
|
||||
monitor_db.action('UPDATE session_history SET parent_rating_key = ? WHERE parent_rating_key = ?',
|
||||
[new_key, old_key])
|
||||
monitor_db.action('UPDATE session_history_metadata SET parent_rating_key = ? WHERE parent_rating_key = ?',
|
||||
monitor_db.action('UPDATE session_history_metadata SET parent_rating_key = ? WHERE parent_rating_key = ?',
|
||||
[new_key, old_key])
|
||||
|
||||
# check grandparent_rating_key (2 tables)
|
||||
monitor_db.action('UPDATE session_history SET grandparent_rating_key = ? WHERE grandparent_rating_key = ?',
|
||||
monitor_db.action('UPDATE session_history SET grandparent_rating_key = ? WHERE grandparent_rating_key = ?',
|
||||
[new_key, old_key])
|
||||
monitor_db.action('UPDATE session_history_metadata SET grandparent_rating_key = ? WHERE grandparent_rating_key = ?',
|
||||
monitor_db.action('UPDATE session_history_metadata SET grandparent_rating_key = ? WHERE grandparent_rating_key = ?',
|
||||
[new_key, old_key])
|
||||
|
||||
# check thumb (1 table)
|
||||
monitor_db.action('UPDATE session_history_metadata SET thumb = replace(thumb, ?, ?) \
|
||||
WHERE thumb LIKE "/library/metadata/%s/thumb/%%"' % old_key,
|
||||
WHERE thumb LIKE "/library/metadata/%s/thumb/%%"' % old_key,
|
||||
[old_key, new_key])
|
||||
|
||||
# check parent_thumb (1 table)
|
||||
monitor_db.action('UPDATE session_history_metadata SET parent_thumb = replace(parent_thumb, ?, ?) \
|
||||
WHERE parent_thumb LIKE "/library/metadata/%s/thumb/%%"' % old_key,
|
||||
WHERE parent_thumb LIKE "/library/metadata/%s/thumb/%%"' % old_key,
|
||||
[old_key, new_key])
|
||||
|
||||
# check grandparent_thumb (1 table)
|
||||
monitor_db.action('UPDATE session_history_metadata SET grandparent_thumb = replace(grandparent_thumb, ?, ?) \
|
||||
WHERE grandparent_thumb LIKE "/library/metadata/%s/thumb/%%"' % old_key,
|
||||
WHERE grandparent_thumb LIKE "/library/metadata/%s/thumb/%%"' % old_key,
|
||||
[old_key, new_key])
|
||||
|
||||
# check art (1 table)
|
||||
monitor_db.action('UPDATE session_history_metadata SET art = replace(art, ?, ?) \
|
||||
WHERE art LIKE "/library/metadata/%s/art/%%"' % old_key,
|
||||
WHERE art LIKE "/library/metadata/%s/art/%%"' % old_key,
|
||||
[old_key, new_key])
|
||||
|
||||
return 'Updated rating key in database.'
|
||||
else:
|
||||
return 'No updated rating key needed in database. No changes were made.'
|
||||
# for debugging
|
||||
#return mapping
|
||||
#return mapping
|
||||
|
||||
def get_session_ip(self, session_key=''):
|
||||
monitor_db = database.MonitorDatabase()
|
||||
|
||||
if session_key:
|
||||
query = 'SELECT ip_address FROM sessions WHERE session_key = %d' % int(session_key)
|
||||
result = monitor_db.select(query)
|
||||
else:
|
||||
return None
|
||||
|
||||
ip_address = 'N/A'
|
||||
|
||||
for item in result:
|
||||
ip_address = item[0]
|
||||
|
||||
return ip_address
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# This file is part of PlexPy.
|
||||
# This file is part of PlexPy.
|
||||
#
|
||||
# PlexPy is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -70,6 +70,39 @@ class DataTables(object):
|
||||
else:
|
||||
grouping = False
|
||||
|
||||
# Build join parameters
|
||||
if join_types:
|
||||
counter = 0
|
||||
for join_type in join_types:
|
||||
if join_type.upper() == 'LEFT OUTER JOIN':
|
||||
join_item = 'LEFT OUTER JOIN %s ON %s = %s ' % \
|
||||
(join_tables[counter], join_evals[counter][0], join_evals[counter][1])
|
||||
elif join_type.upper() == 'JOIN' or join_type.upper() == 'INNER JOIN':
|
||||
join_item = 'JOIN %s ON %s = %s ' % \
|
||||
(join_tables[counter], join_evals[counter][0], join_evals[counter][1])
|
||||
else:
|
||||
join_item = ''
|
||||
|
||||
counter += 1
|
||||
join += join_item
|
||||
|
||||
# Build custom where parameters
|
||||
if custom_where:
|
||||
for w in custom_where:
|
||||
c_where += w[0] + ' = ? AND '
|
||||
|
||||
# The order of our args changes if we are grouping
|
||||
#if grouping:
|
||||
# args.insert(0, w[1])
|
||||
#else:
|
||||
# args.append(w[1])
|
||||
|
||||
# My testing shows that order of args doesn't change
|
||||
args.append(w[1])
|
||||
|
||||
if c_where:
|
||||
c_where = 'WHERE ' + c_where.rstrip(' AND ')
|
||||
|
||||
# Build ordering
|
||||
for o in parameters['order']:
|
||||
sort_order = ' COLLATE NOCASE'
|
||||
@@ -119,36 +152,6 @@ class DataTables(object):
|
||||
if where:
|
||||
where = 'WHERE ' + where.rstrip(' OR ')
|
||||
|
||||
# Build join parameters
|
||||
if join_types:
|
||||
counter = 0
|
||||
for join_type in join_types:
|
||||
if join_type.upper() == 'LEFT OUTER JOIN':
|
||||
join_item = 'LEFT OUTER JOIN %s ON %s = %s ' % \
|
||||
(join_tables[counter], join_evals[counter][0], join_evals[counter][1])
|
||||
elif join_type.upper() == 'JOIN' or join_type.upper() == 'INNER JOIN':
|
||||
join_item = 'JOIN %s ON %s = %s ' % \
|
||||
(join_tables[counter], join_evals[counter][0], join_evals[counter][1])
|
||||
else:
|
||||
join_item = ''
|
||||
|
||||
counter += 1
|
||||
join += join_item
|
||||
|
||||
# Build custom where parameters
|
||||
if custom_where:
|
||||
for w in custom_where:
|
||||
c_where += w[0] + ' = ? AND '
|
||||
|
||||
# The order of our args changes if we are grouping
|
||||
if grouping:
|
||||
args.insert(0, w[1])
|
||||
else:
|
||||
args.append(w[1])
|
||||
|
||||
if c_where:
|
||||
c_where = 'WHERE ' + c_where.rstrip(' AND ')
|
||||
|
||||
# Build our queries
|
||||
if grouping:
|
||||
if c_where == '':
|
||||
@@ -175,12 +178,18 @@ class DataTables(object):
|
||||
filtered = self.ssp_db.select(query, args=args)
|
||||
|
||||
# Build grand totals
|
||||
totalcount = self.ssp_db.select('SELECT COUNT(id) from %s' % table_name)[0][0]
|
||||
totalcount = self.ssp_db.select('SELECT COUNT(id) as total_count from %s' % table_name)[0]['total_count']
|
||||
|
||||
# Get draw counter
|
||||
draw_counter = int(parameters['draw'])
|
||||
|
||||
# Paginate results
|
||||
result = filtered[parameters['start']:(parameters['start'] + parameters['length'])]
|
||||
|
||||
# Sanitize on the way out
|
||||
result = [{k: helpers.sanitize(v) if isinstance(v, basestring) else v for k, v in row.iteritems()}
|
||||
for row in result]
|
||||
|
||||
output = {'result': result,
|
||||
'draw': draw_counter,
|
||||
'filteredCount': len(filtered),
|
||||
|
108
plexpy/graphs.py
108
plexpy/graphs.py
@@ -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, database, helpers
|
||||
from plexpy import logger, database, helpers, common
|
||||
|
||||
import datetime
|
||||
|
||||
@@ -76,10 +76,10 @@ class Graphs(object):
|
||||
series_2_value = 0
|
||||
series_3_value = 0
|
||||
for item in result:
|
||||
if date_string == item[0]:
|
||||
series_1_value = item[1]
|
||||
series_2_value = item[2]
|
||||
series_3_value = item[3]
|
||||
if date_string == item['date_played']:
|
||||
series_1_value = item['tv_duration']
|
||||
series_2_value = item['movie_duration']
|
||||
series_3_value = item['music_duration']
|
||||
break
|
||||
else:
|
||||
series_1_value = 0
|
||||
@@ -165,10 +165,10 @@ class Graphs(object):
|
||||
series_2_value = 0
|
||||
series_3_value = 0
|
||||
for item in result:
|
||||
if day_item == item[1]:
|
||||
series_1_value = item[2]
|
||||
series_2_value = item[3]
|
||||
series_3_value = item[4]
|
||||
if day_item == item['dayofweek']:
|
||||
series_1_value = item['tv_duration']
|
||||
series_2_value = item['movie_duration']
|
||||
series_3_value = item['music_duration']
|
||||
break
|
||||
else:
|
||||
series_1_value = 0
|
||||
@@ -240,10 +240,10 @@ class Graphs(object):
|
||||
series_2_value = 0
|
||||
series_3_value = 0
|
||||
for item in result:
|
||||
if hour_item == item[0]:
|
||||
series_1_value = item[1]
|
||||
series_2_value = item[2]
|
||||
series_3_value = item[3]
|
||||
if hour_item == item['hourofday']:
|
||||
series_1_value = item['tv_duration']
|
||||
series_2_value = item['movie_duration']
|
||||
series_3_value = item['music_duration']
|
||||
break
|
||||
else:
|
||||
series_1_value = 0
|
||||
@@ -316,10 +316,10 @@ class Graphs(object):
|
||||
series_2_value = 0
|
||||
series_3_value = 0
|
||||
for item in result:
|
||||
if date_string == item[0]:
|
||||
series_1_value = item[1]
|
||||
series_2_value = item[2]
|
||||
series_3_value = item[3]
|
||||
if date_string == item['datestring']:
|
||||
series_1_value = item['tv_duration']
|
||||
series_2_value = item['movie_duration']
|
||||
series_3_value = item['music_duration']
|
||||
break
|
||||
else:
|
||||
series_1_value = 0
|
||||
@@ -386,17 +386,10 @@ class Graphs(object):
|
||||
series_3 = []
|
||||
|
||||
for item in result:
|
||||
categories.append(item[0])
|
||||
series_1.append(item[1])
|
||||
series_2.append(item[2])
|
||||
series_3.append(item[3])
|
||||
|
||||
# Rename Mystery platform names
|
||||
platform_names = [('Mystery 3', 'Playstation 3'),
|
||||
('Mystery 4', 'Playstation 4'),
|
||||
('Mystery 5', 'Xbox 360')]
|
||||
for old_name, new_name in platform_names:
|
||||
categories = [item.replace(old_name, new_name) for item in categories]
|
||||
categories.append(common.PLATFORM_NAME_OVERRIDES.get(item['platform'], item['platform']))
|
||||
series_1.append(item['tv_duration'])
|
||||
series_2.append(item['movie_duration'])
|
||||
series_3.append(item['music_duration'])
|
||||
|
||||
series_1_output = {'name': 'TV',
|
||||
'data': series_1}
|
||||
@@ -417,7 +410,7 @@ class Graphs(object):
|
||||
|
||||
if y_axis == 'plays':
|
||||
query = 'SELECT ' \
|
||||
'(case when users.friendly_name is null then session_history.user else ' \
|
||||
'(case when users.friendly_name is null then users.username else ' \
|
||||
'users.friendly_name end) as friendly_name,' \
|
||||
'SUM(case when media_type = "episode" then 1 else 0 end) as tv_count, ' \
|
||||
'SUM(case when media_type = "movie" then 1 else 0 end) as movie_count, ' \
|
||||
@@ -434,7 +427,7 @@ class Graphs(object):
|
||||
result = monitor_db.select(query)
|
||||
else:
|
||||
query = 'SELECT ' \
|
||||
'(case when users.friendly_name is null then session_history.user else ' \
|
||||
'(case when users.friendly_name is null then users.username else ' \
|
||||
'users.friendly_name end) as friendly_name,' \
|
||||
'SUM(case when media_type = "episode" and stopped > 0 then (stopped - started) ' \
|
||||
' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as tv_duration, ' \
|
||||
@@ -460,10 +453,10 @@ class Graphs(object):
|
||||
series_3 = []
|
||||
|
||||
for item in result:
|
||||
categories.append(item[0])
|
||||
series_1.append(item[1])
|
||||
series_2.append(item[2])
|
||||
series_3.append(item[3])
|
||||
categories.append(item['friendly_name'])
|
||||
series_1.append(item['tv_duration'])
|
||||
series_2.append(item['movie_duration'])
|
||||
series_3.append(item['music_duration'])
|
||||
|
||||
series_1_output = {'name': 'TV',
|
||||
'data': series_1}
|
||||
@@ -547,10 +540,10 @@ class Graphs(object):
|
||||
series_2_value = 0
|
||||
series_3_value = 0
|
||||
for item in result:
|
||||
if date_string == item[0]:
|
||||
series_1_value = item[1]
|
||||
series_2_value = item[2]
|
||||
series_3_value = item[3]
|
||||
if date_string == item['date_played']:
|
||||
series_1_value = item['dp_duration']
|
||||
series_2_value = item['ds_duration']
|
||||
series_3_value = item['tc_duration']
|
||||
break
|
||||
else:
|
||||
series_1_value = 0
|
||||
@@ -633,10 +626,10 @@ class Graphs(object):
|
||||
series_3 = []
|
||||
|
||||
for item in result:
|
||||
categories.append(item[0])
|
||||
series_1.append(item[1])
|
||||
series_2.append(item[2])
|
||||
series_3.append(item[3])
|
||||
categories.append(item['resolution'])
|
||||
series_1.append(item['dp_duration'])
|
||||
series_2.append(item['ds_duration'])
|
||||
series_3.append(item['tc_duration'])
|
||||
|
||||
series_1_output = {'name': 'Direct Play',
|
||||
'data': series_1}
|
||||
@@ -730,10 +723,10 @@ class Graphs(object):
|
||||
series_3 = []
|
||||
|
||||
for item in result:
|
||||
categories.append(item[0])
|
||||
series_1.append(item[1])
|
||||
series_2.append(item[2])
|
||||
series_3.append(item[3])
|
||||
categories.append(item['resolution'])
|
||||
series_1.append(item['dp_duration'])
|
||||
series_2.append(item['ds_duration'])
|
||||
series_3.append(item['tc_duration'])
|
||||
|
||||
series_1_output = {'name': 'Direct Play',
|
||||
'data': series_1}
|
||||
@@ -808,17 +801,10 @@ class Graphs(object):
|
||||
series_3 = []
|
||||
|
||||
for item in result:
|
||||
categories.append(item[0])
|
||||
series_1.append(item[1])
|
||||
series_2.append(item[2])
|
||||
series_3.append(item[3])
|
||||
|
||||
# Rename Mystery platform names
|
||||
platform_names = [('Mystery 3', 'Playstation 3'),
|
||||
('Mystery 4', 'Playstation 4'),
|
||||
('Mystery 5', 'Xbox 360')]
|
||||
for old_name, new_name in platform_names:
|
||||
categories = [item.replace(old_name, new_name) for item in categories]
|
||||
categories.append(common.PLATFORM_NAME_OVERRIDES.get(item['platform'], item['platform']))
|
||||
series_1.append(item['dp_duration'])
|
||||
series_2.append(item['ds_duration'])
|
||||
series_3.append(item['tc_duration'])
|
||||
|
||||
series_1_output = {'name': 'Direct Play',
|
||||
'data': series_1}
|
||||
@@ -896,10 +882,10 @@ class Graphs(object):
|
||||
series_3 = []
|
||||
|
||||
for item in result:
|
||||
categories.append(item[0])
|
||||
series_1.append(item[1])
|
||||
series_2.append(item[2])
|
||||
series_3.append(item[3])
|
||||
categories.append(item['username'])
|
||||
series_1.append(item['dp_duration'])
|
||||
series_2.append(item['ds_duration'])
|
||||
series_3.append(item['tc_duration'])
|
||||
|
||||
series_1_output = {'name': 'Direct Play',
|
||||
'data': series_1}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# This file is part of PlexPy.
|
||||
# This file is part of PlexPy.
|
||||
#
|
||||
# PlexPy is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -92,13 +92,15 @@ def latinToAscii(unicrap):
|
||||
}
|
||||
|
||||
r = ''
|
||||
for i in unicrap:
|
||||
if ord(i) in xlate:
|
||||
r += xlate[ord(i)]
|
||||
elif ord(i) >= 0x80:
|
||||
pass
|
||||
else:
|
||||
r += str(i)
|
||||
if unicrap:
|
||||
for i in unicrap:
|
||||
if ord(i) in xlate:
|
||||
r += xlate[ord(i)]
|
||||
elif ord(i) >= 0x80:
|
||||
pass
|
||||
else:
|
||||
r += str(i)
|
||||
|
||||
return r
|
||||
|
||||
|
||||
@@ -144,6 +146,31 @@ def now():
|
||||
now = datetime.datetime.now()
|
||||
return now.strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
def human_duration(s):
|
||||
|
||||
hd = ''
|
||||
|
||||
if str(s).isdigit():
|
||||
d = int(s / 84600)
|
||||
h = int((s % 84600) / 3600)
|
||||
m = int(((s % 84600) % 3600) / 60)
|
||||
s = int(((s % 84600) % 3600) % 60)
|
||||
|
||||
hd_list = []
|
||||
if d > 0:
|
||||
hd_list.append(str(d) + ' days')
|
||||
if h > 0:
|
||||
hd_list.append(str(h) + ' hrs')
|
||||
if m > 0:
|
||||
hd_list.append(str(m) + ' mins')
|
||||
if s > 0:
|
||||
hd_list.append(str(s) + ' secs')
|
||||
|
||||
hd = ' '.join(hd_list)
|
||||
|
||||
return hd
|
||||
else:
|
||||
return hd
|
||||
|
||||
def get_age(date):
|
||||
|
||||
@@ -177,7 +204,7 @@ def piratesize(size):
|
||||
split = size.split(" ")
|
||||
factor = float(split[0])
|
||||
unit = split[1].upper()
|
||||
|
||||
|
||||
if unit == 'MiB':
|
||||
size = factor * 1048576
|
||||
elif unit == 'MB':
|
||||
@@ -403,3 +430,9 @@ def process_json_kwargs(json_kwargs):
|
||||
params = json.loads(json_kwargs)
|
||||
|
||||
return params
|
||||
|
||||
def sanitize(string):
|
||||
if string:
|
||||
return unicode(string).replace('<','<').replace('>','>')
|
||||
else:
|
||||
return ''
|
||||
|
@@ -13,7 +13,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with PlexPy. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from plexpy import logger, config, notifiers, database, helpers
|
||||
from plexpy import logger, config, notifiers, database, helpers, plextv, pmsconnect
|
||||
|
||||
import plexpy
|
||||
import time
|
||||
@@ -30,144 +30,179 @@ def notify(stream_data=None, notify_action=None):
|
||||
if not user_details['do_notify']:
|
||||
return
|
||||
|
||||
if stream_data['media_type'] == 'movie' or stream_data['media_type'] == 'episode':
|
||||
if plexpy.CONFIG.MOVIE_NOTIFY_ENABLE or plexpy.CONFIG.TV_NOTIFY_ENABLE:
|
||||
if (stream_data['media_type'] == 'movie' and plexpy.CONFIG.MOVIE_NOTIFY_ENABLE) \
|
||||
or (stream_data['media_type'] == 'episode' and plexpy.CONFIG.TV_NOTIFY_ENABLE):
|
||||
|
||||
progress_percent = helpers.get_percent(stream_data['view_offset'], stream_data['duration'])
|
||||
progress_percent = helpers.get_percent(stream_data['view_offset'], stream_data['duration'])
|
||||
|
||||
for agent in notifiers.available_notification_agents():
|
||||
if agent['on_play'] and notify_action == 'play':
|
||||
for agent in notifiers.available_notification_agents():
|
||||
if agent['on_play'] and notify_action == 'play':
|
||||
# Build and send notification
|
||||
notify_strings = build_notify_text(session=stream_data, state=notify_action)
|
||||
notifiers.send_notification(config_id=agent['id'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1])
|
||||
# Set the notification state in the db
|
||||
set_notify_state(session=stream_data, state=notify_action, agent_info=agent)
|
||||
|
||||
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 = build_notify_text(session=stream_data, state=notify_action)
|
||||
notifiers.send_notification(config_id=agent['id'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1])
|
||||
|
||||
set_notify_state(session=stream_data, state=notify_action, agent_info=agent)
|
||||
|
||||
elif agent['on_pause'] and notify_action == 'pause' \
|
||||
and (plexpy.CONFIG.NOTIFY_CONSECUTIVE or progress_percent < 99):
|
||||
# Build and send notification
|
||||
notify_strings = build_notify_text(session=stream_data, state=notify_action)
|
||||
notifiers.send_notification(config_id=agent['id'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1])
|
||||
|
||||
set_notify_state(session=stream_data, state=notify_action, agent_info=agent)
|
||||
|
||||
elif agent['on_resume'] and notify_action == 'resume' \
|
||||
and (plexpy.CONFIG.NOTIFY_CONSECUTIVE or progress_percent < 99):
|
||||
# Build and send notification
|
||||
notify_strings = build_notify_text(session=stream_data, state=notify_action)
|
||||
notifiers.send_notification(config_id=agent['id'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1])
|
||||
|
||||
set_notify_state(session=stream_data, state=notify_action, agent_info=agent)
|
||||
|
||||
elif agent['on_buffer'] and notify_action == 'buffer':
|
||||
# Build and send notification
|
||||
notify_strings = build_notify_text(session=stream_data, state=notify_action)
|
||||
notifiers.send_notification(config_id=agent['id'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1])
|
||||
|
||||
set_notify_state(session=stream_data, state=notify_action, agent_info=agent)
|
||||
|
||||
elif agent['on_watched'] and notify_action == 'watched':
|
||||
# Get the current states for notifications from our db
|
||||
notify_states = get_notify_state(session=stream_data)
|
||||
|
||||
# If there is nothing in the notify_log for our agent id but it is enabled we should notify
|
||||
if not any(d['agent_id'] == agent['id'] for d in notify_states):
|
||||
# Build and send notification
|
||||
notify_strings = build_notify_text(session=stream_data, state=notify_action)
|
||||
notifiers.send_notification(config_id=agent['id'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1])
|
||||
# Set the notification state in the db
|
||||
set_notify_state(session=stream_data, state='play', agent_info=agent)
|
||||
set_notify_state(session=stream_data, state=notify_action, agent_info=agent)
|
||||
|
||||
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 = build_notify_text(session=stream_data, state=notify_action)
|
||||
notifiers.send_notification(config_id=agent['id'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1])
|
||||
else:
|
||||
# Check in our notify log if the notification has already been sent
|
||||
for notify_state in notify_states:
|
||||
if not notify_state['on_watched'] and (notify_state['agent_id'] == agent['id']):
|
||||
# Build and send notification
|
||||
notify_strings = build_notify_text(session=stream_data, state=notify_action)
|
||||
notifiers.send_notification(config_id=agent['id'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1])
|
||||
# Set the notification state in the db
|
||||
set_notify_state(session=stream_data, state=notify_action, agent_info=agent)
|
||||
|
||||
set_notify_state(session=stream_data, state='stop', agent_info=agent)
|
||||
elif (stream_data['media_type'] == 'track' and plexpy.CONFIG.MUSIC_NOTIFY_ENABLE):
|
||||
|
||||
elif agent['on_pause'] and notify_action == 'pause' \
|
||||
and (plexpy.CONFIG.NOTIFY_CONSECUTIVE or progress_percent < 99):
|
||||
# Build and send notification
|
||||
notify_strings = build_notify_text(session=stream_data, state=notify_action)
|
||||
notifiers.send_notification(config_id=agent['id'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1])
|
||||
for agent in notifiers.available_notification_agents():
|
||||
if agent['on_play'] and notify_action == 'play':
|
||||
# Build and send notification
|
||||
notify_strings = build_notify_text(session=stream_data, state=notify_action)
|
||||
notifiers.send_notification(config_id=agent['id'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1])
|
||||
# Set the notification state in the db
|
||||
set_notify_state(session=stream_data, state=notify_action, agent_info=agent)
|
||||
|
||||
set_notify_state(session=stream_data, state='pause', agent_info=agent)
|
||||
elif agent['on_stop'] and notify_action == 'stop':
|
||||
# Build and send notification
|
||||
notify_strings = build_notify_text(session=stream_data, state=notify_action)
|
||||
notifiers.send_notification(config_id=agent['id'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1])
|
||||
# Set the notification state in the db
|
||||
set_notify_state(session=stream_data, state=notify_action, agent_info=agent)
|
||||
|
||||
elif agent['on_resume'] and notify_action == 'resume' \
|
||||
and (plexpy.CONFIG.NOTIFY_CONSECUTIVE or progress_percent < 99):
|
||||
# Build and send notification
|
||||
notify_strings = build_notify_text(session=stream_data, state=notify_action)
|
||||
notifiers.send_notification(config_id=agent['id'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1])
|
||||
elif agent['on_pause'] and notify_action == 'pause':
|
||||
# Build and send notification
|
||||
notify_strings = build_notify_text(session=stream_data, state=notify_action)
|
||||
notifiers.send_notification(config_id=agent['id'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1])
|
||||
# Set the notification state in the db
|
||||
set_notify_state(session=stream_data, state=notify_action, agent_info=agent)
|
||||
|
||||
set_notify_state(session=stream_data, state='resume', agent_info=agent)
|
||||
elif agent['on_resume'] and notify_action == 'resume':
|
||||
# Build and send notification
|
||||
notify_strings = build_notify_text(session=stream_data, state=notify_action)
|
||||
notifiers.send_notification(config_id=agent['id'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1])
|
||||
# Set the notification state in the db
|
||||
set_notify_state(session=stream_data, state=notify_action, agent_info=agent)
|
||||
|
||||
elif agent['on_buffer'] and notify_action == 'buffer':
|
||||
# Build and send notification
|
||||
notify_strings = build_notify_text(session=stream_data, state=notify_action)
|
||||
notifiers.send_notification(config_id=agent['id'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1])
|
||||
|
||||
set_notify_state(session=stream_data, state='buffer', agent_info=agent)
|
||||
|
||||
elif agent['on_watched'] and notify_action == 'watched':
|
||||
# Get the current states for notifications from our db
|
||||
notify_states = get_notify_state(session=stream_data)
|
||||
|
||||
# If there is nothing in the notify_log for our agent id but it is enabled we should notify
|
||||
if not any(d['agent_id'] == agent['id'] for d in notify_states):
|
||||
# Build and send notification
|
||||
notify_strings = build_notify_text(session=stream_data, state=notify_action)
|
||||
notifiers.send_notification(config_id=agent['id'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1])
|
||||
# Set the notification state in the db
|
||||
set_notify_state(session=stream_data, state='watched', agent_info=agent)
|
||||
|
||||
else:
|
||||
# Check in our notify log if the notification has already been sent
|
||||
for notify_state in notify_states:
|
||||
if not notify_state['on_watched'] and (notify_state['agent_id'] == agent['id']):
|
||||
# Build and send notification
|
||||
notify_strings = build_notify_text(session=stream_data, state=notify_action)
|
||||
notifiers.send_notification(config_id=agent['id'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1])
|
||||
# Set the notification state in the db
|
||||
set_notify_state(session=stream_data, state='watched', agent_info=agent)
|
||||
|
||||
elif stream_data['media_type'] == 'track':
|
||||
if plexpy.CONFIG.MUSIC_NOTIFY_ENABLE:
|
||||
|
||||
for agent in notifiers.available_notification_agents():
|
||||
if agent['on_play'] and notify_action == 'play':
|
||||
# Build and send notification
|
||||
notify_strings = build_notify_text(session=stream_data, state=notify_action)
|
||||
notifiers.send_notification(config_id=agent['id'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1])
|
||||
# Set the notification state in the db
|
||||
set_notify_state(session=stream_data, state='play', agent_info=agent)
|
||||
|
||||
elif agent['on_stop'] and notify_action == 'stop':
|
||||
# Build and send notification
|
||||
notify_strings = build_notify_text(session=stream_data, state=notify_action)
|
||||
notifiers.send_notification(config_id=agent['id'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1])
|
||||
# Set the notification state in the db
|
||||
set_notify_state(session=stream_data, state='stop', agent_info=agent)
|
||||
|
||||
elif agent['on_pause'] and notify_action == 'pause':
|
||||
# Build and send notification
|
||||
notify_strings = build_notify_text(session=stream_data, state=notify_action)
|
||||
notifiers.send_notification(config_id=agent['id'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1])
|
||||
# Set the notification state in the db
|
||||
set_notify_state(session=stream_data, state='pause', agent_info=agent)
|
||||
|
||||
elif agent['on_resume'] and notify_action == 'resume':
|
||||
# Build and send notification
|
||||
notify_strings = build_notify_text(session=stream_data, state=notify_action)
|
||||
notifiers.send_notification(config_id=agent['id'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1])
|
||||
# Set the notification state in the db
|
||||
set_notify_state(session=stream_data, state='resume', agent_info=agent)
|
||||
|
||||
elif agent['on_buffer'] and notify_action == 'buffer':
|
||||
# Build and send notification
|
||||
notify_strings = build_notify_text(session=stream_data, state=notify_action)
|
||||
notifiers.send_notification(config_id=agent['id'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1])
|
||||
# Set the notification state in the db
|
||||
set_notify_state(session=stream_data, state='buffer', agent_info=agent)
|
||||
elif agent['on_buffer'] and notify_action == 'buffer':
|
||||
# Build and send notification
|
||||
notify_strings = build_notify_text(session=stream_data, state=notify_action)
|
||||
notifiers.send_notification(config_id=agent['id'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1])
|
||||
# Set the notification state in the db
|
||||
set_notify_state(session=stream_data, state=notify_action, agent_info=agent)
|
||||
|
||||
elif stream_data['media_type'] == 'clip':
|
||||
pass
|
||||
else:
|
||||
logger.debug(u"PlexPy Notifier :: Notify called with unsupported media type.")
|
||||
#logger.debug(u"PlexPy Notifier :: Notify called with unsupported media type.")
|
||||
pass
|
||||
else:
|
||||
logger.debug(u"PlexPy Notifier :: Notify called but incomplete data received.")
|
||||
|
||||
|
||||
def notify_timeline(timeline_data=None, notify_action=None):
|
||||
if timeline_data and notify_action:
|
||||
if (timeline_data['media_type'] == 'movie' and plexpy.CONFIG.MOVIE_NOTIFY_ENABLE) \
|
||||
or ((timeline_data['media_type'] == 'show' or timeline_data['media_type'] == 'episode') \
|
||||
and plexpy.CONFIG.TV_NOTIFY_ENABLE) \
|
||||
or ((timeline_data['media_type'] == 'artist' or timeline_data['media_type'] == 'track') \
|
||||
and plexpy.CONFIG.MUSIC_NOTIFY_ENABLE):
|
||||
|
||||
for agent in notifiers.available_notification_agents():
|
||||
if agent['on_created'] and notify_action == 'created':
|
||||
# Build and send notification
|
||||
notify_strings = build_notify_text(timeline=timeline_data, state=notify_action)
|
||||
notifiers.send_notification(config_id=agent['id'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1])
|
||||
# Set the notification state in the db
|
||||
set_notify_state(session=timeline_data, state=notify_action, agent_info=agent)
|
||||
|
||||
elif not timeline_data and notify_action:
|
||||
for agent in notifiers.available_notification_agents():
|
||||
if agent['on_extdown'] and notify_action == 'extdown':
|
||||
# Build and send notification
|
||||
notify_strings = build_server_notify_text(state=notify_action)
|
||||
notifiers.send_notification(config_id=agent['id'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1])
|
||||
if agent['on_intdown'] and notify_action == 'intdown':
|
||||
# Build and send notification
|
||||
notify_strings = build_server_notify_text(state=notify_action)
|
||||
notifiers.send_notification(config_id=agent['id'],
|
||||
subject=notify_strings[0],
|
||||
body=notify_strings[1])
|
||||
else:
|
||||
logger.debug(u"PlexPy Notifier :: Notify timeline called but incomplete data received.")
|
||||
|
||||
|
||||
def get_notify_state(session):
|
||||
monitor_db = database.MonitorDatabase()
|
||||
result = monitor_db.select('SELECT on_play, on_stop, on_pause, on_resume, on_buffer, on_watched, agent_id '
|
||||
@@ -190,6 +225,21 @@ def get_notify_state(session):
|
||||
|
||||
return notify_states
|
||||
|
||||
def get_notify_state_timeline(timeline):
|
||||
monitor_db = database.MonitorDatabase()
|
||||
result = monitor_db.select('SELECT on_created, agent_id '
|
||||
'FROM notify_log '
|
||||
'WHERE rating_key = ? '
|
||||
'ORDER BY id DESC',
|
||||
args=[timeline['rating_key']])
|
||||
notify_states = []
|
||||
for item in result:
|
||||
notify_state = {'on_created': item[0],
|
||||
'agent_id': item[1]}
|
||||
notify_states.append(notify_state)
|
||||
|
||||
return notify_states
|
||||
|
||||
|
||||
def set_notify_state(session, state, agent_info):
|
||||
|
||||
@@ -208,53 +258,77 @@ def set_notify_state(session, state, agent_info):
|
||||
values = {'on_buffer': int(time.time())}
|
||||
elif state == 'watched':
|
||||
values = {'on_watched': int(time.time())}
|
||||
elif state == 'created':
|
||||
values = {'on_created': int(time.time())}
|
||||
else:
|
||||
return
|
||||
|
||||
keys = {'session_key': session['session_key'],
|
||||
'rating_key': session['rating_key'],
|
||||
'user_id': session['user_id'],
|
||||
'user': session['user'],
|
||||
'agent_id': agent_info['id'],
|
||||
'agent_name': agent_info['name']}
|
||||
if state == 'created':
|
||||
keys = {'rating_key': session['rating_key'],
|
||||
'agent_id': agent_info['id'],
|
||||
'agent_name': agent_info['name']}
|
||||
else:
|
||||
keys = {'session_key': session['session_key'],
|
||||
'rating_key': session['rating_key'],
|
||||
'user_id': session['user_id'],
|
||||
'user': session['user'],
|
||||
'agent_id': agent_info['id'],
|
||||
'agent_name': agent_info['name']}
|
||||
|
||||
monitor_db.upsert(table_name='notify_log', key_dict=keys, value_dict=values)
|
||||
else:
|
||||
logger.error('PlexPy Notifier :: Unable to set notify state.')
|
||||
|
||||
|
||||
def build_notify_text(session, state):
|
||||
from plexpy import pmsconnect, helpers
|
||||
def build_notify_text(session=None, timeline=None, state=None):
|
||||
import re
|
||||
|
||||
# Get the server name
|
||||
pms_connect = pmsconnect.PmsConnect()
|
||||
server_name = pms_connect.get_server_pref(pref='FriendlyName')
|
||||
server_name = plexpy.CONFIG.PMS_NAME
|
||||
|
||||
# Get the server uptime
|
||||
plex_tv = plextv.PlexTV()
|
||||
server_times = plex_tv.get_server_times()
|
||||
|
||||
if server_times:
|
||||
updated_at = server_times[0]['updated_at']
|
||||
server_uptime = helpers.human_duration(int(time.time() - helpers.cast_to_float(updated_at)))
|
||||
else:
|
||||
logger.error(u"PlexPy Notifier :: Unable to retrieve server uptime.")
|
||||
server_uptime = 'N/A'
|
||||
|
||||
# Get metadata feed for item
|
||||
metadata = pms_connect.get_metadata_details(rating_key=session['rating_key'])
|
||||
if session:
|
||||
rating_key = session['rating_key']
|
||||
elif timeline:
|
||||
rating_key = timeline['rating_key']
|
||||
|
||||
if metadata:
|
||||
item_metadata = metadata['metadata']
|
||||
pms_connect = pmsconnect.PmsConnect()
|
||||
metadata_list = pms_connect.get_metadata_details(rating_key=rating_key)
|
||||
|
||||
if metadata_list:
|
||||
metadata = metadata_list['metadata']
|
||||
else:
|
||||
logger.error(u"PlexPy Notifier :: Unable to retrieve metadata for rating_key %s" % str(session['rating_key']))
|
||||
logger.error(u"PlexPy Notifier :: Unable to retrieve metadata for rating_key %s" % str(rating_key))
|
||||
return []
|
||||
|
||||
# Check for exclusion tags
|
||||
if session['media_type'] == 'episode':
|
||||
if metadata['media_type'] == 'movie':
|
||||
# Regex pattern to remove the text in the tags we don't want
|
||||
pattern = re.compile('<movie>[^>]+.</movie>|<music>[^>]+.</music>', re.IGNORECASE)
|
||||
elif session['media_type'] == 'movie':
|
||||
pattern = re.compile('<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('<tv>[^>]+.</tv>|<music>[^>]+.</music>', re.IGNORECASE)
|
||||
elif session['media_type'] == 'track':
|
||||
pattern = re.compile('<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('<tv>[^>]+.</tv>|<movie>[^>]+.</movie>', re.IGNORECASE)
|
||||
pattern = re.compile('<tv>[^>]+.</tv>|<movie>[^>]+.</movie>', re.IGNORECASE|re.DOTALL)
|
||||
else:
|
||||
pattern = None
|
||||
|
||||
if session['media_type'] == 'episode' or session['media_type'] == 'movie' or session['media_type'] == 'track' \
|
||||
and pattern:
|
||||
if metadata['media_type'] == 'movie' \
|
||||
or metadata['media_type'] == 'show' or metadata['media_type'] == 'episode' \
|
||||
or metadata['media_type'] == 'artist' or metadata['media_type'] == 'track' \
|
||||
and pattern:
|
||||
# Remove the unwanted tags and strip any unmatch tags too.
|
||||
on_start_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT))
|
||||
on_start_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT))
|
||||
@@ -268,6 +342,8 @@ def build_notify_text(session, state):
|
||||
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))
|
||||
else:
|
||||
on_start_subject = plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT
|
||||
on_start_body = plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT
|
||||
@@ -281,63 +357,87 @@ def build_notify_text(session, state):
|
||||
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
|
||||
|
||||
# Create a title
|
||||
if session['media_type'] == 'episode':
|
||||
full_title = '%s - %s' % (session['grandparent_title'],
|
||||
session['title'])
|
||||
elif session['media_type'] == 'track':
|
||||
full_title = '%s - %s' % (session['grandparent_title'],
|
||||
session['title'])
|
||||
if metadata['media_type'] == 'episode' or metadata['media_type'] == 'track':
|
||||
full_title = '%s - %s' % (metadata['grandparent_title'],
|
||||
metadata['title'])
|
||||
else:
|
||||
full_title = session['title']
|
||||
full_title = metadata['title']
|
||||
|
||||
# Generate a combined transcode decision value
|
||||
if session['video_decision']:
|
||||
if session['video_decision'] == 'transcode':
|
||||
duration = helpers.convert_milliseconds_to_minutes(metadata['duration'])
|
||||
|
||||
# Default values
|
||||
video_decision = ''
|
||||
audio_decision = ''
|
||||
transcode_decision = ''
|
||||
stream_duration = 0
|
||||
view_offset = 0
|
||||
user = ''
|
||||
platform = ''
|
||||
player = ''
|
||||
ip_address = 'N/A'
|
||||
|
||||
# Session values
|
||||
if session:
|
||||
# Generate a combined transcode decision value
|
||||
video_decision = session['video_decision'].title()
|
||||
audio_decision = session['audio_decision'].title()
|
||||
|
||||
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 = 'Direct Stream'
|
||||
else:
|
||||
transcode_decision = 'Direct Play'
|
||||
else:
|
||||
if session['audio_decision'] == 'transcode':
|
||||
transcode_decision = 'Transcode'
|
||||
else:
|
||||
transcode_decision = 'Direct Play'
|
||||
|
||||
duration = helpers.convert_milliseconds_to_minutes(item_metadata['duration'])
|
||||
view_offset = helpers.convert_milliseconds_to_minutes(session['view_offset'])
|
||||
stream_duration = 0
|
||||
if state != 'play':
|
||||
if session['paused_counter']:
|
||||
stream_duration = int((time.time() - helpers.cast_to_float(session['started']) -
|
||||
helpers.cast_to_float(session['paused_counter'])) / 60)
|
||||
else:
|
||||
stream_duration = int((time.time() - helpers.cast_to_float(session['started'])) / 60)
|
||||
if state != 'play':
|
||||
if session['paused_counter']:
|
||||
stream_duration = int((time.time() - helpers.cast_to_float(session['started']) -
|
||||
helpers.cast_to_float(session['paused_counter'])) / 60)
|
||||
else:
|
||||
stream_duration = int((time.time() - helpers.cast_to_float(session['started'])) / 60)
|
||||
|
||||
view_offset = helpers.convert_milliseconds_to_minutes(session['view_offset'])
|
||||
user = session['friendly_name']
|
||||
platform = session['platform']
|
||||
player = session['player']
|
||||
ip_address = session['ip_address'] if session['ip_address'] else 'N/A'
|
||||
|
||||
progress_percent = helpers.get_percent(view_offset, duration)
|
||||
|
||||
available_params = {'server_name': server_name,
|
||||
'user': session['friendly_name'],
|
||||
'platform': session['platform'],
|
||||
'player': session['player'],
|
||||
'media_type': session['media_type'],
|
||||
'server_uptime': server_uptime,
|
||||
'user': user,
|
||||
'platform': platform,
|
||||
'player': player,
|
||||
'ip_address': ip_address,
|
||||
'media_type': metadata['media_type'],
|
||||
'title': full_title,
|
||||
'show_name': item_metadata['grandparent_title'],
|
||||
'episode_name': item_metadata['title'],
|
||||
'artist_name': item_metadata['grandparent_title'],
|
||||
'album_name': item_metadata['parent_title'],
|
||||
'season_num': item_metadata['parent_index'],
|
||||
'season_num00': item_metadata['parent_index'].zfill(2),
|
||||
'episode_num': item_metadata['index'],
|
||||
'episode_num00': item_metadata['index'].zfill(2),
|
||||
'show_name': metadata['grandparent_title'],
|
||||
'episode_name': metadata['title'],
|
||||
'artist_name': metadata['grandparent_title'],
|
||||
'album_name': metadata['parent_title'],
|
||||
'track_name': metadata['title'],
|
||||
'season_num': metadata['parent_index'].zfill(1),
|
||||
'season_num00': metadata['parent_index'].zfill(2),
|
||||
'episode_num': metadata['index'].zfill(1),
|
||||
'episode_num00': metadata['index'].zfill(2),
|
||||
'video_decision': video_decision,
|
||||
'audio_decision': audio_decision,
|
||||
'transcode_decision': transcode_decision,
|
||||
'year': item_metadata['year'],
|
||||
'studio': item_metadata['studio'],
|
||||
'content_rating': item_metadata['content_rating'],
|
||||
'summary': item_metadata['summary'],
|
||||
'rating': item_metadata['rating'],
|
||||
'year': metadata['year'],
|
||||
'studio': metadata['studio'],
|
||||
'content_rating': metadata['content_rating'],
|
||||
'directors': ', '.join(metadata['directors']),
|
||||
'writers': ', '.join(metadata['writers']),
|
||||
'actors': ', '.join(metadata['actors']),
|
||||
'genres': ', '.join(metadata['genres']),
|
||||
'summary': metadata['summary'],
|
||||
'tagline': metadata['tagline'],
|
||||
'rating': metadata['rating'],
|
||||
'duration': duration,
|
||||
'stream_duration': stream_duration,
|
||||
'remaining_duration': duration - view_offset,
|
||||
@@ -489,12 +589,106 @@ def build_notify_text(session, state):
|
||||
except:
|
||||
logger.error(u"PlexPy Notifier :: Unable to parse custom notification body. Using fallback.")
|
||||
|
||||
return [subject_text, body_text]
|
||||
else:
|
||||
return [subject_text, body_text]
|
||||
elif state == 'created':
|
||||
# Default body text
|
||||
body_text = '%s was recently added to Plex.' % full_title
|
||||
|
||||
if on_created_subject and on_created_body:
|
||||
try:
|
||||
subject_text = unicode(on_created_subject).format(**available_params)
|
||||
except LookupError, e:
|
||||
logger.error(u"PlexPy Notifier :: Unable to parse field %s in notification subject. Using fallback." % e)
|
||||
except:
|
||||
logger.error(u"PlexPy Notifier :: Unable to parse custom notification subject. Using fallback.")
|
||||
|
||||
try:
|
||||
body_text = unicode(on_created_body).format(**available_params)
|
||||
except LookupError, e:
|
||||
logger.error(u"PlexPy Notifier :: Unable to parse field %s in notification body. Using fallback." % e)
|
||||
except:
|
||||
logger.error(u"PlexPy Notifier :: Unable to parse custom notification body. Using fallback.")
|
||||
|
||||
return [subject_text, body_text]
|
||||
else:
|
||||
return [subject_text, body_text]
|
||||
else:
|
||||
return None
|
||||
|
||||
def build_server_notify_text(state=None):
|
||||
# Get the server name
|
||||
server_name = plexpy.CONFIG.PMS_NAME
|
||||
|
||||
# Get the server uptime
|
||||
plex_tv = plextv.PlexTV()
|
||||
server_times = plex_tv.get_server_times()
|
||||
|
||||
if server_times:
|
||||
updated_at = server_times[0]['updated_at']
|
||||
server_uptime = helpers.human_duration(int(time.time() - helpers.cast_to_float(updated_at)))
|
||||
else:
|
||||
logger.error(u"PlexPy Notifier :: Unable to retrieve server uptime.")
|
||||
server_uptime = 'N/A'
|
||||
|
||||
on_extdown_subject = plexpy.CONFIG.NOTIFY_ON_EXTDOWN_SUBJECT_TEXT
|
||||
on_extdown_body = plexpy.CONFIG.NOTIFY_ON_EXTDOWN_BODY_TEXT
|
||||
on_intdown_subject = plexpy.CONFIG.NOTIFY_ON_INTDOWN_SUBJECT_TEXT
|
||||
on_intdown_body = plexpy.CONFIG.NOTIFY_ON_INTDOWN_BODY_TEXT
|
||||
|
||||
available_params = {'server_name': server_name,
|
||||
'server_uptime': server_uptime}
|
||||
|
||||
# Default text
|
||||
subject_text = 'PlexPy (%s)' % server_name
|
||||
|
||||
if state == 'extdown':
|
||||
# Default body text
|
||||
body_text = 'The Plex Media Server remote access is down.'
|
||||
|
||||
if on_extdown_subject and on_extdown_body:
|
||||
try:
|
||||
subject_text = unicode(on_extdown_subject).format(**available_params)
|
||||
except LookupError, e:
|
||||
logger.error(u"PlexPy Notifier :: Unable to parse field %s in notification subject. Using fallback." % e)
|
||||
except:
|
||||
logger.error(u"PlexPy Notifier :: Unable to parse custom notification subject. Using fallback.")
|
||||
|
||||
try:
|
||||
body_text = unicode(on_extdown_body).format(**available_params)
|
||||
except LookupError, e:
|
||||
logger.error(u"PlexPy Notifier :: Unable to parse field %s in notification body. Using fallback." % e)
|
||||
except:
|
||||
logger.error(u"PlexPy Notifier :: Unable to parse custom notification body. Using fallback.")
|
||||
|
||||
return [subject_text, body_text]
|
||||
else:
|
||||
return [subject_text, body_text]
|
||||
elif state == 'intdown':
|
||||
# Default body text
|
||||
body_text = 'The Plex Media Server is down.'
|
||||
|
||||
if on_intdown_subject and on_intdown_body:
|
||||
try:
|
||||
subject_text = unicode(on_intdown_subject).format(**available_params)
|
||||
except LookupError, e:
|
||||
logger.error(u"PlexPy Notifier :: Unable to parse field %s in notification subject. Using fallback." % e)
|
||||
except:
|
||||
logger.error(u"PlexPy Notifier :: Unable to parse custom notification subject. Using fallback.")
|
||||
|
||||
try:
|
||||
body_text = unicode(on_intdown_body).format(**available_params)
|
||||
except LookupError, e:
|
||||
logger.error(u"PlexPy Notifier :: Unable to parse field %s in notification body. Using fallback." % e)
|
||||
except:
|
||||
logger.error(u"PlexPy Notifier :: Unable to parse custom notification body. Using fallback.")
|
||||
|
||||
return [subject_text, body_text]
|
||||
else:
|
||||
return [subject_text, body_text]
|
||||
else:
|
||||
return None
|
||||
|
||||
def strip_tag(data):
|
||||
import re
|
||||
|
@@ -50,7 +50,9 @@ AGENT_IDS = {"Growl": 0,
|
||||
"OSX Notify": 8,
|
||||
"Boxcar2": 9,
|
||||
"Email": 10,
|
||||
"Twitter": 11}
|
||||
"Twitter": 11,
|
||||
"IFTTT": 12,
|
||||
"Telegram": 13}
|
||||
|
||||
def available_notification_agents():
|
||||
agents = [{'name': 'Growl',
|
||||
@@ -63,7 +65,10 @@ def available_notification_agents():
|
||||
'on_pause': plexpy.CONFIG.GROWL_ON_PAUSE,
|
||||
'on_resume': plexpy.CONFIG.GROWL_ON_RESUME,
|
||||
'on_buffer': plexpy.CONFIG.GROWL_ON_BUFFER,
|
||||
'on_watched': plexpy.CONFIG.GROWL_ON_WATCHED
|
||||
'on_watched': plexpy.CONFIG.GROWL_ON_WATCHED,
|
||||
'on_created': plexpy.CONFIG.GROWL_ON_CREATED,
|
||||
'on_extdown': plexpy.CONFIG.GROWL_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.GROWL_ON_INTDOWN
|
||||
},
|
||||
{'name': 'Prowl',
|
||||
'id': AGENT_IDS['Prowl'],
|
||||
@@ -75,7 +80,10 @@ def available_notification_agents():
|
||||
'on_pause': plexpy.CONFIG.PROWL_ON_PAUSE,
|
||||
'on_resume': plexpy.CONFIG.PROWL_ON_RESUME,
|
||||
'on_buffer': plexpy.CONFIG.PROWL_ON_BUFFER,
|
||||
'on_watched': plexpy.CONFIG.PROWL_ON_WATCHED
|
||||
'on_watched': plexpy.CONFIG.PROWL_ON_WATCHED,
|
||||
'on_created': plexpy.CONFIG.PROWL_ON_CREATED,
|
||||
'on_extdown': plexpy.CONFIG.PROWL_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.PROWL_ON_INTDOWN
|
||||
},
|
||||
{'name': 'XBMC',
|
||||
'id': AGENT_IDS['XBMC'],
|
||||
@@ -87,7 +95,10 @@ def available_notification_agents():
|
||||
'on_pause': plexpy.CONFIG.XBMC_ON_PAUSE,
|
||||
'on_resume': plexpy.CONFIG.XBMC_ON_RESUME,
|
||||
'on_buffer': plexpy.CONFIG.XBMC_ON_BUFFER,
|
||||
'on_watched': plexpy.CONFIG.XBMC_ON_WATCHED
|
||||
'on_watched': plexpy.CONFIG.XBMC_ON_WATCHED,
|
||||
'on_created': plexpy.CONFIG.XBMC_ON_CREATED,
|
||||
'on_extdown': plexpy.CONFIG.XBMC_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.XBMC_ON_INTDOWN
|
||||
},
|
||||
{'name': 'Plex',
|
||||
'id': AGENT_IDS['Plex'],
|
||||
@@ -99,7 +110,10 @@ def available_notification_agents():
|
||||
'on_pause': plexpy.CONFIG.PLEX_ON_PAUSE,
|
||||
'on_resume': plexpy.CONFIG.PLEX_ON_RESUME,
|
||||
'on_buffer': plexpy.CONFIG.PLEX_ON_BUFFER,
|
||||
'on_watched': plexpy.CONFIG.PLEX_ON_WATCHED
|
||||
'on_watched': plexpy.CONFIG.PLEX_ON_WATCHED,
|
||||
'on_created': plexpy.CONFIG.PLEX_ON_CREATED,
|
||||
'on_extdown': plexpy.CONFIG.PLEX_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.PLEX_ON_INTDOWN
|
||||
},
|
||||
{'name': 'NotifyMyAndroid',
|
||||
'id': AGENT_IDS['NMA'],
|
||||
@@ -111,7 +125,10 @@ def available_notification_agents():
|
||||
'on_pause': plexpy.CONFIG.NMA_ON_PAUSE,
|
||||
'on_resume': plexpy.CONFIG.NMA_ON_RESUME,
|
||||
'on_buffer': plexpy.CONFIG.NMA_ON_BUFFER,
|
||||
'on_watched': plexpy.CONFIG.NMA_ON_WATCHED
|
||||
'on_watched': plexpy.CONFIG.NMA_ON_WATCHED,
|
||||
'on_created': plexpy.CONFIG.NMA_ON_CREATED,
|
||||
'on_extdown': plexpy.CONFIG.NMA_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.NMA_ON_INTDOWN
|
||||
},
|
||||
{'name': 'Pushalot',
|
||||
'id': AGENT_IDS['Pushalot'],
|
||||
@@ -123,7 +140,10 @@ def available_notification_agents():
|
||||
'on_pause': plexpy.CONFIG.PUSHALOT_ON_PAUSE,
|
||||
'on_resume': plexpy.CONFIG.PUSHALOT_ON_RESUME,
|
||||
'on_buffer': plexpy.CONFIG.PUSHALOT_ON_BUFFER,
|
||||
'on_watched': plexpy.CONFIG.PUSHALOT_ON_WATCHED
|
||||
'on_watched': plexpy.CONFIG.PUSHALOT_ON_WATCHED,
|
||||
'on_created': plexpy.CONFIG.PUSHALOT_ON_CREATED,
|
||||
'on_extdown': plexpy.CONFIG.PUSHALOT_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.PUSHALOT_ON_INTDOWN
|
||||
},
|
||||
{'name': 'Pushbullet',
|
||||
'id': AGENT_IDS['Pushbullet'],
|
||||
@@ -135,7 +155,10 @@ def available_notification_agents():
|
||||
'on_pause': plexpy.CONFIG.PUSHBULLET_ON_PAUSE,
|
||||
'on_resume': plexpy.CONFIG.PUSHBULLET_ON_RESUME,
|
||||
'on_buffer': plexpy.CONFIG.PUSHBULLET_ON_BUFFER,
|
||||
'on_watched': plexpy.CONFIG.PUSHBULLET_ON_WATCHED
|
||||
'on_watched': plexpy.CONFIG.PUSHBULLET_ON_WATCHED,
|
||||
'on_created': plexpy.CONFIG.PUSHBULLET_ON_CREATED,
|
||||
'on_extdown': plexpy.CONFIG.PUSHBULLET_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.PUSHBULLET_ON_INTDOWN
|
||||
},
|
||||
{'name': 'Pushover',
|
||||
'id': AGENT_IDS['Pushover'],
|
||||
@@ -147,7 +170,10 @@ def available_notification_agents():
|
||||
'on_pause': plexpy.CONFIG.PUSHOVER_ON_PAUSE,
|
||||
'on_resume': plexpy.CONFIG.PUSHOVER_ON_RESUME,
|
||||
'on_buffer': plexpy.CONFIG.PUSHOVER_ON_BUFFER,
|
||||
'on_watched': plexpy.CONFIG.PUSHOVER_ON_WATCHED
|
||||
'on_watched': plexpy.CONFIG.PUSHOVER_ON_WATCHED,
|
||||
'on_created': plexpy.CONFIG.PUSHOVER_ON_CREATED,
|
||||
'on_extdown': plexpy.CONFIG.PUSHOVER_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.PUSHOVER_ON_INTDOWN
|
||||
},
|
||||
{'name': 'Boxcar2',
|
||||
'id': AGENT_IDS['Boxcar2'],
|
||||
@@ -159,7 +185,10 @@ def available_notification_agents():
|
||||
'on_pause': plexpy.CONFIG.BOXCAR_ON_PAUSE,
|
||||
'on_resume': plexpy.CONFIG.BOXCAR_ON_RESUME,
|
||||
'on_buffer': plexpy.CONFIG.BOXCAR_ON_BUFFER,
|
||||
'on_watched': plexpy.CONFIG.BOXCAR_ON_WATCHED
|
||||
'on_watched': plexpy.CONFIG.BOXCAR_ON_WATCHED,
|
||||
'on_created': plexpy.CONFIG.BOXCAR_ON_CREATED,
|
||||
'on_extdown': plexpy.CONFIG.BOXCAR_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.BOXCAR_ON_INTDOWN
|
||||
},
|
||||
{'name': 'E-mail',
|
||||
'id': AGENT_IDS['Email'],
|
||||
@@ -171,7 +200,10 @@ def available_notification_agents():
|
||||
'on_pause': plexpy.CONFIG.EMAIL_ON_PAUSE,
|
||||
'on_resume': plexpy.CONFIG.EMAIL_ON_RESUME,
|
||||
'on_buffer': plexpy.CONFIG.EMAIL_ON_BUFFER,
|
||||
'on_watched': plexpy.CONFIG.EMAIL_ON_WATCHED
|
||||
'on_watched': plexpy.CONFIG.EMAIL_ON_WATCHED,
|
||||
'on_created': plexpy.CONFIG.EMAIL_ON_CREATED,
|
||||
'on_extdown': plexpy.CONFIG.EMAIL_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.EMAIL_ON_INTDOWN
|
||||
},
|
||||
{'name': 'Twitter',
|
||||
'id': AGENT_IDS['Twitter'],
|
||||
@@ -183,7 +215,40 @@ def available_notification_agents():
|
||||
'on_pause': plexpy.CONFIG.TWITTER_ON_PAUSE,
|
||||
'on_resume': plexpy.CONFIG.TWITTER_ON_RESUME,
|
||||
'on_buffer': plexpy.CONFIG.TWITTER_ON_BUFFER,
|
||||
'on_watched': plexpy.CONFIG.TWITTER_ON_WATCHED
|
||||
'on_watched': plexpy.CONFIG.TWITTER_ON_WATCHED,
|
||||
'on_created': plexpy.CONFIG.TWITTER_ON_CREATED,
|
||||
'on_extdown': plexpy.CONFIG.TWITTER_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.TWITTER_ON_INTDOWN
|
||||
},
|
||||
{'name': 'IFTTT',
|
||||
'id': AGENT_IDS['IFTTT'],
|
||||
'config_prefix': 'ifttt',
|
||||
'has_config': True,
|
||||
'state': checked(plexpy.CONFIG.IFTTT_ENABLED),
|
||||
'on_play': plexpy.CONFIG.IFTTT_ON_PLAY,
|
||||
'on_stop': plexpy.CONFIG.IFTTT_ON_STOP,
|
||||
'on_pause': plexpy.CONFIG.IFTTT_ON_PAUSE,
|
||||
'on_resume': plexpy.CONFIG.IFTTT_ON_RESUME,
|
||||
'on_buffer': plexpy.CONFIG.IFTTT_ON_BUFFER,
|
||||
'on_watched': plexpy.CONFIG.IFTTT_ON_WATCHED,
|
||||
'on_created': plexpy.CONFIG.IFTTT_ON_CREATED,
|
||||
'on_extdown': plexpy.CONFIG.IFTTT_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.IFTTT_ON_INTDOWN
|
||||
},
|
||||
{'name': 'Telegram',
|
||||
'id': AGENT_IDS['Telegram'],
|
||||
'config_prefix': 'telegram',
|
||||
'has_config': True,
|
||||
'state': checked(plexpy.CONFIG.TELEGRAM_ENABLED),
|
||||
'on_play': plexpy.CONFIG.TELEGRAM_ON_PLAY,
|
||||
'on_stop': plexpy.CONFIG.TELEGRAM_ON_STOP,
|
||||
'on_pause': plexpy.CONFIG.TELEGRAM_ON_PAUSE,
|
||||
'on_resume': plexpy.CONFIG.TELEGRAM_ON_RESUME,
|
||||
'on_buffer': plexpy.CONFIG.TELEGRAM_ON_BUFFER,
|
||||
'on_watched': plexpy.CONFIG.TELEGRAM_ON_WATCHED,
|
||||
'on_created': plexpy.CONFIG.TELEGRAM_ON_CREATED,
|
||||
'on_extdown': plexpy.CONFIG.TELEGRAM_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.TELEGRAM_ON_INTDOWN
|
||||
}
|
||||
]
|
||||
|
||||
@@ -200,7 +265,10 @@ def available_notification_agents():
|
||||
'on_pause': plexpy.CONFIG.OSX_NOTIFY_ON_PAUSE,
|
||||
'on_resume': plexpy.CONFIG.OSX_NOTIFY_ON_RESUME,
|
||||
'on_buffer': plexpy.CONFIG.OSX_NOTIFY_ON_BUFFER,
|
||||
'on_watched': plexpy.CONFIG.OSX_NOTIFY_ON_WATCHED
|
||||
'on_watched': plexpy.CONFIG.OSX_NOTIFY_ON_WATCHED,
|
||||
'on_created': plexpy.CONFIG.OSX_NOTIFY_ON_CREATED,
|
||||
'on_extdown': plexpy.CONFIG.OSX_NOTIFY_ON_EXTDOWN,
|
||||
'on_intdown': plexpy.CONFIG.OSX_NOTIFY_ON_INTDOWN
|
||||
})
|
||||
|
||||
return agents
|
||||
@@ -245,6 +313,12 @@ def get_notification_agent_config(config_id):
|
||||
elif config_id == 11:
|
||||
tweet = TwitterNotifier()
|
||||
return tweet.return_config_options()
|
||||
elif config_id == 12:
|
||||
iftttClient = IFTTT()
|
||||
return iftttClient.return_config_options()
|
||||
elif config_id == 13:
|
||||
telegramClient = TELEGRAM()
|
||||
return telegramClient.return_config_options()
|
||||
else:
|
||||
return []
|
||||
else:
|
||||
@@ -290,6 +364,12 @@ def send_notification(config_id, subject, body):
|
||||
elif config_id == 11:
|
||||
tweet = TwitterNotifier()
|
||||
tweet.notify(subject=subject, message=body)
|
||||
elif config_id == 12:
|
||||
iftttClient = IFTTT()
|
||||
iftttClient.notify(subject=subject, message=body)
|
||||
elif config_id == 13:
|
||||
telegramClient = TELEGRAM()
|
||||
telegramClient.notify(message=body, event=subject)
|
||||
else:
|
||||
logger.debug(u"PlexPy Notifier :: Unknown agent id received.")
|
||||
else:
|
||||
@@ -425,7 +505,7 @@ class PROWL(object):
|
||||
|
||||
data = {'apikey': plexpy.CONFIG.PROWL_KEYS,
|
||||
'application': 'PlexPy',
|
||||
'event': event,
|
||||
'event': event.encode("utf-8"),
|
||||
'description': message.encode("utf-8"),
|
||||
'priority': plexpy.CONFIG.PROWL_PRIORITY}
|
||||
|
||||
@@ -799,9 +879,9 @@ class PUSHALOT(object):
|
||||
|
||||
pushalot_authorizationtoken = plexpy.CONFIG.PUSHALOT_APIKEY
|
||||
|
||||
logger.debug(u"Pushalot event: " + event)
|
||||
logger.debug(u"Pushalot message: " + message)
|
||||
logger.debug(u"Pushalot api: " + pushalot_authorizationtoken)
|
||||
#logger.debug(u"Pushalot event: " + event)
|
||||
#logger.debug(u"Pushalot message: " + message)
|
||||
#logger.debug(u"Pushalot api: " + pushalot_authorizationtoken)
|
||||
|
||||
http_handler = HTTPSConnection("pushalot.com")
|
||||
|
||||
@@ -816,9 +896,9 @@ class PUSHALOT(object):
|
||||
response = http_handler.getresponse()
|
||||
request_status = response.status
|
||||
|
||||
logger.debug(u"Pushalot response status: %r" % request_status)
|
||||
logger.debug(u"Pushalot response headers: %r" % response.getheaders())
|
||||
logger.debug(u"Pushalot response body: %r" % response.read())
|
||||
#logger.debug(u"Pushalot response status: %r" % request_status)
|
||||
#logger.debug(u"Pushalot response headers: %r" % response.getheaders())
|
||||
#logger.debug(u"Pushalot response body: %r" % response.read())
|
||||
|
||||
if request_status == 200:
|
||||
logger.info(u"Pushalot notifications sent.")
|
||||
@@ -868,7 +948,7 @@ class PUSHOVER(object):
|
||||
|
||||
data = {'token': self.application_token,
|
||||
'user': plexpy.CONFIG.PUSHOVER_KEYS,
|
||||
'title': event,
|
||||
'title': event.encode("utf-8"),
|
||||
'message': message.encode("utf-8"),
|
||||
'sound': plexpy.CONFIG.PUSHOVER_SOUND,
|
||||
'priority': plexpy.CONFIG.PUSHOVER_PRIORITY}
|
||||
@@ -924,10 +1004,10 @@ class PUSHOVER(object):
|
||||
return {'': ''}
|
||||
|
||||
def return_config_options(self):
|
||||
config_option = [{'label': 'Pushover User Key',
|
||||
config_option = [{'label': 'Pushover User or Group Key',
|
||||
'value': self.keys,
|
||||
'name': 'pushover_keys',
|
||||
'description': 'Your Pushover user key.',
|
||||
'description': 'Your Pushover user or group key.',
|
||||
'input_type': 'text'
|
||||
},
|
||||
{'label': 'Priority',
|
||||
@@ -1268,6 +1348,8 @@ class Email(object):
|
||||
|
||||
mailserver.sendmail(plexpy.CONFIG.EMAIL_FROM, plexpy.CONFIG.EMAIL_TO, message.as_string())
|
||||
mailserver.quit()
|
||||
|
||||
logger.info(u"Email notifications sent.")
|
||||
return True
|
||||
|
||||
except Exception, e:
|
||||
@@ -1319,4 +1401,134 @@ class Email(object):
|
||||
}
|
||||
]
|
||||
|
||||
return config_option
|
||||
return config_option
|
||||
|
||||
|
||||
class IFTTT(object):
|
||||
|
||||
def __init__(self):
|
||||
self.apikey = plexpy.CONFIG.IFTTT_KEY
|
||||
self.event = plexpy.CONFIG.IFTTT_EVENT
|
||||
|
||||
def notify(self, message, subject):
|
||||
if not message or not subject:
|
||||
return
|
||||
|
||||
http_handler = HTTPSConnection("maker.ifttt.com")
|
||||
|
||||
data = {'value1': subject.encode("utf-8"),
|
||||
'value2': message.encode("utf-8")}
|
||||
|
||||
# logger.debug("Ifttt SENDING: %s" % json.dumps(data))
|
||||
|
||||
http_handler.request("POST",
|
||||
"/trigger/%s/with/key/%s" % (self.event, self.apikey),
|
||||
headers={'Content-type': "application/json"},
|
||||
body=json.dumps(data))
|
||||
response = http_handler.getresponse()
|
||||
request_status = response.status
|
||||
# logger.debug(u"Ifttt response status: %r" % request_status)
|
||||
# logger.debug(u"Ifttt response headers: %r" % response.getheaders())
|
||||
# logger.debug(u"Ifttt response body: %r" % response.read())
|
||||
|
||||
if request_status == 200:
|
||||
logger.info(u"Ifttt notifications sent.")
|
||||
return True
|
||||
elif request_status >= 400 and request_status < 500:
|
||||
logger.info(u"Ifttt request failed: %s" % response.reason)
|
||||
return False
|
||||
else:
|
||||
logger.info(u"Ifttt notification failed serverside.")
|
||||
return False
|
||||
|
||||
def test(self):
|
||||
return self.notify('PlexPy', 'Test Message')
|
||||
|
||||
def return_config_options(self):
|
||||
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>.',
|
||||
'input_type': 'text'
|
||||
},
|
||||
{'label': 'Ifttt Event',
|
||||
'value': self.event,
|
||||
'name': 'ifttt_event',
|
||||
'description': 'The Ifttt maker event to fire. The notification subject and body will be sent'
|
||||
' as value1 and value2 respectively.',
|
||||
'input_type': 'text'
|
||||
},
|
||||
{'label': 'Test Event',
|
||||
'value': 'Test Event',
|
||||
'name': 'testIFTTT',
|
||||
'description': 'Test if IFTTT notifications are working. See logs for troubleshooting.',
|
||||
'input_type': 'button'
|
||||
}
|
||||
]
|
||||
|
||||
return config_option
|
||||
|
||||
class TELEGRAM(object):
|
||||
|
||||
def __init__(self):
|
||||
self.enabled = plexpy.CONFIG.TELEGRAM_ENABLED
|
||||
self.bot_token = plexpy.CONFIG.TELEGRAM_BOT_TOKEN
|
||||
self.chat_id = plexpy.CONFIG.TELEGRAM_CHAT_ID
|
||||
|
||||
def conf(self, options):
|
||||
return cherrypy.config['config'].get('Telegram', options)
|
||||
|
||||
def notify(self, message, event):
|
||||
if not message or not event:
|
||||
return
|
||||
|
||||
http_handler = HTTPSConnection("api.telegram.org")
|
||||
|
||||
data = {'chat_id': self.chat_id,
|
||||
'text': event.encode('utf-8') + ': ' + message.encode("utf-8")}
|
||||
|
||||
http_handler.request("POST",
|
||||
"/bot%s/%s" % (self.bot_token, "sendMessage"),
|
||||
headers={'Content-type': "application/x-www-form-urlencoded"},
|
||||
body=urlencode(data))
|
||||
|
||||
response = http_handler.getresponse()
|
||||
request_status = response.status
|
||||
|
||||
if request_status == 200:
|
||||
logger.info(u"Telegram notifications sent.")
|
||||
return True
|
||||
elif request_status >= 400 and request_status < 500:
|
||||
logger.info(u"Telegram request failed: %s" % response.reason)
|
||||
return False
|
||||
else:
|
||||
logger.info(u"Telegram notification failed serverside.")
|
||||
return False
|
||||
|
||||
def updateLibrary(self):
|
||||
#For uniformity reasons not removed
|
||||
return
|
||||
|
||||
def test(self, bot_token, chat_id):
|
||||
self.enabled = True
|
||||
self.bot_token = bot_token
|
||||
self.chat_id = chat_id
|
||||
|
||||
self.notify('Main Screen Activate', 'Test Message')
|
||||
|
||||
def return_config_options(self):
|
||||
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.',
|
||||
'input_type': 'text'
|
||||
},
|
||||
{'label': 'Telegram Chat ID',
|
||||
'value': self.chat_id,
|
||||
'name': 'telegram_chat_id',
|
||||
'description': 'Your Telegram Chat ID, Group ID, or channel username. Contact <a href="http://telegram.me/myidbot" target="_blank">@myidbot</a> on Telegram to get an ID.',
|
||||
'input_type': 'text'
|
||||
}
|
||||
]
|
||||
|
||||
return config_option
|
||||
|
@@ -444,3 +444,22 @@ class PlexTV(object):
|
||||
return clean_servers
|
||||
|
||||
return json.dumps(clean_servers, indent=4)
|
||||
|
||||
def get_server_times(self):
|
||||
servers = self.get_plextv_server_list(output_format='xml')
|
||||
server_times = []
|
||||
|
||||
try:
|
||||
xml_head = servers.getElementsByTagName('Server')
|
||||
except:
|
||||
logger.warn("Error parsing XML for Plex servers.")
|
||||
return []
|
||||
|
||||
for a in xml_head:
|
||||
if helpers.get_xml_attr(a, 'machineIdentifier') == plexpy.CONFIG.PMS_IDENTIFIER:
|
||||
server_times.append({"created_at": helpers.get_xml_attr(a, 'createdAt'),
|
||||
"updated_at": helpers.get_xml_attr(a, 'updatedAt')
|
||||
})
|
||||
break
|
||||
|
||||
return server_times
|
@@ -1,4 +1,4 @@
|
||||
# This file is part of PlexPy.
|
||||
# This file is part of PlexPy.
|
||||
#
|
||||
# PlexPy is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -247,6 +247,8 @@ def import_from_plexwatch(database=None, table_name=None, import_ignore_interval
|
||||
logger.debug(u"PlexPy Importer :: Disabling monitoring while import in progress.")
|
||||
plexpy.schedule_job(activity_pinger.check_active_sessions, 'Check for active sessions',
|
||||
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)
|
||||
|
||||
ap = activity_processor.ActivityProcessor()
|
||||
user_data = users.Users()
|
||||
@@ -290,10 +292,15 @@ def import_from_plexwatch(database=None, table_name=None, import_ignore_interval
|
||||
|
||||
# If we get back None from our xml extractor skip over the record and log error.
|
||||
if not extracted_xml:
|
||||
logger.error(u"PlexPy Importer :: Skipping line with ratingKey %s due to malformed xml."
|
||||
logger.error(u"PlexPy Importer :: Skipping record with ratingKey %s due to malformed xml."
|
||||
% str(row['rating_key']))
|
||||
continue
|
||||
|
||||
# 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.")
|
||||
continue
|
||||
|
||||
# If the user_id no longer exists in the friends list, pull it from the xml.
|
||||
if user_data.get_user_id(user=row['user']):
|
||||
user_id = user_data.get_user_id(user=row['user'])
|
||||
|
@@ -13,12 +13,29 @@
|
||||
# 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, helpers, users, http_handler
|
||||
from plexpy import logger, helpers, users, http_handler, common
|
||||
from urlparse import urlparse
|
||||
|
||||
import plexpy
|
||||
import urllib2
|
||||
|
||||
def get_server_friendly_name():
|
||||
logger.info("Requesting name from server...")
|
||||
server_name = PmsConnect().get_server_pref(pref='FriendlyName')
|
||||
|
||||
# If friendly name is blank
|
||||
if not server_name:
|
||||
servers_info = PmsConnect().get_servers_info()
|
||||
for server in servers_info:
|
||||
if server['machine_identifier'] == plexpy.CONFIG.PMS_IDENTIFIER:
|
||||
server_name = server['name']
|
||||
break
|
||||
|
||||
if server_name and server_name != plexpy.CONFIG.PMS_NAME:
|
||||
plexpy.CONFIG.__setattr__('PMS_NAME', server_name)
|
||||
plexpy.CONFIG.write()
|
||||
|
||||
return server_name
|
||||
|
||||
class PmsConnect(object):
|
||||
"""
|
||||
@@ -254,6 +271,36 @@ class PmsConnect(object):
|
||||
return request
|
||||
|
||||
"""
|
||||
Return account details.
|
||||
|
||||
Optional parameters: output_format { dict, json }
|
||||
|
||||
Output: array
|
||||
"""
|
||||
def get_account(self, output_format=''):
|
||||
uri = '/myplex/account'
|
||||
request = self.request_handler.make_request(uri=uri,
|
||||
proto=self.protocol,
|
||||
request_type='GET',
|
||||
output_format=output_format)
|
||||
|
||||
return request
|
||||
|
||||
"""
|
||||
Refresh Plex remote access port mapping.
|
||||
|
||||
Optional parameters: None
|
||||
|
||||
Output: None
|
||||
"""
|
||||
def put_refresh_reachability(self):
|
||||
uri = '/myplex/refreshReachability'
|
||||
request = self.request_handler.make_request(uri=uri,
|
||||
proto=self.protocol,
|
||||
request_type='PUT')
|
||||
|
||||
return request
|
||||
"""
|
||||
Return processed and validated list of recently added items.
|
||||
|
||||
Parameters required: count { number of results to return }
|
||||
@@ -281,10 +328,13 @@ class PmsConnect(object):
|
||||
recents_main = a.getElementsByTagName('Directory')
|
||||
for item in recents_main:
|
||||
recent_type = helpers.get_xml_attr(item, 'type')
|
||||
recent_items = {'type': recent_type,
|
||||
recent_items = {'media_type': recent_type,
|
||||
'rating_key': helpers.get_xml_attr(item, 'ratingKey'),
|
||||
'parent_rating_key': helpers.get_xml_attr(item, 'parentRatingKey'),
|
||||
'title': helpers.get_xml_attr(item, 'title'),
|
||||
'parent_title': helpers.get_xml_attr(item, 'parentTitle'),
|
||||
'library_id': helpers.get_xml_attr(item, 'librarySectionID'),
|
||||
'library_title': helpers.get_xml_attr(item, 'librarySectionTitle'),
|
||||
'thumb': helpers.get_xml_attr(item, 'thumb'),
|
||||
'added_at': helpers.get_xml_attr(item, 'addedAt')
|
||||
}
|
||||
@@ -296,10 +346,12 @@ class PmsConnect(object):
|
||||
recent_type = helpers.get_xml_attr(item, 'type')
|
||||
|
||||
if recent_type == 'movie':
|
||||
recent_items = {'type': recent_type,
|
||||
recent_items = {'media_type': recent_type,
|
||||
'rating_key': helpers.get_xml_attr(item, 'ratingKey'),
|
||||
'title': helpers.get_xml_attr(item, 'title'),
|
||||
'parent_title': helpers.get_xml_attr(item, 'parentTitle'),
|
||||
'library_id': helpers.get_xml_attr(item, 'librarySectionID'),
|
||||
'library_title': helpers.get_xml_attr(item, 'librarySectionTitle'),
|
||||
'year': helpers.get_xml_attr(item, 'year'),
|
||||
'thumb': helpers.get_xml_attr(item, 'thumb'),
|
||||
'added_at': helpers.get_xml_attr(item, 'addedAt')
|
||||
@@ -369,7 +421,7 @@ class PmsConnect(object):
|
||||
directors.append(helpers.get_xml_attr(director, 'tag'))
|
||||
|
||||
if metadata_type == 'show':
|
||||
metadata = {'type': metadata_type,
|
||||
metadata = {'media_type': metadata_type,
|
||||
'rating_key': helpers.get_xml_attr(metadata_main, 'ratingKey'),
|
||||
'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'),
|
||||
'parent_index': helpers.get_xml_attr(metadata_main, 'parentIndex'),
|
||||
@@ -401,16 +453,16 @@ class PmsConnect(object):
|
||||
elif metadata_type == 'season':
|
||||
parent_rating_key = helpers.get_xml_attr(metadata_main, 'parentRatingKey')
|
||||
show_details = self.get_metadata_details(parent_rating_key)
|
||||
metadata = {'type': metadata_type,
|
||||
metadata = {'media_type': metadata_type,
|
||||
'rating_key': helpers.get_xml_attr(metadata_main, 'ratingKey'),
|
||||
'parent_rating_key': helpers.get_xml_attr(metadata_main, 'parentRatingKey'),
|
||||
'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'),
|
||||
'parent_index': helpers.get_xml_attr(metadata_main, 'parentIndex'),
|
||||
'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'),
|
||||
'index': helpers.get_xml_attr(metadata_main, 'index'),
|
||||
'studio': helpers.get_xml_attr(metadata_main, 'studio'),
|
||||
'studio': show_details['metadata']['studio'],
|
||||
'title': helpers.get_xml_attr(metadata_main, 'title'),
|
||||
'content_rating': helpers.get_xml_attr(metadata_main, 'contentRating'),
|
||||
'content_rating': show_details['metadata']['content_rating'],
|
||||
'summary': show_details['metadata']['summary'],
|
||||
'tagline': helpers.get_xml_attr(metadata_main, 'tagline'),
|
||||
'rating': helpers.get_xml_attr(metadata_main, 'rating'),
|
||||
@@ -425,14 +477,16 @@ class PmsConnect(object):
|
||||
'updated_at': helpers.get_xml_attr(metadata_main, 'updatedAt'),
|
||||
'last_viewed_at': helpers.get_xml_attr(metadata_main, 'lastViewedAt'),
|
||||
'guid': helpers.get_xml_attr(metadata_main, 'guid'),
|
||||
'genres': genres,
|
||||
'actors': actors,
|
||||
'writers': writers,
|
||||
'directors': directors
|
||||
'genres': show_details['metadata']['genres'],
|
||||
'actors': show_details['metadata']['actors'],
|
||||
'writers': show_details['metadata']['writers'],
|
||||
'directors': show_details['metadata']['directors']
|
||||
}
|
||||
metadata_list = {'metadata': metadata}
|
||||
elif metadata_type == 'episode':
|
||||
metadata = {'type': metadata_type,
|
||||
grandparent_rating_key = helpers.get_xml_attr(metadata_main, 'grandparentRatingKey')
|
||||
show_details = self.get_metadata_details(grandparent_rating_key)
|
||||
metadata = {'media_type': metadata_type,
|
||||
'rating_key': helpers.get_xml_attr(metadata_main, 'ratingKey'),
|
||||
'parent_rating_key': helpers.get_xml_attr(metadata_main, 'parentRatingKey'),
|
||||
'grandparent_rating_key': helpers.get_xml_attr(metadata_main, 'grandparentRatingKey'),
|
||||
@@ -440,7 +494,7 @@ class PmsConnect(object):
|
||||
'parent_index': helpers.get_xml_attr(metadata_main, 'parentIndex'),
|
||||
'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'),
|
||||
'index': helpers.get_xml_attr(metadata_main, 'index'),
|
||||
'studio': helpers.get_xml_attr(metadata_main, 'studio'),
|
||||
'studio': show_details['metadata']['studio'],
|
||||
'title': helpers.get_xml_attr(metadata_main, 'title'),
|
||||
'content_rating': helpers.get_xml_attr(metadata_main, 'contentRating'),
|
||||
'summary': helpers.get_xml_attr(metadata_main, 'summary'),
|
||||
@@ -457,14 +511,14 @@ class PmsConnect(object):
|
||||
'updated_at': helpers.get_xml_attr(metadata_main, 'updatedAt'),
|
||||
'last_viewed_at': helpers.get_xml_attr(metadata_main, 'lastViewedAt'),
|
||||
'guid': helpers.get_xml_attr(metadata_main, 'guid'),
|
||||
'genres': show_details['metadata']['genres'],
|
||||
'actors': show_details['metadata']['actors'],
|
||||
'writers': writers,
|
||||
'directors': directors,
|
||||
'genres': genres,
|
||||
'actors': actors
|
||||
'directors': directors
|
||||
}
|
||||
metadata_list = {'metadata': metadata}
|
||||
elif metadata_type == 'movie':
|
||||
metadata = {'type': metadata_type,
|
||||
metadata = {'media_type': metadata_type,
|
||||
'rating_key': helpers.get_xml_attr(metadata_main, 'ratingKey'),
|
||||
'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'),
|
||||
'parent_index': helpers.get_xml_attr(metadata_main, 'parentIndex'),
|
||||
@@ -494,7 +548,7 @@ class PmsConnect(object):
|
||||
}
|
||||
metadata_list = {'metadata': metadata}
|
||||
elif metadata_type == 'artist':
|
||||
metadata = {'type': metadata_type,
|
||||
metadata = {'media_type': metadata_type,
|
||||
'rating_key': helpers.get_xml_attr(metadata_main, 'ratingKey'),
|
||||
'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'),
|
||||
'parent_index': helpers.get_xml_attr(metadata_main, 'parentIndex'),
|
||||
@@ -526,7 +580,7 @@ class PmsConnect(object):
|
||||
elif metadata_type == 'album':
|
||||
parent_rating_key = helpers.get_xml_attr(metadata_main, 'parentRatingKey')
|
||||
artist_details = self.get_metadata_details(parent_rating_key)
|
||||
metadata = {'type': metadata_type,
|
||||
metadata = {'media_type': metadata_type,
|
||||
'rating_key': helpers.get_xml_attr(metadata_main, 'ratingKey'),
|
||||
'parent_rating_key': helpers.get_xml_attr(metadata_main, 'parentRatingKey'),
|
||||
'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'),
|
||||
@@ -559,7 +613,7 @@ class PmsConnect(object):
|
||||
elif metadata_type == 'track':
|
||||
parent_rating_key = helpers.get_xml_attr(metadata_main, 'parentRatingKey')
|
||||
album_details = self.get_metadata_details(parent_rating_key)
|
||||
metadata = {'type': metadata_type,
|
||||
metadata = {'media_type': metadata_type,
|
||||
'rating_key': helpers.get_xml_attr(metadata_main, 'ratingKey'),
|
||||
'parent_rating_key': helpers.get_xml_attr(metadata_main, 'parentRatingKey'),
|
||||
'grandparent_rating_key': helpers.get_xml_attr(metadata_main, 'grandparentRatingKey'),
|
||||
@@ -584,7 +638,7 @@ class PmsConnect(object):
|
||||
'updated_at': helpers.get_xml_attr(metadata_main, 'updatedAt'),
|
||||
'last_viewed_at': helpers.get_xml_attr(metadata_main, 'lastViewedAt'),
|
||||
'guid': helpers.get_xml_attr(metadata_main, 'guid'),
|
||||
'genres': genres,
|
||||
'genres': album_details['metadata']['genres'],
|
||||
'actors': actors,
|
||||
'writers': writers,
|
||||
'directors': directors
|
||||
@@ -595,6 +649,49 @@ class PmsConnect(object):
|
||||
|
||||
return metadata_list
|
||||
|
||||
"""
|
||||
Return processed and validated metadata list for all children of requested item.
|
||||
|
||||
Parameters required: rating_key { Plex ratingKey }
|
||||
|
||||
Output: array
|
||||
"""
|
||||
def get_metadata_children_details(self, rating_key=''):
|
||||
metadata = self.get_metadata_children(str(rating_key), output_format='xml')
|
||||
|
||||
try:
|
||||
xml_head = metadata.getElementsByTagName('MediaContainer')
|
||||
except:
|
||||
logger.warn("Unable to parse XML for get_metadata_children.")
|
||||
return []
|
||||
|
||||
metadata_list = []
|
||||
|
||||
for a in xml_head:
|
||||
if a.getAttribute('size'):
|
||||
if a.getAttribute('size') == '0':
|
||||
metadata_list = {'metadata': None}
|
||||
return metadata_list
|
||||
|
||||
if a.getElementsByTagName('Video'):
|
||||
metadata_main = a.getElementsByTagName('Video')
|
||||
for item in metadata_main:
|
||||
child_rating_key = helpers.get_xml_attr(item, 'ratingKey')
|
||||
metadata = self.get_metadata_details(str(child_rating_key))
|
||||
if metadata:
|
||||
metadata_list.append(metadata['metadata'])
|
||||
|
||||
elif a.getElementsByTagName('Track'):
|
||||
metadata_main = a.getElementsByTagName('Track')
|
||||
for item in metadata_main:
|
||||
child_rating_key = helpers.get_xml_attr(item, 'ratingKey')
|
||||
metadata = self.get_metadata_details(str(child_rating_key))
|
||||
if metadata:
|
||||
metadata_list.append(metadata['metadata'])
|
||||
|
||||
output = {'metadata': metadata_list}
|
||||
return output
|
||||
|
||||
"""
|
||||
Return processed and validated session list.
|
||||
|
||||
@@ -706,6 +803,7 @@ class PmsConnect(object):
|
||||
'user_id': user_details['user_id'],
|
||||
'friendly_name': user_details['friendly_name'],
|
||||
'user_thumb': user_details['thumb'],
|
||||
'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split(':')[-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,
|
||||
@@ -826,6 +924,7 @@ class PmsConnect(object):
|
||||
'user_id': user_details['user_id'],
|
||||
'friendly_name': user_details['friendly_name'],
|
||||
'user_thumb': user_details['thumb'],
|
||||
'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split(':')[-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,
|
||||
@@ -882,6 +981,7 @@ class PmsConnect(object):
|
||||
'user_id': user_details['user_id'],
|
||||
'friendly_name': user_details['friendly_name'],
|
||||
'user_thumb': user_details['thumb'],
|
||||
'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split(':')[-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,
|
||||
@@ -938,6 +1038,7 @@ class PmsConnect(object):
|
||||
'user_id': user_details['user_id'],
|
||||
'friendly_name': user_details['friendly_name'],
|
||||
'user_thumb': user_details['thumb'],
|
||||
'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split(':')[-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,
|
||||
@@ -1027,6 +1128,7 @@ class PmsConnect(object):
|
||||
'user_id': user_details['user_id'],
|
||||
'friendly_name': user_details['friendly_name'],
|
||||
'user_thumb': user_details['thumb'],
|
||||
'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split(':')[-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,
|
||||
@@ -1071,11 +1173,8 @@ class PmsConnect(object):
|
||||
logger.warn(u"No known stream types found in session list.")
|
||||
|
||||
# Rename Mystery platform names
|
||||
platform_names = {'Mystery 3': 'Playstation 3',
|
||||
'Mystery 4': 'Playstation 4',
|
||||
'Mystery 5': 'Xbox 360'}
|
||||
session_output['platform'] = platform_names.get(session_output['platform'],
|
||||
session_output['platform'])
|
||||
session_output['platform'] = common.PLATFORM_NAME_OVERRIDES.get(session_output['platform'],
|
||||
session_output['platform'])
|
||||
|
||||
return session_output
|
||||
|
||||
@@ -1432,9 +1531,9 @@ class PmsConnect(object):
|
||||
for result in result_data:
|
||||
rating_key = helpers.get_xml_attr(result, 'ratingKey')
|
||||
metadata = self.get_metadata_details(rating_key=rating_key)
|
||||
if metadata['metadata']['type'] == 'movie':
|
||||
if metadata['metadata']['media_type'] == 'movie':
|
||||
search_results_list['movie'].append(metadata['metadata'])
|
||||
elif metadata['metadata']['type'] == 'episode':
|
||||
elif metadata['metadata']['media_type'] == 'episode':
|
||||
search_results_list['episode'].append(metadata['metadata'])
|
||||
search_results_count += 1
|
||||
|
||||
@@ -1443,7 +1542,7 @@ class PmsConnect(object):
|
||||
for result in result_data:
|
||||
rating_key = helpers.get_xml_attr(result, 'ratingKey')
|
||||
metadata = self.get_metadata_details(rating_key=rating_key)
|
||||
if metadata['metadata']['type'] == 'show':
|
||||
if metadata['metadata']['media_type'] == 'show':
|
||||
search_results_list['show'].append(metadata['metadata'])
|
||||
|
||||
show_seasons = self.get_item_children(rating_key=metadata['metadata']['rating_key'])
|
||||
@@ -1455,9 +1554,9 @@ class PmsConnect(object):
|
||||
search_results_list['season'].append(metadata['metadata'])
|
||||
search_results_count += 1
|
||||
|
||||
elif metadata['metadata']['type'] == 'artist':
|
||||
elif metadata['metadata']['media_type'] == 'artist':
|
||||
search_results_list['artist'].append(metadata['metadata'])
|
||||
elif metadata['metadata']['type'] == 'album':
|
||||
elif metadata['metadata']['media_type'] == 'album':
|
||||
search_results_list['album'].append(metadata['metadata'])
|
||||
search_results_count += 1
|
||||
|
||||
@@ -1579,4 +1678,26 @@ class PmsConnect(object):
|
||||
'children': parents}
|
||||
}
|
||||
|
||||
return key_list
|
||||
return key_list
|
||||
|
||||
def get_server_response(self):
|
||||
# Refresh Plex remote access port mapping first
|
||||
self.put_refresh_reachability()
|
||||
account_data = self.get_account(output_format='xml')
|
||||
|
||||
try:
|
||||
xml_head = account_data.getElementsByTagName('MyPlex')
|
||||
except:
|
||||
logger.warn("Unable to parse XML for get_server_response.")
|
||||
return None
|
||||
|
||||
server_response = {}
|
||||
|
||||
for a in xml_head:
|
||||
server_response = {'mapping_state': helpers.get_xml_attr(a, 'mappingState'),
|
||||
'mapping_error': helpers.get_xml_attr(a, 'mappingError'),
|
||||
'public_address': helpers.get_xml_attr(a, 'publicAddress'),
|
||||
'public_port': helpers.get_xml_attr(a, 'publicPort')
|
||||
}
|
||||
|
||||
return server_response
|
@@ -24,6 +24,8 @@ class Users(object):
|
||||
def get_user_list(self, kwargs=None):
|
||||
data_tables = datatables.DataTables()
|
||||
|
||||
custom_where = ['users.deleted_user', 0]
|
||||
|
||||
columns = ['session_history.id',
|
||||
'users.user_id as user_id',
|
||||
'users.custom_avatar_url as user_thumb',
|
||||
@@ -48,7 +50,7 @@ class Users(object):
|
||||
try:
|
||||
query = data_tables.ssp_query(table_name='users',
|
||||
columns=columns,
|
||||
custom_where=[],
|
||||
custom_where=[custom_where],
|
||||
group_by=['users.user_id'],
|
||||
join_types=['LEFT OUTER JOIN',
|
||||
'LEFT OUTER JOIN',
|
||||
@@ -85,10 +87,10 @@ class Users(object):
|
||||
user_thumb = item['user_thumb']
|
||||
|
||||
# Rename Mystery platform names
|
||||
platform_names = {'Mystery 3': 'Playstation 3',
|
||||
'Mystery 4': 'Playstation 4',
|
||||
'Mystery 5': 'Xbox 360'}
|
||||
platform = platform_names.get(item["platform"], item["platform"])
|
||||
platform = common.PLATFORM_NAME_OVERRIDES.get(item["platform"], item["platform"])
|
||||
|
||||
# Sanitize player name
|
||||
player = helpers.sanitize(item["player"])
|
||||
|
||||
row = {"id": item['id'],
|
||||
"plays": item['plays'],
|
||||
@@ -96,7 +98,7 @@ class Users(object):
|
||||
"friendly_name": item['friendly_name'],
|
||||
"ip_address": item['ip_address'],
|
||||
"platform": platform,
|
||||
"player": item['player'],
|
||||
"player": player,
|
||||
"last_watched": item['last_watched'],
|
||||
"thumb": thumb,
|
||||
"media_type": item['media_type'],
|
||||
@@ -179,17 +181,17 @@ class Users(object):
|
||||
thumb = item["thumb"]
|
||||
|
||||
# Rename Mystery platform names
|
||||
platform_names = {'Mystery 3': 'Playstation 3',
|
||||
'Mystery 4': 'Playstation 4',
|
||||
'Mystery 5': 'Xbox 360'}
|
||||
platform = platform_names.get(item["platform"], item["platform"])
|
||||
platform = common.PLATFORM_NAME_OVERRIDES.get(item["platform"], item["platform"])
|
||||
|
||||
# Sanitize player name
|
||||
player = helpers.sanitize(item["player"])
|
||||
|
||||
row = {"id": item['id'],
|
||||
"last_seen": item['last_seen'],
|
||||
"ip_address": item['ip_address'],
|
||||
"play_count": item['play_count'],
|
||||
"platform": platform,
|
||||
"player": item['player'],
|
||||
"player": player,
|
||||
"last_watched": item['last_watched'],
|
||||
"thumb": thumb,
|
||||
"media_type": item['media_type'],
|
||||
@@ -269,17 +271,17 @@ class Users(object):
|
||||
if user_id:
|
||||
monitor_db = database.MonitorDatabase()
|
||||
query = 'select username, ' \
|
||||
'(CASE WHEN friendly_name IS NULL THEN username ELSE friendly_name END),' \
|
||||
'(CASE WHEN friendly_name IS NULL THEN username ELSE friendly_name END) as friendly_name,' \
|
||||
'do_notify, keep_history, custom_avatar_url as thumb ' \
|
||||
'FROM users WHERE user_id = ?'
|
||||
result = monitor_db.select(query, args=[user_id])
|
||||
if result:
|
||||
user_detail = {'user_id': user_id,
|
||||
'user': result[0][0],
|
||||
'friendly_name': result[0][1],
|
||||
'thumb': result[0][4],
|
||||
'do_notify': helpers.checked(result[0][2]),
|
||||
'keep_history': helpers.checked(result[0][3])
|
||||
'user': result[0]['username'],
|
||||
'friendly_name': result[0]['friendly_name'],
|
||||
'thumb': result[0]['thumb'],
|
||||
'do_notify': helpers.checked(result[0]['do_notify']),
|
||||
'keep_history': helpers.checked(result[0]['keep_history'])
|
||||
}
|
||||
return user_detail
|
||||
else:
|
||||
@@ -293,17 +295,17 @@ class Users(object):
|
||||
elif user:
|
||||
monitor_db = database.MonitorDatabase()
|
||||
query = 'select user_id, ' \
|
||||
'(CASE WHEN friendly_name IS NULL THEN username ELSE friendly_name END),' \
|
||||
'(CASE WHEN friendly_name IS NULL THEN username ELSE friendly_name END) as friendly_name,' \
|
||||
'do_notify, keep_history, custom_avatar_url as thumb ' \
|
||||
'FROM users WHERE username = ?'
|
||||
result = monitor_db.select(query, args=[user])
|
||||
if result:
|
||||
user_detail = {'user_id': result[0][0],
|
||||
user_detail = {'user_id': result[0]['user_id'],
|
||||
'user': user,
|
||||
'friendly_name': result[0][1],
|
||||
'thumb': result[0][4],
|
||||
'do_notify': helpers.checked(result[0][2]),
|
||||
'keep_history': helpers.checked(result[0][3])}
|
||||
'friendly_name': result[0]['friendly_name'],
|
||||
'thumb': result[0]['thumb'],
|
||||
'do_notify': helpers.checked(result[0]['do_notify']),
|
||||
'keep_history': helpers.checked(result[0]['keep_history'])}
|
||||
return user_detail
|
||||
else:
|
||||
user_detail = {'user_id': None,
|
||||
@@ -490,9 +492,9 @@ class Users(object):
|
||||
result = monitor_db.select(query, args=[user])
|
||||
|
||||
for item in result:
|
||||
if item[0]:
|
||||
total_time = item[0]
|
||||
total_plays = item[1]
|
||||
if item['total_time']:
|
||||
total_time = item['total_time']
|
||||
total_plays = item['total_plays']
|
||||
else:
|
||||
total_time = 0
|
||||
total_plays = 0
|
||||
@@ -533,17 +535,14 @@ class Users(object):
|
||||
|
||||
for item in result:
|
||||
# Rename Mystery platform names
|
||||
platform_names = {'Mystery 3': 'Playstation 3',
|
||||
'Mystery 4': 'Playstation 4',
|
||||
'Mystery 5': 'Xbox 360'}
|
||||
platform_type = platform_names.get(item[2], item[2])
|
||||
platform_type = common.PLATFORM_NAME_OVERRIDES.get(item['platform'], item['platform'])
|
||||
|
||||
row = {'player_name': item[0],
|
||||
row = {'player_name': item['player'],
|
||||
'platform_type': platform_type,
|
||||
'total_plays': item[1],
|
||||
'total_plays': item['player_count'],
|
||||
'result_id': result_id
|
||||
}
|
||||
player_stats.append(row)
|
||||
result_id += 1
|
||||
|
||||
return player_stats
|
||||
return player_stats
|
||||
|
@@ -1,2 +1,2 @@
|
||||
PLEXPY_VERSION = "master"
|
||||
PLEXPY_RELEASE_VERSION = "1.2.3"
|
||||
PLEXPY_VERSION = "master"
|
||||
PLEXPY_RELEASE_VERSION = "1.2.9"
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# This file is part of PlexPy.
|
||||
# This file is part of PlexPy.
|
||||
#
|
||||
# PlexPy is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -139,4 +139,14 @@ def process(opcode, data):
|
||||
activity = activity_handler.ActivityHandler(timeline=time_line[0])
|
||||
activity.process()
|
||||
|
||||
#if type == 'timeline':
|
||||
# try:
|
||||
# time_line = info.get('_children')
|
||||
# except:
|
||||
# logger.debug(u"PlexPy WebSocket :: Timeline event found but unable to get timeline data.")
|
||||
# return False
|
||||
|
||||
# activity = activity_handler.TimelineHandler(timeline=time_line[0])
|
||||
# activity.process()
|
||||
|
||||
return True
|
||||
|
@@ -1,7 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# This file is part of PlexPy.
|
||||
# This file is part of PlexPy.
|
||||
#
|
||||
# PlexPy is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -16,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, notifiers, plextv, pmsconnect, common, log_reader, datafactory, graphs, users
|
||||
from plexpy import logger, notifiers, plextv, pmsconnect, common, log_reader, datafactory, graphs, users, helpers
|
||||
from plexpy.helpers import checked, radio
|
||||
|
||||
from mako.lookup import TemplateLookup
|
||||
@@ -44,11 +41,13 @@ def serve_template(templatename, **kwargs):
|
||||
interface_dir = os.path.join(str(plexpy.PROG_DIR), 'data/interfaces/')
|
||||
template_dir = os.path.join(str(interface_dir), plexpy.CONFIG.INTERFACE)
|
||||
|
||||
_hplookup = TemplateLookup(directories=[template_dir])
|
||||
_hplookup = TemplateLookup(directories=[template_dir], default_filters=['unicode', 'h'])
|
||||
|
||||
server_name = plexpy.CONFIG.PMS_NAME
|
||||
|
||||
try:
|
||||
template = _hplookup.get_template(templatename)
|
||||
return template.render(**kwargs)
|
||||
return template.render(server_name=server_name, **kwargs)
|
||||
except:
|
||||
return exceptions.html_error_template().render()
|
||||
|
||||
@@ -71,7 +70,8 @@ class WebInterface(object):
|
||||
"home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH,
|
||||
"home_stats_cards": plexpy.CONFIG.HOME_STATS_CARDS,
|
||||
"home_library_cards": plexpy.CONFIG.HOME_LIBRARY_CARDS,
|
||||
"pms_identifier": plexpy.CONFIG.PMS_IDENTIFIER
|
||||
"pms_identifier": plexpy.CONFIG.PMS_IDENTIFIER,
|
||||
"pms_name": plexpy.CONFIG.PMS_NAME
|
||||
}
|
||||
return serve_template(templatename="index.html", title="Home", config=config)
|
||||
|
||||
@@ -87,13 +87,14 @@ class WebInterface(object):
|
||||
"pms_token": plexpy.CONFIG.PMS_TOKEN,
|
||||
"pms_ssl": checked(plexpy.CONFIG.PMS_SSL),
|
||||
"pms_uuid": plexpy.CONFIG.PMS_UUID,
|
||||
"tv_notify_enable": checked(plexpy.CONFIG.TV_NOTIFY_ENABLE),
|
||||
"movie_notify_enable": checked(plexpy.CONFIG.MOVIE_NOTIFY_ENABLE),
|
||||
"tv_notify_enable": checked(plexpy.CONFIG.TV_NOTIFY_ENABLE),
|
||||
"music_notify_enable": checked(plexpy.CONFIG.MUSIC_NOTIFY_ENABLE),
|
||||
"tv_notify_on_start": checked(plexpy.CONFIG.TV_NOTIFY_ON_START),
|
||||
"movie_notify_on_start": checked(plexpy.CONFIG.MOVIE_NOTIFY_ON_START),
|
||||
"tv_notify_on_start": checked(plexpy.CONFIG.TV_NOTIFY_ON_START),
|
||||
"music_notify_on_start": checked(plexpy.CONFIG.MUSIC_NOTIFY_ON_START),
|
||||
"video_logging_enable": checked(plexpy.CONFIG.VIDEO_LOGGING_ENABLE),
|
||||
"movie_logging_enable": checked(plexpy.CONFIG.MOVIE_LOGGING_ENABLE),
|
||||
"tv_logging_enable": checked(plexpy.CONFIG.TV_LOGGING_ENABLE),
|
||||
"music_logging_enable": checked(plexpy.CONFIG.MUSIC_LOGGING_ENABLE),
|
||||
"logging_ignore_interval": plexpy.CONFIG.LOGGING_IGNORE_INTERVAL,
|
||||
"check_github": checked(plexpy.CONFIG.CHECK_GITHUB)
|
||||
@@ -420,8 +421,8 @@ class WebInterface(object):
|
||||
"grouping_global_history": checked(plexpy.CONFIG.GROUPING_GLOBAL_HISTORY),
|
||||
"grouping_user_history": checked(plexpy.CONFIG.GROUPING_USER_HISTORY),
|
||||
"grouping_charts": checked(plexpy.CONFIG.GROUPING_CHARTS),
|
||||
"tv_notify_enable": checked(plexpy.CONFIG.TV_NOTIFY_ENABLE),
|
||||
"movie_notify_enable": checked(plexpy.CONFIG.MOVIE_NOTIFY_ENABLE),
|
||||
"tv_notify_enable": checked(plexpy.CONFIG.TV_NOTIFY_ENABLE),
|
||||
"music_notify_enable": checked(plexpy.CONFIG.MUSIC_NOTIFY_ENABLE),
|
||||
"tv_notify_on_start": checked(plexpy.CONFIG.TV_NOTIFY_ON_START),
|
||||
"movie_notify_on_start": checked(plexpy.CONFIG.MOVIE_NOTIFY_ON_START),
|
||||
@@ -432,16 +433,21 @@ class WebInterface(object):
|
||||
"tv_notify_on_pause": checked(plexpy.CONFIG.TV_NOTIFY_ON_PAUSE),
|
||||
"movie_notify_on_pause": checked(plexpy.CONFIG.MOVIE_NOTIFY_ON_PAUSE),
|
||||
"music_notify_on_pause": checked(plexpy.CONFIG.MUSIC_NOTIFY_ON_PAUSE),
|
||||
"monitor_remote_access": checked(plexpy.CONFIG.MONITOR_REMOTE_ACCESS),
|
||||
"monitoring_interval": plexpy.CONFIG.MONITORING_INTERVAL,
|
||||
"monitoring_use_websocket": checked(plexpy.CONFIG.MONITORING_USE_WEBSOCKET),
|
||||
"refresh_users_interval": plexpy.CONFIG.REFRESH_USERS_INTERVAL,
|
||||
"refresh_users_on_startup": checked(plexpy.CONFIG.REFRESH_USERS_ON_STARTUP),
|
||||
"ip_logging_enable": checked(plexpy.CONFIG.IP_LOGGING_ENABLE),
|
||||
"video_logging_enable": checked(plexpy.CONFIG.VIDEO_LOGGING_ENABLE),
|
||||
"movie_logging_enable": checked(plexpy.CONFIG.MOVIE_LOGGING_ENABLE),
|
||||
"tv_logging_enable": checked(plexpy.CONFIG.TV_LOGGING_ENABLE),
|
||||
"music_logging_enable": checked(plexpy.CONFIG.MUSIC_LOGGING_ENABLE),
|
||||
"logging_ignore_interval": plexpy.CONFIG.LOGGING_IGNORE_INTERVAL,
|
||||
"pms_is_remote": checked(plexpy.CONFIG.PMS_IS_REMOTE),
|
||||
"notify_consecutive": checked(plexpy.CONFIG.NOTIFY_CONSECUTIVE),
|
||||
"notify_recently_added": checked(plexpy.CONFIG.NOTIFY_RECENTLY_ADDED),
|
||||
"notify_recently_added_grandparent": checked(plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_GRANDPARENT),
|
||||
"notify_recently_added_delay": plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_DELAY,
|
||||
"notify_watched_percent": plexpy.CONFIG.NOTIFY_WATCHED_PERCENT,
|
||||
"notify_on_start_subject_text": plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT,
|
||||
"notify_on_start_body_text": plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT,
|
||||
@@ -455,6 +461,12 @@ class WebInterface(object):
|
||||
"notify_on_buffer_body_text": plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT,
|
||||
"notify_on_watched_subject_text": plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT,
|
||||
"notify_on_watched_body_text": plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT,
|
||||
"notify_on_created_subject_text": plexpy.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT,
|
||||
"notify_on_created_body_text": plexpy.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT,
|
||||
"notify_on_extdown_subject_text": plexpy.CONFIG.NOTIFY_ON_EXTDOWN_SUBJECT_TEXT,
|
||||
"notify_on_extdown_body_text": plexpy.CONFIG.NOTIFY_ON_EXTDOWN_BODY_TEXT,
|
||||
"notify_on_intdown_subject_text": plexpy.CONFIG.NOTIFY_ON_INTDOWN_SUBJECT_TEXT,
|
||||
"notify_on_intdown_body_text": plexpy.CONFIG.NOTIFY_ON_INTDOWN_BODY_TEXT,
|
||||
"home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH,
|
||||
"home_stats_type": checked(plexpy.CONFIG.HOME_STATS_TYPE),
|
||||
"home_stats_count": plexpy.CONFIG.HOME_STATS_COUNT,
|
||||
@@ -474,12 +486,13 @@ class WebInterface(object):
|
||||
checked_configs = [
|
||||
"launch_browser", "enable_https", "api_enabled", "freeze_db", "check_github",
|
||||
"grouping_global_history", "grouping_user_history", "grouping_charts", "pms_use_bif", "pms_ssl",
|
||||
"tv_notify_enable", "movie_notify_enable", "music_notify_enable", "monitoring_use_websocket",
|
||||
"movie_notify_enable", "tv_notify_enable", "music_notify_enable", "monitoring_use_websocket",
|
||||
"tv_notify_on_start", "movie_notify_on_start", "music_notify_on_start",
|
||||
"tv_notify_on_stop", "movie_notify_on_stop", "music_notify_on_stop",
|
||||
"tv_notify_on_pause", "movie_notify_on_pause", "music_notify_on_pause", "refresh_users_on_startup",
|
||||
"ip_logging_enable", "video_logging_enable", "music_logging_enable", "pms_is_remote", "home_stats_type",
|
||||
"group_history_tables", "notify_consecutive"
|
||||
"ip_logging_enable", "movie_logging_enable", "tv_logging_enable", "music_logging_enable",
|
||||
"pms_is_remote", "home_stats_type", "group_history_tables", "notify_consecutive",
|
||||
"notify_recently_added", "notify_recently_added_grandparent", "monitor_remote_access"
|
||||
]
|
||||
for checked_config in checked_configs:
|
||||
if checked_config not in kwargs:
|
||||
@@ -504,6 +517,14 @@ class WebInterface(object):
|
||||
if (kwargs['monitoring_interval'] != str(plexpy.CONFIG.MONITORING_INTERVAL)) or \
|
||||
(kwargs['refresh_users_interval'] != str(plexpy.CONFIG.REFRESH_USERS_INTERVAL)):
|
||||
reschedule = True
|
||||
|
||||
if 'notify_recently_added' in kwargs and \
|
||||
(kwargs['notify_recently_added'] != plexpy.CONFIG.NOTIFY_RECENTLY_ADDED):
|
||||
reschedule = True
|
||||
|
||||
if 'monitor_remote_access' in kwargs and \
|
||||
(kwargs['monitor_remote_access'] != plexpy.CONFIG.MONITOR_REMOTE_ACCESS):
|
||||
reschedule = True
|
||||
|
||||
if 'pms_ip' in kwargs:
|
||||
if kwargs['pms_ip'] != plexpy.CONFIG.PMS_IP:
|
||||
@@ -571,27 +592,28 @@ class WebInterface(object):
|
||||
|
||||
custom_where = []
|
||||
if user_id:
|
||||
custom_where = [['session_history.user_id', user_id]]
|
||||
custom_where.append(['session_history.user_id', user_id])
|
||||
elif user:
|
||||
custom_where = [['session_history.user', user]]
|
||||
custom_where.append(['session_history.user', user])
|
||||
if 'rating_key' in kwargs:
|
||||
rating_key = kwargs.get('rating_key', "")
|
||||
custom_where = [['session_history.rating_key', rating_key]]
|
||||
custom_where.append(['session_history.rating_key', rating_key])
|
||||
if 'parent_rating_key' in kwargs:
|
||||
rating_key = kwargs.get('parent_rating_key', "")
|
||||
custom_where = [['session_history.parent_rating_key', rating_key]]
|
||||
custom_where.append(['session_history.parent_rating_key', rating_key])
|
||||
if 'grandparent_rating_key' in kwargs:
|
||||
rating_key = kwargs.get('grandparent_rating_key', "")
|
||||
custom_where = [['session_history.grandparent_rating_key', rating_key]]
|
||||
custom_where.append(['session_history.grandparent_rating_key', rating_key])
|
||||
if 'start_date' in kwargs:
|
||||
start_date = kwargs.get('start_date', "")
|
||||
custom_where = [['strftime("%Y-%m-%d", datetime(date, "unixepoch", "localtime"))', start_date]]
|
||||
custom_where.append(['strftime("%Y-%m-%d", datetime(date, "unixepoch", "localtime"))', start_date])
|
||||
if 'reference_id' in kwargs:
|
||||
reference_id = kwargs.get('reference_id', "")
|
||||
custom_where = [['session_history.reference_id', reference_id]]
|
||||
custom_where.append(['session_history.reference_id', reference_id])
|
||||
if 'media_type' in kwargs:
|
||||
media_type = kwargs.get('media_type', "")
|
||||
custom_where = [['session_history_metadata.media_type', media_type]]
|
||||
if media_type != 'all':
|
||||
custom_where.append(['session_history_metadata.media_type', media_type])
|
||||
|
||||
data_factory = datafactory.DataFactory()
|
||||
history = data_factory.get_history(kwargs=kwargs, custom_where=custom_where, grouping=grouping, watched_percent=watched_percent)
|
||||
@@ -652,6 +674,16 @@ class WebInterface(object):
|
||||
else:
|
||||
return "Error sending tweet"
|
||||
|
||||
@cherrypy.expose
|
||||
def test_ifttt(self):
|
||||
cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store"
|
||||
event = notifiers.IFTTT()
|
||||
result = event.test()
|
||||
if result:
|
||||
return "Notification successful."
|
||||
else:
|
||||
return "Error sending event."
|
||||
|
||||
@cherrypy.expose
|
||||
def osxnotifyregister(self, app):
|
||||
cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store"
|
||||
@@ -697,6 +729,15 @@ class WebInterface(object):
|
||||
try:
|
||||
pms_connect = pmsconnect.PmsConnect()
|
||||
result = pms_connect.get_current_activity()
|
||||
|
||||
data_factory = datafactory.DataFactory()
|
||||
for session in result['sessions']:
|
||||
if not session['ip_address']:
|
||||
ip_address = data_factory.get_session_ip(session['session_key'])
|
||||
session['ip_address'] = ip_address
|
||||
# Sanitize player name
|
||||
session['player'] = helpers.sanitize(session['player'])
|
||||
|
||||
except:
|
||||
return serve_template(templatename="current_activity.html", data=None)
|
||||
|
||||
@@ -777,11 +818,11 @@ class WebInterface(object):
|
||||
data_factory = datafactory.DataFactory()
|
||||
metadata = data_factory.get_metadata_details(row_id=item_id)
|
||||
elif item_id == 'movie':
|
||||
metadata = {'type': 'library', 'library': 'movie', 'media_type': 'movie', 'title': 'Movies'}
|
||||
metadata = {'media_type': 'library', 'library': 'movie', 'media_type_filter': 'movie', 'title': 'Movies'}
|
||||
elif item_id == 'show':
|
||||
metadata = {'type': 'library', 'library': 'show', 'media_type': 'episode', 'title': 'TV Shows'}
|
||||
metadata = {'media_type': 'library', 'library': 'show', 'media_type_filter': 'episode', 'title': 'TV Shows'}
|
||||
elif item_id == 'artist':
|
||||
metadata = {'type': 'library', 'library': 'artist', 'media_type': 'track', 'title': 'Music'}
|
||||
metadata = {'media_type': 'library', 'library': 'artist', 'media_type_filter': 'track', 'title': 'Music'}
|
||||
else:
|
||||
pms_connect = pmsconnect.PmsConnect()
|
||||
result = pms_connect.get_metadata_details(rating_key=item_id)
|
||||
@@ -1117,10 +1158,24 @@ class WebInterface(object):
|
||||
logger.warn('Unable to retrieve data.')
|
||||
|
||||
@cherrypy.expose
|
||||
def get_server_prefs(self, **kwargs):
|
||||
def get_server_friendly_name(self, **kwargs):
|
||||
|
||||
pms_connect = pmsconnect.PmsConnect()
|
||||
result = pms_connect.get_server_prefs(output_format='json')
|
||||
result = pmsconnect.get_server_friendly_name()
|
||||
|
||||
if result:
|
||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||
return result
|
||||
else:
|
||||
logger.warn('Unable to retrieve data.')
|
||||
|
||||
@cherrypy.expose
|
||||
def get_server_prefs(self, pref=None, **kwargs):
|
||||
|
||||
if pref:
|
||||
pms_connect = pmsconnect.PmsConnect()
|
||||
result = pms_connect.get_server_pref(pref=pref)
|
||||
else:
|
||||
result = None
|
||||
|
||||
if result:
|
||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||
@@ -1352,8 +1407,41 @@ class WebInterface(object):
|
||||
return json.dumps({'message': 'no data received'})
|
||||
|
||||
@cherrypy.expose
|
||||
def search(self, search_query=''):
|
||||
query = search_query.replace('"', '')
|
||||
def delete_user(self, user_id, **kwargs):
|
||||
data_factory = datafactory.DataFactory()
|
||||
|
||||
if user_id:
|
||||
delete_row = data_factory.delete_user(user_id=user_id)
|
||||
|
||||
if delete_row:
|
||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||
return json.dumps({'message': delete_row})
|
||||
else:
|
||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||
return json.dumps({'message': 'no data received'})
|
||||
|
||||
@cherrypy.expose
|
||||
def undelete_user(self, user_id=None, username=None, **kwargs):
|
||||
data_factory = datafactory.DataFactory()
|
||||
|
||||
if user_id:
|
||||
delete_row = data_factory.undelete_user(user_id=user_id)
|
||||
|
||||
if delete_row:
|
||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||
return json.dumps({'message': delete_row})
|
||||
elif username:
|
||||
delete_row = data_factory.undelete_user(username=username)
|
||||
|
||||
if delete_row:
|
||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||
return json.dumps({'message': delete_row})
|
||||
else:
|
||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||
return json.dumps({'message': 'no data received'})
|
||||
|
||||
@cherrypy.expose
|
||||
def search(self, query=''):
|
||||
|
||||
return serve_template(templatename="search.html", title="Search", query=query)
|
||||
|
||||
|
Reference in New Issue
Block a user