Compare commits

..

15 Commits

Author SHA1 Message Date
JonnyWong16
1046b29c1a v2.1.33 2019-07-27 08:44:26 -07:00
JonnyWong16
d6127e28f3 Remove email sort 2019-07-22 09:03:20 -07:00
JonnyWong16
25a949356d Allow seraching by email address in dropdown menu 2019-07-21 09:46:17 -07:00
JonnyWong16
72a012b817 Update Plex OAuth headers 2019-07-18 11:39:16 -07:00
JonnyWong16
f439bd639c Make sure config has name and value when masking passwords 2019-07-10 21:37:32 -07:00
JonnyWong16
91476a420a Mask notifier and newsletter config passwords (Fixes Tautulli/Tautulli-Issues#172) 2019-07-08 23:50:48 -07:00
JonnyWong16
96c0f9cad5 Add if Docker container to platform 2019-07-08 22:30:58 -07:00
JonnyWong16
df50559495 Fix overwriting version number with None 2019-07-02 08:58:56 -07:00
JonnyWong16
6d35bd7947 v2.1.32 2019-06-26 19:14:30 -07:00
JonnyWong16
d27356bbba Fix timezone error with newsletter scheduler because QNAP devices use a stupid "local" timezone (Fixes Tautulli/Tautulli-Issues#183) 2019-06-26 19:11:33 -07:00
JonnyWong16
3054a824ce v2.1.31 2019-06-24 21:45:57 -07:00
JonnyWong16
3b22b6a3f7 v2.1.31-beta 2019-06-13 22:14:42 -07:00
JonnyWong16
e4be5a716f Fix unable to view database status when auth is disabled 2019-06-13 21:57:05 -07:00
JonnyWong16
13579b8140 Change default database synchronous mode to normal 2019-06-09 10:48:59 -07:00
JonnyWong16
b11437b86b Fix synced content showing incorrect stream info 2019-05-12 17:30:04 -07:00
18 changed files with 188 additions and 83 deletions

View File

@@ -1,5 +1,35 @@
# Changelog
## v2.1.33 (2019-07-27)
* Notifications:
* Change: Mask notification agent password fields.
* Change: Enable searching by email address in dropdown menu.
* Other:
* Fix: Version number being overwritten with "None" which prevented updating in some instances.
* Change: Update Plex OAuth request headers.
## v2.1.32 (2019-06-26)
* Newsletters:
* Fix: Newsletter scheduler issue for QNAP devices using an invalid "local" timezone preventing Tautulli from starting.
## v2.1.31 (2019-06-24)
* No additional changes from v2.1.31-beta.
## v2.1.31-beta (2019-06-13)
* Monitoring:
* Fix: Synced content showing incorrect stream info.
* Other:
* Fix: Unable to view database status when authentication is enabled.
* Change: Default database synchronous mode changed to prevent database corruption. Database response may be slower.
## v2.1.30-beta (2019-05-11)
* Monitoring:
@@ -14,6 +44,7 @@
## v2.1.29 (2019-05-11)
* No additional changes from v2.1.29-beta.
@@ -158,6 +189,7 @@
## v2.1.20 (2018-09-05)
* No additional changes from v2.1.20-beta.

View File

@@ -69,7 +69,7 @@ DOCUMENTATION :: END
% endif
<tr>
<td>Platform:</td>
<td>${common.PLATFORM} ${common.PLATFORM_RELEASE} (${common.PLATFORM_VERSION + (' - {}'.format(common.PLATFORM_LINUX_DISTRO) if common.PLATFORM_LINUX_DISTRO else '')})</td>
<td>${'[Docker] ' if plexpy.DOCKER else ''}${common.PLATFORM} ${common.PLATFORM_RELEASE} (${common.PLATFORM_VERSION + (' - {}'.format(common.PLATFORM_LINUX_DISTRO) if common.PLATFORM_LINUX_DISTRO else '')})</td>
</tr>
<tr>
<td>System Timezone:</td>

View File

@@ -218,7 +218,7 @@ DOCUMENTATION :: END
% if data['stream_container_decision'] == 'transcode':
Transcode (${data['container'].upper()} <i class="fa fa-long-arrow-right"></i> ${data['stream_container'].upper()})
% else:
Direct Play (${data['container'].upper()})
Direct Play (${data['stream_container'].upper()})
% endif
</div>
</li>
@@ -236,7 +236,7 @@ DOCUMENTATION :: END
% elif data['stream_video_decision'] == 'copy':
Direct Stream (${data['stream_video_codec'].upper()} ${VIDEO_RESOLUTION_OVERRIDES.get(data['stream_video_resolution'], data['stream_video_resolution'])})
% else:
Direct Play (${data['video_codec'].upper()} ${VIDEO_RESOLUTION_OVERRIDES.get(data['video_resolution'], data['video_resolution'])})
Direct Play (${data['stream_video_codec'].upper()} ${VIDEO_RESOLUTION_OVERRIDES.get(data['stream_video_resolution'], data['stream_video_resolution'])})
% endif
% elif data['media_type'] == 'photo':
Direct Play (${data['width']}x${data['height']})
@@ -253,7 +253,7 @@ DOCUMENTATION :: END
% elif data['stream_audio_decision'] == 'copy':
Direct Stream (${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()})
% else:
Direct Play (${AUDIO_CODEC_OVERRIDES.get(data['audio_codec'], data['audio_codec'].upper())} ${data['audio_channel_layout'].split('(')[0].capitalize()})
Direct Play (${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()})
% endif
</div>
</li>
@@ -270,7 +270,7 @@ DOCUMENTATION :: END
% elif data['stream_subtitle_decision'] == 'burn':
Burn (${data['subtitle_codec'].upper()})
% else:
Direct Play (${data['stream_subtitle_codec'].upper() if data['synced_version'] else data['subtitle_codec'].upper()})
Direct Play (${data['subtitle_codec'].upper() if data['synced_version'] else data['stream_subtitle_codec'].upper()})
% endif
% else:
None

View File

@@ -430,7 +430,7 @@
if (s.stream_container_decision === 'transcode') {
transcode_container = 'Transcode (' + s.container.toUpperCase() + ' <i class="fa fa-long-arrow-right"></i> ' + s.stream_container.toUpperCase() + ')';
} else {
transcode_container = 'Direct Play (' + s.container.toUpperCase() + ')';
transcode_container = 'Direct Play (' + s.stream_container.toUpperCase() + ')';
}
$('#transcode_container-' + key).html(transcode_container);
@@ -465,7 +465,7 @@
} else if (s.stream_video_decision === 'copy') {
video_decision = 'Direct Stream (' + s.stream_video_codec.toUpperCase() + ' ' + sv_res + ')';
} else {
video_decision = 'Direct Play (' + s.video_codec.toUpperCase() + ' ' + v_res + ')';
video_decision = 'Direct Play (' + s.stream_video_codec.toUpperCase() + ' ' + sv_res + ')';
}
} else if (s.media_type === 'photo') {
video_decision = 'Direct Play (' + s.width + 'x' + s.height + ')';
@@ -481,7 +481,7 @@
} else if (s.stream_audio_decision === 'copy') {
audio_decision = 'Direct Stream (' + sa_codec + ' ' + capitalizeFirstLetter(s.stream_audio_channel_layout.split('(')[0]) + ')';
} else {
audio_decision = 'Direct Play (' + a_codec + ' ' + capitalizeFirstLetter(s.audio_channel_layout.split('(')[0]) + ')';
audio_decision = 'Direct Play (' + sa_codec + ' ' + capitalizeFirstLetter(s.stream_audio_channel_layout.split('(')[0]) + ')';
}
}
$('#audio_decision-' + key).html(audio_decision);
@@ -495,7 +495,7 @@
} else if (s.stream_subtitle_decision === 'burn') {
subtitle_decision = 'Burn (' + s.subtitle_codec.toUpperCase() + ')';
} else {
subtitle_decision = 'Direct Play (' + ((s.synced_version === '1') ? s.stream_subtitle_codec.toUpperCase() : s.subtitle_codec.toUpperCase()) + ')';
subtitle_decision = 'Direct Play (' + ((s.synced_version === '1') ? s.subtitle_codec.toUpperCase() : s.stream_subtitle_codec.toUpperCase()) + ')';
}
}
$('#subtitle_decision-' + key).html(subtitle_decision);

View File

@@ -568,8 +568,11 @@ function getPlexHeaders() {
'X-Plex-Client-Identifier': getLocalStorage('Tautulli_ClientID', uuidv4(), false),
'X-Plex-Platform': p.name,
'X-Plex-Platform-Version': p.version,
'X-Plex-Model': 'Plex OAuth',
'X-Plex-Device': p.os,
'X-Plex-Device-Name': p.name
'X-Plex-Device-Name': p.name,
'X-Plex-Device-Screen-Resolution': window.screen.width + 'x' + window.screen.height,
'X-Plex-Language': 'en'
};
}
@@ -655,7 +658,21 @@ function PlexOAuth(success, error, pre) {
const pin = data.pin;
const code = data.code;
plex_oauth_window.location = 'https://app.plex.tv/auth/#!?clientID=' + x_plex_headers['X-Plex-Client-Identifier'] + '&code=' + code;
var oauth_params = {
'clientID': x_plex_headers['X-Plex-Client-Identifier'],
'context[device][product]': x_plex_headers['X-Plex-Product'],
'context[device][version]': x_plex_headers['X-Plex-Version'],
'context[device][platform]': x_plex_headers['X-Plex-Platform'],
'context[device][platformVersion]': x_plex_headers['X-Plex-Platform-Version'],
'context[device][device]': x_plex_headers['X-Plex-Device'],
'context[device][deviceName]': x_plex_headers['X-Plex-Device-Name'],
'context[device][model]': x_plex_headers['X-Plex-Model'],
'context[device][screenResolution]': x_plex_headers['X-Plex-Device-Screen-Resolution'],
'context[device][layout]': 'desktop',
'code': code
}
plex_oauth_window.location = 'https://app.plex.tv/auth/#!?' + encodeData(oauth_params);
polling = pin;
(function poll() {
@@ -693,4 +710,10 @@ function PlexOAuth(success, error, pre) {
error()
}
});
}
}
function encodeData(data) {
return Object.keys(data).map(function(key) {
return [key, data[key]].map(encodeURIComponent).join("=");
}).join("&");
}

View File

@@ -584,6 +584,7 @@
var $email_selectors = $('#newsletter_email_to, #newsletter_email_cc, #newsletter_email_bcc').selectize({
plugins: ['remove_button'],
maxItems: null,
searchField: ['text', 'value'],
render: {
item: function(item, escape) {
return '<div>' +

View File

@@ -566,6 +566,7 @@
var $email_selectors = $('#email_to, #email_cc, #email_bcc').selectize({
plugins: ['remove_button'],
maxItems: null,
searchField: ['text', 'value'],
render: {
item: function(item, escape) {
return '<div>' +

View File

@@ -100,6 +100,7 @@ UMASK = None
HTTP_PORT = None
HTTP_ROOT = None
AUTH_ENABLED = None
DEV = False
@@ -114,6 +115,7 @@ WIN_SYS_TRAY_ICON = None
SYS_TIMEZONE = None
SYS_UTC_OFFSET = None
def initialize(config_file):
with INIT_LOCK:
@@ -156,8 +158,8 @@ def initialize(config_file):
logger.info(u"Starting Tautulli {}".format(
common.RELEASE
))
logger.info(u"{} {} ({}{})".format(
common.PLATFORM, common.PLATFORM_RELEASE, common.PLATFORM_VERSION,
logger.info(u"{}{} {} ({}{})".format(
'[Docker] ' if DOCKER else '', common.PLATFORM, common.PLATFORM_RELEASE, common.PLATFORM_VERSION,
' - {}'.format(common.PLATFORM_LINUX_DISTRO) if common.PLATFORM_LINUX_DISTRO else ''
))
logger.info(u"{} (UTC{})".format(
@@ -251,7 +253,7 @@ def initialize(config_file):
# Check for new versions
if CONFIG.CHECK_GITHUB_ON_STARTUP and CONFIG.CHECK_GITHUB:
try:
LATEST_VERSION = versioncheck.check_update()
versioncheck.check_update()
except:
logger.exception(u"Unhandled exception")
LATEST_VERSION = CURRENT_VERSION

View File

@@ -284,7 +284,7 @@ _CONFIG_DEFINITIONS = {
'JOIN_ON_PMSUPDATE': (int, 'Join', 0),
'JOIN_ON_CONCURRENT': (int, 'Join', 0),
'JOIN_ON_NEWDEVICE': (int, 'Join', 0),
'JOURNAL_MODE': (str, 'Advanced', 'wal'),
'JOURNAL_MODE': (str, 'Advanced', 'WAL'),
'LAUNCH_BROWSER': (int, 'General', 1),
'LOG_BLACKLIST': (int, 'General', 1),
'LOG_DIR': (str, 'General', ''),
@@ -541,6 +541,7 @@ _CONFIG_DEFINITIONS = {
'SCRIPTS_ON_PMSUPDATE_SCRIPT': (unicode, 'Scripts', ''),
'SCRIPTS_ON_CONCURRENT_SCRIPT': (unicode, 'Scripts', ''),
'SCRIPTS_ON_NEWDEVICE_SCRIPT': (unicode, 'Scripts', ''),
'SYNCHRONOUS_MODE': (str, 'Advanced', 'NORMAL'),
'TELEGRAM_BOT_TOKEN': (str, 'Telegram', ''),
'TELEGRAM_ENABLED': (int, 'Telegram', 0),
'TELEGRAM_CHAT_ID': (str, 'Telegram', ''),

View File

@@ -126,12 +126,12 @@ class MonitorDatabase(object):
def __init__(self, filename=FILENAME):
self.filename = filename
self.connection = sqlite3.connect(db_filename(filename), timeout=20)
# Don't wait for the disk to finish writing
self.connection.execute("PRAGMA synchronous = OFF")
# Journal disabled since we never do rollbacks
# Set database synchronous mode (default NORMAL)
self.connection.execute("PRAGMA synchronous = %s" % plexpy.CONFIG.SYNCHRONOUS_MODE)
# Set database journal mode (default WAL)
self.connection.execute("PRAGMA journal_mode = %s" % plexpy.CONFIG.JOURNAL_MODE)
# 64mb of cache memory, probably need to make it user configurable
self.connection.execute("PRAGMA cache_size=-%s" % (get_cache_size() * 1024))
# Set database cache size (default 32MB)
self.connection.execute("PRAGMA cache_size = -%s" % (get_cache_size() * 1024))
self.connection.row_factory = dict_factory
def action(self, query, args=None, return_last_id=False):

View File

@@ -1178,3 +1178,18 @@ def split_args(args=None):
return [arg.decode(plexpy.SYS_ENCODING, 'ignore')
for arg in shlex.split(args.encode(plexpy.SYS_ENCODING, 'ignore'))]
return []
def mask_config_passwords(config):
if isinstance(config, list):
for cfg in config:
if 'password' in cfg.get('name', '') and cfg.get('value', '') != '':
cfg['value'] = ' '
elif isinstance(config, dict):
for cfg, val in config.iteritems():
# Check for a password config keys and if the password is not blank
if 'password' in cfg and val != '':
# Set the password to blank so it is not exposed in the HTML form
config[cfg] = ' '
return config

View File

@@ -61,13 +61,11 @@ def schedule_newsletter_job(newsletter_job_id, name='', func=None, remove_job=Fa
logger.info(u"Tautulli NewsletterHandler :: Removed scheduled newsletter: %s" % name)
else:
NEWSLETTER_SCHED.reschedule_job(
newsletter_job_id, args=args, trigger=CronTrigger().from_crontab(
cron, timezone=plexpy.SYS_TIMEZONE))
newsletter_job_id, args=args, trigger=CronTrigger.from_crontab(cron))
logger.info(u"Tautulli NewsletterHandler :: Re-scheduled newsletter: %s" % name)
elif not remove_job:
NEWSLETTER_SCHED.add_job(
func, args=args, id=newsletter_job_id, trigger=CronTrigger().from_crontab(
cron, timezone=plexpy.SYS_TIMEZONE))
func, args=args, id=newsletter_job_id, trigger=CronTrigger.from_crontab(cron))
logger.info(u"Tautulli NewsletterHandler :: Scheduled newsletter: %s" % name)

View File

@@ -125,7 +125,7 @@ def delete_newsletter(newsletter_id=None):
return False
def get_newsletter_config(newsletter_id=None):
def get_newsletter_config(newsletter_id=None, mask_passwords=False):
if str(newsletter_id).isdigit():
newsletter_id = int(newsletter_id)
else:
@@ -153,13 +153,16 @@ def get_newsletter_config(newsletter_id=None):
logger.error(u"Tautulli Newsletters :: Failed to get newsletter config options: %s." % e)
return
if mask_passwords:
newsletter_agent.email_config = helpers.mask_config_passwords(newsletter_agent.email_config)
result['subject'] = newsletter_agent.subject
result['body'] = newsletter_agent.body
result['message'] = newsletter_agent.message
result['config'] = newsletter_agent.config
result['email_config'] = newsletter_agent.email_config
result['config_options'] = newsletter_agent.return_config_options()
result['email_config_options'] = newsletter_agent.return_email_config_options()
result['config_options'] = newsletter_agent.return_config_options(mask_passwords=mask_passwords)
result['email_config_options'] = newsletter_agent.return_email_config_options(mask_passwords=mask_passwords)
return result
@@ -230,6 +233,13 @@ def set_newsletter_config(newsletter_id=None, agent_id=None, **kwargs):
email_config = {k[len(email_config_prefix):]: kwargs.pop(k)
for k in kwargs.keys() if k.startswith(email_config_prefix)}
for cfg, val in email_config.iteritems():
# Check for a password config keys and a blank password from the HTML form
if 'password' in cfg and val == ' ':
# Get the previous password so we don't overwrite it with a blank value
old_newsletter_config = get_newsletter_config(newsletter_id=newsletter_id)
email_config[cfg] = old_newsletter_config['email_config'][cfg]
subject = kwargs.pop('subject')
body = kwargs.pop('body')
message = kwargs.pop('message')
@@ -647,16 +657,21 @@ class Newsletter(object):
return filename
def return_config_options(self):
return self._return_config_options()
def return_config_options(self, mask_passwords=False):
config_options = self._return_config_options()
def _return_config_options(self):
config_options = []
# Mask password config options
if mask_passwords:
helpers.mask_config_passwords(config_options)
return config_options
def return_email_config_options(self):
config_options = EMAIL(self.email_config).return_config_options()
def _return_config_options(self):
config_options = []
return config_options
def return_email_config_options(self, mask_passwords=False):
config_options = EMAIL(self.email_config).return_config_options(mask_passwords=mask_passwords)
for c in config_options:
c['name'] = 'newsletter_' + c['name']
return config_options
@@ -926,10 +941,8 @@ class RecentlyAdded(Newsletter):
return parameters
def return_config_options(self):
config_options = self._return_config_options()
additional_config = [
def _return_config_options(self):
config_options = [
{'label': 'Included Libraries',
'value': self.config['incl_libraries'],
'description': 'Select the libraries to include in the newsletter.',
@@ -939,4 +952,4 @@ class RecentlyAdded(Newsletter):
}
]
return additional_config + config_options
return config_options

View File

@@ -455,7 +455,7 @@ def delete_notifier(notifier_id=None):
return False
def get_notifier_config(notifier_id=None):
def get_notifier_config(notifier_id=None, mask_passwords=False):
if str(notifier_id).isdigit():
notifier_id = int(notifier_id)
else:
@@ -472,11 +472,13 @@ def get_notifier_config(notifier_id=None):
try:
config = json.loads(result.pop('notifier_config', '{}'))
notifier_agent = get_agent_class(agent_id=result['agent_id'], config=config)
notifier_config = notifier_agent.return_config_options()
except Exception as e:
logger.error(u"Tautulli Notifiers :: Failed to get notifier config options: %s." % e)
return
if mask_passwords:
notifier_agent.config = helpers.mask_config_passwords(notifier_agent.config)
notify_actions = get_notify_actions(return_dict=True)
notifier_actions = {}
@@ -503,8 +505,8 @@ def get_notifier_config(notifier_id=None):
if not result['custom_conditions_logic']:
result['custom_conditions_logic'] = ''
result['config'] = config
result['config_options'] = notifier_config
result['config'] = notifier_agent.config
result['config_options'] = notifier_agent.return_config_options(mask_passwords=mask_passwords)
result['actions'] = notifier_actions
result['notify_text'] = notifier_text
@@ -587,6 +589,13 @@ def set_notifier_config(notifier_id=None, agent_id=None, **kwargs):
notifier_config = {k[len(config_prefix):]: kwargs.pop(k)
for k in kwargs.keys() if k.startswith(config_prefix)}
for cfg, val in notifier_config.iteritems():
# Check for a password config keys and a blank password from the HTML form
if 'password' in cfg and val == ' ':
# Get the previous password so we don't overwrite it with a blank value
old_notifier_config = get_notifier_config(notifier_id=notifier_id)
notifier_config[cfg] = old_notifier_config['config'][cfg]
agent_class = get_agent_class(agent_id=agent['id'], config=notifier_config)
keys = {'id': notifier_id}
@@ -835,7 +844,16 @@ class Notifier(object):
return False
def return_config_options(self):
def return_config_options(self, mask_passwords=False):
config_options = self._return_config_options()
# Mask password config options
if mask_passwords:
helpers.mask_config_passwords(config_options)
return config_options
def _return_config_options(self):
config_options = []
return config_options
@@ -942,7 +960,7 @@ class ANDROIDAPP(Notifier):
return devices
def return_config_options(self):
def _return_config_options(self):
config_option = []
if not CRYPTODOME:
@@ -1058,7 +1076,7 @@ class BOXCAR(Notifier):
return sounds
def return_config_options(self):
def _return_config_options(self):
config_option = [{'label': 'Boxcar Access Token',
'value': self.config['token'],
'name': 'boxcar_token',
@@ -1089,7 +1107,7 @@ class BROWSER(Notifier):
logger.info(u"Tautulli Notifiers :: {name} notification sent.".format(name=self.NAME))
return True
def return_config_options(self):
def _return_config_options(self):
config_option = [{'label': 'Allow Notifications',
'value': 'Allow Notifications',
'name': 'browser_allow_browser',
@@ -1202,7 +1220,7 @@ class DISCORD(Notifier):
return self.make_request(self.config['hook'], params=params, headers=headers, json=data)
def return_config_options(self):
def _return_config_options(self):
config_option = [{'label': 'Discord Webhook URL',
'value': self.config['hook'],
'name': 'discord_hook',
@@ -1389,7 +1407,7 @@ class EMAIL(Notifier):
return user_emails_to, user_emails_cc, user_emails_bcc
def return_config_options(self):
def _return_config_options(self):
user_emails_to, user_emails_cc, user_emails_bcc = self.get_user_emails()
config_option = [{'label': 'From Name',
@@ -1569,7 +1587,7 @@ class FACEBOOK(Notifier):
return self._post_facebook(**data)
def return_config_options(self):
def _return_config_options(self):
config_option = [{'label': 'OAuth Redirect URI',
'value': self.config['redirect_uri'],
'name': 'facebook_redirect_uri',
@@ -1699,7 +1717,7 @@ class GROUPME(Notifier):
return self.make_request('https://api.groupme.com/v3/bots/post', json=data)
def return_config_options(self):
def _return_config_options(self):
config_option = [{'label': 'GroupMe Access Token',
'value': self.config['access_token'],
'name': 'groupme_access_token',
@@ -1796,7 +1814,7 @@ class GROWL(Notifier):
logger.error(u"Tautulli Notifiers :: {name} notification failed: network error".format(name=self.NAME))
return False
def return_config_options(self):
def _return_config_options(self):
config_option = [{'label': 'Growl Host',
'value': self.config['host'],
'name': 'growl_host',
@@ -1901,7 +1919,7 @@ class HIPCHAT(Notifier):
return self.make_request(self.config['hook'], headers=headers, json=data)
def return_config_options(self):
def _return_config_options(self):
config_option = [{'label': 'Hipchat Custom Integrations URL',
'value': self.config['hook'],
'name': 'hipchat_hook',
@@ -2012,7 +2030,7 @@ class IFTTT(Notifier):
return self.make_request('https://maker.ifttt.com/trigger/{}/with/key/{}'.format(event, self.config['key']),
headers=headers, json=data)
def return_config_options(self):
def _return_config_options(self):
config_option = [{'label': 'IFTTT Webhook Key',
'value': self.config['key'],
'name': 'ifttt_key',
@@ -2131,7 +2149,7 @@ class JOIN(Notifier):
return devices
def return_config_options(self):
def _return_config_options(self):
config_option = [{'label': 'Join API Key',
'value': self.config['api_key'],
'name': 'join_api_key',
@@ -2233,7 +2251,7 @@ class MQTT(Notifier):
return True
def return_config_options(self):
def _return_config_options(self):
config_option = [{'label': 'Broker',
'value': self.config['broker'],
'name': 'mqtt_broker',
@@ -2335,7 +2353,7 @@ class NMA(Notifier):
logger.error(u"Tautulli Notifiers :: {name} notification failed.".format(name=self.NAME))
return False
def return_config_options(self):
def _return_config_options(self):
config_option = [{'label': 'NotifyMyAndroid API Key',
'value': self.config['api_key'],
'name': 'nma_api_key',
@@ -2437,7 +2455,7 @@ class OSX(Notifier):
logger.error(u"Tautulli Notifiers :: {name} failed: {e}".format(name=self.NAME, e=e))
return False
def return_config_options(self):
def _return_config_options(self):
config_option = [{'label': 'Register Notify App',
'value': self.config['notify_app'],
'name': 'osx_notify_app',
@@ -2530,7 +2548,7 @@ class PLEX(Notifier):
return True
def return_config_options(self):
def _return_config_options(self):
config_option = [{'label': 'Plex Home Theater Host Address',
'value': self.config['hosts'],
'name': 'plex_hosts',
@@ -2586,7 +2604,7 @@ class PROWL(Notifier):
return self.make_request('https://api.prowlapp.com/publicapi/add', headers=headers, data=data)
def return_config_options(self):
def _return_config_options(self):
config_option = [{'label': 'Prowl API Key',
'value': self.config['key'],
'name': 'prowl_key',
@@ -2622,7 +2640,7 @@ class PUSHALOT(Notifier):
return self.make_request('https://pushalot.com/api/sendmessage', headers=headers, data=data)
def return_config_options(self):
def _return_config_options(self):
config_option = [{'label': 'Pushalot API Key',
'value': self.config['api_key'],
'name': 'pushalot_api_key',
@@ -2722,7 +2740,7 @@ class PUSHBULLET(Notifier):
return devices
def return_config_options(self):
def _return_config_options(self):
config_option = [{'label': 'Pushbullet Access Token',
'value': self.config['api_key'],
'name': 'pushbullet_api_key',
@@ -2888,7 +2906,7 @@ class PUSHOVER(Notifier):
# else:
# return {'': ''}
def return_config_options(self):
def _return_config_options(self):
config_option = [{'label': 'Pushover API Token',
'value': self.config['api_token'],
'name': 'pushover_api_token',
@@ -3163,7 +3181,7 @@ class SCRIPTS(Notifier):
return True
def return_config_options(self):
def _return_config_options(self):
config_option = [{'label': 'Supported File Types',
'description': '<span class="inline-pre">' + \
', '.join(self.script_exts.keys()) + '</span>',
@@ -3286,7 +3304,7 @@ class SLACK(Notifier):
return self.make_request(self.config['hook'], headers=headers, json=data)
def return_config_options(self):
def _return_config_options(self):
config_option = [{'label': 'Slack Webhook URL',
'value': self.config['hook'],
'name': 'slack_hook',
@@ -3439,7 +3457,7 @@ class TELEGRAM(Notifier):
return self.make_request('https://api.telegram.org/bot{}/sendMessage'.format(self.config['bot_token']),
headers=headers, data=data)
def return_config_options(self):
def _return_config_options(self):
config_option = [{'label': 'Telegram Bot Token',
'value': self.config['bot_token'],
'name': 'telegram_bot_token',
@@ -3537,7 +3555,7 @@ class TWITTER(Notifier):
else:
return self._send_tweet(body, attachment=poster_url)
def return_config_options(self):
def _return_config_options(self):
config_option = [{'label': 'Twitter Consumer Key',
'value': self.config['consumer_key'],
'name': 'twitter_consumer_key',
@@ -3606,7 +3624,7 @@ class WEBHOOK(Notifier):
return self.make_request(self.config['hook'], method=self.config['method'], headers=headers, json=webhook_data)
def return_config_options(self):
def _return_config_options(self):
config_option = [{'label': 'Webhook URL',
'value': self.config['hook'],
'name': 'webhook_hook',
@@ -3703,7 +3721,7 @@ class XBMC(Notifier):
return True
def return_config_options(self):
def _return_config_options(self):
config_option = [{'label': 'Kodi Host Address',
'value': self.config['hosts'],
'name': 'xbmc_hosts',
@@ -3796,7 +3814,7 @@ class ZAPIER(Notifier):
return self.make_request(self.config['hook'], headers=headers, json=data)
def return_config_options(self):
def _return_config_options(self):
config_option = [{'label': 'Zapier Webhook URL',
'value': self.config['hook'],
'name': 'zapier_hook',

View File

@@ -1646,10 +1646,10 @@ class PmsConnect(object):
if helpers.get_xml_attr(stream, 'streamType') == '1':
video_stream_info = stream
elif helpers.get_xml_attr(stream, 'streamType') == '2':
elif helpers.get_xml_attr(stream, 'streamType') == '2' and helpers.get_xml_attr(stream, 'selected') == '1':
audio_stream_info = stream
elif helpers.get_xml_attr(stream, 'streamType') == '3':
elif helpers.get_xml_attr(stream, 'streamType') == '3' and helpers.get_xml_attr(stream, 'selected') == '1':
subtitle_stream_info = stream
video_id = audio_id = subtitle_id = None

View File

@@ -1,2 +1,2 @@
PLEXPY_BRANCH = "beta"
PLEXPY_RELEASE_VERSION = "v2.1.30-beta"
PLEXPY_BRANCH = "master"
PLEXPY_RELEASE_VERSION = "v2.1.33"

View File

@@ -3168,13 +3168,13 @@ class WebInterface(object):
}
```
"""
result = notifiers.get_notifier_config(notifier_id=notifier_id)
result = notifiers.get_notifier_config(notifier_id=notifier_id, mask_passwords=True)
return result
@cherrypy.expose
@requireAuth(member_of("admin"))
def get_notifier_config_modal(self, notifier_id=None, **kwargs):
result = notifiers.get_notifier_config(notifier_id=notifier_id)
result = notifiers.get_notifier_config(notifier_id=notifier_id, mask_passwords=True)
parameters = [
{'name': param['name'], 'type': param['type'], 'value': param['value']}
@@ -5667,13 +5667,13 @@ class WebInterface(object):
}
```
"""
result = newsletters.get_newsletter_config(newsletter_id=newsletter_id)
result = newsletters.get_newsletter_config(newsletter_id=newsletter_id, mask_passwords=True)
return result
@cherrypy.expose
@requireAuth(member_of("admin"))
def get_newsletter_config_modal(self, newsletter_id=None, **kwargs):
result = newsletters.get_newsletter_config(newsletter_id=newsletter_id)
result = newsletters.get_newsletter_config(newsletter_id=newsletter_id, mask_passwords=True)
return serve_template(templatename="newsletter_config.html", newsletter=result)
@cherrypy.expose
@@ -5884,7 +5884,7 @@ class WebInterface(object):
status = {'result': 'success', 'message': 'Ok'}
if args or kwargs:
if not cherrypy.request.path_info == '/api/v2':
if not cherrypy.request.path_info == '/api/v2' and plexpy.AUTH_ENABLED:
cherrypy.request.config['auth.require'] = []
check_auth()

View File

@@ -80,14 +80,15 @@ def initialize(options):
logger.info(u"Tautulli WebStart :: Web server authentication is enabled: %s.", ' and '.join(login_allowed))
if options['http_basic_auth']:
auth_enabled = False
plexpy.AUTH_ENABLED = False
basic_auth_enabled = True
else:
auth_enabled = True
plexpy.AUTH_ENABLED = True
basic_auth_enabled = False
cherrypy.tools.auth = cherrypy.Tool('before_handler', webauth.check_auth, priority=2)
else:
auth_enabled = basic_auth_enabled = False
plexpy.AUTH_ENABLED = False
basic_auth_enabled = False
if options['http_root'].strip('/'):
plexpy.HTTP_ROOT = options['http_root'] = '/' + options['http_root'].strip('/') + '/'
@@ -104,7 +105,7 @@ def initialize(options):
'tools.gzip.mime_types': ['text/html', 'text/plain', 'text/css',
'text/javascript', 'application/json',
'application/javascript'],
'tools.auth.on': auth_enabled,
'tools.auth.on': plexpy.AUTH_ENABLED,
'tools.auth_basic.on': basic_auth_enabled,
'tools.auth_basic.realm': 'Tautulli web server',
'tools.auth_basic.checkpassword': cherrypy.lib.auth_basic.checkpassword_dict({