Skip to content

Instantly share code, notes, and snippets.

@obenland
Last active December 5, 2025 14:31
Show Gist options
  • Select an option

  • Save obenland/32197e7322ee1bad75f6979107378834 to your computer and use it in GitHub Desktop.

Select an option

Save obenland/32197e7322ee1bad75f6979107378834 to your computer and use it in GitHub Desktop.
ActivityPub Relay Mode - Standalone plugin to add relay functionality to WordPress ActivityPub
<?php
/**
* Plugin Name: ActivityPub Relay Mode
* Description: Adds relay mode to the ActivityPub plugin to forward public activities to all followers.
* Version: 1.0.0
* Author: Automattic
* License: GPL-2.0-or-later
* Requires Plugins: activitypub
*/
namespace Activitypub_Relay_Mode;
use Activitypub\Activity\Activity;
use Activitypub\Collection\Actors;
use Activitypub\Collection\Outbox;
use function Activitypub\is_activity_public;
use function Activitypub\is_single_user;
defined( 'ABSPATH' ) || exit;
/**
* Initialize the plugin.
*/
function init() {
// Register setting and handle option changes.
add_action( 'admin_init', __NAMESPACE__ . '\register_setting' );
add_action( 'update_option_activitypub_relay_mode', __NAMESPACE__ . '\relay_mode_changed', 10, 2 );
// Only load relay functionality if relay mode is enabled.
if ( get_option( 'activitypub_relay_mode', false ) ) {
add_action( 'activitypub_handled_create', __NAMESPACE__ . '\handle_activity', 10, 3 );
add_action( 'activitypub_handled_update', __NAMESPACE__ . '\handle_activity', 10, 3 );
add_action( 'activitypub_handled_delete', __NAMESPACE__ . '\handle_activity', 10, 3 );
add_action( 'activitypub_handled_announce', __NAMESPACE__ . '\handle_activity', 10, 3 );
add_action( 'load-settings_page_activitypub', __NAMESPACE__ . '\unhook_settings_fields', 11 );
add_filter( 'activitypub_blog_actor_type', __NAMESPACE__ . '\set_actor_type_service' );
}
}
add_action( 'plugins_loaded', __NAMESPACE__ . '\init' );
/**
* Set the blog actor type to Service when relay mode is enabled.
*
* @param string $type The actor type.
* @return string The modified actor type.
*/
function set_actor_type_service( $type ) {
return 'Service';
}
/**
* Handle incoming activity and relay if needed.
*
* @param array $activity The activity data.
* @param array $user_ids The user IDs that are recipients.
* @param bool $success Whether the activity was handled successfully.
*/
function handle_activity( $activity, $user_ids, $success ) {
// Only relay if: successfully handled, Blog actor is recipient, activity is public, and in single-user mode.
if (
! $success ||
! in_array( Actors::BLOG_USER_ID, (array) $user_ids, true ) ||
! is_activity_public( $activity ) ||
! is_single_user()
) {
return;
}
// Create Announce wrapper.
$announce = new Activity();
$announce->set_type( 'Announce' );
$announce->set_actor( Actors::BLOG_USER_ID );
$announce->set_object( $activity );
$announce->set_published( gmdate( ACTIVITYPUB_DATE_TIME_RFC3339 ) );
// Add to outbox for distribution. The outbox will generate the ID.
Outbox::add( $announce, Actors::BLOG_USER_ID );
}
/**
* Unhook settings fields when relay mode is enabled.
*
* Removes all settings sections except moderation when relay mode is active.
*/
function unhook_settings_fields() {
global $wp_settings_sections;
if ( ! isset( $wp_settings_sections['activitypub_settings'] ) ) {
return;
}
// Keep only the moderation section.
foreach ( $wp_settings_sections['activitypub_settings'] as $section_id => $section ) {
if ( 'activitypub_moderation' !== $section_id ) {
unset( $wp_settings_sections['activitypub_settings'][ $section_id ] );
}
}
}
/**
* Register the relay mode setting.
*/
function register_setting() {
\register_setting(
'activitypub_advanced',
'activitypub_relay_mode',
array(
'type' => 'integer',
'description' => 'Enable relay mode to forward public activities to all followers.',
'default' => 0,
'sanitize_callback' => 'absint',
)
);
// Add settings field to advanced section.
\add_settings_field(
'activitypub_relay_mode',
\__( 'Relay Mode', 'activitypub-relay-mode' ),
__NAMESPACE__ . '\relay_mode_field_callback',
'activitypub_settings',
'activitypub_advanced',
array( 'label_for' => 'activitypub_relay_mode' )
);
}
/**
* Render the relay mode settings field.
*/
function relay_mode_field_callback() {
$value = get_option( 'activitypub_relay_mode', 0 );
?>
<label>
<input type="checkbox" name="activitypub_relay_mode" id="activitypub_relay_mode" value="1" <?php checked( $value, 1 ); ?>>
<?php esc_html_e( 'Enable relay mode to forward public activities to all followers.', 'activitypub-relay-mode' ); ?>
</label>
<p class="description">
<?php esc_html_e( 'When enabled, this site will act as a relay, forwarding public activities from followed accounts to all followers. The blog actor will be set to "Service" type with username "relay".', 'activitypub-relay-mode' ); ?>
</p>
<?php
}
/**
* Handle relay mode option changes.
*
* When relay mode is enabled, switch to blog-only mode and set username to "relay".
* When disabled, restore previous settings.
*
* @param mixed $old_value The old option value.
* @param mixed $new_value The new option value.
*/
function relay_mode_changed( $old_value, $new_value ) {
if ( $new_value && ! $old_value ) {
// Enabling relay mode.
// Store previous username and actor mode for restoration.
update_option( 'activitypub_relay_previous_blog_identifier', get_option( 'activitypub_blog_identifier' ) );
update_option( 'activitypub_relay_previous_actor_mode', get_option( 'activitypub_actor_mode' ) );
// Set blog username to "relay".
update_option( 'activitypub_blog_identifier', 'relay' );
// Switch to blog-only mode.
update_option( 'activitypub_actor_mode', ACTIVITYPUB_BLOG_MODE );
} elseif ( ! $new_value && $old_value ) {
// Disabling relay mode - restore previous settings.
$previous_identifier = get_option( 'activitypub_relay_previous_blog_identifier' );
$previous_actor_mode = get_option( 'activitypub_relay_previous_actor_mode' );
if ( $previous_identifier ) {
update_option( 'activitypub_blog_identifier', $previous_identifier );
delete_option( 'activitypub_relay_previous_blog_identifier' );
}
if ( $previous_actor_mode ) {
update_option( 'activitypub_actor_mode', $previous_actor_mode );
delete_option( 'activitypub_relay_previous_actor_mode' );
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment