Compare commits
29 Commits
v2.0.22-be
...
v2.0.23-be
Author | SHA1 | Date | |
---|---|---|---|
![]() |
522684b2ab | ||
![]() |
feab16b351 | ||
![]() |
ee041db63d | ||
![]() |
2479533d07 | ||
![]() |
d045fd5834 | ||
![]() |
8407f27fed | ||
![]() |
b505286caf | ||
![]() |
feb762ce8b | ||
![]() |
8acdb5af83 | ||
![]() |
5af1294f71 | ||
![]() |
87d2d273d3 | ||
![]() |
b5c52ac71e | ||
![]() |
efe9a15f72 | ||
![]() |
525f1e4b0b | ||
![]() |
d18820b832 | ||
![]() |
7e024fd736 | ||
![]() |
c9c5989474 | ||
![]() |
ce9f96d3be | ||
![]() |
7362dd0bf4 | ||
![]() |
9905ebc144 | ||
![]() |
8f8010884b | ||
![]() |
37afd141be | ||
![]() |
a3643b4302 | ||
![]() |
02cfd8d9b7 | ||
![]() |
941ce439b4 | ||
![]() |
a08bce2073 | ||
![]() |
4e9c8322c3 | ||
![]() |
89bfe85be3 | ||
![]() |
98d994591c |
3
API.md
3
API.md
@@ -1674,7 +1674,8 @@ Optional parameters:
|
|||||||
remote (int): 0 or 1
|
remote (int): 0 or 1
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
string: The unique PMS identifier
|
json:
|
||||||
|
{'identifier': '08u2phnlkdshf890bhdlksghnljsahgleikjfg9t'}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
26
CHANGELOG.md
26
CHANGELOG.md
@@ -1,5 +1,31 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v2.0.23-beta (2018-03-16)
|
||||||
|
|
||||||
|
* Monitoring:
|
||||||
|
* Fix: Certain transcode stream showing incorrectly as direct play in history. Fix is not retroactive.
|
||||||
|
* Notifications:
|
||||||
|
* New: Added season/episode/album/track count to notification parameters.
|
||||||
|
* New: Added "Value 3" setting for IFTTT notifications.
|
||||||
|
* New: Set PLEX_URL, PLEX_TOKEN, TAUTULLI_URL, and TAUTULLI_APIKEY environment variables for scripts.
|
||||||
|
* Fix: Notifications failing to send with invalid custom conditions json.
|
||||||
|
* Fix: Email notifications failing with unicode username/passwords.
|
||||||
|
* Change: Facebook Graph API version updated to v2.12.
|
||||||
|
* UI:
|
||||||
|
* New: Show the Plex Server URL in the settings.
|
||||||
|
* Fix: Incorrect info displayed in the Tautulli login logs.
|
||||||
|
* API:
|
||||||
|
* Fix: API returning empty data if a message was in the original data.
|
||||||
|
* Change: get_server_id command returns json instead of string.
|
||||||
|
* Other:
|
||||||
|
* Fix: Forgot git pull when changing branches in the web UI.
|
||||||
|
|
||||||
|
|
||||||
|
## v2.0.22 (2018-03-10)
|
||||||
|
|
||||||
|
* Tautulli v2 release!
|
||||||
|
|
||||||
|
|
||||||
## v2.0.22-beta (2018-03-09)
|
## v2.0.22-beta (2018-03-09)
|
||||||
|
|
||||||
* Notifications:
|
* Notifications:
|
||||||
|
@@ -186,8 +186,12 @@ def main():
|
|||||||
plexpy.DB_FILE = os.path.join(plexpy.DATA_DIR, database.FILENAME)
|
plexpy.DB_FILE = os.path.join(plexpy.DATA_DIR, database.FILENAME)
|
||||||
|
|
||||||
# Move 'plexpy.db' to 'tautulli.db'
|
# Move 'plexpy.db' to 'tautulli.db'
|
||||||
if os.path.isfile(os.path.join(plexpy.DATA_DIR, 'plexpy.db')):
|
if os.path.isfile(os.path.join(plexpy.DATA_DIR, 'plexpy.db')) and \
|
||||||
|
not os.path.isfile(os.path.join(plexpy.DATA_DIR, plexpy.DB_FILE)):
|
||||||
|
try:
|
||||||
os.rename(os.path.join(plexpy.DATA_DIR, 'plexpy.db'), plexpy.DB_FILE)
|
os.rename(os.path.join(plexpy.DATA_DIR, 'plexpy.db'), plexpy.DB_FILE)
|
||||||
|
except OSError as e:
|
||||||
|
raise SystemExit("Unable to rename plexpy.db to tautulli.db: %s", e)
|
||||||
|
|
||||||
if plexpy.DAEMON:
|
if plexpy.DAEMON:
|
||||||
plexpy.daemonize()
|
plexpy.daemonize()
|
||||||
|
@@ -3694,6 +3694,7 @@ a:hover .overlay-refresh-image:hover {
|
|||||||
}
|
}
|
||||||
.git-group select.form-control {
|
.git-group select.form-control {
|
||||||
width: 50%;
|
width: 50%;
|
||||||
|
height: 32px;
|
||||||
}
|
}
|
||||||
#changelog-modal .modal-body > h2 {
|
#changelog-modal .modal-body > h2 {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
@@ -163,7 +163,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
var media_type = null;
|
var media_type = null;
|
||||||
var selected_user_id = "${_session['user_id']}" == "None" ? null : "${_session['user_id']}";
|
var selected_user_id = "${_session['user_group']}" == "admin" ? null : "${_session['user_id']}";
|
||||||
loadHistoryTable(media_type, selected_user_id);
|
loadHistoryTable(media_type, selected_user_id);
|
||||||
|
|
||||||
% if _session['user_group'] == 'admin':
|
% if _session['user_group'] == 'admin':
|
||||||
|
@@ -552,7 +552,7 @@ DOCUMENTATION :: END
|
|||||||
return {
|
return {
|
||||||
json_data: JSON.stringify( d ),
|
json_data: JSON.stringify( d ),
|
||||||
grandparent_rating_key: "${data['rating_key']}",
|
grandparent_rating_key: "${data['rating_key']}",
|
||||||
user_id: "${_session['user_id']}" == "None" ? null : "${_session['user_id']}"
|
user_id: "${_session['user_group']}" == "admin" ? null : "${_session['user_id']}"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -568,7 +568,7 @@ DOCUMENTATION :: END
|
|||||||
return {
|
return {
|
||||||
json_data: JSON.stringify( d ),
|
json_data: JSON.stringify( d ),
|
||||||
parent_rating_key: "${data['rating_key']}",
|
parent_rating_key: "${data['rating_key']}",
|
||||||
user_id: "${_session['user_id']}" == "None" ? null : "${_session['user_id']}"
|
user_id: "${_session['user_group']}" == "admin" ? null : "${_session['user_id']}"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -584,7 +584,7 @@ DOCUMENTATION :: END
|
|||||||
return {
|
return {
|
||||||
json_data: JSON.stringify( d ),
|
json_data: JSON.stringify( d ),
|
||||||
rating_key: "${data['rating_key']}",
|
rating_key: "${data['rating_key']}",
|
||||||
user_id: "${_session['user_id']}" == "None" ? null : "${_session['user_id']}"
|
user_id: "${_session['user_group']}" == "admin" ? null : "${_session['user_id']}"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -292,7 +292,11 @@ function millisecondsToMinutes(ms, roundToMinute) {
|
|||||||
if (ms > 0) {
|
if (ms > 0) {
|
||||||
var minutes = Math.floor(ms / 60000);
|
var minutes = Math.floor(ms / 60000);
|
||||||
var seconds = ((ms % 60000) / 1000).toFixed(0);
|
var seconds = ((ms % 60000) / 1000).toFixed(0);
|
||||||
return (seconds == 60 ? (minutes+1) + ":00" : minutes + ":" + (seconds < 10 ? "0" : "") + seconds);
|
if (roundToMinute) {
|
||||||
|
return (seconds >= 30 ? (minutes + 1) : minutes);
|
||||||
|
} else {
|
||||||
|
return (seconds == 60 ? (minutes + 1) + ":00" : minutes + ":" + (seconds < 10 ? "0" : "") + seconds);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (roundToMinute) {
|
if (roundToMinute) {
|
||||||
return '0';
|
return '0';
|
||||||
|
@@ -379,7 +379,7 @@ DOCUMENTATION :: END
|
|||||||
return {
|
return {
|
||||||
json_data: JSON.stringify( d ),
|
json_data: JSON.stringify( d ),
|
||||||
section_id: section_id,
|
section_id: section_id,
|
||||||
user_id: "${_session['user_id']}" == "None" ? null : "${_session['user_id']}"
|
user_id: "${_session['user_group']}" == "admin" ? null : "${_session['user_id']}"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -171,7 +171,7 @@
|
|||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="custom_conditions_logic">Condition Logic</label>
|
<label for="custom_conditions_logic">Condition Logic</label>
|
||||||
<input type="text" class="form-control" name="custom_conditions_logic" id="custom_conditions_logic" value="${notifier['custom_conditions_logic']}" required />
|
<input type="text" class="form-control" name="custom_conditions_logic" id="custom_conditions_logic" value="${notifier['custom_conditions_logic']}" />
|
||||||
<div id="custom_conditions_logic_error" class="alert alert-danger" role="alert" style="padding-top: 5px; padding-bottom: 5px; margin: 0; display: none;"><i class="fa fa-exclamation-triangle" style="color: #a94442;"></i> <span></span></div>
|
<div id="custom_conditions_logic_error" class="alert alert-danger" role="alert" style="padding-top: 5px; padding-bottom: 5px; margin: 0; display: none;"><i class="fa fa-exclamation-triangle" style="color: #a94442;"></i> <span></span></div>
|
||||||
<p class="help-block">
|
<p class="help-block">
|
||||||
Optional: Enter custom logic to use when evaluating the conditions (e.g. <span class="inline-pre">{1} and ({2} or {3})</span>).
|
Optional: Enter custom logic to use when evaluating the conditions (e.g. <span class="inline-pre">{1} and ({2} or {3})</span>).
|
||||||
@@ -333,11 +333,11 @@
|
|||||||
$('#notifier-config-modal').unbind('hidden.bs.modal');
|
$('#notifier-config-modal').unbind('hidden.bs.modal');
|
||||||
|
|
||||||
// Need this for setting conditions since conditions contain the character "
|
// Need this for setting conditions since conditions contain the character "
|
||||||
$('#custom_conditions').val(${json.dumps(notifier["custom_conditions"]) | n});
|
$('#custom_conditions').val(JSON.stringify(${json.dumps(notifier["custom_conditions"]) | n}));
|
||||||
|
|
||||||
$('#condition-widget').filterer({
|
$('#condition-widget').filterer({
|
||||||
parameters: ${parameters | n},
|
parameters: ${json.dumps(parameters) | n},
|
||||||
conditions: ${notifier["custom_conditions"] | n},
|
conditions: ${json.dumps(notifier["custom_conditions"]) | n},
|
||||||
updateConditions: function(newConditions){
|
updateConditions: function(newConditions){
|
||||||
$('#custom_conditions').val(JSON.stringify(newConditions));
|
$('#custom_conditions').val(JSON.stringify(newConditions));
|
||||||
}
|
}
|
||||||
|
@@ -642,7 +642,7 @@
|
|||||||
<label for="pms_port">Plex Port</label>
|
<label for="pms_port">Plex Port</label>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-2">
|
<div class="col-md-2">
|
||||||
<input data-parsley-type="integer" class="pms-settings form-control" type="text" id="pms_port" name="pms_port" value="${config['pms_port']}" size="30" data-parsley-trigger="change" data-parsley-errors-container="#pms_port_error" required>
|
<input data-parsley-type="integer" class="form-control pms-settings" type="text" id="pms_port" name="pms_port" value="${config['pms_port']}" size="30" data-parsley-trigger="change" data-parsley-errors-container="#pms_port_error" required>
|
||||||
</div>
|
</div>
|
||||||
<div id="pms_port_error" class="alert alert-danger settings-alert" role="alert"></div>
|
<div id="pms_port_error" class="alert alert-danger settings-alert" role="alert"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -650,29 +650,45 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" id="pms_is_remote_checkbox" class="checkbox-toggle" data-id="pms_is_remote" value="1" ${checked(config['pms_is_remote'])}> Remote Server
|
<input type="checkbox" id="pms_is_remote_checkbox" class="checkbox-toggle pms-settings" data-id="pms_is_remote" value="1" ${checked(config['pms_is_remote'])}> Remote Server
|
||||||
<input type="hidden" id="pms_is_remote" name="pms_is_remote" value="${config['pms_is_remote']}">
|
<input type="hidden" id="pms_is_remote" name="pms_is_remote" value="${config['pms_is_remote']}">
|
||||||
</label>
|
</label>
|
||||||
<p class="help-block">Check this if your Plex Server is not on the same local network as Tautulli.</p>
|
<p class="help-block">Check this if your Plex Server is not on the same local network as Tautulli.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" id="pms_ssl_checkbox" class="checkbox-toggle" data-id="pms_ssl" value="1" ${checked(config['pms_ssl'])}> Use SSL
|
<input type="checkbox" id="pms_ssl_checkbox" class="checkbox-toggle pms-settings" data-id="pms_ssl" value="1" ${checked(config['pms_ssl'])}> Use SSL
|
||||||
<input type="hidden" id="pms_ssl" name="pms_ssl" value="${config['pms_ssl']}">
|
<input type="hidden" id="pms_ssl" name="pms_ssl" value="${config['pms_ssl']}">
|
||||||
</label>
|
</label>
|
||||||
<p class="help-block">If you have secure connections enabled on your Plex Server, communicate with it securely.</p>
|
<p class="help-block">If you have secure connections enabled on your Plex Server, communicate with it securely.</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="pms_url">Plex Server URL</label>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-9">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" class="form-control" id="pms_url" name="pms_url" value="${config['pms_url']}" size="30" readonly>
|
||||||
|
<span class="input-group-btn">
|
||||||
|
<button class="btn btn-form" type="button" id="test_pms_url_button">Test URL</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="help-block">
|
||||||
|
The server URL that Tautulli will use to connect to your Plex server. Retrieved automatically.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<div class="checkbox advanced-setting">
|
<div class="checkbox advanced-setting">
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" id="pms_url_manual" name="pms_url_manual" value="1" ${config['pms_url_manual']}> Manual Connection
|
<input type="checkbox" class="pms-settings" id="pms_url_manual" name="pms_url_manual" value="1" ${config['pms_url_manual']}> Manual Connection
|
||||||
</label>
|
</label>
|
||||||
<span id="cloudManualConnection" style="display: none; color: #eb8600; padding-left: 10px;"> Not available for Plex Cloud servers.</span>
|
<span id="cloudManualConnection" style="display: none; color: #eb8600; padding-left: 10px;"> Not available for Plex Cloud servers.</span>
|
||||||
<p class="help-block">Use the user defined connection details. Do not retrieve the server connection URL automatically.</p>
|
<p class="help-block">Use the user defined connection details. Do not retrieve the server connection URL automatically.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group advanced-setting">
|
<div class="form-group advanced-setting">
|
||||||
<label for="pms_logs_folder">Plex Web URL</label>
|
<label for="pms_web_url">Plex Web URL</label>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-9">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="text" class="form-control" id="pms_web_url" name="pms_web_url" value="${config['pms_web_url']}" size="30" data-parsley-trigger="change" data-parsley-pattern="^https?:\/\/\S+$|^https:\/\/app.plex.tv\/desktop$" data-parsley-errors-container="#pms_web_url_error" data-parsley-error-message="Invalid Plex Web URL.">
|
<input type="text" class="form-control" id="pms_web_url" name="pms_web_url" value="${config['pms_web_url']}" size="30" data-parsley-trigger="change" data-parsley-pattern="^https?:\/\/\S+$|^https:\/\/app.plex.tv\/desktop$" data-parsley-errors-container="#pms_web_url_error" data-parsley-error-message="Invalid Plex Web URL.">
|
||||||
<span class="input-group-btn">
|
<span class="input-group-btn">
|
||||||
@@ -1062,8 +1078,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<p class="form-group">
|
<p class="form-group">
|
||||||
<label>Registered Devices</label>
|
<label>Registered Devices</label>
|
||||||
<p class="help-block">Register a new device, or configure an existing device by clicking the settings icon on the right.</p>
|
<p class="help-block">Register a new device using a QR code, or configure an existing device by clicking the settings icon on the right.</p>
|
||||||
<p id="app_api_msg" style="color: #eb8600;">The API must be enabled under <a data-tab-destination="tabs-access_control" style="cursor: pointer;">Access Control</a> to use the app.</p>
|
<p id="app_api_msg" style="color: #eb8600;">The API must be enabled under <a data-tab-destination="tabs-web_interface" style="cursor: pointer;">Web Interface</a> to use the app.</p>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div id="plexpy-mobile-devices-table" class="col-md-12">
|
<div id="plexpy-mobile-devices-table" class="col-md-12">
|
||||||
<div class='text-muted'><i class="fa fa-refresh fa-spin"></i> Loading registered devices...</div>
|
<div class='text-muted'><i class="fa fa-refresh fa-spin"></i> Loading registered devices...</div>
|
||||||
@@ -1573,7 +1589,7 @@ $(document).ready(function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function preSaveChecks(_callback) {
|
function preSaveChecks(_callback) {
|
||||||
if ($("#pms_identifier").val() == "") {
|
if (serverChanged) {
|
||||||
verifyServer();
|
verifyServer();
|
||||||
}
|
}
|
||||||
verifyPMSWebURL();
|
verifyPMSWebURL();
|
||||||
@@ -1585,7 +1601,7 @@ $(document).ready(function() {
|
|||||||
|
|
||||||
// Alert the user that their changes require a restart.
|
// Alert the user that their changes require a restart.
|
||||||
function postSaveChecks() {
|
function postSaveChecks() {
|
||||||
if (serverChanged || authChanged || httpChanged || directoryChanged) {
|
if (authChanged || httpChanged || directoryChanged) {
|
||||||
$('#restart-modal').modal('show');
|
$('#restart-modal').modal('show');
|
||||||
}
|
}
|
||||||
$("#http_hashed_password").val($("#http_hash_password").is(":checked") ? 1 : 0);
|
$("#http_hashed_password").val($("#http_hash_password").is(":checked") ? 1 : 0);
|
||||||
@@ -1769,9 +1785,8 @@ $(document).ready(function() {
|
|||||||
|
|
||||||
$( ".pms-settings" ).change(function() {
|
$( ".pms-settings" ).change(function() {
|
||||||
serverChanged = true;
|
serverChanged = true;
|
||||||
$("#pms_identifier").val("");
|
|
||||||
$("#server_changed").prop('checked', true);
|
$("#server_changed").prop('checked', true);
|
||||||
verifyServer();
|
$("#pms_verify").hide();
|
||||||
});
|
});
|
||||||
|
|
||||||
$('.checkbox-toggle').click(function () {
|
$('.checkbox-toggle').click(function () {
|
||||||
@@ -1841,6 +1856,7 @@ $(document).ready(function() {
|
|||||||
$('#pms_ssl').val(ssl !== 'undefined' && ssl === 1 ? 1 : 0);
|
$('#pms_ssl').val(ssl !== 'undefined' && ssl === 1 ? 1 : 0);
|
||||||
$('#pms_is_cloud').val(is_cloud !== 'undefined' && is_cloud === true ? 1 : 0);
|
$('#pms_is_cloud').val(is_cloud !== 'undefined' && is_cloud === true ? 1 : 0);
|
||||||
$('#pms_url_manual').prop('checked', false);
|
$('#pms_url_manual').prop('checked', false);
|
||||||
|
$('#pms_url').val('Please verify your server above to retrieve the URL');
|
||||||
PMSCloudCheck();
|
PMSCloudCheck();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1906,6 +1922,7 @@ $(document).ready(function() {
|
|||||||
var pms_identifier = $("#pms_identifier").val();
|
var pms_identifier = $("#pms_identifier").val();
|
||||||
var pms_ssl = $("#pms_ssl").val();
|
var pms_ssl = $("#pms_ssl").val();
|
||||||
var pms_is_remote = $("#pms_is_remote").val();
|
var pms_is_remote = $("#pms_is_remote").val();
|
||||||
|
var pms_url_manual = $("#pms_url_manual").is(':checked') ? 1 : 0;
|
||||||
|
|
||||||
if (($("#pms_ip").val() !== '') || ($("#pms_port").val() !== '')) {
|
if (($("#pms_ip").val() !== '') || ($("#pms_port").val() !== '')) {
|
||||||
$("#pms_verify").html('<i class="fa fa-refresh fa-spin"></i>').fadeIn('fast');
|
$("#pms_verify").html('<i class="fa fa-refresh fa-spin"></i>').fadeIn('fast');
|
||||||
@@ -1916,7 +1933,9 @@ $(document).ready(function() {
|
|||||||
port: pms_port,
|
port: pms_port,
|
||||||
identifier: pms_identifier,
|
identifier: pms_identifier,
|
||||||
ssl: pms_ssl,
|
ssl: pms_ssl,
|
||||||
remote: pms_is_remote
|
remote: pms_is_remote,
|
||||||
|
manual: pms_url_manual,
|
||||||
|
get_url: serverChanged
|
||||||
},
|
},
|
||||||
cache: true,
|
cache: true,
|
||||||
async: true,
|
async: true,
|
||||||
@@ -1925,13 +1944,20 @@ $(document).ready(function() {
|
|||||||
$("#pms_verify").html('<i class="fa fa-close"></i>').fadeIn('fast');
|
$("#pms_verify").html('<i class="fa fa-close"></i>').fadeIn('fast');
|
||||||
$("#pms_ip_group").addClass("has-error");
|
$("#pms_ip_group").addClass("has-error");
|
||||||
},
|
},
|
||||||
success: function (json) {
|
success: function(xhr, status) {
|
||||||
var machine_identifier = json;
|
var result = xhr;
|
||||||
if (machine_identifier) {
|
var identifier = result.identifier;
|
||||||
$("#pms_identifier").val(machine_identifier);
|
var url = result.url;
|
||||||
|
if (identifier) {
|
||||||
|
$("#pms_identifier").val(identifier);
|
||||||
|
if (url) {
|
||||||
|
$("#pms_url").val(url);
|
||||||
|
}
|
||||||
$("#pms_verify").html('<i class="fa fa-check"></i>').fadeIn('fast');
|
$("#pms_verify").html('<i class="fa fa-check"></i>').fadeIn('fast');
|
||||||
$("#pms_ip_group").removeClass("has-error");
|
$("#pms_ip_group").removeClass("has-error");
|
||||||
|
|
||||||
|
serverChanged = false;
|
||||||
|
|
||||||
if (_callback) {
|
if (_callback) {
|
||||||
_callback();
|
_callback();
|
||||||
}
|
}
|
||||||
@@ -1950,7 +1976,6 @@ $(document).ready(function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$('#verify_server_button').on('click', function(){
|
$('#verify_server_button').on('click', function(){
|
||||||
$("#pms_identifier").val("");
|
|
||||||
verifyServer();
|
verifyServer();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1959,6 +1984,13 @@ $(document).ready(function() {
|
|||||||
$("#pms_web_url").val(pms_web_url || 'https://app.plex.tv/desktop');
|
$("#pms_web_url").val(pms_web_url || 'https://app.plex.tv/desktop');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$('#test_pms_url_button').on('click', function(){
|
||||||
|
var pms_url = $.trim($("#pms_url").val());
|
||||||
|
if (pms_url.startsWith('http')) {
|
||||||
|
window.open(pms_url + '/web', '_blank');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$('#test_pms_web_button').on('click', function(){
|
$('#test_pms_web_button').on('click', function(){
|
||||||
var pms_web_url = $.trim($("#pms_web_url").val());
|
var pms_web_url = $.trim($("#pms_web_url").val());
|
||||||
window.open(pms_web_url, '_blank');
|
window.open(pms_web_url, '_blank');
|
||||||
|
@@ -134,7 +134,7 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var selected_user_id = "${_session['user_id']}" == "None" ? null : "${_session['user_id']}";
|
var selected_user_id = "${_session['user_group']}" == "admin" ? null : "${_session['user_id']}";
|
||||||
loadSyncTable(selected_user_id);
|
loadSyncTable(selected_user_id);
|
||||||
|
|
||||||
% if _session['user_group'] == 'admin':
|
% if _session['user_group'] == 'admin':
|
||||||
|
@@ -94,7 +94,7 @@
|
|||||||
<label for="pms_ip">Plex IP or Hostname</label>
|
<label for="pms_ip">Plex IP or Hostname</label>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
<select class="form-control selectize-pms-ip" id="pms_ip" name="pms_ip">
|
<select class="form-control pms-settings selectize-pms-ip" id="pms_ip" name="pms_ip">
|
||||||
<option value="${config['pms_ip']}" selected>${config['pms_ip']}</option>
|
<option value="${config['pms_ip']}" selected>${config['pms_ip']}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -104,12 +104,12 @@
|
|||||||
<label for="pms_port">Plex Port</label>
|
<label for="pms_port">Plex Port</label>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-3">
|
<div class="col-xs-3">
|
||||||
<input type="text" class="form-control pms_settings" name="pms_port" id="pms_port" placeholder="32400" value="${config['pms_port']}" required>
|
<input type="text" class="form-control pms-settings" name="pms_port" id="pms_port" placeholder="32400" value="${config['pms_port']}" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-4">
|
<div class="col-xs-4">
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" id="pms_ssl_checkbox" class="checkbox-toggle" data-id="pms_ssl" value="1" ${helpers.checked(config['pms_ssl'])}> Use SSL
|
<input type="checkbox" id="pms_ssl_checkbox" class="checkbox-toggle pms-settings" data-id="pms_ssl" value="1" ${helpers.checked(config['pms_ssl'])}> Use SSL
|
||||||
<input type="hidden" id="pms_ssl" name="pms_ssl" value="${config['pms_ssl']}">
|
<input type="hidden" id="pms_ssl" name="pms_ssl" value="${config['pms_ssl']}">
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -117,16 +117,16 @@
|
|||||||
<div class="col-xs-4">
|
<div class="col-xs-4">
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" id="pms_is_remote_checkbox" class="checkbox-toggle" data-id="pms_is_remote" value="1" ${helpers.checked(config['pms_is_remote'])}> Remote Server
|
<input type="checkbox" id="pms_is_remote_checkbox" class="checkbox-toggle pms-settings" data-id="pms_is_remote" value="1" ${helpers.checked(config['pms_is_remote'])}> Remote Server
|
||||||
<input type="hidden" id="pms_is_remote" name="pms_is_remote" value="${config['pms_is_remote']}">
|
<input type="hidden" id="pms_is_remote" name="pms_is_remote" value="${config['pms_is_remote']}">
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<input type="hidden" class="form-control pms-settings" id="pms_valid" data-validate="validatePMSip" value="">
|
<input type="hidden" id="pms_valid" data-validate="validatePMSip" value="">
|
||||||
<input type="hidden" id="pms_is_cloud" name="pms_is_cloud" value="${config['pms_is_cloud']}">
|
<input type="hidden" id="pms_is_cloud" name="pms_is_cloud" value="${config['pms_is_cloud']}">
|
||||||
<input type="hidden" class="form-control pms-settings" id="pms_identifier" name="pms_identifier" value="${config['pms_identifier']}">
|
<input type="hidden" id="pms_identifier" name="pms_identifier" value="${config['pms_identifier']}">
|
||||||
<a class="btn btn-dark" id="verify-plex-server" href="#" role="button">Verify</a><span style="margin-left: 10px; display: none;" id="pms-verify-status"></span>
|
<a class="btn btn-dark" id="verify-plex-server" href="#" role="button">Verify</a><span style="margin-left: 10px; display: none;" id="pms-verify-status"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -419,7 +419,8 @@ $(document).ready(function() {
|
|||||||
port: pms_port,
|
port: pms_port,
|
||||||
identifier: pms_identifier,
|
identifier: pms_identifier,
|
||||||
ssl: pms_ssl,
|
ssl: pms_ssl,
|
||||||
remote: pms_is_remote },
|
remote: pms_is_remote
|
||||||
|
},
|
||||||
cache: true,
|
cache: true,
|
||||||
async: true,
|
async: true,
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
@@ -427,10 +428,11 @@ $(document).ready(function() {
|
|||||||
$("#pms-verify-status").html('<i class="fa fa-exclamation-circle"></i> This is not a Plex Server!');
|
$("#pms-verify-status").html('<i class="fa fa-exclamation-circle"></i> This is not a Plex Server!');
|
||||||
$('#pms-verify-status').fadeIn('fast');
|
$('#pms-verify-status').fadeIn('fast');
|
||||||
},
|
},
|
||||||
success: function (json) {
|
success: function(xhr, status) {
|
||||||
var machine_identifier = json;
|
var result = xhr;
|
||||||
if (machine_identifier) {
|
var identifier = result.identifier;
|
||||||
$("#pms_identifier").val(machine_identifier);
|
if (identifier) {
|
||||||
|
$("#pms_identifier").val(identifier);
|
||||||
$("#pms-verify-status").html('<i class="fa fa-check"></i> Server found!');
|
$("#pms-verify-status").html('<i class="fa fa-check"></i> Server found!');
|
||||||
$('#pms-verify-status').fadeIn('fast');
|
$('#pms-verify-status').fadeIn('fast');
|
||||||
pms_verified = true;
|
pms_verified = true;
|
||||||
|
@@ -45,7 +45,8 @@ __version__ = version.__version__
|
|||||||
|
|
||||||
FACEBOOK_GRAPH_URL = "https://graph.facebook.com/"
|
FACEBOOK_GRAPH_URL = "https://graph.facebook.com/"
|
||||||
FACEBOOK_OAUTH_DIALOG_URL = "https://www.facebook.com/dialog/oauth?"
|
FACEBOOK_OAUTH_DIALOG_URL = "https://www.facebook.com/dialog/oauth?"
|
||||||
VALID_API_VERSIONS = ["2.3", "2.4", "2.5", "2.6", "2.7", "2.8", "2.9"]
|
VALID_API_VERSIONS = [
|
||||||
|
"2.5", "2.6", "2.7", "2.8", "2.9", "2.10", "2.11", "2.12"]
|
||||||
VALID_SEARCH_TYPES = ["page", "event", "group", "place", "placetopic", "user"]
|
VALID_SEARCH_TYPES = ["page", "event", "group", "place", "placetopic", "user"]
|
||||||
|
|
||||||
|
|
||||||
@@ -89,7 +90,7 @@ class GraphAPI(object):
|
|||||||
self.session = session or requests.Session()
|
self.session = session or requests.Session()
|
||||||
|
|
||||||
if version:
|
if version:
|
||||||
version_regex = re.compile("^\d\.\d$")
|
version_regex = re.compile("^\d\.\d{1,2}$")
|
||||||
match = version_regex.search(str(version))
|
match = version_regex.search(str(version))
|
||||||
if match is not None:
|
if match is not None:
|
||||||
if str(version) not in VALID_API_VERSIONS:
|
if str(version) not in VALID_API_VERSIONS:
|
||||||
@@ -229,7 +230,7 @@ class GraphAPI(object):
|
|||||||
try:
|
try:
|
||||||
headers = response.headers
|
headers = response.headers
|
||||||
version = headers["facebook-api-version"].replace("v", "")
|
version = headers["facebook-api-version"].replace("v", "")
|
||||||
return float(version)
|
return str(version)
|
||||||
except Exception:
|
except Exception:
|
||||||
raise GraphAPIError("API version number not available")
|
raise GraphAPIError("API version number not available")
|
||||||
|
|
||||||
@@ -369,24 +370,24 @@ class GraphAPIError(Exception):
|
|||||||
self.code = None
|
self.code = None
|
||||||
try:
|
try:
|
||||||
self.type = result["error_code"]
|
self.type = result["error_code"]
|
||||||
except:
|
except (KeyError, TypeError):
|
||||||
self.type = ""
|
self.type = ""
|
||||||
|
|
||||||
# OAuth 2.0 Draft 10
|
# OAuth 2.0 Draft 10
|
||||||
try:
|
try:
|
||||||
self.message = result["error_description"]
|
self.message = result["error_description"]
|
||||||
except:
|
except (KeyError, TypeError):
|
||||||
# OAuth 2.0 Draft 00
|
# OAuth 2.0 Draft 00
|
||||||
try:
|
try:
|
||||||
self.message = result["error"]["message"]
|
self.message = result["error"]["message"]
|
||||||
self.code = result["error"].get("code")
|
self.code = result["error"].get("code")
|
||||||
if not self.type:
|
if not self.type:
|
||||||
self.type = result["error"].get("type", "")
|
self.type = result["error"].get("type", "")
|
||||||
except:
|
except (KeyError, TypeError):
|
||||||
# REST server style
|
# REST server style
|
||||||
try:
|
try:
|
||||||
self.message = result["error_msg"]
|
self.message = result["error_msg"]
|
||||||
except:
|
except (KeyError, TypeError):
|
||||||
self.message = result
|
self.message = result
|
||||||
|
|
||||||
Exception.__init__(self, self.message)
|
Exception.__init__(self, self.message)
|
||||||
|
@@ -46,6 +46,7 @@ import notifiers
|
|||||||
import plextv
|
import plextv
|
||||||
import users
|
import users
|
||||||
import versioncheck
|
import versioncheck
|
||||||
|
import web_socket
|
||||||
import plexpy.config
|
import plexpy.config
|
||||||
|
|
||||||
PROG_DIR = None
|
PROG_DIR = None
|
||||||
@@ -95,6 +96,7 @@ HTTP_ROOT = None
|
|||||||
|
|
||||||
DEV = False
|
DEV = False
|
||||||
|
|
||||||
|
WEBSOCKET = None
|
||||||
WS_CONNECTED = False
|
WS_CONNECTED = False
|
||||||
PLEX_SERVER_UP = None
|
PLEX_SERVER_UP = None
|
||||||
|
|
||||||
@@ -1622,6 +1624,10 @@ def upgrade():
|
|||||||
def shutdown(restart=False, update=False, checkout=False):
|
def shutdown(restart=False, update=False, checkout=False):
|
||||||
cherrypy.engine.exit()
|
cherrypy.engine.exit()
|
||||||
|
|
||||||
|
# Shutdown the websocket connection
|
||||||
|
if WEBSOCKET:
|
||||||
|
web_socket.shutdown()
|
||||||
|
|
||||||
if SCHED.running:
|
if SCHED.running:
|
||||||
SCHED.shutdown(wait=False)
|
SCHED.shutdown(wait=False)
|
||||||
if activity_handler.ACTIVITY_SCHED.running:
|
if activity_handler.ACTIVITY_SCHED.running:
|
||||||
|
@@ -652,10 +652,9 @@ General optional parameters:
|
|||||||
# {result: error, message: 'Some shit happend'}
|
# {result: error, message: 'Some shit happend'}
|
||||||
if isinstance(ret, dict):
|
if isinstance(ret, dict):
|
||||||
if ret.get('message'):
|
if ret.get('message'):
|
||||||
self._api_msg = ret.get('message', {})
|
self._api_msg = ret.pop('message', None)
|
||||||
ret = {}
|
|
||||||
|
|
||||||
if ret.get('result'):
|
if ret.get('result'):
|
||||||
self._api_result_type = ret.get('result')
|
self._api_result_type = ret.pop('result', None)
|
||||||
|
|
||||||
return self._api_out_as(self._api_responds(result_type=self._api_result_type, msg=self._api_msg, data=ret))
|
return self._api_out_as(self._api_responds(result_type=self._api_result_type, msg=self._api_msg, data=ret))
|
||||||
|
@@ -391,6 +391,10 @@ NOTIFICATION_PARAMETERS = [
|
|||||||
{'name': 'Episode Number 00', 'type': 'int', 'value': 'episode_num00', 'description': 'The two digit episode number.', 'example': 'e.g. 06, or 06-10'},
|
{'name': 'Episode Number 00', 'type': 'int', 'value': 'episode_num00', 'description': 'The two digit episode number.', 'example': 'e.g. 06, or 06-10'},
|
||||||
{'name': 'Track Number', 'type': 'int', 'value': 'track_num', 'description': 'The track number.', 'example': 'e.g. 4, or 4-10'},
|
{'name': 'Track Number', 'type': 'int', 'value': 'track_num', 'description': 'The track number.', 'example': 'e.g. 4, or 4-10'},
|
||||||
{'name': 'Track Number 00', 'type': 'int', 'value': 'track_num00', 'description': 'The two digit track number.', 'example': 'e.g. 04, or 04-10'},
|
{'name': 'Track Number 00', 'type': 'int', 'value': 'track_num00', 'description': 'The two digit track number.', 'example': 'e.g. 04, or 04-10'},
|
||||||
|
{'name': 'Season Count', 'type': 'int', 'value': 'season_count', 'description': 'The number of seasons.'},
|
||||||
|
{'name': 'Episode Count', 'type': 'int', 'value': 'episode_count', 'description': 'The number of episodes.'},
|
||||||
|
{'name': 'Album Count', 'type': 'int', 'value': 'album_count', 'description': 'The number of albums.'},
|
||||||
|
{'name': 'Track Count', 'type': 'int', 'value': 'track_count', 'description': 'The number of tracks.'},
|
||||||
{'name': 'Year', 'type': 'int', 'value': 'year', 'description': 'The release year for the item.'},
|
{'name': 'Year', 'type': 'int', 'value': 'year', 'description': 'The release year for the item.'},
|
||||||
{'name': 'Release Date', 'type': 'int', 'value': 'release_date', 'description': 'The release date (in date format) for the item.'},
|
{'name': 'Release Date', 'type': 'int', 'value': 'release_date', 'description': 'The release date (in date format) for the item.'},
|
||||||
{'name': 'Air Date', 'type': 'str', 'value': 'air_date', 'description': 'The air date (in date format) for the item.'},
|
{'name': 'Air Date', 'type': 'str', 'value': 'air_date', 'description': 'The air date (in date format) for the item.'},
|
||||||
|
@@ -933,3 +933,36 @@ def eval_logic_groups_to_bool(logic_groups, eval_conds):
|
|||||||
result = result or eval_cond
|
result = result or eval_cond
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def get_plexpy_url(hostname=None):
|
||||||
|
if plexpy.CONFIG.ENABLE_HTTPS:
|
||||||
|
scheme = 'https'
|
||||||
|
else:
|
||||||
|
scheme = 'http'
|
||||||
|
|
||||||
|
if hostname is None and plexpy.CONFIG.HTTP_HOST == '0.0.0.0':
|
||||||
|
import socket
|
||||||
|
try:
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||||
|
s.connect(('<broadcast>', 0))
|
||||||
|
hostname = s.getsockname()[0]
|
||||||
|
except socket.error:
|
||||||
|
hostname = socket.gethostbyname(socket.gethostname())
|
||||||
|
|
||||||
|
if not hostname:
|
||||||
|
hostname = 'localhost'
|
||||||
|
else:
|
||||||
|
hostname = hostname or plexpy.CONFIG.HTTP_HOST
|
||||||
|
|
||||||
|
if plexpy.CONFIG.HTTP_PORT not in (80, 443):
|
||||||
|
port = ':' + str(plexpy.CONFIG.HTTP_PORT)
|
||||||
|
else:
|
||||||
|
port = ''
|
||||||
|
|
||||||
|
if plexpy.CONFIG.HTTP_ROOT.strip('/'):
|
||||||
|
root = '/' + plexpy.CONFIG.HTTP_ROOT.strip('/')
|
||||||
|
else:
|
||||||
|
root = ''
|
||||||
|
|
||||||
|
return scheme + '://' + hostname + port + root
|
@@ -125,8 +125,8 @@ def update_section_ids():
|
|||||||
library_children = pms_connect.get_library_children_details(section_id=section_id,
|
library_children = pms_connect.get_library_children_details(section_id=section_id,
|
||||||
section_type=section_type)
|
section_type=section_type)
|
||||||
if library_children:
|
if library_children:
|
||||||
children_list = library_children['childern_list']
|
children_list = library_children['children_list']
|
||||||
key_mappings.update({child['rating_key']:child['section_id'] for child in children_list})
|
key_mappings.update({child['rating_key']: child['section_id'] for child in children_list})
|
||||||
else:
|
else:
|
||||||
logger.warn(u"Tautulli Libraries :: Unable to get a list of library items for section_id %s." % section_id)
|
logger.warn(u"Tautulli Libraries :: Unable to get a list of library items for section_id %s." % section_id)
|
||||||
|
|
||||||
@@ -198,7 +198,7 @@ def update_labels():
|
|||||||
label_key=label['label_key'])
|
label_key=label['label_key'])
|
||||||
|
|
||||||
if library_children:
|
if library_children:
|
||||||
children_list = library_children['childern_list']
|
children_list = library_children['children_list']
|
||||||
# rating_key_list = [child['rating_key'] for child in children_list]
|
# rating_key_list = [child['rating_key'] for child in children_list]
|
||||||
|
|
||||||
for rating_key in [child['rating_key'] for child in children_list]:
|
for rating_key in [child['rating_key'] for child in children_list]:
|
||||||
@@ -456,7 +456,7 @@ class Libraries(object):
|
|||||||
get_media_info=True)
|
get_media_info=True)
|
||||||
if library_children:
|
if library_children:
|
||||||
library_count = library_children['library_count']
|
library_count = library_children['library_count']
|
||||||
children_list = library_children['childern_list']
|
children_list = library_children['children_list']
|
||||||
else:
|
else:
|
||||||
logger.warn(u"Tautulli Libraries :: Unable to get a list of library items.")
|
logger.warn(u"Tautulli Libraries :: Unable to get a list of library items.")
|
||||||
return default_return
|
return default_return
|
||||||
|
@@ -208,7 +208,7 @@ def notify_custom_conditions(notifier_id=None, parameters=None):
|
|||||||
notifier_config = notifiers.get_notifier_config(notifier_id=notifier_id)
|
notifier_config = notifiers.get_notifier_config(notifier_id=notifier_id)
|
||||||
|
|
||||||
custom_conditions_logic = notifier_config['custom_conditions_logic']
|
custom_conditions_logic = notifier_config['custom_conditions_logic']
|
||||||
custom_conditions = json.loads(notifier_config['custom_conditions']) or []
|
custom_conditions = notifier_config['custom_conditions']
|
||||||
|
|
||||||
if custom_conditions_logic or any(c for c in custom_conditions if c['value']):
|
if custom_conditions_logic or any(c for c in custom_conditions if c['value']):
|
||||||
logger.debug(u"Tautulli NotificationHandler :: Checking custom notification conditions for notifier_id %s."
|
logger.debug(u"Tautulli NotificationHandler :: Checking custom notification conditions for notifier_id %s."
|
||||||
@@ -507,9 +507,9 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
|
|||||||
user_stream_count = len(user_sessions)
|
user_stream_count = len(user_sessions)
|
||||||
|
|
||||||
# Generate a combined transcode decision value
|
# Generate a combined transcode decision value
|
||||||
if session.get('video_decision','') == 'transcode' or session.get('audio_decision','') == 'transcode':
|
if session.get('stream_video_decision', '') == 'transcode' or session.get('stream_audio_decision', '') == 'transcode':
|
||||||
transcode_decision = 'Transcode'
|
transcode_decision = 'Transcode'
|
||||||
elif session.get('video_decision','') == 'copy' or session.get('audio_decision','') == 'copy':
|
elif session.get('stream_video_decision', '') == 'copy' or session.get('stream_audio_decision', '') == 'copy':
|
||||||
transcode_decision = 'Direct Stream'
|
transcode_decision = 'Direct Stream'
|
||||||
else:
|
else:
|
||||||
transcode_decision = 'Direct Play'
|
transcode_decision = 'Direct Play'
|
||||||
@@ -640,13 +640,17 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
|
|||||||
album_name = ''
|
album_name = ''
|
||||||
track_name = ''
|
track_name = ''
|
||||||
|
|
||||||
num, num00 = format_group_index([helpers.cast_to_int(d['media_index'])
|
child_num = [helpers.cast_to_int(
|
||||||
for d in child_metadata if d['parent_rating_key'] == rating_key])
|
d['media_index']) for d in child_metadata if d['parent_rating_key'] == rating_key]
|
||||||
|
num, num00 = format_group_index(child_num)
|
||||||
season_num, season_num00 = num, num00
|
season_num, season_num00 = num, num00
|
||||||
|
|
||||||
episode_num, episode_num00 = '', ''
|
episode_num, episode_num00 = '', ''
|
||||||
track_num, track_num00 = '', ''
|
track_num, track_num00 = '', ''
|
||||||
|
|
||||||
|
child_count = len(child_num)
|
||||||
|
grandchild_count = ''
|
||||||
|
|
||||||
elif ((manual_trigger or plexpy.CONFIG.NOTIFY_GROUP_RECENTLY_ADDED_PARENT)
|
elif ((manual_trigger or plexpy.CONFIG.NOTIFY_GROUP_RECENTLY_ADDED_PARENT)
|
||||||
and notify_params['media_type'] in ('season', 'album')):
|
and notify_params['media_type'] in ('season', 'album')):
|
||||||
show_name = notify_params['parent_title']
|
show_name = notify_params['parent_title']
|
||||||
@@ -654,14 +658,19 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
|
|||||||
artist_name = notify_params['parent_title']
|
artist_name = notify_params['parent_title']
|
||||||
album_name = notify_params['title']
|
album_name = notify_params['title']
|
||||||
track_name = ''
|
track_name = ''
|
||||||
|
|
||||||
season_num = str(notify_params['media_index']).zfill(1)
|
season_num = str(notify_params['media_index']).zfill(1)
|
||||||
season_num00 = str(notify_params['media_index']).zfill(2)
|
season_num00 = str(notify_params['media_index']).zfill(2)
|
||||||
|
|
||||||
num, num00 = format_group_index([helpers.cast_to_int(d['media_index'])
|
grandchild_num = [helpers.cast_to_int(
|
||||||
for d in child_metadata if d['parent_rating_key'] == rating_key])
|
d['media_index']) for d in child_metadata if d['parent_rating_key'] == rating_key]
|
||||||
|
num, num00 = format_group_index(grandchild_num)
|
||||||
episode_num, episode_num00 = num, num00
|
episode_num, episode_num00 = num, num00
|
||||||
track_num, track_num00 = num, num00
|
track_num, track_num00 = num, num00
|
||||||
|
|
||||||
|
child_count = 1
|
||||||
|
grandchild_count = len(grandchild_num)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
show_name = notify_params['grandparent_title']
|
show_name = notify_params['grandparent_title']
|
||||||
episode_name = notify_params['title']
|
episode_name = notify_params['title']
|
||||||
@@ -674,6 +683,8 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
|
|||||||
episode_num00 = str(notify_params['media_index']).zfill(2)
|
episode_num00 = str(notify_params['media_index']).zfill(2)
|
||||||
track_num = str(notify_params['media_index']).zfill(1)
|
track_num = str(notify_params['media_index']).zfill(1)
|
||||||
track_num00 = str(notify_params['media_index']).zfill(2)
|
track_num00 = str(notify_params['media_index']).zfill(2)
|
||||||
|
child_count = 1
|
||||||
|
grandchild_count = 1
|
||||||
|
|
||||||
available_params = {
|
available_params = {
|
||||||
# Global paramaters
|
# Global paramaters
|
||||||
@@ -783,6 +794,10 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
|
|||||||
'episode_num00': episode_num00,
|
'episode_num00': episode_num00,
|
||||||
'track_num': track_num,
|
'track_num': track_num,
|
||||||
'track_num00': track_num00,
|
'track_num00': track_num00,
|
||||||
|
'season_count': child_count,
|
||||||
|
'episode_count': grandchild_count,
|
||||||
|
'album_count': child_count,
|
||||||
|
'track_count': grandchild_count,
|
||||||
'year': notify_params['year'],
|
'year': notify_params['year'],
|
||||||
'release_date': arrow.get(notify_params['originally_available_at']).format(date_format)
|
'release_date': arrow.get(notify_params['originally_available_at']).format(date_format)
|
||||||
if notify_params['originally_available_at'] else '',
|
if notify_params['originally_available_at'] else '',
|
||||||
|
@@ -54,6 +54,7 @@ import twitter
|
|||||||
import pynma
|
import pynma
|
||||||
|
|
||||||
import plexpy
|
import plexpy
|
||||||
|
import common
|
||||||
import database
|
import database
|
||||||
import helpers
|
import helpers
|
||||||
import logger
|
import logger
|
||||||
@@ -94,6 +95,8 @@ AGENT_IDS = {'growl': 0,
|
|||||||
'zapier': 24
|
'zapier': 24
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DEFAULT_CUSTOM_CONDITIONS = [{'parameter': '', 'operator': '', 'value': ''}]
|
||||||
|
|
||||||
|
|
||||||
def available_notification_agents():
|
def available_notification_agents():
|
||||||
agents = [{'label': 'Tautulli Remote Android App',
|
agents = [{'label': 'Tautulli Remote Android App',
|
||||||
@@ -446,7 +449,6 @@ def get_notifier_config(notifier_id=None):
|
|||||||
db = database.MonitorDatabase()
|
db = database.MonitorDatabase()
|
||||||
result = db.select_single('SELECT * FROM notifiers WHERE id = ?',
|
result = db.select_single('SELECT * FROM notifiers WHERE id = ?',
|
||||||
args=[notifier_id])
|
args=[notifier_id])
|
||||||
|
|
||||||
if not result:
|
if not result:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -468,6 +470,14 @@ def get_notifier_config(notifier_id=None):
|
|||||||
notifier_text[k] = {'subject': result.pop(k + '_subject'),
|
notifier_text[k] = {'subject': result.pop(k + '_subject'),
|
||||||
'body': result.pop(k + '_body')}
|
'body': result.pop(k + '_body')}
|
||||||
|
|
||||||
|
try:
|
||||||
|
result['custom_conditions'] = json.loads(result['custom_conditions'])
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
result['custom_conditions'] = DEFAULT_CUSTOM_CONDITIONS
|
||||||
|
|
||||||
|
if not result['custom_conditions_logic']:
|
||||||
|
result['custom_conditions_logic'] = ''
|
||||||
|
|
||||||
result['config'] = config
|
result['config'] = config
|
||||||
result['config_options'] = notifier_config
|
result['config_options'] = notifier_config
|
||||||
result['actions'] = notifier_actions
|
result['actions'] = notifier_actions
|
||||||
@@ -494,7 +504,9 @@ def add_notifier_config(agent_id=None, **kwargs):
|
|||||||
'agent_name': agent['name'],
|
'agent_name': agent['name'],
|
||||||
'agent_label': agent['label'],
|
'agent_label': agent['label'],
|
||||||
'friendly_name': '',
|
'friendly_name': '',
|
||||||
'notifier_config': json.dumps(get_agent_class(agent_id=agent['id']).config)
|
'notifier_config': json.dumps(get_agent_class(agent_id=agent['id']).config),
|
||||||
|
'custom_conditions': json.dumps(DEFAULT_CUSTOM_CONDITIONS),
|
||||||
|
'custom_conditions_logic': ''
|
||||||
}
|
}
|
||||||
if agent['name'] == 'scripts':
|
if agent['name'] == 'scripts':
|
||||||
for a in available_notification_actions():
|
for a in available_notification_actions():
|
||||||
@@ -549,7 +561,7 @@ def set_notifier_config(notifier_id=None, agent_id=None, **kwargs):
|
|||||||
'agent_label': agent['label'],
|
'agent_label': agent['label'],
|
||||||
'friendly_name': kwargs.get('friendly_name', ''),
|
'friendly_name': kwargs.get('friendly_name', ''),
|
||||||
'notifier_config': json.dumps(notifier_config),
|
'notifier_config': json.dumps(notifier_config),
|
||||||
'custom_conditions': kwargs.get('custom_conditions', ''),
|
'custom_conditions': kwargs.get('custom_conditions', json.dumps(DEFAULT_CUSTOM_CONDITIONS)),
|
||||||
'custom_conditions_logic': kwargs.get('custom_conditions_logic', ''),
|
'custom_conditions_logic': kwargs.get('custom_conditions_logic', ''),
|
||||||
}
|
}
|
||||||
values.update(actions)
|
values.update(actions)
|
||||||
@@ -718,6 +730,13 @@ class PrettyMetadata(object):
|
|||||||
def get_plex_url(self):
|
def get_plex_url(self):
|
||||||
return self.parameters['plex_url']
|
return self.parameters['plex_url']
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_parameters():
|
||||||
|
parameters = {param['value']: param['name']
|
||||||
|
for category in common.NOTIFICATION_PARAMETERS for param in category['parameters']}
|
||||||
|
parameters[''] = ''
|
||||||
|
return parameters
|
||||||
|
|
||||||
|
|
||||||
class Notifier(object):
|
class Notifier(object):
|
||||||
NAME = ''
|
NAME = ''
|
||||||
@@ -1290,7 +1309,7 @@ class EMAIL(Notifier):
|
|||||||
mailserver.ehlo()
|
mailserver.ehlo()
|
||||||
|
|
||||||
if self.config['smtp_user']:
|
if self.config['smtp_user']:
|
||||||
mailserver.login(self.config['smtp_user'], self.config['smtp_password'])
|
mailserver.login(str(self.config['smtp_user']), str(self.config['smtp_password']))
|
||||||
|
|
||||||
mailserver.sendmail(self.config['from'], recipients, msg.as_string())
|
mailserver.sendmail(self.config['from'], recipients, msg.as_string())
|
||||||
mailserver.quit()
|
mailserver.quit()
|
||||||
@@ -1434,7 +1453,7 @@ class FACEBOOK(Notifier):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# Request user access token
|
# Request user access token
|
||||||
api = facebook.GraphAPI(version='2.5')
|
api = facebook.GraphAPI(version='2.12')
|
||||||
response = api.get_access_token_from_code(code=code,
|
response = api.get_access_token_from_code(code=code,
|
||||||
redirect_uri=redirect_uri + '/facebookStep2',
|
redirect_uri=redirect_uri + '/facebookStep2',
|
||||||
app_id=app_id,
|
app_id=app_id,
|
||||||
@@ -1442,7 +1461,7 @@ class FACEBOOK(Notifier):
|
|||||||
access_token = response['access_token']
|
access_token = response['access_token']
|
||||||
|
|
||||||
# Request extended user access token
|
# Request extended user access token
|
||||||
api = facebook.GraphAPI(access_token=access_token, version='2.5')
|
api = facebook.GraphAPI(access_token=access_token, version='2.12')
|
||||||
response = api.extend_access_token(app_id=app_id,
|
response = api.extend_access_token(app_id=app_id,
|
||||||
app_secret=app_secret)
|
app_secret=app_secret)
|
||||||
|
|
||||||
@@ -1460,7 +1479,7 @@ class FACEBOOK(Notifier):
|
|||||||
|
|
||||||
def _post_facebook(self, **data):
|
def _post_facebook(self, **data):
|
||||||
if self.config['group_id']:
|
if self.config['group_id']:
|
||||||
api = facebook.GraphAPI(access_token=self.config['access_token'], version='2.5')
|
api = facebook.GraphAPI(access_token=self.config['access_token'], version='2.12')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
api.put_object(parent_object=self.config['group_id'], connection_name='feed', **data)
|
api.put_object(parent_object=self.config['group_id'], connection_name='feed', **data)
|
||||||
@@ -1932,7 +1951,8 @@ class IFTTT(Notifier):
|
|||||||
"""
|
"""
|
||||||
NAME = 'IFTTT'
|
NAME = 'IFTTT'
|
||||||
_DEFAULT_CONFIG = {'key': '',
|
_DEFAULT_CONFIG = {'key': '',
|
||||||
'event': 'tautulli'
|
'event': 'tautulli',
|
||||||
|
'value3': '',
|
||||||
}
|
}
|
||||||
|
|
||||||
def agent_notify(self, subject='', body='', action='', **kwargs):
|
def agent_notify(self, subject='', body='', action='', **kwargs):
|
||||||
@@ -1941,6 +1961,10 @@ class IFTTT(Notifier):
|
|||||||
data = {'value1': subject.encode("utf-8"),
|
data = {'value1': subject.encode("utf-8"),
|
||||||
'value2': body.encode("utf-8")}
|
'value2': body.encode("utf-8")}
|
||||||
|
|
||||||
|
if self.config['value3']:
|
||||||
|
pretty_metadata = PrettyMetadata(kwargs['parameters'])
|
||||||
|
data['value3'] = pretty_metadata.parameters.get(self.config['value3'], '')
|
||||||
|
|
||||||
headers = {'Content-type': 'application/json'}
|
headers = {'Content-type': 'application/json'}
|
||||||
|
|
||||||
return self.make_request('https://maker.ifttt.com/trigger/{}/with/key/{}'.format(event, self.config['key']),
|
return self.make_request('https://maker.ifttt.com/trigger/{}/with/key/{}'.format(event, self.config['key']),
|
||||||
@@ -1964,6 +1988,13 @@ class IFTTT(Notifier):
|
|||||||
' as <span class="inline-pre">value1</span>'
|
' as <span class="inline-pre">value1</span>'
|
||||||
' and <span class="inline-pre">value2</span> respectively.',
|
' and <span class="inline-pre">value2</span> respectively.',
|
||||||
'input_type': 'text'
|
'input_type': 'text'
|
||||||
|
},
|
||||||
|
{'label': 'Value 3',
|
||||||
|
'value': self.config['value3'],
|
||||||
|
'name': 'ifttt_value3',
|
||||||
|
'description': 'Optional: Select a parameter to send as <span class="inline-pre">value3</span>.',
|
||||||
|
'input_type': 'select',
|
||||||
|
'select_options': PrettyMetadata().get_parameters()
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2923,6 +2954,13 @@ class SCRIPTS(Notifier):
|
|||||||
process.kill()
|
process.kill()
|
||||||
self.script_killed = True
|
self.script_killed = True
|
||||||
|
|
||||||
|
# Common environment variables
|
||||||
|
env = {'PLEX_URL': plexpy.CONFIG.PMS_URL,
|
||||||
|
'PLEX_TOKEN': plexpy.CONFIG.PMS_TOKEN,
|
||||||
|
'TAUTULLI_URL': helpers.get_plexpy_url(hostname='localhost'),
|
||||||
|
'TAUTULLI_APIKEY': plexpy.CONFIG.API_KEY
|
||||||
|
}
|
||||||
|
|
||||||
self.script_killed = False
|
self.script_killed = False
|
||||||
output = error = ''
|
output = error = ''
|
||||||
try:
|
try:
|
||||||
@@ -2930,7 +2968,8 @@ class SCRIPTS(Notifier):
|
|||||||
stdin=subprocess.PIPE,
|
stdin=subprocess.PIPE,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE,
|
stderr=subprocess.PIPE,
|
||||||
cwd=self.config['script_folder'])
|
cwd=self.config['script_folder'],
|
||||||
|
env=env)
|
||||||
|
|
||||||
if self.config['timeout'] > 0:
|
if self.config['timeout'] > 0:
|
||||||
timer = threading.Timer(self.config['timeout'], kill_script, (process,))
|
timer = threading.Timer(self.config['timeout'], kill_script, (process,))
|
||||||
@@ -2938,11 +2977,13 @@ class SCRIPTS(Notifier):
|
|||||||
timer = None
|
timer = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if timer: timer.start()
|
if timer:
|
||||||
|
timer.start()
|
||||||
output, error = process.communicate()
|
output, error = process.communicate()
|
||||||
status = process.returncode
|
status = process.returncode
|
||||||
finally:
|
finally:
|
||||||
if timer: timer.cancel()
|
if timer:
|
||||||
|
timer.cancel()
|
||||||
|
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
logger.error(u"Tautulli Notifiers :: Failed to run script: %s" % e)
|
logger.error(u"Tautulli Notifiers :: Failed to run script: %s" % e)
|
||||||
|
@@ -29,7 +29,7 @@ import pmsconnect
|
|||||||
import session
|
import session
|
||||||
|
|
||||||
|
|
||||||
def get_server_resources(return_presence=False):
|
def get_server_resources(return_presence=False, return_server=False, **kwargs):
|
||||||
if not return_presence:
|
if not return_presence:
|
||||||
logger.info(u"Tautulli PlexTV :: Requesting resources for server...")
|
logger.info(u"Tautulli PlexTV :: Requesting resources for server...")
|
||||||
|
|
||||||
@@ -42,9 +42,15 @@ def get_server_resources(return_presence=False):
|
|||||||
'pms_is_remote': plexpy.CONFIG.PMS_IS_REMOTE,
|
'pms_is_remote': plexpy.CONFIG.PMS_IS_REMOTE,
|
||||||
'pms_is_cloud': plexpy.CONFIG.PMS_IS_CLOUD,
|
'pms_is_cloud': plexpy.CONFIG.PMS_IS_CLOUD,
|
||||||
'pms_url': plexpy.CONFIG.PMS_URL,
|
'pms_url': plexpy.CONFIG.PMS_URL,
|
||||||
'pms_url_manual': plexpy.CONFIG.PMS_URL_MANUAL
|
'pms_url_manual': plexpy.CONFIG.PMS_URL_MANUAL,
|
||||||
|
'pms_identifier': plexpy.CONFIG.PMS_IDENTIFIER
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if kwargs:
|
||||||
|
server.update(kwargs)
|
||||||
|
for k in ['pms_ssl', 'pms_is_remote', 'pms_is_cloud', 'pms_url_manual']:
|
||||||
|
server[k] = int(server[k])
|
||||||
|
|
||||||
if server['pms_url_manual'] and server['pms_ssl'] or server['pms_is_cloud']:
|
if server['pms_url_manual'] and server['pms_ssl'] or server['pms_is_cloud']:
|
||||||
scheme = 'https'
|
scheme = 'https'
|
||||||
else:
|
else:
|
||||||
@@ -55,7 +61,7 @@ def get_server_resources(return_presence=False):
|
|||||||
port=server['pms_port'])
|
port=server['pms_port'])
|
||||||
|
|
||||||
plex_tv = PlexTV()
|
plex_tv = PlexTV()
|
||||||
result = plex_tv.get_server_connections(pms_identifier=plexpy.CONFIG.PMS_IDENTIFIER,
|
result = plex_tv.get_server_connections(pms_identifier=server['pms_identifier'],
|
||||||
pms_ip=server['pms_ip'],
|
pms_ip=server['pms_ip'],
|
||||||
pms_port=server['pms_port'],
|
pms_port=server['pms_port'],
|
||||||
include_https=server['pms_ssl'])
|
include_https=server['pms_ssl'])
|
||||||
@@ -103,6 +109,9 @@ def get_server_resources(return_presence=False):
|
|||||||
server['pms_url'] = fallback_url
|
server['pms_url'] = fallback_url
|
||||||
logger.info(u"Tautulli PlexTV :: Using user-defined URL.")
|
logger.info(u"Tautulli PlexTV :: Using user-defined URL.")
|
||||||
|
|
||||||
|
if return_server:
|
||||||
|
return server
|
||||||
|
|
||||||
plexpy.CONFIG.process_kwargs(server)
|
plexpy.CONFIG.process_kwargs(server)
|
||||||
plexpy.CONFIG.write()
|
plexpy.CONFIG.write()
|
||||||
|
|
||||||
@@ -645,6 +654,7 @@ class PlexTV(object):
|
|||||||
'label': helpers.get_xml_attr(d, 'name'),
|
'label': helpers.get_xml_attr(d, 'name'),
|
||||||
'ip': helpers.get_xml_attr(c, 'address'),
|
'ip': helpers.get_xml_attr(c, 'address'),
|
||||||
'port': helpers.get_xml_attr(c, 'port'),
|
'port': helpers.get_xml_attr(c, 'port'),
|
||||||
|
'uri': helpers.get_xml_attr(c, 'uri'),
|
||||||
'local': helpers.get_xml_attr(c, 'local'),
|
'local': helpers.get_xml_attr(c, 'local'),
|
||||||
'value': helpers.get_xml_attr(c, 'address'),
|
'value': helpers.get_xml_attr(c, 'address'),
|
||||||
'is_cloud': is_cloud
|
'is_cloud': is_cloud
|
||||||
|
@@ -666,6 +666,11 @@ class PmsConnect(object):
|
|||||||
}
|
}
|
||||||
|
|
||||||
elif metadata_type == 'show':
|
elif metadata_type == 'show':
|
||||||
|
# Workaround for for duration sometimes reported in minutes for a show
|
||||||
|
duration = helpers.get_xml_attr(metadata_main, 'duration')
|
||||||
|
if duration.isdigit() and int(duration) < 1000:
|
||||||
|
duration = unicode(int(duration) * 60 * 1000)
|
||||||
|
|
||||||
metadata = {'media_type': metadata_type,
|
metadata = {'media_type': metadata_type,
|
||||||
'section_id': section_id,
|
'section_id': section_id,
|
||||||
'library_name': library_name,
|
'library_name': library_name,
|
||||||
@@ -685,7 +690,7 @@ class PmsConnect(object):
|
|||||||
'rating': helpers.get_xml_attr(metadata_main, 'rating'),
|
'rating': helpers.get_xml_attr(metadata_main, 'rating'),
|
||||||
'audience_rating': helpers.get_xml_attr(metadata_main, 'audienceRating'),
|
'audience_rating': helpers.get_xml_attr(metadata_main, 'audienceRating'),
|
||||||
'user_rating': helpers.get_xml_attr(metadata_main, 'userRating'),
|
'user_rating': helpers.get_xml_attr(metadata_main, 'userRating'),
|
||||||
'duration': helpers.get_xml_attr(metadata_main, 'duration'),
|
'duration': duration,
|
||||||
'year': helpers.get_xml_attr(metadata_main, 'year'),
|
'year': helpers.get_xml_attr(metadata_main, 'year'),
|
||||||
'thumb': helpers.get_xml_attr(metadata_main, 'thumb'),
|
'thumb': helpers.get_xml_attr(metadata_main, 'thumb'),
|
||||||
'parent_thumb': helpers.get_xml_attr(metadata_main, 'parentThumb'),
|
'parent_thumb': helpers.get_xml_attr(metadata_main, 'parentThumb'),
|
||||||
@@ -1465,18 +1470,12 @@ class PmsConnect(object):
|
|||||||
transcode_details['transcode_hw_decoding'] = int(transcode_details['transcode_hw_decode'].lower() in common.HW_DECODERS)
|
transcode_details['transcode_hw_decoding'] = int(transcode_details['transcode_hw_decode'].lower() in common.HW_DECODERS)
|
||||||
transcode_details['transcode_hw_encoding'] = int(transcode_details['transcode_hw_encode'].lower() in common.HW_ENCODERS)
|
transcode_details['transcode_hw_encoding'] = int(transcode_details['transcode_hw_encode'].lower() in common.HW_ENCODERS)
|
||||||
|
|
||||||
# Generate a combined transcode decision value
|
|
||||||
if transcode_details['video_decision'] == 'transcode' or transcode_details['audio_decision'] == 'transcode':
|
|
||||||
transcode_decision = 'transcode'
|
|
||||||
elif transcode_details['video_decision'] == 'copy' or transcode_details['audio_decision'] == 'copy':
|
|
||||||
transcode_decision = 'copy'
|
|
||||||
else:
|
|
||||||
transcode_decision = 'direct play'
|
|
||||||
|
|
||||||
# Determine if a synced version is being played
|
# Determine if a synced version is being played
|
||||||
sync_id = None
|
sync_id = None
|
||||||
if media_type not in ('photo', 'clip') and not session.getElementsByTagName('Session') \
|
if media_type not in ('photo', 'clip') \
|
||||||
and helpers.get_xml_attr(session, 'ratingKey').isdigit() and transcode_decision == 'direct play':
|
and not session.getElementsByTagName('Session') \
|
||||||
|
and not session.getElementsByTagName('TranscodeSession') \
|
||||||
|
and helpers.get_xml_attr(session, 'ratingKey').isdigit():
|
||||||
plex_tv = plextv.PlexTV()
|
plex_tv = plextv.PlexTV()
|
||||||
parent_rating_key = helpers.get_xml_attr(session, 'parentRatingKey')
|
parent_rating_key = helpers.get_xml_attr(session, 'parentRatingKey')
|
||||||
grandparent_rating_key = helpers.get_xml_attr(session, 'grandparentRatingKey')
|
grandparent_rating_key = helpers.get_xml_attr(session, 'grandparentRatingKey')
|
||||||
@@ -1582,6 +1581,14 @@ class PmsConnect(object):
|
|||||||
'stream_subtitle_decision': ''
|
'stream_subtitle_decision': ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Generate a combined transcode decision value
|
||||||
|
if video_details['stream_video_decision'] == 'transcode' or audio_details['stream_audio_decision'] == 'transcode':
|
||||||
|
transcode_decision = 'transcode'
|
||||||
|
elif video_details['stream_video_decision'] == 'copy' or audio_details['stream_audio_decision'] == 'copy':
|
||||||
|
transcode_decision = 'copy'
|
||||||
|
else:
|
||||||
|
transcode_decision = 'direct play'
|
||||||
|
|
||||||
# Get the bif thumbnail
|
# Get the bif thumbnail
|
||||||
indexes = helpers.get_xml_attr(stream_media_parts_info, 'indexes')
|
indexes = helpers.get_xml_attr(stream_media_parts_info, 'indexes')
|
||||||
view_offset = helpers.get_xml_attr(session, 'viewOffset')
|
view_offset = helpers.get_xml_attr(session, 'viewOffset')
|
||||||
@@ -2161,16 +2168,16 @@ class PmsConnect(object):
|
|||||||
logger.warn(u"Tautulli Pmsconnect :: Unable to parse XML for get_library_children_details: %s." % e)
|
logger.warn(u"Tautulli Pmsconnect :: Unable to parse XML for get_library_children_details: %s." % e)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
childern_list = []
|
children_list = []
|
||||||
|
|
||||||
for a in xml_head:
|
for a in xml_head:
|
||||||
if a.getAttribute('size'):
|
if a.getAttribute('size'):
|
||||||
if a.getAttribute('size') == '0':
|
if a.getAttribute('size') == '0':
|
||||||
logger.debug(u"Tautulli Pmsconnect :: No library data.")
|
logger.debug(u"Tautulli Pmsconnect :: No library data.")
|
||||||
childern_list = {'library_count': '0',
|
children_list = {'library_count': '0',
|
||||||
'childern_list': []
|
'children_list': []
|
||||||
}
|
}
|
||||||
return childern_list
|
return children_list
|
||||||
|
|
||||||
if rating_key:
|
if rating_key:
|
||||||
library_count = helpers.get_xml_attr(xml_head[0], 'size')
|
library_count = helpers.get_xml_attr(xml_head[0], 'size')
|
||||||
@@ -2228,10 +2235,10 @@ class PmsConnect(object):
|
|||||||
}
|
}
|
||||||
item_info.update(media_info)
|
item_info.update(media_info)
|
||||||
|
|
||||||
childern_list.append(item_info)
|
children_list.append(item_info)
|
||||||
|
|
||||||
output = {'library_count': library_count,
|
output = {'library_count': library_count,
|
||||||
'childern_list': childern_list
|
'children_list': children_list
|
||||||
}
|
}
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
@@ -38,14 +38,14 @@ def get_session_user():
|
|||||||
Returns the user_id for the current logged in session
|
Returns the user_id for the current logged in session
|
||||||
"""
|
"""
|
||||||
_session = get_session_info()
|
_session = get_session_info()
|
||||||
return _session['user'] if _session and _session['user'] else None
|
return _session['user'] if _session['user_group'] == 'guest' and _session['user'] else None
|
||||||
|
|
||||||
def get_session_user_id():
|
def get_session_user_id():
|
||||||
"""
|
"""
|
||||||
Returns the user_id for the current logged in session
|
Returns the user_id for the current logged in session
|
||||||
"""
|
"""
|
||||||
_session = get_session_info()
|
_session = get_session_info()
|
||||||
return str(_session['user_id']) if _session and _session['user_id'] else None
|
return str(_session['user_id']) if _session['user_group'] == 'guest' and _session['user_id'] else None
|
||||||
|
|
||||||
def get_session_shared_libraries():
|
def get_session_shared_libraries():
|
||||||
"""
|
"""
|
||||||
|
@@ -1,2 +1,2 @@
|
|||||||
PLEXPY_BRANCH = "beta"
|
PLEXPY_BRANCH = "beta"
|
||||||
PLEXPY_RELEASE_VERSION = "v2.0.22-beta"
|
PLEXPY_RELEASE_VERSION = "v2.0.23-beta"
|
||||||
|
@@ -190,9 +190,9 @@ def checkGithub(auto_update=False):
|
|||||||
if plexpy.CONFIG.GIT_BRANCH == 'master':
|
if plexpy.CONFIG.GIT_BRANCH == 'master':
|
||||||
release = next((r for r in releases if not r['prerelease']), releases[0])
|
release = next((r for r in releases if not r['prerelease']), releases[0])
|
||||||
elif plexpy.CONFIG.GIT_BRANCH == 'beta':
|
elif plexpy.CONFIG.GIT_BRANCH == 'beta':
|
||||||
release = next((r for r in releases if r['prerelease'] and '-beta' in r['tag_name']), releases[0])
|
release = next((r for r in releases if not r['tag_name'].endswith('-nightly')), releases[0])
|
||||||
elif plexpy.CONFIG.GIT_BRANCH == 'nightly':
|
elif plexpy.CONFIG.GIT_BRANCH == 'nightly':
|
||||||
release = next((r for r in releases if r['prerelease'] and '-nightly' in r['tag_name']), releases[0])
|
release = next((r for r in releases), releases[0])
|
||||||
else:
|
else:
|
||||||
release = releases[0]
|
release = releases[0]
|
||||||
|
|
||||||
@@ -292,8 +292,8 @@ def update():
|
|||||||
|
|
||||||
def checkout_git_branch():
|
def checkout_git_branch():
|
||||||
if plexpy.INSTALL_TYPE == 'git':
|
if plexpy.INSTALL_TYPE == 'git':
|
||||||
output, err = runGit('fetch ' + plexpy.CONFIG.GIT_REMOTE)
|
output, err = runGit('fetch %s' % plexpy.CONFIG.GIT_REMOTE)
|
||||||
output, err = runGit('checkout ' + plexpy.CONFIG.GIT_BRANCH)
|
output, err = runGit('checkout %s' % plexpy.CONFIG.GIT_BRANCH)
|
||||||
|
|
||||||
if not output:
|
if not output:
|
||||||
logger.error('Unable to change git branch.')
|
logger.error('Unable to change git branch.')
|
||||||
@@ -304,6 +304,8 @@ def checkout_git_branch():
|
|||||||
logger.error('Unable to checkout from git: ' + line)
|
logger.error('Unable to checkout from git: ' + line)
|
||||||
logger.info('Output: ' + str(output))
|
logger.info('Output: ' + str(output))
|
||||||
|
|
||||||
|
output, err = runGit('pull %s %s' % (plexpy.CONFIG.GIT_REMOTE, plexpy.CONFIG.GIT_BRANCH))
|
||||||
|
|
||||||
|
|
||||||
def read_changelog(latest_only=False, since_prev_release=False):
|
def read_changelog(latest_only=False, since_prev_release=False):
|
||||||
changelog_file = os.path.join(plexpy.PROG_DIR, 'CHANGELOG.md')
|
changelog_file = os.path.join(plexpy.PROG_DIR, 'CHANGELOG.md')
|
||||||
|
@@ -29,14 +29,16 @@ import logger
|
|||||||
|
|
||||||
name = 'websocket'
|
name = 'websocket'
|
||||||
opcode_data = (websocket.ABNF.OPCODE_TEXT, websocket.ABNF.OPCODE_BINARY)
|
opcode_data = (websocket.ABNF.OPCODE_TEXT, websocket.ABNF.OPCODE_BINARY)
|
||||||
ws_reconnect = False
|
ws_shutdown = False
|
||||||
|
|
||||||
|
|
||||||
def start_thread():
|
def start_thread():
|
||||||
# Check for any existing sessions on start up
|
# Check for any existing sessions on start up
|
||||||
activity_pinger.check_active_sessions(ws_request=True)
|
activity_pinger.check_active_sessions(ws_request=True)
|
||||||
# Start the websocket listener on it's own thread
|
# Start the websocket listener on it's own thread
|
||||||
threading.Thread(target=run).start()
|
thread = threading.Thread(target=run)
|
||||||
|
thread.daemon = True
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
|
||||||
def on_connect():
|
def on_connect():
|
||||||
@@ -65,8 +67,18 @@ def on_disconnect():
|
|||||||
|
|
||||||
|
|
||||||
def reconnect():
|
def reconnect():
|
||||||
global ws_reconnect
|
shutdown()
|
||||||
ws_reconnect = True
|
logger.info(u"Tautulli WebSocket :: Reconnecting websocket...")
|
||||||
|
start_thread()
|
||||||
|
|
||||||
|
|
||||||
|
def shutdown():
|
||||||
|
global ws_shutdown
|
||||||
|
ws_shutdown = True
|
||||||
|
|
||||||
|
logger.info(u"Tautulli WebSocket :: Disconnecting websocket...")
|
||||||
|
plexpy.WEBSOCKET.close()
|
||||||
|
plexpy.WS_CONNECTED = False
|
||||||
|
|
||||||
|
|
||||||
def run():
|
def run():
|
||||||
@@ -88,8 +100,8 @@ def run():
|
|||||||
else:
|
else:
|
||||||
header = []
|
header = []
|
||||||
|
|
||||||
global ws_reconnect
|
global ws_shutdown
|
||||||
ws_reconnect = False
|
ws_shutdown = False
|
||||||
reconnects = 0
|
reconnects = 0
|
||||||
|
|
||||||
# Try an open the websocket connection
|
# Try an open the websocket connection
|
||||||
@@ -106,7 +118,7 @@ def run():
|
|||||||
logger.info(u"Tautulli WebSocket :: Connection attempt %s." % str(reconnects))
|
logger.info(u"Tautulli WebSocket :: Connection attempt %s." % str(reconnects))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ws = create_connection(uri, header=header)
|
plexpy.WEBSOCKET = create_connection(uri, header=header)
|
||||||
logger.info(u"Tautulli WebSocket :: Ready")
|
logger.info(u"Tautulli WebSocket :: Ready")
|
||||||
plexpy.WS_CONNECTED = True
|
plexpy.WS_CONNECTED = True
|
||||||
except (websocket.WebSocketException, IOError, Exception) as e:
|
except (websocket.WebSocketException, IOError, Exception) as e:
|
||||||
@@ -117,12 +129,15 @@ def run():
|
|||||||
|
|
||||||
while plexpy.WS_CONNECTED:
|
while plexpy.WS_CONNECTED:
|
||||||
try:
|
try:
|
||||||
process(*receive(ws))
|
process(*receive(plexpy.WEBSOCKET))
|
||||||
|
|
||||||
# successfully received data, reset reconnects counter
|
# successfully received data, reset reconnects counter
|
||||||
reconnects = 0
|
reconnects = 0
|
||||||
|
|
||||||
except websocket.WebSocketConnectionClosedException:
|
except websocket.WebSocketConnectionClosedException:
|
||||||
|
if ws_shutdown:
|
||||||
|
break
|
||||||
|
|
||||||
if reconnects == 0:
|
if reconnects == 0:
|
||||||
logger.warn(u"Tautulli WebSocket :: Connection has closed.")
|
logger.warn(u"Tautulli WebSocket :: Connection has closed.")
|
||||||
|
|
||||||
@@ -136,31 +151,25 @@ def run():
|
|||||||
logger.warn(u"Tautulli WebSocket :: Reconnection attempt %s." % str(reconnects))
|
logger.warn(u"Tautulli WebSocket :: Reconnection attempt %s." % str(reconnects))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ws = create_connection(uri, header=header)
|
plexpy.WEBSOCKET = create_connection(uri, header=header)
|
||||||
logger.info(u"Tautulli WebSocket :: Ready")
|
logger.info(u"Tautulli WebSocket :: Ready")
|
||||||
plexpy.WS_CONNECTED = True
|
plexpy.WS_CONNECTED = True
|
||||||
except (websocket.WebSocketException, IOError, Exception) as e:
|
except (websocket.WebSocketException, IOError, Exception) as e:
|
||||||
logger.error(u"Tautulli WebSocket :: %s." % e)
|
logger.error(u"Tautulli WebSocket :: %s." % e)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
ws.shutdown()
|
shutdown()
|
||||||
plexpy.WS_CONNECTED = False
|
|
||||||
break
|
break
|
||||||
|
|
||||||
except (websocket.WebSocketException, Exception) as e:
|
except (websocket.WebSocketException, Exception) as e:
|
||||||
logger.error(u"Tautulli WebSocket :: %s." % e)
|
if ws_shutdown:
|
||||||
ws.shutdown()
|
|
||||||
plexpy.WS_CONNECTED = False
|
|
||||||
break
|
break
|
||||||
|
|
||||||
# Check if we recieved a restart notification and close websocket connection cleanly
|
logger.error(u"Tautulli WebSocket :: %s." % e)
|
||||||
if ws_reconnect:
|
shutdown()
|
||||||
logger.info(u"Tautulli WebSocket :: Reconnecting websocket...")
|
break
|
||||||
ws.shutdown()
|
|
||||||
plexpy.WS_CONNECTED = False
|
|
||||||
start_thread()
|
|
||||||
|
|
||||||
if not plexpy.WS_CONNECTED and not ws_reconnect:
|
if not plexpy.WS_CONNECTED and not ws_shutdown:
|
||||||
on_disconnect()
|
on_disconnect()
|
||||||
|
|
||||||
logger.debug(u"Tautulli WebSocket :: Leaving thread.")
|
logger.debug(u"Tautulli WebSocket :: Leaving thread.")
|
||||||
|
@@ -106,10 +106,10 @@ def check_credentials(username, password, admin_login='0'):
|
|||||||
if plexpy.CONFIG.HTTP_PASSWORD:
|
if plexpy.CONFIG.HTTP_PASSWORD:
|
||||||
if plexpy.CONFIG.HTTP_HASHED_PASSWORD and \
|
if plexpy.CONFIG.HTTP_HASHED_PASSWORD and \
|
||||||
username == plexpy.CONFIG.HTTP_USERNAME and check_hash(password, plexpy.CONFIG.HTTP_PASSWORD):
|
username == plexpy.CONFIG.HTTP_USERNAME and check_hash(password, plexpy.CONFIG.HTTP_PASSWORD):
|
||||||
return True, 'admin'
|
return True, 'tautulli admin'
|
||||||
elif not plexpy.CONFIG.HTTP_HASHED_PASSWORD and \
|
elif not plexpy.CONFIG.HTTP_HASHED_PASSWORD and \
|
||||||
username == plexpy.CONFIG.HTTP_USERNAME and password == plexpy.CONFIG.HTTP_PASSWORD:
|
username == plexpy.CONFIG.HTTP_USERNAME and password == plexpy.CONFIG.HTTP_PASSWORD:
|
||||||
return True, 'admin'
|
return True, 'tautulli admin'
|
||||||
|
|
||||||
if plexpy.CONFIG.HTTP_PLEX_ADMIN or (not admin_login == '1' and plexpy.CONFIG.ALLOW_GUEST_ACCESS):
|
if plexpy.CONFIG.HTTP_PLEX_ADMIN or (not admin_login == '1' and plexpy.CONFIG.ALLOW_GUEST_ACCESS):
|
||||||
plex_login = user_login(username, password)
|
plex_login = user_login(username, password)
|
||||||
@@ -215,12 +215,12 @@ class AuthController(object):
|
|||||||
return
|
return
|
||||||
raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT)
|
raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT)
|
||||||
|
|
||||||
def on_login(self, user_id, username, user_group):
|
def on_login(self, username, user_id=None, user_group=None, success=0):
|
||||||
"""Called on successful login"""
|
"""Called on successful login"""
|
||||||
|
|
||||||
# Save login to the database
|
# Save login to the database
|
||||||
ip_address = cherrypy.request.headers.get('X-Forwarded-For', cherrypy.request.headers.get('Remote-Addr'))
|
ip_address = cherrypy.request.remote.ip
|
||||||
host = cherrypy.request.headers.get('Host', cherrypy.request.headers.get('Origin'))
|
host = cherrypy.request.base
|
||||||
user_agent = cherrypy.request.headers.get('User-Agent')
|
user_agent = cherrypy.request.headers.get('User-Agent')
|
||||||
|
|
||||||
Users().set_user_login(user_id=user_id,
|
Users().set_user_login(user_id=user_id,
|
||||||
@@ -229,28 +229,15 @@ class AuthController(object):
|
|||||||
ip_address=ip_address,
|
ip_address=ip_address,
|
||||||
host=host,
|
host=host,
|
||||||
user_agent=user_agent,
|
user_agent=user_agent,
|
||||||
success=1)
|
success=success)
|
||||||
|
|
||||||
|
if success == 1:
|
||||||
logger.debug(u"Tautulli WebAuth :: %s user '%s' logged into Tautulli." % (user_group.capitalize(), username))
|
logger.debug(u"Tautulli WebAuth :: %s user '%s' logged into Tautulli." % (user_group.capitalize(), username))
|
||||||
|
|
||||||
def on_logout(self, username, user_group):
|
def on_logout(self, username, user_group):
|
||||||
"""Called on logout"""
|
"""Called on logout"""
|
||||||
logger.debug(u"Tautulli WebAuth :: %s user '%s' logged out of Tautulli." % (user_group.capitalize(), username))
|
logger.debug(u"Tautulli WebAuth :: %s user '%s' logged out of Tautulli." % (user_group.capitalize(), username))
|
||||||
|
|
||||||
def on_login_failed(self, username):
|
|
||||||
"""Called on failed login"""
|
|
||||||
|
|
||||||
# Save login attempt to the database
|
|
||||||
ip_address = cherrypy.request.headers.get('X-Forwarded-For', cherrypy.request.headers.get('Remote-Addr'))
|
|
||||||
host = cherrypy.request.headers.get('Origin')
|
|
||||||
user_agent = cherrypy.request.headers.get('User-Agent')
|
|
||||||
|
|
||||||
Users().set_user_login(user=username,
|
|
||||||
ip_address=ip_address,
|
|
||||||
host=host,
|
|
||||||
user_agent=user_agent,
|
|
||||||
success=0)
|
|
||||||
|
|
||||||
def get_loginform(self):
|
def get_loginform(self):
|
||||||
from plexpy.webserve import serve_template
|
from plexpy.webserve import serve_template
|
||||||
return serve_template(templatename="login.html", title="Login")
|
return serve_template(templatename="login.html", title="Login")
|
||||||
@@ -293,15 +280,16 @@ class AuthController(object):
|
|||||||
valid_login, user_group = check_credentials(username, password, admin_login)
|
valid_login, user_group = check_credentials(username, password, admin_login)
|
||||||
|
|
||||||
if valid_login:
|
if valid_login:
|
||||||
if user_group == 'guest':
|
if user_group == 'tautulli admin':
|
||||||
|
user_group = 'admin'
|
||||||
|
user_id = None
|
||||||
|
else:
|
||||||
if re.match(r"[^@]+@[^@]+\.[^@]+", username):
|
if re.match(r"[^@]+@[^@]+\.[^@]+", username):
|
||||||
user_details = Users().get_details(email=username)
|
user_details = Users().get_details(email=username)
|
||||||
else:
|
else:
|
||||||
user_details = Users().get_details(user=username)
|
user_details = Users().get_details(user=username)
|
||||||
|
|
||||||
user_id = user_details['user_id']
|
user_id = user_details['user_id']
|
||||||
else:
|
|
||||||
user_id = None
|
|
||||||
|
|
||||||
time_delta = timedelta(days=30) if remember_me == '1' else timedelta(minutes=60)
|
time_delta = timedelta(days=30) if remember_me == '1' else timedelta(minutes=60)
|
||||||
expiry = datetime.utcnow() + time_delta
|
expiry = datetime.utcnow() + time_delta
|
||||||
@@ -315,7 +303,10 @@ class AuthController(object):
|
|||||||
|
|
||||||
jwt_token = jwt.encode(payload, plexpy.CONFIG.JWT_SECRET, algorithm=JWT_ALGORITHM)
|
jwt_token = jwt.encode(payload, plexpy.CONFIG.JWT_SECRET, algorithm=JWT_ALGORITHM)
|
||||||
|
|
||||||
self.on_login(user_id, username, user_group)
|
self.on_login(username=username,
|
||||||
|
user_id=user_id,
|
||||||
|
user_group=user_group,
|
||||||
|
success=1)
|
||||||
|
|
||||||
jwt_cookie = JWT_COOKIE_NAME + plexpy.CONFIG.PMS_UUID
|
jwt_cookie = JWT_COOKIE_NAME + plexpy.CONFIG.PMS_UUID
|
||||||
cherrypy.response.cookie[jwt_cookie] = jwt_token
|
cherrypy.response.cookie[jwt_cookie] = jwt_token
|
||||||
@@ -327,13 +318,13 @@ class AuthController(object):
|
|||||||
return {'status': 'success', 'token': jwt_token.decode('utf-8'), 'uuid': plexpy.CONFIG.PMS_UUID}
|
return {'status': 'success', 'token': jwt_token.decode('utf-8'), 'uuid': plexpy.CONFIG.PMS_UUID}
|
||||||
|
|
||||||
elif admin_login == '1':
|
elif admin_login == '1':
|
||||||
self.on_login_failed(username)
|
self.on_login(username=username)
|
||||||
logger.debug(u"Tautulli WebAuth :: Invalid admin login attempt from '%s'." % username)
|
logger.debug(u"Tautulli WebAuth :: Invalid admin login attempt from '%s'." % username)
|
||||||
cherrypy.response.status = 401
|
cherrypy.response.status = 401
|
||||||
return error_message
|
return error_message
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.on_login_failed(username)
|
self.on_login(username=username)
|
||||||
logger.debug(u"Tautulli WebAuth :: Invalid login attempt from '%s'." % username)
|
logger.debug(u"Tautulli WebAuth :: Invalid login attempt from '%s'." % username)
|
||||||
cherrypy.response.status = 401
|
cherrypy.response.status = 401
|
||||||
return error_message
|
return error_message
|
||||||
|
@@ -2611,6 +2611,7 @@ class WebInterface(object):
|
|||||||
"pms_ssl": plexpy.CONFIG.PMS_SSL,
|
"pms_ssl": plexpy.CONFIG.PMS_SSL,
|
||||||
"pms_is_remote": plexpy.CONFIG.PMS_IS_REMOTE,
|
"pms_is_remote": plexpy.CONFIG.PMS_IS_REMOTE,
|
||||||
"pms_is_cloud": plexpy.CONFIG.PMS_IS_CLOUD,
|
"pms_is_cloud": plexpy.CONFIG.PMS_IS_CLOUD,
|
||||||
|
"pms_url": plexpy.CONFIG.PMS_URL,
|
||||||
"pms_url_manual": checked(plexpy.CONFIG.PMS_URL_MANUAL),
|
"pms_url_manual": checked(plexpy.CONFIG.PMS_URL_MANUAL),
|
||||||
"pms_uuid": plexpy.CONFIG.PMS_UUID,
|
"pms_uuid": plexpy.CONFIG.PMS_UUID,
|
||||||
"pms_web_url": plexpy.CONFIG.PMS_WEB_URL,
|
"pms_web_url": plexpy.CONFIG.PMS_WEB_URL,
|
||||||
@@ -2812,6 +2813,12 @@ class WebInterface(object):
|
|||||||
|
|
||||||
return {'result': 'success', 'message': 'Settings saved.'}
|
return {'result': 'success', 'message': 'Settings saved.'}
|
||||||
|
|
||||||
|
@cherrypy.expose
|
||||||
|
@cherrypy.tools.json_out()
|
||||||
|
@requireAuth(member_of("admin"))
|
||||||
|
def get_server_resources(self, **kwargs):
|
||||||
|
return plextv.get_server_resources(return_server=True, **kwargs)
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
@cherrypy.tools.json_out()
|
@cherrypy.tools.json_out()
|
||||||
@requireAuth(member_of("admin"))
|
@requireAuth(member_of("admin"))
|
||||||
@@ -3004,18 +3011,12 @@ class WebInterface(object):
|
|||||||
def get_notifier_config_modal(self, notifier_id=None, **kwargs):
|
def get_notifier_config_modal(self, notifier_id=None, **kwargs):
|
||||||
result = notifiers.get_notifier_config(notifier_id=notifier_id)
|
result = notifiers.get_notifier_config(notifier_id=notifier_id)
|
||||||
|
|
||||||
if not result['custom_conditions']:
|
|
||||||
result['custom_conditions'] = json.dumps([{'parameter': '', 'operator': '', 'value': ''}])
|
|
||||||
|
|
||||||
if not result['custom_conditions_logic']:
|
|
||||||
result['custom_conditions_logic'] = ''
|
|
||||||
|
|
||||||
parameters = [
|
parameters = [
|
||||||
{'name': param['name'], 'type': param['type'], 'value': param['value']}
|
{'name': param['name'], 'type': param['type'], 'value': param['value']}
|
||||||
for category in common.NOTIFICATION_PARAMETERS for param in category['parameters']
|
for category in common.NOTIFICATION_PARAMETERS for param in category['parameters']
|
||||||
]
|
]
|
||||||
|
|
||||||
return serve_template(templatename="notifier_config.html", notifier=result, parameters=json.dumps(parameters))
|
return serve_template(templatename="notifier_config.html", notifier=result, parameters=parameters)
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
@cherrypy.tools.json_out()
|
@cherrypy.tools.json_out()
|
||||||
@@ -3461,7 +3462,8 @@ class WebInterface(object):
|
|||||||
@cherrypy.tools.json_out()
|
@cherrypy.tools.json_out()
|
||||||
@requireAuth(member_of("admin"))
|
@requireAuth(member_of("admin"))
|
||||||
@addtoapi()
|
@addtoapi()
|
||||||
def get_server_id(self, hostname=None, port=None, identifier=None, ssl=0, remote=0, **kwargs):
|
def get_server_id(self, hostname=None, port=None, identifier=None, ssl=0, remote=0, manual=0,
|
||||||
|
get_url=False, **kwargs):
|
||||||
""" Get the PMS server identifier.
|
""" Get the PMS server identifier.
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -3474,7 +3476,8 @@ class WebInterface(object):
|
|||||||
remote (int): 0 or 1
|
remote (int): 0 or 1
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
string: The unique PMS identifier
|
json:
|
||||||
|
{'identifier': '08u2phnlkdshf890bhdlksghnljsahgleikjfg9t'}
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
# Attempt to get the pms_identifier from plex.tv if the server is published
|
# Attempt to get the pms_identifier from plex.tv if the server is published
|
||||||
@@ -3505,11 +3508,21 @@ class WebInterface(object):
|
|||||||
xml_head = request.getElementsByTagName('MediaContainer')[0]
|
xml_head = request.getElementsByTagName('MediaContainer')[0]
|
||||||
identifier = xml_head.getAttribute('machineIdentifier')
|
identifier = xml_head.getAttribute('machineIdentifier')
|
||||||
|
|
||||||
|
result = {'identifier': identifier}
|
||||||
|
|
||||||
if identifier:
|
if identifier:
|
||||||
return identifier
|
if get_url == 'true':
|
||||||
|
server = self.get_server_resources(pms_ip=hostname,
|
||||||
|
pms_port=port,
|
||||||
|
pms_ssl=ssl,
|
||||||
|
pms_is_remote=remote,
|
||||||
|
pms_url_manual=manual,
|
||||||
|
pms_identifier=identifier)
|
||||||
|
result['url'] = server['pms_url']
|
||||||
|
return result
|
||||||
else:
|
else:
|
||||||
logger.warn('Unable to retrieve the PMS identifier.')
|
logger.warn('Unable to retrieve the PMS identifier.')
|
||||||
return None
|
return result
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
@requireAuth(member_of("admin"))
|
@requireAuth(member_of("admin"))
|
||||||
@@ -5271,34 +5284,4 @@ class WebInterface(object):
|
|||||||
@cherrypy.tools.json_out()
|
@cherrypy.tools.json_out()
|
||||||
@requireAuth()
|
@requireAuth()
|
||||||
def get_plexpy_url(self, **kwargs):
|
def get_plexpy_url(self, **kwargs):
|
||||||
if plexpy.CONFIG.ENABLE_HTTPS:
|
return helpers.get_plexpy_url()
|
||||||
scheme = 'https'
|
|
||||||
else:
|
|
||||||
scheme = 'http'
|
|
||||||
|
|
||||||
# Have to return some hostname if socket fails even if 127.0.0.1 won't work
|
|
||||||
hostname = '127.0.0.1'
|
|
||||||
|
|
||||||
if plexpy.CONFIG.HTTP_HOST == '0.0.0.0':
|
|
||||||
import socket
|
|
||||||
try:
|
|
||||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
||||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
|
||||||
s.connect(('<broadcast>', 0))
|
|
||||||
hostname = s.getsockname()[0]
|
|
||||||
except socket.error:
|
|
||||||
hostname = socket.gethostbyname(socket.gethostname())
|
|
||||||
else:
|
|
||||||
hostname = plexpy.CONFIG.HTTP_HOST
|
|
||||||
|
|
||||||
if plexpy.CONFIG.HTTP_PORT not in (80, 443):
|
|
||||||
port = ':' + str(plexpy.CONFIG.HTTP_PORT)
|
|
||||||
else:
|
|
||||||
port = ''
|
|
||||||
|
|
||||||
if plexpy.CONFIG.HTTP_ROOT.strip('/'):
|
|
||||||
root = '/' + plexpy.CONFIG.HTTP_ROOT.strip('/')
|
|
||||||
else:
|
|
||||||
root = ''
|
|
||||||
|
|
||||||
return scheme + '://' + hostname + port + root
|
|
||||||
|
Reference in New Issue
Block a user