Skip to content

Federate podcast episodes from both Jetpack podcast features - #3575

Open
pfefferle wants to merge 7 commits into
trunkfrom
add/jetpack-podcast-attachment
Open

Federate podcast episodes from both Jetpack podcast features#3575
pfefferle wants to merge 7 commits into
trunkfrom
add/jetpack-podcast-attachment

Conversation

@pfefferle

@pfefferle pfefferle commented Jul 23, 2026

Copy link
Copy Markdown
Member

Proposed changes:

Jetpack has two podcast features, built at the same time by different people, and an episode can come from either one:

  • Posts to Podcast generates audio for a post and writes a jetpack/podcast-episode block carrying the audio URL, mime type and cover art.
  • Jetpack Podcast treats every post in the configured podcast category as an episode, with the audio arriving as an ordinary WordPress enclosure.

The first version of this PR only handled the block, and I had it labelled as Jetpack Podcast, which is the other one. Thanks to Rob for catching that. It now covers both.

Neither surface reaches ActivityPub on its own. Episode audio is usually hosted off-site, so it has no attachment ID, and Transformer\Base::filter_unique_attachments() drops every media entry without one, so that audio never survives to the activitypub_attachments filter. The integration resolves the episode itself and either adds the audio, or replaces the artwork when the media library already contributed the file.

  • Hooks activitypub_attachments rather than adding a transformer subclass. A subclass is winner-take-all, so a site running Podlove or Seriously Simple Podcasting alongside a Jetpack podcast would get one behavior instead of both.
  • Cover art follows the podcast feed's own order: the episode's own artwork, then the post's featured image, then the show image, so a federated episode carries the artwork subscribers already see.
  • Deduplicates against the existing attachment with normalize_url(), so a site that moved to HTTPS after publishing does not federate the same file twice.
  • Respects the site's attachment maximum, which the transformer applies just before this filter runs.
  • Omits mediaType when the block has no mime type, rather than sending an empty string that stops receivers classifying the attachment at all.

I also pulled the max-attachments resolution out of Transformer\Post into Activitypub\get_max_attachments(), since the integration needs the same value and I did not want a second copy of the meta, option and filter precedence.

Known gap

Podlove_Podcast_Publisher and Seriously_Simple_Podcasting override get_attachment() and never apply activitypub_attachments, so on a site running either of those this callback does not fire. I left that alone here, but it means the three podcast integrations sit at three different levels for one problem.

Worth a follow-up

filter_unique_attachments() drops media without an id, but Post::get_enclosures() deliberately builds complete entries for enclosures that are not in the media library, and Base::transform_attachment() opens with a passthrough for id-less entries that is unreachable today. Fixing that one place would let every site with an external enclosure federate it, and would remove most of the workaround here. It changes shared behavior for every post that merely links an audio file though, and there is no enclosure test coverage yet, so I think it should be its own PR.

Other information:

  • Have you written new tests for your changes, if applicable?

Testing instructions:

Posts to Podcast:

  • Generate an episode for a post so it gets a jetpack/podcast-episode block.
  • Fetch the post's ActivityPub representation and confirm attachment holds the audio with type: Audio, the right mediaType, and the episode cover art as icon.
  • Confirm the audio is not listed twice when WordPress' core enclosure meta points at the same file, including when one of the two uses http and the other https.

Jetpack Podcast:

  • Configure a podcast category and a show image, then publish a post in that category with an audio enclosure hosted off-site.
  • Confirm the episode federates with that audio attached and the show image as icon.
  • Repeat with the audio uploaded to the media library, and confirm the show image wins over the site icon.
  • Confirm a post outside the podcast category that happens to have an enclosure is not treated as an episode.

Changelog entry

Included in the branch (.github/changelog/add-podcast-episode-attachments).

Copilot AI review requested due to automatic review settings July 23, 2026 10:52
@pfefferle pfefferle self-assigned this Jul 23, 2026
@pfefferle
pfefferle requested a review from a team July 23, 2026 10:52
@github-actions github-actions Bot added [Focus] Compatibility Ensuring the plugin plays well with other plugins [Tests] Includes Tests labels Jul 23, 2026
@pfefferle
pfefferle requested a review from jeherve July 23, 2026 10:53

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support in the Jetpack integration to federate Jetpack “podcast episode” posts by emitting an ActivityPub attachment for the episode’s audio enclosure (and cover art), similar to existing podcast integrations.

Changes:

  • Hook activitypub_attachments in Activitypub\Integration\Jetpack and derive audio/cover-art data from Jetpack’s Episode_Block_Tags::get_block_attrs(), enriching existing core-enclosure attachments when present.
  • Add PHPUnit coverage for the new attachment behavior, including a small mock of Jetpack’s Episode_Block_Tags.
  • Add a changelog entry for the new federation capability.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
integration/class-jetpack.php Adds activitypub_attachments filter handler to emit/enrich podcast episode audio + cover art attachments.
tests/phpunit/tests/integration/class-test-jetpack.php Adds integration tests for the new Jetpack podcast attachment behavior and mock setup.
tests/phpunit/data/mocks/class-episode-block-tags.php Introduces a minimal mock for Episode_Block_Tags::get_block_attrs() used by tests.
.github/changelog/add-jetpack-podcast-attachment Documents the new Jetpack podcast episode federation behavior.

Comment thread integration/class-jetpack.php Outdated
Comment thread tests/phpunit/tests/integration/class-test-jetpack.php
Jetpack's podcast-episode block stores the audio URL, mime type and cover art
as block attributes, read via Episode_Block_Tags::get_block_attrs(). Hook the
activitypub_attachments filter from the Jetpack integration to add the episode
audio (and cover art) as an ActivityPub attachment, read directly from the
block rather than relying on WordPress core's asynchronous enclosure meta. When
the enclosure path already added the same audio, enrich it with the cover art
instead of duplicating.

Claude-Session: https://claude.ai/code/session_01Eam1mCnfuFmXKSAtYFxaua
@pfefferle
pfefferle force-pushed the add/jetpack-podcast-attachment branch from fdfccfb to 61bd75b Compare July 23, 2026 10:58
- esc_url_raw() can drop an unsafe media URL to an empty string; return early
  so no attachment is added without a url. Adds a regression test.
- Remove the activitypub_attachments filter in the Jetpack test tear_down so it
  cannot leak into later tests.

Claude-Session: https://claude.ai/code/session_01Eam1mCnfuFmXKSAtYFxaua
@github-actions github-actions Bot added the Docs label Jul 23, 2026
@pfefferle pfefferle changed the title Federate Jetpack podcast episodes Federate podcast episodes from both Jetpack podcast features Jul 24, 2026
@pfefferle
pfefferle requested a review from robertbpugh July 24, 2026 10:18
@robertbpugh
robertbpugh requested a review from arcangelini July 24, 2026 11:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Docs Enhancement New feature or request [Focus] Compatibility Ensuring the plugin plays well with other plugins [Status] Needs review [Tests] Includes Tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants