Compare commits

...

64 Commits

Author SHA1 Message Date
JonnyWong16
d9474cdcc5 Merge branch 'dev' 2016-06-11 11:40:24 -07:00
JonnyWong16
e49a34177a v1.4.6 2016-06-11 11:40:03 -07:00
JonnyWong16
67d203e011 Catch exception and attempt to reconnect websocket 2016-06-11 11:26:52 -07:00
JonnyWong16
0d38b3de16 Catch json parse error for current activity 2016-06-06 00:28:01 -07:00
JonnyWong16
38116a14f3 Fix double clear search button 2016-06-05 11:23:12 -07:00
JonnyWong16
b28f0b65f0 Add download logs button for Plex logs 2016-06-05 11:18:23 -07:00
JonnyWong16
13ab4a9363 Add log level filter to PlexPy logs 2016-06-05 11:17:58 -07:00
JonnyWong16
7cb7783a34 Make sure there is a date before trying to format it 2016-06-05 11:05:17 -07:00
JonnyWong16
d1a13dad38 Merge pull request #729 from sanderploegsma/feature/plex-log-level-filter
Add log level filter for plex server and media logs
2016-06-05 09:30:57 -07:00
JonnyWong16
b4e06dea99 Add some date options for notifications 2016-06-04 22:30:41 -07:00
JonnyWong16
0f92dc0fdf Add refresh button to images 2016-06-04 12:43:45 -07:00
JonnyWong16
6a58895d37 Reflect path suggested in installation guide (FreeNAS) 2016-06-04 08:48:58 -07:00
JonnyWong16
1709a2b7df Merge pull request #741 from Hellowlol/cachefix
Allow refresh of images
2016-06-04 08:43:51 -07:00
JonnyWong16
febb3da0c1 Merge pull request #735 from nortron/fix_freebsd_daemon_template_default_path
Reflect path suggested in installation guide (FreeBSD)
2016-06-04 08:43:29 -07:00
Hellowlol
552a428985 allow refresh of images
Allow refresh of images and fix bug where a disabled cache still would
use the cache
2016-06-02 17:27:23 +02:00
Tim Van
38e04bd42a Allow global config setting for PMS log window size. 2016-05-30 15:59:01 +02:00
JonnyWong16
8f0ba5ba4f Add notification agent ids to the API docs 2016-05-29 16:04:27 -07:00
JonnyWong16
c67aedceb1 Add library statistics to API 2016-05-28 14:09:59 -07:00
JonnyWong16
b3a7fbd9b5 Add pms token to loopback url when retrieving images 2016-05-28 14:02:21 -07:00
JonnyWong16
29522428de Catch exception when retrieving current activity 2016-05-28 14:01:29 -07:00
JonnyWong16
77bd52b2ae Add user details and stats to API 2016-05-26 19:25:14 -07:00
Nick N
d8112e7628 Reflect path suggested in installation guide 2016-05-26 11:44:03 -07:00
JonnyWong16
ffa208e73f Merge branch 'dev' 2016-05-25 21:34:15 -07:00
JonnyWong16
61ead15c38 v1.4.5 2016-05-25 21:33:50 -07:00
JonnyWong16
407e2ae481 Revert pms image proxy to use 127.0.0.1 2016-05-25 21:27:43 -07:00
JonnyWong16
5fb16edf43 Make sure library details were returned when joining shared libraries 2016-05-25 18:55:20 -07:00
JonnyWong16
8eb5c475bb Make sure port is a string when when matching pms url 2016-05-25 18:54:31 -07:00
JonnyWong16
84090310f7 Extract mapped IPv4 address from Plexivity import 2016-05-25 18:53:40 -07:00
JonnyWong16
fc98e2f052 Merge branch 'dev' 2016-05-24 22:17:22 -07:00
JonnyWong16
bedcfa9520 v1.4.4 2016-05-24 22:17:09 -07:00
JonnyWong16
bb152b590b Set all datatable tooltips to body container 2016-05-24 22:13:26 -07:00
JonnyWong16
3623732cf7 Sort sessions by session_key
* Try to minimize instances jumping around under homepage current
activity.
* Still need to fix for music activity. Some clients have a different
session_key per track.
2016-05-24 22:12:57 -07:00
JonnyWong16
05ba89f164 Make sure clip image urls are escaped 2016-05-24 21:33:37 -07:00
Sander Ploegsma
cb5053476d Add log level filter for plex server and media logs 2016-05-25 00:09:04 +02:00
JonnyWong16
cee656a053 Fix ip address in Plexivity import 2016-05-23 18:02:13 -07:00
JonnyWong16
cfc7d529e1 Merge branch 'dev' 2016-05-22 16:24:23 -07:00
JonnyWong16
a93dc68e6c v1.4.3 2016-05-22 16:24:01 -07:00
JonnyWong16
2d91cfd3db Fix basic auth 2016-05-22 16:22:35 -07:00
JonnyWong16
36e81f44cb Merge branch 'dev' 2016-05-22 15:04:25 -07:00
JonnyWong16
cb0e65337f v1.4.2 2016-05-22 15:04:17 -07:00
JonnyWong16
1c627f4649 Fix unable to save settings when checking http proxy 2016-05-22 14:54:44 -07:00
JonnyWong16
16cbfed20b Option to use HTTP basic authentication 2016-05-22 14:23:55 -07:00
JonnyWong16
f6a3bc57e2 Fix typos 2016-05-22 13:24:52 -07:00
JonnyWong16
594443d1dc Match port as well when retrieving pms url 2016-05-22 13:24:45 -07:00
JonnyWong16
c3378e1653 Merge branch 'dev' 2016-05-20 20:54:36 -07:00
JonnyWong16
bc57dd650c v1.4.1 2016-05-20 20:54:06 -07:00
JonnyWong16
311a8c6fa3 Try using requests for Join notifications 2016-05-20 20:44:16 -07:00
JonnyWong16
bdb43c0e9e Add paging for recently added to the API 2016-05-20 20:16:14 -07:00
JonnyWong16
8033b47596 Add secondary sort by most recent for watch statistics 2016-05-19 22:44:28 -07:00
JonnyWong16
9d5052cc68 Add http proxy checkbox to settings 2016-05-19 21:55:11 -07:00
JonnyWong16
f4c9dc8a5f Make sure cherrypy doesn't add the local port twice with http_proxy enabled 2016-05-19 21:54:58 -07:00
JonnyWong16
a3f0a78df0 Reduce cost factor for hashing passwords
* Also reduce memory cost
2016-05-19 20:24:22 -07:00
JonnyWong16
b70363e005 Fix resolution in stream data modal 2016-05-18 20:55:49 -07:00
JonnyWong16
65eab801e8 Format Join device ids 2016-05-18 20:55:49 -07:00
JonnyWong16
9e764248d3 Merge pull request #713 from Hellowlol/fix_log_order
Fix #705
2016-05-18 20:55:06 -07:00
Hellowlol
a660a1c44b fix https://github.com/drzoidberg33/plexpy/issues/705#issuecomment-219927893
Can you test and verify
2016-05-18 20:55:20 +02:00
JonnyWong16
33458c1bdb Make sure current activity returned sessions when refreshing 2016-05-17 21:10:05 -07:00
JonnyWong16
e5530182cd Make sure we get a result when trying to group the session 2016-05-17 20:58:28 -07:00
JonnyWong16
9ecabc3faf Just make sure redirects include http_root 2016-05-16 18:18:52 -07:00
JonnyWong16
8b58f6b861 Make sure pms_identifier is cleared first when verifying server 2016-05-16 09:14:00 -07:00
JonnyWong16
9e41bf529d Don't return inside the loop after sending XBMC/Plex notifications 2016-05-16 08:28:40 -07:00
JonnyWong16
36398fe958 Persist current activity artwork blur across refresh 2016-05-15 21:54:25 -07:00
JonnyWong16
69cfbea5f3 Refresh Join device list when changing API key 2016-05-15 17:20:28 -07:00
JonnyWong16
1e1e3beca6 Check for blank username/passwords on login 2016-05-15 17:20:14 -07:00
45 changed files with 1135 additions and 256 deletions

215
API.md
View File

@@ -543,6 +543,33 @@ Returns:
``` ```
### get_library
Get a library's details.
```
Required parameters:
section_id (str): The id of the Plex library section
Optional parameters:
None
Returns:
json:
{"child_count": null,
"count": 887,
"do_notify": 1,
"do_notify_created": 1,
"keep_history": 1,
"library_art": "/:/resources/movie-fanart.jpg",
"library_thumb": "/:/resources/movie.png",
"parent_count": null,
"section_id": 1,
"section_name": "Movies",
"section_type": "movie"
}
```
### get_library_media_info ### get_library_media_info
Get the data on the PlexPy media info tables. Get the data on the PlexPy media info tables.
@@ -619,6 +646,66 @@ Returns:
``` ```
### get_library_user_stats
Get a library's user statistics.
```
Required parameters:
section_id (str): The id of the Plex library section
Optional parameters:
None
Returns:
json:
[{"friendly_name": "Jon Snow",
"total_plays": 170,
"user_id": 133788,
"user_thumb": "https://plex.tv/users/k10w42309cynaopq/avatar"
},
{"platform_type": "DanyKhaleesi69",
"total_plays": 42,
"user_id": 8008135,
"user_thumb": "https://plex.tv/users/568gwwoib5t98a3a/avatar"
},
{...},
{...}
]
```
### get_library_watch_time_stats
Get a library's watch time statistics.
```
Required parameters:
section_id (str): The id of the Plex library section
Optional parameters:
None
Returns:
json:
[{"query_days": 1,
"total_plays": 0,
"total_time": 0
},
{"query_days": 7,
"total_plays": 3,
"total_time": 15694
},
{"query_days": 30,
"total_plays": 35,
"total_time": 63054
},
{"query_days": 0,
"total_plays": 508,
"total_time": 1183080
}
]
```
### get_logs ### get_logs
Get the PlexPy logs. Get the PlexPy logs.
@@ -1061,6 +1148,7 @@ Required parameters:
count (str): Number of items to return count (str): Number of items to return
Optional parameters: Optional parameters:
start (str): The item number to start at
section_id (str): The id of the Plex library section section_id (str): The id of the Plex library section
Returns: Returns:
@@ -1310,6 +1398,35 @@ Returns:
``` ```
### get_user
Get a user's details.
```
Required parameters:
user_id (str): The id of the Plex user
Optional parameters:
None
Returns:
json:
{"allow_guest": 1,
"deleted_user": 0,
"do_notify": 1,
"email": "Jon.Snow.1337@CastleBlack.com",
"friendly_name": "Jon Snow",
"is_allow_sync": 1,
"is_home_user": 1,
"is_restricted": 0,
"keep_history": 1,
"shared_libraries": ["10", "1", "4", "5", "15", "20", "2"],
"user_id": 133788,
"user_thumb": "https://plex.tv/users/k10w42309cynaopq/avatar",
"username": "LordCommanderSnow"
}
```
### get_user_ips ### get_user_ips
Get the data on PlexPy users IP table. Get the data on PlexPy users IP table.
@@ -1357,7 +1474,7 @@ Returns:
### get_user_logins ### get_user_logins
Get the data on PlexPy user login table. Get the data on PlexPy user login table.
``` ```
Required parameters: Required parameters:
@@ -1376,15 +1493,15 @@ Returns:
"recordsTotal": 2344, "recordsTotal": 2344,
"recordsFiltered": 10, "recordsFiltered": 10,
"data": "data":
[{"browser": "Safari 7.0.3", [{"browser": "Safari 7.0.3",
"friendly_name": "Jon Snow", "friendly_name": "Jon Snow",
"host": "http://plexpy.castleblack.com", "host": "http://plexpy.castleblack.com",
"ip_address": "xxx.xxx.xxx.xxx", "ip_address": "xxx.xxx.xxx.xxx",
"os": "Mac OS X", "os": "Mac OS X",
"timestamp": 1462591869, "timestamp": 1462591869,
"user": "LordCommanderSnow", "user": "LordCommanderSnow",
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A", "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A",
"user_group": "guest", "user_group": "guest",
"user_id": 133788 "user_id": 133788
}, },
{...}, {...},
@@ -1414,6 +1531,66 @@ Returns:
``` ```
### get_user_player_stats
Get a user's player statistics.
```
Required parameters:
user_id (str): The id of the Plex user
Optional parameters:
None
Returns:
json:
[{"platform_type": "Chrome",
"player_name": "Plex Web (Chrome)",
"result_id": 1,
"total_plays": 170
},
{"platform_type": "Chromecast",
"player_name": "Chromecast",
"result_id": 2,
"total_plays": 42
},
{...},
{...}
]
```
### get_user_watch_time_stats
Get a user's watch time statistics.
```
Required parameters:
user_id (str): The id of the Plex user
Optional parameters:
None
Returns:
json:
[{"query_days": 1,
"total_plays": 0,
"total_time": 0
},
{"query_days": 7,
"total_plays": 3,
"total_time": 15694
},
{"query_days": 30,
"total_plays": 35,
"total_time": 63054
},
{"query_days": 0,
"total_plays": 508,
"total_time": 1183080
}
]
```
### get_users ### get_users
Get a list of all users that have access to your server. Get a list of all users that have access to your server.
@@ -1519,6 +1696,24 @@ Send a notification using PlexPy.
``` ```
Required parameters: Required parameters:
agent_id(str): The id of the notification agent to use agent_id(str): The id of the notification agent to use
9 # Boxcar2
17 # Browser
10 # Email
16 # Facebook
0 # Growl
12 # IFTTT
18 # Join
4 # NotifyMyAndroid
3 # Plex Home Theater
1 # Prowl
5 # Pushalot
6 # Pushbullet
7 # Pushover
15 # Scripts
14 # Slack
13 # Telegram
11 # Twitter
2 # XBMC
subject(str): The subject of the message subject(str): The subject of the message
body(str): The body of the message body(str): The body of the message

View File

@@ -1,5 +1,66 @@
# Changelog # Changelog
## v1.4.6 (2016-06-11)
* New: Added User and Library statistics to the API.
* New: Ability to refresh individual poster images without clearing the entire cache. (Thanks @Hellowlol)
* New: Added {added_date}, {updated_date}, and {last_viewed_date} to metadata notification options.
* New: Log level filter for Plex logs. (Thanks @sanderploegsma)
* New: Log level filter for PlexPy logs.
* New: Button to download Plex logs directly from the web interface.
* New: Advanced setting in the config file to change the number of Plex log lines retrieved.
* Fix: FreeBSD and FreeNAS init scripts to reflect the path in the installation guide. (Thanks @nortron)
* Fix: Monitoring crashing when failed to retrieve current activity.
## v1.4.5 (2016-05-25)
* Fix: PlexPy unable to start if failed to get shared libraries for a user.
* Fix: Matching port number when retrieving the PMS url.
* Fix: Extract mapped IPv4 address in Plexivity import.
* Change: Revert back to internal url when retrieving PMS images.
## v1.4.4 (2016-05-24)
* Fix: Image queries crashing the PMS when playing clips from channels.
* Fix: Plexivity import if IP address is missing.
* Fix: Tooltips shown behind the datatable headers.
* Fix: Current activity instances rendered in a random order causing them to jump around.
## v1.4.3 (2016-05-22)
* Fix: PlexPy not starting without any authentication method.
## v1.4.2 (2016-05-22)
* New: Option to use HTTP basic authentication instead of the HTML login form.
* Fix: Unable to save settings when enabling the HTTP proxy setting.
* Change: Match the PMS port when retrieving the PMS url.
## v1.4.1 (2016-05-20)
* New: HTTP Proxy checkbox in the settings. Enable this if using an SSL enabled reverse proxy in front of PlexPy.
* Fix: Check for blank username/password on login.
* Fix: Persist current activity artwork blur across refreshes when transcoding details are visible.
* Fix: Send notifications to multiple XBMC/Plex Home Theater devices.
* Fix: Reset PMS identifier when clicking verify server button in settings.
* Fix: Crash when trying to group current activity session in database.
* Fix: Check current activity returns sessions when refreshing.
* Fix: Logs sorted out of order.
* Fix: Resolution reported incorrectly in the stream info modal.
* Fix: PlexPy crashing when hashing password in the config file.
* Fix: CherryPy doubling the port number when accessing PlexPy locally with http_proxy enabled.
* Change: Sort by most recent for ties in watch statistics.
* Change: Refresh Join devices when changing the API key.
* Change: Format the Join device IDs.
* Change: Join notifications now sent with Python Requests module.
* Change: Add paging for recently added in the API.
## v1.4.0 (2016-05-15) ## v1.4.0 (2016-05-15)
* New: An HTML form login page with sessions support. * New: An HTML form login page with sessions support.

View File

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

View File

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

View File

@@ -1184,6 +1184,7 @@ a:hover .dashboard-recent-media-cover {
margin: 0 40px 0 25px; margin: 0 40px 0 25px;
height: 100px; height: 100px;
overflow: visible; overflow: visible;
position: relative;
} }
.summary-poster-face { .summary-poster-face {
background-position: center; background-position: center;
@@ -1922,6 +1923,7 @@ a .library-user-instance-box:hover {
.home-platforms-instance-poster { .home-platforms-instance-poster {
margin-left: 0px; margin-left: 0px;
position: absolute; position: absolute;
overflow: hidden;
} }
.home-platforms-instance-poster .home-platforms-poster-face { .home-platforms-instance-poster .home-platforms-poster-face {
background-position: center; background-position: center;
@@ -2079,6 +2081,7 @@ a .library-user-instance-box:hover {
.home-platforms-instance-list-poster { .home-platforms-instance-list-poster {
position: absolute; position: absolute;
left: 20px; left: 20px;
overflow: hidden;
} }
.home-platforms-instance-list-poster .home-platforms-list-poster-face { .home-platforms-instance-list-poster .home-platforms-list-poster-face {
background-position: center; background-position: center;
@@ -2954,4 +2957,51 @@ a.no-highlight:hover {
.datatable-wrap { .datatable-wrap {
min-width: 150px; min-width: 150px;
max-width: 250px; max-width: 250px;
}
.inline-pre {
font-family: monospace;
margin: 0 2px;
padding: 2px 5px;
font-size: 13px;
color: #fff;
background-color: #555;
border: 0px solid #444;
border-radius: 3px;
}
.overlay-refresh-image {
opacity: 0;
color: #000;
font-size: 16px;
float: left;
position: absolute;
top: 0px;
right: 10px;
z-index: 1;
transition: all .1s cubic-bezier(.4,0,1,1);
-webkit-transition: all .1s cubic-bezier(.4,0,1,1);
-moz-transition: all .1s cubic-bezier(.4,0,1,1);
-o-transition: all .1s cubic-bezier(.4,0,1,1);
text-shadow: -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff, 1px 1px 0 #fff;
}
.overlay-refresh-image.left {
left: 10px;
}
.overlay-refresh-image.info-art {
color: #999;
top: 15px;
right: 25px;
opacity: 1;
text-shadow: none;
cursor: pointer;
}
.overlay-refresh-image.info-art:hover {
color: #fff;
text-shadow: none;
}
a:hover .overlay-refresh-image {
opacity: .25;
top: 8px;
}
a:hover .overlay-refresh-image:hover {
opacity: .9;
} }

View File

@@ -106,6 +106,7 @@ DOCUMENTATION :: END
% else: % else:
<div class="dashboard-activity-poster-face" style="background-image: url(${a['art']});"></div> <div class="dashboard-activity-poster-face" style="background-image: url(${a['art']});"></div>
% endif % endif
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
<div class="dashboard-activity-button-info"> <div class="dashboard-activity-button-info">
<button type="button" class="btn btn-activity-info btn-lg" data-target="#stream-${a['session_key']}"> <button type="button" class="btn btn-activity-info btn-lg" data-target="#stream-${a['session_key']}">
<i class="fa fa-info-circle"></i> <i class="fa fa-info-circle"></i>

View File

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

View File

@@ -103,6 +103,7 @@ DOCUMENTATION :: END
% if top_stat['rows'][0]['grandparent_thumb']: % if top_stat['rows'][0]['grandparent_thumb']:
<div class="home-platforms-instance-poster"> <div class="home-platforms-instance-poster">
<div class="home-platforms-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][0]['grandparent_thumb']}&width=300&height=450&fallback=poster);"></div> <div class="home-platforms-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][0]['grandparent_thumb']}&width=300&height=450&fallback=poster);"></div>
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
</div> </div>
% else: % else:
<div class="home-platforms-instance-poster"> <div class="home-platforms-instance-poster">
@@ -149,6 +150,7 @@ DOCUMENTATION :: END
% if top_stat['rows'][loop.index]['grandparent_thumb']: % if top_stat['rows'][loop.index]['grandparent_thumb']:
<div class="home-platforms-instance-list-poster"> <div class="home-platforms-instance-list-poster">
<div class="home-platforms-list-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][loop.index]['grandparent_thumb']}&width=300&height=450&fallback=poster);"></div> <div class="home-platforms-list-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][loop.index]['grandparent_thumb']}&width=300&height=450&fallback=poster);"></div>
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
</div> </div>
% else: % else:
<div class="home-platforms-instance-list-poster"> <div class="home-platforms-instance-list-poster">
@@ -199,6 +201,7 @@ DOCUMENTATION :: END
% if top_stat['rows'][0]['grandparent_thumb'] != '': % if top_stat['rows'][0]['grandparent_thumb'] != '':
<div class="home-platforms-instance-poster"> <div class="home-platforms-instance-poster">
<div class="home-platforms-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][0]['grandparent_thumb']}&width=300&height=450&fallback=poster);"></div> <div class="home-platforms-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][0]['grandparent_thumb']}&width=300&height=450&fallback=poster);"></div>
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
</div> </div>
% else: % else:
<div class="home-platforms-instance-poster"> <div class="home-platforms-instance-poster">
@@ -241,6 +244,7 @@ DOCUMENTATION :: END
% if top_stat['rows'][loop.index]['grandparent_thumb']: % if top_stat['rows'][loop.index]['grandparent_thumb']:
<div class="home-platforms-instance-list-poster"> <div class="home-platforms-instance-list-poster">
<div class="home-platforms-list-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][loop.index]['grandparent_thumb']}&width=300&height=450&fallback=poster);"></div> <div class="home-platforms-list-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][loop.index]['grandparent_thumb']}&width=300&height=450&fallback=poster);"></div>
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
</div> </div>
% else: % else:
<div class="home-platforms-instance-list-poster"> <div class="home-platforms-instance-list-poster">
@@ -295,6 +299,7 @@ DOCUMENTATION :: END
% if top_stat['rows'][0]['thumb']: % if top_stat['rows'][0]['thumb']:
<div class="home-platforms-instance-poster"> <div class="home-platforms-instance-poster">
<div class="home-platforms-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][0]['thumb']}&width=300&height=450&fallback=poster);"></div> <div class="home-platforms-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][0]['thumb']}&width=300&height=450&fallback=poster);"></div>
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
</div> </div>
% else: % else:
<div class="home-platforms-instance-poster"> <div class="home-platforms-instance-poster">
@@ -341,6 +346,7 @@ DOCUMENTATION :: END
% if top_stat['rows'][loop.index]['thumb']: % if top_stat['rows'][loop.index]['thumb']:
<div class="home-platforms-instance-list-poster"> <div class="home-platforms-instance-list-poster">
<div class="home-platforms-list-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][loop.index]['thumb']}&width=300&height=450&fallback=poster);"></div> <div class="home-platforms-list-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][loop.index]['thumb']}&width=300&height=450&fallback=poster);"></div>
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
</div> </div>
% else: % else:
<div class="home-platforms-instance-list-poster"> <div class="home-platforms-instance-list-poster">
@@ -391,6 +397,7 @@ DOCUMENTATION :: END
% if top_stat['rows'][0]['thumb']: % if top_stat['rows'][0]['thumb']:
<div class="home-platforms-instance-poster"> <div class="home-platforms-instance-poster">
<div class="home-platforms-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][0]['thumb']}&width=300&height=450&fallback=poster);"></div> <div class="home-platforms-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][0]['thumb']}&width=300&height=450&fallback=poster);"></div>
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
</div> </div>
% else: % else:
<div class="home-platforms-instance-poster"> <div class="home-platforms-instance-poster">
@@ -433,6 +440,7 @@ DOCUMENTATION :: END
% if top_stat['rows'][loop.index]['thumb']: % if top_stat['rows'][loop.index]['thumb']:
<div class="home-platforms-instance-list-poster"> <div class="home-platforms-instance-list-poster">
<div class="home-platforms-list-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][loop.index]['thumb']}&width=300&height=450&fallback=poster);"></div> <div class="home-platforms-list-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][loop.index]['thumb']}&width=300&height=450&fallback=poster);"></div>
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
</div> </div>
% else: % else:
<div class="home-platforms-instance-list-poster"> <div class="home-platforms-instance-list-poster">
@@ -487,6 +495,7 @@ DOCUMENTATION :: END
% if top_stat['rows'][0]['grandparent_thumb']: % if top_stat['rows'][0]['grandparent_thumb']:
<div class="home-platforms-instance-poster"> <div class="home-platforms-instance-poster">
<div class="home-platforms-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][0]['grandparent_thumb']}&width=300&height=300&fallback=poster);"></div> <div class="home-platforms-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][0]['grandparent_thumb']}&width=300&height=300&fallback=poster);"></div>
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
</div> </div>
% else: % else:
<div class="home-platforms-instance-poster"> <div class="home-platforms-instance-poster">
@@ -533,6 +542,7 @@ DOCUMENTATION :: END
% if top_stat['rows'][loop.index]['grandparent_thumb']: % if top_stat['rows'][loop.index]['grandparent_thumb']:
<div class="home-platforms-instance-list-poster"> <div class="home-platforms-instance-list-poster">
<div class="home-platforms-list-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][loop.index]['grandparent_thumb']}&width=300&height=300&fallback=poster);"></div> <div class="home-platforms-list-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][loop.index]['grandparent_thumb']}&width=300&height=300&fallback=poster);"></div>
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
</div> </div>
% else: % else:
<div class="home-platforms-instance-list-poster"> <div class="home-platforms-instance-list-poster">
@@ -583,6 +593,7 @@ DOCUMENTATION :: END
% if top_stat['rows'][0]['grandparent_thumb'] != '': % if top_stat['rows'][0]['grandparent_thumb'] != '':
<div class="home-platforms-instance-poster"> <div class="home-platforms-instance-poster">
<div class="home-platforms-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][0]['grandparent_thumb']}&width=300&height=300&fallback=poster);"></div> <div class="home-platforms-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][0]['grandparent_thumb']}&width=300&height=300&fallback=poster);"></div>
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
</div> </div>
% else: % else:
<div class="home-platforms-instance-poster"> <div class="home-platforms-instance-poster">
@@ -625,6 +636,7 @@ DOCUMENTATION :: END
% if top_stat['rows'][loop.index]['grandparent_thumb']: % if top_stat['rows'][loop.index]['grandparent_thumb']:
<div class="home-platforms-instance-list-poster"> <div class="home-platforms-instance-list-poster">
<div class="home-platforms-list-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][loop.index]['grandparent_thumb']}&width=300&height=300&fallback=poster);"></div> <div class="home-platforms-list-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][loop.index]['grandparent_thumb']}&width=300&height=300&fallback=poster);"></div>
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
</div> </div>
% else: % else:
<div class="home-platforms-instance-list-poster"> <div class="home-platforms-instance-list-poster">
@@ -847,6 +859,7 @@ DOCUMENTATION :: END
% if top_stat['rows'][0]['thumb']: % if top_stat['rows'][0]['thumb']:
<div class="home-platforms-instance-poster"> <div class="home-platforms-instance-poster">
<div class="home-platforms-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][0]['thumb']}&width=300&height=450&fallback=poster);"></div> <div class="home-platforms-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][0]['thumb']}&width=300&height=450&fallback=poster);"></div>
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
</div> </div>
% else: % else:
<div class="home-platforms-instance-poster"> <div class="home-platforms-instance-poster">
@@ -903,6 +916,7 @@ DOCUMENTATION :: END
% if top_stat['rows'][loop.index]['thumb']: % if top_stat['rows'][loop.index]['thumb']:
<div class="home-platforms-instance-list-poster"> <div class="home-platforms-instance-list-poster">
<div class="home-platforms-list-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][loop.index]['thumb']}&width=300&height=450&fallback=poster);"></div> <div class="home-platforms-list-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][loop.index]['thumb']}&width=300&height=450&fallback=poster);"></div>
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
</div> </div>
% else: % else:
<div class="home-platforms-instance-list-poster"> <div class="home-platforms-instance-list-poster">

View File

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

View File

@@ -68,6 +68,7 @@ DOCUMENTATION :: END
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="art-face" style="background-image:url(pms_image_proxy?img=${data['art']}&width=1920&height=1080)"></div> <div class="art-face" style="background-image:url(pms_image_proxy?img=${data['art']}&width=1920&height=1080)"></div>
<span class="overlay-refresh-image info-art" title="Refresh background image"><i class="fa fa-refresh refresh_pms_image"></i></span>
<div class="summary-container"> <div class="summary-container">
<div class="summary-navbar"> <div class="summary-navbar">
<div class="col-md-12"> <div class="col-md-12">
@@ -119,18 +120,21 @@ DOCUMENTATION :: END
<span></span> <span></span>
</div> </div>
</div> </div>
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% elif data['media_type'] == 'artist' or data['media_type'] == 'album' or data['media_type'] == 'track': % elif data['media_type'] == 'artist' or data['media_type'] == 'album' or data['media_type'] == 'track':
<div class="summary-poster-face-track" style="background-image: url(pms_image_proxy?img=${data['thumb']}&width=500&height=500&fallback=cover);"> <div class="summary-poster-face-track" style="background-image: url(pms_image_proxy?img=${data['thumb']}&width=500&height=500&fallback=cover);">
<div class="summary-poster-face-overlay"> <div class="summary-poster-face-overlay">
<span></span> <span></span>
</div> </div>
</div> </div>
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% else: % else:
<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" style="background-image: url(pms_image_proxy?img=${data['thumb']}&width=300&height=450&fallback=poster);">
<div class="summary-poster-face-overlay"> <div class="summary-poster-face-overlay">
<span></span> <span></span>
</div> </div>
</div> </div>
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif % endif
</a> </a>
</div> </div>

View File

@@ -51,6 +51,7 @@ DOCUMENTATION :: END
</div> </div>
</div> </div>
</div> </div>
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
</div> </div>
</a> </a>
% elif data['children_type'] == 'episode': % elif data['children_type'] == 'episode':
@@ -63,6 +64,7 @@ DOCUMENTATION :: END
</div> </div>
</div> </div>
</div> </div>
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
</div> </div>
</a> </a>
<div class="item-children-instance-text-wrapper episode-item"> <div class="item-children-instance-text-wrapper episode-item">
@@ -74,6 +76,7 @@ DOCUMENTATION :: END
<a href="info?rating_key=${child['rating_key']}" title="${child['title']}"> <a href="info?rating_key=${child['rating_key']}" title="${child['title']}">
<div class="item-children-poster"> <div class="item-children-poster">
<div class="item-children-poster-face album-poster" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=300&fallback=cover);"></div> <div class="item-children-poster-face album-poster" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=300&fallback=cover);"></div>
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
</div> </div>
</a> </a>
<div class="item-children-instance-text-wrapper album-item"> <div class="item-children-instance-text-wrapper album-item">

View File

@@ -65,6 +65,7 @@ DOCUMENTATION :: END
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}"> <a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
<div class="item-children-poster"> <div class="item-children-poster">
<div class="item-children-poster-face season-poster" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);"></div> <div class="item-children-poster-face season-poster" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);"></div>
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
</div> </div>
<div class="item-children-instance-text-wrapper season-item"> <div class="item-children-instance-text-wrapper season-item">
<h3 title="${child['title']}">${child['title']}</h3> <h3 title="${child['title']}">${child['title']}</h3>
@@ -87,6 +88,7 @@ DOCUMENTATION :: END
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}"> <a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
<div class="item-children-poster"> <div class="item-children-poster">
<div class="item-children-poster-face season-poster" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);"></div> <div class="item-children-poster-face season-poster" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);"></div>
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
</div> </div>
<div class="item-children-instance-text-wrapper season-item"> <div class="item-children-instance-text-wrapper season-item">
<h3 title="${child['title']}">${child['title']}</h3> <h3 title="${child['title']}">${child['title']}</h3>
@@ -109,6 +111,7 @@ DOCUMENTATION :: END
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}"> <a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
<div class="item-children-poster"> <div class="item-children-poster">
<div class="item-children-poster-face season-poster" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);"></div> <div class="item-children-poster-face season-poster" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);"></div>
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
</div> </div>
<div class="item-children-instance-text-wrapper season-item"> <div class="item-children-instance-text-wrapper season-item">
<h3 title="${child['parent_title']}">${child['parent_title']}</h3> <h3 title="${child['parent_title']}">${child['parent_title']}</h3>
@@ -131,6 +134,7 @@ DOCUMENTATION :: END
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}"> <a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
<div class="item-children-poster"> <div class="item-children-poster">
<div class="item-children-poster-face episode-poster" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=500&height=250&fallback=art);"></div> <div class="item-children-poster-face episode-poster" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=500&height=250&fallback=art);"></div>
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
</div> </div>
<div class="item-children-instance-text-wrapper episode-item"> <div class="item-children-instance-text-wrapper episode-item">
<h3 title="${child['grandparent_title']}">${child['grandparent_title']}</h3> <h3 title="${child['grandparent_title']}">${child['grandparent_title']}</h3>
@@ -154,6 +158,7 @@ DOCUMENTATION :: END
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}"> <a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
<div class="item-children-poster"> <div class="item-children-poster">
<div class="item-children-poster-face album-poster" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=300&fallback=cover);"></div> <div class="item-children-poster-face album-poster" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=300&fallback=cover);"></div>
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
</div> </div>
<div class="item-children-instance-text-wrapper album-item"> <div class="item-children-instance-text-wrapper album-item">
<h3 title="${child['title']}">${child['title']}</h3> <h3 title="${child['title']}">${child['title']}</h3>
@@ -175,6 +180,7 @@ DOCUMENTATION :: END
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}"> <a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
<div class="item-children-poster"> <div class="item-children-poster">
<div class="item-children-poster-face album-poster" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=300&fallback=cover);"></div> <div class="item-children-poster-face album-poster" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=300&fallback=cover);"></div>
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
</div> </div>
<div class="item-children-instance-text-wrapper album-item"> <div class="item-children-instance-text-wrapper album-item">
<h3 title="${child['parent_title']}">${child['parent_title']}</h3> <h3 title="${child['parent_title']}">${child['parent_title']}</h3>
@@ -204,6 +210,7 @@ DOCUMENTATION :: END
</div> </div>
</div> </div>
</div> </div>
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
<div class="item-children-instance-text-wrapper album-item"> <div class="item-children-instance-text-wrapper album-item">
<h3 title="${child['grandparent_title']}">${child['grandparent_title']}</h3> <h3 title="${child['grandparent_title']}">${child['grandparent_title']}</h3>
<h3 title="${child['title']}">${child['title']}</h3> <h3 title="${child['title']}">${child['title']}</h3>

View File

@@ -399,4 +399,27 @@ window.onerror = function (message, file, line) {
'line': line 'line': line
}; };
$.post("log_js_errors", e, function (data) { }); $.post("log_js_errors", e, function (data) { });
}; };
$('*').on('click', '.refresh_pms_image', function (e) {
e.preventDefault();
e.stopPropagation();
var background_div = $(this).parent().siblings(['style*=pms_image_proxy']).first();
var pms_proxy_url = background_div.css('background-image');
pms_proxy_url = /^url\((['"]?)(.*)\1\)$/.exec(pms_proxy_url);
pms_proxy_url = pms_proxy_url ? pms_proxy_url[2] : ""; // If matched, retrieve url, otherwise ""
if (pms_proxy_url.indexOf('pms_image_proxy') == -1) {
console.log('PMS image proxy url not found.');
} else {
if (pms_proxy_url.indexOf('refresh=true') > -1) {
pms_proxy_url = pms_proxy_url.replace("&refresh=true", "");
console.log(pms_proxy_url)
background_div.css('background-image', 'url(' + pms_proxy_url + ')');
background_div.css('background-image', 'url(' + pms_proxy_url + '&refresh=true)');
} else {
background_div.css('background-image', 'url(' + pms_proxy_url + '&refresh=true)');
}
}
});

View File

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

View File

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

View File

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

View File

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

View File

@@ -39,6 +39,7 @@ DOCUMENTATION :: END
<div class="row"> <div class="row">
% if data['library_art']: % if data['library_art']:
<div class="art-face" style="background-image:url(pms_image_proxy?img=${data['library_art']}&width=1920&height=1080)"></div> <div class="art-face" style="background-image:url(pms_image_proxy?img=${data['library_art']}&width=1920&height=1080)"></div>
<span class="overlay-refresh-image info-art" title="Refresh background image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif % endif
<div class="summary-container"> <div class="summary-container">
<div class="summary-navbar"> <div class="summary-navbar">
@@ -362,7 +363,7 @@ DOCUMENTATION :: END
// Populate watch time stats // Populate watch time stats
$.ajax({ $.ajax({
url: 'get_library_watch_time_stats', url: 'library_watch_time_stats',
async: true, async: true,
data: { section_id: section_id }, data: { section_id: section_id },
complete: function(xhr, status) { complete: function(xhr, status) {
@@ -372,7 +373,7 @@ DOCUMENTATION :: END
// Populate user stats // Populate user stats
$.ajax({ $.ajax({
url: 'get_library_user_stats', url: 'library_user_stats',
async: true, async: true,
data: { section_id: section_id }, data: { section_id: section_id },
complete: function(xhr, status) { complete: function(xhr, status) {
@@ -498,7 +499,7 @@ DOCUMENTATION :: END
function recentlyWatched() { function recentlyWatched() {
// Populate recently watched // Populate recently watched
$.ajax({ $.ajax({
url: 'get_library_recently_watched', url: 'library_recently_watched',
async: true, async: true,
data: { data: {
section_id: section_id, section_id: section_id,
@@ -514,7 +515,7 @@ DOCUMENTATION :: END
function recentlyAdded() { function recentlyAdded() {
// Populate recently added // Populate recently added
$.ajax({ $.ajax({
url: 'get_library_recently_added', url: 'library_recently_added',
async: true, async: true,
data: { data: {
section_id: section_id, section_id: section_id,

View File

@@ -60,6 +60,7 @@ DOCUMENTATION :: END
</div> </div>
</div> </div>
</div> </div>
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
</div> </div>
</a> </a>
<div class="dashboard-recent-media-metacontainer"> <div class="dashboard-recent-media-metacontainer">

View File

@@ -21,7 +21,33 @@
<span><i class="fa fa-list-alt"></i> Logs</span> <span><i class="fa fa-list-alt"></i> Logs</span>
</div> </div>
<div class="button-bar"> <div class="button-bar">
<button class="btn btn-dark" id="download-plexpylog"><i class="fa fa-download"></i> Download log</button> <div class="btn-group" id="plexpy-log-levels">
<label>
<select name="plexpy-log-level-filter" id="plexpy-log-level-filter" class="btn" style="color: inherit;">
<option value="">All log levels</option>
<option disabled>&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;</option>
<option value="DEBUG">Debug</option>
<option value="INFO">Info</option>
<option value="WARN">Warning</option>
<option value="ERROR">Error</option>
</select>
</label>
</div>
<div class="btn-group" id="plex-log-levels" style="display: none;">
<label>
<select name="plex-log-level-filter" id="plex-log-level-filter" class="btn" style="color: inherit;">
<option value="">All log levels</option>
<option disabled>&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;</option>
<option value="DEBUG">Debug</option>
<option value="INFO">Info</option>
<option value="WARN">Warning</option>
<option value="ERROR">Error</option>
</select>
</label>
</div>
<button class="btn btn-dark" id="download-plexpylog"><i class="fa fa-download"></i> Download logs</button>
<button class="btn btn-dark" id="download-plexserverlog" style="display: none;"><i class="fa fa-download"></i> Download logs</button>
<button class="btn btn-dark" id="download-plexscannerlog" style="display: none;"><i class="fa fa-download"></i> Download logs</button>
<button class="btn btn-dark" id="clear-logs"><i class="fa fa-trash-o"></i> Clear logs</button> <button class="btn btn-dark" id="clear-logs"><i class="fa fa-trash-o"></i> Clear logs</button>
<button class="btn btn-dark" id="clear-notify-logs" style="display: none;"><i class="fa fa-trash-o"></i> Clear logs</button> <button class="btn btn-dark" id="clear-notify-logs" style="display: none;"><i class="fa fa-trash-o"></i> Clear logs</button>
<button class="btn btn-dark" id="clear-login-logs" style="display: none;"><i class="fa fa-trash-o"></i> Clear logs</button> <button class="btn btn-dark" id="clear-login-logs" style="display: none;"><i class="fa fa-trash-o"></i> Clear logs</button>
@@ -40,27 +66,25 @@
<div role="tabpanel" class="tab-pane active" id="tabs-1"> <div role="tabpanel" class="tab-pane active" id="tabs-1">
<table class="display" id="log_table" width="100%"> <table class="display" id="log_table" width="100%">
<thead> <thead>
<tr> <tr>
<th class="min-tablet" align="left" id="timestamp">Timestamp</th> <th class="min-tablet" align="left" id="timestamp">Timestamp</th>
<th class="desktop" align="left" id="level">Level</th> <th class="desktop" align="left" id="level">Level</th>
<th class="all" align="left" id="message">Message</th> <th class="all" align="left" id="message">Message</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody></tbody>
</tbody>
</table> </table>
</div> </div>
<div role="tabpanel" class="tab-pane" id="tabs-2"> <div role="tabpanel" class="tab-pane" id="tabs-2">
<table class="display" id="plex_log_table" width="100%"> <table class="display" id="plex_log_table" width="100%">
<thead> <thead>
<tr> <tr>
<th align="left" id="plex_timestamp">Timestamp</th> <th align="left" id="plex_timestamp">Timestamp</th>
<th align="left" id="plex_level">Level</th> <th align="left" id="plex_level">Level</th>
<th align="left" id="plex_message">Message</th> <th align="left" id="plex_message">Message</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody></tbody>
</tbody>
</table> </table>
</div> </div>
<div role="tabpanel" class="tab-pane" id="tabs-3"> <div role="tabpanel" class="tab-pane" id="tabs-3">
@@ -114,7 +138,8 @@
</div> </div>
<br> <br>
<div align="center">Refresh rate: <div align="center">
Refresh rate:
<select id="refreshrate" onchange="setRefresh()"> <select id="refreshrate" onchange="setRefresh()">
<option value="0" selected="selected">No Refresh</option> <option value="0" selected="selected">No Refresh</option>
<option value="5">5 Seconds</option> <option value="5">5 Seconds</option>
@@ -139,21 +164,62 @@
<script> <script>
$(document).ready(function() { $(document).ready(function() {
loadPlexPyLogs(); loadPlexPyLogs(selected_log_level);
clearSearchButton('log_table', log_table); clearSearchButton('log_table', log_table);
}); });
function loadPlexPyLogs() { var log_levels = ['DEBUG', 'INFO', 'WARN', 'ERROR'];
function bindLogLevelFilter() {
clearLogLevelFilter();
var log_level_column = this.api().column(1);
var select = $('#plex-log-level-filter');
select.on('change', function () {
var val = $.fn.dataTable.util.escapeRegex(
$(this).val()
);
var search_string = '';
var levelIndex = log_levels.indexOf(val);
if (levelIndex >= 0) {
search_string = '^' + log_levels
.slice(levelIndex)
.join('|') + '$';
}
log_level_column
.search(search_string, true, false)
.draw();
}).change();
}
function clearLogLevelFilter() {
$('#plex-log-level-filter').off('change');
}
var selected_log_level = null;
function loadPlexPyLogs(selected_log_level) {
log_table_options.ajax = { log_table_options.ajax = {
url: "getLog" url: "getLog",
type: 'post',
data: function (d) {
return {
json_data: JSON.stringify(d),
log_level: selected_log_level
};
}
} }
log_table = $('#log_table').DataTable(log_table_options); log_table = $('#log_table').DataTable(log_table_options);
$('#plexpy-log-level-filter').on('change', function () {
selected_log_level = $(this).val() || null;
log_table.draw();
});
} }
function loadPlexLogs() { function loadPlexLogs() {
plex_log_table_options.ajax = { plex_log_table_options.ajax = {
url: "get_plex_log?log_type=server" url: "get_plex_log?log_type=server"
} }
plex_log_table_options.initComplete = bindLogLevelFilter;
plex_log_table = $('#plex_log_table').DataTable(plex_log_table_options); plex_log_table = $('#plex_log_table').DataTable(plex_log_table_options);
} }
@@ -161,6 +227,7 @@
plex_log_table_options.ajax = { plex_log_table_options.ajax = {
url: "get_plex_log?log_type=scanner" url: "get_plex_log?log_type=scanner"
} }
plex_log_table_options.initComplete = bindLogLevelFilter;
plex_scanner_log_table = $('#plex_scanner_log_table').DataTable(plex_log_table_options); plex_scanner_log_table = $('#plex_scanner_log_table').DataTable(plex_log_table_options);
} }
@@ -190,17 +257,25 @@
} }
$("#plexpy-logs-btn").click(function () { $("#plexpy-logs-btn").click(function () {
$("#plexpy-log-levels").show();
$("#plex-log-levels").hide();
$("#clear-logs").show(); $("#clear-logs").show();
$("#download-plexpylog").show() $("#download-plexpylog").show()
$("#download-plexserverlog").hide()
$("#download-plexscannerlog").hide()
$("#clear-notify-logs").hide(); $("#clear-notify-logs").hide();
$("#clear-login-logs").hide(); $("#clear-login-logs").hide();
loadPlexPyLogs(); loadPlexPyLogs(selected_log_level);
clearSearchButton('log_table', log_table); clearSearchButton('log_table', log_table);
}); });
$("#plex-logs-btn").click(function () { $("#plex-logs-btn").click(function () {
$("#plexpy-log-levels").hide();
$("#plex-log-levels").show();
$("#clear-logs").hide(); $("#clear-logs").hide();
$("#download-plexpylog").hide() $("#download-plexpylog").hide()
$("#download-plexserverlog").show()
$("#download-plexscannerlog").hide()
$("#clear-notify-logs").hide(); $("#clear-notify-logs").hide();
$("#clear-login-logs").hide(); $("#clear-login-logs").hide();
loadPlexLogs(); loadPlexLogs();
@@ -208,8 +283,12 @@
}); });
$("#plex-scanner-logs-btn").click(function () { $("#plex-scanner-logs-btn").click(function () {
$("#plexpy-log-levels").hide();
$("#plex-log-levels").show();
$("#clear-logs").hide(); $("#clear-logs").hide();
$("#download-plexpylog").hide() $("#download-plexpylog").hide()
$("#download-plexserverlog").hide()
$("#download-plexscannerlog").show()
$("#clear-notify-logs").hide(); $("#clear-notify-logs").hide();
$("#clear-login-logs").hide(); $("#clear-login-logs").hide();
loadPlexScannerLogs(); loadPlexScannerLogs();
@@ -217,8 +296,12 @@
}); });
$("#notification-logs-btn").click(function () { $("#notification-logs-btn").click(function () {
$("#plexpy-log-levels").hide();
$("#plex-log-levels").hide();
$("#clear-logs").hide(); $("#clear-logs").hide();
$("#download-plexpylog").hide() $("#download-plexpylog").hide()
$("#download-plexserverlog").hide()
$("#download-plexscannerlog").hide()
$("#clear-notify-logs").show(); $("#clear-notify-logs").show();
$("#clear-login-logs").hide(); $("#clear-login-logs").hide();
loadNotificationLogs(); loadNotificationLogs();
@@ -226,8 +309,12 @@
}); });
$("#login-logs-btn").click(function () { $("#login-logs-btn").click(function () {
$("#plexpy-log-levels").hide();
$("#plex-log-levels").hide();
$("#clear-logs").hide(); $("#clear-logs").hide();
$("#download-plexpylog").hide() $("#download-plexpylog").hide()
$("#download-plexserverlog").hide()
$("#download-plexscannerlog").hide()
$("#clear-notify-logs").hide(); $("#clear-notify-logs").hide();
$("#clear-login-logs").show(); $("#clear-login-logs").show();
loadLoginLogs(); loadLoginLogs();
@@ -263,6 +350,13 @@
window.location.href = "download_log"; window.location.href = "download_log";
}); });
$("#download-plexserverlog").click(function () {
window.location.href = "download_plex_log?log_type=server";
});
$("#download-plexscannerlog").click(function () {
window.location.href = "download_plex_log?log_type=scanner";
});
$("#clear-notify-logs").click(function () { $("#clear-notify-logs").click(function () {
$("#confirm-message").text("Are you sure you want to clear the PlexPy notification logs?"); $("#confirm-message").text("Are you sure you want to clear the PlexPy notification logs?");

View File

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

View File

@@ -49,6 +49,7 @@ DOCUMENTATION :: END
</div> </div>
</div> </div>
</div> </div>
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
</div> </div>
</a> </a>
<div class="dashboard-recent-media-metacontainer"> <div class="dashboard-recent-media-metacontainer">
@@ -69,6 +70,7 @@ DOCUMENTATION :: END
</div> </div>
</div> </div>
</div> </div>
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
</div> </div>
</a> </a>
<div class="dashboard-recent-media-metacontainer"> <div class="dashboard-recent-media-metacontainer">
@@ -91,6 +93,7 @@ DOCUMENTATION :: END
</div> </div>
</div> </div>
</div> </div>
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
</div> </div>
</a> </a>
<div class="dashboard-recent-media-metacontainer"> <div class="dashboard-recent-media-metacontainer">
@@ -109,6 +112,5 @@ DOCUMENTATION :: END
</div> </div>
</div> </div>
% else: % else:
<div class="text-muted">There was an error communicating with your Plex Server. Please check your <a href="settings">settings</a>. <div class="text-muted">There was an error communicating with your Plex Server.</div><br>
</div><br>
% endif % endif

View File

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

View File

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

View File

@@ -383,7 +383,7 @@ DOCUMENTATION :: END
// Populate watch time stats // Populate watch time stats
$.ajax({ $.ajax({
url: 'get_user_watch_time_stats', url: 'user_watch_time_stats',
async: true, async: true,
data: { user_id: user_id, user: username }, data: { user_id: user_id, user: username },
complete: function(xhr, status) { complete: function(xhr, status) {
@@ -393,7 +393,7 @@ DOCUMENTATION :: END
// Populate platform stats // Populate platform stats
$.ajax({ $.ajax({
url: 'get_user_player_stats', url: 'user_player_stats',
async: true, async: true,
data: { user_id: user_id, user: username }, data: { user_id: user_id, user: username },
complete: function(xhr, status) { complete: function(xhr, status) {

View File

@@ -49,6 +49,7 @@ DOCUMENTATION :: END
</div> </div>
</div> </div>
</div> </div>
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
</div> </div>
</a> </a>
<div class="dashboard-recent-media-metacontainer"> <div class="dashboard-recent-media-metacontainer">

View File

@@ -14,7 +14,7 @@
# default. Do not sets it as empty or it will run # default. Do not sets it as empty or it will run
# as root. # as root.
# plexpy_dir: Directory where PlexPy lives. # plexpy_dir: Directory where PlexPy lives.
# Default: /usr/local/plexpy # Default: /usr/local/share/plexpy
# plexpy_chdir: Change to this directory before running PlexPy. # plexpy_chdir: Change to this directory before running PlexPy.
# Default is same as plexpy_dir. # Default is same as plexpy_dir.
# plexpy_pid: The name of the pidfile to create. # plexpy_pid: The name of the pidfile to create.
@@ -30,7 +30,7 @@ load_rc_config ${name}
: ${plexpy_enable:="NO"} : ${plexpy_enable:="NO"}
: ${plexpy_user:="_sabnzbd"} : ${plexpy_user:="_sabnzbd"}
: ${plexpy_dir:="/usr/local/plexpy"} : ${plexpy_dir:="/usr/local/share/plexpy"}
: ${plexpy_chdir:="${plexpy_dir}"} : ${plexpy_chdir:="${plexpy_dir}"}
: ${plexpy_pid:="${plexpy_dir}/plexpy.pid"} : ${plexpy_pid:="${plexpy_dir}/plexpy.pid"}
: ${plexpy_flags:=""} : ${plexpy_flags:=""}

View File

@@ -14,7 +14,7 @@
# default. Do not sets it as empty or it will run # default. Do not sets it as empty or it will run
# as root. # as root.
# plexpy_dir: Directory where PlexPy lives. # plexpy_dir: Directory where PlexPy lives.
# Default: /usr/local/plexpy # Default: /usr/local/share/plexpy
# plexpy_chdir: Change to this directory before running PlexPy. # plexpy_chdir: Change to this directory before running PlexPy.
# Default is same as plexpy_dir. # Default is same as plexpy_dir.
# plexpy_pid: The name of the pidfile to create. # plexpy_pid: The name of the pidfile to create.

View File

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

View File

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

View File

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

View File

@@ -48,9 +48,10 @@ class ActivityHandler(object):
pms_connect = pmsconnect.PmsConnect() pms_connect = pmsconnect.PmsConnect()
session_list = pms_connect.get_current_activity() session_list = pms_connect.get_current_activity()
for session in session_list['sessions']: if session_list:
if int(session['session_key']) == self.get_session_key(): for session in session_list['sessions']:
return session if int(session['session_key']) == self.get_session_key():
return session
return None return None

View File

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

View File

@@ -46,6 +46,7 @@ _CONFIG_DEFINITIONS = {
'PMS_IP': (str, 'PMS', '127.0.0.1'), 'PMS_IP': (str, 'PMS', '127.0.0.1'),
'PMS_IS_REMOTE': (int, 'PMS', 0), 'PMS_IS_REMOTE': (int, 'PMS', 0),
'PMS_LOGS_FOLDER': (str, 'PMS', ''), 'PMS_LOGS_FOLDER': (str, 'PMS', ''),
'PMS_LOGS_LINE_CAP': (int, 'PMS', 1000),
'PMS_NAME': (unicode, 'PMS', ''), 'PMS_NAME': (unicode, 'PMS', ''),
'PMS_PORT': (int, 'PMS', 32400), 'PMS_PORT': (int, 'PMS', 32400),
'PMS_TOKEN': (str, 'PMS', ''), 'PMS_TOKEN': (str, 'PMS', ''),
@@ -185,6 +186,7 @@ _CONFIG_DEFINITIONS = {
'HTTPS_KEY': (str, 'General', ''), 'HTTPS_KEY': (str, 'General', ''),
'HTTPS_DOMAIN': (str, 'General', 'localhost'), 'HTTPS_DOMAIN': (str, 'General', 'localhost'),
'HTTPS_IP': (str, 'General', '127.0.0.1'), 'HTTPS_IP': (str, 'General', '127.0.0.1'),
'HTTP_BASIC_AUTH': (int, 'General', 0),
'HTTP_ENVIRONMENT': (str, 'General', 'production'), 'HTTP_ENVIRONMENT': (str, 'General', 'production'),
'HTTP_HASH_PASSWORD': (int, 'General', 0), 'HTTP_HASH_PASSWORD': (int, 'General', 0),
'HTTP_HASHED_PASSWORD': (int, 'General', 0), 'HTTP_HASHED_PASSWORD': (int, 'General', 0),

View File

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

View File

@@ -727,6 +727,16 @@ def build_notify_text(session=None, timeline=None, notify_action=None, agent_id=
'track_num': metadata['media_index'].zfill(1), 'track_num': metadata['media_index'].zfill(1),
'track_num00': metadata['media_index'].zfill(2), 'track_num00': metadata['media_index'].zfill(2),
'year': metadata['year'], 'year': metadata['year'],
'release_date': arrow.get(metadata['originally_available_at']).format(date_format)
if metadata['originally_available_at'] else '',
'air_date': arrow.get(metadata['originally_available_at']).format(date_format)
if metadata['originally_available_at'] else '',
'added_date': arrow.get(metadata['added_at']).format(date_format)
if metadata['added_at'] else '',
'updated_date': arrow.get(metadata['updated_at']).format(date_format)
if metadata['updated_at'] else '',
'last_viewed_date': arrow.get(metadata['last_viewed_at']).format(date_format)
if metadata['last_viewed_at'] else '',
'studio': metadata['studio'], 'studio': metadata['studio'],
'content_rating': metadata['content_rating'], 'content_rating': metadata['content_rating'],
'directors': ', '.join(metadata['directors']), 'directors': ', '.join(metadata['directors']),

View File

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

View File

@@ -100,6 +100,7 @@ def extract_plexivity_xml(xml=None):
video_resolution = helpers.get_xml_attr(c, 'videoResolution') video_resolution = helpers.get_xml_attr(c, 'videoResolution')
width = helpers.get_xml_attr(c, 'width') width = helpers.get_xml_attr(c, 'width')
ip_address = ''
machine_id = '' machine_id = ''
platform = '' platform = ''
player = '' player = ''
@@ -107,7 +108,7 @@ def extract_plexivity_xml(xml=None):
if a.getElementsByTagName('Player'): if a.getElementsByTagName('Player'):
player_elem = a.getElementsByTagName('Player') player_elem = a.getElementsByTagName('Player')
for d in player_elem: for d in player_elem:
ip_address = helpers.get_xml_attr(d, 'address') ip_address = helpers.get_xml_attr(d, 'address').split('::ffff:')[-1]
machine_id = helpers.get_xml_attr(d, 'machineIdentifier') machine_id = helpers.get_xml_attr(d, 'machineIdentifier')
platform = helpers.get_xml_attr(d, 'platform') platform = helpers.get_xml_attr(d, 'platform')
player = helpers.get_xml_attr(d, 'title') player = helpers.get_xml_attr(d, 'title')

View File

@@ -44,7 +44,11 @@ def refresh_users():
if user_tokens and user_tokens['server_token']: if user_tokens and user_tokens['server_token']:
pms_connect = pmsconnect.PmsConnect(token=user_tokens['server_token']) pms_connect = pmsconnect.PmsConnect(token=user_tokens['server_token'])
library_details = pms_connect.get_server_children() library_details = pms_connect.get_server_children()
shared_libraries = ';'.join(d['section_id'] for d in library_details['libraries_list'])
if library_details:
shared_libraries = ';'.join(d['section_id'] for d in library_details['libraries_list'])
else:
shared_libraries = ''
control_value_dict = {"user_id": item['user_id']} control_value_dict = {"user_id": item['user_id']}
new_value_dict = {"username": item['username'], new_value_dict = {"username": item['username'],
@@ -108,7 +112,8 @@ def get_real_pms_url():
if connections: if connections:
# Get connection with matching address, otherwise return first connection # Get connection with matching address, otherwise return first connection
conn = next((c for c in connections if c['address'] == plexpy.CONFIG.PMS_IP), connections[0]) conn = next((c for c in connections if c['address'] == plexpy.CONFIG.PMS_IP
and c['port'] == str(plexpy.CONFIG.PMS_PORT)), connections[0])
plexpy.CONFIG.__setattr__('PMS_URL', conn['uri']) plexpy.CONFIG.__setattr__('PMS_URL', conn['uri'])
plexpy.CONFIG.write() plexpy.CONFIG.write()
logger.info(u"PlexPy PlexTV :: Server URL retrieved.") logger.info(u"PlexPy PlexTV :: Server URL retrieved.")
@@ -142,13 +147,15 @@ class PlexTV(object):
if session.get_session_user_id(): if session.get_session_user_id():
user_data = users.Users() user_data = users.Users()
user_tokens = user_data.get_tokens(user_id=session.get_session_user_id()) user_tokens = user_data.get_tokens(user_id=session.get_session_user_id())
token = user_tokens['server_token'] self.token = user_tokens['server_token']
else: else:
token = plexpy.CONFIG.PMS_TOKEN self.token = plexpy.CONFIG.PMS_TOKEN
else:
self.token = token
self.request_handler = http_handler.HTTPHandler(host='plex.tv', self.request_handler = http_handler.HTTPHandler(host='plex.tv',
port=443, port=443,
token=token, token=self.token,
ssl_verify=self.ssl_verify) ssl_verify=self.ssl_verify)
def get_plex_auth(self, output_format='raw'): def get_plex_auth(self, output_format='raw'):

View File

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

View File

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

View File

@@ -86,7 +86,7 @@ def run():
# successfully received data, reset reconnects counter # successfully received data, reset reconnects counter
reconnects = 0 reconnects = 0
except websocket.WebSocketConnectionClosedException: except (websocket.WebSocketConnectionClosedException, Exception):
if reconnects <= 15: if reconnects <= 15:
reconnects += 1 reconnects += 1
@@ -94,7 +94,7 @@ def run():
if reconnects > 1: if reconnects > 1:
time.sleep(5) time.sleep(5)
logger.warn(u"PlexPy WebSocket :: Connection has closed, reconnecting...") logger.warn(u"PlexPy WebSocket :: Connection has closed, reconnection attempt %s." % reconnects)
try: try:
ws = create_connection(uri, header=header) ws = create_connection(uri, header=header)
except IOError as e: except IOError as e:

View File

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

View File

@@ -34,6 +34,7 @@ import config
import database import database
import datafactory import datafactory
import graphs import graphs
import helpers
import http_handler import http_handler
import libraries import libraries
import log_reader import log_reader
@@ -82,9 +83,9 @@ class WebInterface(object):
@requireAuth() @requireAuth()
def index(self): def index(self):
if plexpy.CONFIG.FIRST_RUN_COMPLETE: if plexpy.CONFIG.FIRST_RUN_COMPLETE:
raise cherrypy.HTTPRedirect("home") raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT + "home")
else: else:
raise cherrypy.HTTPRedirect("welcome") raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT + "welcome")
##### Welcome ##### ##### Welcome #####
@@ -118,7 +119,7 @@ class WebInterface(object):
# The setup wizard just refreshes the page on submit so we must redirect to home if config set. # The setup wizard just refreshes the page on submit so we must redirect to home if config set.
if plexpy.CONFIG.FIRST_RUN_COMPLETE: if plexpy.CONFIG.FIRST_RUN_COMPLETE:
plexpy.initialize_scheduler() plexpy.initialize_scheduler()
raise cherrypy.HTTPRedirect("home") raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT + "home")
else: else:
return serve_template(templatename="welcome.html", title="Welcome", config=config) return serve_template(templatename="welcome.html", title="Welcome", config=config)
@@ -477,9 +478,9 @@ class WebInterface(object):
"get_file_sizes_hold": plexpy.CONFIG.GET_FILE_SIZES_HOLD "get_file_sizes_hold": plexpy.CONFIG.GET_FILE_SIZES_HOLD
} }
library_data = libraries.Libraries()
if section_id: if section_id:
try: try:
library_data = libraries.Libraries()
library_details = library_data.get_details(section_id=section_id) library_details = library_data.get_details(section_id=section_id)
except: except:
logger.warn(u"Unable to retrieve library details for section_id %s " % section_id) logger.warn(u"Unable to retrieve library details for section_id %s " % section_id)
@@ -493,8 +494,8 @@ class WebInterface(object):
@cherrypy.expose @cherrypy.expose
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
def edit_library_dialog(self, section_id=None, **kwargs): def edit_library_dialog(self, section_id=None, **kwargs):
library_data = libraries.Libraries()
if section_id: if section_id:
library_data = libraries.Libraries()
result = library_data.get_details(section_id=section_id) result = library_data.get_details(section_id=section_id)
status_message = '' status_message = ''
else: else:
@@ -528,9 +529,9 @@ class WebInterface(object):
do_notify_created = kwargs.get('do_notify_created', 0) do_notify_created = kwargs.get('do_notify_created', 0)
keep_history = kwargs.get('keep_history', 0) keep_history = kwargs.get('keep_history', 0)
library_data = libraries.Libraries()
if section_id: if section_id:
try: try:
library_data = libraries.Libraries()
library_data.set_config(section_id=section_id, library_data.set_config(section_id=section_id,
custom_thumb=custom_thumb, custom_thumb=custom_thumb,
do_notify=do_notify, do_notify=do_notify,
@@ -543,7 +544,7 @@ class WebInterface(object):
@cherrypy.expose @cherrypy.expose
@requireAuth() @requireAuth()
def get_library_watch_time_stats(self, section_id=None, **kwargs): def library_watch_time_stats(self, section_id=None, **kwargs):
if not allow_session_library(section_id): if not allow_session_library(section_id):
return serve_template(templatename="user_watch_time_stats.html", data=None, title="Watch Stats") return serve_template(templatename="user_watch_time_stats.html", data=None, title="Watch Stats")
@@ -556,12 +557,12 @@ class WebInterface(object):
if result: if result:
return serve_template(templatename="user_watch_time_stats.html", data=result, title="Watch Stats") return serve_template(templatename="user_watch_time_stats.html", data=result, title="Watch Stats")
else: else:
logger.warn(u"Unable to retrieve data for get_library_watch_time_stats.") logger.warn(u"Unable to retrieve data for library_watch_time_stats.")
return serve_template(templatename="user_watch_time_stats.html", data=None, title="Watch Stats") return serve_template(templatename="user_watch_time_stats.html", data=None, title="Watch Stats")
@cherrypy.expose @cherrypy.expose
@requireAuth() @requireAuth()
def get_library_user_stats(self, section_id=None, **kwargs): def library_user_stats(self, section_id=None, **kwargs):
if not allow_session_library(section_id): if not allow_session_library(section_id):
return serve_template(templatename="library_user_stats.html", data=None, title="Player Stats") return serve_template(templatename="library_user_stats.html", data=None, title="Player Stats")
@@ -574,12 +575,12 @@ class WebInterface(object):
if result: if result:
return serve_template(templatename="library_user_stats.html", data=result, title="Player Stats") return serve_template(templatename="library_user_stats.html", data=result, title="Player Stats")
else: else:
logger.warn(u"Unable to retrieve data for get_library_user_stats.") logger.warn(u"Unable to retrieve data for library_user_stats.")
return serve_template(templatename="library_user_stats.html", data=None, title="Player Stats") return serve_template(templatename="library_user_stats.html", data=None, title="Player Stats")
@cherrypy.expose @cherrypy.expose
@requireAuth() @requireAuth()
def get_library_recently_watched(self, section_id=None, limit='10', **kwargs): def library_recently_watched(self, section_id=None, limit='10', **kwargs):
if not allow_session_library(section_id): if not allow_session_library(section_id):
return serve_template(templatename="user_recently_watched.html", data=None, title="Recently Watched") return serve_template(templatename="user_recently_watched.html", data=None, title="Recently Watched")
@@ -592,12 +593,12 @@ class WebInterface(object):
if result: if result:
return serve_template(templatename="user_recently_watched.html", data=result, title="Recently Watched") return serve_template(templatename="user_recently_watched.html", data=result, title="Recently Watched")
else: else:
logger.warn(u"Unable to retrieve data for get_library_recently_watched.") logger.warn(u"Unable to retrieve data for library_recently_watched.")
return serve_template(templatename="user_recently_watched.html", data=None, title="Recently Watched") return serve_template(templatename="user_recently_watched.html", data=None, title="Recently Watched")
@cherrypy.expose @cherrypy.expose
@requireAuth() @requireAuth()
def get_library_recently_added(self, section_id=None, limit='10', **kwargs): def library_recently_added(self, section_id=None, limit='10', **kwargs):
if not allow_session_library(section_id): if not allow_session_library(section_id):
return serve_template(templatename="library_recently_added.html", data=None, title="Recently Added") return serve_template(templatename="library_recently_added.html", data=None, title="Recently Added")
@@ -610,7 +611,7 @@ class WebInterface(object):
if result: if result:
return serve_template(templatename="library_recently_added.html", data=result['recently_added'], title="Recently Added") return serve_template(templatename="library_recently_added.html", data=result['recently_added'], title="Recently Added")
else: else:
logger.warn(u"Unable to retrieve data for get_library_recently_added.") logger.warn(u"Unable to retrieve data for library_recently_added.")
return serve_template(templatename="library_recently_added.html", data=None, title="Recently Added") return serve_template(templatename="library_recently_added.html", data=None, title="Recently Added")
@cherrypy.expose @cherrypy.expose
@@ -733,6 +734,132 @@ class WebInterface(object):
return {'success': result} return {'success': result}
@cherrypy.expose
@cherrypy.tools.json_out()
@requireAuth(member_of("admin"))
@addtoapi()
def get_library(self, section_id=None, **kwargs):
""" Get a library's details.
```
Required parameters:
section_id (str): The id of the Plex library section
Optional parameters:
None
Returns:
json:
{"child_count": null,
"count": 887,
"do_notify": 1,
"do_notify_created": 1,
"keep_history": 1,
"library_art": "/:/resources/movie-fanart.jpg",
"library_thumb": "/:/resources/movie.png",
"parent_count": null,
"section_id": 1,
"section_name": "Movies",
"section_type": "movie"
}
```
"""
if section_id:
library_data = libraries.Libraries()
library_details = library_data.get_details(section_id=section_id)
if library_details:
return library_details
else:
logger.warn(u"Unable to retrieve data for get_library.")
else:
logger.warn(u"Library details requested but no section_id received.")
@cherrypy.expose
@cherrypy.tools.json_out()
@requireAuth(member_of("admin"))
@addtoapi()
def get_library_watch_time_stats(self, section_id=None, **kwargs):
""" Get a library's watch time statistics.
```
Required parameters:
section_id (str): The id of the Plex library section
Optional parameters:
None
Returns:
json:
[{"query_days": 1,
"total_plays": 0,
"total_time": 0
},
{"query_days": 7,
"total_plays": 3,
"total_time": 15694
},
{"query_days": 30,
"total_plays": 35,
"total_time": 63054
},
{"query_days": 0,
"total_plays": 508,
"total_time": 1183080
}
]
```
"""
if section_id:
library_data = libraries.Libraries()
result = library_data.get_watch_time_stats(section_id=section_id)
if result:
return result
else:
logger.warn(u"Unable to retrieve data for get_library_watch_time_stats.")
else:
logger.warn(u"Library watch time stats requested but no section_id received.")
@cherrypy.expose
@cherrypy.tools.json_out()
@requireAuth(member_of("admin"))
@addtoapi()
def get_library_user_stats(self, section_id=None, **kwargs):
""" Get a library's user statistics.
```
Required parameters:
section_id (str): The id of the Plex library section
Optional parameters:
None
Returns:
json:
[{"friendly_name": "Jon Snow",
"total_plays": 170,
"user_id": 133788,
"user_thumb": "https://plex.tv/users/k10w42309cynaopq/avatar"
},
{"platform_type": "DanyKhaleesi69",
"total_plays": 42,
"user_id": 8008135,
"user_thumb": "https://plex.tv/users/568gwwoib5t98a3a/avatar"
},
{...},
{...}
]
```
"""
if section_id:
library_data = libraries.Libraries()
result = library_data.get_user_stats(section_id=section_id)
if result:
return result
else:
logger.warn(u"Unable to retrieve data for get_library_user_stats.")
else:
logger.warn(u"Library user stats requested but no section_id received.")
@cherrypy.expose @cherrypy.expose
@cherrypy.tools.json_out() @cherrypy.tools.json_out()
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
@@ -977,9 +1104,9 @@ class WebInterface(object):
if not allow_session_user(user_id): if not allow_session_user(user_id):
raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT) raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT)
user_data = users.Users()
if user_id: if user_id:
try: try:
user_data = users.Users()
user_details = user_data.get_details(user_id=user_id) user_details = user_data.get_details(user_id=user_id)
except: except:
logger.warn(u"Unable to retrieve user details for user_id %s " % user_id) logger.warn(u"Unable to retrieve user details for user_id %s " % user_id)
@@ -993,8 +1120,8 @@ class WebInterface(object):
@cherrypy.expose @cherrypy.expose
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
def edit_user_dialog(self, user=None, user_id=None, **kwargs): def edit_user_dialog(self, user=None, user_id=None, **kwargs):
user_data = users.Users()
if user_id: if user_id:
user_data = users.Users()
result = user_data.get_details(user_id=user_id) result = user_data.get_details(user_id=user_id)
status_message = '' status_message = ''
else: else:
@@ -1030,9 +1157,9 @@ class WebInterface(object):
keep_history = kwargs.get('keep_history', 0) keep_history = kwargs.get('keep_history', 0)
allow_guest = kwargs.get('allow_guest', 0) allow_guest = kwargs.get('allow_guest', 0)
user_data = users.Users()
if user_id: if user_id:
try: try:
user_data = users.Users()
user_data.set_config(user_id=user_id, user_data.set_config(user_id=user_id,
friendly_name=friendly_name, friendly_name=friendly_name,
custom_thumb=custom_thumb, custom_thumb=custom_thumb,
@@ -1047,7 +1174,7 @@ class WebInterface(object):
@cherrypy.expose @cherrypy.expose
@requireAuth() @requireAuth()
def get_user_watch_time_stats(self, user=None, user_id=None, **kwargs): def user_watch_time_stats(self, user=None, user_id=None, **kwargs):
if not allow_session_user(user_id): if not allow_session_user(user_id):
return serve_template(templatename="user_watch_time_stats.html", data=None, title="Watch Stats") return serve_template(templatename="user_watch_time_stats.html", data=None, title="Watch Stats")
@@ -1060,12 +1187,12 @@ class WebInterface(object):
if result: if result:
return serve_template(templatename="user_watch_time_stats.html", data=result, title="Watch Stats") return serve_template(templatename="user_watch_time_stats.html", data=result, title="Watch Stats")
else: else:
logger.warn(u"Unable to retrieve data for get_user_watch_time_stats.") logger.warn(u"Unable to retrieve data for user_watch_time_stats.")
return serve_template(templatename="user_watch_time_stats.html", data=None, title="Watch Stats") return serve_template(templatename="user_watch_time_stats.html", data=None, title="Watch Stats")
@cherrypy.expose @cherrypy.expose
@requireAuth() @requireAuth()
def get_user_player_stats(self, user=None, user_id=None, **kwargs): def user_player_stats(self, user=None, user_id=None, **kwargs):
if not allow_session_user(user_id): if not allow_session_user(user_id):
return serve_template(templatename="user_player_stats.html", data=None, title="Player Stats") return serve_template(templatename="user_player_stats.html", data=None, title="Player Stats")
@@ -1078,7 +1205,7 @@ class WebInterface(object):
if result: if result:
return serve_template(templatename="user_player_stats.html", data=result, title="Player Stats") return serve_template(templatename="user_player_stats.html", data=result, title="Player Stats")
else: else:
logger.warn(u"Unable to retrieve data for get_user_player_stats.") logger.warn(u"Unable to retrieve data for user_player_stats.")
return serve_template(templatename="user_player_stats.html", data=None, title="Player Stats") return serve_template(templatename="user_player_stats.html", data=None, title="Player Stats")
@cherrypy.expose @cherrypy.expose
@@ -1170,7 +1297,7 @@ class WebInterface(object):
@requireAuth() @requireAuth()
@addtoapi() @addtoapi()
def get_user_logins(self, user_id=None, **kwargs): def get_user_logins(self, user_id=None, **kwargs):
""" Get the data on PlexPy user login table. """ Get the data on PlexPy user login table.
``` ```
Required parameters: Required parameters:
@@ -1189,15 +1316,15 @@ class WebInterface(object):
"recordsTotal": 2344, "recordsTotal": 2344,
"recordsFiltered": 10, "recordsFiltered": 10,
"data": "data":
[{"browser": "Safari 7.0.3", [{"browser": "Safari 7.0.3",
"friendly_name": "Jon Snow", "friendly_name": "Jon Snow",
"host": "http://plexpy.castleblack.com", "host": "http://plexpy.castleblack.com",
"ip_address": "xxx.xxx.xxx.xxx", "ip_address": "xxx.xxx.xxx.xxx",
"os": "Mac OS X", "os": "Mac OS X",
"timestamp": 1462591869, "timestamp": 1462591869,
"user": "LordCommanderSnow", "user": "LordCommanderSnow",
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A", "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A",
"user_group": "guest", "user_group": "guest",
"user_id": 133788 "user_id": 133788
}, },
{...}, {...},
@@ -1222,6 +1349,134 @@ class WebInterface(object):
return history return history
@cherrypy.expose
@cherrypy.tools.json_out()
@requireAuth(member_of("admin"))
@addtoapi()
def get_user(self, user_id=None, **kwargs):
""" Get a user's details.
```
Required parameters:
user_id (str): The id of the Plex user
Optional parameters:
None
Returns:
json:
{"allow_guest": 1,
"deleted_user": 0,
"do_notify": 1,
"email": "Jon.Snow.1337@CastleBlack.com",
"friendly_name": "Jon Snow",
"is_allow_sync": 1,
"is_home_user": 1,
"is_restricted": 0,
"keep_history": 1,
"shared_libraries": ["10", "1", "4", "5", "15", "20", "2"],
"user_id": 133788,
"user_thumb": "https://plex.tv/users/k10w42309cynaopq/avatar",
"username": "LordCommanderSnow"
}
```
"""
if user_id:
user_data = users.Users()
user_details = user_data.get_details(user_id=user_id)
if user_details:
return user_details
else:
logger.warn(u"Unable to retrieve data for get_user.")
else:
logger.warn(u"User details requested but no user_id received.")
@cherrypy.expose
@cherrypy.tools.json_out()
@requireAuth(member_of("admin"))
@addtoapi()
def get_user_watch_time_stats(self, user_id=None, **kwargs):
""" Get a user's watch time statistics.
```
Required parameters:
user_id (str): The id of the Plex user
Optional parameters:
None
Returns:
json:
[{"query_days": 1,
"total_plays": 0,
"total_time": 0
},
{"query_days": 7,
"total_plays": 3,
"total_time": 15694
},
{"query_days": 30,
"total_plays": 35,
"total_time": 63054
},
{"query_days": 0,
"total_plays": 508,
"total_time": 1183080
}
]
```
"""
if user_id:
user_data = users.Users()
result = user_data.get_watch_time_stats(user_id=user_id)
if result:
return result
else:
logger.warn(u"Unable to retrieve data for get_user_watch_time_stats.")
else:
logger.warn(u"User watch time stats requested but no user_id received.")
@cherrypy.expose
@cherrypy.tools.json_out()
@requireAuth(member_of("admin"))
@addtoapi()
def get_user_player_stats(self, user_id=None, **kwargs):
""" Get a user's player statistics.
```
Required parameters:
user_id (str): The id of the Plex user
Optional parameters:
None
Returns:
json:
[{"platform_type": "Chrome",
"player_name": "Plex Web (Chrome)",
"result_id": 1,
"total_plays": 170
},
{"platform_type": "Chromecast",
"player_name": "Chromecast",
"result_id": 2,
"total_plays": 42
},
{...},
{...}
]
```
"""
if user_id:
user_data = users.Users()
result = user_data.get_player_stats(user_id=user_id)
if result:
return result
else:
logger.warn(u"Unable to retrieve data for get_user_player_stats.")
else:
logger.warn(u"User watch time stats requested but no user_id received.")
@cherrypy.expose @cherrypy.expose
@cherrypy.tools.json_out() @cherrypy.tools.json_out()
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
@@ -1240,9 +1495,8 @@ class WebInterface(object):
None None
``` ```
""" """
user_data = users.Users()
if user_id: if user_id:
user_data = users.Users()
delete_row = user_data.delete_all_history(user_id=user_id) delete_row = user_data.delete_all_history(user_id=user_id)
if delete_row: if delete_row:
return {'message': delete_row} return {'message': delete_row}
@@ -1267,11 +1521,9 @@ class WebInterface(object):
None None
``` ```
""" """
user_data = users.Users()
if user_id: if user_id:
user_data = users.Users()
delete_row = user_data.delete(user_id=user_id) delete_row = user_data.delete(user_id=user_id)
if delete_row: if delete_row:
return {'message': delete_row} return {'message': delete_row}
else: else:
@@ -1296,16 +1548,14 @@ class WebInterface(object):
None None
``` ```
""" """
user_data = users.Users()
if user_id: if user_id:
user_data = users.Users()
delete_row = user_data.undelete(user_id=user_id) delete_row = user_data.undelete(user_id=user_id)
if delete_row: if delete_row:
return {'message': delete_row} return {'message': delete_row}
elif username: elif username:
user_data = users.Users()
delete_row = user_data.undelete(username=username) delete_row = user_data.undelete(username=username)
if delete_row: if delete_row:
return {'message': delete_row} return {'message': delete_row}
else: else:
@@ -1984,55 +2234,54 @@ class WebInterface(object):
@cherrypy.expose @cherrypy.expose
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
def getLog(self, start=0, length=100, **kwargs): def getLog(self, **kwargs):
start = int(start) json_data = helpers.process_json_kwargs(json_kwargs=kwargs.get('json_data'))
length = int(length) log_level = kwargs.get('log_level', "")
search_value = ""
search_regex = ""
order_column = 0
order_dir = "desc"
if 'order[0][dir]' in kwargs: start = json_data['start']
order_dir = kwargs.get('order[0][dir]', "desc") length = json_data['length']
order_column = json_data['order'][0]['column']
if 'order[0][column]' in kwargs: order_dir = json_data['order'][0]['dir']
order_column = kwargs.get('order[0][column]', "0") search_value = json_data['search']['value']
sortcolumn = 0
if 'search[value]' in kwargs:
search_value = kwargs.get('search[value]', "")
if 'search[regex]' in kwargs:
search_regex = kwargs.get('search[regex]', "")
filt = [] filt = []
filtered = []
fa = filt.append fa = filt.append
with open(os.path.join(plexpy.CONFIG.LOG_DIR, logger.FILENAME)) as f: with open(os.path.join(plexpy.CONFIG.LOG_DIR, logger.FILENAME)) as f:
for l in f.readlines(): for l in f.readlines():
try: try:
temp_loglevel_and_time = l.split(' - ', 1) temp_loglevel_and_time = l.split(' - ', 1)
loglvl = temp_loglevel_and_time[1].split(' ::', 1)[0].strip() loglvl = temp_loglevel_and_time[1].split(' ::', 1)[0].strip()
msg = l.split(' : ', 1)[1].replace('\n', '') msg = unicode(l.split(' : ', 1)[1].replace('\n', ''), 'utf-8')
fa([temp_loglevel_and_time[0], loglvl, msg]) fa([temp_loglevel_and_time[0], loglvl, msg])
except IndexError: except IndexError:
# Add traceback message to previous msg. # Add traceback message to previous msg.
tl = (len(filt) - 1) tl = (len(filt) - 1)
n = len(l) - len(l.lstrip(' ')) n = len(l) - len(l.lstrip(' '))
l = '&nbsp;' * (2*n) + l[n:] l = '&nbsp;' * (2 * n) + l[n:]
filt[tl][2] += '<br>' + l filt[tl][2] += '<br>' + l
continue continue
filtered = [] log_levels = ['DEBUG', 'INFO', 'WARN', 'ERROR']
if search_value == '': if log_level in log_levels:
filtered = filt log_levels = log_levels[log_levels.index(log_level)::]
filtered = [row for row in filt if row[1] in log_levels]
else: else:
filtered = [row for row in filt for column in row if search_value.lower() in column.lower()] filtered = filt
if search_value:
filtered = [row for row in filtered for column in row if search_value.lower() in column.lower()]
sortcolumn = 0
if order_column == '1': if order_column == '1':
sortcolumn = 2 sortcolumn = 2
elif order_column == '2': elif order_column == '2':
sortcolumn = 1 sortcolumn = 1
filtered.sort(key=lambda x: x[sortcolumn], reverse=order_dir == "desc")
filtered.sort(key=lambda x: x[sortcolumn])
if order_dir == 'desc':
filtered = filtered[::-1]
rows = filtered[start:(start + length)] rows = filtered[start:(start + length)]
@@ -2046,7 +2295,7 @@ class WebInterface(object):
@cherrypy.tools.json_out() @cherrypy.tools.json_out()
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
@addtoapi() @addtoapi()
def get_plex_log(self, window=1000, **kwargs): def get_plex_log(self, **kwargs):
""" Get the PMS logs. """ Get the PMS logs.
``` ```
@@ -2068,6 +2317,7 @@ class WebInterface(object):
] ]
``` ```
""" """
window = int(kwargs.get('window', plexpy.CONFIG.PMS_LOGS_LINE_CAP))
log_lines = [] log_lines = []
log_type = kwargs.get('log_type', 'server') log_type = kwargs.get('log_type', 'server')
@@ -2215,7 +2465,7 @@ class WebInterface(object):
log_dir=plexpy.CONFIG.LOG_DIR, verbose=plexpy.VERBOSE) log_dir=plexpy.CONFIG.LOG_DIR, verbose=plexpy.VERBOSE)
logger.info(u"Verbose toggled, set to %s", plexpy.VERBOSE) logger.info(u"Verbose toggled, set to %s", plexpy.VERBOSE)
logger.debug(u"If you read this message, debug logging is available") logger.debug(u"If you read this message, debug logging is available")
raise cherrypy.HTTPRedirect("logs") raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT + "logs")
@cherrypy.expose @cherrypy.expose
@requireAuth() @requireAuth()
@@ -2255,6 +2505,7 @@ class WebInterface(object):
config = { config = {
"allow_guest_access": checked(plexpy.CONFIG.ALLOW_GUEST_ACCESS), "allow_guest_access": checked(plexpy.CONFIG.ALLOW_GUEST_ACCESS),
"http_basic_auth": checked(plexpy.CONFIG.HTTP_BASIC_AUTH),
"http_hash_password": checked(plexpy.CONFIG.HTTP_HASH_PASSWORD), "http_hash_password": checked(plexpy.CONFIG.HTTP_HASH_PASSWORD),
"http_hashed_password": plexpy.CONFIG.HTTP_HASHED_PASSWORD, "http_hashed_password": plexpy.CONFIG.HTTP_HASHED_PASSWORD,
"http_host": plexpy.CONFIG.HTTP_HOST, "http_host": plexpy.CONFIG.HTTP_HOST,
@@ -2262,6 +2513,7 @@ class WebInterface(object):
"http_port": plexpy.CONFIG.HTTP_PORT, "http_port": plexpy.CONFIG.HTTP_PORT,
"http_password": http_password, "http_password": http_password,
"http_root": plexpy.CONFIG.HTTP_ROOT, "http_root": plexpy.CONFIG.HTTP_ROOT,
"http_proxy": checked(plexpy.CONFIG.HTTP_PROXY),
"launch_browser": checked(plexpy.CONFIG.LAUNCH_BROWSER), "launch_browser": checked(plexpy.CONFIG.LAUNCH_BROWSER),
"enable_https": checked(plexpy.CONFIG.ENABLE_HTTPS), "enable_https": checked(plexpy.CONFIG.ENABLE_HTTPS),
"https_create_cert": checked(plexpy.CONFIG.HTTPS_CREATE_CERT), "https_create_cert": checked(plexpy.CONFIG.HTTPS_CREATE_CERT),
@@ -2374,7 +2626,7 @@ class WebInterface(object):
"ip_logging_enable", "movie_logging_enable", "tv_logging_enable", "music_logging_enable", "ip_logging_enable", "movie_logging_enable", "tv_logging_enable", "music_logging_enable",
"notify_consecutive", "notify_upload_posters", "notify_recently_added", "notify_recently_added_grandparent", "notify_consecutive", "notify_upload_posters", "notify_recently_added", "notify_recently_added_grandparent",
"monitor_pms_updates", "monitor_remote_access", "get_file_sizes", "log_blacklist", "http_hash_password", "monitor_pms_updates", "monitor_remote_access", "get_file_sizes", "log_blacklist", "http_hash_password",
"allow_guest_access", "cache_images" "allow_guest_access", "cache_images", "http_proxy", "http_basic_auth"
] ]
for checked_config in checked_configs: for checked_config in checked_configs:
if checked_config not in kwargs: if checked_config not in kwargs:
@@ -2576,6 +2828,24 @@ class WebInterface(object):
``` ```
Required parameters: Required parameters:
agent_id(str): The id of the notification agent to use agent_id(str): The id of the notification agent to use
9 # Boxcar2
17 # Browser
10 # Email
16 # Facebook
0 # Growl
12 # IFTTT
18 # Join
4 # NotifyMyAndroid
3 # Plex Home Theater
1 # Prowl
5 # Pushalot
6 # Pushbullet
7 # Pushover
15 # Scripts
14 # Slack
13 # Telegram
11 # Twitter
2 # XBMC
subject(str): The subject of the message subject(str): The subject of the message
body(str): The body of the message body(str): The body of the message
@@ -2857,7 +3127,7 @@ class WebInterface(object):
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
def checkGithub(self): def checkGithub(self):
versioncheck.checkGithub() versioncheck.checkGithub()
raise cherrypy.HTTPRedirect("home") raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT + "home")
@cherrypy.expose @cherrypy.expose
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
@@ -2938,13 +3208,17 @@ class WebInterface(object):
@cherrypy.expose @cherrypy.expose
@requireAuth() @requireAuth()
def pms_image_proxy(self, img='', rating_key=None, width='0', height='0', fallback=None, **kwargs): def pms_image_proxy(self, img='', rating_key=None, width='0', height='0',
fallback=None, refresh=False, **kwargs):
""" Gets an image from the PMS and saves it to the image cache directory. """ """ Gets an image from the PMS and saves it to the image cache directory. """
if not img and not rating_key: if not img and not rating_key:
logger.error('No image input received.') logger.error('No image input received.')
return return
refresh = True if refresh == 'true' else False
if rating_key and not img: if rating_key and not img:
img = '/library/metadata/%s/thumb/1337' % rating_key img = '/library/metadata/%s/thumb/1337' % rating_key
@@ -2959,8 +3233,9 @@ class WebInterface(object):
os.mkdir(c_dir) os.mkdir(c_dir)
try: try:
if 'indexes' in img: if not plexpy.CONFIG.CACHE_IMAGES or refresh or 'indexes' in img:
raise NotFound raise NotFound
return serve_file(path=ffp, content_type='image/jpeg') return serve_file(path=ffp, content_type='image/jpeg')
except NotFound: except NotFound:
@@ -2980,7 +3255,7 @@ class WebInterface(object):
raise Exception(u'PMS image request failed') raise Exception(u'PMS image request failed')
except Exception as e: except Exception as e:
logger.exception(u'Failed to get image %s, falling back to %s.' % (img, fallback)) logger.warn(u'Failed to get image %s, falling back to %s.' % (img, fallback))
fbi = None fbi = None
if fallback == 'poster': if fallback == 'poster':
fbi = common.DEFAULT_POSTER_THUMB fbi = common.DEFAULT_POSTER_THUMB
@@ -3007,6 +3282,30 @@ class WebInterface(object):
return serve_download(os.path.join(plexpy.CONFIG.LOG_DIR, log_file), name=log_file) return serve_download(os.path.join(plexpy.CONFIG.LOG_DIR, log_file), name=log_file)
@cherrypy.expose
@requireAuth(member_of("admin"))
@addtoapi()
def download_plex_log(self, **kwargs):
""" Download the Plex log file. """
log_type = kwargs.get('log_type', 'server')
log_file = ""
if plexpy.CONFIG.PMS_LOGS_FOLDER:
if log_type == "server":
log_file = 'Plex Media Server.log'
log_file_path = os.path.join(plexpy.CONFIG.PMS_LOGS_FOLDER, log_file)
elif log_type == "scanner":
log_file = 'Plex Media Scanner.log'
log_file_path = os.path.join(plexpy.CONFIG.PMS_LOGS_FOLDER, log_file)
else:
return "Plex log folder not set in the settings."
if log_file and os.path.isfile(log_file_path):
return serve_download(log_file_path, name=log_file)
else:
return "Plex %s log file not found." % log_type
@cherrypy.expose @cherrypy.expose
@cherrypy.tools.json_out() @cherrypy.tools.json_out()
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
@@ -3336,7 +3635,7 @@ class WebInterface(object):
@cherrypy.tools.json_out() @cherrypy.tools.json_out()
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
@addtoapi("get_recently_added") @addtoapi("get_recently_added")
def get_recently_added_details(self, count='0', section_id='', **kwargs): def get_recently_added_details(self, start='0', count='0', section_id='', **kwargs):
""" Get all items that where recelty added to plex. """ Get all items that where recelty added to plex.
``` ```
@@ -3344,6 +3643,7 @@ class WebInterface(object):
count (str): Number of items to return count (str): Number of items to return
Optional parameters: Optional parameters:
start (str): The item number to start at
section_id (str): The id of the Plex library section section_id (str): The id of the Plex library section
Returns: Returns:
@@ -3373,7 +3673,7 @@ class WebInterface(object):
``` ```
""" """
pms_connect = pmsconnect.PmsConnect() pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_recently_added_details(count=count, section_id=section_id) result = pms_connect.get_recently_added_details(start=start, count=count, section_id=section_id)
if result: if result:
return result return result
@@ -3615,19 +3915,22 @@ class WebInterface(object):
} }
``` ```
""" """
pms_connect = pmsconnect.PmsConnect(token=plexpy.CONFIG.PMS_TOKEN) try:
result = pms_connect.get_current_activity() pms_connect = pmsconnect.PmsConnect(token=plexpy.CONFIG.PMS_TOKEN)
result = pms_connect.get_current_activity()
data_factory = datafactory.DataFactory() if result:
for session in result['sessions']: data_factory = datafactory.DataFactory()
if not session['ip_address']: for session in result['sessions']:
ip_address = data_factory.get_session_ip(session['session_key']) if not session['ip_address']:
session['ip_address'] = ip_address ip_address = data_factory.get_session_ip(session['session_key'])
session['ip_address'] = ip_address
if result: return result
return result else:
else: logger.warn(u"Unable to retrieve data for get_activity.")
logger.warn(u"Unable to retrieve data for get_activity.") except Exception as e:
logger.exception(u"Unable to retrieve data for get_activity: %s" % e)
@cherrypy.expose @cherrypy.expose
@cherrypy.tools.json_out() @cherrypy.tools.json_out()

View File

@@ -66,10 +66,15 @@ def initialize(options):
if options['http_password']: if options['http_password']:
logger.info(u"PlexPy WebStart :: Web server authentication is enabled, username is '%s'", options['http_username']) logger.info(u"PlexPy WebStart :: Web server authentication is enabled, username is '%s'", options['http_username'])
options_dict['tools.sessions.on'] = auth_enabled = session_enabled = True if options['http_basic_auth']:
cherrypy.tools.auth = cherrypy.Tool('before_handler', webauth.check_auth) auth_enabled = session_enabled = False
basic_auth_enabled = True
else:
options_dict['tools.sessions.on'] = auth_enabled = session_enabled = True
basic_auth_enabled = False
cherrypy.tools.auth = cherrypy.Tool('before_handler', webauth.check_auth)
else: else:
auth_enabled = session_enabled = False auth_enabled = session_enabled = basic_auth_enabled = False
if not options['http_root'] or options['http_root'] == '/': if not options['http_root'] or options['http_root'] == '/':
plexpy.HTTP_ROOT = options['http_root'] = '/' plexpy.HTTP_ROOT = options['http_root'] = '/'
@@ -88,7 +93,14 @@ def initialize(options):
'application/javascript'], 'application/javascript'],
'tools.auth.on': auth_enabled, 'tools.auth.on': auth_enabled,
'tools.sessions.on': session_enabled, 'tools.sessions.on': session_enabled,
'tools.sessions.timeout': 30 * 24 * 60 # 30 days 'tools.sessions.timeout': 30 * 24 * 60, # 30 days
'tools.auth_basic.on': basic_auth_enabled,
'tools.auth_basic.realm': 'PlexPy web server',
'tools.auth_basic.checkpassword': cherrypy.lib.auth_basic.checkpassword_dict({
options['http_username']: options['http_password']})
},
'/api': {
'tools.auth_basic.on': False
}, },
'/interfaces': { '/interfaces': {
'tools.staticdir.on': True, 'tools.staticdir.on': True,
@@ -178,17 +190,17 @@ def initialize(options):
'tools.auth.on': False, 'tools.auth.on': False,
'tools.sessions.on': False 'tools.sessions.on': False
}, },
'/pms_image_proxy': { #'/pms_image_proxy': {
'tools.staticdir.on': True, # 'tools.staticdir.on': True,
'tools.staticdir.dir': os.path.join(plexpy.CONFIG.CACHE_DIR, 'images'), # 'tools.staticdir.dir': os.path.join(plexpy.CONFIG.CACHE_DIR, 'images'),
'tools.caching.on': True, # 'tools.caching.on': True,
'tools.caching.force': True, # 'tools.caching.force': True,
'tools.caching.delay': 0, # 'tools.caching.delay': 0,
'tools.expires.on': True, # 'tools.expires.on': True,
'tools.expires.secs': 60 * 60 * 24 * 30, # 30 days # 'tools.expires.secs': 60 * 60 * 24 * 30, # 30 days
'tools.auth.on': False, # 'tools.auth.on': False,
'tools.sessions.on': False # 'tools.sessions.on': False
}, #},
'/favicon.ico': { '/favicon.ico': {
'tools.staticfile.on': True, 'tools.staticfile.on': True,
'tools.staticfile.filename': os.path.abspath(os.path.join(plexpy.PROG_DIR, 'data/interfaces/default/images/favicon.ico')), 'tools.staticfile.filename': os.path.abspath(os.path.join(plexpy.PROG_DIR, 'data/interfaces/default/images/favicon.ico')),
@@ -199,7 +211,7 @@ def initialize(options):
'tools.expires.secs': 60 * 60 * 24 * 30, # 30 days 'tools.expires.secs': 60 * 60 * 24 * 30, # 30 days
'tools.auth.on': False, 'tools.auth.on': False,
'tools.sessions.on': False 'tools.sessions.on': False
}, }
} }
# Prevent time-outs # Prevent time-outs