Federating My Website

## Metadata
- Author: [[alex]]
- Full Title: Federating My Website
- Category: #articles
- Document Note: ---
tags:
- #fediverse
- #wordpress
- #activitypub
Author: alex
Source: Website
Reference: https://www.alexstandiford.com/articles/blog-posts/federating-my-website/
date created: 2025-08-21 00:00
---
# Title: Federating My Website
## Table of Contents:
- [🚀 Overview](#-overview)
- [🔁 Auto-Boosting Strategy](#-auto-boosting-strategy)
- [💬 Replies as “Chat” Format](#-replies-as-chat-format)
- [🧩 Code: Exclude Chat Posts From Queries](#-code-exclude-chat-posts-from-queries)
- [✍️ Editing via Mastodon App](#-editing-via-mastodon-app)
- [🧵 Excerpts for Longform in ActivityPub](#-excerpts-for-longform-in-activitypub)
- [🧰 Code: Build ActivityPub Content From Excerpt + Tags](#-code-build-activitypub-content-from-excerpt--tags)
- [⚙️ Quality-of-Life Automation (Formats, Mobile Replies)](#️-quality-of-life-automation-formats-mobile-replies)
- [🔗 Code: Detect Format on Save](#-code-detect-format-on-save)
- [📱 Mobile Reply Workaround](#-mobile-reply-workaround)
- [🧪 Code: Transform First-Link-to-Reply Block](#-code-transform-first-link-to-reply-block)
- [🖼️ Improved Open Graph Embeds](#-improved-open-graph-embeds)
- [🧠 Notes](#-notes)
- [📖 References](#-references)
- [🗣️ Paraphrase](#-paraphrase)
- [📰 Newsletter](#-newsletter)
- [✅ Action Items (Advocacy + OSPO Alignment Prompt)](#-action-items-advocacy--ospo-alignment-prompt)
- [🧪 Hypotheses](#-hypotheses)
- [📊 Methodology](#-methodology)
- [❓ Questions](#-questions)
- [🔮 Future Research](#-future-research)
- [💡 Implications](#-implications)
- [🧠 Learning Styles Assessment](#-learning-styles-assessment)
- [📊 Accessibility Assessment](#-accessibility-assessment)
- [🌍 DEIB Assessment](#-deib-assessment)
- [✅ Logical validity](#-logical-validity)
- [🔍 DARVO Analysis](#-darvo-analysis)
- [🔍 SEO Evaluation](#-seo-evaluation)
- [🎯 Conclusion](#-conclusion)
### References:
- ActivityPub for WordPress
- Mastodon app integration with WordPress
- WordPress post formats (standard, aside, chat)
- Open Graph and Twitter Card best practices
## 📝 Summary:
Alex documents the journey of federating a personal WordPress site with the Fediverse using the ActivityPub plugin. Key moves include temporarily auto-boosting posts from an existing Mastodon account to grow followers on the site’s federated identity, using “chat” post format for replies to avoid cluttering archives, generating excerpt-based ActivityPub content for longform posts, and quality-of-life automations to set post formats and transform mobile “first link” replies into proper ActivityPub reply blocks. A major enhancement involves custom Open Graph metadata to produce richer embeds for videos and microposts across platforms like Twitter and iMessage. The post includes several pragmatic PHP snippets with a candid caveat that the code is experimental.
## 📖 Main Content:
- Motivation: Centralize social presence on the author’s own domain, making the website the canonical social identity.
- Problem: Cross-posting is time-consuming and fragments discussion off-site; early federation lacks followers.
- Solution highlights:
- Auto-boost posts from the legacy Mastodon account to point people toward the federated site identity.
- Use WordPress post formats to separate replies (chat) and microposts (aside) from longform entries.
- Enhance ActivityPub output to share excerpts and hashtags for longform content.
- Automate format detection on save and add a mobile-friendly workaround for creating ActivityPub replies.
- Improve Open Graph metadata so shared links embed richer previews, including video dimensions.
- Practical code: Multiple filters and actions for WP_Query exclusion, content shaping for ActivityPub, save_post logic for auto-formatting, and Open Graph overrides that detect video blocks.
### 🗒️ Notes
- 🚀 Overview
- “There’s officially nothing stopping me from federating my blog” (0%)—a shift from headless to a more integrated WP stack.
- Strong emphasis on the website as the canonical social account.
- 🔁 Auto-Boosting Strategy (≈7–14%)
- Pain of cross-posting; followers built off-site.
- Temporary “proxy” pattern: existing Mastodon account auto-boosts site posts to funnel followers to the site’s federated identity.
- Recognizes it’s not perfect; migration of followers may require long-term efforts.
- 💬 Replies as “Chat” Format (≈21–29%)
- ActivityPub allows replying from the WP editor.
- Replies set to “chat” format; filtered out from main queries to keep archives clean while preserving deep links.
- 🧩 Code: Exclude Chat Posts From Queries (≈29–36%)
- pre_get_posts hook adds tax_query to exclude post-format-chat globally on the front end.
- ✍️ Editing via Mastodon App (≈36–43%)
- New ActivityPub support for Mastodon app posting.
- Early experimentation: prefers WP app for now; plans to filter tags and post format upon Mastodon-originated posts.
- 🧵 Excerpts for Longform in ActivityPub (≈43–50%)
- Longform posts should publish ActivityPub content as excerpt + hashtags + link.
- Future idea: custom meta for social copy, reused for Open Graph enhancements.
- 🧰 Code: Build ActivityPub Content From Excerpt + Tags (≈50–57%)
- Filter activitypub_the_content to assemble excerpt + CamelCase hashtags + permalink for standard posts.
- ⚙️ Quality-of-Life Automation (Formats, Mobile Replies) (≈57–64%)
- Detect microposts (no title) → aside; replies → chat.
- Reduces friction for mobile publishing.
- 🔗 Code: Detect Format on Save (≈64–71%)
- save_post hook: if has activitypub/reply block → chat; if no title and no reply block → aside.
- 📱 Mobile Reply Workaround (≈71–79%)
- Mobile app can’t add custom blocks; workaround detects first paragraph link and transforms it into an activitypub/reply block at publish time.
- 🧪 Code: Transform First-Link-to-Reply Block (≈79–86%)
- wp_insert_post_data filter parses blocks, finds first-link href, and replaces initial block with an ActivityPub reply block on first publish.
- 🖼️ Improved Open Graph Embeds (≈86–100%)
- Detects video blocks and sizes to emit richer OG/Twitter Card tags; improves embeds in Twitter/iMessage.
- Microposts embed their content directly when shared.
- Custom PHP scans video HTML, attempts dimensions (fallbacks, potential for FFmpeg), then overrides Yoast’s OG/Twitter tags.
## 📖 References:
- ActivityPub for WordPress: https://wordpress.org/plugins/activitypub/
- Mastodon mobile app: https://joinmastodon.org/apps
- WordPress Post Formats: https://wordpress.org/documentation/article/post-formats/
- Open Graph protocol: https://ogp.me/
- Twitter Cards: https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/abouts-cards
## 🗣️ Paraphrase:
1) Original idea (≈0–7%): The site rebuild removed blockers to federation; code shared is experimental and not production-grade.
Paraphrase: After reworking the site back to a traditional WordPress setup, I realized there’s nothing preventing me from federating my blog now. Heads up: the code I’m sharing here is quick-and-dirty, written while I explore and learn—don’t treat it as a PHP tutorial.
2) Auto-boost proxy pattern (≈7–21%): Building followers off-site isn’t ideal; auto-boosting from a legacy Mastodon account promotes the new federated identity.
Paraphrase: Posting everywhere manually scatters conversations and followers, which I want to bring under my own domain. As a bridge, I’m having my original Mastodon account automatically boost posts from my site, effectively turning it into a relay that encourages people to follow the site’s account directly.
## 📰 Newsletter:
Federating WordPress: from islands to archipelago! This week I explored turning your WordPress site into your canonical social identity using the ActivityPub plugin. Highlights:
- Learn dev: We walk through practical PHP hooks—pre_get_posts, save_post, wp_insert_post_data—and block parsing to shape formats and replies.
- Fresh updates: ActivityPub now supports posting via the Mastodon app. It auto-tags, but you’ll likely want filters to tune formats and hashtags.
- Community wins: Using a “proxy” account to auto-boost your site’s posts can gently migrate your audience toward your federated domain.
- New plugins/
- Summary: This post explores how I've added enhancements to my site to better-integrate with the Fediverse, and also how I'm using the Mastodon app to publish content direct to my WP site (!)
- URL: https://www.alexstandiford.com/articles/blog-posts/federating-my-website/

Well, that was a bit of a journey.
If you’ve been following along my site for the last few years, you know that [I’ve been working to turn it into a personal hub](https://www.alexstandiford.com/articles/speaking/presentations/reclaiming-authenticity-building-your-personal-hub-in-a-post-twitter-era/). After finally getting around to [re-building the site on a non-headless](https://www.alexstandiford.com/articles/blog-posts/i-re-built-my-website-using-the-block-editor-and-ollie/) way, I’ve come to realize that there’s officially nothing stopping me from federating my blog, and nudging myself a little closer to that goal.
**I feel obligated to mention the code you’re about to see is not “good”. it’s very much copypasta as I experiment with everything and learn. if you’re coming to this post to learn to write PHP, I *beg* you, look at literally any other [tutorial](https://www.alexstandiford.com/category/essays/tutorials/) of mine.**
#### Auto-Boosting (For Now)
The original version of this site relied on cross-posting content, both on my X and Fosstodon accounts. I had a few different solutions to make this seamless, but eventually gave up on maintaining them in-favor of just manually posting in all 3 places, my blog, X, and Mastodon.
The problem with this approach is that I’m building those followers, and having those discussions off of my site. Instead of actually keeping some, or all of that centralized on my site. Not to mention if I make an edit to the post I have to go back and modify it in all places, post lengths vary, etc.
Unsurprisingly enough – cross-posting is a pain in the ass. Which is why so few people do this, but I still insist it’s worth it. You get in a rhythm and it only adds about a minute or so to the time it takes to publish something. Unless you’re sharing a video, and in that case it’s hit-or-miss.
Anyway, the *real* problem with this is actually that I’m building my followers on these other platforms. This was fine until I decided I wanted to actually start getting people to *follow my blog directly on the fediverse* instead of simply following my social accounts. I want *my website* to become my social account, and the canonical source of everything.
The problem? Nobody’s followin’ me, because I just connected to the fediverse.
[In a recent interview on WPBuilds](https://wpbuilds.com/2024/09/05/388-bringing-back-conversations-matthias-pfefferle-on-activitypub-and-wordpress/), [@
[email protected]](https://mastodon.social/@pfefferle) mentioned that he got around this limitation by [setting up something to automatically boost all posts from his site on his original Mastodon account.](https://github.com/pfefferle/wordpress-crossbooster) This essentially turns your original account into a “proxy” of sorts, shares the canonical post, and invites people to follow the direct account instead of the proxy.
It’s not perfect, but this entire process is going to be a time-intensive process, and require slow movements over a long period of time. Someday, maybe we’ll have a nice migration solution to migrate users from a Mastodon account to a personal WordPress account, but for now, the next best thing is auto-boosting.
#### Replies Use “Chat” Post Format
One nifty feature of the ActivityPub plugin is that you can actually reply to people directly inside of the post editor. There’s a handy little bookmarklet that you can use so you can reply as your personal site. This then creates the post through your site, and allows people to reply to it, etc. This allows you to stop using your Mastodon account and instead just use WordPress for everything.
I, however, do *not* want all of these replies to clog up my post archive, so I am making a point to set their post format to “chat”, and then filtering out that post format from all queries on my site. Deep-linking will still work, though.
This is pretty exciting, because more of the conversation I have on social will be added to my site, including replies and other conversations. This better-centralizes a lot of the conversations I’m having with people on the internet, and gives me a canonical source to link without putting someone on a platform in the process. Just one step closer!
```
// Hook into pre_get_posts to modify WP_Query
function exclude_chat_post_format( $query ) {
// Check if it's the main query and not in the admin area
if ( ! is_admin()) {
// Set the tax_query to exclude posts with post format "chat"
$tax_query = array(
array(
'taxonomy' => 'post_format',
'field' => 'slug',
'terms' => array( 'post-format-chat' ), // The slug for the chat post format
'operator' => 'NOT IN',
),
);
// If there's an existing tax query, merge it
if ( isset( $query->tax_query->queries ) && is_array( $query->tax_query->queries ) ) {
$query->set( 'tax_query', array_merge( $query->tax_query->queries, $tax_query ) );
} else {
$query->set( 'tax_query', $tax_query );
}
}
}
add_action( 'pre_get_posts', 'exclude_chat_post_format' );
```
#### Better Editing (Maybe)
As I’m writing this, [ActivityPub for WordPress](https://www.alexstandiford.com/micro/26397) just added support for the Mastodon app, so you can actually post directly from the Mastodon app.
This literally *just* happened so I’m going to have to tinker with it to see how it works. Looks like it auto-generates tags. Hopefully I can filter into this and detect when it’s being published via Mastodon so I can properly format the hashtags, and also set the post format automatically. 🤞
For now I’m going to keep using the native WordPress app, but I’ll definitely be keeping an eye on this. I think if you’re just getting started with messing with all of this, it might actually be a better choice. Hard to say.
#### Blog Posts Use Excerpts
I don’t want my blog posts to use excerpts, so I’m *hoping* that this filter will actually force my activitypub feed to show the excerpt for longform content, instead. I might end up adding a custom meta field that has the social content since sometimes that needs to be different than the excerpt. Someday, maybe.
What would be *really* cool about that is I could then use all of that formatting for my Open Graph enhancements (more on that in a sec!)
```
add_filter( 'activitypub_the_content', function( $content, $post_id ) {
$post_format = get_post_format( $post_id );
// Only apply to "standard" post format
if ( $post_format === false || $post_format === 'standard' ) {
// Get the post excerpt
$excerpt = get_the_excerpt( $post_id );
// Get the post tags and format them as CamelCase hashtags
$tags = get_the_tags( $post_id );
$hashtags = '';
if ( $tags ) {
foreach ( $tags as $tag ) {
$camel_case_tag = ucwords( str_replace( ' ', '', strtolower( $tag->name ) ) ); // Convert tag to CamelCase
$hashtags .= ' #' . $camel_case_tag;
}
}
// Get the post permalink
$link = get_permalink( $post_id );
// Combine the excerpt, hashtags, and link into the new content
$content = $excerpt . $hashtags . ' ' . $link;
}
return $content;
}, 10, 2 );
```
#### Quality Of Life Stuff
It’s safe to say I have the process of publishing through WordPress pretty much down at this point, but now that I can reply to posts with the WordPress editor, I wanted to make it *a lot* easier to do a few things. Namely, **Replying to posts in the app**, and **removing the need to remember to set the post format.**
I’ve been meaning to do the latter for a *long* time. It wasn’t too difficult of a thing to do – just requires that you filter the content before it publishes, and if it doesn’t have a title, assume it’s a micropost and use the `aside` format. If it’s a reply to a message, use `chat` (that way I can isolate them in my queries and filter them in most cases.)
I was hesitant to do this because I wasn’t sure how to detect it, but it became quite clear pretty fast that the key difference between a micropost and a regular post is simple – microposts don’t have a title! So we can leverage that, as well as the custom activity pub “reply to” block to detect the post format when we save. This saves a step when publishing, which is really important when using the mobile app.
```
add_action( 'save_post', 'detect_post_format_based_on_content', 10, 3 );
function detect_post_format_based_on_content( $post_ID, $post, $update ) {
// Avoid auto-drafts or revisions
if ( $post->post_status !== 'auto-draft' && !wp_is_post_revision( $post_ID ) ) {
// Check if the post has the activitypub/reply block
if ( has_block( 'activitypub/reply', $post->post_content ) ) {
// Set post format to 'chat' if it contains the reply block
set_post_format( $post_ID, 'chat' );
}
// If no title and no reply block, set post format to 'aside'
elseif ( empty( $post->post_title ) && !has_block( 'activitypub/reply', $post->post_content ) ) {
set_post_format( $post_ID, 'aside' );
}
}
}
```
As for **replying to posts**, this was a little tricker. The ActivityPub plugin handles replies by putting a special block at the top of the post that links to the content that the post is replying to. The app then detects this block is in the content, and does the magical ActivityPub things.
Which is all fine and dancy on the *desktop* experience, but guess what? I’m not replying to social posts on the desktop most of the time. I want to do it on mobile. And you know what the mobile app *can’t* do? Custom blocks.
But fear not, for I have become somewhat of an expert at dealing with the WordPress app’s limitations, and have found a workaround – simply detecting if the first block contains a link. If it does, the system transforms that link into the activitypub block just before saving.
*Theoretically*, you’d be able to use the Mastodon plugin I mentioned above, but I’m still experimenting with that. I don’t love how it formats the content, and the way it handles tags is troublesome. Could probably be filtered out, but I decided it would be best if I didn’t change *too much* of my process *too fast*.
Here it is in action:
And here’s the snippet that makes it work:
```
//Automatically set activitypub replies.
add_filter( 'wp_insert_post_data', 'transform_url_to_activitypub_reply_on_first_publish', 10, 2 );
function transform_url_to_activitypub_reply_on_first_publish( $data, $postarr ) {
// Only run if the post status is transitioning to 'publish'
if ( isset( $data['post_status'] ) && $data['post_status'] === 'publish' ) {
// Check the current post status, we only want to run this on first-time publish
if(isset($postarr['ID'])) {
$current_post_status = get_post_status($postarr['ID']);
} else{
$current_post_status = 'draft';
}
if ( $current_post_status !== 'publish' ) {
// Parse the blocks in the post content
$blocks = parse_blocks( $data['post_content'] );
// Check if the first block is a paragraph
if ( isset( $blocks[0] ) && $blocks[0]['blockName'] === 'core/paragraph' ) {
// Load the block's inner HTML into a DOMDocument
$dom = new DOMDocument();
// Suppress warnings when loading HTML with special characters
@$dom->loadHTML( $blocks[0]['innerHTML'], LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD );
// Find the <a> tag and extract its href attribute
$anchor = $dom->getElementsByTagName('a')->item(0);
if ( $anchor && !empty($anchor->getAttribute('href')) ) {
$url = esc_url( $anchor->getAttribute('href') );
// Create the ActivityPub reply block with the extracted URL
$activitypub_reply_block = '<!-- wp:activitypub/reply {"url":"' . $url . '"} /-->';
// Rebuild the full block's raw representation
$original_block = serialize_block( $blocks[0] );
// Replace the entire block with the new ActivityPub reply block
$data['post_content'] = str_replace( $original_block, $activitypub_reply_block, $data['post_content'] );
}
}
}
}
return $data;
}
```
#### Improved OpenGraph
This has me *so damn excited* I can’t even stand it. Since I am able to detect the type of post, I can actually provide much better opengraph data, and if the platform supports it, it will embed the content a lot nicer.
For example, if a post includes a video, it changes the OpenGraph data so that the video can be embedded, both on Twitter and inside iMessage. Conversely, if you share a link to one of my microposts, it will actually embed the micropost content directly in the message.
This is great because an unexpected side-effect of posting…basically everything through your site is that, well, you find yourself sharing links to your own content *a lot*. I never realized how often I would share a Tweet, or a Facebook post that I wrote to a family member directly, or something along those lines. Well, I was doing that with my own site now, and really wasn’t happy with the quality of the content embedding. So I went down a bit of a rabbit hole, and boy oh boy is it awesome. Seriously, try it. [Here’s a video of me flying a kite. It’s a good use for it.](https://www.alexstandiford.com/micro/20557)
Here’s the code that I’m using to customize. This is all kinda custom to my needs, but it can give you an idea on some of the changes I’ve made.
```
<?php
/**
* Plugin Name: Video Block Open Graph & Twitter Card Tweaks
* Description: Overwrite Yoast's Open Graph and Twitter Card meta tags for posts with video blocks or 'video' post format.
*/
/**
* Detects video block data in post content, including URL, width, and height.
* If width and height are not available in the block, it tries to fetch them from the video metadata.
*
* @param string $content The post content.
* @return array|false The video URL and dimensions if found, false otherwise.
*/
function detect_video_block_data( $content ) {
if(!$content){
return false;
}
// Scrape video block HTML
$doc = new DOMDocument();
@$doc->loadHTML($content);
$videoElements = $doc->getElementsByTagName('video');
if ($videoElements->length > 0) {
$video = $videoElements->item(0);
$url = $video->getAttribute('src');
// Check if width and height are set in the HTML attributes
$width = $video->hasAttribute('width') ? $video->getAttribute('width') : null;
$height = $video->hasAttribute('height') ? $video->getAttribute('height') : null;
// If width or height are not available, attempt to fetch metadata from the video file
if (empty($width) || empty($height)) {
$video_metadata = get_video_metadata( $url );
if ( $video_metadata ) {
$width = $video_metadata['width'];
$height = $video_metadata['height'];
}
}
return [
'url' => $url,
'width' => $width ?: '640', // Default width if not found
'height' => $height ?: '360' // Default height if not found
];
}
return false;
}
/**
* Attempts to fetch video metadata such as width and height from a video URL.
* This is a basic implementation, more advanced methods (FFmpeg, external services, etc.) can be used if needed.
*
* @param string $video_url The URL of the video.
* @return array|false Array with width and height, or false if it cannot be determined.
*/
function get_video_metadata( $video_url ) {
// This is a simplified way to get metadata. It would be more complex in a real-world scenario.
// You might need to use a video processing library like FFmpeg for more detailed information.
// For this example, let's assume we can do a quick HTTP HEAD request to check metadata.
// However, WordPress itself doesn't have a built-in way to extract video metadata, so we'll use defaults.
$headers = wp_remote_head( $video_url );
if ( is_wp_error( $headers ) ) {
return false;
}
// Example of how we might pull information from headers (many video hosts won't provide this)
// You might need to implement an external API or FFmpeg to fully extract metadata.
// As a fallback, return default values (in reality, more advanced parsing would happen here)
return [
'width' => '640', // Example fallback width
'height' => '360' // Example fallback height
];
}
// Filter to replace Yoast's Open Graph type if a video is detected or the post format is 'video'.
add_filter( 'wpseo_opengraph_type', function( $type ) {
if ( is_singular() ) {
global $post;
$post_format = get_post_format( $post );
$has_video_block = detect_video_block_data( $post->post_content );
if ( 'video' === $post_format || $has_video_block ) {
return 'video.other'; // Set Open Graph type to video
}
}
return $type;
});
// Filter to overwrite Yoast's Open Graph title based on post type or format.
add_filter( 'wpseo_opengraph_title', function( $title ) {
global $post;
// Check if the post contains a video block
$has_video_block = detect_video_block_data( $post->post_content );
// If the post is a video, use the post content as the title with the prefix.
if ( $has_video_block ) {
$post_content = wp_strip_all_tags( $post->post_content ); // Remove any HTML tags
$custom_title = 'Alex Standiford: ' . $post_content;
return $custom_title;
}
// If the post format is 'aside', set a custom title.
if ( get_post_format( $post ) === 'aside' ) {
return 'Alex Standiford';
}
// Return the default title for other cases.
return $title;
});
// Filter to overwrite Yoast's Open Graph image with video meta tags when a video is detected.
add_filter( 'wpseo_opengraph_image', function( $image ) {
global $post;
$has_video_block = detect_video_block_data( $post->post_content );
if ( $has_video_block ) {
// Return false to remove the image as we're replacing it with video.
return false;
}
return $image;
});
// Filter to add Open Graph video properties.
add_action( 'wpseo_opengraph', function() {
global $post;
$post_format = get_post_format( $post );
$has_video_block = detect_video_block_data( $post->post_content );
if ( 'video' === $post_format || $has_video_block ) {
// Get video data from content
$video_data = detect_video_block_data( $post->post_content );
if ( $video_data ) {
echo '<meta property="og:video" content="' . esc_url( $video_data['url'] ) . '" />';
if ( !empty( $video_data['width'] ) && !empty( $video_data['height'] ) ) {
echo '<meta property="og:video:width" content="' . esc_attr( $video_data['width'] ) . '" />';
echo '<meta property="og:video:height" content="' . esc_attr( $video_data['height'] ) . '" />';
}
echo '<meta property="og:video:type" content="video/mp4" />'; // Adjust type as needed
}
}
}, 20 );
// Filter to overwrite Twitter card type with 'player' if video is detected.
add_filter( 'wpseo_twitter_card_type', function( $type ) {
global $post;
$post_format = get_post_format( $post );
$has_video_block = detect_video_block_data( $post->post_content );
if ( 'video' === $post_format || $has_video_block ) {
return 'player'; // Set Twitter Card to player
}
return $type;
});
// Add Twitter Player card meta tags when a video is detected.
add_action( 'wpseo_twitter', function() {
global $post;
$post_format = get_post_format( $post );
$has_video_block = detect_video_block_data( $post->post_content );
if ( 'video' === $post_format || $has_video_block ) {
// Get video data from content
$video_data = detect_video_block_data( $post->post_content );
if ( $video_data ) {
echo '<meta name="twitter:player" content="' . esc_url( $video_data['url'] ) . '" />';
echo '<meta name="twitter:text:player_width" content="' . esc_attr( $video_data['width'] ) . '" />';
echo '<meta name="twitter:text:player_height" content="' . esc_attr( $video_data['height'] ) . '" />';
echo '<meta name="twitter:player:width" content="' . esc_attr( $video_data['width'] ) . '" />';
echo '<meta name="twitter:player:height" content="' . esc_attr( $video_data['height'] ) . '" />';
echo '<meta name="twitter:player:stream" content="' . esc_url( $video_data['url'] ) . '" />';
echo '<meta name="twitter:player:stream:content_type" content="video/mp4" />'; // Adjust content type if needed
}
}
});
```, Federating My Website, alex, 01j8p1kxgrgyhaa9fzknc9nnnf.