Source code for nefelibata.assistants.twitter_card
import hashlib
import mimetypes
import re
import urllib.parse
from pathlib import Path
import requests
from bs4 import BeautifulSoup
from mutagen.mp3 import MP3
from nefelibata.assistants import Assistant
from nefelibata.assistants import Scope
from nefelibata.post import Post
from nefelibata.utils import modify_html
[docs]class TwitterCardAssistant(Assistant):
scopes = [Scope.POST]
container_filename = "twitter_card.html"
[docs] def process_post(self, post: Post, force: bool = False) -> None:
file_path = post.file_path.with_suffix(".html")
post_directory = post.file_path.parent
card_metadata = {
"twitter:card": "summary",
"twitter:site": f'@{self.config["twitter"]["handle"]}',
"twitter:title": "No title",
"twitter:description": "No description",
# "twitter:image": "",
# "twitter:image:alt": "",
}
# if the post has a single mp3 use a player card instead of summary
mp3_paths = list(post_directory.glob("**/*.mp3"))
if len(mp3_paths) == 1:
container_path = post_directory / self.container_filename
container_url = urllib.parse.urljoin(
self.config["url"],
str(container_path.relative_to(self.root / "posts")),
)
self._create_container(container_path, mp3_paths[0])
card_metadata["twitter:card"] = "player"
card_metadata["twitter:image"] = urllib.parse.urljoin(
self.config["url"], "img/cassette.png",
)
card_metadata["twitter:player"] = container_url
card_metadata["twitter:player:width"] = "800"
card_metadata["twitter:player:height"] = "464" # 463.15
# add meta tags
soup: BeautifulSoup
with modify_html(file_path) as soup:
# collect metadata from the existing page
for attribute in ["title", "description"]:
meta = soup.head.find("meta", {"property": f"og:{attribute}"})
if meta:
card_metadata[f"twitter:{attribute}"] = meta.attrs["content"]
# add new meta tags with the metadata
for name, content in card_metadata.items():
existing = soup.head.find("meta", {"name": name})
if existing:
if existing.attrs["content"] == content:
continue
else:
existing.decompose()
meta = soup.new_tag("meta", attrs={"name": name, "content": content})
soup.head.append(meta)
def _create_container(self, container_path: Path, mp3_path: Path) -> None:
mp3 = MP3(mp3_path)
# create container.html
if not container_path.exists():
with open(container_path, "w") as fp:
fp.write("<!DOCTYPE html><html><head></head><body></body></html>")
with modify_html(container_path) as soup:
css = soup.head.find("link")
if not css:
css = soup.new_tag(
"link",
href=urllib.parse.urljoin(self.config["url"], "css/cassette.css"),
rel="stylesheet",
)
soup.head.append(css)
audio = soup.find("audio")
if audio:
audio.decompose()
audio = soup.new_tag(
"audio",
attrs={
"class": "cassette",
"data-album": mp3.get("TALB", "Unknown album"),
"data-artist": mp3.get("TPE1", "Unknown artist"),
"data-title": mp3.get("TIT2", mp3_path.stem),
"src": mp3_path.relative_to(container_path.parent),
},
)
# do not append, since <script> tag should come last
soup.body.insert(0, audio)
js = soup.body.find("script")
if not js:
js = soup.new_tag(
"script",
attrs={
"src": urllib.parse.urljoin(
self.config["url"], "js/cassette.js",
),
"async": "",
},
)
soup.body.append(js)