Skip to content

Commit

Permalink
Add Reblogging and Announce support for ActivityPub (#168)
Browse files Browse the repository at this point in the history
* Add Announce support for ActivityPub

* add todo

* Fix linting

* Add settings that allow to disable reblogging

* Make reblog more generic
  • Loading branch information
akirk committed Mar 23, 2023
1 parent d117149 commit 2131d00
Show file tree
Hide file tree
Showing 7 changed files with 303 additions and 11 deletions.
138 changes: 137 additions & 1 deletion feed-parsers/class-feed-parser-activitypub.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

namespace Friends;

use PO;
use WP_Error;

/**
Expand All @@ -35,6 +34,7 @@ public function __construct( Feed $friends_feed ) {
$this->friends_feed = $friends_feed;

\add_action( 'init', array( $this, 'register_post_meta' ) );
\add_action( 'admin_menu', array( $this, 'admin_menu' ), 20 );
\add_filter( 'feed_item_allow_set_metadata', array( $this, 'feed_item_allow_set_metadata' ), 10, 3 );

\add_action( 'activitypub_inbox', array( $this, 'handle_received_activity' ), 10, 3 );
Expand All @@ -60,12 +60,88 @@ public function __construct( Feed $friends_feed ) {

\add_filter( 'pre_comment_approved', array( $this, 'pre_comment_approved' ), 10, 2 );

\add_filter( 'friends_reblog_button_label', array( $this, 'friends_reblog_button_label' ), 10, 2 );
// Don't post via ActivityPub since we'll use announce there.
\add_filter( 'friends_reblog', array( $this, 'unqueue_activitypub_create' ), 9 );
\add_filter( 'friends_reblog', array( $this, 'reblog' ), 20, 2 );
\add_filter( 'friends_reblog', array( $this, 'maybe_unqueue_friends_reblog_post' ), 9, 2 );

\add_filter( 'pre_get_remote_metadata_by_actor', array( $this, 'disable_webfinger_for_example_domains' ), 9, 2 );

add_filter( 'friends_get_feed_metadata', array( $this, 'friends_get_feed_metadata' ), 10, 2 );
add_filter( 'friends_get_activitypub_metadata', array( $this, 'friends_activitypub_metadata' ), 10, 2 );
}

/**
* Add the admin menu to the sidebar.
*/
public function admin_menu() {
$friends = Friends::get_instance();
$unread_badge = $friends->admin->get_unread_badge();

$menu_title = __( 'Friends', 'friends' ) . $unread_badge;
$page_type = sanitize_title( $menu_title );

add_submenu_page(
'friends',
__( 'ActivityPub', 'friends' ),
__( 'ActivityPub', 'friends' ),
Friends::required_menu_role(),
'friends-activitypub-settings',
array( $this, 'settings' )
);

add_action( 'load-' . $page_type . '_page_friends-activitypub-settings', array( $this, 'process_settings' ) );
}

public function settings() {
Friends::template_loader()->get_template_part(
'admin/settings-header',
null,
array(
'menu' => array(
__( 'ActivityPub Settings', 'friends' ) => 'friends-activitypub-settings',
),
'active' => 'friends-activitypub-settings',
'title' => __( 'Friends', 'friends' ),
)
);

if ( isset( $_GET['updated'] ) ) {
?>
<div id="message" class="updated notice is-dismissible"><p><?php esc_html_e( 'Settings were updated.', 'friends' ); ?></p></div>
<?php
} elseif ( isset( $_GET['error'] ) ) {
?>
<div id="message" class="updated error is-dismissible"><p><?php esc_html_e( 'An error occurred.', 'friends' ); ?></p></div>
<?php
}

Friends::template_loader()->get_template_part(
'admin/activitypub-settings',
null,
array(
'reblog' => ! get_user_option( 'friends_activitypub_dont_reblog' ),
)
);

Friends::template_loader()->get_template_part( 'admin/settings-footer' );
}

public function process_settings() {
if ( empty( $_POST ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'friends-activitypub-settings' ) ) {
return;
}

if ( isset( $_POST['activitypub_reblog'] ) ) {
delete_user_option( get_current_user_id(), 'friends_activitypub_dont_reblog' );
} else {
update_user_option( get_current_user_id(), 'friends_activitypub_dont_reblog', true );
}
wp_safe_redirect( add_query_arg( 'updated', 'true', admin_url( 'admin.php?page=friends-activitypub-settings' ) ) );
exit;
}

public function friends_get_feed_metadata( $meta, $feed ) {
if ( self::SLUG === $feed->get_parser() ) {
return $this->friends_activitypub_metadata( $meta, $feed->get_url() );
Expand Down Expand Up @@ -1072,6 +1148,66 @@ public function unlike_post( $url, $external_post_id, $user_id ) {
}
}

public function friends_reblog_button_label( $button_label ) {
if ( get_post_meta( get_the_ID(), 'parser', true ) === 'activitypub' ) {
if ( get_user_option( 'friends_activitypub_dont_reblog' ) ) {
$button_label = _x( 'Boost', 'button', 'friends' );
} else {
$button_label = _x( 'Reblog & Boost', 'button', 'friends' );
}
}
return $button_label;
}

public function unqueue_activitypub_create( $ret ) {
// TODO: Harden against cases when ActivityPub changes the priority.
remove_action( 'transition_post_status', array( '\Activitypub\Activitypub', 'schedule_post_activity' ), 33, 3 );
return $ret;
}

public function maybe_unqueue_friends_reblog_post( $ret, $post ) {
if ( ! get_user_option( 'friends_activitypub_dont_reblog' ) ) {
return $ret;
}

if ( get_post_meta( $post->ID, 'parser', true ) !== 'activitypub' ) {
return $ret;
}

remove_filter( 'friends_reblog', array( 'Friends\Frontend', 'reblog' ), 10, 2 );
}

public function reblog( $ret, $post ) {
if ( get_post_meta( $post->ID, 'parser', true ) === 'activitypub' ) {
$this->announce( $post->guid );
return true;
}
return $ret;
}


public function announce( $url ) {
$user_id = get_current_user_id();
$actor = \get_author_posts_url( get_current_user_id() );

$activity = new \Activitypub\Model\Activity( 'Announce', \Activitypub\Model\Activity::TYPE_SIMPLE );
$activity->set_to( null );
$activity->set_cc( null );
$activity->set_actor( $actor );
$activity->set_object( $url );
$activity->set_id( $actor . '#announce-' . \preg_replace( '~^https?://~', '', $url ) );
$inboxes = \Activitypub\get_follower_inboxes( $user_id );

$followers_url = \get_rest_url( null, '/activitypub/1.0/users/' . intval( $user_id ) . '/followers' );

foreach ( $inboxes as $inbox => $to ) {
$to = array_values( array_unique( $to ) );
$activity->set_to( $to );

\Activitypub\safe_remote_post( $inbox, $activity->to_json(), $user_id );
}
}

/**
* Approve comments that
*
Expand Down
19 changes: 19 additions & 0 deletions friends.js
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,25 @@
} );
} );

/* ActivityPub */
$( function () {
$document.on( 'click', 'a.friends-reblog', function () {
const $this = $( this );
wp.ajax.send( 'friends-reblog', {
data: {
_ajax_nonce: $this.data( 'nonce' ),
post_id: $this.data( 'id' ),
},
success() {
$this
.find( 'i.friends-reblog-status' )
.addClass( 'dashicons dashicons-saved' );
},
} );
return false;
} );
} );

$document.on( 'click', 'a.send-new-message', function () {
$( '#friends-send-new-message' )
.toggle()
Expand Down
30 changes: 21 additions & 9 deletions includes/class-admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public function __construct( Friends $friends ) {
* Register the WordPress hooks
*/
private function register_hooks() {
add_action( 'admin_menu', array( $this, 'register_admin_menu' ) );
add_action( 'admin_menu', array( $this, 'admin_menu' ) );
add_action( 'friends_own_site_menu_top', array( $this, 'friends_add_menu_open_friend_request' ), 10, 2 );
add_filter( 'users_list_table_query_args', array( $this, 'allow_role_multi_select' ) );
add_filter( 'user_row_actions', array( get_called_class(), 'user_row_actions' ), 10, 2 );
Expand Down Expand Up @@ -106,7 +106,7 @@ public function admin_notice_unsupported_permalink_structure() {
/**
* Registers the admin menus
*/
public function register_admin_menu() {
public function admin_menu() {
if ( isset( $_REQUEST['rerun-activate'] ) && isset( $_REQUEST['_wpnonce'] ) && wp_verify_nonce( $_REQUEST['_wpnonce'], 'friends-settings' ) ) {
Friends::activate_plugin();
wp_safe_redirect( add_query_arg( array( 'reran-activation' => 'friends' ), wp_get_referer() ) );
Expand All @@ -128,16 +128,15 @@ public function register_admin_menu() {
add_submenu_page( 'friends', __( 'Add New Friend', 'friends' ), __( 'Add New Friend', 'friends' ), $required_role, 'add-friend', array( $this, 'render_admin_add_friend' ) );
add_action( 'load-' . $page_type . '_page_friends-settings', array( $this, 'process_admin_settings' ) );

add_submenu_page( 'friends', __( 'Friends &amp; Requests', 'friends' ), __( 'Friends &amp; Requests', 'friends' ), $required_role, 'friends-list', array( $this, 'render_friends_list' ) );

if ( $this->friends_unread_friend_request_count( 0 ) > 0 ) {
add_submenu_page( 'friends', __( 'Friend Requests', 'friends' ), __( 'Friend Requests', 'friends' ) . $unread_badge, $required_role, 'friends-list-requests', array( $this, 'render_friends_list' ) );
} elseif ( isset( $_GET['page'] ) && 'friends-list-requests' === $_GET['page'] ) {
// Don't show a no permission page but redirect to the friends list.
wp_safe_redirect( self_admin_url( 'admin.php?page=friends-list' ) );
exit;
add_submenu_page( 'friends', __( 'Friend Requests', 'friends' ), __( 'Friend Requests', 'friends' ) . $unread_badge, $required_role, 'friends-list-requests', array( $this, 'render_friends_list' ) );
}

add_submenu_page( 'friends', __( 'Friends &amp; Requests', 'friends' ), __( 'Friends &amp; Requests', 'friends' ), $required_role, 'friends-list', array( $this, 'render_friends_list' ) );

if ( isset( $_GET['page'] ) && 'friends-refresh' === $_GET['page'] ) {
add_submenu_page( 'friends', __( 'Refresh', 'friends' ), __( 'Refresh', 'friends' ), $required_role, 'friends-refresh', array( $this, 'admin_refresh_friend_posts' ) );
}
Expand Down Expand Up @@ -896,9 +895,22 @@ public function ajax_refresh_link_token() {
);
wp_die();
}

public function render_friends_list() {
Friends::template_loader()->get_template_part(
'admin/settings-header',
null,
array(
'menu' => array(
__( 'Your Friends & Subscriptions', 'friends' ) => 'friends-list',
__( 'Your Friend Requests', 'friends' ) => 'friends-list-requests',
),
'active' => $_GET['page'],
'title' => __( 'Friends', 'friends' ),
)
);

if ( isset( $_GET['page'] ) && 'friends-list-requests' === $_GET['page'] ) {
echo '<div class="wrap"><h3>' . esc_html__( 'Your Friend Requests', 'friends' ) . '</h3>';
echo '<p>';
echo wp_kses(
sprintf(
Expand All @@ -916,7 +928,6 @@ public function render_friends_list() {
echo '</p>';
$query = User_Query::all_friend_requests();
} else {
echo '<div class="wrap"><h3>' . esc_html__( 'Your Friends & Subscriptions', 'friends' ) . '</h3>';
$query = User_Query::all_associated_users();
}

Expand Down Expand Up @@ -953,7 +964,8 @@ public function render_friends_list() {
'friends' => $query->get_results(),
)
);
echo '</div>';

Friends::template_loader()->get_template_part( 'admin/settings-footer' );
}

/**
Expand Down
1 change: 0 additions & 1 deletion includes/class-automatic-status.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ private function register_hooks() {
* Add the admin menu to the sidebar.
*/
public function admin_menu() {
$required_role = Friends::required_menu_role();
$unread_badge = $this->friends->admin->get_unread_badge();

$menu_title = __( 'Friends', 'friends' ) . $unread_badge;
Expand Down
74 changes: 74 additions & 0 deletions includes/class-frontend.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ private function register_hooks() {
add_action( 'wp_ajax_friends-autocomplete', array( $this, 'ajax_autocomplete' ) );
add_action( 'wp_ajax_friends-star', array( $this, 'ajax_star_friend_user' ) );
add_action( 'wp_ajax_friends-load-comments', array( $this, 'ajax_load_comments' ) );
add_action( 'wp_ajax_friends-reblog', array( $this, 'wp_ajax_reblog' ) );
add_action( 'friends_post_footer_first', array( $this, 'reblog_button' ) );
add_filter( 'friends_reblog', array( get_called_class(), 'reblog' ), 10, 2 );
add_action( 'wp_untrash_post_status', array( $this, 'untrash_post_status' ), 10, 3 );
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
add_action( 'wp_enqueue_scripts', array( $this, 'dequeue_scripts' ), 99999 );
Expand Down Expand Up @@ -268,6 +271,76 @@ private function get_minimal_query_vars( $query_vars ) {
return array_filter( array_intersect_key( $query_vars, array_flip( array( 'p', 'page_id', 'pagename', 'author', 'author__not_in', 'post_type', 'post_status', 'posts_per_page', 'order', 'tax_query' ) ) ) );
}


public function wp_ajax_reblog() {
if ( ! current_user_can( Friends::REQUIRED_ROLE ) ) {
wp_send_json_error( 'error' );
}

$post = get_post( $_POST['post_id'] );
if ( ! $post || ! Friends::check_url( $post->guid ) ) {
wp_send_json_error( 'unknown-post', array( 'guid' => $post->guid ) );
}

$ret = apply_filters( 'friends_reblog', null, $post );
if ( ! $ret || is_wp_error( $ret ) ) {
wp_send_json_error( 'error' );
}

wp_send_json_success(
array(
'post_id' => $post->ID,
)
);
}

public function reblog_button() {
$button_label = apply_filters( 'friends_reblog_button_label', _x( 'Reblog', 'button', 'friends' ) );

Friends::template_loader()->get_template_part(
'frontend/parts/reblog-button',
null,
array(
'button-label' => $button_label,
)
);
}

public static function reblog( $ret, $post ) {
$author = get_post_meta( $post->ID, 'author', true );
if ( ! $author ) {
$friend = new User( $post->post_author );
$author = $friend->display_name;
}

$old_guid = $post->guid;
$old_post_id = $post->ID;

$post_format = get_post_format( $post );

$reblog = '<!-- wp:paragraph -->' . PHP_EOL . '<p >';
$reblog .= sprintf(
// translators: %s is a link.
__( 'Reblog via %s', 'friends' ),
'<a href="' . esc_url( $post->guid ) . '">' . esc_html( $author ) . '</a>'
);

$reblog .= PHP_EOL . '</p>' . PHP_EOL . '<!-- /wp:paragraph -->' . PHP_EOL;

unset( $post->ID, $post->guid, $post->name, $post->post_date, $post->post_date_gmt, $post->post_modified, $post->post_modified_gmt );
$post->post_author = get_current_user_id();
$post->post_status = 'publish';
$post->post_type = 'post';
$post->post_content = $reblog . $post->post_content;
$post_id = wp_insert_post( $post );

set_post_format( $post_id, $post_format );
update_post_meta( $post_id, 'reblog', $old_guid );
update_post_meta( $old_post_id, 'reblogged', $post_id );

return true;
}

/**
* The Ajax function to be called upon posting from /friends
*/
Expand Down Expand Up @@ -1003,6 +1076,7 @@ public function friend_posts_query( $query ) {
// Show your own posts on the status feed.
$post_types[] = 'post';
}

$query->set( 'post_type', $post_types );
$query->set( 'tax_query', $tax_query );

Expand Down

0 comments on commit 2131d00

Please sign in to comment.