Faire apparaître l’image d’un post WordPress sur Mastodon

J’ai remarqué que mes articles s’affichaient sans l’image mise en avant lorsque je les partage sur Mastodon, ce qui est très agaçant. J’ai cherché pourquoi et trouvé deux solutions.

Mastodon essaie d’abord de construire une preview sur la base des meta OpenGraph, et il m’en manquait. Il faut, à minima :

  • og:type
  • og:title
  • og:description
  • og:image
  • og:url
  • og:locale

Sinon, Mastodon récupère les données intéressantes d’un lien via l’adresse présente dans les entêtes de la page du post :

<link rel="alternate" type="application/json+oembed" href=...>

Cette URL retourne du JSON, généré par WordPress, sous le format suivant :

{
  "version": "1.0",
  "provider_name": "colin@colino.net",
  "provider_url": "https://www.colino.net/wordpress",
  "author_name": "Colin",
  "author_url": "/wordpress/archives/author/colin/",
  "title": "Au revoir Twitter, et merci !",
  "type": "rich",
  "width": 600,
  "height": 338,
  "html": "<blockquote class=\"wp-embedded-content\" data-secret=\"YsQM2MlHiO\"><a href=\"/wordpress/archives/2022/11/27/au-revoir-twitter-et-merci/\">Au revoir Twitter, et merci !</a></blockquote><iframe sandbox=\"allow-scripts\" security=\"restricted\" src=\"/wordpress/archives/2022/11/27/au-revoir-twitter-et-merci/embed/#?secret=YsQM2MlHiO\" width=\"600\" height=\"338\" title=\"« Au revoir Twitter, et merci ! » &#8212; colin@colino.net\" data-secret=\"YsQM2MlHiO\" frameborder=\"0\" marginwidth=\"0\" marginheight=\"0\" scrolling=\"no\" class=\"wp-embedded-content\"></iframe><script type=\"text/javascript\">\n/*! This file is auto-generated */\n!function(c,l){\"use strict\";var e=!1,o=!1;if(l.querySelector)if(c.addEventListener)e=!0;if(c.wp=c.wp||{},c.wp.receiveEmbedMessage);else if(c.wp.receiveEmbedMessage=function(e){var t=e.data;if(!t);else if(!(t.secret||t.message||t.value));else if(/[^a-zA-Z0-9]/.test(t.secret));else{for(var r,s,a,i=l.querySelectorAll('iframe[data-secret=\"'+t.secret+'\"]'),n=l.querySelectorAll('blockquote[data-secret=\"'+t.secret+'\"]'),o=0;o<n.length;o++)n[o].style.display=\"none\";for(o=0;o<i.length;o++)if(r=i[o],e.source!==r.contentWindow);else{if(r.removeAttribute(\"style\"),\"height\"===t.message){if(1e3<(s=parseInt(t.value,10)))s=1e3;else if(~~s<200)s=200;r.height=s}if(\"link\"===t.message)if(s=l.createElement(\"a\"),a=l.createElement(\"a\"),s.href=r.getAttribute(\"src\"),a.href=t.value,a.host===s.host)if(l.activeElement===r)c.top.location.href=t.value}}},e)c.addEventListener(\"message\",c.wp.receiveEmbedMessage,!1),l.addEventListener(\"DOMContentLoaded\",t,!1),c.addEventListener(\"load\",t,!1);function t(){if(o);else{o=!0;for(var e,t,r,s=-1!==navigator.appVersion.indexOf(\"MSIE 10\"),a=!!navigator.userAgent.match(/Trident.*rv:11\\./),i=l.querySelectorAll(\"iframe.wp-embedded-content\"),n=0;n<i.length;n++){if(!(r=(t=i[n]).getAttribute(\"data-secret\")))r=Math.random().toString(36).substr(2,10),t.src+=\"#?secret=\"+r,t.setAttribute(\"data-secret\",r);if(s||a)(e=t.cloneNode(!0)).removeAttribute(\"security\"),t.parentNode.replaceChild(e,t);t.contentWindow.postMessage({message:\"ready\",secret:r},\"*\")}}}}(window,document);\n</script>\n",
  "thumbnail_url": "/wordpress/wp-content/uploads/image-59.png",
  "thumbnail_width": 600,
  "thumbnail_height": 305
}

Mastodon ne veut rien avoir à voir avec un OEmbed de type: rich, expliquant pourquoi dans son code source :

    case @card.type
    when 'link'
      @card.image_remote_url = (url + embed[:thumbnail_url]).to_s if embed[:thumbnail_url].present?
    when 'photo'
      return false if embed[:url].blank?

      @card.embed_url        = (url + embed[:url]).to_s
      @card.image_remote_url = (url + embed[:url]).to_s
      @card.width            = embed[:width].presence  || 0
      @card.height           = embed[:height].presence || 0
    when 'video'
      @card.width            = embed[:width].presence  || 0
      @card.height           = embed[:height].presence || 0
      @card.html             = Sanitize.fragment(embed[:html], Sanitize::Config::MASTODON_OEMBED)
      @card.image_remote_url = (url + embed[:thumbnail_url]).to_s if embed[:thumbnail_url].present?
    when 'rich'
      # Most providers rely on <script> tags, which is a no-no
      return false
    end

Cela se comprend aisément : personne de sain d’esprit n’a envie d’injecter du javascript aléatoirement dans une autre page. Heureusement, nous pouvons corriger le générateur d’OEmbed de WordPress grâce à un filtre. Pour cela, on peut ajouter ce filtre dans le fichier functions.php de notre thème enfant, supprimer le champ html and positionner le type à link :

/* Return link instead of rich oembed data */
function filter_oembed_response_data($data) {
  if ($data['type'] === 'rich') {
    $data['type'] = 'link';
    unset($data['html']);
  }
  /* while we're at it remove single quotes html entities */
  $data["title"] = str_replace("&rsquo;","'", $data["title"]);

  return $data;
}
add_filter('oembed_response_data', 'filter_oembed_response_data', 11, 4);

Enfin, Mastodon n’utilisera pas une image de plus de 2Mo. WordPress jusqu’à la version 6.6.2, à minima, envoie les images pleine taille en thumbnail_url, ce qui peut rapidement dépasser cette limite. Voir #62094.

Avant / après :

Un pouet avec un lien WordPress sans image
Un pouet avec un lien WordPress avec son image