Last active
July 26, 2020 10:29
-
-
Save dmerejkowsky/d105bbf82b7746bf60b044ebd1c5fef8 to your computer and use it in GitHub Desktop.
Accompanying code for https://dmerej.info/blog/post/my-blogging-flow-part-2-publishing/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Code for https://dmerej.info/blog/post/my-blogging-flow-part-2-publishing/, | |
because some people asked. | |
Entry points are either `publish()`or `new_post()`. | |
""" | |
import contextlib | |
from email.mime.multipart import MIMEMultipart | |
from email.mime.text import MIMEText | |
import subprocess | |
import smtplib | |
import sys | |
import feedparser | |
from path import Path | |
import ruamel.yaml | |
import twitter | |
from mastodon import Mastodon | |
import markdown | |
CFG_PATH = Path("~/.config/dmerej.info.yml").expanduser() | |
NEW_POST_EMAIL_SUBJECT = "[dmerej.info] New blog post" | |
NEW_POST_TEXT = """\ | |
New #blog post | |
{post_title}: {post_url} | |
{hashtags} | |
""" | |
NEW_POST_EMAIL_MD = """\ | |
### New blog post on dmerej.info/blog | |
[{post_title}]({post_url}) | |
To unsubscribe, reply to this e-mail and tell me you want to opt out, | |
no questions asked :) | |
""" | |
NEW_POST_EMAIL_HTML = markdown.markdown(NEW_POST_EMAIL_MD) | |
def publish(): | |
""" Synchronize local blog repo on the server """ | |
blog_src_path = Path(read_conf()["blog"]["src"]) | |
build(blog_src_path) | |
rsync(blog_src_path) | |
def new_post(tags): | |
""" Tell the world about the latest article """ | |
title, url = fetch_latest_article() | |
notify_newsletter_subscribers(title, url) | |
notify_social_media(title, url, tags) | |
def read_conf(): | |
parsed = ruamel.yaml.safe_load(CFG_PATH.read_text()) | |
return parsed | |
class Tweeting: | |
""" Wraps the twitter library for a simpler API """ | |
def __init__(self): | |
conf = read_conf() | |
auth_dict = conf["twitter"]["auth"] | |
keys = ["token", "token_secret", "api_key", "api_secret"] | |
auth_values = (auth_dict[key] for key in keys) | |
auth = twitter.OAuth(*auth_values) | |
self._api = twitter.Twitter(auth=auth) | |
def tweet(self, text): | |
self._api.statuses.update(status=text) | |
class Tooting: | |
""" Wraps the Mastodon library for a simple API """ | |
def __init__(self): | |
conf = read_conf() | |
mastodon_conf = conf["mastodon"] | |
instance_url = mastodon_conf["instance_url"] | |
auth_conf = mastodon_conf["auth"] | |
client_id = auth_conf["client_id"] | |
client_secret = auth_conf["client_secret"] | |
token = auth_conf["token"] | |
self.mastodon = Mastodon( | |
client_id=client_id, | |
client_secret=client_secret, | |
access_token=token, | |
api_base_url=instance_url, | |
) | |
def toot(self, text): | |
self.mastodon.toot(text) | |
def build(blog_src_path): | |
""" Build the static blog pages, without the drafts """ | |
cmd = ["hugo", "--buildDrafts=false"] | |
public_path = blog_src_path / "public" | |
public_path.rmtree_p() | |
subprocess.run(cmd, cwd=blog_src_path, check=True) | |
def rsync(blog_src_path, dry_run=False): | |
""" Synchronize the built HTML pages with the files on the server """ | |
# fmt: off | |
cmd = [ | |
"rsync", | |
"--recursive", "--itemize-changes", "--checksum", "--delete" | |
"public", | |
"ssh://...", | |
] | |
# fmt: on | |
subprocess.run(cmd, cwd=blog_src_path, check=True) | |
def fetch_latest_article(): | |
""" Extract the latest title and url from the RSS feed """ | |
feed = feedparser.parse("https://dmerej.info/blog/index.xml") | |
latest = feed.entries[0] | |
title = latest.title | |
url = latest.link | |
return title, url | |
def notify_newsletter_subscribers(title, url): | |
""" Send an email to the recipents listed in the config file """ | |
conf = read_conf() | |
recipients = conf["newsletter"]["subscribers"] | |
author = conf["newsletter"]["from"] | |
subject = NEW_POST_EMAIL_SUBJECT | |
text = NEW_POST_EMAIL_MD.format(post_url=url, post_title=title) | |
html = NEW_POST_EMAIL_HTML.format(post_url=url, post_title=title) | |
send_email( | |
subject=subject, author=author, recipients=recipients, text=text, html=html | |
) | |
def notify_social_media(title, url, tags): | |
""" | |
Create a tweet on twitter and a toot on mastodon about the | |
latest blog post | |
""" | |
hashtags = " ".join(["#" + x for x in tags]) | |
text = NEW_POST_TEXT.format(post_url=url, post_title=title, hashtags=hashtags) | |
tweeting = Tweeting() | |
tweeting.tweet(text) | |
tooting = Tooting() | |
tooting.toot(text) | |
@contextlib.contextmanager | |
def mail_server(conf): | |
""" Wrap SMTP class for a simler API """ | |
smtp_conf = conf["smtp"] | |
server = smtplib.SMTP(smtp_conf["address"]) | |
server.starttls() | |
auth_conf = conf["auth"] | |
server.login(auth_conf["login"], auth_conf["password"]) | |
yield server | |
server.quit() | |
def send_email(*, subject, author, recipients, text, html): | |
""" Generic function to send emails using a SMTP server """ | |
conf = read_conf() | |
with mail_server(conf["email"]) as server: | |
message = MIMEMultipart("alternative") | |
message["subject"] = subject | |
message["From"] = author | |
part1 = MIMEText(text, "plain") | |
part2 = MIMEText(html, "html") | |
message.attach(part1) | |
message.attach(part2) | |
server.sendmail(author, recipients, message.as_string()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
blog: | |
src: /home/dmerej/src/blog | |
email: | |
smtp: | |
address: ... | |
auth: | |
login: ... | |
password: ... | |
newsletter: | |
from: ... | |
subscribers: | |
- ..@... | |
- ..@... | |
twitter: | |
auth: | |
api_key: ... | |
api_secret: ... | |
token: ... | |
token_secret: ... | |
mastodon: | |
instance_url: "https://mamot.fr" | |
auth: | |
client_id: ... | |
client_secret: ... | |
token: ... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[tool.poetry] | |
name = "dmerej.info" | |
version = "0.1.0" | |
description = "tools for dmerej.info" | |
# ... | |
[tool.poetry.dependencies] | |
python = "^3.7" | |
feedparser = "^5.2.1" | |
markdown = "^3.1.1" | |
"Mastodon.py" = "^1.5.0" | |
path = "^13.1.0" | |
"ruamel.yaml" = "^0.16.7" | |
twitter = "^1.18.0" | |
[build-system] | |
requires = ["poetry>=0.12"] | |
build-backend = "poetry.masonry.api" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment