From 5f6cf78da16434b29d34f15f0f09eb691b8de530 Mon Sep 17 00:00:00 2001 From: Alex Kirk Date: Wed, 9 Nov 2022 07:08:32 -0700 Subject: [PATCH 01/23] Add a parser to the Friends Plugin --- activitypub.php | 8 + includes/class-health-check.php | 63 ++---- includes/functions.php | 10 +- includes/model/class-activity.php | 2 +- includes/rest/class-inbox.php | 5 +- includes/rest/class-webfinger.php | 40 ++++ .../class-friends-feed-parser-activitypub.php | 198 ++++++++++++++++++ templates/author-json.php | 2 +- 8 files changed, 283 insertions(+), 45 deletions(-) create mode 100644 integration/class-friends-feed-parser-activitypub.php diff --git a/activitypub.php b/activitypub.php index d802cd7..69dfeb6 100644 --- a/activitypub.php +++ b/activitypub.php @@ -132,3 +132,11 @@ function enable_buddypress_features() { \Activitypub\Integration\Buddypress::init(); } add_action( 'bp_include', '\Activitypub\enable_buddypress_features' ); + +add_action( + 'friends_load_parsers', + function( \Friends\Feed $friends_feed ) { + require_once __DIR__ . '/integration/class-friends-feed-parser-activitypub.php'; + $friends_feed->register_parser( Friends_Feed_Parser_ActivityPub::SLUG, new Friends_Feed_Parser_ActivityPub( $friends_feed ) ); + } +); diff --git a/includes/class-health-check.php b/includes/class-health-check.php index 12f8d71..bdc3b0b 100644 --- a/includes/class-health-check.php +++ b/includes/class-health-check.php @@ -1,6 +1,8 @@ ID ); + $user = \wp_get_current_user(); + $account = \Activitypub\get_webfinger_resource( $user->ID ); - $url = \wp_parse_url( \home_url(), \PHP_URL_SCHEME ) . '://' . \wp_parse_url( \home_url(), \PHP_URL_HOST ); - - if ( \wp_parse_url( \home_url(), \PHP_URL_PORT ) ) { - $url .= ':' . \wp_parse_url( \home_url(), \PHP_URL_PORT ); - } - - $url = \trailingslashit( $url ) . '.well-known/webfinger'; - - $url = \add_query_arg( 'resource', 'acct:' . $webfinger, $url ); - - // try to access author URL - $response = \wp_remote_get( - $url, - array( - 'headers' => array( 'Accept' => 'application/activity+json' ), - 'redirection' => 0, - ) - ); - - if ( \is_wp_error( $response ) ) { - return new \WP_Error( - 'webfinger_url_not_accessible', - \sprintf( + $url = Webfinger::resolve( $account ); + if ( \is_wp_error( $url ) ) { + $health_messages = array( + 'webfinger_url_not_accessible' => \sprintf( // translators: %s: Author URL \__( '

Your WebFinger endpoint %s is not accessible. Please check your WordPress setup or permalink structure.

', 'activitypub' ), - $url - ) - ); - } - - $response_code = \wp_remote_retrieve_response_code( $response ); - - // check if response is JSON - $body = \wp_remote_retrieve_body( $response ); - - if ( ! \is_string( $body ) || ! \is_array( \json_decode( $body, true ) ) ) { - return new \WP_Error( - 'webfinger_url_not_accessible', - \sprintf( + $url->get_error_data() + ), + 'webfinger_url_invalid_response' => \sprintf( // translators: %s: Author URL \__( '

Your WebFinger endpoint %s does not return valid JSON for application/jrd+json.

', 'activitypub' ), - $url - ) + $url->get_error_data() + ), + ); + $message = null; + if ( isset( $messages[ $url->get_error_code() ] ) ) { + $message = $health_messages[ $url->get_error_code() ]; + } + return new \WP_Error( + $url->get_error_code(), + $message, + $url->get_error_data() ); } diff --git a/includes/functions.php b/includes/functions.php index 3c33664..1c1470f 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -108,11 +108,19 @@ function get_webfinger_resource( $user_id ) { /** * [get_metadata_by_actor description] * - * @param sting $actor + * @param string $actor * * @return array */ function get_remote_metadata_by_actor( $actor ) { + if ( preg_match( '/^@?[^@]+@((?:[a-z0-9-]+\.)+[a-z]+)$/i', $actor ) ) { + $actor = Rest\Webfinger::resolve( $actor ); + } + + if ( ! $actor ) { + return null; + } + $metadata = \get_transient( 'activitypub_' . $actor ); if ( $metadata ) { diff --git a/includes/model/class-activity.php b/includes/model/class-activity.php index eb96d11..9de1031 100644 --- a/includes/model/class-activity.php +++ b/includes/model/class-activity.php @@ -75,7 +75,7 @@ class Activity { } public function to_array() { - $array = \get_object_vars( $this ); + $array = array_filter( \get_object_vars( $this ) ); if ( $this->context ) { $array = array( '@context' => $this->context ) + $array; diff --git a/includes/rest/class-inbox.php b/includes/rest/class-inbox.php index 1ffe451..3408950 100644 --- a/includes/rest/class-inbox.php +++ b/includes/rest/class-inbox.php @@ -161,7 +161,6 @@ class Inbox { public static function shared_inbox_post( $request ) { $data = $request->get_params(); $type = $request->get_param( 'type' ); - $users = self::extract_recipients( $data ); if ( ! $users ) { @@ -407,6 +406,10 @@ class Inbox { public static function handle_create( $object, $user_id ) { $meta = \Activitypub\get_remote_metadata_by_actor( $object['actor'] ); + if ( ! isset( $object['object']['inReplyTo'] ) ) { + return; + } + $comment_post_id = \url_to_postid( $object['object']['inReplyTo'] ); // save only replys and reactions diff --git a/includes/rest/class-webfinger.php b/includes/rest/class-webfinger.php index 60eb5d2..ebf3890 100644 --- a/includes/rest/class-webfinger.php +++ b/includes/rest/class-webfinger.php @@ -120,4 +120,44 @@ class Webfinger { return $array; } + + public static function resolve( $account ) { + if ( ! preg_match( '/^@?[^@]+@((?:[a-z0-9-]+\.)+[a-z]+)$/i', $account, $m ) ) { + return null; + } + $url = \add_query_arg( 'resource', 'acct:' . ltrim( $account, '@' ), 'https://' . $m[1] . '/.well-known/webfinger' ); + if ( ! \wp_http_validate_url( $url ) ) { + return new \WP_Error( 'invalid_webfinger_url', null, $url ); + } + + // try to access author URL + $response = \wp_remote_get( + $url, + array( + 'headers' => array( 'Accept' => 'application/activity+json' ), + 'redirection' => 0, + ) + ); + + if ( \is_wp_error( $response ) ) { + return new \WP_Error( 'webfinger_url_not_accessible', null, $url ); + } + + $response_code = \wp_remote_retrieve_response_code( $response ); + + $body = \wp_remote_retrieve_body( $response ); + $body = \json_decode( $body, true ); + + if ( ! isset( $body['links'] ) ) { + return new \WP_Error( 'webfinger_url_invalid_response', null, $url ); + } + + foreach ( $body['links'] as $link ) { + if ( $link['rel'] === 'self' && $link['type'] == 'application/activity+json' ) { + return $link['href']; + } + } + + return new \WP_Error( 'webfinger_url_no_activity_pub', null, $body ); + } } diff --git a/integration/class-friends-feed-parser-activitypub.php b/integration/class-friends-feed-parser-activitypub.php new file mode 100644 index 0000000..164e6be --- /dev/null +++ b/integration/class-friends-feed-parser-activitypub.php @@ -0,0 +1,198 @@ +friends_feed = $friends_feed; + + \add_action( 'activitypub_inbox_create', array( $this, 'handle_received_activity' ), 10, 2 ); + \add_action( 'activitypub_inbox_accept', array( $this, 'handle_received_activity' ), 10, 2 ); + \add_filter( 'friends_user_feed_activated', array( $this, 'follow_user' ), 10 ); + \add_filter( 'friends_user_feed_deactivated', array( $this, 'unfollow_user' ), 10 ); + \add_filter( 'friends_rewrite_incoming_url', array( $this, 'friends_rewrite_incoming_url' ), 10, 2 ); + } + + /** + * Determines if this is a supported feed and to what degree we feel it's supported. + * + * @param string $url The url. + * @param string $mime_type The mime type. + * @param string $title The title. + * @param string|null $content The content, it can't be assumed that it's always available. + * + * @return int Return 0 if unsupported, a positive value representing the confidence for the feed, use 10 if you're reasonably confident. + */ + public function feed_support_confidence( $url, $mime_type, $title, $content = null ) { + if ( preg_match( '/^@?[^@]+@((?:[a-z0-9-]+\.)+[a-z]+)$/i', $url ) ) { + return 10; + } + + return 0; + } + + /** + * Format the feed title and autoselect the posts feed. + * + * @param array $feed_details The feed details. + * + * @return array The (potentially) modified feed details. + */ + public function update_feed_details( $feed_details ) { + $meta = \Activitypub\get_remote_metadata_by_actor( $feed_details['url'] ); + if ( $meta && ! is_wp_error( $meta ) ) { + if ( isset( $meta['preferredUsername'] ) ) { + $feed_details['title'] = $meta['preferredUsername']; + } + $feed_details['url'] = $meta['id']; + } + + return $feed_details; + } + + public function friends_rewrite_incoming_url( $url, $incoming_url ) { + if ( preg_match( '/^@?[^@]+@((?:[a-z0-9-]+\.)+[a-z]+)$/i', $incoming_url ) ) { + $resolved_url = \Activitypub\Rest\Webfinger::resolve( $incoming_url ); + if ( ! is_wp_error( $resolved_url ) ) { + return $resolved_url; + } + } + return $url; + } + + /** + * Discover the feeds available at the URL specified. + * + * @param string $content The content for the URL is already provided here. + * @param string $url The url to search. + * + * @return array A list of supported feeds at the URL. + */ + public function discover_available_feeds( $content, $url ) { + $discovered_feeds = array(); + + $meta = \Activitypub\get_remote_metadata_by_actor( $url ); + if ( $meta && ! is_wp_error( $meta ) ) { + $discovered_feeds[ $meta['id'] ] = array( + 'type' => 'application/activity+json', + 'rel' => 'self', + 'post-format' => 'autodetect', + 'parser' => self::SLUG, + 'autoselect' => true, + ); + } + return $discovered_feeds; + } + + /** + * Fetches a feed and returns the processed items. + * + * @param string $url The url. + * + * @return array An array of feed items. + */ + public function fetch_feed( $url ) { + // There is no feed to fetch, we'll receive items via ActivityPub. + return array(); + } + + /** + * Handles "Create" requests + * + * @param array $object The activity-object + * @param int $user_id The id of the local blog-user + */ + public function handle_received_activity( $object, $user_id ) { + $user_feed = $this->friends_feed->get_user_feed_by_url( $object['actor'] ); + if ( is_wp_error( $user_feed ) ) { + $meta = \Activitypub\get_remote_metadata_by_actor( $object['actor'] ); + $user_feed = $this->friends_feed->get_user_feed_by_url( $meta['url'] ); + if ( is_wp_error( $user_feed ) ) { + // We're not following this user. + return false; + } + } + switch ( $object['type'] ) { + case 'Accept': + // nothing to do. + break; + case 'Create': + $this->handle_incoming_post( $object['object'], $user_feed ); + + } + + return true; + } + + private function map_type_to_post_format( $type ) { + return 'status'; + } + + private function handle_incoming_post( $object, \Friends\User_Feed $user_feed ) { + $item = new \Friends\Feed_Item( + array( + 'permalink' => $object['url'], + // 'title' => '', + 'content' => $object['content'], + 'post_format' => $this->map_type_to_post_format( $object['type'] ), + 'date' => $object['published'], + ) + ); + + $this->friends_feed->process_incoming_feed_items( array( $item ), $user_feed ); + } + + public function follow_user( \Friends\User_Feed $user_feed ) { + if ( self::SLUG != $user_feed->get_parser() ) { + return; + } + + $meta = \Activitypub\get_remote_metadata_by_actor( $user_feed->get_url() ); + $to = $meta['id']; + $inbox = \Activitypub\get_inbox_by_actor( $to ); + $user_id = get_current_user_id(); + $actor = \get_author_posts_url( $user_id ); + + $activity = new \Activitypub\Model\Activity( 'Follow', \Activitypub\Model\Activity::TYPE_SIMPLE ); + $activity->set_to( null ); + $activity->set_cc( null ); + $activity->set_actor( \get_author_posts_url( $user_id ) ); + $activity->set_object( $to ); + $activity->set_id( $actor . '#follow-' . \preg_replace( '~^https?://~', '', $to ) ); + $activity = $activity->to_json(); + \Activitypub\safe_remote_post( $inbox, $activity, $user_id ); + } + + public function unfollow_user( \Friends\User_Feed $user_feed ) { + if ( self::SLUG != $user_feed->get_parser() ) { + return; + } + + $meta = \Activitypub\get_remote_metadata_by_actor( $user_feed->get_url() ); + $to = $meta['id']; + $inbox = \Activitypub\get_inbox_by_actor( $to ); + $user_id = get_current_user_id(); + $actor = \get_author_posts_url( $user_id ); + + $activity = new \Activitypub\Model\Activity( 'Unfollow', \Activitypub\Model\Activity::TYPE_SIMPLE ); + $activity->set_to( null ); + $activity->set_cc( null ); + $activity->set_actor( \get_author_posts_url( $user_id ) ); + $activity->set_object( $to ); + $activity->set_id( $actor . '#unfollow-' . \preg_replace( '~^https?://~', '', $to ) ); + $activity = $activity->to_json(); + \Activitypub\safe_remote_post( $inbox, $activity, $user_id ); + } +} diff --git a/templates/author-json.php b/templates/author-json.php index c5d39a5..d5a0b69 100644 --- a/templates/author-json.php +++ b/templates/author-json.php @@ -16,7 +16,7 @@ $json->preferredUsername = \get_the_author_meta( 'login', $author_id ); // phpcs $json->url = \get_author_posts_url( $author_id ); $json->icon = array( 'type' => 'Image', - 'url' => \get_avatar_url( $author_id, array( 'size' => 120 ) ), + 'url' => 'https://akirk.blog/wp-content/uploads/2022/11/alex.kirk-small.jpg', ); if ( \has_header_image() ) { From 04db99730d9bf0293a403860893d5a031f8223f1 Mon Sep 17 00:00:00 2001 From: Alex Kirk Date: Wed, 9 Nov 2022 07:17:59 -0700 Subject: [PATCH 02/23] phpcs --- includes/rest/class-webfinger.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/rest/class-webfinger.php b/includes/rest/class-webfinger.php index ebf3890..1b00d86 100644 --- a/includes/rest/class-webfinger.php +++ b/includes/rest/class-webfinger.php @@ -153,7 +153,7 @@ class Webfinger { } foreach ( $body['links'] as $link ) { - if ( $link['rel'] === 'self' && $link['type'] == 'application/activity+json' ) { + if ( 'self' === $link['rel'] && 'application/activity+json' === $link['type'] ) { return $link['href']; } } From eff60ed5ddca8be8ee5b5a0f9f3ea31e16460b3e Mon Sep 17 00:00:00 2001 From: Alex Kirk Date: Sun, 6 Nov 2022 16:49:53 -0700 Subject: [PATCH 03/23] Fix the signature for HTTP GET requests --- includes/class-signature.php | 6 +++--- includes/functions.php | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/includes/class-signature.php b/includes/class-signature.php index 5caf884..f78b87d 100644 --- a/includes/class-signature.php +++ b/includes/class-signature.php @@ -70,7 +70,7 @@ class Signature { \update_user_meta( $user_id, 'magic_sig_public_key', $detail['key'] ); } - public static function generate_signature( $user_id, $url, $date, $digest = null ) { + public static function generate_signature( $user_id, $http_method, $url, $date, $digest = null ) { $key = self::get_private_key( $user_id ); $url_parts = \wp_parse_url( $url ); @@ -89,9 +89,9 @@ class Signature { } if ( ! empty( $digest ) ) { - $signed_string = "(request-target): post $path\nhost: $host\ndate: $date\ndigest: SHA-256=$digest"; + $signed_string = "(request-target): $http_method $path\nhost: $host\ndate: $date\ndigest: SHA-256=$digest"; } else { - $signed_string = "(request-target): post $path\nhost: $host\ndate: $date"; + $signed_string = "(request-target): $http_method $path\nhost: $host\ndate: $date"; } $signature = null; diff --git a/includes/functions.php b/includes/functions.php index 1c1470f..1b1269c 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -35,7 +35,7 @@ function get_context() { function safe_remote_post( $url, $body, $user_id ) { $date = \gmdate( 'D, d M Y H:i:s T' ); $digest = \Activitypub\Signature::generate_digest( $body ); - $signature = \Activitypub\Signature::generate_signature( $user_id, $url, $date, $digest ); + $signature = \Activitypub\Signature::generate_signature( $user_id, 'post', $url, $date, $digest ); $wp_version = \get_bloginfo( 'version' ); $user_agent = \apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . \get_bloginfo( 'url' ) ); @@ -63,7 +63,7 @@ function safe_remote_post( $url, $body, $user_id ) { function safe_remote_get( $url, $user_id ) { $date = \gmdate( 'D, d M Y H:i:s T' ); - $signature = \Activitypub\Signature::generate_signature( $user_id, $url, $date ); + $signature = \Activitypub\Signature::generate_signature( $user_id, 'get', $url, $date ); $wp_version = \get_bloginfo( 'version' ); $user_agent = \apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . \get_bloginfo( 'url' ) ); From 568b258c771dc73a0814ff5c1a36fd0f9df28989 Mon Sep 17 00:00:00 2001 From: Alex Kirk Date: Wed, 9 Nov 2022 07:27:05 -0700 Subject: [PATCH 04/23] undo temp change --- templates/author-json.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/author-json.php b/templates/author-json.php index d5a0b69..c5d39a5 100644 --- a/templates/author-json.php +++ b/templates/author-json.php @@ -16,7 +16,7 @@ $json->preferredUsername = \get_the_author_meta( 'login', $author_id ); // phpcs $json->url = \get_author_posts_url( $author_id ); $json->icon = array( 'type' => 'Image', - 'url' => 'https://akirk.blog/wp-content/uploads/2022/11/alex.kirk-small.jpg', + 'url' => \get_avatar_url( $author_id, array( 'size' => 120 ) ), ); if ( \has_header_image() ) { From 3def5832697fedc8ab72b5bd902767d5b102acf6 Mon Sep 17 00:00:00 2001 From: Alex Kirk Date: Wed, 9 Nov 2022 07:27:50 -0700 Subject: [PATCH 05/23] typo --- includes/class-health-check.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class-health-check.php b/includes/class-health-check.php index bdc3b0b..8989c2f 100644 --- a/includes/class-health-check.php +++ b/includes/class-health-check.php @@ -224,7 +224,7 @@ class Health_Check { ), ); $message = null; - if ( isset( $messages[ $url->get_error_code() ] ) ) { + if ( isset( $health_messages[ $url->get_error_code() ] ) ) { $message = $health_messages[ $url->get_error_code() ]; } return new \WP_Error( From 4300c579aa64f77ed8951787ff2ff06d9205686c Mon Sep 17 00:00:00 2001 From: Alex Kirk Date: Mon, 14 Nov 2022 20:04:01 -0500 Subject: [PATCH 06/23] Queue the activitypub request --- .../class-friends-feed-parser-activitypub.php | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/integration/class-friends-feed-parser-activitypub.php b/integration/class-friends-feed-parser-activitypub.php index 164e6be..293322f 100644 --- a/integration/class-friends-feed-parser-activitypub.php +++ b/integration/class-friends-feed-parser-activitypub.php @@ -20,8 +20,10 @@ class Friends_Feed_Parser_ActivityPub extends \Friends\Feed_Parser { \add_action( 'activitypub_inbox_create', array( $this, 'handle_received_activity' ), 10, 2 ); \add_action( 'activitypub_inbox_accept', array( $this, 'handle_received_activity' ), 10, 2 ); - \add_filter( 'friends_user_feed_activated', array( $this, 'follow_user' ), 10 ); - \add_filter( 'friends_user_feed_deactivated', array( $this, 'unfollow_user' ), 10 ); + \add_filter( 'friends_user_feed_activated', array( $this, 'queue_follow_user' ), 10 ); + \add_filter( 'friends_user_feed_deactivated', array( $this, 'queue_unfollow_user' ), 10 ); + \add_filter( 'friends_feed_parser_activitypub_follow', array( $this, 'follow_user' ), 10 ); + \add_filter( 'friends_feed_parser_activitypub_unfollow', array( $this, 'unfollow_user' ), 10 ); \add_filter( 'friends_rewrite_incoming_url', array( $this, 'friends_rewrite_incoming_url' ), 10, 2 ); } @@ -154,6 +156,18 @@ class Friends_Feed_Parser_ActivityPub extends \Friends\Feed_Parser { $this->friends_feed->process_incoming_feed_items( array( $item ), $user_feed ); } + public function queue_follow_user( \Friends\User_Feed $user_feed ) { + if ( self::SLUG != $user_feed->get_parser() ) { + return; + } + + if ( wp_next_scheduled( 'friends_feed_parser_activitypub_follow', array( $user_feed ) ) ) { + return; + } + + return \wp_schedule_single_event( \time(), 'friends_feed_parser_activitypub_follow', array( $user_feed ) ); + } + public function follow_user( \Friends\User_Feed $user_feed ) { if ( self::SLUG != $user_feed->get_parser() ) { return; @@ -175,6 +189,18 @@ class Friends_Feed_Parser_ActivityPub extends \Friends\Feed_Parser { \Activitypub\safe_remote_post( $inbox, $activity, $user_id ); } + public function queue_unfollow_user( \Friends\User_Feed $user_feed ) { + if ( self::SLUG != $user_feed->get_parser() ) { + return; + } + + if ( wp_next_scheduled( 'friends_feed_parser_activitypub_unfollow', array( $user_feed ) ) ) { + return; + } + + return \wp_schedule_single_event( \time(), 'friends_feed_parser_activitypub_unfollow', array( $user_feed ) ); + } + public function unfollow_user( \Friends\User_Feed $user_feed ) { if ( self::SLUG != $user_feed->get_parser() ) { return; From 8ab20c5de0efdaa4afc2b711ce6b26da87c89eff Mon Sep 17 00:00:00 2001 From: Alex Kirk Date: Tue, 15 Nov 2022 18:46:40 +0100 Subject: [PATCH 07/23] Don't use full object as cron parameters --- .../class-friends-feed-parser-activitypub.php | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/integration/class-friends-feed-parser-activitypub.php b/integration/class-friends-feed-parser-activitypub.php index 293322f..d2f75b5 100644 --- a/integration/class-friends-feed-parser-activitypub.php +++ b/integration/class-friends-feed-parser-activitypub.php @@ -20,10 +20,10 @@ class Friends_Feed_Parser_ActivityPub extends \Friends\Feed_Parser { \add_action( 'activitypub_inbox_create', array( $this, 'handle_received_activity' ), 10, 2 ); \add_action( 'activitypub_inbox_accept', array( $this, 'handle_received_activity' ), 10, 2 ); - \add_filter( 'friends_user_feed_activated', array( $this, 'queue_follow_user' ), 10 ); - \add_filter( 'friends_user_feed_deactivated', array( $this, 'queue_unfollow_user' ), 10 ); - \add_filter( 'friends_feed_parser_activitypub_follow', array( $this, 'follow_user' ), 10 ); - \add_filter( 'friends_feed_parser_activitypub_unfollow', array( $this, 'unfollow_user' ), 10 ); + \add_action( 'friends_user_feed_activated', array( $this, 'queue_follow_user' ), 10 ); + \add_action( 'friends_user_feed_deactivated', array( $this, 'queue_unfollow_user' ), 10 ); + \add_action( 'friends_feed_parser_activitypub_follow', array( $this, 'follow_user' ), 10, 2 ); + \add_action( 'friends_feed_parser_activitypub_unfollow', array( $this, 'unfollow_user' ), 10, 2 ); \add_filter( 'friends_rewrite_incoming_url', array( $this, 'friends_rewrite_incoming_url' ), 10, 2 ); } @@ -161,14 +161,16 @@ class Friends_Feed_Parser_ActivityPub extends \Friends\Feed_Parser { return; } - if ( wp_next_scheduled( 'friends_feed_parser_activitypub_follow', array( $user_feed ) ) ) { + $args = array( $user_feed->get_id(), get_current_user_id() ); + if ( wp_next_scheduled( 'friends_feed_parser_activitypub_follow', $args ) ) { return; } - return \wp_schedule_single_event( \time(), 'friends_feed_parser_activitypub_follow', array( $user_feed ) ); + return \wp_schedule_single_event( \time(), 'friends_feed_parser_activitypub_follow', $args ); } - public function follow_user( \Friends\User_Feed $user_feed ) { + public function follow_user( $user_feed_id, $user_id ) { + $user_feed = \Friends\User_Feed::get_by_id( $user_feed_id ); if ( self::SLUG != $user_feed->get_parser() ) { return; } @@ -176,7 +178,6 @@ class Friends_Feed_Parser_ActivityPub extends \Friends\Feed_Parser { $meta = \Activitypub\get_remote_metadata_by_actor( $user_feed->get_url() ); $to = $meta['id']; $inbox = \Activitypub\get_inbox_by_actor( $to ); - $user_id = get_current_user_id(); $actor = \get_author_posts_url( $user_id ); $activity = new \Activitypub\Model\Activity( 'Follow', \Activitypub\Model\Activity::TYPE_SIMPLE ); @@ -194,14 +195,16 @@ class Friends_Feed_Parser_ActivityPub extends \Friends\Feed_Parser { return; } - if ( wp_next_scheduled( 'friends_feed_parser_activitypub_unfollow', array( $user_feed ) ) ) { + $args = array( $user_feed->get_id(), get_current_user_id() ); + if ( wp_next_scheduled( 'friends_feed_parser_activitypub_unfollow', $args ) ) { return; } - return \wp_schedule_single_event( \time(), 'friends_feed_parser_activitypub_unfollow', array( $user_feed ) ); + return \wp_schedule_single_event( \time(), 'friends_feed_parser_activitypub_unfollow', $args ); } - public function unfollow_user( \Friends\User_Feed $user_feed ) { + public function unfollow_user( $user_feed_id, $user_id ) { + $user_feed = \Friends\User_Feed::get_by_id( $user_feed_id ); if ( self::SLUG != $user_feed->get_parser() ) { return; } @@ -209,7 +212,6 @@ class Friends_Feed_Parser_ActivityPub extends \Friends\Feed_Parser { $meta = \Activitypub\get_remote_metadata_by_actor( $user_feed->get_url() ); $to = $meta['id']; $inbox = \Activitypub\get_inbox_by_actor( $to ); - $user_id = get_current_user_id(); $actor = \get_author_posts_url( $user_id ); $activity = new \Activitypub\Model\Activity( 'Unfollow', \Activitypub\Model\Activity::TYPE_SIMPLE ); From 4cc9cda67a5d309248270982751347dcbfd342c1 Mon Sep 17 00:00:00 2001 From: Alex Kirk Date: Tue, 15 Nov 2022 20:04:01 +0100 Subject: [PATCH 08/23] Remove potentially queued reverse follow/unfollow events --- .../class-friends-feed-parser-activitypub.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/integration/class-friends-feed-parser-activitypub.php b/integration/class-friends-feed-parser-activitypub.php index d2f75b5..ef5265d 100644 --- a/integration/class-friends-feed-parser-activitypub.php +++ b/integration/class-friends-feed-parser-activitypub.php @@ -162,6 +162,13 @@ class Friends_Feed_Parser_ActivityPub extends \Friends\Feed_Parser { } $args = array( $user_feed->get_id(), get_current_user_id() ); + + $unfollow_timestamp = wp_next_scheduled( 'friends_feed_parser_activitypub_unfollow', $args ); + if ( $unfollow_timestamp ) { + // If we just unfollowed, we don't want the event to potentially be executed after our follow event. + wp_unschedule_event( $unfollow_timestamp, $args ); + } + if ( wp_next_scheduled( 'friends_feed_parser_activitypub_follow', $args ) ) { return; } @@ -196,6 +203,13 @@ class Friends_Feed_Parser_ActivityPub extends \Friends\Feed_Parser { } $args = array( $user_feed->get_id(), get_current_user_id() ); + + $follow_timestamp = wp_next_scheduled( 'friends_feed_parser_activitypub_follow', $args ); + if ( $follow_timestamp ) { + // If we just followed, we don't want the event to potentially be executed after our unfollow event. + wp_unschedule_event( $follow_timestamp, $args ); + } + if ( wp_next_scheduled( 'friends_feed_parser_activitypub_unfollow', $args ) ) { return; } From 370ea3a05487a0ca724c9f321cb2f0a280207da2 Mon Sep 17 00:00:00 2001 From: Andreas Date: Wed, 16 Nov 2022 16:14:34 +0100 Subject: [PATCH 09/23] change regex matching potential hashtags Matches any string starting with '#' and consisting of any number and combination of [A-Za-z0-9_] that is directly followed by whitespace or punctuation. Groups everything after '#' for access in functions using this regex. This fixes #183 (incomplete links on hashtags containing special characters) by not matching these at all. --- activitypub.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activitypub.php b/activitypub.php index d802cd7..757b410 100644 --- a/activitypub.php +++ b/activitypub.php @@ -19,7 +19,7 @@ namespace Activitypub; * Initialize plugin */ function init() { - \defined( 'ACTIVITYPUB_HASHTAGS_REGEXP' ) || \define( 'ACTIVITYPUB_HASHTAGS_REGEXP', '(?:(?<=\s)|^)#(\w*[A-Za-z_]+\w*)' ); + \defined( 'ACTIVITYPUB_HASHTAGS_REGEXP' ) || \define( 'ACTIVITYPUB_HASHTAGS_REGEXP', '(?:(?<=\s)|^)#([A-Za-z0-9_]+)(?:(?=\s|[[:punct:]]))' ); \defined( 'ACTIVITYPUB_ALLOWED_HTML' ) || \define( 'ACTIVITYPUB_ALLOWED_HTML', '