Compare commits
36 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
be058eaff7 | ||
![]() |
f409dda2ef | ||
![]() |
f409cdda8f | ||
![]() |
9cd6396c35 | ||
![]() |
ee754ea533 | ||
![]() |
36de20dd75 | ||
![]() |
a957e8eb4f | ||
![]() |
14a90d84ec | ||
![]() |
fae9bc618a | ||
![]() |
3248e6500e | ||
![]() |
c17bf79d79 | ||
![]() |
1ff1270bfa | ||
![]() |
b1a2cf33d8 | ||
![]() |
b2292e98c1 | ||
![]() |
4d156a8911 | ||
![]() |
7193b6518b | ||
![]() |
cff6b44109 | ||
![]() |
fb7ad9438e | ||
![]() |
afc265a188 | ||
![]() |
01fe7bf612 | ||
![]() |
1cb75bd053 | ||
![]() |
0eaea4d011 | ||
![]() |
67377a2561 | ||
![]() |
a8aae9f1f5 | ||
![]() |
a9ce92decb | ||
![]() |
c19162295a | ||
![]() |
58796c45ed | ||
![]() |
d94b348780 | ||
![]() |
95f92bd292 | ||
![]() |
bc52ac3559 | ||
![]() |
8bbc6a6611 | ||
![]() |
8902b93a26 | ||
![]() |
ae36af807d | ||
![]() |
fd256625c6 | ||
![]() |
bee543a25a | ||
![]() |
55eb79cb52 |
42
CHANGELOG.md
42
CHANGELOG.md
@@ -1,9 +1,43 @@
|
||||
# Changelog
|
||||
|
||||
## v1.3.5 (2016-02-02)
|
||||
|
||||
* Fix: Removing unique constraints from database.
|
||||
* Fix: Unable to expand media info table when missing "Added At" date.
|
||||
* Fix: Server verification for unpublished servers.
|
||||
* Fix: Updating PMS identifier for server change.
|
||||
* Add: {stream_time}, {remaining_time}, and {progress_time} to notification options.
|
||||
* Add: Powershell script support. (Thanks @Hellowlol)
|
||||
* Add: Method to delete duplicate libraries.
|
||||
* Change: Daemonize before running start up tasks.
|
||||
|
||||
|
||||
## v1.3.4 (2016-01-29)
|
||||
|
||||
* Fix: Activity checker not starting with library update (history not logging).
|
||||
* Fix: Libraries duplicated in database.
|
||||
* Fix: Buffer notifications even when disabled when using websockets.
|
||||
* Fix: Libraries and Users lists not refreshing.
|
||||
* Fix: Server verification in settings.
|
||||
* Fix: Empty libraries not added to database.
|
||||
* Add: Unique identifiers to notification options.
|
||||
* Remove: Requirement of media type toggles for recently added notifications.
|
||||
* Remove: Built in Twitter key and secret.
|
||||
* Change: Unnecessary quoting of script arguments.
|
||||
* Change: Facebook notification instructions.
|
||||
|
||||
|
||||
## v1.3.3 (2016-01-26)
|
||||
|
||||
* Fix: Plays by Month graph not loading.
|
||||
* Change: Disable caching for datatables.
|
||||
* Change: Improved updating library data in the database again.
|
||||
|
||||
|
||||
## v1.3.2 (2016-01-24)
|
||||
|
||||
* Fix: 'datestamp' and 'timestamp' for server notifications
|
||||
* Change: New method for updating library data in database
|
||||
* Fix: 'datestamp' and 'timestamp' for server notifications.
|
||||
* Change: New method for updating library data in database.
|
||||
|
||||
|
||||
## v1.3.1 (2016-01-23)
|
||||
@@ -13,8 +47,8 @@
|
||||
* Fix: Star rating overlapping text.
|
||||
* Fix: Unable to startup when library refresh fails.
|
||||
* Fix: Unable to parse 'datestamp' and 'timestamp' format.
|
||||
* Change: Rename "Last Watched" to "Last Played"
|
||||
* Change: More descriptive libraries updating message
|
||||
* Change: Rename "Last Watched" to "Last Played".
|
||||
* Change: More descriptive libraries updating message.
|
||||
|
||||
|
||||
## v1.3.0 (2016-01-23)
|
||||
|
@@ -153,12 +153,12 @@ def main():
|
||||
# Put the database in the DATA_DIR
|
||||
plexpy.DB_FILE = os.path.join(plexpy.DATA_DIR, 'plexpy.db')
|
||||
|
||||
# Read config and start logging
|
||||
plexpy.initialize(config_file)
|
||||
|
||||
if plexpy.DAEMON:
|
||||
plexpy.daemonize()
|
||||
|
||||
# Read config and start logging
|
||||
plexpy.initialize(config_file)
|
||||
|
||||
# Force the http port if neccessary
|
||||
if args.port:
|
||||
http_port = args.port
|
||||
|
@@ -22,7 +22,7 @@ history_table_options = {
|
||||
"emptyTable": "No data in table"
|
||||
},
|
||||
"pagingType": "bootstrap",
|
||||
"stateSave": true,
|
||||
"stateSave": false,
|
||||
"processing": false,
|
||||
"serverSide": true,
|
||||
"pageLength": 25,
|
||||
|
@@ -16,7 +16,7 @@ libraries_list_table_options = {
|
||||
"pageLength": 10,
|
||||
"order": [ 2, 'asc'],
|
||||
"autoWidth": true,
|
||||
"stateSave": true,
|
||||
"stateSave": false,
|
||||
"pagingType": "bootstrap",
|
||||
"columnDefs": [
|
||||
{
|
||||
|
@@ -5,7 +5,7 @@ var log_table_options = {
|
||||
"pagingType": "bootstrap",
|
||||
"order": [ 0, 'desc'],
|
||||
"pageLength": 50,
|
||||
"stateSave": true,
|
||||
"stateSave": false,
|
||||
"language": {
|
||||
"search":"Search: ",
|
||||
"lengthMenu":"Show _MENU_ lines per page",
|
||||
|
@@ -34,9 +34,12 @@ media_info_table_options = {
|
||||
"targets": [0],
|
||||
"data": "added_at",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== null && cellData !== '') {
|
||||
if (rowData) {
|
||||
var expand_details = '';
|
||||
var date = moment(cellData, "X").format(date_format);
|
||||
var date = '';
|
||||
if (cellData !== null && cellData !== '') {
|
||||
date = moment(cellData, "X").format(date_format);
|
||||
}
|
||||
if (rowData['media_type'] === 'show') {
|
||||
expand_details = '<span class="expand-media-info-tooltip" data-toggle="tooltip" title="Show Seasons"><i class="fa fa-plus-circle fa-fw"></i></span>';
|
||||
$(td).html('<div><a href="#"><div style="float: left;">' + expand_details + ' ' + date + '</div></a></div>');
|
||||
|
@@ -5,7 +5,7 @@ var plex_log_table_options = {
|
||||
"pagingType": "bootstrap",
|
||||
"order": [ 0, 'desc'],
|
||||
"pageLength": 50,
|
||||
"stateSave": true,
|
||||
"stateSave": false,
|
||||
"language": {
|
||||
"search":"Search: ",
|
||||
"lengthMenu":"Show _MENU_ lines per page",
|
||||
|
@@ -4,7 +4,7 @@ sync_table_options = {
|
||||
"pagingType": "bootstrap",
|
||||
"order": [ [ 0, 'desc'], [ 1, 'asc'], [2, 'asc'] ],
|
||||
"pageLength": 25,
|
||||
"stateSave": true,
|
||||
"stateSave": false,
|
||||
"language": {
|
||||
"search":"Search: ",
|
||||
"lengthMenu":"Show _MENU_ lines per page",
|
||||
|
@@ -8,7 +8,7 @@ user_ip_table_options = {
|
||||
"infoFiltered":"(filtered from _MAX_ total entries)",
|
||||
"emptyTable": "No data in table",
|
||||
},
|
||||
"stateSave": true,
|
||||
"stateSave": false,
|
||||
"pagingType": "bootstrap",
|
||||
"processing": false,
|
||||
"serverSide": true,
|
||||
|
@@ -16,7 +16,7 @@ users_list_table_options = {
|
||||
"pageLength": 10,
|
||||
"order": [ 2, 'asc'],
|
||||
"autoWidth": true,
|
||||
"stateSave": true,
|
||||
"stateSave": false,
|
||||
"pagingType": "bootstrap",
|
||||
"columnDefs": [
|
||||
{
|
||||
|
@@ -181,6 +181,10 @@ from plexpy import helpers
|
||||
});
|
||||
|
||||
$('#facebookStep1').click(function () {
|
||||
// Remove trailing '/' from Facebook redirect URI
|
||||
if ($('#facebook_redirect_uri') && $('#facebook_redirect_uri').val().endsWith('/')) {
|
||||
$('#facebook_redirect_uri').val($('#facebook_redirect_uri').val().slice(0, -1));
|
||||
}
|
||||
doAjaxCall('set_notification_config', $(this), 'tabs', true);
|
||||
$.get('facebookStep1', function (data) { window.open(data); })
|
||||
.done(function () { showMsg('<i class="fa fa-check"></i> Confirm Authorization. Check pop-up blocker if no response.', false, true, 3000); });
|
||||
|
@@ -359,6 +359,10 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
||||
</label>
|
||||
<p class="help-block">Force PlexPy to connect to your Plex Server via SSL. Your server needs to have remote access enabled.</p>
|
||||
</div>
|
||||
|
||||
<input type="hidden" id="pms_identifier" name="pms_identifier" value="${config['pms_identifier']}">
|
||||
<input type="checkbox" name="server_changed" id="server_changed" value="1" style="display:none">
|
||||
|
||||
<div class="padded-header">
|
||||
<h3>Plex Logs</h3>
|
||||
</div>
|
||||
@@ -374,8 +378,6 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
||||
<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']}">
|
||||
|
||||
<input type="button" class="btn btn-bright save-button" value="Save" data-success="Changes saved successfully">
|
||||
|
||||
</div>
|
||||
@@ -1072,7 +1074,7 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Server Details
|
||||
Global
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -1085,6 +1087,18 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
||||
<td><strong>{server_uptime}</strong></td>
|
||||
<td>The uptime (in days, hours, mins, secs) of your Plex Server.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{action}</strong></td>
|
||||
<td>The action that triggered the notification.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{datestamp}</strong></td>
|
||||
<td>The date (in date format) the notification was triggered.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{timestamp}</strong></td>
|
||||
<td>The time (in time format) the notification was triggered.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="notification-params">
|
||||
@@ -1100,18 +1114,6 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
||||
<td><strong>{streams}</strong></td>
|
||||
<td>The number of concurrent streams.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{action}</strong></td>
|
||||
<td>The action that triggered the notification.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{datestamp}</strong></td>
|
||||
<td>The date the notification was triggered.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{timestamp}</strong></td>
|
||||
<td>The time the notification was triggered.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{user}</strong></td>
|
||||
<td>The username of the person streaming.</td>
|
||||
@@ -1128,22 +1130,30 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
||||
<td><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><strong>{media_type}</strong></td>
|
||||
<td>The type of media being played (movie, episode, track).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{stream_duration}</strong></td>
|
||||
<td>The stream duration (in minutes) for the item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{stream_time}</strong></td>
|
||||
<td>The stream duration (in time format) for the item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{remaining_duration}</strong></td>
|
||||
<td>The remaining duration (in minutes) for the item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{progress}</strong></td>
|
||||
<td><strong>{remaining_time}</strong></td>
|
||||
<td>The remaining duration (in time format) for the item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{progress_duration}</strong></td>
|
||||
<td>The last reported offset (in minutes) for the item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{progress_time}</strong></td>
|
||||
<td>The last reported offset (in time format) for the item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{progress_percent}</strong></td>
|
||||
<td>The last reported progress percent for the item.</td>
|
||||
@@ -1224,6 +1234,14 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
||||
<td><strong>{transcode_audio_channels}</strong></td>
|
||||
<td>The audio channels of the transcoded media.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{session_key}</strong></td>
|
||||
<td>The unique identifier for the session.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{user_id}</strong></td>
|
||||
<td>The unique identifier for the user.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="notification-params">
|
||||
@@ -1235,37 +1253,41 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>{media_type}</strong></td>
|
||||
<td>The type of media (movie, episode, track).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{title}</strong></td>
|
||||
<td>The full title of the item being played.</td>
|
||||
<td>The full title of the item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{library_name}</strong></td>
|
||||
<td>The library title of the item being played.</td>
|
||||
<td>The library title of the media item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{show_name}</strong></td>
|
||||
<td>The title of the TV series being played.</td>
|
||||
<td>The title of the TV series.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{episode_name}</strong></td>
|
||||
<td>The title of the episode being played.</td>
|
||||
<td>The title of the episode.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{artist_name}</strong></td>
|
||||
<td>The name of the artist being played.</td>
|
||||
<td>The name of the artistd.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{album_name}</strong></td>
|
||||
<td>The title of the album being played.</td>
|
||||
<td>The title of the album.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{track_name}</strong></td>
|
||||
<td>The title of the track being played.</td>
|
||||
<td>The title of the track.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{season_num}</strong></td>
|
||||
<td>The season number for the media item if item is episode.</td>
|
||||
<td>The season number for the item if item is episode.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{season_num00}</strong></td>
|
||||
@@ -1273,43 +1295,51 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{episode_num}</strong></td>
|
||||
<td>The episode number for the media item if item is episode.</td>
|
||||
<td>The episode number for the item if item is episode.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{episode_num00}</strong></td>
|
||||
<td>The two digit episode number.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{track_num}</strong></td>
|
||||
<td>The track number for the item if item is track.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{track_num00}</strong></td>
|
||||
<td>The two digit track number.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{year}</strong></td>
|
||||
<td>The release year for the media item.</td>
|
||||
<td>The release year for the item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{studio}</strong></td>
|
||||
<td>The studio for the media item.</td>
|
||||
<td>The studio for the item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{content_rating}</strong></td>
|
||||
<td>The content rating for the media item. (e.g. TV-MA, TV-PG, etc.)</td>
|
||||
<td>The content rating for the item. (e.g. TV-MA, TV-PG, etc.)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{directors}</strong></td>
|
||||
<td>A list of directors for the media item.</td>
|
||||
<td>A list of directors for the item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{writers}</strong></td>
|
||||
<td>A list of writers for the media item.</td>
|
||||
<td>A list of writers for the item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{actors}</strong></td>
|
||||
<td>A list of actors for the media item.</td>
|
||||
<td>A list of actors for the item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{genres}</strong></td>
|
||||
<td>A list of genres for the media item.</td>
|
||||
<td>A list of genres for the item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{summary}</strong></td>
|
||||
<td>A short plot summary for the media item.</td>
|
||||
<td>A short plot summary for the item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{tagline}</strong></td>
|
||||
@@ -1323,6 +1353,22 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
||||
<td><strong>{duration}</strong></td>
|
||||
<td>The duration (in minutes) for the item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{section_id}</strong></td>
|
||||
<td>The unique identifier for the library.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{rating_key}</strong></td>
|
||||
<td>The unique identifier for the item.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{parent_rating_key}</strong></td>
|
||||
<td>The unique identifier for the item's parent (season or album).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{grandparent_rating_key}</strong></td>
|
||||
<td>The unique identifier for the item's grandparent (TV show or artist).</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -1534,15 +1580,16 @@ $(document).ready(function() {
|
||||
serverChanged = true;
|
||||
$("#pms_identifier").val("");
|
||||
$("#pms-verify-status").html("");
|
||||
$("#server_changed").prop('checked', true);
|
||||
verifyServer();
|
||||
});
|
||||
|
||||
function verifyServer(_callback) {
|
||||
var pms_ip = $("#pms_ip").val()
|
||||
var pms_port = $("#pms_port").val()
|
||||
var pms_identifier = $("#pms_identifier").val()
|
||||
var pms_ssl = $("#pms_ssl").val()
|
||||
var pms_is_remote = $("#pms_is_remote").val()
|
||||
var pms_ip = $("#pms_ip").val();
|
||||
var pms_port = $("#pms_port").val();
|
||||
var pms_identifier = $("#pms_identifier").val();
|
||||
var pms_ssl = $("#pms_ssl").is(':checked') ? 1 : 0;
|
||||
var pms_is_remote = $("#pms_is_remote").is(':checked') ? 1 : 0;
|
||||
if (($("#pms_ip").val() !== '') || ($("#pms_port").val() !== '')) {
|
||||
$("#pms-verify").html('<i class="fa fa-refresh fa-spin"></i>');
|
||||
$('#pms-verify').fadeIn('fast');
|
||||
@@ -1551,15 +1598,16 @@ $(document).ready(function() {
|
||||
data : { hostname: pms_ip, port: pms_port, identifier: pms_identifier, ssl: pms_ssl, remote: pms_is_remote },
|
||||
cache: true,
|
||||
async: true,
|
||||
timeout: 5000,
|
||||
timeout: 10000,
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
$("#pms-verify").html('<i class="fa fa-close"></i>');
|
||||
$('#pms-verify').fadeIn('fast');
|
||||
$("#pms-ip-group").addClass("has-error");
|
||||
},
|
||||
success: function (xml) {
|
||||
if ($(xml).find('MediaContainer').attr('machineIdentifier')) {
|
||||
$("#pms_identifier").val($(xml).find('MediaContainer').attr('machineIdentifier'));
|
||||
success: function (json) {
|
||||
var machine_identifier = json;
|
||||
if (machine_identifier) {
|
||||
$("#pms_identifier").val(machine_identifier);
|
||||
$("#pms-verify").html('<i class="fa fa-check"></i>');
|
||||
$('#pms-verify').fadeIn('fast');
|
||||
$("#pms-ip-group").removeClass("has-error");
|
||||
|
@@ -169,6 +169,7 @@ from plexpy import common
|
||||
<input type="checkbox" name="launch_browser" id="launch_browser" value="1" ${config['launch_browser']}>
|
||||
<input type="checkbox" name="refresh_users_on_startup" id="refresh_users_on_startup" value="1" ${config['refresh_users_on_startup']}>
|
||||
<input type="checkbox" name="refresh_libraries_on_startup" id="refresh_libraries_on_startup" value="1" ${config['refresh_libraries_on_startup']}>
|
||||
<input type="checkbox" name="server_changed" id="server_changed" value="1" checked>
|
||||
<input type="checkbox" name="first_run_complete" id="first_run_complete" value="1" checked>
|
||||
<input type="checkbox" name="check_github" id="check_github" value="1" checked>
|
||||
<input type="text" name="home_stats_cards" id="home_stats_cards" value="first_run_wizard">
|
||||
@@ -392,9 +393,10 @@ from plexpy import common
|
||||
$("#pms-verify-status").html('<i class="fa fa-exclamation-circle"></i> This is not a Plex Server!');
|
||||
$('#pms-verify-status').fadeIn('fast');
|
||||
},
|
||||
success: function (xml) {
|
||||
if ($(xml).find('MediaContainer').attr('machineIdentifier')) {
|
||||
$("#pms_identifier").val($(xml).find('MediaContainer').attr('machineIdentifier'));
|
||||
success: function (json) {
|
||||
var machine_identifier = json;
|
||||
if (machine_identifier) {
|
||||
$("#pms_identifier").val(machine_identifier);
|
||||
$("#pms-verify-status").html('<i class="fa fa-check"></i> Server found!');
|
||||
$('#pms-verify-status').fadeIn('fast');
|
||||
pms_verified = true;
|
||||
|
@@ -176,7 +176,7 @@ def initialize(config_file):
|
||||
plextv.refresh_users()
|
||||
|
||||
# Refresh the libraries list on startup
|
||||
if CONFIG.PMS_TOKEN and CONFIG.REFRESH_LIBRARIES_ON_STARTUP:
|
||||
if CONFIG.PMS_IP and CONFIG.PMS_TOKEN and CONFIG.REFRESH_LIBRARIES_ON_STARTUP:
|
||||
pmsconnect.refresh_libraries()
|
||||
|
||||
# Store the original umask
|
||||
@@ -282,7 +282,7 @@ def initialize_scheduler():
|
||||
else:
|
||||
seconds = 0
|
||||
|
||||
if CONFIG.PMS_IP and CONFIG.PMS_TOKEN and CONFIG.UPDATE_SECTION_IDS != -1:
|
||||
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(pmsconnect.get_server_friendly_name, 'Refresh Plex Server Name',
|
||||
@@ -713,8 +713,8 @@ def dbcheck():
|
||||
|
||||
# Upgrade library_sections table from earlier versions (remove UNIQUE constraint on section_id)
|
||||
try:
|
||||
result = c_db.execute('PRAGMA index_xinfo("sqlite_autoindex_library_sections_1")')
|
||||
if result and 'server_id' not in [row[2] for row in result]:
|
||||
result = c_db.execute('SELECT SQL FROM sqlite_master WHERE type="table" AND name="library_sections"').fetchone()
|
||||
if 'section_id INTEGER UNIQUE' in result[0]:
|
||||
logger.debug(u"Altering database. Removing unique constraint on section_id from library_sections table.")
|
||||
c_db.execute(
|
||||
'CREATE TABLE library_sections_temp (id INTEGER PRIMARY KEY AUTOINCREMENT, '
|
||||
@@ -739,7 +739,7 @@ def dbcheck():
|
||||
'ALTER TABLE library_sections_temp RENAME TO library_sections'
|
||||
)
|
||||
except sqlite3.OperationalError:
|
||||
logger.debug(u"Unable to remove section_id unique constraint from library_sections.")
|
||||
logger.warn(u"Unable to remove section_id unique constraint from library_sections.")
|
||||
try:
|
||||
c_db.execute(
|
||||
'DROP TABLE library_sections_temp'
|
||||
@@ -747,10 +747,21 @@ def dbcheck():
|
||||
except:
|
||||
pass
|
||||
|
||||
# Upgrade library_sections table from earlier versions (remove duplicated libraries)
|
||||
try:
|
||||
result = c_db.execute('SELECT * FROM library_sections WHERE server_id = ""')
|
||||
if result.rowcount > 0:
|
||||
logger.debug(u"Altering database. Removing duplicate libraries from library_sections table.")
|
||||
c_db.execute(
|
||||
'DELETE FROM library_sections WHERE server_id = ""'
|
||||
)
|
||||
except sqlite3.OperationalError:
|
||||
logger.warn(u"Unable to remove duplicate libraries from library_sections table.")
|
||||
|
||||
# Upgrade users table from earlier versions (remove UNIQUE constraint on username)
|
||||
try:
|
||||
result = c_db.execute('PRAGMA index_xinfo("sqlite_autoindex_users_2")')
|
||||
if result and 'username' in [row[2] for row in result]:
|
||||
result = c_db.execute('SELECT SQL FROM sqlite_master WHERE type="table" AND name="users"').fetchone()
|
||||
if 'username TEXT NOT NULL UNIQUE' in result[0]:
|
||||
logger.debug(u"Altering database. Removing unique constraint on username from users table.")
|
||||
c_db.execute(
|
||||
'CREATE TABLE users_temp (id INTEGER PRIMARY KEY AUTOINCREMENT, '
|
||||
@@ -773,7 +784,7 @@ def dbcheck():
|
||||
'ALTER TABLE users_temp RENAME TO users'
|
||||
)
|
||||
except sqlite3.OperationalError:
|
||||
logger.debug(u"Unable to remove username unique constraint from users.")
|
||||
logger.warn(u"Unable to remove username unique constraint from users.")
|
||||
try:
|
||||
c_db.execute(
|
||||
'DROP TABLE users_temp'
|
||||
|
@@ -156,8 +156,8 @@ class ActivityHandler(object):
|
||||
(self.get_session_key(), buffer_last_triggered))
|
||||
time_since_last_trigger = int(time.time()) - int(buffer_last_triggered)
|
||||
|
||||
if current_buffer_count >= plexpy.CONFIG.BUFFER_THRESHOLD and time_since_last_trigger == 0 or \
|
||||
time_since_last_trigger >= plexpy.CONFIG.BUFFER_WAIT:
|
||||
if plexpy.CONFIG.BUFFER_THRESHOLD > 0 and (current_buffer_count >= plexpy.CONFIG.BUFFER_THRESHOLD and \
|
||||
time_since_last_trigger == 0 or time_since_last_trigger >= plexpy.CONFIG.BUFFER_WAIT):
|
||||
ap.set_session_buffer_trigger_time(session_key=self.get_session_key())
|
||||
threading.Thread(target=notification_handler.notify,
|
||||
kwargs=dict(stream_data=db_stream, notify_action='buffer')).start()
|
||||
|
@@ -360,9 +360,10 @@ _CONFIG_DEFINITIONS = {
|
||||
'TV_NOTIFY_ON_STOP': (int, 'Monitoring', 0),
|
||||
'TV_NOTIFY_ON_PAUSE': (int, 'Monitoring', 0),
|
||||
'TWITTER_ENABLED': (int, 'Twitter', 0),
|
||||
'TWITTER_PASSWORD': (str, 'Twitter', ''),
|
||||
'TWITTER_PREFIX': (str, 'Twitter', 'PlexPy'),
|
||||
'TWITTER_USERNAME': (str, 'Twitter', ''),
|
||||
'TWITTER_ACCESS_TOKEN': (str, 'Twitter', ''),
|
||||
'TWITTER_ACCESS_TOKEN_SECRET': (str, 'Twitter', ''),
|
||||
'TWITTER_CONSUMER_KEY': (str, 'Twitter', ''),
|
||||
'TWITTER_CONSUMER_SECRET': (str, 'Twitter', ''),
|
||||
'TWITTER_ON_PLAY': (int, 'Twitter', 0),
|
||||
'TWITTER_ON_STOP': (int, 'Twitter', 0),
|
||||
'TWITTER_ON_PAUSE': (int, 'Twitter', 0),
|
||||
@@ -511,6 +512,7 @@ class Config(object):
|
||||
self.MOVIE_LOGGING_ENABLE = 0
|
||||
self.TV_LOGGING_ENABLE = 0
|
||||
self.CONFIG_VERSION = '1'
|
||||
|
||||
if self.CONFIG_VERSION == '1':
|
||||
# Change home_stats_cards to list
|
||||
if self.HOME_STATS_CARDS:
|
||||
@@ -525,3 +527,19 @@ class Config(object):
|
||||
home_library_cards.remove('library_statistics')
|
||||
self.HOME_LIBRARY_CARDS = home_library_cards
|
||||
self.CONFIG_VERSION = '2'
|
||||
|
||||
if self.CONFIG_VERSION == '2':
|
||||
self.NOTIFY_ON_START_SUBJECT_TEXT = self.NOTIFY_ON_START_SUBJECT_TEXT.replace('{progress}','{progress_duration}')
|
||||
self.NOTIFY_ON_START_BODY_TEXT = self.NOTIFY_ON_START_BODY_TEXT.replace('{progress}','{progress_duration}')
|
||||
self.NOTIFY_ON_STOP_SUBJECT_TEXT = self.NOTIFY_ON_STOP_SUBJECT_TEXT.replace('{progress}','{progress_duration}')
|
||||
self.NOTIFY_ON_STOP_BODY_TEXT = self.NOTIFY_ON_STOP_BODY_TEXT.replace('{progress}','{progress_duration}')
|
||||
self.NOTIFY_ON_PAUSE_SUBJECT_TEXT = self.NOTIFY_ON_PAUSE_SUBJECT_TEXT.replace('{progress}','{progress_duration}')
|
||||
self.NOTIFY_ON_PAUSE_BODY_TEXT = self.NOTIFY_ON_PAUSE_BODY_TEXT.replace('{progress}','{progress_duration}')
|
||||
self.NOTIFY_ON_RESUME_SUBJECT_TEXT = self.NOTIFY_ON_RESUME_SUBJECT_TEXT.replace('{progress}','{progress_duration}')
|
||||
self.NOTIFY_ON_RESUME_BODY_TEXT = self.NOTIFY_ON_RESUME_BODY_TEXT.replace('{progress}','{progress_duration}')
|
||||
self.NOTIFY_ON_BUFFER_SUBJECT_TEXT = self.NOTIFY_ON_BUFFER_SUBJECT_TEXT.replace('{progress}','{progress_duration}')
|
||||
self.NOTIFY_ON_BUFFER_BODY_TEXT = self.NOTIFY_ON_BUFFER_BODY_TEXT.replace('{progress}','{progress_duration}')
|
||||
self.NOTIFY_ON_WATCHED_SUBJECT_TEXT = self.NOTIFY_ON_WATCHED_SUBJECT_TEXT.replace('{progress}','{progress_duration}')
|
||||
self.NOTIFY_ON_WATCHED_BODY_TEXT = self.NOTIFY_ON_WATCHED_BODY_TEXT.replace('{progress}','{progress_duration}')
|
||||
self.NOTIFY_SCRIPTS_ARGS_TEXT = self.NOTIFY_SCRIPTS_ARGS_TEXT.replace('{progress}','{progress_duration}')
|
||||
self.CONFIG_VERSION = '3'
|
||||
|
@@ -14,9 +14,9 @@
|
||||
# along with PlexPy. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from plexpy import logger, database, helpers, common
|
||||
import plexpy
|
||||
|
||||
import datetime
|
||||
import locale
|
||||
|
||||
|
||||
class Graphs(object):
|
||||
@@ -321,7 +321,7 @@ class Graphs(object):
|
||||
dt = datetime.datetime(*month_item[:6])
|
||||
date_string = dt.strftime('%Y-%m')
|
||||
|
||||
categories.append(dt.strftime('%b %Y').decode(locale.getlocale()[1]))
|
||||
categories.append(dt.strftime('%b %Y').decode(plexpy.SYS_ENCODING, 'replace'))
|
||||
series_1_value = 0
|
||||
series_2_value = 0
|
||||
series_3_value = 0
|
||||
|
@@ -135,6 +135,15 @@ def convert_seconds(s):
|
||||
|
||||
return minutes
|
||||
|
||||
def convert_seconds_to_minutes(s):
|
||||
|
||||
if str(s).isdigit():
|
||||
minutes = round(float(s) / 60, 0)
|
||||
|
||||
return math.trunc(minutes)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def today():
|
||||
today = datetime.date.today()
|
||||
|
@@ -44,7 +44,8 @@ class HTTPHandler(object):
|
||||
headers=None,
|
||||
output_format='raw',
|
||||
return_type=False,
|
||||
no_token=False):
|
||||
no_token=False,
|
||||
timeout=20):
|
||||
|
||||
valid_request_types = ['GET', 'POST', 'PUT', 'DELETE']
|
||||
|
||||
@@ -56,12 +57,12 @@ class HTTPHandler(object):
|
||||
if proto.upper() == 'HTTPS':
|
||||
if not self.ssl_verify and hasattr(ssl, '_create_unverified_context'):
|
||||
context = ssl._create_unverified_context()
|
||||
handler = HTTPSConnection(host=self.host, port=self.port, timeout=20, context=context)
|
||||
handler = HTTPSConnection(host=self.host, port=self.port, timeout=timeout, context=context)
|
||||
logger.warn(u"PlexPy HTTP Handler :: Unverified HTTPS request made. This connection is not secure.")
|
||||
else:
|
||||
handler = HTTPSConnection(host=self.host, port=self.port, timeout=20)
|
||||
handler = HTTPSConnection(host=self.host, port=self.port, timeout=timeout)
|
||||
else:
|
||||
handler = HTTPConnection(host=self.host, port=self.port, timeout=20)
|
||||
handler = HTTPConnection(host=self.host, port=self.port, timeout=timeout)
|
||||
|
||||
token_string = ''
|
||||
if not no_token:
|
||||
|
@@ -22,8 +22,6 @@ def update_section_ids():
|
||||
|
||||
plexpy.CONFIG.UPDATE_SECTION_IDS = -1
|
||||
|
||||
logger.info(u"PlexPy Libraries :: Updating section_id's in database.")
|
||||
|
||||
#logger.debug(u"PlexPy Libraries :: Disabling monitoring while update in progress.")
|
||||
#plexpy.schedule_job(activity_pinger.check_active_sessions, 'Check for active sessions',
|
||||
# hours=0, minutes=0, seconds=0)
|
||||
@@ -35,7 +33,8 @@ def update_section_ids():
|
||||
monitor_db = database.MonitorDatabase()
|
||||
|
||||
try:
|
||||
query = 'SELECT id, rating_key FROM session_history_metadata WHERE section_id IS NULL'
|
||||
query = 'SELECT id, rating_key, grandparent_rating_key, media_type ' \
|
||||
'FROM session_history_metadata WHERE section_id IS NULL'
|
||||
history_results = monitor_db.select(query=query)
|
||||
query = 'SELECT section_id, section_type FROM library_sections'
|
||||
library_results = monitor_db.select(query=query)
|
||||
@@ -43,13 +42,20 @@ def update_section_ids():
|
||||
logger.warn(u"PlexPy Libraries :: Unable to execute database query for update_section_ids: %s." % e)
|
||||
|
||||
logger.warn(u"PlexPy Libraries :: Unable to update section_id's in database.")
|
||||
plexpy.CONFIG.__setattr__('UPDATE_SECTION_IDS', 1)
|
||||
plexpy.CONFIG.UPDATE_SECTION_IDS = 1
|
||||
plexpy.CONFIG.write()
|
||||
|
||||
#logger.debug(u"PlexPy Libraries :: Re-enabling monitoring.")
|
||||
#plexpy.initialize_scheduler()
|
||||
return None
|
||||
|
||||
if not history_results:
|
||||
plexpy.CONFIG.UPDATE_SECTION_IDS = 0
|
||||
plexpy.CONFIG.write()
|
||||
return None
|
||||
|
||||
logger.info(u"PlexPy Libraries :: Updating section_id's in database.")
|
||||
|
||||
# Add thread filter to the logger
|
||||
#logger.debug(u"PlexPy Libraries :: Disabling logging in the current thread while update in progress.")
|
||||
#thread_filter = logger.NoThreadFilter(threading.current_thread().name)
|
||||
@@ -58,21 +64,15 @@ def update_section_ids():
|
||||
|
||||
# Get rating_key: section_id mapping pairs
|
||||
key_mappings = {}
|
||||
section_type_child = {'movie': 'movie',
|
||||
'show': 'episode',
|
||||
'artist': 'track'}
|
||||
|
||||
pms_connect = pmsconnect.PmsConnect()
|
||||
for library in library_results:
|
||||
section_id = library['section_id']
|
||||
section_type = section_type_child.get(library['section_type'], None)
|
||||
section_type = library['section_type']
|
||||
|
||||
if section_type:
|
||||
if section_type != 'photo':
|
||||
library_children = pms_connect.get_library_children_details(section_id=section_id,
|
||||
section_type=section_type)
|
||||
else:
|
||||
continue
|
||||
|
||||
if library_children:
|
||||
children_list = library_children['childern_list']
|
||||
key_mappings.update({child['rating_key']:child['section_id'] for child in children_list})
|
||||
@@ -81,15 +81,18 @@ def update_section_ids():
|
||||
|
||||
error_keys = set()
|
||||
for item in history_results:
|
||||
rating_key = item['rating_key']
|
||||
rating_key = item['grandparent_rating_key'] if item['media_type'] != 'movie' else item['rating_key']
|
||||
section_id = key_mappings.get(str(rating_key), None)
|
||||
|
||||
if section_id:
|
||||
try:
|
||||
section_keys = {'id': item['id']}
|
||||
section_values = {'section_id': section_id}
|
||||
monitor_db.upsert('session_history_metadata', key_dict=section_keys, value_dict=section_values)
|
||||
except:
|
||||
error_keys.add(item['rating_key'])
|
||||
else:
|
||||
error_keys.add(rating_key)
|
||||
error_keys.add(item['rating_key'])
|
||||
|
||||
# Remove thread filter from the logger
|
||||
#for handler in logger.logger.handlers:
|
||||
@@ -102,7 +105,7 @@ def update_section_ids():
|
||||
else:
|
||||
logger.info(u"PlexPy Libraries :: Updated all section_id's in database.")
|
||||
|
||||
plexpy.CONFIG.__setattr__('UPDATE_SECTION_IDS', 0)
|
||||
plexpy.CONFIG.UPDATE_SECTION_IDS = 0
|
||||
plexpy.CONFIG.write()
|
||||
|
||||
#logger.debug(u"PlexPy Libraries :: Re-enabling monitoring.")
|
||||
@@ -574,11 +577,10 @@ class Libraries(object):
|
||||
return library_details
|
||||
else:
|
||||
logger.warn(u"PlexPy Libraries :: Unable to retrieve library from local database. Requesting library list refresh.")
|
||||
# Let's first refresh the user list to make sure the user isn't newly added and not in the db yet
|
||||
# Let's first refresh the libraries list to make sure the library isn't newly added and not in the db yet
|
||||
pmsconnect.refresh_libraries()
|
||||
try:
|
||||
if section_id:
|
||||
# Refresh libraries
|
||||
pmsconnect.refresh_libraries()
|
||||
query = 'SELECT section_id, section_name, section_type, count, parent_count, child_count, ' \
|
||||
'thumb AS library_thumb, custom_thumb_url AS custom_thumb, art, ' \
|
||||
'do_notify, do_notify_created, keep_history ' \
|
||||
@@ -877,3 +879,21 @@ class Libraries(object):
|
||||
return 'Unable to delete media info table cache, section_id not valid.'
|
||||
except Exception as e:
|
||||
logger.warn(u"PlexPy Libraries :: Unable to delete media info table cache: %s." % e)
|
||||
|
||||
def delete_duplicate_libraries(self):
|
||||
from plexpy import plextv
|
||||
|
||||
monitor_db = database.MonitorDatabase()
|
||||
|
||||
# Refresh the PMS_URL to make sure the server_id is updated
|
||||
plextv.get_real_pms_url()
|
||||
|
||||
server_id = plexpy.CONFIG.PMS_IDENTIFIER
|
||||
|
||||
try:
|
||||
logger.debug(u"PlexPy Libraries :: Deleting libraries where server_id does not match %s." % server_id)
|
||||
monitor_db.action('DELETE FROM library_sections WHERE server_id != ?', [server_id])
|
||||
|
||||
return 'Deleted duplicate libraries from the database.'
|
||||
except Exception as e:
|
||||
logger.warn(u"PlexPy Libraries :: Unable to delete duplicate libraries: %s." % e)
|
@@ -211,12 +211,6 @@ def notify(stream_data=None, notify_action=None):
|
||||
|
||||
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
|
||||
@@ -346,6 +340,11 @@ def set_notify_state(session, state, agent_info):
|
||||
|
||||
|
||||
def build_notify_text(session=None, timeline=None, state=None):
|
||||
# Get time formats
|
||||
date_format = plexpy.CONFIG.DATE_FORMAT.replace('Do','').replace('zz','')
|
||||
time_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('zz','')
|
||||
duration_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('zz','').replace('a','').replace('A','')
|
||||
|
||||
# Get the server name
|
||||
server_name = plexpy.CONFIG.PMS_NAME
|
||||
|
||||
@@ -434,78 +433,30 @@ def build_notify_text(session=None, timeline=None, state=None):
|
||||
else:
|
||||
full_title = metadata['title']
|
||||
|
||||
duration = helpers.convert_milliseconds_to_minutes(metadata['duration'])
|
||||
|
||||
# Default values
|
||||
user = ''
|
||||
platform = ''
|
||||
player = ''
|
||||
ip_address = 'N/A'
|
||||
stream_duration = 0
|
||||
view_offset = 0
|
||||
container = ''
|
||||
video_codec = ''
|
||||
video_bitrate = ''
|
||||
video_width = ''
|
||||
video_height = ''
|
||||
video_resolution = ''
|
||||
video_framerate = ''
|
||||
aspect_ratio = ''
|
||||
audio_codec = ''
|
||||
audio_channels = ''
|
||||
transcode_decision = ''
|
||||
video_decision = ''
|
||||
audio_decision = ''
|
||||
transcode_container = ''
|
||||
transcode_video_codec = ''
|
||||
transcode_video_width = ''
|
||||
transcode_video_height = ''
|
||||
transcode_audio_codec = ''
|
||||
transcode_audio_channels = ''
|
||||
|
||||
# Session values
|
||||
if session:
|
||||
# Generate a combined transcode decision value
|
||||
video_decision = session['video_decision'].title()
|
||||
audio_decision = session['audio_decision'].title()
|
||||
if session is None:
|
||||
session = {}
|
||||
|
||||
if session['video_decision'] == 'transcode' or session['audio_decision'] == 'transcode':
|
||||
# Generate a combined transcode decision value
|
||||
if session.get('video_decision','') == 'transcode' or session.get('audio_decision','') == 'transcode':
|
||||
transcode_decision = 'Transcode'
|
||||
elif session['video_decision'] == 'copy' or session['audio_decision'] == 'copy':
|
||||
elif session.get('video_decision','') == 'copy' or session.get('audio_decision','') == 'copy':
|
||||
transcode_decision = 'Direct Stream'
|
||||
else:
|
||||
transcode_decision = 'Direct Play'
|
||||
|
||||
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)
|
||||
stream_duration = helpers.convert_seconds_to_minutes(
|
||||
time.time() -
|
||||
helpers.cast_to_float(session.get('started', 0)) -
|
||||
helpers.cast_to_float(session.get('paused_counter', 0)))
|
||||
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'
|
||||
container = session['container']
|
||||
video_codec = session['video_codec']
|
||||
video_bitrate = session['bitrate']
|
||||
video_width = session['width']
|
||||
video_height = session['height']
|
||||
video_resolution = session['video_resolution']
|
||||
video_framerate = session['video_framerate']
|
||||
aspect_ratio = session['aspect_ratio']
|
||||
audio_codec = session['audio_codec']
|
||||
audio_channels = session['audio_channels']
|
||||
transcode_container = session['transcode_container']
|
||||
transcode_video_codec = session['transcode_video_codec']
|
||||
transcode_video_width = session['transcode_width']
|
||||
transcode_video_height = session['transcode_height']
|
||||
transcode_audio_codec = session['transcode_audio_codec']
|
||||
transcode_audio_channels = session['transcode_audio_channels']
|
||||
stream_duration = 0
|
||||
|
||||
view_offset = helpers.convert_milliseconds_to_minutes(session.get('view_offset', 0))
|
||||
duration = helpers.convert_milliseconds_to_minutes(metadata['duration'])
|
||||
progress_percent = helpers.get_percent(view_offset, duration)
|
||||
remaining_duration = duration - view_offset
|
||||
|
||||
# Fix metadata params for notify recently added grandparent
|
||||
if state == 'created' and plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_GRANDPARENT:
|
||||
@@ -521,40 +472,48 @@ def build_notify_text(session=None, timeline=None, state=None):
|
||||
album_name = metadata['parent_title']
|
||||
track_name = metadata['title']
|
||||
|
||||
available_params = {'server_name': server_name,
|
||||
available_params = {# Global paramaters
|
||||
'server_name': server_name,
|
||||
'server_uptime': server_uptime,
|
||||
'action': state.title(),
|
||||
'datestamp': arrow.now().format(date_format),
|
||||
'timestamp': arrow.now().format(time_format),
|
||||
# Stream parameters
|
||||
'streams': stream_count,
|
||||
'action': state,
|
||||
'datestamp': arrow.now().format(plexpy.CONFIG.DATE_FORMAT.replace('Do','').replace('zz','')),
|
||||
'timestamp': arrow.now().format(plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('zz','')),
|
||||
'user': user,
|
||||
'platform': platform,
|
||||
'player': player,
|
||||
'ip_address': ip_address,
|
||||
'media_type': metadata['media_type'],
|
||||
'user': session.get('friendly_name',''),
|
||||
'platform': session.get('platform',''),
|
||||
'player': session.get('player',''),
|
||||
'ip_address': session.get('ip_address','N/A'),
|
||||
'stream_duration': stream_duration,
|
||||
'remaining_duration': duration - view_offset,
|
||||
'progress': view_offset,
|
||||
'stream_time': arrow.get(stream_duration * 60).format(duration_format),
|
||||
'remaining_duration': remaining_duration,
|
||||
'remaining_time': arrow.get(remaining_duration * 60).format(duration_format),
|
||||
'progress_duration': view_offset,
|
||||
'progress_time': arrow.get(view_offset * 60).format(duration_format),
|
||||
'progress_percent': progress_percent,
|
||||
'container': container,
|
||||
'video_codec': video_codec,
|
||||
'video_bitrate': video_bitrate,
|
||||
'video_width': video_width,
|
||||
'video_height': video_height,
|
||||
'video_resolution': video_resolution,
|
||||
'video_framerate': video_framerate,
|
||||
'aspect_ratio': aspect_ratio,
|
||||
'audio_codec': audio_codec,
|
||||
'audio_channels': audio_channels,
|
||||
'container': session.get('container',''),
|
||||
'video_codec': session.get('video_codec',''),
|
||||
'video_bitrate': session.get('bitrate',''),
|
||||
'video_width': session.get('width',''),
|
||||
'video_height': session.get('height',''),
|
||||
'video_resolution': session.get('video_resolution',''),
|
||||
'video_framerate': session.get('video_framerate',''),
|
||||
'aspect_ratio': session.get('aspect_ratio',''),
|
||||
'audio_codec': session.get('audio_codec',''),
|
||||
'audio_channels': session.get('audio_channels',''),
|
||||
'transcode_decision': transcode_decision,
|
||||
'video_decision': video_decision,
|
||||
'audio_decision': audio_decision,
|
||||
'transcode_container': transcode_container,
|
||||
'transcode_video_codec': transcode_video_codec,
|
||||
'transcode_video_width': transcode_video_width,
|
||||
'transcode_video_height': transcode_video_height,
|
||||
'transcode_audio_codec': transcode_audio_codec,
|
||||
'transcode_audio_channels': transcode_audio_channels,
|
||||
'video_decision': session.get('video_decision','').title(),
|
||||
'audio_decision': session.get('audio_decision','').title(),
|
||||
'transcode_container': session.get('transcode_container',''),
|
||||
'transcode_video_codec': session.get('transcode_video_codec',''),
|
||||
'transcode_video_width': session.get('transcode_width',''),
|
||||
'transcode_video_height': session.get('transcode_height',''),
|
||||
'transcode_audio_codec': session.get('transcode_audio_codec',''),
|
||||
'transcode_audio_channels': session.get('transcode_audio_channels',''),
|
||||
'session_key': session.get('session_key',''),
|
||||
'user_id': session.get('user_id',''),
|
||||
# Metadata parameters
|
||||
'media_type': metadata['media_type'],
|
||||
'title': full_title,
|
||||
'library_name': metadata['library_name'],
|
||||
'show_name': show_name,
|
||||
@@ -566,6 +525,8 @@ def build_notify_text(session=None, timeline=None, state=None):
|
||||
'season_num00': metadata['parent_media_index'].zfill(2),
|
||||
'episode_num': metadata['media_index'].zfill(1),
|
||||
'episode_num00': metadata['media_index'].zfill(2),
|
||||
'track_num': metadata['media_index'].zfill(1),
|
||||
'track_num00': metadata['media_index'].zfill(2),
|
||||
'year': metadata['year'],
|
||||
'studio': metadata['studio'],
|
||||
'content_rating': metadata['content_rating'],
|
||||
@@ -576,7 +537,11 @@ def build_notify_text(session=None, timeline=None, state=None):
|
||||
'summary': metadata['summary'],
|
||||
'tagline': metadata['tagline'],
|
||||
'rating': metadata['rating'],
|
||||
'duration': duration
|
||||
'duration': metadata['duration'],
|
||||
'section_id': metadata['section_id'],
|
||||
'rating_key': metadata['rating_key'],
|
||||
'parent_rating_key': metadata['parent_rating_key'],
|
||||
'grandparent_rating_key': metadata['grandparent_rating_key']
|
||||
}
|
||||
|
||||
# Default subject text
|
||||
@@ -585,10 +550,6 @@ def build_notify_text(session=None, timeline=None, state=None):
|
||||
# Default scripts args
|
||||
script_args = []
|
||||
|
||||
# Regex to match {param} but not "{param}"
|
||||
params_to_quote = re.compile(r'(?<!\")([\{][^}]+[\}])(?!\"\})')
|
||||
script_args_text = re.sub(params_to_quote, r'"\g<0>"', script_args_text)
|
||||
|
||||
if script_args_text:
|
||||
try:
|
||||
script_args = [unicode(arg).format(**available_params) for arg in script_args_text.split()]
|
||||
@@ -599,7 +560,7 @@ def build_notify_text(session=None, timeline=None, state=None):
|
||||
|
||||
if state == 'play':
|
||||
# Default body text
|
||||
body_text = '%s (%s) is watching %s' % (session['friendly_name'],
|
||||
body_text = '%s (%s) started playing %s' % (session['friendly_name'],
|
||||
session['player'],
|
||||
full_title)
|
||||
|
||||
@@ -768,6 +729,10 @@ def build_notify_text(session=None, timeline=None, state=None):
|
||||
|
||||
|
||||
def build_server_notify_text(state=None):
|
||||
# Get time formats
|
||||
date_format = plexpy.CONFIG.DATE_FORMAT.replace('Do','').replace('zz','')
|
||||
time_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('zz','')
|
||||
|
||||
# Get the server name
|
||||
server_name = plexpy.CONFIG.PMS_NAME
|
||||
|
||||
@@ -792,11 +757,12 @@ def build_server_notify_text(state=None):
|
||||
on_intup_body = plexpy.CONFIG.NOTIFY_ON_INTUP_BODY_TEXT
|
||||
script_args_text = plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT
|
||||
|
||||
available_params = {'server_name': server_name,
|
||||
available_params = {# Global paramaters
|
||||
'server_name': server_name,
|
||||
'server_uptime': server_uptime,
|
||||
'action': state,
|
||||
'datestamp': arrow.now().format(plexpy.CONFIG.DATE_FORMAT.replace('Do','').replace('zz','')),
|
||||
'timestamp': arrow.now().format(plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('zz',''))}
|
||||
'action': state.title(),
|
||||
'datestamp': arrow.now().format(date_format),
|
||||
'timestamp': arrow.now().format(time_format)}
|
||||
|
||||
# Default text
|
||||
subject_text = 'PlexPy (%s)' % server_name
|
||||
@@ -804,10 +770,6 @@ def build_server_notify_text(state=None):
|
||||
# Default scripts args
|
||||
script_args = []
|
||||
|
||||
# Regex to match {param} but not "{param}"
|
||||
params_to_quote = re.compile(r'(?<!\")([\{][^}]+[\}])(?!\"\})')
|
||||
script_args_text = re.sub(params_to_quote, r'"\g<0>"', script_args_text)
|
||||
|
||||
if script_args_text:
|
||||
try:
|
||||
script_args = [unicode(arg).format(**available_params) for arg in script_args_text.split()]
|
||||
|
@@ -1165,8 +1165,10 @@ class TwitterNotifier(object):
|
||||
SIGNIN_URL = 'https://api.twitter.com/oauth/authenticate'
|
||||
|
||||
def __init__(self):
|
||||
self.consumer_key = "2LdJKXHDUwJtjYBsdwJisIOsh"
|
||||
self.consumer_secret = "QWbUcZzAIiL4zbDCIhy2EdUkV8yEEav3qMdo5y3FugxCFelWrA"
|
||||
self.access_token = plexpy.CONFIG.TWITTER_ACCESS_TOKEN
|
||||
self.access_token_secret = plexpy.CONFIG.TWITTER_ACCESS_TOKEN_SECRET
|
||||
self.consumer_key = plexpy.CONFIG.TWITTER_CONSUMER_KEY
|
||||
self.consumer_secret = plexpy.CONFIG.TWITTER_CONSUMER_SECRET
|
||||
|
||||
def notify(self, subject, message):
|
||||
if not subject or not message:
|
||||
@@ -1191,16 +1193,16 @@ class TwitterNotifier(object):
|
||||
else:
|
||||
request_token = dict(parse_qsl(content))
|
||||
|
||||
plexpy.CONFIG.TWITTER_USERNAME = request_token['oauth_token']
|
||||
plexpy.CONFIG.TWITTER_PASSWORD = request_token['oauth_token_secret']
|
||||
plexpy.CONFIG.TWITTER_ACCESS_TOKEN = request_token['oauth_token']
|
||||
plexpy.CONFIG.TWITTER_ACCESS_TOKEN_SECRET = request_token['oauth_token_secret']
|
||||
|
||||
return self.AUTHORIZATION_URL + "?oauth_token=" + request_token['oauth_token']
|
||||
|
||||
def _get_credentials(self, key):
|
||||
request_token = {}
|
||||
|
||||
request_token['oauth_token'] = plexpy.CONFIG.TWITTER_USERNAME
|
||||
request_token['oauth_token_secret'] = plexpy.CONFIG.TWITTER_PASSWORD
|
||||
request_token['oauth_token'] = plexpy.CONFIG.TWITTER_ACCESS_TOKEN
|
||||
request_token['oauth_token_secret'] = plexpy.CONFIG.TWITTER_ACCESS_TOKEN_SECRET
|
||||
request_token['oauth_callback_confirmed'] = 'true'
|
||||
|
||||
token = oauth.Token(request_token['oauth_token'], request_token['oauth_token_secret'])
|
||||
@@ -1225,20 +1227,20 @@ class TwitterNotifier(object):
|
||||
else:
|
||||
# logger.info(u"PlexPy Notifiers :: Your Twitter Access Token key: %s" % access_token['oauth_token'])
|
||||
# logger.info(u"PlexPy Notifiers :: Access Token secret: %s" % access_token['oauth_token_secret'])
|
||||
plexpy.CONFIG.TWITTER_USERNAME = access_token['oauth_token']
|
||||
plexpy.CONFIG.TWITTER_PASSWORD = access_token['oauth_token_secret']
|
||||
plexpy.CONFIG.TWITTER_ACCESS_TOKEN = access_token['oauth_token']
|
||||
plexpy.CONFIG.TWITTER_ACCESS_TOKEN_SECRET = access_token['oauth_token_secret']
|
||||
plexpy.CONFIG.write()
|
||||
return True
|
||||
|
||||
def _send_tweet(self, message=None):
|
||||
username = self.consumer_key
|
||||
password = self.consumer_secret
|
||||
access_token_key = plexpy.CONFIG.TWITTER_USERNAME
|
||||
access_token_secret = plexpy.CONFIG.TWITTER_PASSWORD
|
||||
consumer_key = self.consumer_key
|
||||
consumer_secret = self.consumer_secret
|
||||
access_token = self.access_token
|
||||
access_token_secret = self.access_token_secret
|
||||
|
||||
# logger.info(u"PlexPy Notifiers :: Sending tweet: " + message)
|
||||
|
||||
api = twitter.Api(username, password, access_token_key, access_token_secret)
|
||||
api = twitter.Api(consumer_key, consumer_secret, access_token, access_token_secret)
|
||||
|
||||
try:
|
||||
api.PostUpdate(message)
|
||||
@@ -1251,30 +1253,37 @@ class TwitterNotifier(object):
|
||||
|
||||
def return_config_options(self):
|
||||
config_option = [{'label': 'Instructions',
|
||||
'description': 'Step 1: Click the <strong>Request Authorization</strong> button below.<br>\
|
||||
Step 2: Input the <strong>Authorization Key</strong> you received from Step 1 below.<br>\
|
||||
Step 3: Click the <strong>Verify Key</strong> button below.',
|
||||
'description': 'Step 1: Visit <a href="https://apps.twitter.com/" target="_blank"> \
|
||||
Twitter Apps</a> to <strong>Create New App</strong>. A vaild "Website" is not required.<br>\
|
||||
Step 2: Go to <strong>Keys and Access Tokens</strong> and click \
|
||||
<strong>Create my access token</strong>.<br>\
|
||||
Step 3: Fill in the <strong>Consumer Key</strong>, <strong>Consumer Secret</strong>, \
|
||||
<strong>Access Token</strong>, and <strong>Access Token Secret</strong> below.',
|
||||
'input_type': 'help'
|
||||
},
|
||||
{'label': 'Request Authorization',
|
||||
'value': 'Request Authorization',
|
||||
'name': 'twitterStep1',
|
||||
'description': 'Request Twitter authorization. (Ensure you allow the browser pop-up).',
|
||||
'input_type': 'button'
|
||||
},
|
||||
{'label': 'Authorization Key',
|
||||
'value': '',
|
||||
'name': 'twitter_key',
|
||||
'description': 'Your Twitter authorization key.',
|
||||
{'label': 'Twitter Consumer Key',
|
||||
'value': self.consumer_key,
|
||||
'name': 'twitter_consumer_key',
|
||||
'description': 'Your Twitter consumer key.',
|
||||
'input_type': 'text'
|
||||
},
|
||||
{'label': 'Verify Key',
|
||||
'value': 'Verify Key',
|
||||
'name': 'twitterStep2',
|
||||
'description': 'Verify your Twitter authorization key.',
|
||||
'input_type': 'button'
|
||||
{'label': 'Twitter Consumer Secret',
|
||||
'value': self.consumer_secret,
|
||||
'name': 'twitter_consumer_secret',
|
||||
'description': 'Your Twitter consumer secret.',
|
||||
'input_type': 'text'
|
||||
},
|
||||
{'input_type': 'nosave'
|
||||
{'label': 'Twitter Access Token',
|
||||
'value': self.access_token,
|
||||
'name': 'twitter_access_token',
|
||||
'description': 'Your Twitter access token.',
|
||||
'input_type': 'text'
|
||||
},
|
||||
{'label': 'Twitter Access Token Secret',
|
||||
'value': self.access_token_secret,
|
||||
'name': 'twitter_access_token_secret',
|
||||
'description': 'Your Twitter access token secret.',
|
||||
'input_type': 'text'
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1668,10 +1677,10 @@ class TELEGRAM(object):
|
||||
'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',
|
||||
{'label': 'Telegram Chat ID, Group ID, or Channel Username',
|
||||
'value': self.chat_id,
|
||||
'name': 'telegram_chat_id',
|
||||
'description': 'Your Telegram Chat ID, Group ID, or channel username. Contact <a href="http://telegram.me/myidbot" target="_blank">@myidbot</a> on Telegram to get an ID.',
|
||||
'description': 'Your Telegram Chat ID, Group ID, or @channelusername. Contact <a href="http://telegram.me/myidbot" target="_blank">@myidbot</a> on Telegram to get an ID.',
|
||||
'input_type': 'text'
|
||||
}
|
||||
]
|
||||
@@ -1768,7 +1777,7 @@ class SLACK(object):
|
||||
class Scripts(object):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.script_exts = ('.bat', '.cmd', '.exe', '.php', '.pl', '.py', '.pyw', '.rb', '.sh')
|
||||
self.script_exts = ('.bat', '.cmd', '.exe', '.php', '.pl', '.ps1', '.py', '.pyw', '.rb', '.sh')
|
||||
|
||||
def conf(self, options):
|
||||
return cherrypy.config['config'].get('Scripts', options)
|
||||
@@ -1798,7 +1807,7 @@ class Scripts(object):
|
||||
|
||||
return scripts
|
||||
|
||||
def notify(self, subject='', message='', notify_action='', script_args=[], *args, **kwargs):
|
||||
def notify(self, subject='', message='', notify_action='', script_args=None, *args, **kwargs):
|
||||
"""
|
||||
Args:
|
||||
subject(string, optional): Head text,
|
||||
@@ -1809,6 +1818,9 @@ class Scripts(object):
|
||||
logger.debug(u"PlexPy Notifiers :: Trying to run notify script, action: %s, arguments: %s" %
|
||||
(notify_action if notify_action else None, script_args if script_args else None))
|
||||
|
||||
if script_args is None:
|
||||
script_args = []
|
||||
|
||||
if not plexpy.CONFIG.SCRIPTS_FOLDER:
|
||||
return
|
||||
|
||||
@@ -1860,14 +1872,16 @@ class Scripts(object):
|
||||
|
||||
name, ext = os.path.splitext(script)
|
||||
|
||||
if ext == '.py':
|
||||
prefix = 'python'
|
||||
elif ext == '.pyw':
|
||||
prefix = 'pythonw'
|
||||
elif ext == '.php':
|
||||
if ext == '.php':
|
||||
prefix = 'php'
|
||||
elif ext == '.pl':
|
||||
prefix = 'perl'
|
||||
elif ext == '.ps1':
|
||||
prefix = 'powershell -executionPolicy bypass -file'
|
||||
elif ext == '.py':
|
||||
prefix = 'python'
|
||||
elif ext == '.pyw':
|
||||
prefix = 'pythonw'
|
||||
elif ext == '.rb':
|
||||
prefix = 'ruby'
|
||||
else:
|
||||
@@ -1877,7 +1891,7 @@ class Scripts(object):
|
||||
script = script.encode(plexpy.SYS_ENCODING, 'ignore')
|
||||
|
||||
if prefix:
|
||||
script = [prefix, script]
|
||||
script = prefix.split() + [script]
|
||||
else:
|
||||
script = [script]
|
||||
|
||||
@@ -2088,12 +2102,16 @@ class FacebookNotifier(object):
|
||||
config_option = [{'label': 'Instructions',
|
||||
'description': '<strong>Facebook notifications are currently experimental!</strong><br><br> \
|
||||
Step 1: Visit <a href="https://developers.facebook.com/apps/" target="_blank"> \
|
||||
Facebook Developers</a> to create a new app using <strong>advanced setup</strong>.<br>\
|
||||
Step 2: Go to <strong>Settings > Advanced</strong> and fill in \
|
||||
Facebook Developers</a> to add a new app using <strong>basic setup</strong>.<br>\
|
||||
Step 2: Go to <strong>Settings > Basic</strong> and fill in a \
|
||||
<strong>Contact Email</strong>.<br>\
|
||||
Step 3: Go to <strong>Settings > Advanced</strong> and fill in \
|
||||
<strong>Valid OAuth redirect URIs</strong> with your PlexPy URL (i.e. http://localhost:8181).<br>\
|
||||
Step 3: Fill in the <strong>PlexPy URL</strong> below with the exact same URL from Step 2.<br>\
|
||||
Step 4: Fill in the <strong>App ID</strong> and <strong>App Secret</strong> below.<br>\
|
||||
Step 5: Click the <strong>Request Authorization</strong> button below.',
|
||||
Step 4: Go to <strong>App Review</strong> and toggle public to <strong>Yes</strong>.<br>\
|
||||
Step 5: Fill in the <strong>PlexPy URL</strong> below with the exact same URL from Step 3.<br>\
|
||||
Step 6: Fill in the <strong>App ID</strong> and <strong>App Secret</strong> below.<br>\
|
||||
Step 7: Click the <strong>Request Authorization</strong> button below.<br> \
|
||||
Step 8: Fill in the <strong>Group ID</strong> below.',
|
||||
'input_type': 'help'
|
||||
},
|
||||
{'label': 'PlexPy URL',
|
||||
|
@@ -383,7 +383,6 @@ class PlexTV(object):
|
||||
return []
|
||||
|
||||
plextv_resources = self.get_plextv_resources(include_https=include_https)
|
||||
server_urls = []
|
||||
|
||||
try:
|
||||
xml_parse = minidom.parseString(plextv_resources)
|
||||
@@ -400,36 +399,51 @@ class PlexTV(object):
|
||||
logger.warn(u"PlexPy PlexTV :: Unable to parse XML for get_server_urls: %s." % e)
|
||||
return []
|
||||
|
||||
# Function to get all connections for a device
|
||||
def get_connections(device):
|
||||
conn = []
|
||||
connections = device.getElementsByTagName('Connection')
|
||||
|
||||
for c in connections:
|
||||
server_details = {"protocol": helpers.get_xml_attr(c, 'protocol'),
|
||||
"address": helpers.get_xml_attr(c, 'address'),
|
||||
"port": helpers.get_xml_attr(c, 'port'),
|
||||
"uri": helpers.get_xml_attr(c, 'uri'),
|
||||
"local": helpers.get_xml_attr(c, 'local')
|
||||
}
|
||||
conn.append(server_details)
|
||||
|
||||
return conn
|
||||
|
||||
server_urls = []
|
||||
|
||||
# Try to match the device
|
||||
for a in xml_head:
|
||||
if helpers.get_xml_attr(a, 'clientIdentifier') == server_id:
|
||||
connections = a.getElementsByTagName('Connection')
|
||||
for connection in connections:
|
||||
server_details = {"protocol": helpers.get_xml_attr(connection, 'protocol'),
|
||||
"address": helpers.get_xml_attr(connection, 'address'),
|
||||
"port": helpers.get_xml_attr(connection, 'port'),
|
||||
"uri": helpers.get_xml_attr(connection, 'uri'),
|
||||
"local": helpers.get_xml_attr(connection, 'local')
|
||||
}
|
||||
server_urls = get_connections(a)
|
||||
break
|
||||
|
||||
server_urls.append(server_details)
|
||||
# Else try to match the PMS_IP and PMS_PORT
|
||||
else:
|
||||
# Else no device match found
|
||||
if not server_urls:
|
||||
# Try to match the PMS_IP and PMS_PORT
|
||||
for a in xml_head:
|
||||
if helpers.get_xml_attr(a, 'provides') == 'server':
|
||||
connections = a.getElementsByTagName('Connection')
|
||||
|
||||
for connection in connections:
|
||||
if helpers.get_xml_attr(connection, 'address') == plexpy.CONFIG.PMS_IP and \
|
||||
int(helpers.get_xml_attr(connection, 'port')) == plexpy.CONFIG.PMS_PORT:
|
||||
|
||||
plexpy.CONFIG.PMS_IDENTIFIER = helpers.get_xml_attr(a, 'clientIdentifier')
|
||||
plexpy.CONFIG.write()
|
||||
|
||||
logger.info(u"PlexPy PlexTV :: PMS identifier changed from %s to %s." % \
|
||||
(server_id, plexpy.CONFIG.PMS_IDENTIFIER))
|
||||
|
||||
server_details = {"protocol": helpers.get_xml_attr(connection, 'protocol'),
|
||||
"address": helpers.get_xml_attr(connection, 'address'),
|
||||
"port": helpers.get_xml_attr(connection, 'port'),
|
||||
"uri": helpers.get_xml_attr(connection, 'uri'),
|
||||
"local": helpers.get_xml_attr(connection, 'local')
|
||||
}
|
||||
server_urls = get_connections(a)
|
||||
break
|
||||
|
||||
if server_urls:
|
||||
break
|
||||
|
||||
return server_urls
|
||||
|
@@ -40,15 +40,19 @@ def get_server_friendly_name():
|
||||
|
||||
def refresh_libraries():
|
||||
logger.info(u"PlexPy Pmsconnect :: Requesting libraries list refresh...")
|
||||
library_sections = PmsConnect().get_library_details()
|
||||
|
||||
server_id = plexpy.CONFIG.PMS_IDENTIFIER
|
||||
if not server_id:
|
||||
logger.error(u"PlexPy Pmsconnect :: No PMS identifier, cannot refresh libraries. Verify server in settings.")
|
||||
return
|
||||
|
||||
library_keys = []
|
||||
library_sections = PmsConnect().get_library_details()
|
||||
|
||||
if library_sections:
|
||||
monitor_db = database.MonitorDatabase()
|
||||
|
||||
library_keys = []
|
||||
|
||||
for section in library_sections:
|
||||
section_keys = {'server_id': server_id,
|
||||
'section_id': section['section_id']}
|
||||
@@ -1690,8 +1694,8 @@ class PmsConnect(object):
|
||||
section_id = library['section_id']
|
||||
children_list = self.get_library_children_details(section_id=section_id, section_type=section_type, count='1')
|
||||
|
||||
if children_list and children_list['library_count'] != '0':
|
||||
library_stats = {'section_id': library['section_id'],
|
||||
if children_list:
|
||||
library_stats = {'section_id': section_id,
|
||||
'section_name': library['section_name'],
|
||||
'section_type': section_type,
|
||||
'thumb': library['thumb'],
|
||||
|
@@ -292,10 +292,9 @@ class Users(object):
|
||||
else:
|
||||
logger.warn(u"PlexPy Users :: Unable to retrieve user from local database. Requesting user list refresh.")
|
||||
# Let's first refresh the user list to make sure the user isn't newly added and not in the db yet
|
||||
plextv.refresh_users()
|
||||
try:
|
||||
if str(user_id).isdigit():
|
||||
# Refresh users
|
||||
plextv.refresh_users()
|
||||
query = 'SELECT user_id, username, friendly_name, thumb AS user_thumb, custom_avatar_url AS custom_thumb, ' \
|
||||
'email, is_home_user, is_allow_sync, is_restricted, do_notify, keep_history ' \
|
||||
'FROM users ' \
|
||||
|
@@ -1,2 +1,2 @@
|
||||
PLEXPY_VERSION = "master"
|
||||
PLEXPY_RELEASE_VERSION = "1.3.2"
|
||||
PLEXPY_RELEASE_VERSION = "1.3.5"
|
||||
|
@@ -494,6 +494,19 @@ class WebInterface(object):
|
||||
else:
|
||||
return json.dumps({'message': 'Cannot refresh library while getting file sizes.'})
|
||||
|
||||
@cherrypy.expose
|
||||
def delete_duplicate_libraries(self):
|
||||
library_data = libraries.Libraries()
|
||||
|
||||
result = library_data.delete_duplicate_libraries()
|
||||
|
||||
if result:
|
||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||
return json.dumps({'message': result})
|
||||
else:
|
||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||
return json.dumps({'message': 'Unable to delete duplicate libraries from the database.'})
|
||||
|
||||
##### Users #####
|
||||
|
||||
@cherrypy.expose
|
||||
@@ -1151,6 +1164,7 @@ class WebInterface(object):
|
||||
del kwargs[use_config]
|
||||
|
||||
# Check if we should refresh our data
|
||||
server_changed = False
|
||||
refresh_libraries = False
|
||||
refresh_users = False
|
||||
reschedule = False
|
||||
@@ -1173,11 +1187,6 @@ class WebInterface(object):
|
||||
(kwargs['monitor_remote_access'] != plexpy.CONFIG.MONITOR_REMOTE_ACCESS):
|
||||
reschedule = True
|
||||
|
||||
if 'pms_ip' in kwargs:
|
||||
if kwargs['pms_ip'] != plexpy.CONFIG.PMS_IP:
|
||||
refresh_libraries = True
|
||||
refresh_users = True
|
||||
|
||||
# Remove config with 'hscard-' prefix and change home_stats_cards to list
|
||||
if 'home_stats_cards' in kwargs:
|
||||
for k in kwargs.keys():
|
||||
@@ -1198,15 +1207,23 @@ class WebInterface(object):
|
||||
if kwargs['home_library_cards'] == ['first_run_wizard']:
|
||||
refresh_libraries = True
|
||||
|
||||
if 'server_changed' in kwargs:
|
||||
del kwargs['server_changed']
|
||||
server_changed = True
|
||||
refresh_users = True
|
||||
refresh_libraries = True
|
||||
|
||||
plexpy.CONFIG.process_kwargs(kwargs)
|
||||
|
||||
# Write the config
|
||||
plexpy.CONFIG.write()
|
||||
|
||||
# Get new server URLs for SSL communications.
|
||||
if server_changed:
|
||||
plextv.get_real_pms_url()
|
||||
|
||||
# Get new server friendly name.
|
||||
if server_changed:
|
||||
pmsconnect.get_server_friendly_name()
|
||||
|
||||
# Reconfigure scheduler if intervals changed
|
||||
@@ -1380,27 +1397,39 @@ class WebInterface(object):
|
||||
def get_server_id(self, hostname=None, port=None, identifier=None, ssl=0, remote=0, **kwargs):
|
||||
from plexpy import http_handler
|
||||
|
||||
if hostname and port:
|
||||
# Set PMS attributes to get the real PMS url
|
||||
plexpy.CONFIG.__setattr__('PMS_IP', hostname)
|
||||
plexpy.CONFIG.__setattr__('PMS_PORT', port)
|
||||
plexpy.CONFIG.__setattr__('PMS_IDENTIFIER', identifier)
|
||||
plexpy.CONFIG.__setattr__('PMS_SSL', ssl)
|
||||
plexpy.CONFIG.__setattr__('PMS_IS_REMOTE', remote)
|
||||
plexpy.CONFIG.write()
|
||||
# Attempt to get the pms_identifier from plex.tv if the server is published
|
||||
# Works for all PMS SSL settings
|
||||
if not identifier and hostname and port:
|
||||
plex_tv = plextv.PlexTV()
|
||||
servers = plex_tv.discover()
|
||||
|
||||
plextv.get_real_pms_url()
|
||||
|
||||
pms_connect = pmsconnect.PmsConnect()
|
||||
request = pms_connect.get_local_server_identity()
|
||||
for server in servers:
|
||||
if server['ip'] == hostname and server['port'] == port:
|
||||
identifier = server['clientIdentifier']
|
||||
break
|
||||
|
||||
# Fallback to checking /identity endpoint is server is unpublished
|
||||
# Cannot set SSL settings on the PMS if unpublished so 'http' is okay
|
||||
if not identifier:
|
||||
request_handler = http_handler.HTTPHandler(host=hostname,
|
||||
port=port,
|
||||
token=None)
|
||||
uri = '/identity'
|
||||
request = request_handler.make_request(uri=uri,
|
||||
proto='http',
|
||||
request_type='GET',
|
||||
output_format='xml',
|
||||
no_token=True,
|
||||
timeout=10)
|
||||
if request:
|
||||
cherrypy.response.headers['Content-type'] = 'application/xml'
|
||||
return request
|
||||
else:
|
||||
logger.warn(u"Unable to retrieve data for get_server_id.")
|
||||
return None
|
||||
xml_head = request.getElementsByTagName('MediaContainer')[0]
|
||||
identifier = xml_head.getAttribute('machineIdentifier')
|
||||
|
||||
if identifier:
|
||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||
return json.dumps(identifier)
|
||||
else:
|
||||
logger.warn('Unable to retrieve the PMS identifier.')
|
||||
return None
|
||||
|
||||
@cherrypy.expose
|
||||
|
Reference in New Issue
Block a user