Compare commits

..

23 Commits

Author SHA1 Message Date
JonnyWong16
331be52327 v2.1.12 2018-06-08 22:12:51 -07:00
JonnyWong16
6ece690e23 Fix grouping plays on graphs with new grouping logic 2018-06-08 18:20:51 -07:00
JonnyWong16
6b63a1399e Add collection tag to raw newsletter data 2018-06-08 17:42:20 -07:00
JonnyWong16
5c77cf652b Fix typo in Join notifier help text 2018-06-08 17:18:05 -07:00
JonnyWong16
d6f9a82edb Fix mutable default arguments 2018-06-07 18:51:52 -07:00
JonnyWong16
a887489666 Change notifier link sources to blank for disabled 2018-06-07 18:51:20 -07:00
JonnyWong16
8c0bcd0059 Ability to stop a stream using session key 2018-06-05 20:30:07 -07:00
JonnyWong16
c18ee81130 Fix typo in http handler 2018-06-03 12:25:15 -07:00
JonnyWong16
44428cc6e5 Update logger blacklist for newsleter/notifier configs 2018-06-03 11:01:58 -07:00
JonnyWong16
c19cc858bd v2.1.11-beta 2018-06-02 10:39:32 -07:00
JonnyWong16
668913fd60 Allow manual PMS URL override in config file for XML shortcuts 2018-06-02 09:55:39 -07:00
JonnyWong16
50c5407a46 Fix Monitor Remote Access checkbox (Plex server update chaged bool to int) 2018-06-02 09:32:05 -07:00
JonnyWong16
939755d3b7 Add Plex XML shortcuts to libraries, users, and sync headers 2018-06-02 08:58:55 -07:00
JonnyWong16
54f4696713 Refactor open Plex XML script 2018-06-02 08:53:53 -07:00
JonnyWong16
c85af521fe Add Plex XML shortcuts for metadata and server resources 2018-06-02 08:26:30 -07:00
JonnyWong16
917d19db85 Fix grouping or new plays not previously in history 2018-06-01 18:45:58 -07:00
JonnyWong16
7292f25eb9 Reword group play history setting 2018-05-31 09:10:44 -07:00
JonnyWong16
22a2ad4bc7 Fix progress percent in grouping logic 2018-05-31 08:34:58 -07:00
JonnyWong16
95e56f5ea5 Fix activity progress bar not updating in some cases 2018-05-31 08:33:50 -07:00
JonnyWong16
ed24232a0a Improve logic for grouping history items 2018-05-30 22:18:41 -07:00
JonnyWong16
15225faee7 Add filename to notification parameters 2018-05-30 21:52:18 -07:00
JonnyWong16
041a35a35a Fallback to title if missing index for update metadata 2018-05-30 21:46:31 -07:00
JonnyWong16
6d365c174a Update X-Plex headers 2018-05-30 21:39:45 -07:00
25 changed files with 261 additions and 115 deletions

8
API.md
View File

@@ -2624,15 +2624,15 @@ Returns:
### terminate_session ### terminate_session
Add a new notification agent. Stop a streaming session.
``` ```
Required parameters: Required parameters:
session_id (str): The id of the session to terminate session_key (int): The session key of the session to terminate, OR
message (str): A custom message to send to the client session_id (str): The session id of the session to terminate
Optional parameters: Optional parameters:
None message (str): A custom message to send to the client
Returns: Returns:
None None

View File

@@ -1,5 +1,27 @@
# Changelog # Changelog
## v2.1.12 (2018-06-08)
* Notifications:
* Change: Blank notification link source means disabled instead of default.
* Newsletters:
* New: Make collection tags available in the raw newsletter data for custom templates.
* API:
* New: Ability to terminate a stream using the session key.
## v2.1.11-beta (2018-06-02)
* Monitoring:
* Fix: Activity progress bar not updating in some cases.
* Fix: Monitory Remote Access setting disabled due to Plex Media Server API changes.
* Change: Improved logic for grouping history items without being successive plays.
* Notifications:
* New: Added filename to notification parameters.
* Other:
* Fix: Update metadata failing for tracks without track numbers.
## v2.1.10-beta (2018-05-28) ## v2.1.10-beta (2018-05-28)
* Monitoring: * Monitoring:

View File

@@ -292,6 +292,7 @@ ${next.modalIncludes()}
<script src="${http_root}js/pnotify.custom.min.js"></script> <script src="${http_root}js/pnotify.custom.min.js"></script>
<script src="${http_root}js/script.js${cache_param}"></script> <script src="${http_root}js/script.js${cache_param}"></script>
<script src="${http_root}js/jquery.qrcode.min.js"></script> <script src="${http_root}js/jquery.qrcode.min.js"></script>
<script src="${http_root}js/jquery.tripleclick.min.js"></script>
% if _session['user_group'] == 'admin' and BROWSER_NOTIFIERS: % if _session['user_group'] == 'admin' and BROWSER_NOTIFIERS:
<script src="${http_root}js/ajaxNotifications.js"></script> <script src="${http_root}js/ajaxNotifications.js"></script>
% endif % endif

View File

@@ -12,7 +12,7 @@
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<div class="padded-header" id="current-activity-header"> <div class="padded-header" id="current-activity-header">
<h3><span id="sessions-shortcut">Activity</span> &nbsp;&nbsp; <h3><span id="sessions-xml">Activity</span> &nbsp;&nbsp;
<small> <small>
<span id="currentActivityHeader" style="display: none;"> <span id="currentActivityHeader" style="display: none;">
Streams: <span id="currentActivityHeader-streams"></span> | Streams: <span id="currentActivityHeader-streams"></span> |
@@ -236,7 +236,6 @@
<script src="${http_root}js/moment-with-locale.js"></script> <script src="${http_root}js/moment-with-locale.js"></script>
<script src="${http_root}js/jquery.scrollbar.min.js"></script> <script src="${http_root}js/jquery.scrollbar.min.js"></script>
<script src="${http_root}js/jquery.mousewheel.min.js"></script> <script src="${http_root}js/jquery.mousewheel.min.js"></script>
<script src="${http_root}js/jquery.tripleclick.min.js"></script>
<script> <script>
var date_format = 'YYYY-MM-DD'; var date_format = 'YYYY-MM-DD';
var time_format = 'hh:mm a'; var time_format = 'hh:mm a';
@@ -546,7 +545,7 @@
.attr('data-original-title', 'Transcoder Progress ' + s.transcode_progress + '%'); .attr('data-original-title', 'Transcoder Progress ' + s.transcode_progress + '%');
var progress_bar = $('#progress-bar-' + key); var progress_bar = $('#progress-bar-' + key);
progress_bar.data('state', s.state); progress_bar.data('state', s.state);
if (progress_bar.data('last_view_offset') && progress_bar.data('last_view_offset') !== s.view_offset) { if (progress_bar.data('last_view_offset') !== s.view_offset) {
progress_bar.data('last_view_offset', s.view_offset).data('view_offset', s.view_offset); progress_bar.data('last_view_offset', s.view_offset).data('view_offset', s.view_offset);
} }
@@ -676,6 +675,7 @@
$.ajax({ $.ajax({
url: 'terminate_session', url: 'terminate_session',
data: { data: {
session_key: key,
session_id: session_id, session_id: session_id,
message: message message: message
}, },
@@ -694,10 +694,8 @@
}); });
}); });
$('#sessions-shortcut').on('tripleclick', function () { $('#sessions-xml').on('tripleclick', function () {
$.getJSON('return_sessions_url', function(sessions_url) { openPlexXML('/status/sessions');
window.open(sessions_url, '_blank');
});
}); });
% endif % endif
</script> </script>

View File

@@ -83,31 +83,31 @@ DOCUMENTATION :: END
<ul class="list-unstyled breadcrumb"> <ul class="list-unstyled breadcrumb">
% if data['media_type'] in ('movie', 'collection'): % if data['media_type'] in ('movie', 'collection'):
<li><a href="library?section_id=${data['section_id']}">${data['library_name']}</a></li> <li><a href="library?section_id=${data['section_id']}">${data['library_name']}</a></li>
<li class="active">${data['title']}</li> <li class="active metadata-xml">${data['title']}</li>
% elif data['media_type'] == 'show': % elif data['media_type'] == 'show':
<li><a href="library?section_id=${data['section_id']}">${data['library_name']}</a></li> <li><a href="library?section_id=${data['section_id']}">${data['library_name']}</a></li>
<li class="active">${data['title']}</li> <li class="active metadata-xml">${data['title']}</li>
% elif data['media_type'] == 'season': % elif data['media_type'] == 'season':
<li class="hidden-xs hidden-sm"><a href="library?section_id=${data['section_id']}">${data['library_name']}</a></li> <li class="hidden-xs hidden-sm"><a href="library?section_id=${data['section_id']}">${data['library_name']}</a></li>
<li><a href="info?rating_key=${data['parent_rating_key']}">${data['parent_title']}</a></li> <li><a href="info?rating_key=${data['parent_rating_key']}">${data['parent_title']}</a></li>
<li class="active">Season ${data['media_index']}</li> <li class="active metadata-xml">Season ${data['media_index']}</li>
% elif data['media_type'] == 'episode': % elif data['media_type'] == 'episode':
<li class="hidden-xs hidden-sm"><a href="library?section_id=${data['section_id']}">${data['library_name']}</a></li> <li class="hidden-xs hidden-sm"><a href="library?section_id=${data['section_id']}">${data['library_name']}</a></li>
<li class="hidden-xs hidden-sm"><a href="info?rating_key=${data['grandparent_rating_key']}">${data['grandparent_title']}</a></li> <li class="hidden-xs hidden-sm"><a href="info?rating_key=${data['grandparent_rating_key']}">${data['grandparent_title']}</a></li>
<li><a href="info?rating_key=${data['parent_rating_key']}">Season ${data['parent_media_index']}</a></li> <li><a href="info?rating_key=${data['parent_rating_key']}">Season ${data['parent_media_index']}</a></li>
<li class="active">Episode ${data['media_index']} - ${data['title']}</li> <li class="active metadata-xml">Episode ${data['media_index']} - ${data['title']}</li>
% elif data['media_type'] == 'artist': % elif data['media_type'] == 'artist':
<li><a href="library?section_id=${data['section_id']}">${data['library_name']}</a></li> <li><a href="library?section_id=${data['section_id']}">${data['library_name']}</a></li>
<li class="active">${data['title']}</li> <li class="active metadata-xml">${data['title']}</li>
% elif data['media_type'] == 'album': % elif data['media_type'] == 'album':
<li class="hidden-xs hidden-sm"><a href="library?section_id=${data['section_id']}">${data['library_name']}</a></li> <li class="hidden-xs hidden-sm"><a href="library?section_id=${data['section_id']}">${data['library_name']}</a></li>
<li><a href="info?rating_key=${data['parent_rating_key']}">${data['parent_title']}</a></li> <li><a href="info?rating_key=${data['parent_rating_key']}">${data['parent_title']}</a></li>
<li class="active">${data['title']}</li> <li class="active metadata-xml">${data['title']}</li>
% elif data['media_type'] == 'track': % elif data['media_type'] == 'track':
<li class="hidden-xs hidden-sm"><a href="library?section_id=${data['section_id']}">${data['library_name']}</a></li> <li class="hidden-xs hidden-sm"><a href="library?section_id=${data['section_id']}">${data['library_name']}</a></li>
<li class="hidden-xs hidden-sm"><a href="info?rating_key=${data['grandparent_rating_key']}">${data['grandparent_title']}</a></li> <li class="hidden-xs hidden-sm"><a href="info?rating_key=${data['grandparent_rating_key']}">${data['grandparent_title']}</a></li>
<li><a href="info?rating_key=${data['parent_rating_key']}">${data['parent_title']}</a></li> <li><a href="info?rating_key=${data['parent_rating_key']}">${data['parent_title']}</a></li>
<li class="active">Track ${data['media_index']} - ${data['title']}</li> <li class="active metadata-xml">Track ${data['media_index']} - ${data['title']}</li>
% endif % endif
</ul> </ul>
</div> </div>
@@ -703,6 +703,10 @@ DOCUMENTATION :: END
</script> </script>
% endif % endif
<script> <script>
$('.metadata-xml').on('tripleclick', function () {
openPlexXML("/library/metadata/${data['rating_key']}");
});
$("#airdate").html(moment($("#airdate").text()).format('MMM DD, YYYY')); $("#airdate").html(moment($("#airdate").text()).format('MMM DD, YYYY'));
$("#runtime").html(millisecondsToMinutes($("#runtime").text(), true)); $("#runtime").html(millisecondsToMinutes($("#runtime").text(), true));
$('div.art-face').animate({ opacity: 0.2 }, { duration: 1000 }); $('div.art-face').animate({ opacity: 0.2 }, { duration: 1000 });

View File

@@ -457,4 +457,11 @@ function capitalizeFirstLetter(string) {
$.fn.slideToggleBool = function(bool, options) { $.fn.slideToggleBool = function(bool, options) {
return bool ? $(this).slideDown(options) : $(this).slideUp(options); return bool ? $(this).slideDown(options) : $(this).slideUp(options);
}; };
function openPlexXML(endpoint, plextv, params) {
var data = $.extend({endpoint: endpoint, plextv: plextv}, params);
$.getJSON('return_plex_xml_url', data, function(xml_url) {
window.open(xml_url, '_blank');
});
}

View File

@@ -10,7 +10,7 @@
<div class='container-fluid'> <div class='container-fluid'>
<div class='table-card-header'> <div class='table-card-header'>
<div class="header-bar"> <div class="header-bar">
<span><i class="fa fa-book"></i> All Libraries</span> <span id="libraries-xml"><i class="fa fa-book"></i> All Libraries</span>
</div> </div>
<div class="button-bar"> <div class="button-bar">
% if _session['user_group'] == 'admin': % if _session['user_group'] == 'admin':
@@ -198,5 +198,9 @@
}); });
}); });
% endif % endif
$('#libraries-xml').on('tripleclick', function () {
openPlexXML('/library/sections/all');
});
</script> </script>
</%def> </%def>

View File

@@ -115,9 +115,9 @@
</div> </div>
<div class="checkbox advanced-setting"> <div class="checkbox advanced-setting">
<label> <label>
<input type="checkbox" id="group_history_tables" name="group_history_tables" value="1" ${config['group_history_tables']}> Group Successive Play History <input type="checkbox" id="group_history_tables" name="group_history_tables" value="1" ${config['group_history_tables']}> Group Play History
</label> </label>
<p class="help-block">Group successive play history by the same user as a single entry in the watch statistics, tables, and graphs.</p> <p class="help-block">Group play history for the same item and user as a single entry when progress is less than the watched percent.</p>
</div> </div>
<div class="checkbox advanced-setting"> <div class="checkbox advanced-setting">
<label> <label>
@@ -646,7 +646,7 @@
<div role="tabpanel" class="tab-pane" id="tabs-plex_media_server"> <div role="tabpanel" class="tab-pane" id="tabs-plex_media_server">
<div class="padded-header"> <div class="padded-header">
<h3>Plex Media Server <small style="color: #fff;">Version <span id="pms_version">${config['pms_version']}</span></small></h3> <h3 id="resources-xml">Plex Media Server <small style="color: #fff;">Version <span id="pms_version">${config['pms_version']}</span></small></h3>
</div> </div>
<div class="form-group has-feedback" id="pms_ip_group"> <div class="form-group has-feedback" id="pms_ip_group">
@@ -2358,7 +2358,7 @@ $(document).ready(function() {
data: { pref: 'PublishServerOnPlexOnlineKey' }, data: { pref: 'PublishServerOnPlexOnlineKey' },
async: true, async: true,
success: function(data) { success: function(data) {
if (data !== 'true') { if (data === 'false' || data === '0') {
$("#remoteAccessCheck").html("Remote access must be enabled on your Plex Server. <a target='_blank' href='${anon_url('https://support.plex.tv/hc/en-us/articles/200484543-Enabling-Remote-Access-for-a-Server')}'>Click here</a> for help."); $("#remoteAccessCheck").html("Remote access must be enabled on your Plex Server. <a target='_blank' href='${anon_url('https://support.plex.tv/hc/en-us/articles/200484543-Enabling-Remote-Access-for-a-Server')}'>Click here</a> for help.");
$("#monitor_remote_access").attr("checked", false).attr("disabled", true); $("#monitor_remote_access").attr("checked", false).attr("disabled", true);
} }
@@ -2753,6 +2753,10 @@ $(document).ready(function() {
body_container.animate({scrollTop: scroll_pos}); body_container.animate({scrollTop: scroll_pos});
} }
}); });
$('#resources-xml').on('tripleclick', function () {
openPlexXML('/api/resources', true, {includeHttps: 1});
});
}); });
</script> </script>
</%def> </%def>

View File

@@ -16,7 +16,7 @@
<div class='container-fluid'> <div class='container-fluid'>
<div class='table-card-header'> <div class='table-card-header'>
<div class="header-bar"> <div class="header-bar">
<span><i class="fa fa-cloud-download"></i> Synced Items</span> <span id="sync-xml"><i class="fa fa-cloud-download"></i> Synced Items</span>
</div> </div>
<div class="button-bar"> <div class="button-bar">
% if _session['user_group'] == 'admin': % if _session['user_group'] == 'admin':
@@ -185,5 +185,9 @@
$("#refresh-syncs-list").click(function() { $("#refresh-syncs-list").click(function() {
sync_table.ajax.reload(); sync_table.ajax.reload();
}); });
$('#sync-xml').on('tripleclick', function () {
openPlexXML('/servers/{machine_id}/sync_lists', true);
});
</script> </script>
</%def> </%def>

View File

@@ -10,7 +10,7 @@
<div class='container-fluid'> <div class='container-fluid'>
<div class='table-card-header'> <div class='table-card-header'>
<div class="header-bar"> <div class="header-bar">
<span><i class="fa fa-group"></i> All Users</span> <span id="users-xml"><i class="fa fa-group"></i> All Users</span>
</div> </div>
<div class="button-bar"> <div class="button-bar">
% if _session['user_group'] == 'admin': % if _session['user_group'] == 'admin':
@@ -202,5 +202,9 @@
}); });
}); });
% endif % endif
$('#users-xml').on('tripleclick', function () {
openPlexXML('/api/users', true);
});
</script> </script>
</%def> </%def>

View File

@@ -42,6 +42,7 @@ import datafactory
import libraries import libraries
import logger import logger
import mobile_app import mobile_app
import newsletters
import newsletter_handler import newsletter_handler
import notification_handler import notification_handler
import notifiers import notifiers
@@ -202,6 +203,7 @@ def initialize(config_file):
logger.error(u"Could not perform upgrades: %s" % e) logger.error(u"Could not perform upgrades: %s" % e)
# Add notifier configs to logger blacklist # Add notifier configs to logger blacklist
newsletters.blacklist_logger()
notifiers.blacklist_logger() notifiers.blacklist_logger()
mobile_app.blacklist_logger() mobile_app.blacklist_logger()
@@ -516,7 +518,7 @@ def dbcheck():
# sessions table :: This is a temp table that logs currently active sessions # sessions table :: This is a temp table that logs currently active sessions
c_db.execute( c_db.execute(
'CREATE TABLE IF NOT EXISTS sessions (id INTEGER PRIMARY KEY AUTOINCREMENT, session_key INTEGER, ' 'CREATE TABLE IF NOT EXISTS sessions (id INTEGER PRIMARY KEY AUTOINCREMENT, session_key INTEGER, session_id TEXT, '
'transcode_key TEXT, rating_key INTEGER, section_id INTEGER, media_type TEXT, started INTEGER, stopped INTEGER, ' 'transcode_key TEXT, rating_key INTEGER, section_id INTEGER, media_type TEXT, started INTEGER, stopped INTEGER, '
'paused_counter INTEGER DEFAULT 0, state TEXT, user_id INTEGER, user TEXT, friendly_name TEXT, ' 'paused_counter INTEGER DEFAULT 0, state TEXT, user_id INTEGER, user TEXT, friendly_name TEXT, '
'ip_address TEXT, machine_id TEXT, player TEXT, product TEXT, platform TEXT, title TEXT, parent_title TEXT, ' 'ip_address TEXT, machine_id TEXT, player TEXT, product TEXT, platform TEXT, title TEXT, parent_title TEXT, '
@@ -1079,6 +1081,15 @@ def dbcheck():
'ALTER TABLE sessions ADD COLUMN live_uuid TEXT' 'ALTER TABLE sessions ADD COLUMN live_uuid TEXT'
) )
# Upgrade sessions table from earlier versions
try:
c_db.execute('SELECT session_id FROM sessions')
except sqlite3.OperationalError:
logger.debug(u"Altering database. Updating database table sessions.")
c_db.execute(
'ALTER TABLE sessions ADD COLUMN session_id TEXT'
)
# Upgrade sessions table from earlier versions # Upgrade sessions table from earlier versions
try: try:
c_db.execute('SELECT original_title FROM sessions') c_db.execute('SELECT original_title FROM sessions')

View File

@@ -15,18 +15,13 @@
from collections import defaultdict from collections import defaultdict
import json import json
import threading
import time import time
import re
import plexpy import plexpy
import database import database
import datafactory import helpers
import libraries import libraries
import log_reader
import logger import logger
import notification_handler
import notifiers
import pmsconnect import pmsconnect
import users import users
@@ -39,6 +34,7 @@ class ActivityProcessor(object):
def write_session(self, session=None, notify=True): def write_session(self, session=None, notify=True):
if session: if session:
values = {'session_key': session.get('session_key', ''), values = {'session_key': session.get('session_key', ''),
'session_id': session.get('session_id', ''),
'transcode_key': session.get('transcode_key', ''), 'transcode_key': session.get('transcode_key', ''),
'section_id': session.get('section_id', ''), 'section_id': session.get('section_id', ''),
'rating_key': session.get('rating_key', ''), 'rating_key': session.get('rating_key', ''),
@@ -272,14 +268,15 @@ class ActivityProcessor(object):
self.db.upsert(table_name='session_history', key_dict=keys, value_dict=values) self.db.upsert(table_name='session_history', key_dict=keys, value_dict=values)
# Check if we should group the session, select the last two rows from the user # Check if we should group the session, select the last two rows from the user
query = 'SELECT id, rating_key, view_offset, user_id, reference_id FROM session_history \ query = 'SELECT id, rating_key, view_offset, user_id, reference_id FROM session_history ' \
WHERE user_id = ? ORDER BY id DESC LIMIT 2 ' 'WHERE user_id = ? AND rating_key = ? ORDER BY id DESC LIMIT 2 '
args = [session['user_id']] args = [session['user_id'], session['rating_key']]
result = self.db.select(query=query, args=args) result = self.db.select(query=query, args=args)
new_session = prev_session = None new_session = prev_session = None
prev_progress_percent = media_watched_percent = 0
# Get the last insert row id # Get the last insert row id
last_id = self.db.last_insert_id() last_id = self.db.last_insert_id()
@@ -296,11 +293,23 @@ class ActivityProcessor(object):
'user_id': result[1]['user_id'], 'user_id': result[1]['user_id'],
'reference_id': result[1]['reference_id']} 'reference_id': result[1]['reference_id']}
watched_percent = {'movie': plexpy.CONFIG.MOVIE_WATCHED_PERCENT,
'episode': plexpy.CONFIG.TV_WATCHED_PERCENT,
'track': plexpy.CONFIG.MUSIC_WATCHED_PERCENT
}
prev_progress_percent = helpers.get_percent(prev_session['view_offset'], session['duration'])
media_watched_percent = watched_percent.get(session['media_type'], 0)
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 previous session view offset less than watched percent,
# and new session view offset is greater,
# then set the reference_id to the previous row,
# else set the reference_id to the new id
if prev_session is None and new_session is None: if prev_session is None and new_session is None:
args = [last_id, last_id] args = [last_id, last_id]
elif prev_session['rating_key'] == new_session['rating_key'] and prev_session['view_offset'] <= new_session['view_offset']: elif prev_progress_percent < media_watched_percent 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']]
@@ -458,6 +467,16 @@ class ActivityProcessor(object):
return None return None
def get_session_by_id(self, session_id=None):
if session_id:
session = self.db.select_single('SELECT * FROM sessions '
'WHERE session_id = ? ',
args=[session_id])
if session:
return session
return None
def set_session_state(self, session_key=None, state=None, **kwargs): def set_session_state(self, session_key=None, state=None, **kwargs):
if str(session_key).isdigit(): if str(session_key).isdigit():
values = {} values = {}

View File

@@ -23,6 +23,7 @@ PLATFORM = platform.system()
PLATFORM_RELEASE = platform.release() PLATFORM_RELEASE = platform.release()
PLATFORM_VERSION = platform.version() PLATFORM_VERSION = platform.version()
PLATFORM_LINUX_DISTRO = ' '.join(x for x in platform.linux_distribution() if x) PLATFORM_LINUX_DISTRO = ' '.join(x for x in platform.linux_distribution() if x)
PLATFORM_DEVICE_NAME = platform.node()
BRANCH = version.PLEXPY_BRANCH BRANCH = version.PLEXPY_BRANCH
RELEASE = version.PLEXPY_RELEASE_VERSION RELEASE = version.PLEXPY_RELEASE_VERSION
@@ -475,6 +476,7 @@ NOTIFICATION_PARAMETERS = [
{'name': 'Subtitle Language', 'type': 'str', 'value': 'subtitle_language', 'description': 'The subtitle language of the original media.'}, {'name': 'Subtitle Language', 'type': 'str', 'value': 'subtitle_language', 'description': 'The subtitle language of the original media.'},
{'name': 'Subtitle Language Code', 'type': 'str', 'value': 'subtitle_language_code', 'description': 'The subtitle language code of the original media.'}, {'name': 'Subtitle Language Code', 'type': 'str', 'value': 'subtitle_language_code', 'description': 'The subtitle language code of the original media.'},
{'name': 'File', 'type': 'str', 'value': 'file', 'description': 'The file path to the item.'}, {'name': 'File', 'type': 'str', 'value': 'file', 'description': 'The file path to the item.'},
{'name': 'Filename', 'type': 'str', 'value': 'filename', 'description': 'The file name of the item.'},
{'name': 'File Size', 'type': 'int', 'value': 'file_size', 'description': 'The file size of the item.'}, {'name': 'File Size', 'type': 'int', 'value': 'file_size', 'description': 'The file size of the item.'},
{'name': 'Section ID', 'type': 'int', 'value': 'section_id', 'description': 'The unique identifier for the library.'}, {'name': 'Section ID', 'type': 'int', 'value': 'section_id', 'description': 'The unique identifier for the library.'},
{'name': 'Rating Key', 'type': 'int', 'value': 'rating_key', 'description': 'The unique identifier for the movie, episode, or track.'}, {'name': 'Rating Key', 'type': 'int', 'value': 'rating_key', 'description': 'The unique identifier for the movie, episode, or track.'},

View File

@@ -54,6 +54,7 @@ _CONFIG_DEFINITIONS = {
'PMS_TOKEN': (str, 'PMS', ''), 'PMS_TOKEN': (str, 'PMS', ''),
'PMS_SSL': (int, 'PMS', 0), 'PMS_SSL': (int, 'PMS', 0),
'PMS_URL': (str, 'PMS', ''), 'PMS_URL': (str, 'PMS', ''),
'PMS_URL_OVERRIDE': (str, 'PMS', ''),
'PMS_URL_MANUAL': (int, 'PMS', 0), 'PMS_URL_MANUAL': (int, 'PMS', 0),
'PMS_USE_BIF': (int, 'PMS', 0), 'PMS_USE_BIF': (int, 'PMS', 0),
'PMS_UUID': (str, 'PMS', ''), 'PMS_UUID': (str, 'PMS', ''),

View File

@@ -65,7 +65,7 @@ class DataFactory(object):
columns = [ columns = [
'session_history.reference_id', 'session_history.reference_id',
'session_history.id', 'session_history.id',
'started AS date', 'MAX(started) AS date',
'MIN(started) AS started', 'MIN(started) AS started',
'MAX(stopped) AS stopped', 'MAX(stopped) AS stopped',
'SUM(CASE WHEN stopped > 0 THEN (stopped - started) ELSE 0 END) - \ 'SUM(CASE WHEN stopped > 0 THEN (stopped - started) ELSE 0 END) - \
@@ -1467,7 +1467,7 @@ class DataFactory(object):
result = monitor_db.select(query=query.format('parent_rating_key', 'rating_key'), result = monitor_db.select(query=query.format('parent_rating_key', 'rating_key'),
args=[item['parent_rating_key']]) args=[item['parent_rating_key']])
for item in result: for item in result:
key = item['media_index'] key = item['media_index'] if item['media_index'] else item['title']
children.update({key: {'rating_key': item['rating_key']}}) children.update({key: {'rating_key': item['rating_key']}})
key = item['parent_media_index'] if match_type == 'index' else item['parent_title'] key = item['parent_media_index'] if match_type == 'index' else item['parent_title']

View File

@@ -50,7 +50,9 @@ class Graphs(object):
'SUM(CASE WHEN media_type = "episode" THEN 1 ELSE 0 END) AS tv_count, ' \ 'SUM(CASE WHEN media_type = "episode" THEN 1 ELSE 0 END) AS tv_count, ' \
'SUM(CASE WHEN media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \ 'SUM(CASE WHEN media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \
'SUM(CASE WHEN media_type = "track" THEN 1 ELSE 0 END) AS music_count ' \ 'SUM(CASE WHEN media_type = "track" THEN 1 ELSE 0 END) AS music_count ' \
'FROM (SELECT * FROM session_history GROUP BY %s) AS session_history ' \ 'FROM (SELECT * FROM session_history ' \
'GROUP BY date(started, "unixepoch", "localtime"), %s) ' \
'AS session_history ' \
'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime") %s' \ 'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime") %s' \
'GROUP BY date_played ' \ 'GROUP BY date_played ' \
'ORDER BY started ASC' % (group_by, time_range, user_cond) 'ORDER BY started ASC' % (group_by, time_range, user_cond)
@@ -147,7 +149,9 @@ class Graphs(object):
'SUM(CASE WHEN media_type = "episode" THEN 1 ELSE 0 END) AS tv_count, ' \ 'SUM(CASE WHEN media_type = "episode" THEN 1 ELSE 0 END) AS tv_count, ' \
'SUM(CASE WHEN media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \ 'SUM(CASE WHEN media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \
'SUM(CASE WHEN media_type = "track" THEN 1 ELSE 0 END) AS music_count ' \ 'SUM(CASE WHEN media_type = "track" THEN 1 ELSE 0 END) AS music_count ' \
'FROM (SELECT * FROM session_history GROUP BY %s) AS session_history ' \ 'FROM (SELECT * FROM session_history ' \
'GROUP BY strftime("%%w", datetime(started, "unixepoch", "localtime")), %s) ' \
'AS session_history ' \
'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime") %s' \ 'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime") %s' \
'GROUP BY dayofweek ' \ 'GROUP BY dayofweek ' \
'ORDER BY daynumber' % (group_by, time_range, user_cond) 'ORDER BY daynumber' % (group_by, time_range, user_cond)
@@ -245,7 +249,9 @@ class Graphs(object):
'SUM(CASE WHEN media_type = "episode" THEN 1 ELSE 0 END) AS tv_count, ' \ 'SUM(CASE WHEN media_type = "episode" THEN 1 ELSE 0 END) AS tv_count, ' \
'SUM(CASE WHEN media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \ 'SUM(CASE WHEN media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \
'SUM(CASE WHEN media_type = "track" THEN 1 ELSE 0 END) AS music_count ' \ 'SUM(CASE WHEN media_type = "track" THEN 1 ELSE 0 END) AS music_count ' \
'FROM (SELECT * FROM session_history GROUP BY %s) AS session_history ' \ 'FROM (SELECT * FROM session_history ' \
'GROUP BY strftime("%%H", datetime(started, "unixepoch", "localtime")) , %s) ' \
'AS session_history ' \
'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime") %s' \ 'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime") %s' \
'GROUP BY hourofday ' \ 'GROUP BY hourofday ' \
'ORDER BY hourofday' % (group_by, time_range, user_cond) 'ORDER BY hourofday' % (group_by, time_range, user_cond)
@@ -335,7 +341,9 @@ class Graphs(object):
'SUM(CASE WHEN media_type = "episode" THEN 1 ELSE 0 END) AS tv_count, ' \ 'SUM(CASE WHEN media_type = "episode" THEN 1 ELSE 0 END) AS tv_count, ' \
'SUM(CASE WHEN media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \ 'SUM(CASE WHEN media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \
'SUM(CASE WHEN media_type = "track" THEN 1 ELSE 0 END) AS music_count ' \ 'SUM(CASE WHEN media_type = "track" THEN 1 ELSE 0 END) AS music_count ' \
'FROM (SELECT * FROM session_history GROUP BY %s) AS session_history ' \ 'FROM (SELECT * FROM session_history ' \
'GROUP BY strftime("%%Y-%%m", datetime(started, "unixepoch", "localtime")), %s) ' \
'AS session_history ' \
'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s months", "localtime") %s' \ 'WHERE datetime(started, "unixepoch", "localtime") >= datetime("now", "-%s months", "localtime") %s' \
'GROUP BY strftime("%%Y-%%m", datetime(started, "unixepoch", "localtime")) ' \ 'GROUP BY strftime("%%Y-%%m", datetime(started, "unixepoch", "localtime")) ' \
'ORDER BY datestring DESC LIMIT %s' % (group_by, time_range, user_cond, time_range) 'ORDER BY datestring DESC LIMIT %s' % (group_by, time_range, user_cond, time_range)
@@ -591,7 +599,9 @@ class Graphs(object):
'THEN 1 ELSE 0 END) AS ds_count, ' \ 'THEN 1 ELSE 0 END) AS ds_count, ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \ 'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \
'THEN 1 ELSE 0 END) AS tc_count ' \ 'THEN 1 ELSE 0 END) AS tc_count ' \
'FROM (SELECT * FROM session_history GROUP BY %s) AS session_history ' \ 'FROM (SELECT * FROM session_history ' \
'GROUP BY date(session_history.started, "unixepoch", "localtime"), %s) ' \
'AS session_history ' \
'JOIN session_history_media_info ON session_history.id = session_history_media_info.id ' \ 'JOIN session_history_media_info ON session_history.id = session_history_media_info.id ' \
'WHERE (datetime(started, "unixepoch", "localtime") >= ' \ 'WHERE (datetime(started, "unixepoch", "localtime") >= ' \
'datetime("now", "-%s days", "localtime")) AND ' \ 'datetime("now", "-%s days", "localtime")) AND ' \

View File

@@ -39,12 +39,13 @@ class HTTPHandler(object):
else: else:
self.urls = urls self.urls = urls
self.headers = {'X-Plex-Device-Name': 'Tautulli', self.headers = {'X-Plex-Product': 'Tautulli',
'X-Plex-Product': 'Tautulli',
'X-Plex-Version': plexpy.common.RELEASE, 'X-Plex-Version': plexpy.common.RELEASE,
'X-Plex-Client-Identifier': plexpy.CONFIG.PMS_UUID,
'X-Plex-Platform': plexpy.common.PLATFORM, 'X-Plex-Platform': plexpy.common.PLATFORM,
'X-Plex-Platform-Version': plexpy.common.PLATFORM_RELEASE, 'X-Plex-Platform-Version': plexpy.common.PLATFORM_RELEASE,
'X-Plex-Client-Identifier': plexpy.CONFIG.PMS_UUID, 'X-Plex-Device': 'Web',
'X-Plex-Device-Name': plexpy.common.PLATFORM_DEVICE_NAME
} }
self.token = token self.token = token
@@ -178,5 +179,5 @@ class HTTPHandler(object):
return output return output
except Exception as e: except Exception as e:
logger.warn(u"Failed format response from uri %s to %s error %s" % (self.uri, self.response_type, e)) logger.warn(u"Failed format response from uri %s to %s error %s" % (self.uri, self.output_format, e))
return None return None

View File

@@ -28,6 +28,7 @@ import traceback
import plexpy import plexpy
import helpers import helpers
from plexpy.config import _BLACKLIST_KEYS, _WHITELIST_KEYS
# These settings are for file logging only # These settings are for file logging only
FILENAME = "tautulli.log" FILENAME = "tautulli.log"
@@ -48,6 +49,20 @@ logger_plex_websocket = logging.getLogger("plex_websocket")
# Global queue for multiprocessing logging # Global queue for multiprocessing logging
queue = None queue = None
def blacklist_config(config):
blacklist = set()
blacklist_keys = ['HOOK', 'APIKEY', 'KEY', 'PASSWORD', 'TOKEN']
for key, value in config.iteritems():
if isinstance(value, basestring) and len(value.strip()) > 5 and \
key.upper() not in _WHITELIST_KEYS and (key.upper() in blacklist_keys or
any(bk in key.upper() for bk in _BLACKLIST_KEYS)):
blacklist.add(value.strip())
_BLACKLIST_WORDS.update(blacklist)
class NoThreadFilter(logging.Filter): class NoThreadFilter(logging.Filter):
""" """
Log filter for the current thread Log filter for the current thread

View File

@@ -138,7 +138,5 @@ def set_last_seen(device_token=None):
def blacklist_logger(): def blacklist_logger():
devices = get_mobile_devices() devices = get_mobile_devices()
for d in devices:
blacklist = set(d['device_token'] for d in devices) logger.blacklist_config(d)
logger._BLACKLIST_WORDS.update(blacklist)

View File

@@ -196,6 +196,7 @@ def add_newsletter_config(agent_id=None, **kwargs):
newsletter_id = db.last_insert_id() newsletter_id = db.last_insert_id()
logger.info(u"Tautulli Newsletters :: Added new newsletter agent: %s (newsletter_id %s)." logger.info(u"Tautulli Newsletters :: Added new newsletter agent: %s (newsletter_id %s)."
% (agent['label'], newsletter_id)) % (agent['label'], newsletter_id))
blacklist_logger()
return newsletter_id return newsletter_id
except Exception as e: except Exception as e:
logger.warn(u"Tautulli Newsletters :: Unable to add newsletter agent: %s." % e) logger.warn(u"Tautulli Newsletters :: Unable to add newsletter agent: %s." % e)
@@ -254,6 +255,7 @@ def set_newsletter_config(newsletter_id=None, agent_id=None, **kwargs):
logger.info(u"Tautulli Newsletters :: Updated newsletter agent: %s (newsletter_id %s)." logger.info(u"Tautulli Newsletters :: Updated newsletter agent: %s (newsletter_id %s)."
% (agent['label'], newsletter_id)) % (agent['label'], newsletter_id))
newsletter_handler.schedule_newsletters(newsletter_id=newsletter_id) newsletter_handler.schedule_newsletters(newsletter_id=newsletter_id)
blacklist_logger()
return True return True
except Exception as e: except Exception as e:
logger.warn(u"Tautulli Newsletters :: Unable to update newsletter agent: %s." % e) logger.warn(u"Tautulli Newsletters :: Unable to update newsletter agent: %s." % e)
@@ -274,6 +276,17 @@ def send_newsletter(newsletter_id=None, subject=None, body=None, message=None, n
logger.debug(u"Tautulli Newsletters :: Notification requested but no newsletter_id received.") logger.debug(u"Tautulli Newsletters :: Notification requested but no newsletter_id received.")
def blacklist_logger():
db = database.MonitorDatabase()
notifiers = db.select('SELECT newsletter_config, email_config FROM newsletters')
for n in notifiers:
config = json.loads(n['newsletter_config'] or '{}')
logger.blacklist_config(config)
email_config = json.loads(n['email_config'] or '{}')
logger.blacklist_config(email_config)
def serve_template(templatename, **kwargs): def serve_template(templatename, **kwargs):
if plexpy.CONFIG.NEWSLETTER_CUSTOM_DIR: if plexpy.CONFIG.NEWSLETTER_CUSTOM_DIR:
template_dir = plexpy.CONFIG.NEWSLETTER_CUSTOM_DIR template_dir = plexpy.CONFIG.NEWSLETTER_CUSTOM_DIR

View File

@@ -883,6 +883,7 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
'subtitle_language': notify_params['subtitle_language'], 'subtitle_language': notify_params['subtitle_language'],
'subtitle_language_code': notify_params['subtitle_language_code'], 'subtitle_language_code': notify_params['subtitle_language_code'],
'file': notify_params['file'], 'file': notify_params['file'],
'filename': os.path.basename(notify_params['file']),
'file_size': helpers.humanFileSize(notify_params['file_size']), 'file_size': helpers.humanFileSize(notify_params['file_size']),
'indexes': notify_params['indexes'], 'indexes': notify_params['indexes'],
'section_id': notify_params['section_id'], 'section_id': notify_params['section_id'],

View File

@@ -62,7 +62,6 @@ import mobile_app
import pmsconnect import pmsconnect
import request import request
import users import users
from plexpy.config import _BLACKLIST_KEYS, _WHITELIST_KEYS
BROWSER_NOTIFIERS = {} BROWSER_NOTIFIERS = {}
@@ -612,17 +611,9 @@ def blacklist_logger():
db = database.MonitorDatabase() db = database.MonitorDatabase()
notifiers = db.select('SELECT notifier_config FROM notifiers') notifiers = db.select('SELECT notifier_config FROM notifiers')
blacklist = set()
blacklist_keys = ['hook', 'key', 'password', 'token']
for n in notifiers: for n in notifiers:
config = json.loads(n['notifier_config'] or '{}') config = json.loads(n['notifier_config'] or '{}')
for key, value in config.iteritems(): logger.blacklist_config(config)
if isinstance(value, basestring) and len(value.strip()) > 5 and \
key.upper() not in _WHITELIST_KEYS and (key.upper() in blacklist_keys or any(bk in key.upper() for bk in _BLACKLIST_KEYS)):
blacklist.add(value.strip())
logger._BLACKLIST_WORDS.update(blacklist)
class PrettyMetadata(object): class PrettyMetadata(object):
@@ -682,13 +673,13 @@ class PrettyMetadata(object):
provider_name = 'Trakt.tv' provider_name = 'Trakt.tv'
elif provider == 'lastfm': elif provider == 'lastfm':
provider_name = 'Last.fm' provider_name = 'Last.fm'
else: # else:
if self.media_type == 'movie': # if self.media_type == 'movie':
provider_name = 'IMDb' # provider_name = 'IMDb'
elif self.media_type in ('show', 'season', 'episode'): # elif self.media_type in ('show', 'season', 'episode'):
provider_name = 'TheTVDB' # provider_name = 'TheTVDB'
elif self.media_type in ('artist', 'album', 'track'): # elif self.media_type in ('artist', 'album', 'track'):
provider_name = 'Last.fm' # provider_name = 'Last.fm'
return provider_name return provider_name
def get_provider_link(self, provider=None): def get_provider_link(self, provider=None):
@@ -697,13 +688,13 @@ class PrettyMetadata(object):
provider_link = self.get_plex_url() provider_link = self.get_plex_url()
elif provider: elif provider:
provider_link = self.parameters.get(provider + '_url', '') provider_link = self.parameters.get(provider + '_url', '')
else: # else:
if self.media_type == 'movie': # if self.media_type == 'movie':
provider_link = self.parameters.get('imdb_url', '') # provider_link = self.parameters.get('imdb_url', '')
elif self.media_type in ('show', 'season', 'episode'): # elif self.media_type in ('show', 'season', 'episode'):
provider_link = self.parameters.get('thetvdb_url', '') # provider_link = self.parameters.get('thetvdb_url', '')
elif self.media_type in ('artist', 'album', 'track'): # elif self.media_type in ('artist', 'album', 'track'):
provider_link = self.parameters.get('lastfm_url', '') # provider_link = self.parameters.get('lastfm_url', '')
return provider_link return provider_link
def get_caption(self, provider): def get_caption(self, provider):
@@ -711,6 +702,7 @@ class PrettyMetadata(object):
return 'View on ' + provider_name return 'View on ' + provider_name
def get_title(self, divider='-'): def get_title(self, divider='-'):
title = ''
if self.media_type == 'movie': if self.media_type == 'movie':
title = '%s (%s)' % (self.parameters['title'], self.parameters['year']) title = '%s (%s)' % (self.parameters['title'], self.parameters['year'])
elif self.media_type == 'show': elif self.media_type == 'show':
@@ -1251,7 +1243,7 @@ class DISCORD(Notifier):
{'label': 'Movie Link Source', {'label': 'Movie Link Source',
'value': self.config['movie_provider'], 'value': self.config['movie_provider'],
'name': 'discord_movie_provider', 'name': 'discord_movie_provider',
'description': 'Select the source for movie links on the info cards. Leave blank for default.<br>' 'description': 'Select the source for movie links on the info cards. Leave blank to disable.<br>'
'Note: 3rd party API lookup may need to be enabled under the notifications settings tab.', 'Note: 3rd party API lookup may need to be enabled under the notifications settings tab.',
'input_type': 'select', 'input_type': 'select',
'select_options': PrettyMetadata().get_movie_providers() 'select_options': PrettyMetadata().get_movie_providers()
@@ -1259,7 +1251,7 @@ class DISCORD(Notifier):
{'label': 'TV Show Link Source', {'label': 'TV Show Link Source',
'value': self.config['tv_provider'], 'value': self.config['tv_provider'],
'name': 'discord_tv_provider', 'name': 'discord_tv_provider',
'description': 'Select the source for tv show links on the info cards. Leave blank for default.<br>' 'description': 'Select the source for tv show links on the info cards. Leave blank to disable.<br>'
'Note: 3rd party API lookup may need to be enabled under the notifications settings tab.', 'Note: 3rd party API lookup may need to be enabled under the notifications settings tab.',
'input_type': 'select', 'input_type': 'select',
'select_options': PrettyMetadata().get_tv_providers() 'select_options': PrettyMetadata().get_tv_providers()
@@ -1267,7 +1259,7 @@ class DISCORD(Notifier):
{'label': 'Music Link Source', {'label': 'Music Link Source',
'value': self.config['music_provider'], 'value': self.config['music_provider'],
'name': 'discord_music_provider', 'name': 'discord_music_provider',
'description': 'Select the source for music links on the info cards. Leave blank for default.', 'description': 'Select the source for music links on the info cards. Leave blank to disable.',
'input_type': 'select', 'input_type': 'select',
'select_options': PrettyMetadata().get_music_providers() 'select_options': PrettyMetadata().get_music_providers()
} }
@@ -1600,7 +1592,7 @@ class FACEBOOK(Notifier):
{'label': 'Movie Link Source', {'label': 'Movie Link Source',
'value': self.config['movie_provider'], 'value': self.config['movie_provider'],
'name': 'facebook_movie_provider', 'name': 'facebook_movie_provider',
'description': 'Select the source for movie links on the info cards. Leave blank for default.<br>' 'description': 'Select the source for movie links on the info cards. Leave blank to disable.<br>'
'Note: 3rd party API lookup may need to be enabled under the notifications settings tab.', 'Note: 3rd party API lookup may need to be enabled under the notifications settings tab.',
'input_type': 'select', 'input_type': 'select',
'select_options': PrettyMetadata().get_movie_providers() 'select_options': PrettyMetadata().get_movie_providers()
@@ -1608,7 +1600,7 @@ class FACEBOOK(Notifier):
{'label': 'TV Show Link Source', {'label': 'TV Show Link Source',
'value': self.config['tv_provider'], 'value': self.config['tv_provider'],
'name': 'facebook_tv_provider', 'name': 'facebook_tv_provider',
'description': 'Select the source for tv show links on the info cards. Leave blank for default.<br>' 'description': 'Select the source for tv show links on the info cards. Leave blank to disable.<br>'
'Note: 3rd party API lookup may need to be enabled under the notifications settings tab.', 'Note: 3rd party API lookup may need to be enabled under the notifications settings tab.',
'input_type': 'select', 'input_type': 'select',
'select_options': PrettyMetadata().get_tv_providers() 'select_options': PrettyMetadata().get_tv_providers()
@@ -1616,7 +1608,7 @@ class FACEBOOK(Notifier):
{'label': 'Music Link Source', {'label': 'Music Link Source',
'value': self.config['music_provider'], 'value': self.config['music_provider'],
'name': 'facebook_music_provider', 'name': 'facebook_music_provider',
'description': 'Select the source for music links on the info cards. Leave blank for default.', 'description': 'Select the source for music links on the info cards. Leave blank to disable.',
'input_type': 'select', 'input_type': 'select',
'select_options': PrettyMetadata().get_music_providers() 'select_options': PrettyMetadata().get_music_providers()
} }
@@ -1936,7 +1928,7 @@ class HIPCHAT(Notifier):
{'label': 'Movie Link Source', {'label': 'Movie Link Source',
'value': self.config['movie_provider'], 'value': self.config['movie_provider'],
'name': 'hipchat_movie_provider', 'name': 'hipchat_movie_provider',
'description': 'Select the source for movie links on the info cards. Leave blank for default.<br>' 'description': 'Select the source for movie links on the info cards. Leave blank to disable.<br>'
'Note: 3rd party API lookup may need to be enabled under the notifications settings tab.', 'Note: 3rd party API lookup may need to be enabled under the notifications settings tab.',
'input_type': 'select', 'input_type': 'select',
'select_options': PrettyMetadata().get_movie_providers() 'select_options': PrettyMetadata().get_movie_providers()
@@ -1944,7 +1936,7 @@ class HIPCHAT(Notifier):
{'label': 'TV Show Link Source', {'label': 'TV Show Link Source',
'value': self.config['tv_provider'], 'value': self.config['tv_provider'],
'name': 'hipchat_tv_provider', 'name': 'hipchat_tv_provider',
'description': 'Select the source for tv show links on the info cards. Leave blank for default.<br>' 'description': 'Select the source for tv show links on the info cards. Leave blank to disable.<br>'
'Note: 3rd party API lookup may need to be enabled under the notifications settings tab.', 'Note: 3rd party API lookup may need to be enabled under the notifications settings tab.',
'input_type': 'select', 'input_type': 'select',
'select_options': PrettyMetadata().get_tv_providers() 'select_options': PrettyMetadata().get_tv_providers()
@@ -1952,7 +1944,7 @@ class HIPCHAT(Notifier):
{'label': 'Music Link Source', {'label': 'Music Link Source',
'value': self.config['music_provider'], 'value': self.config['music_provider'],
'name': 'hipchat_music_provider', 'name': 'hipchat_music_provider',
'description': 'Select the source for music links on the info cards. Leave blank for default.', 'description': 'Select the source for music links on the info cards. Leave blank to disable.',
'input_type': 'select', 'input_type': 'select',
'select_options': PrettyMetadata().get_music_providers() 'select_options': PrettyMetadata().get_music_providers()
} }
@@ -2144,7 +2136,7 @@ class JOIN(Notifier):
{'label': 'Movie Link Source', {'label': 'Movie Link Source',
'value': self.config['movie_provider'], 'value': self.config['movie_provider'],
'name': 'join_movie_provider', 'name': 'join_movie_provider',
'description': 'Select the source for movie links in the notificaation. Leave blank for default.<br>' 'description': 'Select the source for movie links in the notification. Leave blank to disable.<br>'
'Note: 3rd party API lookup may need to be enabled under the notifications settings tab.', 'Note: 3rd party API lookup may need to be enabled under the notifications settings tab.',
'input_type': 'select', 'input_type': 'select',
'select_options': PrettyMetadata().get_movie_providers() 'select_options': PrettyMetadata().get_movie_providers()
@@ -2152,7 +2144,7 @@ class JOIN(Notifier):
{'label': 'TV Show Link Source', {'label': 'TV Show Link Source',
'value': self.config['tv_provider'], 'value': self.config['tv_provider'],
'name': 'join_tv_provider', 'name': 'join_tv_provider',
'description': 'Select the source for tv show links in the notificaation. Leave blank for default.<br>' 'description': 'Select the source for tv show links in the notification. Leave blank to disable.<br>'
'Note: 3rd party API lookup may need to be enabled under the notifications settings tab.', 'Note: 3rd party API lookup may need to be enabled under the notifications settings tab.',
'input_type': 'select', 'input_type': 'select',
'select_options': PrettyMetadata().get_tv_providers() 'select_options': PrettyMetadata().get_tv_providers()
@@ -2160,7 +2152,7 @@ class JOIN(Notifier):
{'label': 'Music Link Source', {'label': 'Music Link Source',
'value': self.config['music_provider'], 'value': self.config['music_provider'],
'name': 'join_music_provider', 'name': 'join_music_provider',
'description': 'Select the source for music links in the notificaation. Leave blank for default.', 'description': 'Select the source for music links in the notification. Leave blank to disable.',
'input_type': 'select', 'input_type': 'select',
'select_options': PrettyMetadata().get_music_providers() 'select_options': PrettyMetadata().get_music_providers()
} }
@@ -2450,7 +2442,8 @@ class PLEX(Notifier):
else: else:
return request.request_content(url) return request.request_content(url)
def _sendjson(self, host, method, params={}): def _sendjson(self, host, method, params=None):
params = params or {}
data = [{'id': 0, 'jsonrpc': '2.0', 'method': method, 'params': params}] data = [{'id': 0, 'jsonrpc': '2.0', 'method': method, 'params': params}]
headers = {'Content-Type': 'application/json'} headers = {'Content-Type': 'application/json'}
url = host + '/jsonrpc' url = host + '/jsonrpc'
@@ -2928,7 +2921,7 @@ class PUSHOVER(Notifier):
{'label': 'Movie Link Source', {'label': 'Movie Link Source',
'value': self.config['movie_provider'], 'value': self.config['movie_provider'],
'name': 'pushover_movie_provider', 'name': 'pushover_movie_provider',
'description': 'Select the source for movie links in the notification. Leave blank for default.<br>' 'description': 'Select the source for movie links in the notification. Leave blank to disable.<br>'
'Note: 3rd party API lookup may need to be enabled under the notifications settings tab.', 'Note: 3rd party API lookup may need to be enabled under the notifications settings tab.',
'input_type': 'select', 'input_type': 'select',
'select_options': PrettyMetadata().get_movie_providers() 'select_options': PrettyMetadata().get_movie_providers()
@@ -2936,7 +2929,7 @@ class PUSHOVER(Notifier):
{'label': 'TV Show Link Source', {'label': 'TV Show Link Source',
'value': self.config['tv_provider'], 'value': self.config['tv_provider'],
'name': 'pushover_tv_provider', 'name': 'pushover_tv_provider',
'description': 'Select the source for tv show links in the notification. Leave blank for default.<br>' 'description': 'Select the source for tv show links in the notification. Leave blank to disable.<br>'
'Note: 3rd party API lookup may need to be enabled under the notifications settings tab.', 'Note: 3rd party API lookup may need to be enabled under the notifications settings tab.',
'input_type': 'select', 'input_type': 'select',
'select_options': PrettyMetadata().get_tv_providers() 'select_options': PrettyMetadata().get_tv_providers()
@@ -2944,7 +2937,7 @@ class PUSHOVER(Notifier):
{'label': 'Music Link Source', {'label': 'Music Link Source',
'value': self.config['music_provider'], 'value': self.config['music_provider'],
'name': 'pushover_music_provider', 'name': 'pushover_music_provider',
'description': 'Select the source for music links in the notification. Leave blank for default.', 'description': 'Select the source for music links in the notification. Leave blank to disable.',
'input_type': 'select', 'input_type': 'select',
'select_options': PrettyMetadata().get_music_providers() 'select_options': PrettyMetadata().get_music_providers()
} }
@@ -3303,7 +3296,7 @@ class SLACK(Notifier):
{'label': 'Movie Link Source', {'label': 'Movie Link Source',
'value': self.config['movie_provider'], 'value': self.config['movie_provider'],
'name': 'slack_movie_provider', 'name': 'slack_movie_provider',
'description': 'Select the source for movie links on the info cards. Leave blank for default.<br>' 'description': 'Select the source for movie links on the info cards. Leave blank to disable.<br>'
'Note: 3rd party API lookup may need to be enabled under the notifications settings tab.', 'Note: 3rd party API lookup may need to be enabled under the notifications settings tab.',
'input_type': 'select', 'input_type': 'select',
'select_options': PrettyMetadata().get_movie_providers() 'select_options': PrettyMetadata().get_movie_providers()
@@ -3311,7 +3304,7 @@ class SLACK(Notifier):
{'label': 'TV Show Link Source', {'label': 'TV Show Link Source',
'value': self.config['tv_provider'], 'value': self.config['tv_provider'],
'name': 'slack_tv_provider', 'name': 'slack_tv_provider',
'description': 'Select the source for tv show links on the info cards. Leave blank for default.<br>' 'description': 'Select the source for tv show links on the info cards. Leave blank to disable.<br>'
'Note: 3rd party API lookup may need to be enabled under the notifications settings tab.', 'Note: 3rd party API lookup may need to be enabled under the notifications settings tab.',
'input_type': 'select', 'input_type': 'select',
'select_options': PrettyMetadata().get_tv_providers() 'select_options': PrettyMetadata().get_tv_providers()
@@ -3319,7 +3312,7 @@ class SLACK(Notifier):
{'label': 'Music Link Source', {'label': 'Music Link Source',
'value': self.config['music_provider'], 'value': self.config['music_provider'],
'name': 'slack_music_provider', 'name': 'slack_music_provider',
'description': 'Select the source for music links on the info cards. Leave blank for default.', 'description': 'Select the source for music links on the info cards. Leave blank to disable.',
'input_type': 'select', 'input_type': 'select',
'select_options': PrettyMetadata().get_music_providers() 'select_options': PrettyMetadata().get_music_providers()
} }
@@ -3553,7 +3546,8 @@ class XBMC(Notifier):
else: else:
return request.request_content(url) return request.request_content(url)
def _sendjson(self, host, method, params={}): def _sendjson(self, host, method, params=None):
params = params or {}
data = [{'id': 0, 'jsonrpc': '2.0', 'method': method, 'params': params}] data = [{'id': 0, 'jsonrpc': '2.0', 'method': method, 'params': params}]
headers = {'Content-Type': 'application/json'} headers = {'Content-Type': 'application/json'}
url = host + '/jsonrpc' url = host + '/jsonrpc'
@@ -3714,7 +3708,7 @@ class ZAPIER(Notifier):
{'label': 'Movie Link Source', {'label': 'Movie Link Source',
'value': self.config['movie_provider'], 'value': self.config['movie_provider'],
'name': 'zapier_movie_provider', 'name': 'zapier_movie_provider',
'description': 'Select the source for movie links in the notification. Leave blank for default.<br>' 'description': 'Select the source for movie links in the notification. Leave blank to disable.<br>'
'Note: 3rd party API lookup may need to be enabled under the notifications settings tab.', 'Note: 3rd party API lookup may need to be enabled under the notifications settings tab.',
'input_type': 'select', 'input_type': 'select',
'select_options': PrettyMetadata().get_movie_providers() 'select_options': PrettyMetadata().get_movie_providers()
@@ -3722,7 +3716,7 @@ class ZAPIER(Notifier):
{'label': 'TV Show Link Source', {'label': 'TV Show Link Source',
'value': self.config['tv_provider'], 'value': self.config['tv_provider'],
'name': 'zapier_tv_provider', 'name': 'zapier_tv_provider',
'description': 'Select the source for tv show links in the notification. Leave blank for default.<br>' 'description': 'Select the source for tv show links in the notification. Leave blank to disable.<br>'
'Note: 3rd party API lookup may need to be enabled under the notifications settings tab.', 'Note: 3rd party API lookup may need to be enabled under the notifications settings tab.',
'input_type': 'select', 'input_type': 'select',
'select_options': PrettyMetadata().get_tv_providers() 'select_options': PrettyMetadata().get_tv_providers()
@@ -3730,7 +3724,7 @@ class ZAPIER(Notifier):
{'label': 'Music Link Source', {'label': 'Music Link Source',
'value': self.config['music_provider'], 'value': self.config['music_provider'],
'name': 'zapier_music_provider', 'name': 'zapier_music_provider',
'description': 'Select the source for music links in the notification. Leave blank for default.', 'description': 'Select the source for music links in the notification. Leave blank to disable.',
'input_type': 'select', 'input_type': 'select',
'select_options': PrettyMetadata().get_music_providers() 'select_options': PrettyMetadata().get_music_providers()
} }

View File

@@ -19,6 +19,7 @@ import time
import urllib import urllib
import plexpy import plexpy
import activity_processor
import common import common
import helpers import helpers
import http_handler import http_handler
@@ -482,6 +483,7 @@ class PmsConnect(object):
actors = [] actors = []
genres = [] genres = []
labels = [] labels = []
collections = []
if m.getElementsByTagName('Director'): if m.getElementsByTagName('Director'):
for director in m.getElementsByTagName('Director'): for director in m.getElementsByTagName('Director'):
@@ -503,6 +505,10 @@ class PmsConnect(object):
for label in m.getElementsByTagName('Label'): for label in m.getElementsByTagName('Label'):
labels.append(helpers.get_xml_attr(label, 'tag')) labels.append(helpers.get_xml_attr(label, 'tag'))
if m.getElementsByTagName('Collection'):
for collection in m.getElementsByTagName('Collection'):
collections.append(helpers.get_xml_attr(collection, 'tag'))
recent_item = {'media_type': helpers.get_xml_attr(m, 'type'), recent_item = {'media_type': helpers.get_xml_attr(m, 'type'),
'section_id': helpers.get_xml_attr(m, 'librarySectionID'), 'section_id': helpers.get_xml_attr(m, 'librarySectionID'),
'library_name': helpers.get_xml_attr(m, 'librarySectionTitle'), 'library_name': helpers.get_xml_attr(m, 'librarySectionTitle'),
@@ -540,6 +546,7 @@ class PmsConnect(object):
'actors': actors, 'actors': actors,
'genres': genres, 'genres': genres,
'labels': labels, 'labels': labels,
'collections': collections,
'full_title': helpers.get_xml_attr(m, 'title'), 'full_title': helpers.get_xml_attr(m, 'title'),
'child_count': helpers.get_xml_attr(m, 'childCount') 'child_count': helpers.get_xml_attr(m, 'childCount')
} }
@@ -1919,7 +1926,7 @@ class PmsConnect(object):
return session_output return session_output
def terminate_session(self, session_id='', message=''): def terminate_session(self, session_key='', session_id='', message=''):
""" """
Terminates a streaming session. Terminates a streaming session.
@@ -1927,10 +1934,22 @@ class PmsConnect(object):
""" """
message = message or 'The server owner has ended the stream.' message = message or 'The server owner has ended the stream.'
if session_key and not session_id:
ap = activity_processor.ActivityProcessor()
session = ap.get_session_by_key(session_key=session_key)
session_id = session['session_id']
elif session_id and not session_key:
ap = activity_processor.ActivityProcessor()
session = ap.get_session_by_id(session_id=session_id)
session_key = session['session_key']
if session_id: if session_id:
logger.info(u"Tautulli Pmsconnect :: Terminating session %s (session_id %s)." % (session_key, session_id))
result = self.get_sessions_terminate(session_id=session_id, reason=urllib.quote_plus(message)) result = self.get_sessions_terminate(session_id=session_id, reason=urllib.quote_plus(message))
return result return result
else: else:
logger.warn(u"Tautulli Pmsconnect :: Failed to terminate session %s. Missing session_id." % session_key)
return False return False
def get_item_children(self, rating_key='', get_grandchildren=False): def get_item_children(self, rating_key='', get_grandchildren=False):
@@ -2664,7 +2683,7 @@ class PmsConnect(object):
child_title = helpers.get_xml_attr(item, 'title') child_title = helpers.get_xml_attr(item, 'title')
if child_rating_key: if child_rating_key:
key = int(child_index) key = int(child_index) if child_index else child_title
children.update({key: {'rating_key': int(child_rating_key)}}) children.update({key: {'rating_key': int(child_rating_key)}})
key = int(parent_index) if match_type == 'index' else parent_title key = int(parent_index) if match_type == 'index' else parent_title
@@ -2676,9 +2695,9 @@ class PmsConnect(object):
key = 0 if match_type == 'index' else title key = 0 if match_type == 'index' else title
key_list = {key: {'rating_key': int(rating_key), key_list = {key: {'rating_key': int(rating_key),
'children': parents}, 'children': parents},
'section_id': section_id, 'section_id': section_id,
'library_name': library_name 'library_name': library_name
} }
return key_list return key_list

View File

@@ -1,2 +1,2 @@
PLEXPY_BRANCH = "beta" PLEXPY_BRANCH = "master"
PLEXPY_RELEASE_VERSION = "v2.1.10-beta" PLEXPY_RELEASE_VERSION = "v2.1.12"

View File

@@ -18,6 +18,7 @@ import json
import os import os
import shutil import shutil
import threading import threading
import urllib
import cherrypy import cherrypy
from cherrypy.lib.static import serve_file, serve_download from cherrypy.lib.static import serve_file, serve_download
@@ -247,23 +248,23 @@ class WebInterface(object):
@cherrypy.tools.json_out() @cherrypy.tools.json_out()
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
@addtoapi() @addtoapi()
def terminate_session(self, session_id=None, message=None, **kwargs): def terminate_session(self, session_key=None, session_id=None, message=None, **kwargs):
""" Add a new notification agent. """ Stop a streaming session.
``` ```
Required parameters: Required parameters:
session_id (str): The id of the session to terminate session_key (int): The session key of the session to terminate, OR
message (str): A custom message to send to the client session_id (str): The session id of the session to terminate
Optional parameters: Optional parameters:
None message (str): A custom message to send to the client
Returns: Returns:
None None
``` ```
""" """
pms_connect = pmsconnect.PmsConnect() pms_connect = pmsconnect.PmsConnect()
result = pms_connect.terminate_session(session_id=session_id, message=message) result = pms_connect.terminate_session(session_key=session_key, session_id=session_id, message=message)
if result: if result:
return {'result': 'success', 'message': 'Session terminated.'} return {'result': 'success', 'message': 'Session terminated.'}
@@ -273,8 +274,21 @@ class WebInterface(object):
@cherrypy.expose @cherrypy.expose
@cherrypy.tools.json_out() @cherrypy.tools.json_out()
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
def return_sessions_url(self, **kwargs): def return_plex_xml_url(self, endpoint='', plextv=False, **kwargs):
return plexpy.CONFIG.PMS_URL + '/status/sessions?X-Plex-Token=' + plexpy.CONFIG.PMS_TOKEN kwargs['X-Plex-Token'] = plexpy.CONFIG.PMS_TOKEN
if plextv:
base_url = 'https://plex.tv'
else:
if plexpy.CONFIG.PMS_URL_OVERRIDE:
base_url = plexpy.CONFIG.PMS_URL_OVERRIDE
else:
base_url = plexpy.CONFIG.PMS_URL
if '{machine_id}' in endpoint:
endpoint = endpoint.format(machine_id=plexpy.CONFIG.PMS_IDENTIFIER)
return base_url + endpoint + '?' + urllib.urlencode(kwargs)
@cherrypy.expose @cherrypy.expose
@requireAuth() @requireAuth()