Good day plex!

This commit is contained in:
John 2015-09-28 03:04:15 +02:00
parent f7bc208fd1
commit 37df262b24
5 changed files with 578 additions and 53 deletions

View file

@ -0,0 +1,401 @@
/**
* selectize.bootstrap3.css (v0.12.1) - Bootstrap 3 Theme
* Copyright (c) 20132015 Brian Reavis & contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
* file except in compliance with the License. You may obtain a copy of the License at:
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
* ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*
* @author Brian Reavis <brian@thirdroute.com>
*/
.selectize-control.plugin-drag_drop.multi > .selectize-input > div.ui-sortable-placeholder {
visibility: visible !important;
background: #f2f2f2 !important;
background: rgba(0, 0, 0, 0.06) !important;
border: 0 none !important;
-webkit-box-shadow: inset 0 0 12px 4px #ffffff;
box-shadow: inset 0 0 12px 4px #ffffff;
}
.selectize-control.plugin-drag_drop .ui-sortable-placeholder::after {
content: '!';
visibility: hidden;
}
.selectize-control.plugin-drag_drop .ui-sortable-helper {
-webkit-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
.selectize-dropdown-header {
position: relative;
padding: 3px 12px;
border-bottom: 1px solid #d0d0d0;
background: #f8f8f8;
-webkit-border-radius: 4px 4px 0 0;
-moz-border-radius: 4px 4px 0 0;
border-radius: 4px 4px 0 0;
}
.selectize-dropdown-header-close {
position: absolute;
right: 12px;
top: 50%;
color: #333333;
opacity: 0.4;
margin-top: -12px;
line-height: 20px;
font-size: 20px !important;
}
.selectize-dropdown-header-close:hover {
color: #000000;
}
.selectize-dropdown.plugin-optgroup_columns .optgroup {
border-right: 1px solid #f2f2f2;
border-top: 0 none;
float: left;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.selectize-dropdown.plugin-optgroup_columns .optgroup:last-child {
border-right: 0 none;
}
.selectize-dropdown.plugin-optgroup_columns .optgroup:before {
display: none;
}
.selectize-dropdown.plugin-optgroup_columns .optgroup-header {
border-top: 0 none;
}
.selectize-control.plugin-remove_button [data-value] {
position: relative;
padding-right: 24px !important;
}
.selectize-control.plugin-remove_button [data-value] .remove {
z-index: 1;
/* fixes ie bug (see #392) */
position: absolute;
top: 0;
right: 0;
bottom: 0;
width: 17px;
text-align: center;
font-weight: bold;
font-size: 12px;
color: inherit;
text-decoration: none;
vertical-align: middle;
display: inline-block;
padding: 1px 0 0 0;
border-left: 1px solid rgba(0, 0, 0, 0);
-webkit-border-radius: 0 2px 2px 0;
-moz-border-radius: 0 2px 2px 0;
border-radius: 0 2px 2px 0;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.selectize-control.plugin-remove_button [data-value] .remove:hover {
background: rgba(0, 0, 0, 0.05);
}
.selectize-control.plugin-remove_button [data-value].active .remove {
border-left-color: rgba(0, 0, 0, 0);
}
.selectize-control.plugin-remove_button .disabled [data-value] .remove:hover {
background: none;
}
.selectize-control.plugin-remove_button .disabled [data-value] .remove {
border-left-color: rgba(77, 77, 77, 0);
}
.selectize-control {
position: relative;
}
.selectize-dropdown,
.selectize-input,
.selectize-input input {
color: #333333;
font-family: inherit;
font-size: inherit;
line-height: 20px;
-webkit-font-smoothing: inherit;
}
.selectize-input,
.selectize-control.single .selectize-input.input-active {
background: #ffffff;
cursor: text;
display: inline-block;
}
.selectize-input {
border: 1px solid #cccccc;
padding: 6px 12px;
display: inline-block;
width: 100%;
overflow: hidden;
position: relative;
z-index: 1;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
-webkit-box-shadow: none;
box-shadow: none;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.selectize-control.multi .selectize-input.has-items {
padding: 5px 12px 2px;
}
.selectize-input.full {
background-color: #ffffff;
}
.selectize-input.disabled,
.selectize-input.disabled * {
cursor: default !important;
}
.selectize-input.focus {
-webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.15);
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.15);
}
.selectize-input.dropdown-active {
-webkit-border-radius: 4px 4px 0 0;
-moz-border-radius: 4px 4px 0 0;
border-radius: 4px 4px 0 0;
}
.selectize-input > * {
vertical-align: baseline;
display: -moz-inline-stack;
display: inline-block;
zoom: 1;
*display: inline;
}
.selectize-control.multi .selectize-input > div {
cursor: pointer;
margin: 0 3px 3px 0;
padding: 1px 3px;
background: #efefef;
color: #333333;
border: 0 solid rgba(0, 0, 0, 0);
}
.selectize-control.multi .selectize-input > div.active {
background: #428bca;
color: #ffffff;
border: 0 solid rgba(0, 0, 0, 0);
}
.selectize-control.multi .selectize-input.disabled > div,
.selectize-control.multi .selectize-input.disabled > div.active {
color: #808080;
background: #ffffff;
border: 0 solid rgba(77, 77, 77, 0);
}
.selectize-input > input {
display: inline-block !important;
padding: 0 !important;
min-height: 0 !important;
max-height: none !important;
max-width: 100% !important;
margin: 0 !important;
text-indent: 0 !important;
border: 0 none !important;
background: none !important;
line-height: inherit !important;
-webkit-user-select: auto !important;
-webkit-box-shadow: none !important;
box-shadow: none !important;
}
.selectize-input > input::-ms-clear {
display: none;
}
.selectize-input > input:focus {
outline: none !important;
}
.selectize-input::after {
content: ' ';
display: block;
clear: left;
}
.selectize-input.dropdown-active::before {
content: ' ';
display: block;
position: absolute;
background: #ffffff;
height: 1px;
bottom: 0;
left: 0;
right: 0;
}
.selectize-dropdown {
position: absolute;
z-index: 10;
border: 1px solid #d0d0d0;
background: #ffffff;
margin: -1px 0 0 0;
border-top: 0 none;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
-webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
-webkit-border-radius: 0 0 4px 4px;
-moz-border-radius: 0 0 4px 4px;
border-radius: 0 0 4px 4px;
}
.selectize-dropdown [data-selectable] {
cursor: pointer;
overflow: hidden;
}
.selectize-dropdown [data-selectable] .highlight {
background: rgba(255, 237, 40, 0.4);
-webkit-border-radius: 1px;
-moz-border-radius: 1px;
border-radius: 1px;
}
.selectize-dropdown [data-selectable],
.selectize-dropdown .optgroup-header {
padding: 3px 12px;
}
.selectize-dropdown .optgroup:first-child .optgroup-header {
border-top: 0 none;
}
.selectize-dropdown .optgroup-header {
color: #777777;
background: #ffffff;
cursor: default;
}
.selectize-dropdown .active {
background-color: #f5f5f5;
color: #262626;
}
.selectize-dropdown .active.create {
color: #262626;
}
.selectize-dropdown .create {
color: rgba(51, 51, 51, 0.5);
}
.selectize-dropdown-content {
overflow-y: auto;
overflow-x: hidden;
max-height: 200px;
}
.selectize-control.single .selectize-input,
.selectize-control.single .selectize-input input {
cursor: pointer;
}
.selectize-control.single .selectize-input.input-active,
.selectize-control.single .selectize-input.input-active input {
cursor: text;
}
.selectize-control.single .selectize-input:after {
content: ' ';
display: block;
position: absolute;
top: 50%;
right: 17px;
margin-top: -3px;
width: 0;
height: 0;
border-style: solid;
border-width: 5px 5px 0 5px;
border-color: #333333 transparent transparent transparent;
}
.selectize-control.single .selectize-input.dropdown-active:after {
margin-top: -4px;
border-width: 0 5px 5px 5px;
border-color: transparent transparent #333333 transparent;
}
.selectize-control.rtl.single .selectize-input:after {
left: 17px;
right: auto;
}
.selectize-control.rtl .selectize-input > input {
margin: 0 4px 0 -2px !important;
}
.selectize-control .selectize-input.disabled {
opacity: 0.5;
background-color: #ffffff;
}
.selectize-dropdown,
.selectize-dropdown.form-control {
height: auto;
padding: 0;
margin: 2px 0 0 0;
z-index: 1000;
background: #ffffff;
border: 1px solid #cccccc;
border: 1px solid rgba(0, 0, 0, 0.15);
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
-webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
}
.selectize-dropdown .optgroup-header {
font-size: 12px;
line-height: 1.42857143;
}
.selectize-dropdown .optgroup:first-child:before {
display: none;
}
.selectize-dropdown .optgroup:before {
content: ' ';
display: block;
height: 1px;
margin: 9px 0;
overflow: hidden;
background-color: #e5e5e5;
margin-left: -12px;
margin-right: -12px;
}
.selectize-dropdown-content {
padding: 5px 0;
}
.selectize-dropdown-header {
padding: 6px 12px;
}
.selectize-input {
min-height: 34px;
}
.selectize-input.dropdown-active {
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.selectize-input.dropdown-active::before {
display: none;
}
.selectize-input.focus {
border-color: #66afe9;
outline: 0;
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);
}
.has-error .selectize-input {
border-color: #a94442;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
}
.has-error .selectize-input:focus {
border-color: #843534;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;
}
.selectize-control.multi .selectize-input.has-items {
padding-left: 9px;
padding-right: 9px;
}
.selectize-control.multi .selectize-input > div {
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
}
.form-control.selectize-control {
padding: 0;
height: auto;
border: none;
background: none;
-webkit-box-shadow: none;
box-shadow: none;
-webkit-border-radius: 0;
-moz-border-radius: 0;
border-radius: 0;
}

File diff suppressed because one or more lines are too long

View file

@ -15,6 +15,7 @@ from plexpy import common
<link href="interfaces/default/css/bootstrap3/bootstrap.css" rel="stylesheet"> <link href="interfaces/default/css/bootstrap3/bootstrap.css" rel="stylesheet">
<link href="interfaces/default/css/bootstrap-wizard.css" rel="stylesheet"> <link href="interfaces/default/css/bootstrap-wizard.css" rel="stylesheet">
<link href="interfaces/default/css/plexpy.css" rel="stylesheet"> <link href="interfaces/default/css/plexpy.css" rel="stylesheet">
<link href="interfaces/default/css/selectize.bootstrap3.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet" type="text/css"> <link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet" type="text/css">
<link href="interfaces/default/css/font-awesome.min.css" rel="stylesheet"> <link href="interfaces/default/css/font-awesome.min.css" rel="stylesheet">
<link rel="icon" type="image/x-icon" href="interfaces/default/images/favicon.ico"/> <link rel="icon" type="image/x-icon" href="interfaces/default/images/favicon.ico"/>
@ -40,45 +41,6 @@ from plexpy import common
</div> </div>
</div> </div>
<div class="wizard-card" data-cardname="card2"> <div class="wizard-card" data-cardname="card2">
<h3>Plex Media Server</h3>
<form>
<p class="help-block">Enter your Plex Server details and then click the Verify button to make sure PlexPy can reach the server.</p>
<div class="wizard-input-section">
<label for="pms_ip">Plex IP or Hostname</label>
<div class="row">
<div class="col-xs-8">
<input type="text" class="form-control pms-settings" name="pms_ip" id="pms_ip" placeholder="127.0.0.1" value="${config['pms_ip']}" required>
</div>
</div>
</div>
<div class="wizard-input-section">
<label for="pms_port">Port Number</label>
<div class="row">
<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>
</div>
<div class="col-xs-4">
<div class="checkbox">
<label>
<input type="checkbox" id="pms_ssl" name="pms_ssl" value="1" ${config['pms_ssl']}> Force SSL
</label>
</div>
</div>
<div class="col-xs-4">
<div class="checkbox">
<label>
<input type="checkbox" id="pms_is_remote" name="pms_is_remote" value="1" ${config['pms_is_remote']}> Remote Server
</label>
</div>
</div>
</div>
</div>
<input type="hidden" class="form-control pms-settings" id="pms_valid" data-validate="validatePMSip" value="">
<input type="hidden" class="form-control pms-settings" 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>
</div>
<div class="wizard-card" data-cardname="card3">
<h3>Plex Authentication</h3> <h3>Plex Authentication</h3>
<p class="help-block">Enter your Plex.tv username and password. PlexPy does not store your username or password.</p> <p class="help-block">Enter your Plex.tv username and password. PlexPy does not store your username or password.</p>
<div class="wizard-input-section"> <div class="wizard-input-section">
@ -100,6 +62,46 @@ from plexpy import common
<input type="hidden" class="form-control pms-auth" name="pms_token" id="pms_token" value="${config['pms_token']}" data-validate="validatePMStoken"> <input type="hidden" class="form-control pms-auth" name="pms_token" id="pms_token" value="${config['pms_token']}" data-validate="validatePMStoken">
<a class="btn btn-dark" id="pms-authenticate" href="#" role="button">Authenticate</a><span style="margin-left: 10px; display: none;" id="pms-token-status"></span> <a class="btn btn-dark" id="pms-authenticate" href="#" role="button">Authenticate</a><span style="margin-left: 10px; display: none;" id="pms-token-status"></span>
</div> </div>
<div class="wizard-card" data-cardname="card3">
<h3>Plex Media Server</h3>
<form>
<p class="help-block">Enter your Plex Server details and then click the Verify button to make sure PlexPy can reach the server.</p>
<div class="wizard-input-section">
<label for="pms_ip">Plex IP or Hostname</label>
<div class="row">
<div class="col-xs-8">
<select id="pms_ip"></select>
</div>
</div>
</div>
<div class="wizard-input-section">
<label for="pms_port">Port Number</label>
<div class="row">
<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>
</div>
<div class="col-xs-4">
<div class="checkbox">
<label>
<input type="checkbox" id="pms_ssl" name="pms_ssl" value="1"> Force SSL
</label>
</div>
</div>
<div class="col-xs-4">
<div class="checkbox">
<label>
<input type="checkbox" id="pms_is_remote" name="pms_is_remote" value="1"> Remote Server
</label>
</div>
</div>
</div>
</div>
<input type="hidden" class="form-control pms-settings" id="pms_valid" data-validate="validatePMSip" value="">
<input type="hidden" class="form-control pms-settings" 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>
</div>
<div class="wizard-card" data-cardname="card4"> <div class="wizard-card" data-cardname="card4">
<h3>Monitoring</h3> <h3>Monitoring</h3>
<div class="wizard-input-section"> <div class="wizard-input-section">
@ -184,6 +186,7 @@ from plexpy import common
<script src="interfaces/default/js/jquery-2.1.4.min.js"></script> <script src="interfaces/default/js/jquery-2.1.4.min.js"></script>
<script src="interfaces/default/js/bootstrap3/bootstrap.min.js"></script> <script src="interfaces/default/js/bootstrap3/bootstrap.min.js"></script>
<script src="interfaces/default/js/selectize.min.js"></script>
<script src="interfaces/default/js/script.js"></script> <script src="interfaces/default/js/script.js"></script>
<script src="interfaces/default/js/bootstrap-wizard.min.js"></script> <script src="interfaces/default/js/bootstrap-wizard.min.js"></script>
<script> <script>
@ -218,6 +221,73 @@ from plexpy import common
} }
}) })
}); });
$select_pms = $('#pms_ip').selectize({
create: true,
createOnBlur: true,
openOnFocus: true,
maxItems: 1,
closeAfterSelect: true,
onInitialize: function () {
var s = this;
this.revertSettings.$children.each(function () {
$.extend(s.options[this.value], $(this).data());
});
},
render: {
option: function (item, escape) {
return '<div data-use_ssl="' + item.httpsRequired + '" data-local="' + item.local + '" data-ci="' + item.clientIdentifier + '" data-ip="' + item.ip + '" data-port="' + item.port + '">' + item.value + '</div>';
},
item: function (item, escape) {
// first item is rendered before initialization bug?
if (!item.ci) {
$.extend(item,
$(this.revertSettings.$children)
.filter('[value="' + item.value + '"]').data());
}
return '<div data-use_ssl="' + item.httpsRequired + '" data-local="' + item.local + '" data-ci="' + item.clientIdentifier + '" data-ip="' + item.ip + '" data-port="' + item.port + '">' + item.value + '</div>';
}
},
onChange: function (item) {
var ci = $('.selectize-input').find('div').attr('data-ci');
var port = $('.selectize-input').find('div').attr('data-port')
var local = $('.selectize-input').find('div').attr('data-local')
var ssl = $('.selectize-input').find('div').attr('data-use_ssl')
$("#pms-verify-status").html("");
// If a option was added by a user its
// data-xxx="undefined"
if (ci != "undefined") {
// To allow next step in the guide.
// servers with clientIdentifier is verified
$("#pms_valid").val("valid");
$("#pms-verify-status").html('<i class="fa fa-check"></i> Server found!').show();
} else {
// Self made options must be verified
$("#pms_valid").val("");
$("#pms-verify-status").html("").hide();
}
// If the server is verified set the correct port
if (port != "undefined") {
$('#pms_port').val(port);
} else {
// set default port
$('#pms_port').val("32400");
}
if (local != "undefined" && local == '0') {
$('#pms_is_remote').prop('checked', true);
} else {
$('#pms_is_remote').prop('checked', false);
}
if (ssl != "undefined" && ssl == "1") {
$('#pms_ssl').prop('checked', true);
} else {
$('#pms_ssl').prop('checked', false);
}
}
});
}); });
@ -371,6 +441,7 @@ from plexpy import common
$('#pms-token-status').fadeIn('fast'); $('#pms-token-status').fadeIn('fast');
$("#pms_token").val(authToken); $("#pms_token").val(authToken);
authenticated = true; authenticated = true;
getServerOptions(authToken)
} }
}); });
} else { } else {
@ -399,6 +470,18 @@ from plexpy import common
} }
}); });
}); });
function getServerOptions(token) {
/* Set token and returns server options */
$.ajax({
url: "discover/" + token,
success: function (result) {
$('#pms_ip').html("")
// Add all servers to the "combobox"
$select_pms[0].selectize.addOption(result);
}
})
}
</script> </script>
</body> </body>

View file

@ -1,3 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This file is part of PlexPy. # This file is part of PlexPy.
# #
# PlexPy is free software: you can redistribute it and/or modify # PlexPy is free software: you can redistribute it and/or modify
@ -14,7 +17,8 @@
# along with PlexPy. If not, see <http://www.gnu.org/licenses/>. # along with PlexPy. If not, see <http://www.gnu.org/licenses/>.
from plexpy import logger, helpers, users, http_handler, database from plexpy import logger, helpers, users, http_handler, database
import xmltodict
import json
from xml.dom import minidom from xml.dom import minidom
import base64 import base64
@ -402,3 +406,20 @@ class PlexTV(object):
server_urls.append(server_details) server_urls.append(server_details)
return server_urls return server_urls
def discover(self):
""" Query plex for all servers online. Returns the ones you own in a selectize format """
result = self.get_plextv_resources(include_https=True, output_format='raw')
clean_servers = []
servers = xmltodict.parse(result, process_namespaces=True, attr_prefix='')
if servers:
for server in servers['MediaContainer']['Device']:
if server.get('presence', None) == '1' and server.get('owned', None) == '1':
for s in server['Connection']:
s.update(server)
s['value'] = s['address']
s['label'] = server['name']
del s['Connection']
clean_servers.append(s)
return json.dumps(clean_servers, indent=4)

View file

@ -1,4 +1,7 @@
# This file is part of PlexPy. #!/usr/bin/env python
# -*- coding: utf-8 -*-
# This file is part of PlexPy.
# #
# PlexPy is free software: you can redistribute it and/or modify # PlexPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -97,8 +100,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.
# Also redirecting to home if a PMS token already exists - will remove this in future. if plexpy.CONFIG.FIRST_RUN_COMPLETE:
if plexpy.CONFIG.FIRST_RUN_COMPLETE or plexpy.CONFIG.PMS_TOKEN:
plexpy.initialize_scheduler() plexpy.initialize_scheduler()
raise cherrypy.HTTPRedirect("home") raise cherrypy.HTTPRedirect("home")
else: else:
@ -1396,7 +1398,6 @@ class WebInterface(object):
cherrypy.response.headers['Content-type'] = 'application/json' cherrypy.response.headers['Content-type'] = 'application/json'
return json.dumps({'message': 'no data received'}) return json.dumps({'message': 'no data received'})
# test code # test code
@cherrypy.expose @cherrypy.expose
def get_new_rating_keys(self, rating_key='', media_type='', **kwargs): def get_new_rating_keys(self, rating_key='', media_type='', **kwargs):
@ -1441,3 +1442,19 @@ class WebInterface(object):
return json.dumps(result) return json.dumps(result)
else: else:
logger.warn('Unable to retrieve data.') logger.warn('Unable to retrieve data.')
@cherrypy.expose
def discover(self, token=''):
"""
Returns the servers that you own as a
list of dicts (formatted for selectize)
"""
# Need to set token so result dont return http 401
plexpy.CONFIG.__setattr__('PMS_TOKEN', token)
plexpy.CONFIG.write()
result = plextv.PlexTV()
servers = result.discover()
if servers:
cherrypy.response.headers['Content-type'] = 'application/json'
return servers