Merge branch 'dev'

This commit is contained in:
JonnyWong16 2016-06-11 11:40:24 -07:00
commit d9474cdcc5
29 changed files with 862 additions and 108 deletions

194
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.
@ -1311,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.
@ -1415,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.
@ -1520,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,18 @@
# 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) ## v1.4.5 (2016-05-25)
* Fix: PlexPy unable to start if failed to get shared libraries for a user. * Fix: PlexPy unable to start if failed to get shared libraries for a user.

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;
@ -2964,4 +2967,41 @@ a.no-highlight:hover {
background-color: #555; background-color: #555;
border: 0px solid #444; border: 0px solid #444;
border-radius: 3px; 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

@ -108,6 +108,7 @@ 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']}" data-id="${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>

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,20 @@
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)) { if (!(current_activity)) {
$('#currentActivity').html('<div id="dashboard-no-activity" class="text-muted">There was an error communicating with your Plex Server.</div>'); $('#currentActivity').html('<div id="dashboard-no-activity" class="text-muted">There was an error communicating with your Plex Server.</div>');
return return

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

@ -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

@ -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">

View file

@ -1825,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>

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

@ -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

@ -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', ''),

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

@ -147,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

@ -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=''):
""" """
@ -1904,7 +1906,7 @@ class PmsConnect(object):
""" """
if img: if img:
params = {'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():
params['width'] = width params['width'] = width
params['height'] = height params['height'] = height

View file

@ -1,2 +1,2 @@
PLEXPY_VERSION = "master" PLEXPY_VERSION = "master"
PLEXPY_RELEASE_VERSION = "1.4.5" 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

@ -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
@ -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
@ -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,13 +2234,15 @@ 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', "")
order_dir = kwargs.get('order[0][dir]', "desc")
order_column = kwargs.get('order[0][column]', "0") start = json_data['start']
search_value = kwargs.get('search[value]', "") length = json_data['length']
search_regex = kwargs.get('search[regex]', "") # Remove? order_column = json_data['order'][0]['column']
order_dir = json_data['order'][0]['dir']
search_value = json_data['search']['value']
sortcolumn = 0 sortcolumn = 0
filt = [] filt = []
@ -2001,7 +2253,7 @@ class WebInterface(object):
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.
@ -2011,10 +2263,15 @@ class WebInterface(object):
filt[tl][2] += '<br>' + l filt[tl][2] += '<br>' + l
continue continue
if search_value == '': log_levels = ['DEBUG', 'INFO', 'WARN', 'ERROR']
filtered = filt if log_level in log_levels:
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()]
if order_column == '1': if order_column == '1':
sortcolumn = 2 sortcolumn = 2
@ -2038,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.
``` ```
@ -2060,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')
@ -2570,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
@ -2932,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
@ -2953,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:
@ -2974,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
@ -3001,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"))
@ -3610,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()
if result: if result:
data_factory = datafactory.DataFactory() data_factory = datafactory.DataFactory()
for session in result['sessions']: for session in result['sessions']:
if not session['ip_address']: if not session['ip_address']:
ip_address = data_factory.get_session_ip(session['session_key']) ip_address = data_factory.get_session_ip(session['session_key'])
session['ip_address'] = ip_address session['ip_address'] = ip_address
return result return result
else: else:
logger.warn(u"Unable to retrieve data for get_activity.") logger.warn(u"Unable to retrieve data for get_activity.")
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

@ -190,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')),