예제 #1
0
    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)
예제 #2
0
    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
                )
예제 #3
0
 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
예제 #4
0
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
예제 #5
0
 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
예제 #6
0
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