De reservat a rei de les xarxes: automatitzant les vistes prèvies dels enllaços amb Zola
T’has preguntat alguna vegada com aplicacions com WhatsApp, Telegram o Mastodon mostren una vista prèvia d’un enllaç? A continuació, trobaràs la captura de pantalla d’un link compartit a WhatsApp sense vista prèvia. Fes clic a la imatge per veure com canvia en afegir la imatge:
Molt millor, oi? Aquestes imatges s’obtenen d’etiquetes HTML, concretament og:image
i twitter:image
. Pots assignar qualsevol imatge a aquestes etiquetes.
Mentre desenvolupava el tema del meu lloc web —tabi—, em vaig topar amb un article de Simon Willison que explica com utilitzar la seva eina shot-scraper
per generar aquestes imatges.
Vaig decidir explorar si podia seguir una enfocament similar per crear les miniatures per a tots els articles d’aquest lloc, així com els de la demo de tabi. I, encara més divertit, intentar automatitzar el procés per a futures publicacions.
Context
Aquest lloc està construït amb Zola, que utilitza el front matter de TOML per emmagatzemar metadades (per exemple, títol, data, etiquetes…). Per defecte, Zola no gestiona aquestes imatges.
No obstant això, és possible afegir variables personalitzades a la secció [extra]
, així que vaig decidir afegir una clau social_media_card
a tabi (PR 130).
Ara, qualsevol lloc que utilitzi tabi pot afegir social_media_card = path/to/img.jpg
a un fitxer. Quan la publicació es comparteixi a les xarxes socials, aquesta imatge es mostrarà com a vista prèvia.
Primer pas: aconseguir una bona imatge
Vaig començar a experimentar amb shot-scraper
, intentant trobar la comanda perfecta. Després d’algunes proves[1], vaig acabar amb:
Aquesta comanda crea una captura de pantalla JPEG de 1400 per 800 amb un factor de qualitat de 60. Crec que funciona bé per a les proporcions del tema:
La qualitat no és excel·lent, però és prou bona™ per a l’ús que se’n farà.
Triant el nom dels fitxers
La documentació per a desenvolupadors de Meta diu:
«Les imatges per a miniatures s'emmagatzemen en memòria cau en base a l'URL i no s'actualitzaran a menys que l'URL canviï.»
«[Social media card] Images are cached based on the URL and won't be updated unless the URL changes.»
Això significa que si actualitzem una publicació, la URL de la imatge també ha de canviar.
Primer vaig pensar a utilitzar el hash truncat SHA-1 de la captura de pantalla com a prefix per al nom. Si la imatge era diferent, el nom també ho seria.
Després vaig recordar que simplement podia afegir el mecanisme de «cache busting» de Zola a tabi, que afegeix ?h=<sha256>
al final de l’URL. Això simplifica força el procés, especialment quan es tracta d’actualitzar les metadades de l’article.
La lògica
Donat un fitxer Markdown (un article) hem de:
- Fer una captura de pantalla de la pàgina en viu
- Guardar-la a la ruta adequada (per exemple,
static/img/social_cards
) - Actualitzar el front matter (metadades) del fitxer
.md
amb la rutasocial_media_card
Així és com vaig aconseguir els dos primers passos utilitzant Bash:
base_url="http://127.0.0.1:1111" # Interfície/port predeterminats de Zola.
output_path="static/img/social_cards"
post="" # El primer argument que es proporcioni a l'script.
post_name="" # Elimina l'extensió.
url="" # Elimina el prefix "content/"; el directori pare del contingut de Zola.
# Arxiu temporal per a la captura de pantalla.
temp_file=
# Genera la captura de pantalla a l'arxiu temporal.
# Neteja el nom de l'arxiu.
safe_filename= # Slugify.
image_filename=" / .jpg"
# Mou la captura de pantalla al directori de sortida.
¡Fàcil! Vaig guardar l’script com a social-cards-zola
.
Comencen els problemes
Què passa amb els idiomes?
En aquest punt, amb l’script bàsic fet, em vaig trobar amb un problema: els noms d’arxiu no sempre coincideixen amb les URL.
Una publicació en un altre idioma, per exemple, mi-primer-post.es.md
, no estarà disponible a base_url/mi-primer-post.es
, sinó a base_url/es/mi-primer-post
.
Afegim una mica de lògica per extreure el codi d’idioma i construir l’URL adequada. Utilitzant l’expansió de paràmetres de Bash, obtenim:
# Elimina l'extensió i el prefix "content/".
post_name=""
url=""
# Intenta capturar el codi d'idioma.
lang_code=""
if ; then
# No hi havia codi d'idioma.
lang_code=""
else
# Elimina el codi d'idioma de l'URL.
url=""
fi
url=" /"
Llest.
I les seccions?
Ja hem aconseguit que les articles tinguin una captura de pantalla, però què passa amb l’índex principal o la pàgina d’arxiu? A Zola, no són pàgines, sinó seccions, i la seva URL correspon al nom del directori. Per exemple, content/archive/_index.fr.md
està disponible a base_url/fr/archive/
.
Podem adaptar la lògica anterior per a eliminar la part _index
del nom del fitxer:
Així és com convert_filename_to_url
gestiona diferents fitxers:
Entrada | Sortida |
---|---|
content/_index.es.md | es/ |
content/blog/markdown.fr.md | fr/blog/markdown/ |
content/blog/comments.md | blog/comments/ |
content/archive/_index.md | archive/ |
content/archive/_index.ca.md | ca/archive/ |
Si afegim l’URL base abans de cada sortida, obtenim l’enllaç complet.
Modificant les metadades
Ara puc generar fàcilment les captures de pantalla per a les entrades, però encara necessito associar les imatges amb els fitxers Markdown.
Per a actualitzar les metadades, vaig utilitzar awk
per a trobar on comença el front matter, localitzar o crear la secció [extra]
, i afegir o actualitzar la clau social_media_card
:
# Inicialitzar les variables per al seguiment de l'estat.
BEGIN in_extra=done=front_matter=0; }
# Funció per a inserir la ruta de la miniatura.
function insert_card() print "social_media_card = \"" card_path "\""; done=1; }
# Si la miniatura s'ha inserit, simplement mostra les línies restants.
if done print; next; }
# Canviar la bandera front_matter a l'inici, denotat per +++
if /^\+\+\+/ && front_matter == 0
front_matter = 1;
print "+++";
next;
}
# Detectar secció [extra] i establir la bandera extra_exists.
if /^\[\]/ in_extra=1; extra_exists=1; print; next; }
# Actualitzar la miniatura existent per a xarxes socials.
if in_extra && /^/ insert_card ; in_extra=0; next; }
# Fi del front matter o inici d'una nova secció.
if in_extra && /^\[+\]/ || /^\+\+\+/ && front_matter == 1
insert_card ; # Afegir la miniatura faltant per a xarxes socials.
in_extra=0;
}
# Inserir la secció [extra] faltant.
if /^\+\+\+/ && front_matter == 1 && in_extra == 0 && extra_exists == 0
print "\n[extra]";
insert_card ;
in_extra=0;
front_matter = 0;
print "+++";
next;
}
# Mostrar totes les altres línies tal com són.
print;
}
Vaig afegir aquesta funció a social-cards-zola
, que s’activa amb l’opció -u
o --update-front-matter
.
Concurrència
Volia crear les imatges per a totes les entrades alhora, així que vaig utilitzar GNU parallel per processar tots els arxius Markdown de forma concurrent:
|
Uns segons després, ja tenia un munt de captures de pantalla i arxius Markdown actualitzats. 🎉
Automatització del procés
Naturalment, el pas següent va ser afegir aquest procés al meu ganxo pre-commit de Git, que ja estava actualitzant les dates de les publicacions i optimitzant arxius PNG.
Cada vegada que faig un commit d’arxius Markdown (nous o modificats), genero la captura de pantalla i actualitzo la metadada de la publicació o de la secció:
# Crear/actualitzar la miniatura per a xarxes socials per a cada arxiu Markdown modificat.
arxius_md_modificats=
if ; then
|
fi
Pots veure com queda integrat en el ganxo pre-commit complet.
L’script complet de social-cards-zola
, amb més ajustos (com l’ús d’una clau de front matter diferent), està disponible al repositori d’aquest lloc.
Fi?
L’script funciona, però és força fràgil: falla si l’uses fora de la ruta arrel del lloc, requereix parallel
per gestionar la concurrència, i probablement donarà problemes si intentes actualitzar molts arxius Markdown alhora (Zola reconstrueix el lloc després de cada modificació, retornant errors 404 breument).
Estic content d’haver resolt el problema, però també ho veig com una oportunitat per convertir aquest script prou bo™ en un petit però sòlid programa en Rust —un bon primer projecte en Rust, no?
Així que… continuarà, potser.
-
Inicialment, estava convertint les captures de pantalla PNG a WebP, ja que són significativament més petites (~40KB) que les JPEG d’aspecte similar (~100KB). Tanmateix, en intentar fer la captura per a la primera imatge de l’article, em vaig adonar que WhatsApp no admet miniatures per a xarxes socials en format WebP. Llàstima. ↩