Ever since the Great Federating of Giantpaper, I’ve been thinking about how I would get Activitypub to correctly display the different types of blog posts I have set up. Ex: this post, which looked like this on Mastodon:

Or this one, which turned into this:

There were some things I learned about WP posts ending up on the Fediverse through this plugin:

  • Custom fields (like those generated by Advanced Custom Fields) don’t get included, so links from my linklog posts won’t appear (which is why this post was posted as a micropost instead of a linkpost)
  • Most HTML gets stripped out by some Fedi services like Mastodon, which is why videos weren’t appearing on microposts

The Activitypub plugin has a setting for tweaking the template that gets used for federation:

…but there’s no template tag for custom fields (and no way to not have the plugin render video URLs as video embeds).

Enter Filters

Photo by Nathan Dumlao on Unsplash

I saw that there was a PR for making the post template filterable, that was upcoming for v2.0.0. And against my judgement, I jumped at it when it released, thinking there would be some sort of documentation for the new filters, but NOPE. I didn’t even know what the filter was called!! 😬 I did see the addition of a new filter name in v2.0.0.0, which is what I thought it was going to be, but actually trying to use it to filter stuff got me the dreaded “Your author URL does not return valid JSON for application/activity+json. Please check if your hosting supports alternate Accept headers.” error in WordPress’s Site Health page. And also I (somehow) found that outputting the contents of $template from the first parameter that it was actually meant to alter the post template (from the screenshot above with the [ap_....] tags), but I’m trying to figure out how to modify the HTML output of [ap_content]. So that wasn’t it.

I saw that the plugin repo has a discussion board! And that people actually use said discussion board! I searched “filters” and found “Modifying ActivityPub posts via WordPress filter? #228“. Trying to add a filter for activitypub_post got me an error. So that wasn’t it either.

(FYI, the filter name wasn’t what I was looking for, but I did find a Very Important Piece of info in the linked thread that helped me debug my code later. More on that later.)

AND THEN, the search bar at the top, I searched for apply_filters (because if there’s a new way to filter content, it for sure would be added to the code via apply_filters()):

searching for apply_filters() on Github automattic/wordpress-activitypub

And there it was: Line 623 in includes/transformer/class-post.php:

Screenshot showing activitypub_the_content being applied

After some trial and error, I fiiiiiiiinally got it to work. 😩

screenshot showing Now or Never by Audiomachine on Mastodon

Behind the Scenes!

Here is my (very unofficial) documentation for this:

PHP
  1. /**
  2.  *
  3.  * @param string $content The outputted HTML of [ap_content]
  4.  * @param WP_Post $post The post object -- see https://developer.wordpress.org/reference/classes/wp_post/ and https://developer.wordpress.org/reference/functions/get_post/ for more info
  5.  *
  6.  */
  7. add_filter('activitypub_the_content', function($content, $post){
  8.  
  9. // add code here
  10.  
  11. return $content
  12.  
  13. }, 100, 2);

First the videos…

This is just some normal non-WP specific code. So if you’re already familiar with PHP, you might already know this. But to make sure your videos show up in your fediposts:

PHP
  1. // Remove iframes from Youtube videos
  2. $content = preg_replace(
  3. "#<figure[^>]+><div[^>]+><iframe.+ src=\"https://www\.youtube\.com/embed/(.+)\?[^\"]+\".+></iframe></div></figure>#",
  4. "<p><a href=\"https://youtube.com/watch?v=\\1\">https://youtube.com/watch?v=\\1</a></p>",
  5. $content
  6. );
  7. // Remove iframes from Vimeo videos
  8. $content = preg_replace(
  9. "#<figure[^>]+><div[^>]+><iframe.+ src=\"https://player\.vimeo.com/video/(.+)\?[^\"]+\".+></iframe></div></figure>#",
  10. "<p><a href=\"https://vimeo.com/\\1\">https://vimeo.com/\\1</a></p>",
  11. $content
  12. );

You’re basically removing the iframes and surrounding <figure> and <div> tags from around the videos and reformatting the embed URLs (inside the src="" attribute) back to their web accessible URLs (so https://www.youtube.com/embed/t476sB13EOg → https://www.youtube.com/watch?v=t476sB13EOg). And then linking to themselves, which will make Mastodon (and maybe other fediservers) think “oh hey, this is a link, let’s put up a preview”, and embeds the video.

So from this:

HTML5
<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-4-3 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper"><iframe loading="lazy" title="The Prince of Peace" width="500" height="375" src="https://www.youtube.com/embed/AQZqaz10TOM?feature=oembed" frameborder="0" allow="web-share" allowfullscreen></iframe></div></figure>

To this:

HTML5
<p><a href="https://youtube.com/watch?v=AQZqaz10TOM">https://youtube.com/watch?v=AQZqaz10TOM</a></p>

And the links!

This uses some more WP-specific PHP to detect what post you’re displaying, but here’s what I have:

PHP
  1. // Linklog -- prepend external link to post
  2. $href = get_field('external_url', $post->ID);
  3. if (has_category('linklog', $post)) {
  4. $content = '<p><a href="' .$href. '">' .$href. '</a></p>' . $content;
  5. }

All together now:

PHP
  1. add_filter('activitypub_the_content', function($content, $post){
  2. // Remove iframes from Youtube videos
  3. $content = preg_replace(
  4. "#<figure[^>]+><div[^>]+><iframe.+ src=\"https://www\.youtube\.com/embed/(.+)\?[^\"]+\".+></iframe></div></figure>#",
  5. "<p><a href=\"https://youtube.com/watch?v=\\1\">https://youtube.com/watch?v=\\1</a></p>",
  6. $content
  7. );
  8. // Remove iframes from Vimeo videos
  9. $content = preg_replace(
  10. "#<figure[^>]+><div[^>]+><iframe.+ src=\"https://player\.vimeo.com/video/(.+)\?[^\"]+\".+></iframe></div></figure>#",
  11. "<p><a href=\"https://vimeo.com/\\1\">https://vimeo.com/\\1</a></p>",
  12. $content
  13. );
  14.  
  15. // Linklog -- prepend external link to post
  16. $href = get_field('external_url', $post->ID);
  17. if (has_category('linklog', $post)) {
  18. $content = '<p><a href="' .$href. '">' .$href. '</a></p>' .$content;
  19. }
  20. return $content;
  21. }, 100, 2);

Edit

Updated code to handle syntax highlighting better (it looks REEEALLY bad on Mastodon).

Edit #2

Mastodon does not apply monospace fonts to <pre>. TIL

Edit #3

Fixed syntax error in the last code snippet. Getting PHP and Javascript mixed up.