From aee572705d0fb460f1e585d6261b527f896f2de0 Mon Sep 17 00:00:00 2001 From: Michael Tremer Date: Fri, 29 Nov 2019 18:19:31 +0000 Subject: [PATCH] blog: Send out emails for posts Signed-off-by: Michael Tremer --- Makefile.am | 6 +++ src/backend/base.py | 25 ++++----- src/backend/blog.py | 53 +++++++++++++++++++ src/backend/messages.py | 8 ++- src/crontab/ipfire | 3 ++ src/templates/blog/messages/announcement.html | 33 ++++++++++++ src/templates/blog/messages/announcement.txt | 16 ++++++ src/templates/messages/main.scss | 4 ++ 8 files changed, 135 insertions(+), 13 deletions(-) create mode 100644 src/templates/blog/messages/announcement.html create mode 100644 src/templates/blog/messages/announcement.txt diff --git a/Makefile.am b/Makefile.am index c9610d3b..a5f6e265 100644 --- a/Makefile.am +++ b/Makefile.am @@ -152,6 +152,12 @@ templates_blog_DATA = \ templates_blogdir = $(templatesdir)/blog +templates_blog_messages_DATA = \ + src/templates/blog/messages/announcement.html \ + src/templates/blog/messages/announcement.txt + +templates_blog_messagesdir = $(templates_blogdir)/messages + templates_blog_modules_DATA = \ src/templates/blog/modules/history-navigation.html \ src/templates/blog/modules/list.html \ diff --git a/src/backend/base.py b/src/backend/base.py index 1578a015..6bda25ab 100644 --- a/src/backend/base.py +++ b/src/backend/base.py @@ -96,18 +96,19 @@ class Backend(object): async def run_task(self, task, *args, **kwargs): tasks = { - "check-mirrors" : self.mirrors.check_all, - "check-spam" : self.accounts.check_spam, - "cleanup" : self.cleanup, - "launch-campaigns" : self.campaigns.launch_manually, - "run-campaigns" : self.campaigns.run, - "scan-files" : self.releases.scan_files, - "send-message" : self.messages.send_cli, - "send-all-messages" : self.messages.queue.send_all, - "test-blacklist" : self.geoip.test_blacklist, - "test-ldap" : self.accounts.test_ldap, - "tweet" : self.tweets.tweet, - "update-blog-feeds" : self.blog.update_feeds, + "announce-blog-posts" : self.blog.announce, + "check-mirrors" : self.mirrors.check_all, + "check-spam" : self.accounts.check_spam, + "cleanup" : self.cleanup, + "launch-campaigns" : self.campaigns.launch_manually, + "run-campaigns" : self.campaigns.run, + "scan-files" : self.releases.scan_files, + "send-message" : self.messages.send_cli, + "send-all-messages" : self.messages.queue.send_all, + "test-blacklist" : self.geoip.test_blacklist, + "test-ldap" : self.accounts.test_ldap, + "tweet" : self.tweets.tweet, + "update-blog-feeds" : self.blog.update_feeds, } # Get the task from the list of all tasks diff --git a/src/backend/blog.py b/src/backend/blog.py index 61f4ba3f..ceb438a1 100644 --- a/src/backend/blog.py +++ b/src/backend/blog.py @@ -2,6 +2,7 @@ import datetime import feedparser +import html2text import markdown import markdown.extensions import markdown.preprocessors @@ -165,6 +166,14 @@ class Blog(misc.Object): for row in res: yield row.year + async def announce(self): + posts = self._get_posts("SELECT * FROM blog \ + WHERE (published_at IS NOT NULL AND published_at <= NOW()) \ + AND announced_at IS NULL") + + for post in posts: + await post.announce() + async def update_feeds(self): """ Updates all enabled feeds @@ -300,6 +309,34 @@ class Post(misc.Object): """ return self.data.html or self.backend.blog._render_text(self.text, lang=self.lang) + @lazy_property + def plaintext(self): + h = html2text.HTML2Text() + h.ignore_links = True + + return h.handle(self.html) + + # Excerpt + + @property + def excerpt(self): + paragraphs = self.plaintext.split("\n\n") + + excerpt = [] + + for paragraph in paragraphs: + excerpt.append(paragraph) + + # Add another paragraph if we encountered a headline + if paragraph.startswith("#"): + continue + + # End if this paragraph was long enough + if len(paragraph) >= 40: + break + + return "\n\n".join(excerpt) + # Tags @property @@ -355,6 +392,22 @@ class Post(misc.Object): # Update search indices self.backend.blog.refresh() + async def announce(self): + # Get people who should receive this message + group = self.backend.groups.get_by_gid("promotional-consent") + if not group: + return + + with self.db.transaction(): + # Generate an email for everybody in this group + for account in group: + self.backend.messages.send_template("blog/messages/announcement", + account=account, post=self) + + # Mark this post as announced + self.db.execute("UPDATE blog SET announced_at = CURRENT_TIMESTAMP \ + WHERE id = %s", self.id) + class PrettyLinksExtension(markdown.extensions.Extension): def extendMarkdown(self, md): diff --git a/src/backend/messages.py b/src/backend/messages.py index 69dbf802..f1481f11 100644 --- a/src/backend/messages.py +++ b/src/backend/messages.py @@ -5,6 +5,7 @@ import email.mime.multipart import email.mime.text import email.utils import logging +import random import subprocess import tornado.locale import tornado.template @@ -32,7 +33,7 @@ class Messages(misc.Object): def make_recipient(self, recipient): # Use the contact instead of the account if isinstance(recipient, accounts.Account): - recipient = "%s <%s>" % (recipient, recipient.email) + recipient = account.email_to # Fall back to pass on strings return recipient @@ -159,6 +160,8 @@ class Messages(misc.Object): """ account = self.backend.accounts.get_by_mail(recipient) + posts = list(self.backend.blog.get_newest(limit=5)) + kwargs = { "account" : account, "first_name" : account.first_name, @@ -169,6 +172,9 @@ class Messages(misc.Object): # Random activation/reset codes "activation_code" : util.random_string(36), "reset_code" : util.random_string(64), + + # The latest blog post + "post" : random.choice(posts), } return self.send_template(template, recipients=[recipient,], **kwargs) diff --git a/src/crontab/ipfire b/src/crontab/ipfire index df1293c6..0bf86500 100644 --- a/src/crontab/ipfire +++ b/src/crontab/ipfire @@ -12,6 +12,9 @@ SHELL=/bin/bash # Run campaigns */5 * * * * nobody ipfire.org run-campaigns +# Announce blog posts +*/5 * * * * nobody ipfire.org announce-blog-posts + # Cleanup once an hour 30 * * * * nobody ipfire.org cleanup diff --git a/src/templates/blog/messages/announcement.html b/src/templates/blog/messages/announcement.html new file mode 100644 index 00000000..566069bf --- /dev/null +++ b/src/templates/blog/messages/announcement.html @@ -0,0 +1,33 @@ +{% extends "../../messages/base-promo.html" %} + +{% block content %} +

+ {{ _("there is a new post from %s on the IPFire Blog:") % post.author }} +

+ +

+ {{ post.title }} +

+ + {% if post.excerpt %} +
{{ post.excerpt }}
+ {% end %} + + + + + + + + +{% end block %} diff --git a/src/templates/blog/messages/announcement.txt b/src/templates/blog/messages/announcement.txt new file mode 100644 index 00000000..fb4a06b2 --- /dev/null +++ b/src/templates/blog/messages/announcement.txt @@ -0,0 +1,16 @@ +From: IPFire Project +To: {{ account.email_to }} +Subject: {{ post.title }} +X-Auto-Response-Suppress: OOF + +{{ _("Hello %s,") % account.first_name }} + +{{ _("there is a new post from %s on the IPFire Blog:") % post.author }} + + {{ post.title }} + +{% if post.excerpt %}{{ "\n".join((" %s" % l for l in post.excerpt.splitlines())) }}{% end %} + +{{ _("Click here to read more:") }} + + https://blog.ipfire.org/post/{{ post.slug }} diff --git a/src/templates/messages/main.scss b/src/templates/messages/main.scss index 1f469178..2e44dbfc 100644 --- a/src/templates/messages/main.scss +++ b/src/templates/messages/main.scss @@ -147,6 +147,10 @@ a { text-decoration: underline; } +blockquote { + font-style: italic; +} + // Buttons .btn { -- 2.39.2