mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-11 07:46:07 -07:00
v1.2.2
This commit is contained in:
commit
5fe47d797f
32 changed files with 1476 additions and 372 deletions
57
API.md
57
API.md
|
@ -4,24 +4,77 @@ The API is still pretty new and needs some serious cleaning up on the backend bu
|
|||
## General structure
|
||||
The API endpoint is `http://ip:port + HTTP_ROOT + /api?apikey=$apikey&cmd=$command`
|
||||
|
||||
Data response in JSON formatted.
|
||||
Response example
|
||||
```
|
||||
{
|
||||
"response": {
|
||||
"data": [
|
||||
{
|
||||
"loglevel": "INFO",
|
||||
"msg": "Signal 2 caught, saving and exiting...",
|
||||
"thread": "MainThread",
|
||||
"time": "22-sep-2015 01:42:56 "
|
||||
}
|
||||
],
|
||||
"message": null,
|
||||
"result": "success"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
General parameters:
|
||||
out_type: 'xml',
|
||||
callback: 'pong',
|
||||
'debug': 1
|
||||
|
||||
|
||||
## API methods
|
||||
|
||||
### getLogs
|
||||
Not working yet
|
||||
Possible params: sort='', search='', order='desc', regex='', start=0, end=0
|
||||
Returns the plexpy log
|
||||
|
||||
### getApikey
|
||||
Possible params: username='', password='' (required if auth is enabled)
|
||||
Returns the apikey
|
||||
|
||||
### getSettings
|
||||
No params
|
||||
Returns the config file
|
||||
|
||||
### getVersion
|
||||
No params
|
||||
Returns some version information: git_path, install_type, current_version, installed_version, commits_behind
|
||||
|
||||
### getHistory
|
||||
possible params: user=None, user_id=None, ,rating_key='', parent_rating_key='', grandparent_rating_key='', start_date=''
|
||||
Returns
|
||||
|
||||
### getMetadata
|
||||
Required params: rating_key
|
||||
Returns metadata about a file
|
||||
|
||||
### getSync
|
||||
Possible params: machine_id=None, user_id=None,
|
||||
Returns
|
||||
|
||||
### getUserips
|
||||
Possible params: user_id=None, user=None
|
||||
|
||||
### getPlayby
|
||||
Possible params: time_range=30, y_axis='plays', playtype='total_plays_per_month'
|
||||
|
||||
### checkGithub
|
||||
Updates the version information above and returns getVersion data
|
||||
|
||||
### shutdown
|
||||
No params
|
||||
Shut down plexpy
|
||||
|
||||
### restart
|
||||
No params
|
||||
Restart plexpy
|
||||
|
||||
### update
|
||||
No params
|
||||
Update plexpy - you may want to check the install type in get version and not allow this if type==exe
|
||||
|
|
14
CHANGELOG.md
14
CHANGELOG.md
|
@ -1,5 +1,19 @@
|
|||
# Changelog
|
||||
|
||||
## v1.2.2 (2015-10-12)
|
||||
|
||||
* Add server discovery on first run.
|
||||
* Add column to tables for Platform.
|
||||
* Add link to top level breadcrumbs on info pages.
|
||||
* Add ability to change notification sounds for Pushover and Boxcar.
|
||||
* Show watched percentage tooltip on progress column in history tables.
|
||||
* More logging in event an http request fails.
|
||||
* Code cleanups and other fixes.
|
||||
* Fix ordering on sync table.
|
||||
* Fix bug on home stats cards.
|
||||
* Fix bug on activity pane where music details were not shown.
|
||||
|
||||
|
||||
## v1.2.1 (2015-09-29)
|
||||
|
||||
* Fix for possible issue when paused_counter is null.
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# This file is part of PlexPy.
|
||||
#
|
||||
# PlexPy is free software: you can redistribute it and/or modify
|
||||
|
|
|
@ -961,14 +961,24 @@ a:hover .dashboard-recent-media-cover {
|
|||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
.summary-navbar-list span {
|
||||
display: inline-block;
|
||||
margin-right: 15px;
|
||||
.summary-navbar-list .breadcrumb {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background: none;
|
||||
}
|
||||
.summary-navbar-list span a {
|
||||
.summary-navbar-list .breadcrumb > li + li:before {
|
||||
color: #444;
|
||||
font-family: FontAwesome;
|
||||
content: "\f054";
|
||||
padding: 0 15px;
|
||||
}
|
||||
.summary-navbar-list .breadcrumb > .active {
|
||||
color: #eee;
|
||||
}
|
||||
.summary-navbar-list .breadcrumb a {
|
||||
color: #999;
|
||||
}
|
||||
.summary-navbar-list span a:hover {
|
||||
.summary-navbar-list .breadcrumb a:hover {
|
||||
color: #F9AA03;
|
||||
}
|
||||
.summary-content-title-wrapper {
|
||||
|
@ -1565,19 +1575,19 @@ a:hover .item-children-poster {
|
|||
top: 3px;
|
||||
left: 3px;
|
||||
}
|
||||
.user-platforms ul {
|
||||
.user-player ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
}
|
||||
.user-platforms-instance {
|
||||
.user-player-instance {
|
||||
float: left;
|
||||
width: 240px;
|
||||
height: 80px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
.user-platforms-instance li {
|
||||
.user-player-instance li {
|
||||
}
|
||||
.user-platforms-instance-box {
|
||||
.user-player-instance-box {
|
||||
float: left;
|
||||
width: 75px;
|
||||
border-radius: 3px;
|
||||
|
@ -1589,7 +1599,7 @@ a:hover .item-children-poster {
|
|||
height: 80px;
|
||||
width: 80px;
|
||||
}
|
||||
.user-platforms-instance-name {
|
||||
.user-player-instance-name {
|
||||
float: left;
|
||||
padding-top: 14px;
|
||||
color: #fff;
|
||||
|
@ -1602,7 +1612,7 @@ a:hover .item-children-poster {
|
|||
width: 140px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
.user-platforms-instance-playcount h3 {
|
||||
.user-player-instance-playcount h3 {
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: #F9AA03;
|
||||
|
@ -1612,7 +1622,7 @@ a:hover .item-children-poster {
|
|||
margin: 0 5px 0 10px;
|
||||
float: left;
|
||||
}
|
||||
.user-platforms-instance-playcount p {
|
||||
.user-player-instance-playcount p {
|
||||
color: #aaa;
|
||||
font-size: 12px;
|
||||
float: left;
|
||||
|
|
401
data/interfaces/default/css/selectize.bootstrap3.css
Normal file
401
data/interfaces/default/css/selectize.bootstrap3.css
Normal file
|
@ -0,0 +1,401 @@
|
|||
/**
|
||||
* selectize.bootstrap3.css (v0.12.1) - Bootstrap 3 Theme
|
||||
* Copyright (c) 2013–2015 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;
|
||||
}
|
|
@ -137,9 +137,9 @@ DOCUMENTATION :: END
|
|||
<br />
|
||||
% if a['audio_decision'] == 'direct play':
|
||||
Audio <strong>Direct Play (${a['audio_codec']}) (${a['audio_channels']}ch)</strong>
|
||||
% elif a['audio_decision'] == 'Copy':
|
||||
% elif a['audio_decision'] == 'copy':
|
||||
Audio <strong>Direct Stream (${a['transcode_audio_codec']}) (${a['transcode_audio_channels']}ch)</strong>
|
||||
% elif a['audio_decision'] != 'transcode':
|
||||
% elif a['audio_decision'] == 'transcode':
|
||||
Audio <strong>Transcode (${a['transcode_audio_codec']}) (${a['transcode_audio_channels']}ch)</strong>
|
||||
% endif
|
||||
% elif a['media_type'] == 'episode' or a['media_type'] == 'movie' or a['media_type'] == 'clip':
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
<th align='left' id="friendly_name">User</th>
|
||||
<th align='left' id="ip_address">IP Address</th>
|
||||
<th align='left' id="platform">Platform</th>
|
||||
<th align='left' id="device">Player</th>
|
||||
<th align='left' id="title">Title</th>
|
||||
<th align='left' id="started">Started</th>
|
||||
<th align='left' id="paused_counter">Paused</th>
|
||||
|
@ -84,7 +85,7 @@
|
|||
}
|
||||
}
|
||||
history_table = $('#history_table').DataTable(history_table_options);
|
||||
var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 10] });
|
||||
var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 11] });
|
||||
$(colvis.button()).appendTo('div.colvis-button-bar');
|
||||
|
||||
clearSearchButton('history_table', history_table);
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<th align='left' id="started">Started</th>
|
||||
<th align='left' id="stopped">Stopped</th>
|
||||
<th align='left' id="friendly_name">User</th>
|
||||
<th align='left' id="platform">Platform</th>
|
||||
<th align='left' id="player">Player</th>
|
||||
<th align='left' id="title">Title</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
|
@ -65,7 +65,7 @@ DOCUMENTATION :: END
|
|||
%>
|
||||
|
||||
% if data:
|
||||
% if data[0]['rows'] or data[1]['rows'] or data[2]['rows'] or data[3]['rows'] or data[4]['rows'] or data[5]['rows']:
|
||||
% if data[0]['rows']:
|
||||
<ul class="list-unstyled">
|
||||
% for top_stat in data:
|
||||
% if top_stat['stat_id'] == 'top_tv' and top_stat['rows']:
|
||||
|
|
|
@ -60,61 +60,61 @@ DOCUMENTATION :: END
|
|||
% if data:
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
% if data['type'] != 'library':
|
||||
<div class="art-face" style="background-image:url(pms_image_proxy?img=${data['art']}&width=1920&height=1080)"></div>
|
||||
% endif
|
||||
<div class="summary-container">
|
||||
<div class="summary-navbar">
|
||||
<div class="col-md-12">
|
||||
<div class="summary-navbar-list">
|
||||
% if data['type'] == 'movie':
|
||||
<span>Movies</span>
|
||||
<span><i class="fa fa-chevron-right"></i></span>
|
||||
<span><a href="#">${data['title']}</a></span>
|
||||
% elif data['type'] == 'show':
|
||||
<span>TV Shows</span>
|
||||
<span><i class="fa fa-chevron-right"></i></span>
|
||||
<span><a href="#">${data['title']}</a></span>
|
||||
% elif data['type'] == 'season':
|
||||
<span class="hidden-xs hidden-sm">TV Shows</span>
|
||||
<span class="hidden-xs hidden-sm"><i class="fa fa-chevron-right"></i></span>
|
||||
<span><a href="info?item_id=${data['parent_rating_key']}">${data['parent_title']}</a></span>
|
||||
<span><i class="fa fa-chevron-right"></i></span>
|
||||
<span><a href="#">Season ${data['index']}</a></span>
|
||||
% elif data['type'] == 'episode':
|
||||
<span class="hidden-xs hidden-sm">TV Shows</span>
|
||||
<span class="hidden-xs hidden-sm"><i class="fa fa-chevron-right"></i></span>
|
||||
<span class="hidden-xs hidden-sm"><a href="info?item_id=${data['grandparent_rating_key']}">${data['grandparent_title']}</a></span>
|
||||
<span class="hidden-xs hidden-sm"><i class="fa fa-chevron-right"></i></span>
|
||||
<span><a href="info?item_id=${data['parent_rating_key']}">Season ${data['parent_index']}</a></span>
|
||||
<span><i class="fa fa-chevron-right"></i></span>
|
||||
<span><a href="#">Episode ${data['index']} - ${data['title']}</a></span>
|
||||
% elif data['type'] == 'artist':
|
||||
<span>Music</span>
|
||||
<span><i class="fa fa-chevron-right"></i></span>
|
||||
<span><a href="#">${data['title']}</a></span>
|
||||
% elif data['type'] == 'album':
|
||||
<span class="hidden-xs hidden-sm">Music</span>
|
||||
<span class="hidden-xs hidden-sm"><i class="fa fa-chevron-right"></i></span>
|
||||
<span><a href="info?item_id=${data['parent_rating_key']}">${data['parent_title']}</a></span>
|
||||
<span><i class="fa fa-chevron-right"></i></span>
|
||||
<span><a href="#">${data['title']}</a></span>
|
||||
% elif data['type'] == 'track':
|
||||
<span class="hidden-xs hidden-sm">Music</span>
|
||||
<span class="hidden-xs hidden-sm"><i class="fa fa-chevron-right"></i></span>
|
||||
<span class="hidden-xs hidden-sm"><a href="info?item_id=${data['grandparent_rating_key']}">${data['grandparent_title']}</a></span>
|
||||
<span class="hidden-xs hidden-sm"><i class="fa fa-chevron-right"></i></span>
|
||||
<span><a href="info?item_id=${data['parent_rating_key']}">${data['parent_title']}</a></span>
|
||||
<span><i class="fa fa-chevron-right"></i></span>
|
||||
<span><a href="#">Track ${data['index']} - ${data['title']}</a></span>
|
||||
<ul class="list-unstyled breadcrumb">
|
||||
% if data['type'] == 'library':
|
||||
% if data['library'] == 'movie':
|
||||
<li class="active">Movies</li>
|
||||
% elif data['library'] == 'show':
|
||||
<li class="active">TV Shows</li>
|
||||
% elif data['library'] == 'artist':
|
||||
<li class="active">Music</li>
|
||||
% endif
|
||||
% elif data['type'] == 'movie':
|
||||
<li><a href="info?item_id=movie">Movies</a></li>
|
||||
<li class="active">${data['title']}</li>
|
||||
% elif data['type'] == 'show':
|
||||
<li><a href="info?item_id=show">TV Shows</a></li>
|
||||
<li class="active">${data['title']}</li>
|
||||
% elif data['type'] == 'season':
|
||||
<li class="hidden-xs hidden-sm"><a href="info?item_id=show">TV Shows</a></li>
|
||||
<li><a href="info?item_id=${data['parent_rating_key']}">${data['parent_title']}</a></li>
|
||||
<li class="active">Season ${data['index']}</li>
|
||||
% elif data['type'] == 'episode':
|
||||
<li class="hidden-xs hidden-sm"><a href="info?item_id=show">TV Shows</a></li>
|
||||
<li class="hidden-xs hidden-sm"><a href="info?item_id=${data['grandparent_rating_key']}">${data['grandparent_title']}</a></li>
|
||||
<li><a href="info?item_id=${data['parent_rating_key']}">Season ${data['parent_index']}</a></li>
|
||||
<li class="active">Episode ${data['index']} - ${data['title']}</li>
|
||||
% elif data['type'] == 'artist':
|
||||
<li><a href="info?item_id=artist">Music</a></li>
|
||||
<li class="active">${data['title']}</li>
|
||||
% elif data['type'] == 'album':
|
||||
<li class="hidden-xs hidden-sm"><a href="info?item_id=artist">Music</a></li>
|
||||
<li><a href="info?item_id=${data['parent_rating_key']}">${data['parent_title']}</a></li>
|
||||
<li class="active">${data['title']}</li>
|
||||
% elif data['type'] == 'track':
|
||||
<li class="hidden-xs hidden-sm"><a href="info?item_id=artist">Music</a></li>
|
||||
<li class="hidden-xs hidden-sm"><a href="info?item_id=${data['grandparent_rating_key']}">${data['grandparent_title']}</a></li>
|
||||
<li><a href="info?item_id=${data['parent_rating_key']}">${data['parent_title']}</a></li>
|
||||
<li class="active">Track ${data['index']} - ${data['title']}</li>
|
||||
% endif
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
% if data['type'] != 'library':
|
||||
<div class="summary-content-title-wrapper">
|
||||
<div class="col-md-9">
|
||||
<div class="summary-content-poster hidden-xs hidden-sm">
|
||||
% if data['type'] == 'track':
|
||||
<a href="http://app.plex.tv/web/app#!/server/${config['pms_identifier']}/details/%2Flibrary%2Fmetadata%2F${data['parent_rating_key']}" target="Plex/Web" title="View in Plex/Web">
|
||||
% else:
|
||||
% elif data['type'] != 'library':
|
||||
<a href="http://app.plex.tv/web/app#!/server/${config['pms_identifier']}/details/%2Flibrary%2Fmetadata%2F${data['rating_key']}" target="Plex/Web" title="View in Plex/Web">
|
||||
% endif
|
||||
% if data['type'] == 'episode':
|
||||
|
@ -129,7 +129,7 @@ DOCUMENTATION :: END
|
|||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
% else:
|
||||
% elif data['type'] != 'library':
|
||||
<div class="summary-poster-face" style="background-image: url(pms_image_proxy?img=${data['thumb']}&width=300&height=450&fallback=poster);">
|
||||
<div class="summary-poster-face-overlay">
|
||||
<span></span>
|
||||
|
@ -159,7 +159,9 @@ DOCUMENTATION :: END
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
<div class="summary-content-wrapper">
|
||||
% if data['type'] != 'library':
|
||||
<div class="col-md-9">
|
||||
% if data['type'] == 'movie' or data['type'] == 'show' or data['type'] == 'season':
|
||||
<div class="summary-content-padding hidden-xs hidden-sm" style="height: 275px;"></div>
|
||||
|
@ -311,6 +313,7 @@ DOCUMENTATION :: END
|
|||
</div>
|
||||
</div>
|
||||
% endif
|
||||
% endif
|
||||
<div class='col-md-12'>
|
||||
<div class='table-card-header'>
|
||||
<div class="header-bar">
|
||||
|
@ -333,6 +336,7 @@ DOCUMENTATION :: END
|
|||
<th align='left' id="friendly_name">User</th>
|
||||
<th align='left' id="ip_address">IP Address</th>
|
||||
<th align='left' id="platform">Platform</th>
|
||||
<th align='left' id="player">Player</th>
|
||||
<th align='left' id="title">Title</th>
|
||||
<th align='left' id="started">Started</th>
|
||||
<th align='left' id="paused_counter">Paused</th>
|
||||
|
@ -506,7 +510,20 @@ DOCUMENTATION :: END
|
|||
|
||||
% if data:
|
||||
<script src="interfaces/default/js/tables/history_table.js"></script>
|
||||
% if data['type'] == 'show' or data['type'] == 'artist':
|
||||
% if data['type'] == 'library':
|
||||
<script>
|
||||
function get_history() {
|
||||
history_table_options.ajax = {
|
||||
"url": "get_history",
|
||||
type: 'post',
|
||||
data: function ( d ) {
|
||||
return { 'json_data': JSON.stringify( d ),
|
||||
'media_type': '${data['media_type']}' };
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
% elif data['type'] == 'show' or data['type'] == 'artist':
|
||||
<script>
|
||||
function get_history() {
|
||||
history_table_options.ajax = {
|
||||
|
@ -550,7 +567,7 @@ DOCUMENTATION :: END
|
|||
$(document).ready(function () {
|
||||
get_history();
|
||||
history_table = $('#history_table').DataTable(history_table_options);
|
||||
var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 10] });
|
||||
var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 11] });
|
||||
$(colvis.button()).appendTo('div.colvis-button-bar');
|
||||
|
||||
clearSearchButton('history_table', history_table);
|
||||
|
@ -605,7 +622,7 @@ DOCUMENTATION :: END
|
|||
});
|
||||
</script>
|
||||
% endif
|
||||
% if data['rating']:
|
||||
% if data['type'] != 'library' and data['rating']:
|
||||
<script>
|
||||
// Convert rating to 5 star rating type
|
||||
var starRating = Math.round(${data['rating']} / 2);
|
||||
|
|
|
@ -217,13 +217,13 @@ function getPlatformImagePath(platformName) {
|
|||
return 'interfaces/default/images/platforms/playstation.png';
|
||||
} else if (platformName.indexOf("Playstation 4") > -1) {
|
||||
return 'interfaces/default/images/platforms/playstation.png';
|
||||
} else if (platformName.indexOf("Xbox 360") > -1) {
|
||||
} else if (platformName.indexOf("Xbox 360") > -1) {
|
||||
return 'interfaces/default/images/platforms/xbox.png';
|
||||
} else if (platformName.indexOf("Windows") > -1) {
|
||||
return 'interfaces/default/images/platforms/win8.png';
|
||||
} else if (platformName.indexOf("Windows phone") > -1) {
|
||||
return 'interfaces/default/images/platforms/wp.png';
|
||||
} else {
|
||||
} else if (platformName.indexOf("Windows") > -1) {
|
||||
return 'interfaces/default/images/platforms/win8.png';
|
||||
} else if (platformName.indexOf("Windows phone") > -1) {
|
||||
return 'interfaces/default/images/platforms/wp.png';
|
||||
} else {
|
||||
return 'interfaces/default/images/platforms/default.png';
|
||||
}
|
||||
}
|
||||
|
|
3
data/interfaces/default/js/selectize.min.js
vendored
Normal file
3
data/interfaces/default/js/selectize.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -100,7 +100,18 @@ history_table_options = {
|
|||
},
|
||||
{
|
||||
"targets": [4],
|
||||
"data":"player",
|
||||
"data":"platform",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
$(td).html(cellData);
|
||||
}
|
||||
},
|
||||
"width": "8%",
|
||||
"className": "no-wrap hidden-md hidden-sm hidden-xs modal-control"
|
||||
},
|
||||
{
|
||||
"targets": [5],
|
||||
"data": "player",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
var transcode_dec = '';
|
||||
|
@ -114,11 +125,11 @@ history_table_options = {
|
|||
$(td).html('<div><a href="#" data-target="#info-modal" data-toggle="modal"><div style="float: left;">' + transcode_dec + ' ' + cellData + '</div></a></div>');
|
||||
}
|
||||
},
|
||||
"width": "15%",
|
||||
"width": "12%",
|
||||
"className": "no-wrap hidden-md hidden-sm hidden-xs modal-control"
|
||||
},
|
||||
{
|
||||
"targets": [5],
|
||||
"targets": [6],
|
||||
"data":"full_title",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
|
@ -145,7 +156,7 @@ history_table_options = {
|
|||
"width": "35%"
|
||||
},
|
||||
{
|
||||
"targets": [6],
|
||||
"targets": [7],
|
||||
"data":"started",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData === null) {
|
||||
|
@ -159,7 +170,7 @@ history_table_options = {
|
|||
"className": "no-wrap hidden-sm hidden-xs"
|
||||
},
|
||||
{
|
||||
"targets": [7],
|
||||
"targets": [8],
|
||||
"data":"paused_counter",
|
||||
"render": function (data, type, full) {
|
||||
if (data !== null) {
|
||||
|
@ -173,7 +184,7 @@ history_table_options = {
|
|||
"className": "no-wrap hidden-md hidden-sm hidden-xs"
|
||||
},
|
||||
{
|
||||
"targets": [8],
|
||||
"targets": [9],
|
||||
"data":"stopped",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData === null) {
|
||||
|
@ -187,7 +198,7 @@ history_table_options = {
|
|||
"className": "no-wrap hidden-sm hidden-xs"
|
||||
},
|
||||
{
|
||||
"targets": [9],
|
||||
"targets": [10],
|
||||
"data":"duration",
|
||||
"render": function (data, type, full) {
|
||||
if (data !== null) {
|
||||
|
@ -201,15 +212,15 @@ history_table_options = {
|
|||
"className": "no-wrap hidden-xs"
|
||||
},
|
||||
{
|
||||
"targets": [10],
|
||||
"targets": [11],
|
||||
"data": "watched_status",
|
||||
"render": function (data, type, full) {
|
||||
if (data == 1) {
|
||||
return '<span class="watched-tooltip" data-toggle="tooltip" title="Watched"><i class="fa fa-lg fa-circle"></i></span>'
|
||||
} else if (data == 0.5) {
|
||||
return '<span class="watched-tooltip" data-toggle="tooltip" title="Partial"><i class="fa fa-lg fa-adjust fa-rotate-180"></i></span>'
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData == 1) {
|
||||
$(td).html('<span class="watched-tooltip" data-toggle="tooltip" title="' + rowData['percent_complete'] + '%"><i class="fa fa-lg fa-circle"></i></span>');
|
||||
} else if (cellData == 0.5) {
|
||||
$(td).html('<span class="watched-tooltip" data-toggle="tooltip" title="' + rowData['percent_complete'] + '%"><i class="fa fa-lg fa-adjust fa-rotate-180"></i></span>');
|
||||
} else {
|
||||
return '<span class="watched-tooltip" data-toggle="tooltip" title="Unwatched"><i class="fa fa-lg fa-circle-o"></i></span>'
|
||||
$(td).html('<span class="watched-tooltip" data-toggle="tooltip" title="' + rowData['percent_complete'] + '%"><i class="fa fa-lg fa-circle-o"></i></span>');
|
||||
}
|
||||
},
|
||||
"searchable": false,
|
||||
|
@ -225,12 +236,13 @@ history_table_options = {
|
|||
|
||||
// Create the tooltips.
|
||||
$('.expand-history-tooltip').tooltip({ container: 'body' });
|
||||
$('.external-ip-tooltip').tooltip();
|
||||
$('.transcode-tooltip').tooltip();
|
||||
$('.media-type-tooltip').tooltip();
|
||||
$('.watched-tooltip').tooltip();
|
||||
$('.external-ip-tooltip').tooltip({ container: 'body' });
|
||||
$('.transcode-tooltip').tooltip({ container: 'body' });
|
||||
$('.media-type-tooltip').tooltip({ container: 'body' });
|
||||
$('.watched-tooltip').tooltip({ container: 'body' });
|
||||
$('.thumb-tooltip').popover({
|
||||
html: true,
|
||||
container: 'body',
|
||||
trigger: 'hover',
|
||||
placement: 'right',
|
||||
content: function () {
|
||||
|
@ -462,6 +474,7 @@ function childTableFormat(rowData) {
|
|||
'<th align="left" id="friendly_name">User</th>' +
|
||||
'<th align="left" id="ip_address">IP Address</th>' +
|
||||
'<th align="left" id="platform">Platform</th>' +
|
||||
'<th align="left" id="platform">Player</th>' +
|
||||
'<th align="left" id="title">Title</th>' +
|
||||
'<th align="left" id="started">Started</th>' +
|
||||
'<th align="left" id="paused_counter">Paused</th>' +
|
||||
|
|
|
@ -2,7 +2,7 @@ sync_table_options = {
|
|||
"processing": false,
|
||||
"serverSide": false,
|
||||
"pagingType": "bootstrap",
|
||||
"order": [ 0, 'desc'],
|
||||
"order": [ [ 0, 'desc'], [ 1, 'asc'], [2, 'asc'] ],
|
||||
"pageLength": 25,
|
||||
"stateSave": true,
|
||||
"language": {
|
||||
|
@ -67,13 +67,13 @@ sync_table_options = {
|
|||
},
|
||||
{
|
||||
"targets": [4],
|
||||
"data": "device_name",
|
||||
"className": "no-wrap hidden-xs"
|
||||
"data": "platform",
|
||||
"className": "no-wrap hidden-sm hidden-xs"
|
||||
},
|
||||
{
|
||||
"targets": [5],
|
||||
"data": "platform",
|
||||
"className": "no-wrap hidden-sm hidden-xs"
|
||||
"data": "device_name",
|
||||
"className": "no-wrap hidden-xs"
|
||||
},
|
||||
{
|
||||
"targets": [6],
|
||||
|
|
|
@ -49,7 +49,18 @@ user_ip_table_options = {
|
|||
},
|
||||
{
|
||||
"targets": [2],
|
||||
"data":"platform",
|
||||
"data": "platform",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
$(td).html(cellData);
|
||||
}
|
||||
},
|
||||
"width": "15%",
|
||||
"className": "no-wrap hidden-md hidden-sm hidden-xs modal-control"
|
||||
},
|
||||
{
|
||||
"targets": [3],
|
||||
"data":"player",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData) {
|
||||
var transcode_dec = '';
|
||||
|
@ -69,7 +80,7 @@ user_ip_table_options = {
|
|||
"className": "no-wrap hidden-md hidden-sm hidden-xs modal-control"
|
||||
},
|
||||
{
|
||||
"targets": [3],
|
||||
"targets": [4],
|
||||
"data":"last_watched",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
|
@ -94,10 +105,11 @@ user_ip_table_options = {
|
|||
}
|
||||
}
|
||||
},
|
||||
"width": "30%",
|
||||
"className": "hidden-sm hidden-xs"
|
||||
},
|
||||
{
|
||||
"targets": [4],
|
||||
"targets": [5],
|
||||
"data":"play_count",
|
||||
"searchable": false,
|
||||
"width": "10%"
|
||||
|
|
|
@ -64,7 +64,7 @@ users_list_table_options = {
|
|||
$(td).html(cellData);
|
||||
}
|
||||
},
|
||||
"width": "12%",
|
||||
"width": "10%",
|
||||
"className": "edit-user-control no-wrap"
|
||||
},
|
||||
{
|
||||
|
@ -78,7 +78,7 @@ users_list_table_options = {
|
|||
}
|
||||
},
|
||||
"searchable": false,
|
||||
"width": "12%",
|
||||
"width": "10%",
|
||||
"className": "no-wrap hidden-xs"
|
||||
},
|
||||
{
|
||||
|
@ -99,12 +99,25 @@ users_list_table_options = {
|
|||
$(td).html('n/a');
|
||||
}
|
||||
},
|
||||
"width": "12%",
|
||||
"width": "10%",
|
||||
"className": "no-wrap hidden-md hidden-sm hidden-xs modal-control-ip"
|
||||
},
|
||||
{
|
||||
"targets": [5],
|
||||
"data":"platform",
|
||||
"data": "platform",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
$(td).html(cellData);
|
||||
} else {
|
||||
$(td).html('n/a');
|
||||
}
|
||||
},
|
||||
"width": "10%",
|
||||
"className": "no-wrap hidden-md hidden-sm hidden-xs modal-control"
|
||||
},
|
||||
{
|
||||
"targets": [6],
|
||||
"data":"player",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData) {
|
||||
var transcode_dec = '';
|
||||
|
@ -120,11 +133,11 @@ users_list_table_options = {
|
|||
$(td).html('n/a');
|
||||
}
|
||||
},
|
||||
"width": "12%",
|
||||
"width": "15%",
|
||||
"className": "no-wrap hidden-md hidden-sm hidden-xs modal-control"
|
||||
},
|
||||
{
|
||||
"targets": [6],
|
||||
"targets": [7],
|
||||
"data":"last_watched",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
|
@ -153,7 +166,7 @@ users_list_table_options = {
|
|||
"className": "hidden-sm hidden-xs"
|
||||
},
|
||||
{
|
||||
"targets": [7],
|
||||
"targets": [8],
|
||||
"data": "plays",
|
||||
"searchable": false,
|
||||
"width": "10%"
|
||||
|
|
|
@ -37,7 +37,13 @@ DOCUMENTATION :: END
|
|||
<li>
|
||||
<div class="home-platforms-instance-info">
|
||||
<div class="home-platforms-instance-name">
|
||||
% if library['type'] != 'photo':
|
||||
<h4>
|
||||
<a href="info?item_id=${library['type']}" title="${library['rows']['title']}">${library['rows']['title']}</a>
|
||||
</h4>
|
||||
% else:
|
||||
<h4>${library['rows']['title']}</h4>
|
||||
% endif
|
||||
</div>
|
||||
<div class="home-platforms-instance-playcount">
|
||||
<h5>${library['rows']['count_type']}</h5>
|
||||
|
|
|
@ -44,6 +44,24 @@ from plexpy import helpers
|
|||
<p class="help-block">${item['description']}</p>
|
||||
<input type="hidden" id="${item['name']}" name="${item['name']}" value="${item['value']}">
|
||||
</div>
|
||||
% elif item['input_type'] == 'select':
|
||||
<div class="form-group">
|
||||
<label for="${item['name']}">${item['label']}</label>
|
||||
<div class="row">
|
||||
<div class="col-md-5">
|
||||
<select class="form-control" id="${item['name']}" name="${item['name']}" >
|
||||
% for key, value in sorted(item['select_options'].iteritems()):
|
||||
% if key == item['value']:
|
||||
<option value="${key}" selected>${value}</option>
|
||||
% else:
|
||||
<option value="${key}">${value}</option>
|
||||
% endif
|
||||
% endfor
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">${item['description']}</p>
|
||||
</div>
|
||||
% endif
|
||||
% endfor
|
||||
</div>
|
||||
|
@ -68,29 +86,38 @@ from plexpy import helpers
|
|||
<script>
|
||||
$('#osxnotifyregister').click(function () {
|
||||
var osx_notify_app = $("#osx_notify_app").val();
|
||||
$.get("/osxnotifyregister", {'app': osx_notify_app}, function (data) { $('#ajaxMsg').html("<div class='msg'><span class='ui-icon ui-icon-check'></span>"+data+"</div>"); });
|
||||
$('#ajaxMsg').addClass('success').fadeIn().delay(3000).fadeOut()
|
||||
$.get("/osxnotifyregister", { 'app': osx_notify_app }, function (data) { $('#ajaxMsg').html("<i class='fa fa-check'></i> " + data); });
|
||||
$('#ajaxMsg').addClass('success').fadeIn().delay(3000).fadeOut();
|
||||
})
|
||||
|
||||
var notificationConfig = $("#set_notification_config");
|
||||
$('#save-notification-item').click(function() {
|
||||
doAjaxCall('set_notification_config',$(this),'tabs',true);
|
||||
doAjaxCall('set_notification_config', $(this), 'tabs', true);
|
||||
// Reload modal to update certain fields
|
||||
$.ajax({
|
||||
url: 'get_notification_agent_config',
|
||||
data: { config_id: '${config_id}' },
|
||||
cache: false,
|
||||
async: true,
|
||||
complete: function (xhr, status) {
|
||||
$("#notification-config-modal").html(xhr.responseText);
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#twitterStep1').click(function () {
|
||||
$.get("/twitterStep1", function (data) {window.open(data); })
|
||||
.done(function () { $('#ajaxMsg').html("<div class='msg'><span class='ui-icon ui-icon-check'></span>Confirm Authorization. Check pop-up blocker if no response.</div>"); });
|
||||
.done(function () { $('#ajaxMsg').html("<i class='fa fa-check'></i> Confirm Authorization. Check pop-up blocker if no response."); });
|
||||
$('#ajaxMsg').addClass('success').fadeIn().delay(3000).fadeOut();
|
||||
});
|
||||
$('#twitterStep2').click(function () {
|
||||
var twitter_key = $("#twitter_key").val();
|
||||
$.get("/twitterStep2", {'key': twitter_key}, function (data) { $('#ajaxMsg').html("<div class='msg'><span class='ui-icon ui-icon-check'></span>"+data+"</div>"); });
|
||||
$.get("/twitterStep2", { 'key': twitter_key }, function (data) { $('#ajaxMsg').html("<i class='fa fa-check'></i> " + data); });
|
||||
$('#ajaxMsg').addClass('success').fadeIn().delay(3000).fadeOut();
|
||||
});
|
||||
$('#testTwitter').click(function () {
|
||||
$.get("/testTwitter",
|
||||
function (data) { $('#ajaxMsg').html("<div class='msg'><span class='ui-icon ui-icon-check'></span>"+data+"</div>"); });
|
||||
function (data) { $('#ajaxMsg').html("<i class='fa fa-check'></i> " + data); });
|
||||
$('#ajaxMsg').addClass('success').fadeIn().delay(3000).fadeOut();
|
||||
});
|
||||
|
||||
|
|
|
@ -26,11 +26,11 @@
|
|||
<thead>
|
||||
<tr>
|
||||
<th align='left' id="state">State</th>
|
||||
<th align='left' id="username">Username</th>
|
||||
<th align='left' id="user">User</th>
|
||||
<th align='left' id="title">Title</th>
|
||||
<th align='left' id="type">Type</th>
|
||||
<th align='left' id="device">Device</th>
|
||||
<th align='left' id="platform">Platform</th>
|
||||
<th align='left' id="device">Device</th>
|
||||
<th align='left' id="size">Total Size</th>
|
||||
<th align='left' id="items">Total Items</th>
|
||||
<th align='left' id="converted">Converted</th>
|
||||
|
|
|
@ -85,11 +85,11 @@ from plexpy import helpers
|
|||
<div class="col-md-12">
|
||||
<div class="table-card-header">
|
||||
<div class="header-bar">
|
||||
<span><i class="fa fa-television"></i> Platform Stats</span>
|
||||
<span><i class="fa fa-television"></i> Player Stats</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-card-back">
|
||||
<div id="user-platform-stats" class="user-platforms">
|
||||
<div id="user-player-stats" class="user-player">
|
||||
<div class='muted'><i class="fa fa-refresh fa-spin"></i> Loading data...</div>
|
||||
<br>
|
||||
</div>
|
||||
|
@ -133,6 +133,7 @@ from plexpy import helpers
|
|||
<th align="left">Last Seen</th>
|
||||
<th align="left">IP Address</th>
|
||||
<th align="left">Last Platform</th>
|
||||
<th align="left">Last Player</th>
|
||||
<th align="left">Last Watched</th>
|
||||
<th align="left">Play Count</th>
|
||||
</tr>
|
||||
|
@ -170,6 +171,7 @@ from plexpy import helpers
|
|||
<th align='left' id="friendly_name">User</th>
|
||||
<th align='left' id="ip_address">IP Address</th>
|
||||
<th align='left' id="platform">Platform</th>
|
||||
<th align='left' id="player">Player</th>
|
||||
<th align='left' id="title">Title</th>
|
||||
<th align='left' id="started">Started</th>
|
||||
<th align='left' id="paused_counter">Paused</th>
|
||||
|
@ -207,8 +209,8 @@ from plexpy import helpers
|
|||
<th align='left' id="username">Username</th>
|
||||
<th align='left' id="sync_title">Title</th>
|
||||
<th align='left' id="type">Type</th>
|
||||
<th align='left' id="device">Device</th>
|
||||
<th align='left' id="sync_platform">Platform</th>
|
||||
<th align='left' id="device">Device</th>
|
||||
<th align='left' id="size">Total Size</th>
|
||||
<th align='left' id="items">Total Items</th>
|
||||
<th align='left' id="converted">Converted</th>
|
||||
|
@ -309,11 +311,11 @@ from plexpy import helpers
|
|||
|
||||
// Populate platform stats
|
||||
$.ajax({
|
||||
url: 'get_user_platform_stats',
|
||||
url: 'get_user_player_stats',
|
||||
async: true,
|
||||
data: { user_id: user_id, user: '${data['username']}' },
|
||||
complete: function(xhr, status) {
|
||||
$("#user-platform-stats").html(xhr.responseText);
|
||||
$("#user-player-stats").html(xhr.responseText);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -332,7 +334,7 @@ from plexpy import helpers
|
|||
history_table = $('#history_table').DataTable(history_table_options);
|
||||
history_table.column(2).visible(false);
|
||||
|
||||
var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 10] });
|
||||
var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 11] });
|
||||
$(colvis.button()).appendTo('#button-bar-history');
|
||||
|
||||
clearSearchButton('history_table', history_table);
|
||||
|
|
|
@ -11,8 +11,9 @@ data[array_index] :: Usable parameters
|
|||
|
||||
== Global keys ==
|
||||
result_id Returns a unique identifier for the result.
|
||||
platform_name Returns the name of the platform.
|
||||
total_plays Returns the play count for the platform.
|
||||
player_name Returns the name of the player.
|
||||
platform_type Returns the name of the platform
|
||||
total_plays Returns the play count for the player.
|
||||
|
||||
DOCUMENTATION :: END
|
||||
</%doc>
|
||||
|
@ -20,13 +21,13 @@ DOCUMENTATION :: END
|
|||
% if data != None:
|
||||
% for a in data:
|
||||
<ul class="list-unstyled">
|
||||
<div class="user-platforms-instance">
|
||||
<div class="user-player-instance">
|
||||
<li>
|
||||
<span id="user-platform-image-${a['result_id']}"></span>
|
||||
<div class="user-platforms-instance-name">
|
||||
${a['platform_name']}
|
||||
<span id="user-player-image-${a['result_id']}"></span>
|
||||
<div class="user-player-instance-name">
|
||||
${a['player_name']}
|
||||
</div>
|
||||
<div class="user-platforms-instance-playcount">
|
||||
<div class="user-player-instance-playcount">
|
||||
<h3>${a['total_plays']}</h3>
|
||||
<p> plays</p>
|
||||
</div>
|
||||
|
@ -34,7 +35,7 @@ DOCUMENTATION :: END
|
|||
</div>
|
||||
</ul>
|
||||
<script>
|
||||
$("#user-platform-image-${a['result_id']}").html("<div class='user-platforms-instance-box' style='background-image: url(" + getPlatformImagePath('${a['platform_type']}') + ");'>");
|
||||
$("#user-player-image-${a['result_id']}").html("<div class='user-player-instance-box' style='background-image: url(" + getPlatformImagePath('${a['platform_type']}') + ");'>");
|
||||
</script>
|
||||
% endfor
|
||||
% else:
|
|
@ -29,6 +29,7 @@
|
|||
<th align="left" id="last_seen">Last Seen</th>
|
||||
<th align="left" id="last_known_ip">Last Known IP</th>
|
||||
<th align="left" id="last_platform">Last Platform</th>
|
||||
<th align="left" id="last_player">Last Player</th>
|
||||
<th align="left" id="last_watched">Last Watched</th>
|
||||
<th align="left" id="total_plays">Total Plays</th>
|
||||
</tr>
|
||||
|
|
|
@ -15,6 +15,7 @@ from plexpy import common
|
|||
<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/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="interfaces/default/css/font-awesome.min.css" rel="stylesheet">
|
||||
<link rel="icon" type="image/x-icon" href="interfaces/default/images/favicon.ico"/>
|
||||
|
@ -40,45 +41,6 @@ from plexpy import common
|
|||
</div>
|
||||
</div>
|
||||
<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>
|
||||
<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">
|
||||
|
@ -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">
|
||||
<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 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" name="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">
|
||||
<h3>Monitoring</h3>
|
||||
<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/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/bootstrap-wizard.min.js"></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").val(authToken);
|
||||
authenticated = true;
|
||||
getServerOptions(authToken)
|
||||
}
|
||||
});
|
||||
} 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>
|
||||
|
||||
</body>
|
||||
|
|
554
plexpy/api.py
554
plexpy/api.py
|
@ -1,3 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# This file is part of PlexPy.
|
||||
#
|
||||
# PlexPy is free software: you can redistribute it and/or modify
|
||||
|
@ -13,86 +16,143 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with PlexPy. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from plexpy import db, cache, versioncheck, logger, helpers
|
||||
|
||||
from plexpy import versioncheck, logger, plextv, pmsconnect, datafactory, graphs, users
|
||||
import os
|
||||
import plexpy
|
||||
import json
|
||||
from xml.dom import minidom
|
||||
import traceback
|
||||
import cherrypy
|
||||
import re
|
||||
import hashlib
|
||||
import random
|
||||
import xmltodict
|
||||
|
||||
cmd_list = ['getHistory', 'getLogs', 'getVersion', 'checkGithub', 'shutdown', 'restart', 'update']
|
||||
cmd_list = ['getLogs', 'getVersion', 'checkGithub', 'shutdown',
|
||||
'getSettings', 'restart', 'update', 'getApikey', 'getHistory',
|
||||
'getMetadata', 'getUserips', 'getPlayby', 'getSync']
|
||||
|
||||
|
||||
class Api(object):
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, out='json'):
|
||||
|
||||
self.apikey = None
|
||||
self.authenticated = False
|
||||
self.cmd = None
|
||||
self.id = None
|
||||
|
||||
self.kwargs = None
|
||||
|
||||
# For the responses
|
||||
self.data = None
|
||||
|
||||
self.msg = None
|
||||
self.result_type = 'error'
|
||||
# Possible general params
|
||||
self.callback = None
|
||||
self.out_type = out
|
||||
self.debug = None
|
||||
|
||||
def checkParams(self, *args, **kwargs):
|
||||
|
||||
if not plexpy.CONFIG.API_ENABLED:
|
||||
self.data = 'API not enabled'
|
||||
return
|
||||
if not plexpy.CONFIG.API_KEY:
|
||||
self.data = 'API key not generated'
|
||||
return
|
||||
if len(plexpy.CONFIG.API_KEY) != 32:
|
||||
self.data = 'API key not generated correctly'
|
||||
return
|
||||
self.msg = 'API not enabled'
|
||||
elif not plexpy.CONFIG.API_KEY:
|
||||
self.msg = 'API key not generated'
|
||||
elif len(plexpy.CONFIG.API_KEY) != 32:
|
||||
self.msg = 'API key not generated correctly'
|
||||
elif 'apikey' not in kwargs:
|
||||
self.msg = 'Parameter apikey is required'
|
||||
elif kwargs.get('apikey', '') != plexpy.CONFIG.API_KEY:
|
||||
self.msg = 'Invalid apikey'
|
||||
elif 'cmd' not in kwargs:
|
||||
self.msg = 'Parameter %s required. possible commands are: %s' % ', '.join(cmd_list)
|
||||
elif 'cmd' in kwargs and kwargs.get('cmd') not in cmd_list:
|
||||
self.msg = 'Unknown command, %s possible commands are: %s' % (kwargs.get('cmd', ''), ', '.join(cmd_list))
|
||||
|
||||
if 'apikey' not in kwargs:
|
||||
self.data = 'Missing api key'
|
||||
return
|
||||
# Set default values or remove them from kwargs
|
||||
|
||||
if kwargs['apikey'] != plexpy.CONFIG.API_KEY:
|
||||
self.data = 'Incorrect API key'
|
||||
return
|
||||
else:
|
||||
self.apikey = kwargs.pop('apikey')
|
||||
self.callback = kwargs.pop('callback', None)
|
||||
self.apikey = kwargs.pop('apikey', None)
|
||||
self.cmd = kwargs.pop('cmd', None)
|
||||
self.debug = kwargs.pop('debug', False)
|
||||
# Allow override for the api.
|
||||
self.out_type = kwargs.pop('out_type', 'json')
|
||||
|
||||
if 'cmd' not in kwargs:
|
||||
self.data = 'Missing parameter: cmd'
|
||||
return
|
||||
|
||||
if kwargs['cmd'] not in cmd_list:
|
||||
self.data = 'Unknown command: %s' % kwargs['cmd']
|
||||
return
|
||||
else:
|
||||
self.cmd = kwargs.pop('cmd')
|
||||
if self.apikey == plexpy.CONFIG.API_KEY and plexpy.CONFIG.API_ENABLED and self.cmd in cmd_list:
|
||||
self.authenticated = True
|
||||
self.msg = None
|
||||
elif self.cmd == 'getApikey' and plexpy.CONFIG.API_ENABLED:
|
||||
self.authenticated = True
|
||||
# Remove the old error msg
|
||||
self.msg = None
|
||||
|
||||
self.kwargs = kwargs
|
||||
self.data = 'OK'
|
||||
|
||||
def _responds(self, result_type='success', data=None, msg=''):
|
||||
|
||||
if data is None:
|
||||
data = {}
|
||||
return {"response": {"result": result_type, "message": msg, "data": data}}
|
||||
|
||||
def _out_as(self, out):
|
||||
|
||||
if self.out_type == 'json':
|
||||
cherrypy.response.headers['Content-Type'] = 'application/json;charset=UTF-8'
|
||||
try:
|
||||
out = json.dumps(out, indent=4, sort_keys=True)
|
||||
if self.callback is not None:
|
||||
cherrypy.response.headers['Content-Type'] = 'application/javascript'
|
||||
# wrap with JSONP call if requested
|
||||
out = self.callback + '(' + out + ');'
|
||||
# if we fail to generate the output fake an error
|
||||
except Exception as e:
|
||||
logger.info(u"API :: " + traceback.format_exc())
|
||||
out['message'] = traceback.format_exc()
|
||||
out['result'] = 'error'
|
||||
if self.out_type == 'xml':
|
||||
cherrypy.response.headers['Content-Type'] = 'application/xml'
|
||||
try:
|
||||
out = xmltodict.unparse(out, pretty=True)
|
||||
except ValueError as e:
|
||||
logger.error('Failed to parse xml result')
|
||||
try:
|
||||
out['message'] = e
|
||||
out['result'] = 'error'
|
||||
out = xmltodict.unparse(out, pretty=True)
|
||||
|
||||
except Exception as e:
|
||||
logger.error('Failed to parse xml result error message')
|
||||
out = '''<?xml version="1.0" encoding="utf-8"?>
|
||||
<response>
|
||||
<message>%s</message>
|
||||
<data></data>
|
||||
<result>error</result>
|
||||
</response>
|
||||
''' % e
|
||||
|
||||
return out
|
||||
|
||||
def fetchData(self):
|
||||
|
||||
if self.data == 'OK':
|
||||
logger.info('Recieved API command: %s', self.cmd)
|
||||
methodToCall = getattr(self, "_" + self.cmd)
|
||||
methodToCall(**self.kwargs)
|
||||
if 'callback' not in self.kwargs:
|
||||
if isinstance(self.data, basestring):
|
||||
return self.data
|
||||
else:
|
||||
return json.dumps(self.data)
|
||||
logger.info('Recieved API command: %s' % self.cmd)
|
||||
if self.cmd and self.authenticated:
|
||||
methodtocall = getattr(self, "_" + self.cmd)
|
||||
# Let the traceback hit cherrypy so we can
|
||||
# see the traceback there
|
||||
if self.debug:
|
||||
methodtocall(**self.kwargs)
|
||||
else:
|
||||
self.callback = self.kwargs['callback']
|
||||
self.data = json.dumps(self.data)
|
||||
self.data = self.callback + '(' + self.data + ');'
|
||||
return self.data
|
||||
else:
|
||||
return self.data
|
||||
try:
|
||||
methodtocall(**self.kwargs)
|
||||
except Exception as e:
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
# Im just lazy, fix me plx
|
||||
if self.data or isinstance(self.data, (dict, list)):
|
||||
if len(self.data):
|
||||
self.result_type = 'success'
|
||||
|
||||
return self._out_as(self._responds(result_type=self.result_type, msg=self.msg, data=self.data))
|
||||
|
||||
def _dic_from_query(self, query):
|
||||
|
||||
myDB = db.DBConnection()
|
||||
myDB = database.DBConnection()
|
||||
rows = myDB.select(query)
|
||||
|
||||
rows_as_dic = []
|
||||
|
@ -103,104 +163,115 @@ class Api(object):
|
|||
|
||||
return rows_as_dic
|
||||
|
||||
def _getHistory(self, iDisplayStart=0, iDisplayLength=100, sSearch="", iSortCol_0='0', sSortDir_0='asc', **kwargs):
|
||||
iDisplayStart = int(iDisplayStart)
|
||||
iDisplayLength = int(iDisplayLength)
|
||||
filtered = []
|
||||
totalcount = 0
|
||||
myDB = db.DBConnection()
|
||||
db_table = db.DBConnection().get_history_table_name()
|
||||
def _getApikey(self, username='', password=''):
|
||||
""" Returns api key, requires username and password is active """
|
||||
|
||||
sortcolumn = 'time'
|
||||
sortbyhavepercent = False
|
||||
if iSortCol_0 == '1':
|
||||
sortcolumn = 'user'
|
||||
if iSortCol_0 == '2':
|
||||
sortcolumn = 'platform'
|
||||
elif iSortCol_0 == '3':
|
||||
sortcolumn = 'ip_address'
|
||||
elif iSortCol_0 == '4':
|
||||
sortcolumn = 'title'
|
||||
elif iSortCol_0 == '5':
|
||||
sortcolumn = 'time'
|
||||
elif iSortCol_0 == '6':
|
||||
sortcolumn = 'paused_counter'
|
||||
elif iSortCol_0 == '7':
|
||||
sortcolumn = 'stopped'
|
||||
elif iSortCol_0 == '8':
|
||||
sortbyhavepercent = True
|
||||
|
||||
if sSearch == "":
|
||||
query = 'SELECT * from %s order by %s COLLATE NOCASE %s' % (db_table, sortcolumn, sSortDir_0)
|
||||
filtered = myDB.select(query)
|
||||
totalcount = len(filtered)
|
||||
else:
|
||||
query = 'SELECT * from ' + db_table + ' WHERE user LIKE "%' + sSearch + \
|
||||
'%" OR title LIKE "%' + sSearch + '%"' + 'ORDER BY %s COLLATE NOCASE %s' % (sortcolumn, sSortDir_0)
|
||||
filtered = myDB.select(query)
|
||||
totalcount = myDB.select('SELECT COUNT(*) from processed')[0][0]
|
||||
|
||||
history = filtered[iDisplayStart:(iDisplayStart + iDisplayLength)]
|
||||
rows = []
|
||||
for item in history:
|
||||
row = {"date": item['time'],
|
||||
"user": item["user"],
|
||||
"platform": item["platform"],
|
||||
"ip_address": item["ip_address"],
|
||||
"title": item["title"],
|
||||
"started": item["time"],
|
||||
"paused": item["paused_counter"],
|
||||
"stopped": item["stopped"],
|
||||
"duration": "",
|
||||
"percent_complete": 0,
|
||||
}
|
||||
|
||||
if item['paused_counter'] > 0:
|
||||
row['paused'] = item['paused_counter']
|
||||
apikey = hashlib.sha224(str(random.getrandbits(256))).hexdigest()[0:32]
|
||||
if plexpy.CONFIG.HTTP_USERNAME and plexpy.CONFIG.HTTP_PASSWORD:
|
||||
if username == plexpy.HTTP_USERNAME and password == plexpy.CONFIG.HTTP_PASSWORD:
|
||||
if plexpy.CONFIG.API_KEY:
|
||||
self.data = plexpy.CONFIG.API_KEY
|
||||
else:
|
||||
self.data = apikey
|
||||
plexpy.CONFIG.API_KEY = apikey
|
||||
plexpy.CONFIG.write()
|
||||
else:
|
||||
row['paused'] = 0
|
||||
self.msg = 'Authentication is enabled, please add the correct username and password to the parameters'
|
||||
else:
|
||||
if plexpy.CONFIG.API_KEY:
|
||||
self.data = plexpy.CONFIG.API_KEY
|
||||
else:
|
||||
# Make a apikey if the doesn't exist
|
||||
self.data = apikey
|
||||
plexpy.CONFIG.API_KEY = apikey
|
||||
plexpy.CONFIG.write()
|
||||
|
||||
if item['time']:
|
||||
if item['stopped'] > 0:
|
||||
stopped = item['stopped']
|
||||
else:
|
||||
stopped = 0
|
||||
if item['paused_counter'] > 0:
|
||||
paused_counter = item['paused_counter']
|
||||
else:
|
||||
paused_counter = 0
|
||||
return self.data
|
||||
|
||||
row['duration'] = stopped - item['time'] + paused_counter
|
||||
def _getLogs(self, sort='', search='', order='desc', regex='', **kwargs):
|
||||
"""
|
||||
Returns the log
|
||||
|
||||
Returns [{"response":
|
||||
{"msg": "Hey",
|
||||
"result": "success"},
|
||||
"data": [{"time": "29-sept.2015",
|
||||
"thread: "MainThread",
|
||||
"msg: "Called x from y",
|
||||
"loglevel": "DEBUG"
|
||||
}
|
||||
]
|
||||
|
||||
}
|
||||
]
|
||||
"""
|
||||
logfile = os.path.join(plexpy.CONFIG.LOG_DIR, 'plexpy.log')
|
||||
templog = []
|
||||
start = int(kwargs.get('start', 0))
|
||||
end = int(kwargs.get('end', 0))
|
||||
|
||||
if regex:
|
||||
logger.debug('Filtering log using regex %s' % regex)
|
||||
reg = re.compile('u' + regex, flags=re.I)
|
||||
|
||||
for line in open(logfile, 'r').readlines():
|
||||
temp_loglevel_and_time = None
|
||||
|
||||
try:
|
||||
xml_parse = minidom.parseString(helpers.latinToAscii(item['xml']))
|
||||
except IOError, e:
|
||||
logger.warn("Error parsing XML in PlexWatch db: %s" % e)
|
||||
temp_loglevel_and_time = line.split('- ')
|
||||
loglvl = temp_loglevel_and_time[1].split(' :')[0].strip()
|
||||
tl_tread = line.split(' :: ')
|
||||
if loglvl is None:
|
||||
msg = line.replace('\n', '')
|
||||
else:
|
||||
msg = line.split(' : ')[1].replace('\n', '')
|
||||
thread = tl_tread[1].split(' : ')[0]
|
||||
except IndexError:
|
||||
# We assume this is a traceback
|
||||
tl = (len(templog) - 1)
|
||||
templog[tl]['msg'] += line.replace('\n', '')
|
||||
continue
|
||||
|
||||
xml_head = xml_parse.getElementsByTagName('opt')
|
||||
if not xml_head:
|
||||
logger.warn("Error parsing XML in PlexWatch db: %s" % e)
|
||||
if len(line) > 1 and temp_loglevel_and_time is not None and loglvl in line:
|
||||
|
||||
for s in xml_head:
|
||||
if s.getAttribute('duration') and s.getAttribute('viewOffset'):
|
||||
view_offset = helpers.cast_to_float(s.getAttribute('viewOffset'))
|
||||
duration = helpers.cast_to_float(s.getAttribute('duration'))
|
||||
if duration > 0:
|
||||
row['percent_complete'] = (view_offset / duration)*100
|
||||
else:
|
||||
row['percent_complete'] = 0
|
||||
|
||||
rows.append(row)
|
||||
|
||||
dict = {'iTotalDisplayRecords': len(filtered),
|
||||
'iTotalRecords': totalcount,
|
||||
'aaData': rows,
|
||||
d = {
|
||||
'time': temp_loglevel_and_time[0],
|
||||
'loglevel': loglvl,
|
||||
'msg': msg.replace('\n', ''),
|
||||
'thread': thread
|
||||
}
|
||||
self.data = json.dumps(dict)
|
||||
#cherrypy.response.headers['Content-type'] = 'application/json'
|
||||
templog.append(d)
|
||||
|
||||
def _getLogs(self, **kwargs):
|
||||
pass
|
||||
if end > 0:
|
||||
logger.debug('Slicing the log from %s to %s' % (start, end))
|
||||
templog = templog[start:end]
|
||||
|
||||
if sort:
|
||||
logger.debug('Sorting log based on %s' % sort)
|
||||
templog = sorted(templog, key=lambda k: k[sort])
|
||||
|
||||
if search:
|
||||
logger.debug('Searching log values for %s' % search)
|
||||
tt = [d for d in templog for k, v in d.items() if search.lower() in v.lower()]
|
||||
|
||||
if len(tt):
|
||||
templog = tt
|
||||
|
||||
if regex:
|
||||
tt = []
|
||||
for l in templog:
|
||||
stringdict = ' '.join('{}{}'.format(k, v) for k, v in l.items())
|
||||
if reg.search(stringdict):
|
||||
tt.append(l)
|
||||
|
||||
if len(tt):
|
||||
templog = tt
|
||||
|
||||
if order == 'desc':
|
||||
templog = templog[::-1]
|
||||
|
||||
self.data = templog
|
||||
return templog
|
||||
|
||||
def _getVersion(self, **kwargs):
|
||||
self.data = {
|
||||
|
@ -210,6 +281,7 @@ class Api(object):
|
|||
'latest_version': plexpy.LATEST_VERSION,
|
||||
'commits_behind': plexpy.COMMITS_BEHIND,
|
||||
}
|
||||
self.result_type = 'success'
|
||||
|
||||
def _checkGithub(self, **kwargs):
|
||||
versioncheck.checkGithub()
|
||||
|
@ -217,9 +289,211 @@ class Api(object):
|
|||
|
||||
def _shutdown(self, **kwargs):
|
||||
plexpy.SIGNAL = 'shutdown'
|
||||
self.msg = 'Shutting down plexpy'
|
||||
self.result_type = 'success'
|
||||
|
||||
def _restart(self, **kwargs):
|
||||
plexpy.SIGNAL = 'restart'
|
||||
self.msg = 'Restarting plexpy'
|
||||
self.result_type = 'success'
|
||||
|
||||
def _update(self, **kwargs):
|
||||
plexpy.SIGNAL = 'update'
|
||||
self.msg = 'Updating plexpy'
|
||||
self.result_type = 'success'
|
||||
|
||||
def _getHistory(self, user=None, user_id=None, rating_key='', parent_rating_key='', grandparent_rating_key='', start_date='', **kwargs):
|
||||
|
||||
custom_where = []
|
||||
if user_id:
|
||||
custom_where = [['user_id', user_id]]
|
||||
elif user:
|
||||
custom_where = [['user', user]]
|
||||
if 'rating_key' in kwargs:
|
||||
rating_key = kwargs.get('rating_key', "")
|
||||
custom_where = [['rating_key', rating_key]]
|
||||
if 'parent_rating_key' in kwargs:
|
||||
rating_key = kwargs.get('parent_rating_key', "")
|
||||
custom_where = [['parent_rating_key', rating_key]]
|
||||
if 'grandparent_rating_key' in kwargs:
|
||||
rating_key = kwargs.get('grandparent_rating_key', "")
|
||||
custom_where = [['grandparent_rating_key', rating_key]]
|
||||
if 'start_date' in kwargs:
|
||||
start_date = kwargs.get('start_date', "")
|
||||
custom_where = [['strftime("%Y-%m-%d", datetime(date, "unixepoch", "localtime"))', start_date]]
|
||||
|
||||
data_factory = datafactory.DataFactory()
|
||||
history = data_factory.get_history(kwargs=kwargs, custom_where=custom_where)
|
||||
|
||||
self.data = history
|
||||
return self.data
|
||||
|
||||
def _getSync(self, machine_id=None, user_id=None, **kwargs):
|
||||
|
||||
pms_connect = pmsconnect.PmsConnect()
|
||||
server_id = pms_connect.get_server_identity()
|
||||
|
||||
plex_tv = plextv.PlexTV()
|
||||
if not machine_id:
|
||||
result = plex_tv.get_synced_items(machine_id=server_id['machine_identifier'], user_id=user_id)
|
||||
else:
|
||||
result = plex_tv.get_synced_items(machine_id=machine_id, user_id=user_id)
|
||||
|
||||
if result:
|
||||
self.data = result
|
||||
return result
|
||||
else:
|
||||
self.msg = 'Unable to retrieve sync data for user'
|
||||
logger.warn('Unable to retrieve sync data for user.')
|
||||
|
||||
def _getMetadata(self, rating_key='', **kwargs):
|
||||
|
||||
pms_connect = pmsconnect.PmsConnect()
|
||||
result = pms_connect.get_metadata(rating_key, 'dict')
|
||||
|
||||
if result:
|
||||
self.data = result
|
||||
return result
|
||||
else:
|
||||
self.msg = 'Unable to retrive metadata %s' % rating_key
|
||||
logger.warn('Unable to retrieve data.')
|
||||
|
||||
def _getSettings(self):
|
||||
interface_dir = os.path.join(plexpy.PROG_DIR, 'data/interfaces/')
|
||||
interface_list = [name for name in os.listdir(interface_dir) if
|
||||
os.path.isdir(os.path.join(interface_dir, name))]
|
||||
|
||||
config = {
|
||||
"http_host": plexpy.CONFIG.HTTP_HOST,
|
||||
"http_username": plexpy.CONFIG.HTTP_USERNAME,
|
||||
"http_port": plexpy.CONFIG.HTTP_PORT,
|
||||
"http_password": plexpy.CONFIG.HTTP_PASSWORD,
|
||||
"launch_browser": bool(plexpy.CONFIG.LAUNCH_BROWSER),
|
||||
"enable_https": bool(plexpy.CONFIG.ENABLE_HTTPS),
|
||||
"https_cert": plexpy.CONFIG.HTTPS_CERT,
|
||||
"https_key": plexpy.CONFIG.HTTPS_KEY,
|
||||
"api_enabled": plexpy.CONFIG.API_ENABLED,
|
||||
"api_key": plexpy.CONFIG.API_KEY,
|
||||
"update_db_interval": plexpy.CONFIG.UPDATE_DB_INTERVAL,
|
||||
"freeze_db": bool(plexpy.CONFIG.FREEZE_DB),
|
||||
"log_dir": plexpy.CONFIG.LOG_DIR,
|
||||
"cache_dir": plexpy.CONFIG.CACHE_DIR,
|
||||
"check_github": bool(plexpy.CONFIG.CHECK_GITHUB),
|
||||
"interface_list": interface_list,
|
||||
"cache_sizemb": plexpy.CONFIG.CACHE_SIZEMB,
|
||||
"pms_identifier": plexpy.CONFIG.PMS_IDENTIFIER,
|
||||
"pms_ip": plexpy.CONFIG.PMS_IP,
|
||||
"pms_logs_folder": plexpy.CONFIG.PMS_LOGS_FOLDER,
|
||||
"pms_port": plexpy.CONFIG.PMS_PORT,
|
||||
"pms_token": plexpy.CONFIG.PMS_TOKEN,
|
||||
"pms_ssl": bool(plexpy.CONFIG.PMS_SSL),
|
||||
"pms_use_bif": bool(plexpy.CONFIG.PMS_USE_BIF),
|
||||
"pms_uuid": plexpy.CONFIG.PMS_UUID,
|
||||
"date_format": plexpy.CONFIG.DATE_FORMAT,
|
||||
"time_format": plexpy.CONFIG.TIME_FORMAT,
|
||||
"grouping_global_history": bool(plexpy.CONFIG.GROUPING_GLOBAL_HISTORY),
|
||||
"grouping_user_history": bool(plexpy.CONFIG.GROUPING_USER_HISTORY),
|
||||
"grouping_charts": bool(plexpy.CONFIG.GROUPING_CHARTS),
|
||||
"tv_notify_enable": bool(plexpy.CONFIG.TV_NOTIFY_ENABLE),
|
||||
"movie_notify_enable": bool(plexpy.CONFIG.MOVIE_NOTIFY_ENABLE),
|
||||
"music_notify_enable": bool(plexpy.CONFIG.MUSIC_NOTIFY_ENABLE),
|
||||
"tv_notify_on_start": bool(plexpy.CONFIG.TV_NOTIFY_ON_START),
|
||||
"movie_notify_on_start": bool(plexpy.CONFIG.MOVIE_NOTIFY_ON_START),
|
||||
"music_notify_on_start": bool(plexpy.CONFIG.MUSIC_NOTIFY_ON_START),
|
||||
"tv_notify_on_stop": bool(plexpy.CONFIG.TV_NOTIFY_ON_STOP),
|
||||
"movie_notify_on_stop": bool(plexpy.CONFIG.MOVIE_NOTIFY_ON_STOP),
|
||||
"music_notify_on_stop": bool(plexpy.CONFIG.MUSIC_NOTIFY_ON_STOP),
|
||||
"tv_notify_on_pause": bool(plexpy.CONFIG.TV_NOTIFY_ON_PAUSE),
|
||||
"movie_notify_on_pause": bool(plexpy.CONFIG.MOVIE_NOTIFY_ON_PAUSE),
|
||||
"music_notify_on_pause": bool(plexpy.CONFIG.MUSIC_NOTIFY_ON_PAUSE),
|
||||
"monitoring_interval": plexpy.CONFIG.MONITORING_INTERVAL,
|
||||
"refresh_users_interval": plexpy.CONFIG.REFRESH_USERS_INTERVAL,
|
||||
"refresh_users_on_startup": bool(plexpy.CONFIG.REFRESH_USERS_ON_STARTUP),
|
||||
"ip_logging_enable": bool(plexpy.CONFIG.IP_LOGGING_ENABLE),
|
||||
"video_logging_enable": bool(plexpy.CONFIG.VIDEO_LOGGING_ENABLE),
|
||||
"music_logging_enable": bool(plexpy.CONFIG.MUSIC_LOGGING_ENABLE),
|
||||
"logging_ignore_interval": plexpy.CONFIG.LOGGING_IGNORE_INTERVAL,
|
||||
"pms_is_remote": bool(plexpy.CONFIG.PMS_IS_REMOTE),
|
||||
"notify_watched_percent": plexpy.CONFIG.NOTIFY_WATCHED_PERCENT,
|
||||
"notify_on_start_subject_text": plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT,
|
||||
"notify_on_start_body_text": plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT,
|
||||
"notify_on_stop_subject_text": plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT,
|
||||
"notify_on_stop_body_text": plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT,
|
||||
"notify_on_pause_subject_text": plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT,
|
||||
"notify_on_pause_body_text": plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT,
|
||||
"notify_on_resume_subject_text": plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT,
|
||||
"notify_on_resume_body_text": plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT,
|
||||
"notify_on_buffer_subject_text": plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT,
|
||||
"notify_on_buffer_body_text": plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT,
|
||||
"notify_on_watched_subject_text": plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT,
|
||||
"notify_on_watched_body_text": plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT,
|
||||
"home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH,
|
||||
"home_stats_type": bool(plexpy.CONFIG.HOME_STATS_TYPE),
|
||||
"home_stats_count": plexpy.CONFIG.HOME_STATS_COUNT,
|
||||
"home_stats_cards": plexpy.CONFIG.HOME_STATS_CARDS,
|
||||
"home_library_cards": plexpy.CONFIG.HOME_LIBRARY_CARDS,
|
||||
"buffer_threshold": plexpy.CONFIG.BUFFER_THRESHOLD,
|
||||
"buffer_wait": plexpy.CONFIG.BUFFER_WAIT
|
||||
}
|
||||
|
||||
self.data = config
|
||||
return config
|
||||
|
||||
def _getUserips(self, user_id=None, user=None, **kwargs):
|
||||
custom_where = []
|
||||
if user_id:
|
||||
custom_where = [['user_id', user_id]]
|
||||
elif user:
|
||||
custom_where = [['user', user]]
|
||||
|
||||
user_data = users.Users()
|
||||
history = user_data.get_user_unique_ips(kwargs=kwargs,
|
||||
custom_where=custom_where)
|
||||
|
||||
if history:
|
||||
self.data = history
|
||||
return history
|
||||
else:
|
||||
self.msg = 'Failed to find users ips'
|
||||
|
||||
def _getPlayby(self, time_range='30', y_axis='plays', playtype='total_plays_per_month', **kwargs):
|
||||
|
||||
graph = graphs.Graphs()
|
||||
if playtype == 'total_plays_per_month':
|
||||
result = graph.get_total_plays_per_month(y_axis=y_axis)
|
||||
|
||||
elif playtype == 'total_plays_per_day':
|
||||
result = graph.get_total_plays_per_day(time_range=time_range, y_axis=y_axis)
|
||||
|
||||
elif playtype == 'total_plays_per_hourofday':
|
||||
result = graph.get_total_plays_per_hourofday(time_range=time_range, y_axis=y_axis)
|
||||
|
||||
elif playtype == 'total_plays_per_dayofweek':
|
||||
result = graph.get_total_plays_per_dayofweek(time_range=time_range, y_axis=y_axis)
|
||||
|
||||
elif playtype == 'stream_type_by_top_10_users':
|
||||
result = graph.get_stream_type_by_top_10_users(time_range=time_range, y_axis=y_axis)
|
||||
|
||||
elif playtype == 'stream_type_by_top_10_platforms':
|
||||
result = graph.get_stream_type_by_top_10_platforms(time_range=time_range, y_axis=y_axis)
|
||||
|
||||
elif playtype == 'total_plays_by_stream_resolution':
|
||||
result = graph.get_total_plays_by_stream_resolution(time_range=time_range, y_axis=y_axis)
|
||||
|
||||
elif playtype == 'total_plays_by_source_resolution':
|
||||
result = graph.get_total_plays_by_source_resolution(time_range=time_range, y_axis=y_axis)
|
||||
|
||||
elif playtype == 'total_plays_per_stream_type':
|
||||
result = graph.get_total_plays_per_stream_type(time_range=time_range, y_axis=y_axis)
|
||||
|
||||
elif playtype == 'total_plays_by_top_10_users':
|
||||
result = graph.get_total_plays_by_top_10_users(time_range=time_range, y_axis=y_axis)
|
||||
|
||||
elif playtype == 'total_plays_by_top_10_platforms':
|
||||
result = graph.get_total_plays_by_top_10_platforms(time_range=time_range, y_axis=y_axis)
|
||||
|
||||
if result:
|
||||
self.data = result
|
||||
return result
|
||||
else:
|
||||
logger.warn('Unable to retrieve %s from db' % playtype)
|
||||
|
|
|
@ -37,6 +37,7 @@ _CONFIG_DEFINITIONS = {
|
|||
'API_KEY': (str, 'General', ''),
|
||||
'BOXCAR_ENABLED': (int, 'Boxcar', 0),
|
||||
'BOXCAR_TOKEN': (str, 'Boxcar', ''),
|
||||
'BOXCAR_SOUND': (str, 'Boxcar', ''),
|
||||
'BOXCAR_ON_PLAY': (int, 'Boxcar', 0),
|
||||
'BOXCAR_ON_STOP': (int, 'Boxcar', 0),
|
||||
'BOXCAR_ON_PAUSE': (int, 'Boxcar', 0),
|
||||
|
@ -185,6 +186,7 @@ _CONFIG_DEFINITIONS = {
|
|||
'PUSHOVER_ENABLED': (int, 'Pushover', 0),
|
||||
'PUSHOVER_KEYS': (str, 'Pushover', ''),
|
||||
'PUSHOVER_PRIORITY': (int, 'Pushover', 0),
|
||||
'PUSHOVER_SOUND': (str, 'Pushover', ''),
|
||||
'PUSHOVER_ON_PLAY': (int, 'Pushover', 0),
|
||||
'PUSHOVER_ON_STOP': (int, 'Pushover', 0),
|
||||
'PUSHOVER_ON_PAUSE': (int, 'Pushover', 0),
|
||||
|
|
|
@ -42,6 +42,7 @@ class DataFactory(object):
|
|||
'session_history.user_id',
|
||||
'session_history.user',
|
||||
'(CASE WHEN users.friendly_name IS NULL THEN user ELSE users.friendly_name END) as friendly_name',
|
||||
'platform',
|
||||
'player',
|
||||
'ip_address',
|
||||
'session_history_metadata.media_type',
|
||||
|
@ -104,6 +105,12 @@ class DataFactory(object):
|
|||
else:
|
||||
watched_status = 0
|
||||
|
||||
# Rename Mystery platform names
|
||||
platform_names = {'Mystery 3': 'Playstation 3',
|
||||
'Mystery 4': 'Playstation 4',
|
||||
'Mystery 5': 'Xbox 360'}
|
||||
platform = platform_names.get(item["platform"], item["platform"])
|
||||
|
||||
row = {"reference_id": item["reference_id"],
|
||||
"id": item["id"],
|
||||
"date": item["date"],
|
||||
|
@ -114,6 +121,7 @@ class DataFactory(object):
|
|||
"user_id": item["user_id"],
|
||||
"user": item["user"],
|
||||
"friendly_name": item["friendly_name"],
|
||||
"platform": platform,
|
||||
"player": item["player"],
|
||||
"ip_address": item["ip_address"],
|
||||
"media_type": item["media_type"],
|
||||
|
@ -128,6 +136,7 @@ class DataFactory(object):
|
|||
"thumb": thumb,
|
||||
"video_decision": item["video_decision"],
|
||||
"audio_decision": item["audio_decision"],
|
||||
"percent_complete": int(round(item['percent_complete'])),
|
||||
"watched_status": watched_status,
|
||||
"group_count": item["group_count"],
|
||||
"group_ids": item["group_ids"]
|
||||
|
@ -151,7 +160,7 @@ class DataFactory(object):
|
|||
home_stats = []
|
||||
|
||||
for stat in stats_cards:
|
||||
if 'top_tv' in stat:
|
||||
if stat == 'top_tv':
|
||||
top_tv = []
|
||||
try:
|
||||
query = 'SELECT session_history_metadata.id, ' \
|
||||
|
@ -197,7 +206,7 @@ class DataFactory(object):
|
|||
'stat_type': sort_type,
|
||||
'rows': top_tv})
|
||||
|
||||
elif 'popular_tv' in stat:
|
||||
elif stat == 'popular_tv':
|
||||
popular_tv = []
|
||||
try:
|
||||
query = 'SELECT session_history_metadata.id, ' \
|
||||
|
@ -243,7 +252,7 @@ class DataFactory(object):
|
|||
home_stats.append({'stat_id': stat,
|
||||
'rows': popular_tv})
|
||||
|
||||
elif 'top_movies' in stat:
|
||||
elif stat == 'top_movies':
|
||||
top_movies = []
|
||||
try:
|
||||
query = 'SELECT session_history_metadata.id, ' \
|
||||
|
@ -289,7 +298,7 @@ class DataFactory(object):
|
|||
'stat_type': sort_type,
|
||||
'rows': top_movies})
|
||||
|
||||
elif 'popular_movies' in stat:
|
||||
elif stat == 'popular_movies':
|
||||
popular_movies = []
|
||||
try:
|
||||
query = 'SELECT session_history_metadata.id, ' \
|
||||
|
@ -335,7 +344,7 @@ class DataFactory(object):
|
|||
home_stats.append({'stat_id': stat,
|
||||
'rows': popular_movies})
|
||||
|
||||
elif 'top_music' in stat:
|
||||
elif stat == 'top_music':
|
||||
top_music = []
|
||||
try:
|
||||
query = 'SELECT session_history_metadata.id, ' \
|
||||
|
@ -381,7 +390,7 @@ class DataFactory(object):
|
|||
'stat_type': sort_type,
|
||||
'rows': top_music})
|
||||
|
||||
elif 'popular_music' in stat:
|
||||
elif stat == 'popular_music':
|
||||
popular_music = []
|
||||
try:
|
||||
query = 'SELECT session_history_metadata.id, ' \
|
||||
|
@ -427,7 +436,7 @@ class DataFactory(object):
|
|||
home_stats.append({'stat_id': stat,
|
||||
'rows': popular_music})
|
||||
|
||||
elif 'top_users' in stat:
|
||||
elif stat == 'top_users':
|
||||
top_users = []
|
||||
try:
|
||||
query = 'SELECT session_history.user, ' \
|
||||
|
@ -480,7 +489,7 @@ class DataFactory(object):
|
|||
'stat_type': sort_type,
|
||||
'rows': top_users})
|
||||
|
||||
elif 'top_platforms' in stat:
|
||||
elif stat == 'top_platforms':
|
||||
top_platform = []
|
||||
|
||||
try:
|
||||
|
@ -528,7 +537,7 @@ class DataFactory(object):
|
|||
'stat_type': sort_type,
|
||||
'rows': top_platform})
|
||||
|
||||
elif 'last_watched' in stat:
|
||||
elif stat == 'last_watched':
|
||||
last_watched = []
|
||||
try:
|
||||
query = 'SELECT session_history_metadata.id, ' \
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# This file is part of PlexPy.
|
||||
#
|
||||
# PlexPy is free software: you can redistribute it and/or modify
|
||||
|
@ -14,12 +17,11 @@
|
|||
# along with PlexPy. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from plexpy import logger, helpers
|
||||
|
||||
from httplib import HTTPSConnection
|
||||
from httplib import HTTPConnection
|
||||
|
||||
import ssl
|
||||
|
||||
|
||||
class HTTPHandler(object):
|
||||
"""
|
||||
Retrieve data from Plex Server
|
||||
|
@ -88,19 +90,25 @@ class HTTPHandler(object):
|
|||
return None
|
||||
|
||||
if request_status == 200:
|
||||
if output_format == 'dict':
|
||||
output = helpers.convert_xml_to_dict(request_content)
|
||||
elif output_format == 'json':
|
||||
output = helpers.convert_xml_to_json(request_content)
|
||||
elif output_format == 'xml':
|
||||
output = helpers.parse_xml(request_content)
|
||||
else:
|
||||
output = request_content
|
||||
try:
|
||||
if output_format == 'dict':
|
||||
output = helpers.convert_xml_to_dict(request_content)
|
||||
elif output_format == 'json':
|
||||
output = helpers.convert_xml_to_json(request_content)
|
||||
elif output_format == 'xml':
|
||||
output = helpers.parse_xml(request_content)
|
||||
else:
|
||||
output = request_content
|
||||
|
||||
if return_type:
|
||||
return output, content_type
|
||||
if return_type:
|
||||
return output, content_type
|
||||
|
||||
return output
|
||||
|
||||
except Exception as e:
|
||||
logger.warn(u"Failed format response from uri %s to %s error %s" % (uri, output_format, e))
|
||||
return None
|
||||
|
||||
return output
|
||||
else:
|
||||
logger.warn(u"Failed to access uri endpoint %s. Status code %r" % (uri, request_status))
|
||||
return None
|
||||
|
|
|
@ -467,8 +467,9 @@ class PROWL(object):
|
|||
{'label': 'Priority',
|
||||
'value': self.priority,
|
||||
'name': 'prowl_priority',
|
||||
'description': 'Set the priority (-2,-1,0,1 or 2).',
|
||||
'input_type': 'number'
|
||||
'description': 'Set the priority.',
|
||||
'input_type': 'select',
|
||||
'select_options': {-2: -2, -1: -1, 0: 0, 1: 1, 2: 2}
|
||||
}
|
||||
]
|
||||
|
||||
|
@ -695,8 +696,9 @@ class NMA(object):
|
|||
{'label': 'Priority',
|
||||
'value': plexpy.CONFIG.NMA_PRIORITY,
|
||||
'name': 'nma_priority',
|
||||
'description': 'Set the priority (-2,-1,0,1 or 2).',
|
||||
'input_type': 'number'
|
||||
'description': 'Set the priority.',
|
||||
'input_type': 'select',
|
||||
'select_options': {-2: -2, -1: -1, 0: 0, 1: 1, 2: 2}
|
||||
}
|
||||
]
|
||||
|
||||
|
@ -845,6 +847,7 @@ class PUSHOVER(object):
|
|||
self.enabled = plexpy.CONFIG.PUSHOVER_ENABLED
|
||||
self.keys = plexpy.CONFIG.PUSHOVER_KEYS
|
||||
self.priority = plexpy.CONFIG.PUSHOVER_PRIORITY
|
||||
self.sound = plexpy.CONFIG.PUSHOVER_SOUND
|
||||
self.on_play = plexpy.CONFIG.PUSHOVER_ON_PLAY
|
||||
self.on_stop = plexpy.CONFIG.PUSHOVER_ON_STOP
|
||||
self.on_watched = plexpy.CONFIG.PUSHOVER_ON_WATCHED
|
||||
|
@ -867,12 +870,13 @@ class PUSHOVER(object):
|
|||
'user': plexpy.CONFIG.PUSHOVER_KEYS,
|
||||
'title': event,
|
||||
'message': message.encode("utf-8"),
|
||||
'sound': plexpy.CONFIG.PUSHOVER_SOUND,
|
||||
'priority': plexpy.CONFIG.PUSHOVER_PRIORITY}
|
||||
|
||||
http_handler.request("POST",
|
||||
"/1/messages.json",
|
||||
headers={'Content-type': "application/x-www-form-urlencoded"},
|
||||
body=urlencode(data))
|
||||
"/1/messages.json",
|
||||
headers={'Content-type': "application/x-www-form-urlencoded"},
|
||||
body=urlencode(data))
|
||||
response = http_handler.getresponse()
|
||||
request_status = response.status
|
||||
# logger.debug(u"Pushover response status: %r" % request_status)
|
||||
|
@ -893,30 +897,57 @@ class PUSHOVER(object):
|
|||
#For uniformity reasons not removed
|
||||
return
|
||||
|
||||
def test(self, keys, priority):
|
||||
def test(self, keys, priority, sound):
|
||||
self.enabled = True
|
||||
self.keys = keys
|
||||
self.priority = priority
|
||||
self.sound = sound
|
||||
|
||||
self.notify('Main Screen Activate', 'Test Message')
|
||||
|
||||
def get_sounds(self):
|
||||
http_handler = HTTPSConnection("api.pushover.net")
|
||||
http_handler.request("GET", "/1/sounds.json?token=" + self.application_token)
|
||||
response = http_handler.getresponse()
|
||||
request_status = response.status
|
||||
|
||||
if request_status == 200:
|
||||
data = json.loads(response.read())
|
||||
sounds = data.get('sounds', {})
|
||||
sounds.update({'': ''})
|
||||
return sounds
|
||||
elif request_status >= 400 and request_status < 500:
|
||||
logger.info(u"Unable to retrieve Pushover notification sounds list: %s" % response.reason)
|
||||
return {'': ''}
|
||||
else:
|
||||
logger.info(u"Unable to retrieve Pushover notification sounds list.")
|
||||
return {'': ''}
|
||||
|
||||
def return_config_options(self):
|
||||
config_option = [{'label': 'Pushover API Key',
|
||||
config_option = [{'label': 'Pushover User Key',
|
||||
'value': self.keys,
|
||||
'name': 'pushover_keys',
|
||||
'description': 'Your Pushover API key.',
|
||||
'description': 'Your Pushover user key.',
|
||||
'input_type': 'text'
|
||||
},
|
||||
{'label': 'Priority',
|
||||
'value': self.priority,
|
||||
'name': 'pushover_priority',
|
||||
'description': 'Set the priority (-2,-1,0,1 or 2).',
|
||||
'input_type': 'number'
|
||||
'description': 'Set the priority.',
|
||||
'input_type': 'select',
|
||||
'select_options': {-2: -2, -1: -1, 0: 0, 1: 1, 2: 2}
|
||||
},
|
||||
{'label': 'Sound',
|
||||
'value': self.sound,
|
||||
'name': 'pushover_sound',
|
||||
'description': 'Set the notification sound. Leave blank for the default sound.',
|
||||
'input_type': 'select',
|
||||
'select_options': self.get_sounds()
|
||||
},
|
||||
{'label': 'Pushover API Token',
|
||||
'value': plexpy.CONFIG.PUSHOVER_APITOKEN,
|
||||
'name': 'pushover_apitoken',
|
||||
'description': 'Your Pushover API toekn. Leave blank to use PlexPy default.',
|
||||
'description': 'Your Pushover API token. Leave blank to use PlexPy default.',
|
||||
'input_type': 'text'
|
||||
}
|
||||
]
|
||||
|
@ -1135,6 +1166,7 @@ class BOXCAR(object):
|
|||
def __init__(self):
|
||||
self.url = 'https://new.boxcar.io/api/notifications'
|
||||
self.token = plexpy.CONFIG.BOXCAR_TOKEN
|
||||
self.sound = plexpy.CONFIG.BOXCAR_SOUND
|
||||
self.on_play = plexpy.CONFIG.BOXCAR_ON_PLAY
|
||||
self.on_stop = plexpy.CONFIG.BOXCAR_ON_STOP
|
||||
self.on_watched = plexpy.CONFIG.BOXCAR_ON_WATCHED
|
||||
|
@ -1148,7 +1180,7 @@ class BOXCAR(object):
|
|||
'user_credentials': plexpy.CONFIG.BOXCAR_TOKEN,
|
||||
'notification[title]': title.encode('utf-8'),
|
||||
'notification[long_message]': message.encode('utf-8'),
|
||||
'notification[sound]': "done"
|
||||
'notification[sound]': plexpy.CONFIG.BOXCAR_SOUND
|
||||
})
|
||||
|
||||
req = urllib2.Request(self.url)
|
||||
|
@ -1166,6 +1198,42 @@ class BOXCAR(object):
|
|||
'name': 'boxcar_token',
|
||||
'description': 'Your Boxcar access token.',
|
||||
'input_type': 'text'
|
||||
},
|
||||
{'label': 'Sound',
|
||||
'value': self.sound,
|
||||
'name': 'boxcar_sound',
|
||||
'description': 'Set the notification sound. Leave blank for the default sound.',
|
||||
'input_type': 'select',
|
||||
'select_options': {'': '',
|
||||
'beep-crisp': 'Beep (Crisp)',
|
||||
'beep-soft': 'Beep (Soft)',
|
||||
'bell-modern': 'Bell (Modern)',
|
||||
'bell-one-tone': 'Bell (One Tone)',
|
||||
'bell-simple': 'Bell (Simple)',
|
||||
'bell-triple': 'Bell (Triple)',
|
||||
'bird-1': 'Bird (1)',
|
||||
'bird-2': 'Bird (2)',
|
||||
'boing': 'Boing',
|
||||
'cash': 'Cash',
|
||||
'clanging': 'Clanging',
|
||||
'detonator-charge': 'Detonator Charge',
|
||||
'digital-alarm': 'Digital Alarm',
|
||||
'done': 'Done',
|
||||
'echo': 'Echo',
|
||||
'flourish': 'Flourish',
|
||||
'harp': 'Harp',
|
||||
'light': 'Light',
|
||||
'magic-chime':'Magic Chime',
|
||||
'magic-coin': 'Magic Coin',
|
||||
'no-sound': 'No Sound',
|
||||
'notifier-1': 'Notifier (1)',
|
||||
'notifier-2': 'Notifier (2)',
|
||||
'notifier-3': 'Notifier (3)',
|
||||
'orchestral-long': 'Orchestral (Long)',
|
||||
'orchestral-short': 'Orchestral (Short)',
|
||||
'score': 'Score',
|
||||
'success': 'Success',
|
||||
'up': 'Up'}
|
||||
}
|
||||
]
|
||||
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# This file is part of PlexPy.
|
||||
#
|
||||
# PlexPy is free software: you can redistribute it and/or modify
|
||||
|
@ -14,12 +17,14 @@
|
|||
# along with PlexPy. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from plexpy import logger, helpers, users, http_handler, database
|
||||
|
||||
import xmltodict
|
||||
import json
|
||||
from xml.dom import minidom
|
||||
|
||||
import base64
|
||||
import plexpy
|
||||
|
||||
|
||||
def refresh_users():
|
||||
logger.info("Requesting users list refresh...")
|
||||
result = PlexTV().get_full_users_list()
|
||||
|
@ -54,6 +59,7 @@ def refresh_users():
|
|||
else:
|
||||
logger.warn("Unable to refresh users list.")
|
||||
|
||||
|
||||
def get_real_pms_url():
|
||||
logger.info("Requesting URLs for server...")
|
||||
|
||||
|
@ -91,6 +97,7 @@ def get_real_pms_url():
|
|||
plexpy.CONFIG.__setattr__('PMS_URL', fallback_url)
|
||||
plexpy.CONFIG.write()
|
||||
|
||||
|
||||
class PlexTV(object):
|
||||
"""
|
||||
Plex.tv authentication
|
||||
|
@ -133,7 +140,7 @@ class PlexTV(object):
|
|||
if plextv_response:
|
||||
xml_head = plextv_response.getElementsByTagName('user')
|
||||
if not xml_head:
|
||||
logger.warn("Error parsing XML for Plex.tv token: %s" % e)
|
||||
logger.warn("Error parsing XML for Plex.tv token")
|
||||
return []
|
||||
|
||||
auth_token = xml_head[0].getAttribute('authenticationToken')
|
||||
|
@ -402,3 +409,38 @@ class PlexTV(object):
|
|||
server_urls.append(server_details)
|
||||
|
||||
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')
|
||||
servers = xmltodict.parse(result, process_namespaces=True, attr_prefix='')
|
||||
clean_servers = []
|
||||
|
||||
try:
|
||||
if servers:
|
||||
# Fix if its only one "device"
|
||||
if int(servers['MediaContainer']['size']) == 1:
|
||||
servers['MediaContainer']['Device'] = [servers['MediaContainer']['Device']]
|
||||
|
||||
for server in servers['MediaContainer']['Device']:
|
||||
# Only grab servers online and own
|
||||
if server.get('presence', None) == '1' and server.get('owned', None) == '1' and server.get('provides', None) == 'server':
|
||||
# If someone only has one connection..
|
||||
if isinstance(server['Connection'], dict):
|
||||
server['Connection'] = [server['Connection']]
|
||||
|
||||
for s in server['Connection']:
|
||||
# to avoid circular ref
|
||||
d = {}
|
||||
d.update(s)
|
||||
d.update(server)
|
||||
d['label'] = d['name']
|
||||
d['value'] = d['address']
|
||||
del d['Connection']
|
||||
clean_servers.append(d)
|
||||
|
||||
except Exception as e:
|
||||
logger.warn('Failed to get servers from plex %s' % e)
|
||||
return clean_servers
|
||||
|
||||
return json.dumps(clean_servers, indent=4)
|
||||
|
|
|
@ -32,7 +32,8 @@ class Users(object):
|
|||
'MAX(session_history.started) as last_seen',
|
||||
'session_history.ip_address as ip_address',
|
||||
'COUNT(session_history.id) as plays',
|
||||
'session_history.player as platform',
|
||||
'session_history.platform as platform',
|
||||
'session_history.player as player',
|
||||
'session_history_metadata.full_title as last_watched',
|
||||
'session_history_metadata.thumb',
|
||||
'session_history_metadata.parent_thumb',
|
||||
|
@ -83,12 +84,19 @@ class Users(object):
|
|||
else:
|
||||
user_thumb = item['user_thumb']
|
||||
|
||||
# Rename Mystery platform names
|
||||
platform_names = {'Mystery 3': 'Playstation 3',
|
||||
'Mystery 4': 'Playstation 4',
|
||||
'Mystery 5': 'Xbox 360'}
|
||||
platform = platform_names.get(item["platform"], item["platform"])
|
||||
|
||||
row = {"id": item['id'],
|
||||
"plays": item['plays'],
|
||||
"last_seen": item['last_seen'],
|
||||
"friendly_name": item['friendly_name'],
|
||||
"ip_address": item['ip_address'],
|
||||
"platform": item['platform'],
|
||||
"platform": platform,
|
||||
"player": item['player'],
|
||||
"last_watched": item['last_watched'],
|
||||
"thumb": thumb,
|
||||
"media_type": item['media_type'],
|
||||
|
@ -121,7 +129,8 @@ class Users(object):
|
|||
'session_history.started as last_seen',
|
||||
'session_history.ip_address as ip_address',
|
||||
'COUNT(session_history.id) as play_count',
|
||||
'session_history.player as platform',
|
||||
'session_history.platform as platform',
|
||||
'session_history.player as player',
|
||||
'session_history_metadata.full_title as last_watched',
|
||||
'session_history_metadata.thumb',
|
||||
'session_history_metadata.parent_thumb',
|
||||
|
@ -169,11 +178,18 @@ class Users(object):
|
|||
else:
|
||||
thumb = item["thumb"]
|
||||
|
||||
# Rename Mystery platform names
|
||||
platform_names = {'Mystery 3': 'Playstation 3',
|
||||
'Mystery 4': 'Playstation 4',
|
||||
'Mystery 5': 'Xbox 360'}
|
||||
platform = platform_names.get(item["platform"], item["platform"])
|
||||
|
||||
row = {"id": item['id'],
|
||||
"last_seen": item['last_seen'],
|
||||
"ip_address": item['ip_address'],
|
||||
"play_count": item['play_count'],
|
||||
"platform": item['platform'],
|
||||
"platform": platform,
|
||||
"player": item['player'],
|
||||
"last_watched": item['last_watched'],
|
||||
"thumb": thumb,
|
||||
"media_type": item['media_type'],
|
||||
|
@ -490,10 +506,10 @@ class Users(object):
|
|||
|
||||
return user_watch_time_stats
|
||||
|
||||
def get_user_platform_stats(self, user=None, user_id=None):
|
||||
def get_user_player_stats(self, user=None, user_id=None):
|
||||
monitor_db = database.MonitorDatabase()
|
||||
|
||||
platform_stats = []
|
||||
player_stats = []
|
||||
result_id = 0
|
||||
|
||||
try:
|
||||
|
@ -522,12 +538,12 @@ class Users(object):
|
|||
'Mystery 5': 'Xbox 360'}
|
||||
platform_type = platform_names.get(item[2], item[2])
|
||||
|
||||
row = {'platform_name': item[0],
|
||||
row = {'player_name': item[0],
|
||||
'platform_type': platform_type,
|
||||
'total_plays': item[1],
|
||||
'result_id': result_id
|
||||
}
|
||||
platform_stats.append(row)
|
||||
player_stats.append(row)
|
||||
result_id += 1
|
||||
|
||||
return platform_stats
|
||||
return player_stats
|
|
@ -1,2 +1,2 @@
|
|||
PLEXPY_VERSION = "master"
|
||||
PLEXPY_RELEASE_VERSION = "1.2.1"
|
||||
PLEXPY_RELEASE_VERSION = "1.2.2"
|
||||
|
|
|
@ -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
|
||||
# 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.
|
||||
# Also redirecting to home if a PMS token already exists - will remove this in future.
|
||||
if plexpy.CONFIG.FIRST_RUN_COMPLETE or plexpy.CONFIG.PMS_TOKEN:
|
||||
if plexpy.CONFIG.FIRST_RUN_COMPLETE:
|
||||
plexpy.initialize_scheduler()
|
||||
raise cherrypy.HTTPRedirect("home")
|
||||
else:
|
||||
|
@ -567,7 +569,7 @@ class WebInterface(object):
|
|||
|
||||
watched_percent = plexpy.CONFIG.NOTIFY_WATCHED_PERCENT
|
||||
|
||||
custom_where=[]
|
||||
custom_where = []
|
||||
if user_id:
|
||||
custom_where = [['session_history.user_id', user_id]]
|
||||
elif user:
|
||||
|
@ -587,6 +589,9 @@ class WebInterface(object):
|
|||
if 'reference_id' in kwargs:
|
||||
reference_id = kwargs.get('reference_id', "")
|
||||
custom_where = [['session_history.reference_id', reference_id]]
|
||||
if 'media_type' in kwargs:
|
||||
media_type = kwargs.get('media_type', "")
|
||||
custom_where = [['session_history_metadata.media_type', media_type]]
|
||||
|
||||
data_factory = datafactory.DataFactory()
|
||||
history = data_factory.get_history(kwargs=kwargs, custom_where=custom_where, grouping=grouping, watched_percent=watched_percent)
|
||||
|
@ -771,6 +776,12 @@ class WebInterface(object):
|
|||
if source == 'history':
|
||||
data_factory = datafactory.DataFactory()
|
||||
metadata = data_factory.get_metadata_details(row_id=item_id)
|
||||
elif item_id == 'movie':
|
||||
metadata = {'type': 'library', 'library': 'movie', 'media_type': 'movie', 'title': 'Movies'}
|
||||
elif item_id == 'show':
|
||||
metadata = {'type': 'library', 'library': 'show', 'media_type': 'episode', 'title': 'TV Shows'}
|
||||
elif item_id == 'artist':
|
||||
metadata = {'type': 'library', 'library': 'artist', 'media_type': 'track', 'title': 'Music'}
|
||||
else:
|
||||
pms_connect = pmsconnect.PmsConnect()
|
||||
result = pms_connect.get_metadata_details(rating_key=item_id)
|
||||
|
@ -813,17 +824,17 @@ class WebInterface(object):
|
|||
return serve_template(templatename="user_watch_time_stats.html", data=None, title="Watch Stats")
|
||||
|
||||
@cherrypy.expose
|
||||
def get_user_platform_stats(self, user=None, user_id=None, **kwargs):
|
||||
def get_user_player_stats(self, user=None, user_id=None, **kwargs):
|
||||
|
||||
user_data = users.Users()
|
||||
result = user_data.get_user_platform_stats(user_id=user_id, user=user)
|
||||
result = user_data.get_user_player_stats(user_id=user_id, user=user)
|
||||
|
||||
if result:
|
||||
return serve_template(templatename="user_platform_stats.html", data=result,
|
||||
title="Platform Stats")
|
||||
return serve_template(templatename="user_player_stats.html", data=result,
|
||||
title="Player Stats")
|
||||
else:
|
||||
logger.warn('Unable to retrieve data.')
|
||||
return serve_template(templatename="user_platform_stats.html", data=None, title="Platform Stats")
|
||||
return serve_template(templatename="user_player_stats.html", data=None, title="Player Stats")
|
||||
|
||||
@cherrypy.expose
|
||||
def get_item_children(self, rating_key='', **kwargs):
|
||||
|
@ -888,7 +899,7 @@ class WebInterface(object):
|
|||
@cherrypy.expose
|
||||
def get_user_ips(self, user_id=None, user=None, **kwargs):
|
||||
|
||||
custom_where=[]
|
||||
custom_where = []
|
||||
if user_id:
|
||||
custom_where = [['user_id', user_id]]
|
||||
elif user:
|
||||
|
@ -1294,7 +1305,7 @@ class WebInterface(object):
|
|||
checkboxes = {'email_tls': checked(plexpy.CONFIG.EMAIL_TLS)}
|
||||
|
||||
return serve_template(templatename="notification_config.html", title="Notification Configuration",
|
||||
data=config, checkboxes=checkboxes)
|
||||
config_id=config_id, data=config, checkboxes=checkboxes)
|
||||
|
||||
@cherrypy.expose
|
||||
def get_notification_agent_triggers(self, config_id, **kwargs):
|
||||
|
@ -1387,8 +1398,8 @@ class WebInterface(object):
|
|||
new_key_list = pms_connect.get_rating_keys_list(rating_key=new_rating_key, media_type=media_type)
|
||||
|
||||
update_db = data_factory.update_rating_key(old_key_list=old_key_list,
|
||||
new_key_list=new_key_list,
|
||||
media_type=media_type)
|
||||
new_key_list=new_key_list,
|
||||
media_type=media_type)
|
||||
|
||||
if update_db:
|
||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||
|
@ -1397,7 +1408,6 @@ class WebInterface(object):
|
|||
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||
return json.dumps({'message': 'no data received'})
|
||||
|
||||
|
||||
# test code
|
||||
@cherrypy.expose
|
||||
def get_new_rating_keys(self, rating_key='', media_type='', **kwargs):
|
||||
|
@ -1434,11 +1444,27 @@ class WebInterface(object):
|
|||
new_key_list = pms_connect.get_rating_keys_list(rating_key=new_rating_key, media_type=media_type)
|
||||
|
||||
result = data_factory.update_rating_key(old_key_list=old_key_list,
|
||||
new_key_list=new_key_list,
|
||||
media_type=media_type)
|
||||
new_key_list=new_key_list,
|
||||
media_type=media_type)
|
||||
|
||||
if result:
|
||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||
return json.dumps(result)
|
||||
else:
|
||||
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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue