Merge branch 'nightly' into v2-newsletter

This commit is contained in:
JonnyWong16 2018-03-18 17:49:14 -07:00
commit b6bd305694
11 changed files with 138 additions and 91 deletions

View file

@ -1,5 +1,16 @@
# Changelog
## v2.0.24 (2018-03-18)
* Monitoring:
* Fix: Fix stream data not showing for history recorded before v2.
* Notifications:
* Fix: Set all environment variables for scripts.
* Change: Moved all notification agent instructions to the wiki.
* Change: XBMC notification agent renamed to Kodi.
* Change: OSX Notify notification agent renamed to macOS Notification Center.
## v2.0.23-beta (2018-03-16)
* Monitoring:

View file

@ -55,7 +55,7 @@ DOCUMENTATION :: END
})
}
return deferred;
}
};
function checkQRAddress(url) {
var parser = document.createElement('a');
@ -82,7 +82,7 @@ DOCUMENTATION :: END
verifiedDevice = false;
getPlexPyURL().then(function (url) {
checkQRAddress(url)
checkQRAddress(url);
$.get('generate_api_key', { device: true }).then(function (token) {
$('#api_qr_address').val(url);
@ -120,7 +120,7 @@ DOCUMENTATION :: END
$('#api_qr_address').change(function () {
var url = $(this).val();
checkQRAddress(url)
checkQRAddress(url);
$('#api_qr_code').empty().qrcode({
text: url + '|' + $('#api_qr_token').val()

View file

@ -43,9 +43,6 @@
<div class="row">
<div class="col-md-12">
<input type="${item['input_type']}" class="form-control" id="${item['name']}" name="${item['name']}" value="${item['value']}" size="30" ${'readonly' if item.get('readonly') else ''}>
% if item['name'] == 'osx_notify_app':
<a href="javascript:void(0)" id="osxnotifyregister">Register</a>
% endif
</div>
</div>
<p class="help-block">${item['description'] | n}</p>
@ -431,16 +428,30 @@
});
% if notifier['agent_name'] == 'facebook':
if (location.protocol !== 'https:') {
$('#tabs-config .form-group:first').prepend(
'<div class="form-group">' +
'<label>Warning</label>' +
'<p class="help-block" style="color: #eb8600;">Facebook requires HTTPS for authorization. ' +
'Please enable HTTPS for Tautulli under <a data-tab-destination="tabs-web_interface" data-dismiss="modal" style="cursor: pointer;">Web Interface</a>.</p>' +
'</div>'
);
$('#facebook_redirect_uri').val('HTTPS not enabled');
} else {
$('#facebook_redirect_uri').val(location.href.split('/settings')[0] + '/facebook_redirect');
}
function disableFacebookRequest() {
if ($('#facebook_app_id').val() !== '' && $('#facebook_app_secret').val() !== '') { $('#facebook_facebookStep1').prop('disabled', false); }
else { $('#facebook_facebookStep1').prop('disabled', true); }
if ($('#facebook_app_id').val() !== '' && $('#facebook_app_secret').val() !== '') { $('#facebook_facebook_auth').prop('disabled', false); }
else { $('#facebook_facebook_auth').prop('disabled', true); }
}
disableFacebookRequest();
$('#facebook_app_id, #facebook_app_secret').on('change', function () {
disableFacebookRequest();
});
$('#facebook_facebookStep1').click(function () {
$('#facebook_facebook_auth').click(function () {
// Remove trailing '/' from Facebook redirect URI
if ($('#facebook_redirect_uri') && $('#facebook_redirect_uri').val().endsWith('/')) {
$('#facebook_redirect_uri').val($('#facebook_redirect_uri').val().slice(0, -1));
@ -448,7 +459,7 @@
var facebook_token;
$.ajax({
url: 'facebookStep1',
url: 'facebook_auth',
data: {
app_id: $('#facebook_app_id').val(),
app_secret: $('#facebook_app_secret').val(),
@ -506,7 +517,7 @@
});
% elif notifier['agent_name'] == 'osx':
$('#osxnotifyregister').click(function () {
$('#osx_notify_register').click(function () {
var osx_notify_app = $('#osx_notify_app').val();
$.get('osxnotifyregister', { 'app': osx_notify_app }, function (data) { showMsg('<i class="fa fa-check"></i> ' + data, false, true, 3000); });
});

View file

@ -10,7 +10,7 @@ DOCUMENTATION :: END
</%doc>
<ul class="stacked-configs list-unstyled">
% for notifier in sorted(notifiers_list, key=lambda k: (k['agent_label'], k['friendly_name'], k['id'])):
% for notifier in sorted(notifiers_list, key=lambda k: (k['agent_label'].lower(), k['friendly_name'], k['id'])):
<li class="notification-agent" data-id="${notifier['id']}">
<span>
<span class="toggle-left trigger-tooltip ${'active' if notifier['active'] else ''}" data-toggle="tooltip" data-placement="top" title="Triggers ${'active' if notifier['active'] else 'inactive'}"><i class="fa fa-lg fa-bell"></i></span>

View file

@ -971,6 +971,9 @@
<p class="help-block">
Add a new notification agent, or configure an existing notification agent by clicking the settings icon on the right.
</p>
<p class="help-block">
Please see the <a target='_blank' href='${anon_url('https://github.com/%s/%s-Wiki/wiki/Notification-Agents-Guide' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO))}'>Notification Agents Guide</a> for instructions on setting up each notification agent.
</p>
<br />
<div id="plexpy-notifiers-table">
<div class='text-muted'><i class="fa fa-refresh fa-spin"></i> Loading notification agents...</div>
@ -1002,7 +1005,7 @@
<h3>Database Import</h3>
</div>
<p class="help-block">Click a button below to import an exisiting database from another app.</p>
<p class="help-block">Click a button below to import an existing database from another app.</p>
<div class="btn-group">
<button class="btn btn-form toggle-app-import-modal" type="button" data-target="#app-import-modal" data-toggle="modal" data-app="plexwatch">PlexWatch</button>
<button class="btn btn-form toggle-app-import-modal" type="button" data-target="#app-import-modal" data-toggle="modal" data-app="plexivity">Plexivity</button>
@ -1249,7 +1252,7 @@
<div class="row">
<div class="col-md-12">
<ul class="stacked-configs list-unstyled">
% for agent in available_notification_agents:
% for agent in sorted(available_notification_agents, key=lambda k: k['label'].lower()):
<li class="new-notification-agent" data-id="${agent['id']}">
<span>${agent['label']}</span>
</li>

View file

@ -58,6 +58,10 @@ DOCUMENTATION :: END
<div class="col-sm-12 text-muted stream-info-current">
<i class="fa fa-exclamation-circle"></i> Current session. Updated stream details below may be delayed.
</div>
% elif data['pre_tautulli']:
<div class="col-sm-12 text-muted stream-info-current">
<i class="fa fa-exclamation-circle"></i> Pre-Tautulli history. Stream details below may be incorrect.
</div>
% endif
<table class="stream-info" style="margin-top: 0;">
<thead>
@ -84,8 +88,8 @@ DOCUMENTATION :: END
<tbody>
<tr>
<td>Bitrate</td>
<td>${data['stream_bitrate']} kbps</td>
<td>${data['bitrate']} kbps</td>
<td>${data['stream_bitrate']} ${'kbps' if data['stream_bitrate'] else ''}</td>
<td>${data['bitrate']} ${'kbps' if data['bitrate'] else ''}</td>
</tr>
% if data['media_type'] != 'track':
<tr>
@ -154,8 +158,8 @@ DOCUMENTATION :: END
</tr>
<tr>
<td>Bitrate</td>
<td>${data['stream_video_bitrate']} kbps</td>
<td>${data['video_bitrate']} kbps</td>
<td>${data['stream_video_bitrate']} ${'kbps' if data['stream_video_bitrate'] else ''}</td>
<td>${data['video_bitrate']} ${'kbps' if data['video_bitrate'] else ''}</td>
</tr>
<tr>
<td>Width</td>
@ -199,8 +203,8 @@ DOCUMENTATION :: END
</tr>
<tr>
<td>Bitrate</td>
<td>${data['stream_audio_bitrate']} kbps</td>
<td>${data['audio_bitrate']} kbps</td>
<td>${data['stream_audio_bitrate']} ${'kbps' if data['stream_audio_bitrate'] else ''}</td>
<td>${data['audio_bitrate']} ${'kbps' if data['audio_bitrate'] else ''}</td>
</tr>
<tr>
<td>Channels</td>

View file

@ -1622,6 +1622,15 @@ def dbcheck():
'ALTER TABLE poster_urls ADD COLUMN delete_hash TEXT'
)
# Rename notifiers in the database
logger.debug(u"Altering database. Renaming notifiers.")
c_db.execute(
'UPDATE notifiers SET agent_label = "Kodi" WHERE agent_label = "XBMC"'
)
c_db.execute(
'UPDATE notifiers SET agent_label = "macOS Notification Center" WHERE agent_label = "OSX Notify"'
)
# Add "Local" user to database as default unauthenticated user.
result = c_db.execute('SELECT id FROM users WHERE username = "Local"')
if not result.fetchone():

View file

@ -885,6 +885,9 @@ class DataFactory(object):
'stream_audio_decision, stream_audio_codec, stream_audio_bitrate, stream_audio_channels, ' \
'subtitles, stream_subtitle_decision, stream_subtitle_codec, ' \
'transcode_hw_decoding, transcode_hw_encoding, ' \
'video_decision, audio_decision, transcode_decision, width, height, container, ' \
'transcode_container, transcode_video_codec, transcode_audio_codec, transcode_audio_channels, ' \
'transcode_width, transcode_height, ' \
'session_history_metadata.media_type, title, grandparent_title ' \
'FROM session_history_media_info ' \
'JOIN session_history ON session_history_media_info.id = session_history.id ' \
@ -903,6 +906,9 @@ class DataFactory(object):
'stream_audio_decision, stream_audio_codec, stream_audio_bitrate, stream_audio_channels, ' \
'subtitles, stream_subtitle_decision, stream_subtitle_codec, ' \
'transcode_hw_decoding, transcode_hw_encoding, ' \
'video_decision, audio_decision, transcode_decision, width, height, container, ' \
'transcode_container, transcode_video_codec, transcode_audio_codec, transcode_audio_channels, ' \
'transcode_width, transcode_height, ' \
'media_type, title, grandparent_title ' \
'FROM sessions ' \
'WHERE session_key = ? %s' % user_cond
@ -913,6 +919,23 @@ class DataFactory(object):
stream_output = {}
for item in result:
pre_tautulli = 0
# For backwards compatibility. Pick one new Tautulli key to check and override with old values.
if not item['stream_video_resolution']:
item['stream_video_resolution'] = item['video_resolution']
item['stream_container'] = item['transcode_container'] or item['container']
item['stream_video_decision'] = item['video_decision']
item['stream_video_codec'] = item['transcode_video_codec'] or item['video_codec']
item['stream_video_width'] = item['transcode_width'] or item['width']
item['stream_video_height'] = item['transcode_height'] or item['height']
item['stream_audio_decision'] = item['audio_decision']
item['stream_audio_codec'] = item['transcode_audio_codec'] or item['audio_codec']
item['stream_audio_channels'] = item['transcode_audio_channels'] or item['audio_channels']
item['video_width'] = item['width']
item['video_height'] = item['height']
pre_tautulli = 1
stream_output = {'bitrate': item['bitrate'],
'video_resolution': item['video_resolution'],
'optimized_version': item['optimized_version'],
@ -951,10 +974,13 @@ class DataFactory(object):
'stream_subtitle_codec': item['stream_subtitle_codec'],
'transcode_hw_decoding': item['transcode_hw_decoding'],
'transcode_hw_encoding': item['transcode_hw_encoding'],
'video_decision': item['video_decision'],
'audio_decision': item['audio_decision'],
'media_type': item['media_type'],
'title': item['title'],
'grandparent_title': item['grandparent_title'],
'current_session': 1 if session_key else 0
'current_session': 1 if session_key else 0,
'pre_tautulli': pre_tautulli
}
stream_output = {k: v or '' for k, v in stream_output.iteritems()}

View file

@ -143,6 +143,10 @@ def available_notification_agents():
'name': 'join',
'id': AGENT_IDS['join']
},
{'label': 'Kodi',
'name': 'xbmc',
'id': AGENT_IDS['xbmc']
},
{'label': 'Notify My Android',
'name': 'nma',
'id': AGENT_IDS['nma']
@ -159,10 +163,10 @@ def available_notification_agents():
'name': 'prowl',
'id': AGENT_IDS['prowl']
},
{'label': 'Pushalot',
'name': 'pushalot',
'id': AGENT_IDS['pushalot']
},
# {'label': 'Pushalot',
# 'name': 'pushalot',
# 'id': AGENT_IDS['pushalot']
# },
{'label': 'Pushbullet',
'name': 'pushbullet',
'id': AGENT_IDS['pushbullet']
@ -187,10 +191,6 @@ def available_notification_agents():
'name': 'twitter',
'id': AGENT_IDS['twitter']
},
{'label': 'XBMC',
'name': 'xbmc',
'id': AGENT_IDS['xbmc']
},
{'label': 'Zapier',
'name': 'zapier',
'id': AGENT_IDS['zapier']
@ -199,7 +199,7 @@ def available_notification_agents():
# OSX Notifications should only be visible if it can be used
if OSX().validate():
agents.append({'label': 'OSX Notify',
agents.append({'label': 'macOS Notification Center',
'name': 'osx',
'id': AGENT_IDS['osx']
})
@ -928,11 +928,9 @@ class ANDROIDAPP(Notifier):
'The content of your notifications will be sent unencrypted!</strong><br>'
'Please install the library to encrypt the notification contents. '
'Instructions can be found in the '
'<a href="' + helpers.anon_url(
'https://github.com/%s/%s-Wiki/wiki/'
'<a href="https://github.com/%s/%s-Wiki/wiki/'
'Frequently-Asked-Questions#notifications-pycryptodome'
% (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO)) +
'" target="_blank">FAQ</a>.',
'" target="_blank">FAQ</a>.' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO),
'input_type': 'help'
})
else:
@ -1454,7 +1452,7 @@ class FACEBOOK(Notifier):
plexpy.CONFIG.FACEBOOK_TOKEN = 'temp'
return facebook.auth_url(app_id=app_id,
canvas_url=redirect_uri + '/facebookStep2',
canvas_url=redirect_uri,
perms=['user_managed_groups','publish_actions'])
def _get_credentials(self, code=''):
@ -1468,7 +1466,7 @@ class FACEBOOK(Notifier):
# Request user access token
api = facebook.GraphAPI(version='2.12')
response = api.get_access_token_from_code(code=code,
redirect_uri=redirect_uri + '/facebookStep2',
redirect_uri=redirect_uri,
app_id=app_id,
app_secret=app_secret)
access_token = response['access_token']
@ -1532,25 +1530,11 @@ class FACEBOOK(Notifier):
return self._post_facebook(**data)
def return_config_options(self):
config_option = [{'label': 'Instructions',
'description': 'Step 1: Visit <a href="' + helpers.anon_url('https://developers.facebook.com/apps') + '" target="_blank">'
'Facebook Developers</a> to add a new app using <strong>basic setup</strong>.<br>'
'Step 2: Click <strong>Add Product</strong> on the left, then <strong>Get Started</strong>'
'for <strong>Facebook Login</strong>.<br>'
'Step 3: Fill in <strong>Valid OAuth redirect URIs</strong> with your Tautulli URL (e.g. http://localhost:8181).<br>'
'Step 4: Click <strong>App Review</strong> on the left and toggle "make public" to <strong>Yes</strong>.<br>'
'Step 5: Fill in the <strong>Tautulli URL</strong> below with the exact same URL from Step 3.<br>'
'Step 6: Fill in the <strong>App ID</strong> and <strong>App Secret</strong> below.<br>'
'Step 7: Click the <strong>Request Authorization</strong> button below to retrieve your access token.<br>'
'Step 8: Fill in your <strong>Access Token</strong> below if it is not filled in automatically.<br>'
'Step 9: Fill in your <strong>Group ID</strong> number below. It can be found in the URL of your group page.',
'input_type': 'help'
},
{'label': 'Tautulli URL',
config_option = [{'label': 'OAuth Redirect URI',
'value': self.config['redirect_uri'],
'name': 'facebook_redirect_uri',
'description': 'Your Tautulli URL. This will tell Facebook where to redirect you after authorization.\
(e.g. http://localhost:8181)',
'description': 'Fill in this address for the "Valid OAuth redirect URIs" '
'in your Facebook App.',
'input_type': 'text'
},
{'label': 'Facebook App ID',
@ -1567,14 +1551,15 @@ class FACEBOOK(Notifier):
},
{'label': 'Request Authorization',
'value': 'Request Authorization',
'name': 'facebook_facebookStep1',
'name': 'facebook_facebook_auth',
'description': 'Request Facebook authorization. (Ensure you allow the browser pop-up).',
'input_type': 'button'
},
{'label': 'Facebook Access Token',
'value': self.config['access_token'],
'name': 'facebook_access_token',
'description': 'Your Facebook access token. Automatically filled in after requesting authorization.',
'description': 'Your Facebook access token. '
'Automatically filled in after requesting authorization.',
'input_type': 'text'
},
{'label': 'Facebook Group ID',
@ -1773,7 +1758,7 @@ class GROWL(Notifier):
config_option = [{'label': 'Growl Host',
'value': self.config['host'],
'name': 'growl_host',
'description': 'Your Growl hostname.',
'description': 'Your Growl hostname or IP address.',
'input_type': 'text'
},
{'label': 'Growl Password',
@ -1875,7 +1860,7 @@ class HIPCHAT(Notifier):
return self.make_request(self.config['hook'], headers=headers, json=data)
def return_config_options(self):
config_option = [{'label': 'Hipchat Custom Integrations Full URL',
config_option = [{'label': 'Hipchat Custom Integrations URL',
'value': self.config['hook'],
'name': 'hipchat_hook',
'description': 'Your Hipchat BYO integration URL. You can get a key from'
@ -2330,9 +2315,9 @@ class NMA(Notifier):
class OSX(Notifier):
"""
OSX notifications
macOS notifications
"""
NAME = 'OSX Notify'
NAME = 'macOS'
_DEFAULT_CONFIG = {'notify_app': '/Applications/Tautulli'
}
@ -2415,9 +2400,15 @@ class OSX(Notifier):
config_option = [{'label': 'Register Notify App',
'value': self.config['notify_app'],
'name': 'osx_notify_app',
'description': 'Enter the path/application name to be registered with the '
'Notification Center, default is /Applications/Tautulli.',
'description': 'Enter the path/application name to be registered with the Notification Center. '
'Default is <span class="inline-pre">/Applications/Tautulli</span>.',
'input_type': 'text'
},
{'label': 'Register App',
'value': 'Register App',
'name': 'osx_notify_register',
'description': 'Register Tautulli with the Notification Center.',
'input_type': 'button'
}
]
@ -2498,7 +2489,7 @@ class PLEX(Notifier):
return True
def return_config_options(self):
config_option = [{'label': 'Plex Home Theater Host:Port',
config_option = [{'label': 'Plex Home Theater Host Address',
'value': self.config['hosts'],
'name': 'plex_hosts',
'description': 'Host running Plex Home Theater (eg. http://localhost:3005). Separate multiple hosts with commas (,).',
@ -2689,10 +2680,10 @@ class PUSHBULLET(Notifier):
return {'': ''}
def return_config_options(self):
config_option = [{'label': 'Pushbullet API Key',
config_option = [{'label': 'Pushbullet Access Token',
'value': self.config['api_key'],
'name': 'pushbullet_api_key',
'description': 'Your Pushbullet API key.',
'description': 'Your Pushbullet access token.',
'input_type': 'text',
'refresh': True
},
@ -2974,6 +2965,7 @@ class SCRIPTS(Notifier):
'TAUTULLI_URL': helpers.get_plexpy_url(hostname='localhost'),
'TAUTULLI_APIKEY': plexpy.CONFIG.API_KEY
}
env.update(os.environ)
self.script_killed = False
output = error = ''
@ -3439,16 +3431,7 @@ class TWITTER(Notifier):
return self._send_tweet(body, attachment=poster_url)
def return_config_options(self):
config_option = [{'label': 'Instructions',
'description': 'Step 1: Visit <a href="' + helpers.anon_url('https://apps.twitter.com') + '" target="_blank">'
'Twitter Apps</a> to <strong>Create New App</strong>. A vaild "Website" is not required.<br>'
'Step 2: Go to <strong>Keys and Access Tokens</strong> and click '
'<strong>Create my access token</strong>.<br>'
'Step 3: Fill in the <strong>Consumer Key</strong>, <strong>Consumer Secret</strong>, '
'<strong>Access Token</strong>, and <strong>Access Token Secret</strong> below.',
'input_type': 'help'
},
{'label': 'Twitter Consumer Key',
config_option = [{'label': 'Twitter Consumer Key',
'value': self.config['consumer_key'],
'name': 'twitter_consumer_key',
'description': 'Your Twitter consumer key.',
@ -3492,9 +3475,9 @@ class TWITTER(Notifier):
class XBMC(Notifier):
"""
XBMC notifications
Kodi notifications
"""
NAME = 'XBMC'
NAME = 'Kodi'
_DEFAULT_CONFIG = {'hosts': '',
'username': '',
'password': '',
@ -3564,22 +3547,22 @@ class XBMC(Notifier):
return True
def return_config_options(self):
config_option = [{'label': 'XBMC Host:Port',
config_option = [{'label': 'Kodi Host Address',
'value': self.config['hosts'],
'name': 'xbmc_hosts',
'description': 'Host running XBMC (e.g. http://localhost:8080). Separate multiple hosts with commas (,).',
'description': 'Host running Kodi (e.g. http://localhost:8080). Separate multiple hosts with commas (,).',
'input_type': 'text'
},
{'label': 'XBMC Username',
{'label': 'Kodi Username',
'value': self.config['username'],
'name': 'xbmc_username',
'description': 'Username of your XBMC client API (blank for none).',
'description': 'Username of your Kodi client API (blank for none).',
'input_type': 'text'
},
{'label': 'XBMC Password',
{'label': 'Kodi Password',
'value': self.config['password'],
'name': 'xbmc_password',
'description': 'Password of your XBMC client API (blank for none).',
'description': 'Password of your Kodi client API (blank for none).',
'input_type': 'password'
},
{'label': 'Notification Duration',

View file

@ -1,2 +1,2 @@
PLEXPY_BRANCH = "beta"
PLEXPY_RELEASE_VERSION = "v2.0.23-beta"
PLEXPY_BRANCH = "master"
PLEXPY_RELEASE_VERSION = "v2.0.24"

View file

@ -3193,7 +3193,7 @@ class WebInterface(object):
@cherrypy.expose
@cherrypy.tools.json_out()
@requireAuth(member_of("admin"))
def facebookStep1(self, app_id='', app_secret='', redirect_uri='', **kwargs):
def facebook_auth(self, app_id='', app_secret='', redirect_uri='', **kwargs):
cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store"
facebook_notifier = notifiers.FACEBOOK()
@ -3208,7 +3208,7 @@ class WebInterface(object):
@cherrypy.expose
@requireAuth(member_of("admin"))
def facebookStep2(self, code='', **kwargs):
def facebook_redirect(self, code='', **kwargs):
cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store"
facebook = notifiers.FACEBOOK()
@ -5151,10 +5151,10 @@ class WebInterface(object):
quote_list = ['To crush your enemies, see them driven before you, and to hear the lamentation of their women!',
'Your clothes, give them to me, now!',
'Do it!',
'If it bleeds, we can kill it',
'If it bleeds, we can kill it.',
'See you at the party Richter!',
'Let off some steam, Bennett',
'I\'ll be back',
'Let off some steam, Bennett.',
'I\'ll be back.',
'Get to the chopper!',
'Hasta La Vista, Baby!',
'It\'s not a tumor!',
@ -5175,7 +5175,7 @@ class WebInterface(object):
'What killed the dinosaurs? The Ice Age!',
'That\'s for sleeping with my wife!',
'Remember when I said I\'d kill you last... I lied!',
'You want to be a farmer? Here\'s a couple of acres',
'You want to be a farmer? Here\'s a couple of acres.',
'Now, this is the plan. Get your ass to Mars.',
'I just had a terrible thought... What if this is a dream?',
'Well, listen to this one: Rubber baby buggy bumpers!',