Compare commits

...

35 Commits

Author SHA1 Message Date
JonnyWong16
fc98e2f052 Merge branch 'dev' 2016-05-24 22:17:22 -07:00
JonnyWong16
bedcfa9520 v1.4.4 2016-05-24 22:17:09 -07:00
JonnyWong16
bb152b590b Set all datatable tooltips to body container 2016-05-24 22:13:26 -07:00
JonnyWong16
3623732cf7 Sort sessions by session_key
* Try to minimize instances jumping around under homepage current
activity.
* Still need to fix for music activity. Some clients have a different
session_key per track.
2016-05-24 22:12:57 -07:00
JonnyWong16
05ba89f164 Make sure clip image urls are escaped 2016-05-24 21:33:37 -07:00
JonnyWong16
cee656a053 Fix ip address in Plexivity import 2016-05-23 18:02:13 -07:00
JonnyWong16
cfc7d529e1 Merge branch 'dev' 2016-05-22 16:24:23 -07:00
JonnyWong16
a93dc68e6c v1.4.3 2016-05-22 16:24:01 -07:00
JonnyWong16
2d91cfd3db Fix basic auth 2016-05-22 16:22:35 -07:00
JonnyWong16
36e81f44cb Merge branch 'dev' 2016-05-22 15:04:25 -07:00
JonnyWong16
cb0e65337f v1.4.2 2016-05-22 15:04:17 -07:00
JonnyWong16
1c627f4649 Fix unable to save settings when checking http proxy 2016-05-22 14:54:44 -07:00
JonnyWong16
16cbfed20b Option to use HTTP basic authentication 2016-05-22 14:23:55 -07:00
JonnyWong16
f6a3bc57e2 Fix typos 2016-05-22 13:24:52 -07:00
JonnyWong16
594443d1dc Match port as well when retrieving pms url 2016-05-22 13:24:45 -07:00
JonnyWong16
c3378e1653 Merge branch 'dev' 2016-05-20 20:54:36 -07:00
JonnyWong16
bc57dd650c v1.4.1 2016-05-20 20:54:06 -07:00
JonnyWong16
311a8c6fa3 Try using requests for Join notifications 2016-05-20 20:44:16 -07:00
JonnyWong16
bdb43c0e9e Add paging for recently added to the API 2016-05-20 20:16:14 -07:00
JonnyWong16
8033b47596 Add secondary sort by most recent for watch statistics 2016-05-19 22:44:28 -07:00
JonnyWong16
9d5052cc68 Add http proxy checkbox to settings 2016-05-19 21:55:11 -07:00
JonnyWong16
f4c9dc8a5f Make sure cherrypy doesn't add the local port twice with http_proxy enabled 2016-05-19 21:54:58 -07:00
JonnyWong16
a3f0a78df0 Reduce cost factor for hashing passwords
* Also reduce memory cost
2016-05-19 20:24:22 -07:00
JonnyWong16
b70363e005 Fix resolution in stream data modal 2016-05-18 20:55:49 -07:00
JonnyWong16
65eab801e8 Format Join device ids 2016-05-18 20:55:49 -07:00
JonnyWong16
9e764248d3 Merge pull request #713 from Hellowlol/fix_log_order
Fix #705
2016-05-18 20:55:06 -07:00
Hellowlol
a660a1c44b fix https://github.com/drzoidberg33/plexpy/issues/705#issuecomment-219927893
Can you test and verify
2016-05-18 20:55:20 +02:00
JonnyWong16
33458c1bdb Make sure current activity returned sessions when refreshing 2016-05-17 21:10:05 -07:00
JonnyWong16
e5530182cd Make sure we get a result when trying to group the session 2016-05-17 20:58:28 -07:00
JonnyWong16
9ecabc3faf Just make sure redirects include http_root 2016-05-16 18:18:52 -07:00
JonnyWong16
8b58f6b861 Make sure pms_identifier is cleared first when verifying server 2016-05-16 09:14:00 -07:00
JonnyWong16
9e41bf529d Don't return inside the loop after sending XBMC/Plex notifications 2016-05-16 08:28:40 -07:00
JonnyWong16
36398fe958 Persist current activity artwork blur across refresh 2016-05-15 21:54:25 -07:00
JonnyWong16
69cfbea5f3 Refresh Join device list when changing API key 2016-05-15 17:20:28 -07:00
JonnyWong16
1e1e3beca6 Check for blank username/passwords on login 2016-05-15 17:20:14 -07:00
29 changed files with 272 additions and 159 deletions

1
API.md
View File

@@ -1061,6 +1061,7 @@ Required parameters:
count (str): Number of items to return count (str): Number of items to return
Optional parameters: Optional parameters:
start (str): The item number to start at
section_id (str): The id of the Plex library section section_id (str): The id of the Plex library section
Returns: Returns:

View File

@@ -1,5 +1,45 @@
# Changelog # Changelog
## v1.4.4 (2016-05-24)
* Fix: Image queries crashing the PMS when playing clips from channels.
* Fix: Plexivity import if IP address is missing.
* Fix: Tooltips shown behind the datatable headers.
* Fix: Current activity instances rendered in a random order causing them to jump around.
## v1.4.3 (2016-05-22)
* Fix: PlexPy not starting without any authentication method.
## v1.4.2 (2016-05-22)
* New: Option to use HTTP basic authentication instead of the HTML login form.
* Fix: Unable to save settings when enabling the HTTP proxy setting.
* Change: Match the PMS port when retrieving the PMS url.
## v1.4.1 (2016-05-20)
* New: HTTP Proxy checkbox in the settings. Enable this if using an SSL enabled reverse proxy in front of PlexPy.
* Fix: Check for blank username/password on login.
* Fix: Persist current activity artwork blur across refreshes when transcoding details are visible.
* Fix: Send notifications to multiple XBMC/Plex Home Theater devices.
* Fix: Reset PMS identifier when clicking verify server button in settings.
* Fix: Crash when trying to group current activity session in database.
* Fix: Check current activity returns sessions when refreshing.
* Fix: Logs sorted out of order.
* Fix: Resolution reported incorrectly in the stream info modal.
* Fix: PlexPy crashing when hashing password in the config file.
* Fix: CherryPy doubling the port number when accessing PlexPy locally with http_proxy enabled.
* Change: Sort by most recent for ties in watch statistics.
* Change: Refresh Join devices when changing the API key.
* Change: Format the Join device IDs.
* Change: Join notifications now sent with Python Requests module.
* Change: Add paging for recently added in the API.
## v1.4.0 (2016-05-15) ## v1.4.0 (2016-05-15)
* New: An HTML form login page with sessions support. * New: An HTML form login page with sessions support.

View File

@@ -1,10 +1,10 @@
<!--- <!---
Reporting Issues: Reporting Issues:
* To ensure that a develpoer has enough information to work with please include all of the information below. * To ensure that a developer has enough information to work with please include all of the information below.
Please provide as much detail as possible. Screenshots can be very useful to see the problem. Please provide as much detail as possible. Screenshots can be very useful to see the problem.
* Use proper markdown syntax to structure your post (i.e. code/log in code blocks). * Use proper markdown syntax to structure your post (i.e. code/log in code blocks).
See: https://help.github.com/articles/basic-writing-and-formatting-syntax/ See: https://help.github.com/articles/basic-writing-and-formatting-syntax/
* Iclude a link to your **FULL** log file that has the error(not just a few lines!). * Include a link to your **FULL** log file that has the error(not just a few lines!).
Please use [Gist](http://gist.github.com) or [Pastebin](http://pastebin.com/). Please use [Gist](http://gist.github.com) or [Pastebin](http://pastebin.com/).
Feature Requests: Feature Requests:

View File

@@ -214,6 +214,7 @@ def main():
'https_key': plexpy.CONFIG.HTTPS_KEY, 'https_key': plexpy.CONFIG.HTTPS_KEY,
'http_username': plexpy.CONFIG.HTTP_USERNAME, 'http_username': plexpy.CONFIG.HTTP_USERNAME,
'http_password': plexpy.CONFIG.HTTP_PASSWORD, 'http_password': plexpy.CONFIG.HTTP_PASSWORD,
'http_basic_auth': plexpy.CONFIG.HTTP_BASIC_AUTH
} }
webstart.initialize(web_config) webstart.initialize(web_config)

View File

@@ -2955,3 +2955,13 @@ a.no-highlight:hover {
min-width: 150px; min-width: 150px;
max-width: 250px; max-width: 250px;
} }
.inline-pre {
font-family: monospace;
margin: 0 2px;
padding: 2px 5px;
font-size: 13px;
color: #fff;
background-color: #555;
border: 0px solid #444;
border-radius: 3px;
}

View File

@@ -61,6 +61,8 @@ DOCUMENTATION :: END
% if data is not None: % if data is not None:
<% <%
from urllib import quote
from plexpy import helpers from plexpy import helpers
data['indexes'] = helpers.cast_to_int(data['indexes']) data['indexes'] = helpers.cast_to_int(data['indexes'])
%> %>
@@ -71,7 +73,7 @@ DOCUMENTATION :: END
% else: % else:
<a href="#"> <a href="#">
% endif % endif
<div class="dashboard-activity-poster"> <div class="dashboard-activity-poster" id="poster-${data['session_key']}">
% if not data['art'].startswith('interfaces') or not data['thumb'].startswith('interfaces'): % if not data['art'].startswith('interfaces') or not data['thumb'].startswith('interfaces'):
% if (data['media_type'] == 'movie' and not data['indexes']) or (data['indexes'] and not data['view_offset']): % if (data['media_type'] == 'movie' and not data['indexes']) or (data['indexes'] and not data['view_offset']):
<div id="bif-${data['session_key']}" class="dashboard-activity-poster-face" style="background-image: url(pms_image_proxy?img=${data['art']}&width=500&height=280&fallback=art);"></div> <div id="bif-${data['session_key']}" class="dashboard-activity-poster-face" style="background-image: url(pms_image_proxy?img=${data['art']}&width=500&height=280&fallback=art);"></div>
@@ -90,9 +92,11 @@ DOCUMENTATION :: END
<div class="dashboard-activity-poster-face" style="background-image: url(${data['thumb']});"></div> <div class="dashboard-activity-poster-face" style="background-image: url(${data['thumb']});"></div>
% else: % else:
% if data['art']: % if data['art']:
<div class="dashboard-activity-poster-face" style="background-image: url(pms_image_proxy?img=${data['art']}&width=500&height=280&fallback=art);"></div> <!--Hacky solution to escape the image url until I come up with something better-->
<div class="dashboard-activity-poster-face" style="background-image: url(pms_image_proxy?img=${quote(data['art'])}&width=500&height=280&fallback=art);"></div>
% else: % else:
<div class="dashboard-activity-poster-face" style="background-image: url(pms_image_proxy?img=${data['thumb']}&width=500&height=280&fallback=art);"></div> <!--Hacky solution to escape the image url until I come up with something better-->
<div class="dashboard-activity-poster-face" style="background-image: url(pms_image_proxy?img=${quote(data['thumb'])}&width=500&height=280&fallback=art);"></div>
% endif % endif
% endif % endif
% elif data['media_type'] == 'photo': % elif data['media_type'] == 'photo':
@@ -105,7 +109,7 @@ DOCUMENTATION :: END
<div class="dashboard-activity-poster-face" style="background-image: url(${data['art']});"></div> <div class="dashboard-activity-poster-face" style="background-image: url(${data['art']});"></div>
% endif % endif
<div class="dashboard-activity-button-info"> <div class="dashboard-activity-button-info">
<button type="button" class="btn btn-activity-info btn-lg" data-target="#stream-${data['session_key']}"> <button type="button" class="btn btn-activity-info btn-lg" data-target="#stream-${data['session_key']}" data-id="${data['session_key']}">
<i class="fa fa-info-circle"></i> <i class="fa fa-info-circle"></i>
</button> </button>
</div> </div>

View File

@@ -107,6 +107,11 @@
$('#dashboard-checking-activity').remove(); $('#dashboard-checking-activity').remove();
var current_activity = $.parseJSON(xhr.responseText); var current_activity = $.parseJSON(xhr.responseText);
if (!(current_activity)) {
$('#currentActivity').html('<div id="dashboard-no-activity" class="text-muted">There was an error communicating with your Plex Server.</div>');
return
}
var stream_count = parseInt(current_activity.stream_count); var stream_count = parseInt(current_activity.stream_count);
var sessions = current_activity.sessions; var sessions = current_activity.sessions;
@@ -150,6 +155,7 @@
bif_poster.animate({ opacity: 0 }, { duration: 1000, queue: false }); bif_poster.animate({ opacity: 0 }, { duration: 1000, queue: false });
bif_poster.after($('<div id="bif-' + key + '"class="dashboard-activity-poster-face" style="background-image: url(pms_image_proxy?img=' bif_poster.after($('<div id="bif-' + key + '"class="dashboard-activity-poster-face" style="background-image: url(pms_image_proxy?img='
+ s.bif_thumb + '&width=500&height=280&fallback=art);"></div>').fadeIn(1000, function () { bif_poster.remove() })); + s.bif_thumb + '&width=500&height=280&fallback=art);"></div>').fadeIn(1000, function () { bif_poster.remove() }));
blurArtwork(key);
} }
// if transcoding, update the transcode state // if transcoding, update the transcode state
@@ -210,14 +216,18 @@
getCurrentActivity(); getCurrentActivity();
}, 15000); }, 15000);
function blurArtwork(session_key) {
var filterVal = $('#stream-' + session_key).is(':visible') ? 'blur(5px)' : '';
$($('#poster-' + session_key).find('.dashboard-activity-poster-face, .dashboard-activity-cover-face'))
.css('filter', filterVal).css('webkitFilter', filterVal).css('mozFilter', filterVal).css('oFilter', filterVal).css('msFilter', filterVal);
}
// Show/Hide activity info // Show/Hide activity info
$('#currentActivity').on('click', '.btn-activity-info', function (e) { $('#currentActivity').on('click', '.btn-activity-info', function (e) {
e.preventDefault(); e.preventDefault();
$($(this).attr('data-target')).toggle(); $($(this).attr('data-target')).toggle();
var id = $(this).closest('.dashboard-instance').data('id'); var key = $(this).data('id');
var filterVal = $('#stream-' + id).is(':visible') ? 'blur(5px)' : ''; blurArtwork(key);
$($(this).closest('.dashboard-activity-poster').find('.dashboard-activity-poster-face, .dashboard-activity-cover-face'))
.css('filter',filterVal).css('webkitFilter',filterVal).css('mozFilter',filterVal).css('oFilter',filterVal).css('msFilter',filterVal);
}); });
// Add hover class to dashboard-instance // Add hover class to dashboard-instance

View File

@@ -448,10 +448,10 @@ function childTableOptions(rowData) {
// Create the tooltips. // Create the tooltips.
$('.expand-history-tooltip').tooltip({ container: 'body' }); $('.expand-history-tooltip').tooltip({ container: 'body' });
$('.external-ip-tooltip').tooltip(); $('.external-ip-tooltip').tooltip({ container: 'body' });
$('.transcode-tooltip').tooltip(); $('.transcode-tooltip').tooltip({ container: 'body' });
$('.media-type-tooltip').tooltip(); $('.media-type-tooltip').tooltip({ container: 'body' });
$('.watched-tooltip').tooltip(); $('.watched-tooltip').tooltip({ container: 'body' });
$('.thumb-tooltip').popover({ $('.thumb-tooltip').popover({
html: true, html: true,
container: 'body', container: 'body',

View File

@@ -132,8 +132,8 @@ history_table_modal_options = {
$('#ajaxMsg').fadeOut(); $('#ajaxMsg').fadeOut();
// Create the tooltips. // Create the tooltips.
$('.transcode-tooltip').tooltip(); $('.transcode-tooltip').tooltip({ container: 'body' });
$('.media-type-tooltip').tooltip(); $('.media-type-tooltip').tooltip({ container: 'body' });
$('.thumb-tooltip').popover({ $('.thumb-tooltip').popover({
html: true, html: true,
container: '#history-modal', container: '#history-modal',

View File

@@ -217,10 +217,10 @@ libraries_list_table_options = {
$('#ajaxMsg').fadeOut(); $('#ajaxMsg').fadeOut();
// Create the tooltips. // Create the tooltips.
$('.purge-tooltip').tooltip(); $('.purge-tooltip').tooltip({ container: 'body' });
$('.edit-tooltip').tooltip(); $('.edit-tooltip').tooltip({ container: 'body' });
$('.transcode-tooltip').tooltip(); $('.transcode-tooltip').tooltip({ container: 'body' });
$('.media-type-tooltip').tooltip(); $('.media-type-tooltip').tooltip({ container: 'body' });
$('.thumb-tooltip').popover({ $('.thumb-tooltip').popover({
html: true, html: true,
container: 'body', container: 'body',

View File

@@ -220,13 +220,14 @@ users_list_table_options = {
$('#ajaxMsg').fadeOut(); $('#ajaxMsg').fadeOut();
// Create the tooltips. // Create the tooltips.
$('.purge-tooltip').tooltip(); $('.purge-tooltip').tooltip({ container: 'body' });
$('.edit-tooltip').tooltip(); $('.edit-tooltip').tooltip({ container: 'body' });
$('.transcode-tooltip').tooltip(); $('.transcode-tooltip').tooltip({ container: 'body' });
$('.media-type-tooltip').tooltip(); $('.media-type-tooltip').tooltip({ container: 'body' });
$('.watched-tooltip').tooltip(); $('.watched-tooltip').tooltip({ container: 'body' });
$('.thumb-tooltip').popover({ $('.thumb-tooltip').popover({
html: true, html: true,
container: 'body',
trigger: 'hover', trigger: 'hover',
placement: 'right', placement: 'right',
template: '<div class="popover history-thumbnail-popover" role="tooltip"><div class="arrow" style="top: 50%;"></div><div class="popover-content"></div></div>', template: '<div class="popover history-thumbnail-popover" role="tooltip"><div class="arrow" style="top: 50%;"></div><div class="popover-content"></div></div>',

View File

@@ -217,7 +217,7 @@
} }
} }
$('#pushbullet_apikey, #pushover_apitoken, #scripts_folder').on('change', function () { $('#pushbullet_apikey, #pushover_apitoken, #scripts_folder, #join_apikey').on('change', function () {
// Reload modal to update certain fields // Reload modal to update certain fields
doAjaxCall('set_notification_config', $(this), 'tabs', true, reloadModal); doAjaxCall('set_notification_config', $(this), 'tabs', true, reloadModal);
return false; return false;

View File

@@ -109,6 +109,5 @@ DOCUMENTATION :: END
</div> </div>
</div> </div>
% else: % else:
<div class="text-muted">There was an error communicating with your Plex Server. Please check your <a href="settings">settings</a>. <div class="text-muted">There was an error communicating with your Plex Server.</div><br>
</div><br>
% endif % endif

View File

@@ -432,6 +432,13 @@
</div> </div>
<p class="help-block">The base URL of the web server. Used for reverse proxies.</p> <p class="help-block">The base URL of the web server. Used for reverse proxies.</p>
</div> </div>
<div class="checkbox">
<label>
<input type="checkbox" class="http-settings" name="http_proxy" id="http_proxy" value="1" ${config['http_proxy']}> Enable HTTP Proxy
</label>
<p class="help-block">Respect the X-Forwarded-Proto header. Used for reverse proxies with SSL.</p>
</div>
<br />
<div class="checkbox"> <div class="checkbox">
<label> <label>
<input type="checkbox" name="launch_browser" id="launch_browser" value="1" ${config['launch_browser']}> Launch Browser on Startup <input type="checkbox" name="launch_browser" id="launch_browser" value="1" ${config['launch_browser']}> Launch Browser on Startup
@@ -521,10 +528,17 @@
<label> <label>
<input type="checkbox" name="http_hash_password" id="http_hash_password" value="1" ${config['http_hash_password']} data-parsley-trigger="change"> Hash Password in the Config File <input type="checkbox" name="http_hash_password" id="http_hash_password" value="1" ${config['http_hash_password']} data-parsley-trigger="change"> Hash Password in the Config File
</label> </label>
<span id="hashPasswordCheck" style="color: #eb8600; padding-left: 10px;"></span>
<p class="help-block">Store a hashed password in the config file.<br />Warning: Your password cannot be recovered if forgotten!</p> <p class="help-block">Store a hashed password in the config file.<br />Warning: Your password cannot be recovered if forgotten!</p>
</div> </div>
<input type="text" id="http_hashed_password" name="http_hashed_password" value="${config['http_hashed_password']}" style="display: none;" data-parsley-trigger="change" data-parsley-type="integer" data-parsley-range="[0, 1]" <input type="text" id="http_hashed_password" name="http_hashed_password" value="${config['http_hashed_password']}" style="display: none;" data-parsley-trigger="change" data-parsley-type="integer" data-parsley-range="[0, 1]"
data-parsley-errors-container="#http_hash_password_error" data-parsley-error-message="Cannot un-hash password, please set a new password." data-parsley-no-focus required> data-parsley-errors-container="#http_hash_password_error" data-parsley-error-message="Cannot un-hash password, please set a new password." data-parsley-no-focus required>
<div class="checkbox">
<label>
<input type="checkbox" class="auth-settings" name="http_basic_auth" id="http_basic_auth" value="1" ${config['http_basic_auth']} data-parsley-trigger="change"> Use Basic Authentication
</label>
<p class="help-block">Use basic HTTP authentication instead of the HTML login form.</p>
</div>
<div class="padded-header"> <div class="padded-header">
@@ -559,7 +573,7 @@
</div> </div>
</div> </div>
</div> </div>
<p class="help-block">Current API key: <strong><br/>${config['api_key']}</strong></p> <p class="help-block">Current API key: <strong> ${config['api_key']}</strong></p>
</div> </div>
<p><input type="button" class="btn btn-bright save-button" value="Save" data-success="Changes saved successfully"></p> <p><input type="button" class="btn btn-bright save-button" value="Save" data-success="Changes saved successfully"></p>
@@ -2234,7 +2248,6 @@ $(document).ready(function() {
$( ".pms-settings" ).change(function() { $( ".pms-settings" ).change(function() {
serverChanged = true; serverChanged = true;
$("#pms_identifier").val(""); $("#pms_identifier").val("");
$("#pms-verify-status").html("");
$("#server_changed").prop('checked', true); $("#server_changed").prop('checked', true);
verifyServer(); verifyServer();
}); });
@@ -2287,6 +2300,7 @@ $(document).ready(function() {
} }
$('#verify_server_button').on('click', function(){ $('#verify_server_button').on('click', function(){
$("#pms_identifier").val("");
verifyServer(); verifyServer();
}); });
@@ -2563,7 +2577,11 @@ $(document).ready(function() {
}); });
function allowGuestAccessCheck () { function allowGuestAccessCheck () {
if ($('#http_username').val() == '' || $('#http_password').val() == '') { if ($("#http_basic_auth").is(":checked")) {
$("#allow_guest_access").attr("disabled", true);
$("#allow_guest_access").attr("checked", false);
$("#allowGuestCheck").html("Guest access cannot be enabled with basic authentication.");
} else if ($('#http_username').val() == '' || $('#http_password').val() == '') {
$("#allow_guest_access").attr("disabled", true); $("#allow_guest_access").attr("disabled", true);
$("#allow_guest_access").attr("checked", false); $("#allow_guest_access").attr("checked", false);
$("#allowGuestCheck").html("You must set an admin password above to allow guest access."); $("#allowGuestCheck").html("You must set an admin password above to allow guest access.");
@@ -2574,18 +2592,30 @@ $(document).ready(function() {
} }
allowGuestAccessCheck(); allowGuestAccessCheck();
$('#http_username, #http_password').change(function () { $('#http_username, #http_password, #http_basic_auth').change(function () {
allowGuestAccessCheck(); allowGuestAccessCheck();
}); });
function hashPasswordCheck () {
$("#http_hash_password").click(function(){ if ($("#http_basic_auth").is(":checked")) {
$("#http_hash_password").attr("checked", false);
$("#http_hash_password").attr("disabled", true);
$("#hashPasswordCheck").html("Password cannot be hashed with basic authentication.");
} else {
$("#http_hash_password").attr("disabled", false);
$("#hashPasswordCheck").html("");
}
if (!($("#http_hash_password").is(":checked")) && $("#http_hashed_password").val() == "1" && $("#http_password").val() == " ") { if (!($("#http_hash_password").is(":checked")) && $("#http_hashed_password").val() == "1" && $("#http_password").val() == " ") {
$("#http_hashed_password").val(-1); $("#http_hashed_password").val(-1);
} else if ($("#http_hash_password").is(":checked") && $("#http_hashed_password").val() == "-1" && $("#http_password").val() == " ") { } else if ($("#http_hash_password").is(":checked") && $("#http_hashed_password").val() == "-1" && $("#http_password").val() == " ") {
$("#http_hashed_password").val(1); $("#http_hashed_password").val(1);
$("#http_hash_password_error").html(""); $("#http_hash_password_error").html("");
} }
}
hashPasswordCheck();
$('#http_password, #http_hash_password, #http_basic_auth').change(function () {
hashPasswordCheck();
}); });
$('#http_password').change(function () { $('#http_password').change(function () {

View File

@@ -59,7 +59,7 @@ DOCUMENTATION :: END
<ul class="list-unstyled"> <ul class="list-unstyled">
<li>Container: <strong>${data['transcode_container'] if data['transcode_container'] else data['container']}</strong></li> <li>Container: <strong>${data['transcode_container'] if data['transcode_container'] else data['container']}</strong></li>
% if data['media_type'] != 'track': % if data['media_type'] != 'track':
<li>Resolution: <strong>${data['video_resolution'] + 'p' if data['video_resolution'] != 'sd' else data['video_resolution']}</strong></li> <li>Resolution: <strong>${data['transcode_height'] if data['transcode_height'] else data['height']}p</strong></li>
% endif % endif
</ul> </ul>
</div> </div>
@@ -101,7 +101,7 @@ DOCUMENTATION :: END
<ul class="list-unstyled"> <ul class="list-unstyled">
<li>Container: <strong>${data['container']}</strong></li> <li>Container: <strong>${data['container']}</strong></li>
% if data['media_type'] != 'track': % if data['media_type'] != 'track':
<li>Resolution: <strong>${data['height']}p</strong></li> <li>Resolution: <strong>${data['video_resolution'] + 'p' if data['video_resolution'] != 'sd' else data['video_resolution']}</strong></li>
% endif % endif
<li>Bitrate: <strong>${data['bitrate']} kbps</strong></li> <li>Bitrate: <strong>${data['bitrate']} kbps</strong></li>
</ul> </ul>

View File

@@ -195,7 +195,7 @@ def proxy(base=None, local='X-Forwarded-Host', remote='X-Forwarded-For',
if not base: if not base:
base = request.headers.get('Host', '127.0.0.1') base = request.headers.get('Host', '127.0.0.1')
port = request.local.port port = request.local.port
if port != 80: if port != 80 and not base.endswith(':%s' % port):
base += ':%s' % port base += ':%s' % port
if base.find("://") == -1: if base.find("://") == -1:

View File

@@ -32,7 +32,7 @@ HASH_FUNCTION = 'sha256' # Must be in hashlib.
# Linear to the hashing time. Adjust to be high but take a reasonable # Linear to the hashing time. Adjust to be high but take a reasonable
# amount of time on your server. Measure with: # amount of time on your server. Measure with:
# python -m timeit -s 'import passwords as p' 'p.make_hash("something")' # python -m timeit -s 'import passwords as p' 'p.make_hash("something")'
COST_FACTOR = 29000 COST_FACTOR = 10000
def make_hash(password): def make_hash(password):

View File

@@ -72,7 +72,7 @@ def pbkdf2_bin(data, salt, iterations=1000, keylen=24, hashfunc=None):
rv = u = _pseudorandom(salt + _pack_int(block)) rv = u = _pseudorandom(salt + _pack_int(block))
for i in xrange(iterations - 1): for i in xrange(iterations - 1):
u = _pseudorandom(''.join(map(chr, u))) u = _pseudorandom(''.join(map(chr, u)))
rv = starmap(xor, izip(rv, u)) rv = list(starmap(xor, izip(rv, u)))
buf.extend(rv) buf.extend(rv)
return ''.join(map(chr, buf))[:keylen] return ''.join(map(chr, buf))[:keylen]

View File

@@ -220,25 +220,29 @@ class ActivityProcessor(object):
result = self.db.select(query=query, args=args) result = self.db.select(query=query, args=args)
new_session = {'id': result[0]['id'], new_session = prev_session = last_id = None
'rating_key': result[0]['rating_key'], if len(result) > 1:
'view_offset': result[0]['view_offset'], new_session = {'id': result[0]['id'],
'user_id': result[0]['user_id'], 'rating_key': result[0]['rating_key'],
'reference_id': result[0]['reference_id']} 'view_offset': result[0]['view_offset'],
'user_id': result[0]['user_id'],
'reference_id': result[0]['reference_id']}
if len(result) == 1:
prev_session = None
else:
prev_session = {'id': result[1]['id'], prev_session = {'id': result[1]['id'],
'rating_key': result[1]['rating_key'], 'rating_key': result[1]['rating_key'],
'view_offset': result[1]['view_offset'], 'view_offset': result[1]['view_offset'],
'user_id': result[1]['user_id'], 'user_id': result[1]['user_id'],
'reference_id': result[1]['reference_id']} 'reference_id': result[1]['reference_id']}
else:
# Get the last insert row id
result = self.db.select(query='SELECT last_insert_rowid() AS last_id')
last_id = result[0]['last_id'] if result else None
query = 'UPDATE session_history SET reference_id = ? WHERE id = ? ' query = 'UPDATE session_history SET reference_id = ? WHERE id = ? '
# If rating_key is the same in the previous session, then set the reference_id to the previous row, else set the reference_id to the new id # If rating_key is the same in the previous session, then set the reference_id to the previous row, else set the reference_id to the new id
if (prev_session is not None) and (prev_session['rating_key'] == new_session['rating_key'] \ if prev_session == new_session == None:
and prev_session['view_offset'] <= new_session['view_offset']): args = [last_id, last_id]
elif prev_session['rating_key'] == new_session['rating_key'] and prev_session['view_offset'] <= new_session['view_offset']:
args = [prev_session['reference_id'], new_session['id']] args = [prev_session['reference_id'], new_session['id']]
else: else:
args = [new_session['id'], new_session['id']] args = [new_session['id'], new_session['id']]

View File

@@ -185,6 +185,7 @@ _CONFIG_DEFINITIONS = {
'HTTPS_KEY': (str, 'General', ''), 'HTTPS_KEY': (str, 'General', ''),
'HTTPS_DOMAIN': (str, 'General', 'localhost'), 'HTTPS_DOMAIN': (str, 'General', 'localhost'),
'HTTPS_IP': (str, 'General', '127.0.0.1'), 'HTTPS_IP': (str, 'General', '127.0.0.1'),
'HTTP_BASIC_AUTH': (int, 'General', 0),
'HTTP_ENVIRONMENT': (str, 'General', 'production'), 'HTTP_ENVIRONMENT': (str, 'General', 'production'),
'HTTP_HASH_PASSWORD': (int, 'General', 0), 'HTTP_HASH_PASSWORD': (int, 'General', 0),
'HTTP_HASHED_PASSWORD': (int, 'General', 0), 'HTTP_HASHED_PASSWORD': (int, 'General', 0),

View File

@@ -198,7 +198,7 @@ class DataFactory(object):
top_tv = [] top_tv = []
try: try:
query = 'SELECT t.id, t.grandparent_title, t.grandparent_rating_key, t.grandparent_thumb, t.section_id, ' \ query = 'SELECT t.id, t.grandparent_title, t.grandparent_rating_key, t.grandparent_thumb, t.section_id, ' \
't.media_type, t.content_rating, t.labels, ' \ 't.media_type, t.content_rating, t.labels, t.started, ' \
'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, SUM(t.d) AS total_duration ' \ 'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, SUM(t.d) AS total_duration ' \
'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \ 'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \
' (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) ' \ ' (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) ' \
@@ -210,7 +210,7 @@ class DataFactory(object):
' AND session_history.media_type = "episode" ' \ ' AND session_history.media_type = "episode" ' \
' GROUP BY %s) AS t ' \ ' GROUP BY %s) AS t ' \
'GROUP BY t.grandparent_title ' \ 'GROUP BY t.grandparent_title ' \
'ORDER BY %s DESC ' \ 'ORDER BY %s DESC, started DESC ' \
'LIMIT %s ' % (time_range, group_by, sort_type, stats_count) 'LIMIT %s ' % (time_range, group_by, sort_type, stats_count)
result = monitor_db.select(query) result = monitor_db.select(query)
except Exception as e: except Exception as e:
@@ -246,7 +246,7 @@ class DataFactory(object):
popular_tv = [] popular_tv = []
try: try:
query = 'SELECT t.id, t.grandparent_title, t.grandparent_rating_key, t.grandparent_thumb, t.section_id, ' \ query = 'SELECT t.id, t.grandparent_title, t.grandparent_rating_key, t.grandparent_thumb, t.section_id, ' \
't.media_type, t.content_rating, t.labels, ' \ 't.media_type, t.content_rating, t.labels, t.started, ' \
'COUNT(DISTINCT t.user_id) AS users_watched, ' \ 'COUNT(DISTINCT t.user_id) AS users_watched, ' \
'MAX(t.started) AS last_watch, COUNT(t.id) as total_plays, SUM(t.d) AS total_duration ' \ 'MAX(t.started) AS last_watch, COUNT(t.id) as total_plays, SUM(t.d) AS total_duration ' \
'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \ 'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \
@@ -259,7 +259,7 @@ class DataFactory(object):
' AND session_history.media_type = "episode" ' \ ' AND session_history.media_type = "episode" ' \
' GROUP BY %s) AS t ' \ ' GROUP BY %s) AS t ' \
'GROUP BY t.grandparent_title ' \ 'GROUP BY t.grandparent_title ' \
'ORDER BY users_watched DESC, %s DESC ' \ 'ORDER BY users_watched DESC, %s DESC, started DESC ' \
'LIMIT %s ' % (time_range, group_by, sort_type, stats_count) 'LIMIT %s ' % (time_range, group_by, sort_type, stats_count)
result = monitor_db.select(query) result = monitor_db.select(query)
except Exception as e: except Exception as e:
@@ -293,7 +293,7 @@ class DataFactory(object):
top_movies = [] top_movies = []
try: try:
query = 'SELECT t.id, t.full_title, t.rating_key, t.thumb, t.section_id, ' \ query = 'SELECT t.id, t.full_title, t.rating_key, t.thumb, t.section_id, ' \
't.media_type, t.content_rating, t.labels, ' \ 't.media_type, t.content_rating, t.labels, t.started, ' \
'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, SUM(t.d) AS total_duration ' \ 'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, SUM(t.d) AS total_duration ' \
'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \ 'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \
' (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) ' \ ' (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) ' \
@@ -305,7 +305,7 @@ class DataFactory(object):
' AND session_history.media_type = "movie" ' \ ' AND session_history.media_type = "movie" ' \
' GROUP BY %s) AS t ' \ ' GROUP BY %s) AS t ' \
'GROUP BY t.full_title ' \ 'GROUP BY t.full_title ' \
'ORDER BY %s DESC ' \ 'ORDER BY %s DESC, started DESC ' \
'LIMIT %s ' % (time_range, group_by, sort_type, stats_count) 'LIMIT %s ' % (time_range, group_by, sort_type, stats_count)
result = monitor_db.select(query) result = monitor_db.select(query)
except Exception as e: except Exception as e:
@@ -341,7 +341,7 @@ class DataFactory(object):
popular_movies = [] popular_movies = []
try: try:
query = 'SELECT t.id, t.full_title, t.rating_key, t.thumb, t.section_id, ' \ query = 'SELECT t.id, t.full_title, t.rating_key, t.thumb, t.section_id, ' \
't.media_type, t.content_rating, t.labels, ' \ 't.media_type, t.content_rating, t.labels, t.started, ' \
'COUNT(DISTINCT t.user_id) AS users_watched, ' \ 'COUNT(DISTINCT t.user_id) AS users_watched, ' \
'MAX(t.started) AS last_watch, COUNT(t.id) as total_plays, SUM(t.d) AS total_duration ' \ 'MAX(t.started) AS last_watch, COUNT(t.id) as total_plays, SUM(t.d) AS total_duration ' \
'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \ 'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \
@@ -354,7 +354,7 @@ class DataFactory(object):
' AND session_history.media_type = "movie" ' \ ' AND session_history.media_type = "movie" ' \
' GROUP BY %s) AS t ' \ ' GROUP BY %s) AS t ' \
'GROUP BY t.full_title ' \ 'GROUP BY t.full_title ' \
'ORDER BY users_watched DESC, %s DESC ' \ 'ORDER BY users_watched DESC, %s DESC, started DESC ' \
'LIMIT %s ' % (time_range, group_by, sort_type, stats_count) 'LIMIT %s ' % (time_range, group_by, sort_type, stats_count)
result = monitor_db.select(query) result = monitor_db.select(query)
except Exception as e: except Exception as e:
@@ -388,7 +388,7 @@ class DataFactory(object):
top_music = [] top_music = []
try: try:
query = 'SELECT t.id, t.grandparent_title, t.grandparent_rating_key, t.grandparent_thumb, t.section_id, ' \ query = 'SELECT t.id, t.grandparent_title, t.grandparent_rating_key, t.grandparent_thumb, t.section_id, ' \
't.media_type, t.content_rating, t.labels, ' \ 't.media_type, t.content_rating, t.labels, t.started, ' \
'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, SUM(t.d) AS total_duration ' \ 'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, SUM(t.d) AS total_duration ' \
'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \ 'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \
' (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) ' \ ' (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) ' \
@@ -400,7 +400,7 @@ class DataFactory(object):
' AND session_history.media_type = "track" ' \ ' AND session_history.media_type = "track" ' \
' GROUP BY %s) AS t ' \ ' GROUP BY %s) AS t ' \
'GROUP BY t.grandparent_title ' \ 'GROUP BY t.grandparent_title ' \
'ORDER BY %s DESC ' \ 'ORDER BY %s DESC, started DESC ' \
'LIMIT %s ' % (time_range, group_by, sort_type, stats_count) 'LIMIT %s ' % (time_range, group_by, sort_type, stats_count)
result = monitor_db.select(query) result = monitor_db.select(query)
except Exception as e: except Exception as e:
@@ -436,7 +436,7 @@ class DataFactory(object):
popular_music = [] popular_music = []
try: try:
query = 'SELECT t.id, t.grandparent_title, t.grandparent_rating_key, t.grandparent_thumb, t.section_id, ' \ query = 'SELECT t.id, t.grandparent_title, t.grandparent_rating_key, t.grandparent_thumb, t.section_id, ' \
't.media_type, t.content_rating, t.labels, ' \ 't.media_type, t.content_rating, t.labels, t.started, ' \
'COUNT(DISTINCT t.user_id) AS users_watched, ' \ 'COUNT(DISTINCT t.user_id) AS users_watched, ' \
'MAX(t.started) AS last_watch, COUNT(t.id) as total_plays, SUM(t.d) AS total_duration ' \ 'MAX(t.started) AS last_watch, COUNT(t.id) as total_plays, SUM(t.d) AS total_duration ' \
'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \ 'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \
@@ -449,7 +449,7 @@ class DataFactory(object):
' AND session_history.media_type = "track" ' \ ' AND session_history.media_type = "track" ' \
' GROUP BY %s) AS t ' \ ' GROUP BY %s) AS t ' \
'GROUP BY t.grandparent_title ' \ 'GROUP BY t.grandparent_title ' \
'ORDER BY users_watched DESC, %s DESC ' \ 'ORDER BY users_watched DESC, %s DESC, started DESC ' \
'LIMIT %s ' % (time_range, group_by, sort_type, stats_count) 'LIMIT %s ' % (time_range, group_by, sort_type, stats_count)
result = monitor_db.select(query) result = monitor_db.select(query)
except Exception as e: except Exception as e:
@@ -482,7 +482,7 @@ class DataFactory(object):
elif stat == 'top_users': elif stat == 'top_users':
top_users = [] top_users = []
try: try:
query = 'SELECT t.user, t.user_id, t.user_thumb, t.custom_thumb, ' \ query = 'SELECT t.user, t.user_id, t.user_thumb, t.custom_thumb, t.started, ' \
'(CASE WHEN t.friendly_name IS NULL THEN t.username ELSE t.friendly_name END) ' \ '(CASE WHEN t.friendly_name IS NULL THEN t.username ELSE t.friendly_name END) ' \
' AS friendly_name, ' \ ' AS friendly_name, ' \
'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, SUM(t.d) AS total_duration ' \ 'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, SUM(t.d) AS total_duration ' \
@@ -496,7 +496,7 @@ class DataFactory(object):
' >= datetime("now", "-%s days", "localtime") ' \ ' >= datetime("now", "-%s days", "localtime") ' \
' GROUP BY %s) AS t ' \ ' GROUP BY %s) AS t ' \
'GROUP BY t.user_id ' \ 'GROUP BY t.user_id ' \
'ORDER BY %s DESC ' \ 'ORDER BY %s DESC, started DESC ' \
'LIMIT %s ' % (time_range, group_by, sort_type, stats_count) 'LIMIT %s ' % (time_range, group_by, sort_type, stats_count)
result = monitor_db.select(query) result = monitor_db.select(query)
except Exception as e: except Exception as e:
@@ -536,7 +536,7 @@ class DataFactory(object):
top_platform = [] top_platform = []
try: try:
query = 'SELECT t.platform, ' \ query = 'SELECT t.platform, t.started, ' \
'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, SUM(t.d) AS total_duration ' \ 'MAX(t.started) AS last_watch, COUNT(t.id) AS total_plays, SUM(t.d) AS total_duration ' \
'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \ 'FROM (SELECT *, SUM(CASE WHEN stopped > 0 THEN (stopped - started) - ' \
' (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) ' \ ' (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) ' \
@@ -547,7 +547,7 @@ class DataFactory(object):
' >= datetime("now", "-%s days", "localtime") ' \ ' >= datetime("now", "-%s days", "localtime") ' \
' GROUP BY %s) AS t ' \ ' GROUP BY %s) AS t ' \
'GROUP BY t.platform ' \ 'GROUP BY t.platform ' \
'ORDER BY %s DESC ' \ 'ORDER BY %s DESC, started DESC ' \
'LIMIT %s ' % (time_range, group_by, sort_type, stats_count) 'LIMIT %s ' % (time_range, group_by, sort_type, stats_count)
result = monitor_db.select(query) result = monitor_db.select(query)
except Exception as e: except Exception as e:

View File

@@ -786,12 +786,13 @@ class XBMC(object):
raise Exception raise Exception
else: else:
logger.info(u"PlexPy Notifiers :: XBMC notification sent.") logger.info(u"PlexPy Notifiers :: XBMC notification sent.")
return True
except Exception: except Exception:
logger.warn(u"PlexPy Notifiers :: XBMC notification filed.") logger.warn(u"PlexPy Notifiers :: XBMC notification failed.")
return False return False
return True
def return_config_options(self): def return_config_options(self):
config_option = [{'label': 'XBMC Host:Port', config_option = [{'label': 'XBMC Host:Port',
'value': self.hosts, 'value': self.hosts,
@@ -870,12 +871,13 @@ class Plex(object):
raise Exception raise Exception
else: else:
logger.info(u"PlexPy Notifiers :: Plex Home Theater notification sent.") logger.info(u"PlexPy Notifiers :: Plex Home Theater notification sent.")
return True
except Exception: except Exception:
logger.warn(u"PlexPy Notifiers :: Plex Home Theater notification filed.") logger.warn(u"PlexPy Notifiers :: Plex Home Theater notification failed.")
return False return False
return True
def return_config_options(self): def return_config_options(self):
config_option = [{'label': 'Plex Home Theater Host:Port', config_option = [{'label': 'Plex Home Theater Host:Port',
'value': self.client_hosts, 'value': self.client_hosts,
@@ -2567,18 +2569,12 @@ class JOIN(object):
'title': subject.encode("utf-8"), 'title': subject.encode("utf-8"),
'text': message.encode("utf-8")} 'text': message.encode("utf-8")}
http_handler = HTTPSConnection("joinjoaomgcd.appspot.com") response = requests.post('https://joinjoaomgcd.appspot.com/_ah/api/messaging/v1/sendPush',
http_handler.request("POST", params=data)
"/_ah/api/messaging/v1/sendPush?%s" % urlencode(data)) request_status = response.status_code
response = http_handler.getresponse()
request_status = response.status
# logger.debug(u"PushBullet response status: %r" % request_status)
# logger.debug(u"PushBullet response headers: %r" % response.getheaders())
# logger.debug(u"PushBullet response body: %r" % response.read())
if request_status == 200: if request_status == 200:
data = json.loads(response.read()) data = json.loads(response.text)
if data.get('success'): if data.get('success'):
logger.info(u"PlexPy Notifiers :: Join notification sent.") logger.info(u"PlexPy Notifiers :: Join notification sent.")
return True return True
@@ -2632,7 +2628,10 @@ class JOIN(object):
return {'': ''} return {'': ''}
def return_config_options(self): def return_config_options(self):
devices = '<br>'.join(['%s: %s' % (v, k) for k, v in self.get_devices().iteritems() if k]) devices = '<br>'.join(['%s: <span class="inline-pre">%s</span>'
% (v, k) for k, v in self.get_devices().iteritems() if k])
if not devices:
devices = 'Enter your Join API key to load your device list.'
config_option = [{'label': 'Join API Key', config_option = [{'label': 'Join API Key',
'value': self.apikey, 'value': self.apikey,

View File

@@ -100,6 +100,7 @@ def extract_plexivity_xml(xml=None):
video_resolution = helpers.get_xml_attr(c, 'videoResolution') video_resolution = helpers.get_xml_attr(c, 'videoResolution')
width = helpers.get_xml_attr(c, 'width') width = helpers.get_xml_attr(c, 'width')
ip_address = ''
machine_id = '' machine_id = ''
platform = '' platform = ''
player = '' player = ''

View File

@@ -108,7 +108,8 @@ def get_real_pms_url():
if connections: if connections:
# Get connection with matching address, otherwise return first connection # Get connection with matching address, otherwise return first connection
conn = next((c for c in connections if c['address'] == plexpy.CONFIG.PMS_IP), connections[0]) conn = next((c for c in connections if c['address'] == plexpy.CONFIG.PMS_IP
and c['port'] == plexpy.CONFIG.PMS_PORT), connections[0])
plexpy.CONFIG.__setattr__('PMS_URL', conn['uri']) plexpy.CONFIG.__setattr__('PMS_URL', conn['uri'])
plexpy.CONFIG.write() plexpy.CONFIG.write()
logger.info(u"PlexPy PlexTV :: Server URL retrieved.") logger.info(u"PlexPy PlexTV :: Server URL retrieved.")

View File

@@ -14,7 +14,7 @@
# along with PlexPy. If not, see <http://www.gnu.org/licenses/>. # along with PlexPy. If not, see <http://www.gnu.org/licenses/>.
import threading import threading
import urllib2 import urllib
from urlparse import urlparse from urlparse import urlparse
import plexpy import plexpy
@@ -179,7 +179,7 @@ class PmsConnect(object):
return request return request
def get_recently_added(self, count='0', output_format=''): def get_recently_added(self, start='0', count='0', output_format=''):
""" """
Return list of recently added items. Return list of recently added items.
@@ -188,7 +188,7 @@ class PmsConnect(object):
Output: array Output: array
""" """
uri = '/library/recentlyAdded?X-Plex-Container-Start=0&X-Plex-Container-Size=' + count uri = '/library/recentlyAdded?X-Plex-Container-Start=%s&X-Plex-Container-Size=%s' % (start, count)
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol, proto=self.protocol,
request_type='GET', request_type='GET',
@@ -196,7 +196,7 @@ class PmsConnect(object):
return request return request
def get_library_recently_added(self, section_id='', count='0', output_format=''): def get_library_recently_added(self, section_id='', start='0', count='0', output_format=''):
""" """
Return list of recently added items. Return list of recently added items.
@@ -205,7 +205,7 @@ class PmsConnect(object):
Output: array Output: array
""" """
uri = '/library/sections/' + section_id + '/recentlyAdded?X-Plex-Container-Start=0&X-Plex-Container-Size=' + count uri = '/library/sections/%s/recentlyAdded?X-Plex-Container-Start=%s&X-Plex-Container-Size=%s' % (section_id, start, count)
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol, proto=self.protocol,
request_type='GET', request_type='GET',
@@ -387,7 +387,7 @@ class PmsConnect(object):
Output: array Output: array
""" """
uri = '/search?query=' + urllib2.quote(query.encode('utf8')) + track uri = '/search?query=' + urllib.quote(query.encode('utf8')) + track
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol, proto=self.protocol,
request_type='GET', request_type='GET',
@@ -458,7 +458,7 @@ class PmsConnect(object):
return request return request
def get_recently_added_details(self, section_id='', count='0'): def get_recently_added_details(self, section_id='', start='0', count='0'):
""" """
Return processed and validated list of recently added items. Return processed and validated list of recently added items.
@@ -467,9 +467,9 @@ class PmsConnect(object):
Output: array Output: array
""" """
if section_id: if section_id:
recent = self.get_library_recently_added(section_id, count, output_format='xml') recent = self.get_library_recently_added(section_id, start, count, output_format='xml')
else: else:
recent = self.get_recently_added(count, output_format='xml') recent = self.get_recently_added(start, count, output_format='xml')
try: try:
xml_head = recent.getElementsByTagName('MediaContainer') xml_head = recent.getElementsByTagName('MediaContainer')
@@ -1021,6 +1021,8 @@ class PmsConnect(object):
session_output = self.get_session_each(session_type, session_) session_output = self.get_session_each(session_type, session_)
session_list.append(session_output) session_list.append(session_output)
session_list = sorted(session_list, key=lambda k: k['session_key'])
output = {'stream_count': helpers.get_xml_attr(xml_head[0], 'size'), output = {'stream_count': helpers.get_xml_attr(xml_head[0], 'size'),
'sessions': session.mask_session_info(session_list) 'sessions': session.mask_session_info(session_list)
} }
@@ -1902,10 +1904,12 @@ class PmsConnect(object):
""" """
if img: if img:
uri = '/photo/:/transcode?url=http://127.0.0.1:32400%s' % img params = {'url': plexpy.CONFIG.PMS_URL + img}
if width.isdigit() and height.isdigit(): if width.isdigit() and height.isdigit():
uri += '&width=%s&height=%s' % (width, height) params['width'] = width
params['height'] = height
uri = '/photo/:/transcode?%s' % urllib.urlencode(params)
result = self.request_handler.make_request(uri=uri, result = self.request_handler.make_request(uri=uri,
proto=self.protocol, proto=self.protocol,
request_type='GET', request_type='GET',

View File

@@ -1,2 +1,2 @@
PLEXPY_VERSION = "master" PLEXPY_VERSION = "master"
PLEXPY_RELEASE_VERSION = "1.4.0" PLEXPY_RELEASE_VERSION = "1.4.4"

View File

@@ -36,7 +36,7 @@ from plexpy.plextv import PlexTV
SESSION_KEY = '_cp_username' SESSION_KEY = '_cp_username'
def user_login(username=None, password=None): def user_login(username=None, password=None):
if not username and not password: if not username or not password:
return None return None
# Try to login to Plex.tv to check if the user has a vaild account # Try to login to Plex.tv to check if the user has a vaild account
@@ -119,7 +119,7 @@ def check_auth(*args, **kwargs):
if not condition(): if not condition():
raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT) raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT)
else: else:
raise cherrypy.HTTPRedirect("auth/logout") raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT + "auth/logout")
def requireAuth(*conditions): def requireAuth(*conditions):
"""A decorator that appends conditions to the auth.require config """A decorator that appends conditions to the auth.require config
@@ -204,14 +204,14 @@ class AuthController(object):
@cherrypy.expose @cherrypy.expose
def index(self): def index(self):
raise cherrypy.HTTPRedirect("login") raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT + "auth/login")
@cherrypy.expose @cherrypy.expose
def login(self, username=None, password=None, remember_me='0', admin_login='0'): def login(self, username=None, password=None, remember_me='0', admin_login='0'):
if not cherrypy.config.get('tools.sessions.on'): if not cherrypy.config.get('tools.sessions.on'):
raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT) raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT)
if username is None or password is None: if not username and not password:
return self.get_loginform() return self.get_loginform()
(vaild_login, user_group) = check_credentials(username, password, admin_login) (vaild_login, user_group) = check_credentials(username, password, admin_login)
@@ -257,4 +257,4 @@ class AuthController(object):
if _session and _session['user']: if _session and _session['user']:
cherrypy.request.login = None cherrypy.request.login = None
self.on_logout(_session['user'], _session['user_group']) self.on_logout(_session['user'], _session['user_group'])
raise cherrypy.HTTPRedirect("login") raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT + "auth/login")

View File

@@ -82,9 +82,9 @@ class WebInterface(object):
@requireAuth() @requireAuth()
def index(self): def index(self):
if plexpy.CONFIG.FIRST_RUN_COMPLETE: if plexpy.CONFIG.FIRST_RUN_COMPLETE:
raise cherrypy.HTTPRedirect("home") raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT + "home")
else: else:
raise cherrypy.HTTPRedirect("welcome") raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT + "welcome")
##### Welcome ##### ##### Welcome #####
@@ -118,7 +118,7 @@ class WebInterface(object):
# The setup wizard just refreshes the page on submit so we must redirect to home if config set. # The setup wizard just refreshes the page on submit so we must redirect to home if config set.
if plexpy.CONFIG.FIRST_RUN_COMPLETE: if plexpy.CONFIG.FIRST_RUN_COMPLETE:
plexpy.initialize_scheduler() plexpy.initialize_scheduler()
raise cherrypy.HTTPRedirect("home") raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT + "home")
else: else:
return serve_template(templatename="welcome.html", title="Welcome", config=config) return serve_template(templatename="welcome.html", title="Welcome", config=config)
@@ -1987,24 +1987,14 @@ class WebInterface(object):
def getLog(self, start=0, length=100, **kwargs): def getLog(self, start=0, length=100, **kwargs):
start = int(start) start = int(start)
length = int(length) length = int(length)
search_value = "" order_dir = kwargs.get('order[0][dir]', "desc")
search_regex = "" order_column = kwargs.get('order[0][column]', "0")
order_column = 0 search_value = kwargs.get('search[value]', "")
order_dir = "desc" search_regex = kwargs.get('search[regex]', "") # Remove?
sortcolumn = 0
if 'order[0][dir]' in kwargs:
order_dir = kwargs.get('order[0][dir]', "desc")
if 'order[0][column]' in kwargs:
order_column = kwargs.get('order[0][column]', "0")
if 'search[value]' in kwargs:
search_value = kwargs.get('search[value]', "")
if 'search[regex]' in kwargs:
search_regex = kwargs.get('search[regex]', "")
filt = [] filt = []
filtered = []
fa = filt.append fa = filt.append
with open(os.path.join(plexpy.CONFIG.LOG_DIR, logger.FILENAME)) as f: with open(os.path.join(plexpy.CONFIG.LOG_DIR, logger.FILENAME)) as f:
for l in f.readlines(): for l in f.readlines():
@@ -2017,22 +2007,24 @@ class WebInterface(object):
# Add traceback message to previous msg. # Add traceback message to previous msg.
tl = (len(filt) - 1) tl = (len(filt) - 1)
n = len(l) - len(l.lstrip(' ')) n = len(l) - len(l.lstrip(' '))
l = '&nbsp;' * (2*n) + l[n:] l = '&nbsp;' * (2 * n) + l[n:]
filt[tl][2] += '<br>' + l filt[tl][2] += '<br>' + l
continue continue
filtered = []
if search_value == '': if search_value == '':
filtered = filt filtered = filt
else: else:
filtered = [row for row in filt for column in row if search_value.lower() in column.lower()] filtered = [row for row in filt for column in row if search_value.lower() in column.lower()]
sortcolumn = 0
if order_column == '1': if order_column == '1':
sortcolumn = 2 sortcolumn = 2
elif order_column == '2': elif order_column == '2':
sortcolumn = 1 sortcolumn = 1
filtered.sort(key=lambda x: x[sortcolumn], reverse=order_dir == "desc")
filtered.sort(key=lambda x: x[sortcolumn])
if order_dir == 'desc':
filtered = filtered[::-1]
rows = filtered[start:(start + length)] rows = filtered[start:(start + length)]
@@ -2215,7 +2207,7 @@ class WebInterface(object):
log_dir=plexpy.CONFIG.LOG_DIR, verbose=plexpy.VERBOSE) log_dir=plexpy.CONFIG.LOG_DIR, verbose=plexpy.VERBOSE)
logger.info(u"Verbose toggled, set to %s", plexpy.VERBOSE) logger.info(u"Verbose toggled, set to %s", plexpy.VERBOSE)
logger.debug(u"If you read this message, debug logging is available") logger.debug(u"If you read this message, debug logging is available")
raise cherrypy.HTTPRedirect("logs") raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT + "logs")
@cherrypy.expose @cherrypy.expose
@requireAuth() @requireAuth()
@@ -2255,6 +2247,7 @@ class WebInterface(object):
config = { config = {
"allow_guest_access": checked(plexpy.CONFIG.ALLOW_GUEST_ACCESS), "allow_guest_access": checked(plexpy.CONFIG.ALLOW_GUEST_ACCESS),
"http_basic_auth": checked(plexpy.CONFIG.HTTP_BASIC_AUTH),
"http_hash_password": checked(plexpy.CONFIG.HTTP_HASH_PASSWORD), "http_hash_password": checked(plexpy.CONFIG.HTTP_HASH_PASSWORD),
"http_hashed_password": plexpy.CONFIG.HTTP_HASHED_PASSWORD, "http_hashed_password": plexpy.CONFIG.HTTP_HASHED_PASSWORD,
"http_host": plexpy.CONFIG.HTTP_HOST, "http_host": plexpy.CONFIG.HTTP_HOST,
@@ -2262,6 +2255,7 @@ class WebInterface(object):
"http_port": plexpy.CONFIG.HTTP_PORT, "http_port": plexpy.CONFIG.HTTP_PORT,
"http_password": http_password, "http_password": http_password,
"http_root": plexpy.CONFIG.HTTP_ROOT, "http_root": plexpy.CONFIG.HTTP_ROOT,
"http_proxy": checked(plexpy.CONFIG.HTTP_PROXY),
"launch_browser": checked(plexpy.CONFIG.LAUNCH_BROWSER), "launch_browser": checked(plexpy.CONFIG.LAUNCH_BROWSER),
"enable_https": checked(plexpy.CONFIG.ENABLE_HTTPS), "enable_https": checked(plexpy.CONFIG.ENABLE_HTTPS),
"https_create_cert": checked(plexpy.CONFIG.HTTPS_CREATE_CERT), "https_create_cert": checked(plexpy.CONFIG.HTTPS_CREATE_CERT),
@@ -2374,7 +2368,7 @@ class WebInterface(object):
"ip_logging_enable", "movie_logging_enable", "tv_logging_enable", "music_logging_enable", "ip_logging_enable", "movie_logging_enable", "tv_logging_enable", "music_logging_enable",
"notify_consecutive", "notify_upload_posters", "notify_recently_added", "notify_recently_added_grandparent", "notify_consecutive", "notify_upload_posters", "notify_recently_added", "notify_recently_added_grandparent",
"monitor_pms_updates", "monitor_remote_access", "get_file_sizes", "log_blacklist", "http_hash_password", "monitor_pms_updates", "monitor_remote_access", "get_file_sizes", "log_blacklist", "http_hash_password",
"allow_guest_access", "cache_images" "allow_guest_access", "cache_images", "http_proxy", "http_basic_auth"
] ]
for checked_config in checked_configs: for checked_config in checked_configs:
if checked_config not in kwargs: if checked_config not in kwargs:
@@ -2857,7 +2851,7 @@ class WebInterface(object):
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
def checkGithub(self): def checkGithub(self):
versioncheck.checkGithub() versioncheck.checkGithub()
raise cherrypy.HTTPRedirect("home") raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT + "home")
@cherrypy.expose @cherrypy.expose
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
@@ -3336,7 +3330,7 @@ class WebInterface(object):
@cherrypy.tools.json_out() @cherrypy.tools.json_out()
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
@addtoapi("get_recently_added") @addtoapi("get_recently_added")
def get_recently_added_details(self, count='0', section_id='', **kwargs): def get_recently_added_details(self, start='0', count='0', section_id='', **kwargs):
""" Get all items that where recelty added to plex. """ Get all items that where recelty added to plex.
``` ```
@@ -3344,6 +3338,7 @@ class WebInterface(object):
count (str): Number of items to return count (str): Number of items to return
Optional parameters: Optional parameters:
start (str): The item number to start at
section_id (str): The id of the Plex library section section_id (str): The id of the Plex library section
Returns: Returns:
@@ -3373,7 +3368,7 @@ class WebInterface(object):
``` ```
""" """
pms_connect = pmsconnect.PmsConnect() pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_recently_added_details(count=count, section_id=section_id) result = pms_connect.get_recently_added_details(start=start, count=count, section_id=section_id)
if result: if result:
return result return result
@@ -3618,13 +3613,13 @@ class WebInterface(object):
pms_connect = pmsconnect.PmsConnect(token=plexpy.CONFIG.PMS_TOKEN) pms_connect = pmsconnect.PmsConnect(token=plexpy.CONFIG.PMS_TOKEN)
result = pms_connect.get_current_activity() result = pms_connect.get_current_activity()
data_factory = datafactory.DataFactory()
for session in result['sessions']:
if not session['ip_address']:
ip_address = data_factory.get_session_ip(session['session_key'])
session['ip_address'] = ip_address
if result: if result:
data_factory = datafactory.DataFactory()
for session in result['sessions']:
if not session['ip_address']:
ip_address = data_factory.get_session_ip(session['session_key'])
session['ip_address'] = ip_address
return result return result
else: else:
logger.warn(u"Unable to retrieve data for get_activity.") logger.warn(u"Unable to retrieve data for get_activity.")

View File

@@ -66,10 +66,15 @@ def initialize(options):
if options['http_password']: if options['http_password']:
logger.info(u"PlexPy WebStart :: Web server authentication is enabled, username is '%s'", options['http_username']) logger.info(u"PlexPy WebStart :: Web server authentication is enabled, username is '%s'", options['http_username'])
options_dict['tools.sessions.on'] = auth_enabled = session_enabled = True if options['http_basic_auth']:
cherrypy.tools.auth = cherrypy.Tool('before_handler', webauth.check_auth) auth_enabled = session_enabled = False
basic_auth_enabled = True
else:
options_dict['tools.sessions.on'] = auth_enabled = session_enabled = True
basic_auth_enabled = False
cherrypy.tools.auth = cherrypy.Tool('before_handler', webauth.check_auth)
else: else:
auth_enabled = session_enabled = False auth_enabled = session_enabled = basic_auth_enabled = False
if not options['http_root'] or options['http_root'] == '/': if not options['http_root'] or options['http_root'] == '/':
plexpy.HTTP_ROOT = options['http_root'] = '/' plexpy.HTTP_ROOT = options['http_root'] = '/'
@@ -88,7 +93,14 @@ def initialize(options):
'application/javascript'], 'application/javascript'],
'tools.auth.on': auth_enabled, 'tools.auth.on': auth_enabled,
'tools.sessions.on': session_enabled, 'tools.sessions.on': session_enabled,
'tools.sessions.timeout': 30 * 24 * 60 # 30 days 'tools.sessions.timeout': 30 * 24 * 60, # 30 days
'tools.auth_basic.on': basic_auth_enabled,
'tools.auth_basic.realm': 'PlexPy web server',
'tools.auth_basic.checkpassword': cherrypy.lib.auth_basic.checkpassword_dict({
options['http_username']: options['http_password']})
},
'/api': {
'tools.auth_basic.on': False
}, },
'/interfaces': { '/interfaces': {
'tools.staticdir.on': True, 'tools.staticdir.on': True,
@@ -199,7 +211,7 @@ def initialize(options):
'tools.expires.secs': 60 * 60 * 24 * 30, # 30 days 'tools.expires.secs': 60 * 60 * 24 * 30, # 30 days
'tools.auth.on': False, 'tools.auth.on': False,
'tools.sessions.on': False 'tools.sessions.on': False
}, }
} }
# Prevent time-outs # Prevent time-outs