def _send(self): if self.config['formatted']: newsletter_stripped = ''.join( l.strip() for l in self.newsletter.splitlines()) plaintext = 'HTML email support is required to view the newsletter.\n' if plexpy.CONFIG.NEWSLETTER_SELF_HOSTED and plexpy.CONFIG.HTTP_BASE_URL: plaintext += self._DEFAULT_BODY.format(**self.parameters) email_reply_msg_id = self.email_reply_msg_id if self.config[ 'threaded'] else None if self.email_config['notifier_id']: return send_notification( notifier_id=self.email_config['notifier_id'], subject=self.subject_formatted, body=newsletter_stripped, plaintext=plaintext, msg_id=self.email_msg_id, reply_msg_id=email_reply_msg_id) else: email = EMAIL(config=self.email_config) return email.notify(subject=self.subject_formatted, body=newsletter_stripped, plaintext=plaintext, msg_id=self.email_msg_id, reply_msg_id=email_reply_msg_id) elif self.config['notifier_id']: return send_notification(notifier_id=self.config['notifier_id'], subject=self.subject_formatted, body=self.body_formatted)
def _send(self): if self.config['formatted']: if self.email_config['notifier_id']: return send_notification( notifier_id=self.email_config['notifier_id'], subject=self.subject_formatted, body=self.newsletter ) else: email = EMAIL(config=self.email_config) return email.notify( subject=self.subject_formatted, body=self.newsletter ) elif self.config['notifier_id']: return send_notification( notifier_id=self.config['notifier_id'], subject=self.subject_formatted, body=self.body_formatted )
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
class Newsletter(object): NAME = '' _DEFAULT_CONFIG = { 'custom_cron': 0, 'time_frame': 7, 'time_frame_units': 'days', 'formatted': 1, 'threaded': 0, 'notifier_id': 0, 'filename': '', 'save_only': 0 } _DEFAULT_EMAIL_CONFIG = EMAIL().return_default_config() _DEFAULT_EMAIL_CONFIG['from_name'] = 'Tautulli Newsletter' _DEFAULT_EMAIL_CONFIG['notifier_id'] = 0 _DEFAULT_SUBJECT = 'Tautulli Newsletter' _DEFAULT_BODY = 'View the newsletter here: {newsletter_url}' _DEFAULT_MESSAGE = '' _DEFAULT_FILENAME = 'newsletter_{newsletter_uuid}.html' _TEMPLATE = '' def __init__(self, newsletter_id=None, newsletter_id_name=None, config=None, email_config=None, start_date=None, end_date=None, subject=None, body=None, message=None, email_msg_id=None, email_reply_msg_id=None): self.config = self.set_config(config=config, default=self._DEFAULT_CONFIG) self.email_config = self.set_config(config=email_config, default=self._DEFAULT_EMAIL_CONFIG) self.uuid = generate_newsletter_uuid() self.email_msg_id = email_msg_id self.email_reply_msg_id = email_reply_msg_id self.newsletter_id = newsletter_id self.newsletter_id_name = newsletter_id_name or '' self.start_date = None self.end_date = None if end_date: try: self.end_date = arrow.get(end_date, 'YYYY-MM-DD', tzinfo='local').ceil('day') except ValueError: pass if self.end_date is None: self.end_date = arrow.now() if start_date: try: self.start_date = arrow.get(start_date, 'YYYY-MM-DD', tzinfo='local').floor('day') except ValueError: pass if self.start_date is None: if self.config['time_frame_units'] == 'days': self.start_date = self.end_date.shift( days=-self.config['time_frame']) else: self.start_date = self.end_date.shift( hours=-self.config['time_frame']) self.end_time = self.end_date.timestamp self.start_time = self.start_date.timestamp self.parameters = self.build_params() self.subject = subject or self._DEFAULT_SUBJECT self.body = body or self._DEFAULT_BODY self.message = message or self._DEFAULT_MESSAGE self.filename = self.config['filename'] or self._DEFAULT_FILENAME if not self.filename.endswith('.html'): self.filename += '.html' self.subject_formatted, self.body_formatted, self.message_formatted = self.build_text( ) self.filename_formatted = self.build_filename() self.data = {} self.newsletter = None self.is_preview = False self.template_error = None def set_config(self, config=None, default=None): return self._validate_config(config=config, default=default) def _validate_config(self, config=None, default=None): if config is None: return default new_config = {} for k, v in default.items(): if isinstance(v, int): new_config[k] = helpers.cast_to_int(config.get(k, v)) elif isinstance(v, list): c = config.get(k, v) if not isinstance(c, list): new_config[k] = [c] else: new_config[k] = c else: new_config[k] = config.get(k, v) return new_config def retrieve_data(self): pass def _has_data(self): return False def raw_data(self, preview=False): if preview: self.is_preview = True self.retrieve_data() return { 'title': self.NAME, 'parameters': self.parameters, 'data': self.data } def generate_newsletter(self, preview=False): if preview: self.is_preview = True self.retrieve_data() logger.info("Tautulli Newsletters :: Generating newsletter%s." % (' preview' if self.is_preview else '')) newsletter_rendered, self.template_error = serve_template( templatename=self._TEMPLATE, uuid=self.uuid, subject=self.subject_formatted, body=self.body_formatted, message=self.message_formatted, parameters=self.parameters, data=self.data, preview=self.is_preview) if self.template_error: return newsletter_rendered # Force Tautulli footer if '<!-- FOOTER MESSAGE - DO NOT REMOVE -->' in newsletter_rendered: newsletter_rendered = newsletter_rendered.replace( '<!-- FOOTER MESSAGE - DO NOT REMOVE -->', 'Newsletter generated by <a href="https://tautulli.com" target="_blank" ' 'style="text-decoration: underline;color: inherit;font-size: inherit;">Tautulli</a>.' ) else: msg = ( '<div style="text-align: center;padding-top: 100px;padding-bottom: 100px;">' '<p style="font-family: \'Open Sans\', Helvetica, Arial, sans-serif;color: #282A2D;' 'font-size: 18px;line-height: 30px;">' 'The Tautulli newsletter footer was removed from the newsletter template.<br>' 'Please leave the footer in place as it is unobtrusive and supports ' '<a href="https://tautulli.com" target="_blank">Tautulli</a>.<br>Thank you.' '</p></div>') newsletter_rendered = re.sub(r'(<body.*?>)', r'\1' + msg, newsletter_rendered) return newsletter_rendered def send(self): self.newsletter = self.generate_newsletter() if self.template_error: logger.error( "Tautulli Newsletters :: %s newsletter failed to render template. Newsletter not sent." % self.NAME) return False if not self._has_data(): logger.warn( "Tautulli Newsletters :: %s newsletter has no data. Newsletter not sent." % self.NAME) return False self._save() if self.config['save_only']: return True return self._send() def _save(self): newsletter_file = self.filename_formatted newsletter_folder = plexpy.CONFIG.NEWSLETTER_DIR or os.path.join( plexpy.DATA_DIR, 'newsletters') newsletter_file_fp = os.path.join(newsletter_folder, newsletter_file) # In case the user has deleted it manually if not os.path.exists(newsletter_folder): os.makedirs(newsletter_folder) try: with open(newsletter_file_fp, 'wb') as n_file: for line in self.newsletter.splitlines(): if '<!-- IGNORE SAVE -->' not in line: n_file.write((line + '\r\n').encode('utf-8')) #n_file.write(line.strip()) logger.info("Tautulli Newsletters :: %s newsletter saved to '%s'" % (self.NAME, newsletter_file)) except OSError as e: logger.error( "Tautulli Newsletters :: Failed to save %s newsletter to '%s': %s" % (self.NAME, newsletter_file, e)) def _send(self): if self.config['formatted']: newsletter_stripped = ''.join( l.strip() for l in self.newsletter.splitlines()) plaintext = 'HTML email support is required to view the newsletter.\n' if plexpy.CONFIG.NEWSLETTER_SELF_HOSTED and plexpy.CONFIG.HTTP_BASE_URL: plaintext += self._DEFAULT_BODY.format(**self.parameters) email_reply_msg_id = self.email_reply_msg_id if self.config[ 'threaded'] else None if self.email_config['notifier_id']: return send_notification( notifier_id=self.email_config['notifier_id'], subject=self.subject_formatted, body=newsletter_stripped, plaintext=plaintext, msg_id=self.email_msg_id, reply_msg_id=email_reply_msg_id) else: email = EMAIL(config=self.email_config) return email.notify(subject=self.subject_formatted, body=newsletter_stripped, plaintext=plaintext, msg_id=self.email_msg_id, reply_msg_id=email_reply_msg_id) elif self.config['notifier_id']: return send_notification(notifier_id=self.config['notifier_id'], subject=self.subject_formatted, body=self.body_formatted) def build_params(self): parameters = self._build_params() return parameters def _build_params(self): date_format = helpers.momentjs_to_arrow(plexpy.CONFIG.DATE_FORMAT) if plexpy.CONFIG.NEWSLETTER_SELF_HOSTED and plexpy.CONFIG.HTTP_BASE_URL: base_url = plexpy.CONFIG.HTTP_BASE_URL + plexpy.HTTP_ROOT + 'newsletter/' else: base_url = helpers.get_plexpy_url() + '/newsletter/' parameters = { 'server_name': plexpy.CONFIG.PMS_NAME, 'start_date': self.start_date.format(date_format), 'end_date': self.end_date.format(date_format), 'current_year': self.start_date.year, 'current_month': self.start_date.month, 'current_day': self.start_date.day, 'current_hour': self.start_date.hour, 'current_minute': self.start_date.minute, 'current_second': self.start_date.second, 'current_weekday': self.start_date.isocalendar()[2], 'current_week': self.start_date.isocalendar()[1], 'week_number': self.start_date.isocalendar()[1], 'newsletter_time_frame': self.config['time_frame'], 'newsletter_time_frame_units': self.config['time_frame_units'], 'newsletter_url': base_url + self.uuid, 'newsletter_static_url': base_url + 'id/' + self.newsletter_id_name, 'newsletter_uuid': self.uuid, 'newsletter_id': self.newsletter_id, 'newsletter_id_name': self.newsletter_id_name, 'newsletter_password': plexpy.CONFIG.NEWSLETTER_PASSWORD } return parameters def build_text(self): from plexpy.notification_handler import CustomFormatter custom_formatter = CustomFormatter() try: subject = custom_formatter.format(str(self.subject), **self.parameters) except LookupError as e: logger.error( "Tautulli Newsletter :: Unable to parse parameter %s in newsletter subject. Using fallback." % e) subject = str(self._DEFAULT_SUBJECT).format(**self.parameters) except Exception as e: logger.error( "Tautulli Newsletter :: Unable to parse custom newsletter subject: %s. Using fallback." % e) subject = str(self._DEFAULT_SUBJECT).format(**self.parameters) try: body = custom_formatter.format(str(self.body), **self.parameters) except LookupError as e: logger.error( "Tautulli Newsletter :: Unable to parse parameter %s in newsletter body. Using fallback." % e) body = str(self._DEFAULT_BODY).format(**self.parameters) except Exception as e: logger.error( "Tautulli Newsletter :: Unable to parse custom newsletter body: %s. Using fallback." % e) body = str(self._DEFAULT_BODY).format(**self.parameters) try: message = custom_formatter.format(str(self.message), **self.parameters) except LookupError as e: logger.error( "Tautulli Newsletter :: Unable to parse parameter %s in newsletter message. Using fallback." % e) message = str(self._DEFAULT_MESSAGE).format(**self.parameters) except Exception as e: logger.error( "Tautulli Newsletter :: Unable to parse custom newsletter message: %s. Using fallback." % e) message = str(self._DEFAULT_MESSAGE).format(**self.parameters) return subject, body, message def build_filename(self): from plexpy.notification_handler import CustomFormatter custom_formatter = CustomFormatter() try: filename = custom_formatter.format(str(self.filename), **self.parameters) except LookupError as e: logger.error( "Tautulli Newsletter :: Unable to parse parameter %s in newsletter filename. Using fallback." % e) filename = str(self._DEFAULT_FILENAME).format(**self.parameters) except Exception as e: logger.error( "Tautulli Newsletter :: Unable to parse custom newsletter subject: %s. Using fallback." % e) filename = str(self._DEFAULT_FILENAME).format(**self.parameters) return filename 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 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
def return_email_config_options(self): config_options = EMAIL(self.email_config).return_config_options() for c in config_options: c['name'] = 'newsletter_' + c['name'] return config_options
class Newsletter(object): NAME = '' _DEFAULT_CONFIG = {'custom_cron': 0, 'time_frame': 7, 'time_frame_units': 'days', 'formatted': 1, 'notifier_id': 0} _DEFAULT_EMAIL_CONFIG = EMAIL().return_default_config() _DEFAULT_EMAIL_CONFIG['from_name'] = 'Tautulli Newsletter' _DEFAULT_EMAIL_CONFIG['notifier_id'] = 0 _DEFAULT_SUBJECT = 'Tautulli Newsletter' _DEFAULT_BODY = 'View the newsletter here: {newsletter_url}' _DEFAULT_MESSAGE = '' _TEMPLATE_MASTER = '' _TEMPLATE = '' def __init__(self, config=None, email_config=None, start_date=None, end_date=None, subject=None, body=None, message=None): self.config = self.set_config(config=config, default=self._DEFAULT_CONFIG) self.email_config = self.set_config(config=email_config, default=self._DEFAULT_EMAIL_CONFIG) self.uuid = generate_newsletter_uuid() self.start_date = None self.end_date = None if end_date: try: self.end_date = arrow.get(end_date, 'YYYY-MM-DD', tzinfo='local').ceil('day') except ValueError: pass if self.end_date is None: self.end_date = arrow.now() if start_date: try: self.start_date = arrow.get(start_date, 'YYYY-MM-DD', tzinfo='local').floor('day') except ValueError: pass if self.start_date is None: if self.config['time_frame_units'] == 'days': self.start_date = self.end_date.shift(days=-self.config['time_frame']+1).floor('day') else: self.start_date = self.end_date.shift(hours=-self.config['time_frame']).floor('hour') self.end_time = self.end_date.timestamp self.start_time = self.start_date.timestamp self.parameters = self.build_params() self.subject = subject or self._DEFAULT_SUBJECT self.body = body or self._DEFAULT_BODY self.message = message or self._DEFAULT_MESSAGE self.subject_formatted, self.body_formatted, self.message_formatted = self.build_text() self.data = {} self.newsletter = None self.is_preview = False def set_config(self, config=None, default=None): return self._validate_config(config=config, default=default) def _validate_config(self, config=None, default=None): if config is None: return default new_config = {} for k, v in default.iteritems(): if isinstance(v, int): new_config[k] = helpers.cast_to_int(config.get(k, v)) elif isinstance(v, list): c = config.get(k, v) if not isinstance(c, list): new_config[k] = [c] else: new_config[k] = c else: new_config[k] = config.get(k, v) return new_config def retrieve_data(self): pass def _has_data(self): return False def raw_data(self, preview=False): if preview: self.is_preview = True self.retrieve_data() return {'title': self.NAME, 'parameters': self.parameters, 'data': self.data} def generate_newsletter(self, preview=False, master=False): if preview: self.is_preview = True if master: template = self._TEMPLATE_MASTER else: template = self._TEMPLATE self.retrieve_data() return serve_template( templatename=template, uuid=self.uuid, subject=self.subject_formatted, body=self.body_formatted, message=self.message_formatted, parameters=self.parameters, data=self.data, preview=self.is_preview ) def send(self): self.newsletter = self.generate_newsletter() if not self._has_data(): logger.warn(u"Tautulli Newsletters :: %s newsletter has no data. Newsletter not sent." % self.NAME) return False self._save() return self._send() def _save(self): newsletter_file = 'newsletter_%s-%s_%s.html' % (self.start_date.format('YYYYMMDD'), self.end_date.format('YYYYMMDD'), self.uuid) newsletter_folder = plexpy.CONFIG.NEWSLETTER_DIR newsletter_file_fp = os.path.join(newsletter_folder, newsletter_file) # In case the user has deleted it manually if not os.path.exists(newsletter_folder): os.makedirs(newsletter_folder) try: with open(newsletter_file_fp, 'wb') as n_file: for line in self.newsletter.encode('utf-8').splitlines(): if '<!-- IGNORE SAVE -->' not in line: n_file.write(line + '\r\n') logger.info(u"Tautulli Newsletters :: %s newsletter saved to %s" % (self.NAME, newsletter_file)) except OSError as e: logger.error(u"Tautulli Newsletters :: Failed to save %s newsletter to %s: %s" % (self.NAME, newsletter_file, e)) def _send(self): if self.config['formatted']: if self.email_config['notifier_id']: return send_notification( notifier_id=self.email_config['notifier_id'], subject=self.subject_formatted, body=self.newsletter ) else: email = EMAIL(config=self.email_config) return email.notify( subject=self.subject_formatted, body=self.newsletter ) elif self.config['notifier_id']: return send_notification( notifier_id=self.config['notifier_id'], subject=self.subject_formatted, body=self.body_formatted ) def build_params(self): parameters = self._build_params() return parameters def _build_params(self): date_format = helpers.momentjs_to_arrow(plexpy.CONFIG.DATE_FORMAT) if plexpy.CONFIG.NEWSLETTER_SELF_HOSTED and plexpy.CONFIG.HTTP_BASE_URL: base_url = plexpy.CONFIG.HTTP_BASE_URL + plexpy.HTTP_ROOT else: base_url = helpers.get_plexpy_url() + '/' parameters = { 'server_name': plexpy.CONFIG.PMS_NAME, 'start_date': self.start_date.format(date_format), 'end_date': self.end_date.format(date_format), 'week_number': self.start_date.isocalendar()[1], 'newsletter_time_frame': self.config['time_frame'], 'newsletter_time_frame_units': self.config['time_frame_units'], 'newsletter_url': base_url + 'newsletter/' + self.uuid, 'newsletter_uuid': self.uuid } return parameters def build_text(self): from notification_handler import CustomFormatter custom_formatter = CustomFormatter() try: subject = custom_formatter.format(unicode(self.subject), **self.parameters) except LookupError as e: logger.error( u"Tautulli Newsletter :: Unable to parse parameter %s in newsletter subject. Using fallback." % e) subject = unicode(self._DEFAULT_SUBJECT).format(**self.parameters) except Exception as e: logger.error( u"Tautulli Newsletter :: Unable to parse custom newsletter subject: %s. Using fallback." % e) subject = unicode(self._DEFAULT_SUBJECT).format(**self.parameters) try: body = custom_formatter.format(unicode(self.body), **self.parameters) except LookupError as e: logger.error( u"Tautulli Newsletter :: Unable to parse parameter %s in newsletter body. Using fallback." % e) body = unicode(self._DEFAULT_BODY).format(**self.parameters) except Exception as e: logger.error( u"Tautulli Newsletter :: Unable to parse custom newsletter body: %s. Using fallback." % e) body = unicode(self._DEFAULT_BODY).format(**self.parameters) try: message = custom_formatter.format(unicode(self.message), **self.parameters) except LookupError as e: logger.error( u"Tautulli Newsletter :: Unable to parse parameter %s in newsletter message. Using fallback." % e) message = unicode(self._DEFAULT_MESSAGE).format(**self.parameters) except Exception as e: logger.error( u"Tautulli Newsletter :: Unable to parse custom newsletter message: %s. Using fallback." % e) message = unicode(self._DEFAULT_MESSAGE).format(**self.parameters) return subject, body, message def return_config_options(self): return self._return_config_options() def _return_config_options(self): config_options = [] return config_options def return_email_config_options(self): config_options = EMAIL(self.email_config).return_config_options() for c in config_options: c['name'] = 'newsletter_' + c['name'] return config_options