Skip to content

Micropub, Crossposting to Twitter, and Enabling “Tweetstorms”

I’ve previously talked about how I crosspost from this blog to my Mastodon account without the need for a third-party service, and how I leverage WordPress’s hook system to even enable toot threading.

In this post, I’m going to really quickly explain my (extremely similar) Twitter setup. (Note: I don’t actually syndicate this blog’s posts to Twitter, but I do use this very setup on another site of mine.)

I liked the idea of a dead-simple Twitter plugin, so I forked my Mastodon plugin and tweaked a few things here and there. Once I’ve installed it, and created a developer account, generated the necessary keys, and let WordPress know about them, things look, well, very familiar. In fact, crossposting should now just work.

Now, to enable this when posting through Micropub rather than WordPress’s admin interface! Again, since posting through Micropub means no WordPress interface, and thus no “meta box” and no checkbox, and no way for WordPress to know if I wanted to crosspost a certain article or not, I’m going to have to use … syndication targets (which were invented precisely for this reason).

First, I’m updating my Micropub config:

add_filter( 'micropub_syndicate-to', function( $syndicate_to, $user_id ) {
  return array(
    array(
      'uid'  => 'https://twitter.com/ochtendgrijs',
      'name' => 'Twitter',
    ),
    array(
      'uid'  => 'https://geekcompass.com/@jan',
      'name' => 'Mastodon',
    ),
  );
}, 10, 2 );

That should inform my Micropub client of the various endpoints my CMS supports.

Then, the callback. As always, there’s quite a few ways to get what we want. What this snippet does, is set the custom field that tells Share on Twitter if it should crosspost, and then simply re-trigger the hook used by that same plugin.

add_action( 'micropub_syndication', function( $post_id, $synd_requested ) {
  $post      = get_post( $post_id );
  $syndicate = false;

  if ( in_array( 'https://twitter.com/ochtendgrijs', $synd_requested, true ) ) {
    // Update sharing setting.
    update_post_meta( $post_id, '_share_on_twitter', '1' );

    $syndicate = true;
  }

  if ( in_array( 'https://geekcompass.com/@jan', $synd_requested, true ) ) {
    // Update sharing setting.
    update_post_meta( $post_id, '_share_on_mastodon', '1' );

    $syndicate = true;
  }

  if ( $syndicate && 'publish' === $post->post_status ) {
    // Re-run the `transition_post_status` hook, in order to trigger
    // syndication.
    wp_transition_post_status( 'publish', 'publish', $post );
  }
}, 10, 2 );

Note: As the callback above is run after the transition_post_status hook, which is where the actual crossposting would’ve happened, we have to trigger it once more (for published posts only).

And that’s literally all it takes to get syndication working in combination with Micropub! (Again, if you’ve seen my “Mastodon” implementation, all of this should be really familiar.)

Optional: Enabling Threaded Tweets

And, finally, the “Tweetstorm” bit, which is fairly ugly, mainly because I’m using get_page_by_path() instead of the somehow less robust—in my case—url_to_postid(). Also, part of this code references the specific custom post type I’m using for short-form content, and would not simply work elsewhere.

Oh, and lastly, this would require you filter the actual statuses, too, so that they contain actual notes and not just a title and permalink.

add_filter( 'share_on_twitter_tweet_args', function( $args ) {
  $status = $args['status'];
  $post   = null;

  $pattern = '#<a(?:.+?)href="' . home_url( '/notes/' ) . '(.+?)"(?:.*?)>(?:.+?)</a>#'; // `notes`, without `front`, is what's definied as my note archive's permalink slug.

  if ( preg_match( $pattern, $status, $matches ) ) {
    // Status contains a link to an earlier note. Try to fetch it.
    $post = get_page_by_path( rtrim( $matches[1], '/' ), OBJECT, array( 'iwcpt_note' ) ); // `iwcpt_note` is my "note" CPT.
  } else {
    // Same thing, but for articles.
    $pattern = '#<a(?:.+?)href="' . home_url( '/articles/' ) . '(.+?)"(?:.*?)>(?:.+?)</a>#'; // The exact argument of `home_url()` would depend on your permalink `front`.

    if ( preg_match( $pattern, $status, $matches ) ) {
      // Status contains a link to an earlier article. Try to fetch it.
      $post = get_page_by_path( rtrim( $matches[1], '/' ), OBJECT, array( 'post' ) );
    }
  }

  if ( $post ) {
    $tweet_id = basename( get_post_meta( $post->ID, '_share_on_twitter_url', true ) );

    if ( ! empty( $tweet_id ) ) {
      $args['in_reply_to_status_id'] = $tweet_id ;

      // Twitter demands replies contain the username of the person replying to.
      $status = get_option( 'share_on_twitter_settings', array( 'twitter_username' => '' ) )['twitter_username'] . $status;
    }
  } elseif ( isset( $matches[1] ) ) {
    error_log( "Could not convert URL to post ID for the page with slug {$matches[1]}." );
  }

  $args['status'] = trim( wp_strip_all_tags( $status ) );

  return $args;
} );

Note: Still using get_page_by_path() rather than url_to_postid(), as that’s what works for me and my hacky WordPress install.

Replies

  1. Tim Chambers Tim Chambers on

    … reblogged this!