From 5089ede207099d9009100a15386eb67246625be4 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Wed, 10 Jan 2018 00:29:35 -0800 Subject: [PATCH] Add selectize for email input --- data/interfaces/default/css/plexpy.css | 33 +++++++- data/interfaces/default/notifier_config.html | 84 ++++++++++++++++++-- plexpy/notifiers.py | 37 ++++++--- plexpy/users.py | 21 +++++ 4 files changed, 153 insertions(+), 22 deletions(-) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index dd72bea0..053a85fe 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -79,6 +79,13 @@ select.form-control { padding: 1px 2px; transition: background-color .3s; } +.selectize-control.form-control .selectize-input { + display: flex; + align-items: center; + flex-wrap: wrap; + margin-bottom: 4px; + padding-left: 5px; +} .react-selectize.root-node .react-selectize-control .react-selectize-placeholder { color: #fff !important; } @@ -95,7 +102,7 @@ select.form-control { .react-selectize.root-node .simple-value span { padding-bottom: 2px !important; } -.react-selectize.root-node .react-selectize-control .react-selectize-search-field-and-selected-values .resizable-input{ +.react-selectize.root-node .react-selectize-control .react-selectize-search-field-and-selected-values .resizable-input { padding-top: 3px !important; padding-bottom: 3px !important; } @@ -110,7 +117,7 @@ select.form-control:focus, } .react-selectize.root-node.open .simple-value, .selectize-control.multi .selectize-input.focus > div, -.selectize-control.multi .selectize-input > div.active{ +.selectize-control.multi .selectize-input > div.active { background: #efefef !important; color: #333333 !important; transition: background-color .3s; @@ -118,6 +125,28 @@ select.form-control:focus, .react-selectize.root-node.open .react-selectize-control .react-selectize-toggle-button path { fill: #999 !important; } +.selectize-control .selectize-input > div .email { + opacity: 0.8; + font-size: 12px; +} +.selectize-control .selectize-input > div .user + .email { + margin-left: 5px; +} +.selectize-control .selectize-input > div .email:before { + content: '<'; + opacity: 0.8; + font-size: 12px; +} +.selectize-control .selectize-input > div .email:after { + content: '>'; + opacity: 0.8; + font-size: 12px; +} +.selectize-control .selectize-dropdown .caption { + font-size: 12px; + display: block; + color: #a0a0a0; +} select.form-control option { color: #555; background-color: #fff; diff --git a/data/interfaces/default/notifier_config.html b/data/interfaces/default/notifier_config.html index 8395a804..b57ac7a2 100644 --- a/data/interfaces/default/notifier_config.html +++ b/data/interfaces/default/notifier_config.html @@ -1,6 +1,10 @@ <%! - from plexpy import helpers, notifiers + import json + from plexpy import helpers, notifiers, users available_notification_actions = notifiers.available_notification_actions() + + user_emails = [{'user': u['friendly_name'] or u['username'], 'email': u['email']} for u in users.Users().get_users() if u['email']] + sorted(user_emails, key=lambda u: u['user']) %> % if notifier: @@ -39,7 +43,7 @@
-
+
% if item['name'] == 'osx_notify_app': Register @@ -62,7 +66,7 @@
-
+
@@ -80,7 +84,7 @@
-
+
@@ -185,7 +189,7 @@
-
+
@@ -212,7 +216,7 @@
-
+
@@ -278,7 +282,7 @@ % endif
-
+
@@ -465,6 +469,70 @@ var osx_notify_app = $('#osx_notify_app').val(); $.get('osxnotifyregister', { 'app': osx_notify_app }, function (data) { showMsg(' ' + data, false, true, 3000); }); }) + + % elif notifier['agent_name'] == 'email': + var REGEX_EMAIL = '([a-z0-9!#$%&\'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&\'*+/=?^_`{|}~-]+)*@' + + '(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)'; + var $email_selectors = $('#email_to, #email_cc, #email_bcc').selectize({ + plugins: ['remove_button'], + persist: false, + maxItems: null, + valueField: 'email', + labelField: 'user', + searchField: ['user', 'email'], + options: ${json.dumps(user_emails) | n}, + render: { + item: function(item, escape) { + return '
' + + (item.user ? '' + escape(item.user) + '' : '') + + (item.email ? '' : '') + + '
'; + }, + option: function(item, escape) { + var label = item.user || item.email; + var caption = item.user ? item.email : null; + return '
' + + escape(label) + + (caption ? '' + escape(caption) + '' : '') + + '
'; + } + }, + createFilter: function(input) { + var match, regex; + + // email@address.com + regex = new RegExp('^' + REGEX_EMAIL + '$', 'i'); + match = input.match(regex); + if (match) return !this.options.hasOwnProperty(match[0]); + + // user + regex = new RegExp('^([^<]*)\<' + REGEX_EMAIL + '\>$', 'i'); + match = input.match(regex); + if (match) return !this.options.hasOwnProperty(match[2]); + + return false; + }, + create: function(input) { + if ((new RegExp('^' + REGEX_EMAIL + '$', 'i')).test(input)) { + return {email: input}; + } + var match = input.match(new RegExp('^([^<]*)\<' + REGEX_EMAIL + '\>$', 'i')); + if (match) { + return { + email : match[2], + user : $.trim(match[1]) + }; + } + alert('Invalid email address.'); + return false; + } + }); + var email_to = $email_selectors[0].selectize; + var email_cc = $email_selectors[1].selectize; + var email_bcc = $email_selectors[2].selectize; + email_to.setValue(${json.dumps(next((c['value'] for c in notifier['config_options'] if c['name'] == 'email_to'), [])) | n}); + email_cc.setValue(${json.dumps(next((c['value'] for c in notifier['config_options'] if c['name'] == 'email_cc'), [])) | n}); + email_bcc.setValue(${json.dumps(next((c['value'] for c in notifier['config_options'] if c['name'] == 'email_bcc'), [])) | n}); % endif function validateLogic() { diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py index b1e91a48..5ec858c7 100644 --- a/plexpy/notifiers.py +++ b/plexpy/notifiers.py @@ -60,6 +60,7 @@ import logger import mobile_app import pmsconnect import request +import users from plexpy.config import _BLACKLIST_KEYS, _WHITELIST_KEYS @@ -1245,6 +1246,16 @@ class EMAIL(Notifier): 'html_support': 1 } + def __init__(self, config=None): + super(EMAIL, self).__init__(config=config) + + if not isinstance(self.config['to'], list): + self.config['to'] = [x.strip() for x in self.config['to'].split(';')] + if not isinstance(self.config['cc'], list): + self.config['cc'] = [x.strip() for x in self.config['cc'].split(';')] + if not isinstance(self.config['bcc'], list): + self.config['bcc'] = [x.strip() for x in self.config['bcc'].split(';')] + def notify(self, subject='', body='', action='', **kwargs): if not subject or not body: return @@ -1259,13 +1270,10 @@ class EMAIL(Notifier): msg['Subject'] = subject msg['From'] = email.utils.formataddr((self.config['from_name'], self.config['from'])) - msg['To'] = self.config['to'] - msg['CC'] = self.config['cc'] + msg['To'] = ','.join(self.config['to']) + msg['CC'] = ','.join(self.config['cc']) - recipients = [x.strip() for x in self.config['to'].split(';')] \ - + [x.strip() for x in self.config['cc'].split(';')] \ - + [x.strip() for x in self.config['bcc'].split(';')] - recipients = filter(None, recipients) + recipients = self.config['to'] + self.config['cc'] + self.config['bcc'] try: mailserver = smtplib.SMTP(self.config['smtp_server'], self.config['smtp_port']) @@ -1289,6 +1297,8 @@ class EMAIL(Notifier): return False def return_config_options(self): + user_emails = {} # User selection set with selectize options + config_option = [{'label': 'From Name', 'value': self.config['from_name'], 'name': 'email_from_name', @@ -1304,20 +1314,23 @@ class EMAIL(Notifier): {'label': 'To', 'value': self.config['to'], 'name': 'email_to', - 'description': 'The email address(es) of the recipients, separated by semicolons (;).', - 'input_type': 'text' + 'description': 'The email address(es) of the recipients.', + 'input_type': 'select', + 'select_options': user_emails }, {'label': 'CC', 'value': self.config['cc'], 'name': 'email_cc', - 'description': 'The email address(es) to CC, separated by semicolons (;).', - 'input_type': 'text' + 'description': 'The email address(es) to CC.', + 'input_type': 'select', + 'select_options': user_emails }, {'label': 'BCC', 'value': self.config['bcc'], 'name': 'email_bcc', - 'description': 'The email address(es) to BCC, separated by semicolons (;).', - 'input_type': 'text' + 'description': 'The email address(es) to BCC.', + 'input_type': 'select', + 'select_options': user_emails }, {'label': 'SMTP Server', 'value': self.config['smtp_server'], diff --git a/plexpy/users.py b/plexpy/users.py index 4c79939b..4ba3ee9d 100644 --- a/plexpy/users.py +++ b/plexpy/users.py @@ -580,6 +580,27 @@ class Users(object): return recently_watched + def get_users(self): + monitor_db = database.MonitorDatabase() + + try: + query = 'SELECT user_id, username, friendly_name, email FROM users WHERE deleted_user = 0' + result = monitor_db.select(query=query) + except Exception as e: + logger.warn(u"Tautulli Users :: Unable to execute database query for get_users: %s." % e) + return None + + users = [] + for item in result: + user = {'user_id': item['user_id'], + 'username': item['username'], + 'friendly_name': item['friendly_name'], + 'email': item['email'] + } + users.append(user) + + return users + def delete_all_history(self, user_id=None): monitor_db = database.MonitorDatabase()