From b5c4f473decc8573e1576826c298f2aca0ce9eaf Mon Sep 17 00:00:00 2001 From: Alex Kirk Date: Thu, 8 Dec 2022 21:23:19 +0100 Subject: [PATCH] Start adding support for outgoing mentions --- includes/class-activity-dispatcher.php | 23 ++++- includes/model/class-activity.php | 10 +- ...-class-activitypub-activity-dispatcher.php | 97 +++++++++++++++++++ tests/test-class-activitypub-activity.php | 32 ++++++ 4 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 tests/test-class-activitypub-activity-dispatcher.php create mode 100644 tests/test-class-activitypub-activity.php diff --git a/includes/class-activity-dispatcher.php b/includes/class-activity-dispatcher.php index a4ed0c7..a130ba3 100644 --- a/includes/class-activity-dispatcher.php +++ b/includes/class-activity-dispatcher.php @@ -23,17 +23,36 @@ class Activity_Dispatcher { * * @param \Activitypub\Model\Post $activitypub_post */ - public static function send_post_activity( $activitypub_post ) { + public static function send_post_activity( Model\Post $activitypub_post ) { // get latest version of post $user_id = $activitypub_post->get_post_author(); $activitypub_activity = new \Activitypub\Model\Activity( 'Create', \Activitypub\Model\Activity::TYPE_FULL ); - $activitypub_activity->from_post( $activitypub_post->to_array() ); + $activitypub_activity->from_post( $activitypub_post ); + $sent = array(); foreach ( \Activitypub\get_follower_inboxes( $user_id ) as $inbox => $to ) { + $sent[ $to ] = true; $activitypub_activity->set_to( $to ); $activity = $activitypub_activity->to_json(); // phpcs:ignore + \Activitypub\safe_remote_post( $inbox, $activity, $user_id ); + } + $followers_url = \get_rest_url( null, '/activitypub/1.0/users/' . intval( $user_id ) . '/followers' ); + foreach ( $activitypub_activity->get_cc() as $cc ) { + if ( $cc === $followers_url ) { + continue; + } + $inbox = \Activitypub\get_inbox_by_actor( $cc ); + if ( ! $inbox || \is_wp_error( $inbox ) ) { + continue; + } + if ( isset( $sent[ $cc ] ) ) { + continue; + } + $sent[ $cc ] = true; + $activity = $activitypub_activity->to_json(); // phpcs:ignore + \Activitypub\safe_remote_post( $inbox, $activity, $user_id ); } } diff --git a/includes/model/class-activity.php b/includes/model/class-activity.php index 9de1031..57bf3b1 100644 --- a/includes/model/class-activity.php +++ b/includes/model/class-activity.php @@ -45,16 +45,24 @@ class Activity { } } - public function from_post( $object ) { + public function from_post( Post $post ) { + $object = apply_filters( 'activitypub_from_post_array', $post->to_array() ); $this->object = $object; + if ( isset( $object['published'] ) ) { $this->published = $object['published']; } + $this->cc = array( \get_rest_url( null, '/activitypub/1.0/users/' . intval( $post->get_post_author() ) . '/followers' ) ); if ( isset( $object['attributedTo'] ) ) { $this->actor = $object['attributedTo']; } + $mentions = apply_filters( 'activitypub_extract_mentions', array(), $post ); + foreach ( $mentions as $mention ) { + $this->cc[] = $mention; + } + $type = \strtolower( $this->type ); if ( isset( $object['id'] ) ) { diff --git a/tests/test-class-activitypub-activity-dispatcher.php b/tests/test-class-activitypub-activity-dispatcher.php new file mode 100644 index 0000000..a5dd7b9 --- /dev/null +++ b/tests/test-class-activitypub-activity-dispatcher.php @@ -0,0 +1,97 @@ + 1, + 'post_content' => '@alex hello', + ) + ); + + self::$users['https://example.com/alex'] = array( + 'url' => 'https://example.com/alex', + 'inbox' => 'https://example.com/alex/inbox', + 'name' => 'alex', + ); + + add_filter( + 'activitypub_extract_mentions', + function( $mentions ) { + $mentions[] = 'https://example.com/alex'; + return $mentions; + }, + 10 + ); + + $pre_http_request = new MockAction(); + add_filter( 'pre_http_request', array( $pre_http_request, 'filter' ), 10, 3 ); + + $activitypub_post = new \Activitypub\Model\Post( $post ); + \Activitypub\Activity_Dispatcher::send_post_activity( $activitypub_post ); + + $this->assertSame( 1, $pre_http_request->get_call_count() ); + $all_args = $pre_http_request->get_args(); + $first_call_args = $all_args[0]; + $this->assertEquals( 'https://example.com/alex/inbox', $first_call_args[2] ); + + remove_all_filters( 'activitypub_from_post_object' ); + remove_filter( 'pre_http_request', array( $pre_http_request, 'filter' ), 10 ); + } + + public function set_up() { + parent::set_up(); + + add_filter( 'pre_http_request', array( get_called_class(), 'pre_http_request' ), 10, 3 ); + add_filter( 'http_response', array( get_called_class(), 'http_response' ), 10, 3 ); + add_filter( 'http_request_host_is_external', array( get_called_class(), 'http_request_host_is_external' ), 10, 2 ); + add_filter( 'http_request_args', array( get_called_class(), 'http_request_args' ), 10, 2 ); + add_filter( 'pre_get_remote_metadata_by_actor', array( get_called_class(), 'pre_get_remote_metadata_by_actor' ), 10, 2 ); + + _delete_all_posts(); + } + + public function tear_down() { + remove_filter( 'pre_http_request', array( get_called_class(), 'pre_http_request' ) ); + remove_filter( 'http_response', array( get_called_class(), 'http_response' ) ); + remove_filter( 'http_request_host_is_external', array( get_called_class(), 'http_request_host_is_external' ) ); + remove_filter( 'http_request_args', array( get_called_class(), 'http_request_args' ) ); + remove_filter( 'pre_get_remote_metadata_by_actor', array( get_called_class(), 'pre_get_remote_metadata_by_actor' ) ); + parent::tear_down(); + } + + public static function pre_get_remote_metadata_by_actor( $pre, $actor ) { + if ( isset( self::$users[ $actor ] ) ) { + return self::$users[ $actor ]; + } + return $pre; + } + + public static function http_request_host_is_external( $in, $host ) { + if ( in_array( $host, array( 'example.com', 'example.org' ), true ) ) { + return true; + } + return $in; + } + public static function http_request_args( $args, $url ) { + if ( in_array( parse_url( $url, PHP_URL_HOST ), array( 'example.com', 'example.org' ), true ) ) { + $args['reject_unsafe_urls'] = false; + } + return $args; + } + public static function pre_http_request( $preempt, $request, $url ) { + return array( + 'headers' => array( + 'content-type' => 'text/json', + ), + 'body' => '', + 'response' => array( + 'code' => 202, + ), + ); + } + + public static function http_response( $response, $args, $url ) { + return $response; + } +} diff --git a/tests/test-class-activitypub-activity.php b/tests/test-class-activitypub-activity.php new file mode 100644 index 0000000..eff350d --- /dev/null +++ b/tests/test-class-activitypub-activity.php @@ -0,0 +1,32 @@ + 1, + 'post_content' => '@alex hello', + ) + ); + + add_filter( + 'activitypub_extract_mentions', + function( $mentions, $post ) { + $mentions[] = 'https://example.com/alex'; + return $mentions; + }, + 10, + 2 + ); + + $activitypub_post = new \Activitypub\Model\Post( $post ); + + $activitypub_activity = new \Activitypub\Model\Activity( 'Create', \Activitypub\Model\Activity::TYPE_FULL ); + $activitypub_activity->from_post( $activitypub_post ); + + $this->assertContains( \get_rest_url( null, '/activitypub/1.0/users/1/followers' ), $activitypub_activity->get_cc() ); + $this->assertContains( 'https://example.com/alex', $activitypub_activity->get_cc() ); + + remove_all_filters( 'activitypub_from_post_object' ); + \wp_trash_post( $post ); + } +}