implemented "follow"
This commit is contained in:
parent
c12bab0805
commit
2d84fcc600
15 changed files with 403 additions and 61 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,3 +2,4 @@
|
||||||
/vendor/
|
/vendor/
|
||||||
package-lock.json
|
package-lock.json
|
||||||
composer.lock
|
composer.lock
|
||||||
|
.DS_Store
|
||||||
|
|
|
@ -26,10 +26,10 @@ Implemented:
|
||||||
* profile pages (JSON representation)
|
* profile pages (JSON representation)
|
||||||
* custom links
|
* custom links
|
||||||
* functional inbox/outbox
|
* functional inbox/outbox
|
||||||
|
* follow (accept follows)
|
||||||
|
|
||||||
To implement:
|
To implement:
|
||||||
|
|
||||||
* follow (accept follows)
|
|
||||||
* share posts
|
* share posts
|
||||||
* share comments
|
* share comments
|
||||||
|
|
||||||
|
@ -55,6 +55,10 @@ To implement:
|
||||||
|
|
||||||
Project maintained on github at [pfefferle/wordpress-activitypub](https://github.com/pfefferle/wordpress-activitypub).
|
Project maintained on github at [pfefferle/wordpress-activitypub](https://github.com/pfefferle/wordpress-activitypub).
|
||||||
|
|
||||||
|
### 0.1.0 ###
|
||||||
|
|
||||||
|
* fully functional "follow" activity
|
||||||
|
|
||||||
### 0.0.2 ###
|
### 0.0.2 ###
|
||||||
|
|
||||||
* refactorins
|
* refactorins
|
||||||
|
|
|
@ -18,18 +18,31 @@
|
||||||
function activitypub_init() {
|
function activitypub_init() {
|
||||||
require_once dirname( __FILE__ ) . '/includes/class-activitypub-signature.php';
|
require_once dirname( __FILE__ ) . '/includes/class-activitypub-signature.php';
|
||||||
require_once dirname( __FILE__ ) . '/includes/class-activitypub-post.php';
|
require_once dirname( __FILE__ ) . '/includes/class-activitypub-post.php';
|
||||||
|
require_once dirname( __FILE__ ) . '/includes/class-db-activitypub-actor.php';
|
||||||
require_once dirname( __FILE__ ) . '/includes/functions.php';
|
require_once dirname( __FILE__ ) . '/includes/functions.php';
|
||||||
|
|
||||||
require_once dirname( __FILE__ ) . '/includes/class-activitypub.php';
|
require_once dirname( __FILE__ ) . '/includes/class-activitypub.php';
|
||||||
add_filter( 'template_include', array( 'Activitypub', 'render_json_template' ), 99 );
|
add_filter( 'template_include', array( 'Activitypub', 'render_json_template' ), 99 );
|
||||||
add_action( 'webfinger_user_data', array( 'Activitypub', 'add_webfinger_discovery' ), 10, 3 );
|
|
||||||
add_filter( 'query_vars', array( 'Activitypub', 'add_query_vars' ) );
|
add_filter( 'query_vars', array( 'Activitypub', 'add_query_vars' ) );
|
||||||
add_action( 'init', array( 'Activitypub', 'add_rewrite_endpoint' ) );
|
add_action( 'init', array( 'Activitypub', 'add_rewrite_endpoint' ) );
|
||||||
|
|
||||||
require_once dirname( __FILE__ ) . '/includes/class-activitypub-outbox.php';
|
|
||||||
require_once dirname( __FILE__ ) . '/includes/class-activitypub-inbox.php';
|
|
||||||
// Configure the REST API route
|
// Configure the REST API route
|
||||||
add_action( 'rest_api_init', array( 'Activitypub_Outbox', 'register_routes' ) );
|
require_once dirname( __FILE__ ) . '/includes/class-rest-activitypub-outbox.php';
|
||||||
add_action( 'rest_api_init', array( 'Activitypub_Inbox', 'register_routes' ) );
|
add_action( 'rest_api_init', array( 'Rest_Activitypub_Outbox', 'register_routes' ) );
|
||||||
|
|
||||||
|
require_once dirname( __FILE__ ) . '/includes/class-rest-activitypub-inbox.php';
|
||||||
|
add_action( 'rest_api_init', array( 'Rest_Activitypub_Inbox', 'register_routes' ) );
|
||||||
|
|
||||||
|
require_once dirname( __FILE__ ) . '/includes/class-rest-activitypub-followers.php';
|
||||||
|
add_action( 'rest_api_init', array( 'Rest_Activitypub_Followers', 'register_routes' ) );
|
||||||
|
|
||||||
|
require_once dirname( __FILE__ ) . '/includes/class-rest-activitypub-webfinger.php';
|
||||||
|
add_action( 'webfinger_user_data', array( 'Rest_Activitypub_Webfinger', 'add_webfinger_discovery' ), 10, 3 );
|
||||||
|
|
||||||
|
// Configure activities
|
||||||
|
require_once dirname( __FILE__ ) . '/includes/class-activitypub-activities.php';
|
||||||
|
add_action( 'activitypub_inbox_follow', array( 'Activitypub_Activities', 'accept' ), 10, 2 );
|
||||||
|
add_action( 'activitypub_inbox_follow', array( 'Activitypub_Activities', 'follow' ), 10, 2 );
|
||||||
|
add_action( 'activitypub_inbox_unfollow', array( 'Activitypub_Activities', 'unfollow' ), 10, 2 );
|
||||||
}
|
}
|
||||||
add_action( 'plugins_loaded', 'activitypub_init' );
|
add_action( 'plugins_loaded', 'activitypub_init' );
|
||||||
|
|
64
includes/class-activitypub-activities.php
Normal file
64
includes/class-activitypub-activities.php
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class Activitypub_Activities {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [accept description]
|
||||||
|
* @param [type] $data [description]
|
||||||
|
* @param [type] $author_id [description]
|
||||||
|
* @return [type] [description]
|
||||||
|
*/
|
||||||
|
public static function accept( $data, $author_id ) {
|
||||||
|
if ( ! array_key_exists( 'actor', $data ) ) {
|
||||||
|
return new WP_Error( 'activitypub_no_actor', __( 'No "Actor" found', 'activitypub' ), $metadata );
|
||||||
|
}
|
||||||
|
|
||||||
|
$inbox = Db_Activitypub_Actor::get_inbox_by_actor( $data['actor'] );
|
||||||
|
|
||||||
|
$activity = wp_json_encode(
|
||||||
|
array(
|
||||||
|
'@context' => array( 'https://www.w3.org/ns/activitystreams' ),
|
||||||
|
'type' => 'Accept',
|
||||||
|
'actor' => get_author_posts_url( $author_id ),
|
||||||
|
'object' => $data,
|
||||||
|
'to' => $data['actor'],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return activitypub_safe_remote_post( $inbox, $activity, $author_id );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [follow description]
|
||||||
|
* @param [type] $data [description]
|
||||||
|
* @param [type] $author_id [description]
|
||||||
|
* @return [type] [description]
|
||||||
|
*/
|
||||||
|
public static function follow( $data, $author_id ) {
|
||||||
|
if ( ! array_key_exists( 'actor', $data ) ) {
|
||||||
|
return new WP_Error( 'activitypub_no_actor', __( 'No "Actor" found', 'activitypub' ), $metadata );
|
||||||
|
}
|
||||||
|
|
||||||
|
Db_Activitypub_Actor::add_follower( $data['actor'], $author_id );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [unfollow description]
|
||||||
|
* @param [type] $data [description]
|
||||||
|
* @param [type] $author_id [description]
|
||||||
|
* @return [type] [description]
|
||||||
|
*/
|
||||||
|
public static function unfollow( $data, $author_id ) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [create description]
|
||||||
|
* @param [type] $data [description]
|
||||||
|
* @param [type] $author_id [description]
|
||||||
|
* @return [type] [description]
|
||||||
|
*/
|
||||||
|
public static function create( $data, $author_id ) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,38 +1,39 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Activitypub_Signature {
|
class Activitypub_Signature {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $user_id
|
* @param int $author_id
|
||||||
*
|
*
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public static function get_public_key( $user_id, $force = false ) {
|
public static function get_public_key( $author_id, $force = false ) {
|
||||||
$key = get_user_meta( $user_id, 'magic_sig_public_key' );
|
$key = get_user_meta( $author_id, 'magic_sig_public_key' );
|
||||||
|
|
||||||
if ( $key && ! $force ) {
|
if ( $key && ! $force ) {
|
||||||
return $key[0];
|
return $key[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
self::generate_key_pair( $user_id );
|
self::generate_key_pair( $author_id );
|
||||||
$key = get_user_meta( $user_id, 'magic_sig_public_key' );
|
$key = get_user_meta( $author_id, 'magic_sig_public_key' );
|
||||||
|
|
||||||
return $key[0];
|
return $key[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $user_id
|
* @param int $author_id
|
||||||
*
|
*
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public static function get_private_key( $user_id, $force = false ) {
|
public static function get_private_key( $author_id, $force = false ) {
|
||||||
$key = get_user_meta( $user_id, 'magic_sig_private_key' );
|
$key = get_user_meta( $author_id, 'magic_sig_private_key' );
|
||||||
|
|
||||||
if ( $key && ! $force ) {
|
if ( $key && ! $force ) {
|
||||||
return $key[0];
|
return $key[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
self::generate_key_pair( $user_id );
|
self::generate_key_pair( $author_id );
|
||||||
$key = get_user_meta( $user_id, 'magic_sig_private_key' );
|
$key = get_user_meta( $author_id, 'magic_sig_private_key' );
|
||||||
|
|
||||||
return $key[0];
|
return $key[0];
|
||||||
}
|
}
|
||||||
|
@ -40,9 +41,9 @@ class Activitypub_Signature {
|
||||||
/**
|
/**
|
||||||
* Generates the pair keys
|
* Generates the pair keys
|
||||||
*
|
*
|
||||||
* @param int $user_id
|
* @param int $author_id
|
||||||
*/
|
*/
|
||||||
public static function generate_key_pair( $user_id ) {
|
public static function generate_key_pair( $author_id ) {
|
||||||
$config = array(
|
$config = array(
|
||||||
'digest_alg' => 'sha512',
|
'digest_alg' => 'sha512',
|
||||||
'private_key_bits' => 2048,
|
'private_key_bits' => 2048,
|
||||||
|
@ -55,16 +56,16 @@ class Activitypub_Signature {
|
||||||
openssl_pkey_export( $key, $priv_key );
|
openssl_pkey_export( $key, $priv_key );
|
||||||
|
|
||||||
// private key
|
// private key
|
||||||
update_user_meta( $user_id, 'magic_sig_private_key', $priv_key );
|
update_user_meta( $author_id, 'magic_sig_private_key', $priv_key );
|
||||||
|
|
||||||
$detail = openssl_pkey_get_details( $key );
|
$detail = openssl_pkey_get_details( $key );
|
||||||
|
|
||||||
// public key
|
// public key
|
||||||
update_user_meta( $user_id, 'magic_sig_public_key', $detail['key'] );
|
update_user_meta( $author_id, 'magic_sig_public_key', $detail['key'] );
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function generate_signature( $user_id, $inbox ) {
|
public static function generate_signature( $author_id, $inbox, $date ) {
|
||||||
$key = self::get_private_key( $user_id );
|
$key = self::get_private_key( $author_id );
|
||||||
|
|
||||||
$url_parts = wp_parse_url( $inbox );
|
$url_parts = wp_parse_url( $inbox );
|
||||||
|
|
||||||
|
@ -73,7 +74,7 @@ class Activitypub_Signature {
|
||||||
|
|
||||||
// add path
|
// add path
|
||||||
if ( ! empty( $url_parts['path'] ) ) {
|
if ( ! empty( $url_parts['path'] ) ) {
|
||||||
$path .= $url_parts['path'];
|
$path = $url_parts['path'];
|
||||||
}
|
}
|
||||||
|
|
||||||
// add query
|
// add query
|
||||||
|
@ -81,11 +82,11 @@ class Activitypub_Signature {
|
||||||
$path .= '?' . $url_parts['query'];
|
$path .= '?' . $url_parts['query'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$date = gmdate( 'D, d M Y H:i:s T' );
|
|
||||||
$signed_string = "(request-target): post $path\nhost: $host\ndate: $date";
|
$signed_string = "(request-target): post $path\nhost: $host\ndate: $date";
|
||||||
|
|
||||||
$signature = null;
|
$signature = null;
|
||||||
openssl_sign( $signed_string, $signature, $key, OPENSSL_ALGO_SHA256 );
|
openssl_sign( $signed_string, $signature, $key, OPENSSL_ALGO_SHA256 );
|
||||||
|
$signature = base64_encode( $signature );
|
||||||
|
|
||||||
$key_id = get_author_posts_url( $author_id ) . '#main-key';
|
$key_id = get_author_posts_url( $author_id ) . '#main-key';
|
||||||
|
|
||||||
|
|
|
@ -54,23 +54,6 @@ class Activitypub {
|
||||||
return $json_template;
|
return $json_template;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Add WebFinger discovery links
|
|
||||||
*
|
|
||||||
* @param array $array the jrd array
|
|
||||||
* @param string $resource the WebFinger resource
|
|
||||||
* @param WP_User $user the WordPress user
|
|
||||||
*/
|
|
||||||
public static function add_webfinger_discovery( $array, $resource, $user ) {
|
|
||||||
$array['links'][] = array(
|
|
||||||
'rel' => 'self',
|
|
||||||
'type' => 'application/activity+json',
|
|
||||||
'href' => get_author_posts_url( $user->ID ),
|
|
||||||
);
|
|
||||||
|
|
||||||
return $array;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add the 'photos' query variable so WordPress
|
* Add the 'photos' query variable so WordPress
|
||||||
* won't mangle it.
|
* won't mangle it.
|
||||||
|
|
93
includes/class-db-activitypub-actor.php
Normal file
93
includes/class-db-activitypub-actor.php
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class Db_Activitypub_Actor {
|
||||||
|
/**
|
||||||
|
* [get_inbox_by_actor description]
|
||||||
|
* @param [type] $actor [description]
|
||||||
|
* @return [type] [description]
|
||||||
|
*/
|
||||||
|
public static function get_inbox_by_actor( $actor ) {
|
||||||
|
$metadata = self::get_metadata_by_actor( $actor );
|
||||||
|
|
||||||
|
if ( is_wp_error( $metadata ) ) {
|
||||||
|
return $metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( array_key_exists( 'inbox', $metadata ) ) {
|
||||||
|
return $metadata['inbox'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new WP_Error( 'activitypub_no_inbox', __( 'No "Inbox" found', 'activitypub' ), $metadata );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [get_metadata_by_actor description]
|
||||||
|
* @param [type] $actor [description]
|
||||||
|
* @return [type] [description]
|
||||||
|
*/
|
||||||
|
public static function get_metadata_by_actor( $actor ) {
|
||||||
|
$metadata = get_transient( 'activitypub_' . $actor );
|
||||||
|
|
||||||
|
if ( $metadata ) {
|
||||||
|
return $metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! wp_http_validate_url( $actor ) ) {
|
||||||
|
return new WP_Error( 'activitypub_no_valid_actor_url', __( 'The "actor" is no valid URL', 'activitypub' ), $actor );
|
||||||
|
}
|
||||||
|
|
||||||
|
$wp_version = get_bloginfo( 'version' );
|
||||||
|
|
||||||
|
$user_agent = apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . get_bloginfo( 'url' ) );
|
||||||
|
$args = array(
|
||||||
|
'timeout' => 100,
|
||||||
|
'limit_response_size' => 1048576,
|
||||||
|
'redirection' => 3,
|
||||||
|
'user-agent' => "$user_agent; ActivityPub",
|
||||||
|
'headers' => array( 'accept' => 'application/activity+json' ),
|
||||||
|
);
|
||||||
|
|
||||||
|
$response = wp_safe_remote_get( $actor, $args );
|
||||||
|
|
||||||
|
if ( is_wp_error( $response ) ) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
$metadata = wp_remote_retrieve_body( $response );
|
||||||
|
$metadata = json_decode( $metadata, true );
|
||||||
|
|
||||||
|
if ( ! $metadata ) {
|
||||||
|
return new WP_Error( 'activitypub_invalid_json', __( 'No valid JSON data', 'activitypub' ), $actor );
|
||||||
|
}
|
||||||
|
|
||||||
|
set_transient( 'activitypub_' . $actor, $metadata, WEEK_IN_SECONDS );
|
||||||
|
|
||||||
|
return $metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function add_follower( $actor, $author_id ) {
|
||||||
|
$followers = get_user_option( 'activitypub_followers', $author_id );
|
||||||
|
|
||||||
|
if ( ! is_array( $followers ) ) {
|
||||||
|
$followers = array( $actor );
|
||||||
|
} else {
|
||||||
|
$followers[] = $actor;
|
||||||
|
}
|
||||||
|
|
||||||
|
$followers = array_unique( $followers );
|
||||||
|
|
||||||
|
update_user_meta( $author_id, 'activitypub_followers', $followers );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function remove_follower( $actor, $author_id ) {
|
||||||
|
$followers = get_user_option( 'activitypub_followers', $author_id );
|
||||||
|
|
||||||
|
foreach ( $followers as $key => $value ) {
|
||||||
|
if ( $value === $actor) {
|
||||||
|
unset( $followers[$key] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update_user_meta( $author_id, 'activitypub_followers', $followers );
|
||||||
|
}
|
||||||
|
}
|
76
includes/class-rest-activitypub-followers.php
Normal file
76
includes/class-rest-activitypub-followers.php
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class Rest_Activitypub_Followers {
|
||||||
|
/**
|
||||||
|
* Register routes
|
||||||
|
*/
|
||||||
|
public static function register_routes() {
|
||||||
|
register_rest_route(
|
||||||
|
'activitypub/1.0', '/users/(?P<id>\d+)/followers', array(
|
||||||
|
array(
|
||||||
|
'methods' => WP_REST_Server::READABLE,
|
||||||
|
'callback' => array( 'Rest_Activitypub_Followers', 'get' ),
|
||||||
|
'args' => self::request_parameters(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function get( $request ) {
|
||||||
|
$author_id = $request->get_param( 'id' );
|
||||||
|
$author = get_user_by( 'ID', $author_id );
|
||||||
|
|
||||||
|
if ( ! $author ) {
|
||||||
|
return new WP_Error( 'rest_invalid_param', __( 'User not found', 'activitypub' ), array(
|
||||||
|
'status' => 404, 'params' => array(
|
||||||
|
'user_id' => __( 'User not found', 'activitypub' )
|
||||||
|
)
|
||||||
|
) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$page = $request->get_param( 'page', 0 );
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Action triggerd prior to the ActivityPub profile being created and sent to the client
|
||||||
|
*/
|
||||||
|
do_action( 'activitypub_outbox_pre' );
|
||||||
|
|
||||||
|
$json = new stdClass();
|
||||||
|
|
||||||
|
$json->{'@context'} = get_activitypub_context();
|
||||||
|
|
||||||
|
$followers = get_user_option( 'activitypub_followers', $author_id );
|
||||||
|
|
||||||
|
if ( ! is_array( $followers ) ) {
|
||||||
|
$followers = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$json->totlaItems = count( $followers );
|
||||||
|
$json->orderedItems = $followers;
|
||||||
|
|
||||||
|
$response = new WP_REST_Response( $json, 200 );
|
||||||
|
$response->header( 'Content-Type', 'application/activity+json' );
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The supported parameters
|
||||||
|
*
|
||||||
|
* @return array list of parameters
|
||||||
|
*/
|
||||||
|
public static function request_parameters() {
|
||||||
|
$params = array();
|
||||||
|
|
||||||
|
$params['page'] = array(
|
||||||
|
'type' => 'integer',
|
||||||
|
);
|
||||||
|
|
||||||
|
$params['id'] = array(
|
||||||
|
'required' => true,
|
||||||
|
'type' => 'integer',
|
||||||
|
);
|
||||||
|
|
||||||
|
return $params;
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,7 @@
|
||||||
*
|
*
|
||||||
* @author Matthias Pfefferle
|
* @author Matthias Pfefferle
|
||||||
*/
|
*/
|
||||||
class Activitypub_Inbox {
|
class Rest_Activitypub_Inbox {
|
||||||
/**
|
/**
|
||||||
* Register routes
|
* Register routes
|
||||||
*/
|
*/
|
||||||
|
@ -13,7 +13,7 @@ class Activitypub_Inbox {
|
||||||
'activitypub/1.0', '/inbox', array(
|
'activitypub/1.0', '/inbox', array(
|
||||||
array(
|
array(
|
||||||
'methods' => WP_REST_Server::EDITABLE,
|
'methods' => WP_REST_Server::EDITABLE,
|
||||||
'callback' => array( 'Activitypub_Inbox', 'global_inbox' ),
|
'callback' => array( 'Rest_Activitypub_Inbox', 'global_inbox' ),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -22,7 +22,7 @@ class Activitypub_Inbox {
|
||||||
'activitypub/1.0', '/users/(?P<id>\d+)/inbox', array(
|
'activitypub/1.0', '/users/(?P<id>\d+)/inbox', array(
|
||||||
array(
|
array(
|
||||||
'methods' => WP_REST_Server::EDITABLE,
|
'methods' => WP_REST_Server::EDITABLE,
|
||||||
'callback' => array( 'Activitypub_Inbox', 'user_inbox' ),
|
'callback' => array( 'Rest_Activitypub_Inbox', 'user_inbox' ),
|
||||||
'args' => self::request_parameters(),
|
'args' => self::request_parameters(),
|
||||||
),
|
),
|
||||||
)
|
)
|
|
@ -4,7 +4,7 @@
|
||||||
*
|
*
|
||||||
* @author Matthias Pfefferle
|
* @author Matthias Pfefferle
|
||||||
*/
|
*/
|
||||||
class Activitypub_Outbox {
|
class Rest_Activitypub_Outbox {
|
||||||
/**
|
/**
|
||||||
* Register routes
|
* Register routes
|
||||||
*/
|
*/
|
||||||
|
@ -13,7 +13,7 @@ class Activitypub_Outbox {
|
||||||
'activitypub/1.0', '/users/(?P<id>\d+)/outbox', array(
|
'activitypub/1.0', '/users/(?P<id>\d+)/outbox', array(
|
||||||
array(
|
array(
|
||||||
'methods' => WP_REST_Server::READABLE,
|
'methods' => WP_REST_Server::READABLE,
|
||||||
'callback' => array( 'Activitypub_Outbox', 'get' ),
|
'callback' => array( 'Rest_Activitypub_Outbox', 'get' ),
|
||||||
'args' => self::request_parameters(),
|
'args' => self::request_parameters(),
|
||||||
),
|
),
|
||||||
)
|
)
|
20
includes/class-rest-activitypub-webfinger.php
Normal file
20
includes/class-rest-activitypub-webfinger.php
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class Rest_Activitypub_Webfinger {
|
||||||
|
/**
|
||||||
|
* Add WebFinger discovery links
|
||||||
|
*
|
||||||
|
* @param array $array the jrd array
|
||||||
|
* @param string $resource the WebFinger resource
|
||||||
|
* @param WP_User $user the WordPress user
|
||||||
|
*/
|
||||||
|
public static function add_webfinger_discovery( $array, $resource, $user ) {
|
||||||
|
$array['links'][] = array(
|
||||||
|
'rel' => 'self',
|
||||||
|
'type' => 'application/activity+json',
|
||||||
|
'href' => get_author_posts_url( $user->ID ),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $array;
|
||||||
|
}
|
||||||
|
}
|
|
@ -38,3 +38,51 @@ function get_activitypub_context() {
|
||||||
|
|
||||||
return apply_filters( 'activitypub_json_context', $context );
|
return apply_filters( 'activitypub_json_context', $context );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( ! function_exists( 'base64_url_encode' ) ) {
|
||||||
|
/**
|
||||||
|
* Encode data
|
||||||
|
*
|
||||||
|
* @param string $input input text
|
||||||
|
*
|
||||||
|
* @return string the encoded text
|
||||||
|
*/
|
||||||
|
function base64_url_encode( $input ) {
|
||||||
|
return strtr( base64_encode( $input ), '+/', '-_' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( ! function_exists( 'base64_url_decode' ) ) {
|
||||||
|
/**
|
||||||
|
* Dencode data
|
||||||
|
*
|
||||||
|
* @param string $input input text
|
||||||
|
*
|
||||||
|
* @return string the decoded text
|
||||||
|
*/
|
||||||
|
function base64_url_decode( $input ) {
|
||||||
|
return base64_decode( strtr( $input, '-_', '+/' ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function activitypub_safe_remote_post( $url, $body, $author_id ) {
|
||||||
|
$date = gmdate( 'D, d M Y H:i:s T' );
|
||||||
|
$signature = Activitypub_Signature::generate_signature( $author_id, $url, $date );
|
||||||
|
|
||||||
|
$wp_version = get_bloginfo( 'version' );
|
||||||
|
$user_agent = apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . get_bloginfo( 'url' ) );
|
||||||
|
$args = array(
|
||||||
|
'timeout' => 100,
|
||||||
|
'limit_response_size' => 1048576,
|
||||||
|
'redirection' => 3,
|
||||||
|
'user-agent' => "$user_agent; ActivityPub",
|
||||||
|
'headers' => array(
|
||||||
|
'Accept' => 'application/activity+json',
|
||||||
|
'Content-Type' => 'application/activity+json',
|
||||||
|
'Signature' => $signature,
|
||||||
|
'Date' => $date,
|
||||||
|
),
|
||||||
|
'body' => $body,
|
||||||
|
);
|
||||||
|
|
||||||
|
$response = wp_safe_remote_post( $url, $args );
|
||||||
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: ActivityPub 0.0.2\n"
|
"Project-Id-Version: ActivityPub 0.0.2\n"
|
||||||
"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/activitypub\n"
|
"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/activitypub\n"
|
||||||
"POT-Creation-Date: 2018-12-01 20:22:43+00:00\n"
|
"POT-Creation-Date: 2018-12-07 23:01:55+00:00\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=utf-8\n"
|
"Content-Type: text/plain; charset=utf-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
@ -13,28 +13,47 @@ msgstr ""
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
"X-Generator: grunt-wp-i18n1.0.2\n"
|
"X-Generator: grunt-wp-i18n1.0.2\n"
|
||||||
|
|
||||||
#: includes/class-activitypub-inbox.php:47
|
#: includes/class-activitypub-activities.php:13
|
||||||
msgid "Invalid payload"
|
#: includes/class-activitypub-activities.php:39
|
||||||
|
msgid "No \"Actor\" found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-activitypub-inbox.php:62
|
#: includes/class-db-activitypub-actor.php:20
|
||||||
msgid "This method is not yet implemented"
|
msgid "No \"Inbox\" found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-activitypub-outbox.php:28
|
#: includes/class-db-activitypub-actor.php:36
|
||||||
#: includes/class-activitypub-outbox.php:30
|
msgid "The \"actor\" is no valid URL"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: includes/class-db-activitypub-actor.php:60
|
||||||
|
msgid "No valid JSON data"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: includes/class-rest-activitypub-followers.php:24
|
||||||
|
#: includes/class-rest-activitypub-followers.php:26
|
||||||
|
#: includes/class-rest-activitypub-outbox.php:28
|
||||||
|
#: includes/class-rest-activitypub-outbox.php:30
|
||||||
msgid "User not found"
|
msgid "User not found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/json-author.php:44
|
#: includes/class-rest-activitypub-inbox.php:47
|
||||||
|
msgid "Invalid payload"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: includes/class-rest-activitypub-inbox.php:62
|
||||||
|
msgid "This method is not yet implemented"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/json-author.php:48
|
||||||
msgid "Blog"
|
msgid "Blog"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/json-author.php:50
|
#: templates/json-author.php:58
|
||||||
msgid "Profile"
|
msgid "Profile"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/json-author.php:57
|
#: templates/json-author.php:69
|
||||||
msgid "Website"
|
msgid "Website"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -26,10 +26,10 @@ Implemented:
|
||||||
* profile pages (JSON representation)
|
* profile pages (JSON representation)
|
||||||
* custom links
|
* custom links
|
||||||
* functional inbox/outbox
|
* functional inbox/outbox
|
||||||
|
* follow (accept follows)
|
||||||
|
|
||||||
To implement:
|
To implement:
|
||||||
|
|
||||||
* follow (accept follows)
|
|
||||||
* share posts
|
* share posts
|
||||||
* share comments
|
* share comments
|
||||||
|
|
||||||
|
@ -55,6 +55,10 @@ To implement:
|
||||||
|
|
||||||
Project maintained on github at [pfefferle/wordpress-activitypub](https://github.com/pfefferle/wordpress-activitypub).
|
Project maintained on github at [pfefferle/wordpress-activitypub](https://github.com/pfefferle/wordpress-activitypub).
|
||||||
|
|
||||||
|
= 0.1.0 =
|
||||||
|
|
||||||
|
* fully functional "follow" activity
|
||||||
|
|
||||||
= 0.0.2 =
|
= 0.0.2 =
|
||||||
|
|
||||||
* refactorins
|
* refactorins
|
||||||
|
|
|
@ -7,7 +7,11 @@ $json->{'@context'} = get_activitypub_context();
|
||||||
$json->id = get_author_posts_url( $author_id );
|
$json->id = get_author_posts_url( $author_id );
|
||||||
$json->type = 'Person';
|
$json->type = 'Person';
|
||||||
$json->name = get_the_author_meta( 'display_name', $author_id );
|
$json->name = get_the_author_meta( 'display_name', $author_id );
|
||||||
$json->summary = esc_html( get_the_author_meta( 'description', $author_id ) );
|
$json->summary = html_entity_decode(
|
||||||
|
get_the_author_meta( 'description', $author_id ),
|
||||||
|
ENT_QUOTES,
|
||||||
|
'UTF-8'
|
||||||
|
);
|
||||||
$json->preferredUsername = get_the_author_meta( 'login', $author_id ); // phpcs:ignore
|
$json->preferredUsername = get_the_author_meta( 'login', $author_id ); // phpcs:ignore
|
||||||
$json->url = get_author_posts_url( $author_id );
|
$json->url = get_author_posts_url( $author_id );
|
||||||
$json->icon = array(
|
$json->icon = array(
|
||||||
|
@ -42,20 +46,32 @@ $json->attachment = array();
|
||||||
$json->attachment[] = array(
|
$json->attachment[] = array(
|
||||||
'type' => 'PropertyValue',
|
'type' => 'PropertyValue',
|
||||||
'name' => __( 'Blog', 'activitypub' ),
|
'name' => __( 'Blog', 'activitypub' ),
|
||||||
'value' => esc_html( '<a rel="me" title="' . esc_attr( home_url( '/' ) ) . '" target="_blank" href="' . home_url( '/' ) . '">' . wp_parse_url( home_url( '/' ), PHP_URL_HOST ) . '</a>' ),
|
'value' => html_entity_decode(
|
||||||
|
'<a rel="me" title="' . esc_attr( home_url( '/' ) ) . '" target="_blank" href="' . home_url( '/' ) . '">' . wp_parse_url( home_url( '/' ), PHP_URL_HOST ) . '</a>',
|
||||||
|
ENT_QUOTES,
|
||||||
|
'UTF-8'
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
$json->attachment[] = array(
|
$json->attachment[] = array(
|
||||||
'type' => 'PropertyValue',
|
'type' => 'PropertyValue',
|
||||||
'name' => __( 'Profile', 'activitypub' ),
|
'name' => __( 'Profile', 'activitypub' ),
|
||||||
'value' => esc_html( '<a rel="me" title="' . esc_attr( get_author_posts_url( $author_id ) ) . '" target="_blank" href="' . get_author_posts_url( $author_id ) . '">' . wp_parse_url( get_author_posts_url( $author_id ), PHP_URL_HOST ) . '</a>' ),
|
'value' => html_entity_decode(
|
||||||
|
'<a rel="me" title="' . esc_attr( get_author_posts_url( $author_id ) ) . '" target="_blank" href="' . get_author_posts_url( $author_id ) . '">' . wp_parse_url( get_author_posts_url( $author_id ), PHP_URL_HOST ) . '</a>',
|
||||||
|
ENT_QUOTES,
|
||||||
|
'UTF-8'
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if ( get_the_author_meta( 'user_url', $author_id ) ) {
|
if ( get_the_author_meta( 'user_url', $author_id ) ) {
|
||||||
$json->attachment[] = array(
|
$json->attachment[] = array(
|
||||||
'type' => 'PropertyValue',
|
'type' => 'PropertyValue',
|
||||||
'name' => __( 'Website', 'activitypub' ),
|
'name' => __( 'Website', 'activitypub' ),
|
||||||
'value' => esc_html( '<a rel="me" title="' . esc_attr( get_the_author_meta( 'user_url', $author_id ) ) . '" target="_blank" href="' . get_the_author_meta( 'user_url', $author_id ) . '">' . wp_parse_url( get_the_author_meta( 'user_url', $author_id ), PHP_URL_HOST ) . '</a>' ),
|
'value' => html_entity_decode(
|
||||||
|
'<a rel="me" title="' . esc_attr( get_the_author_meta( 'user_url', $author_id ) ) . '" target="_blank" href="' . get_the_author_meta( 'user_url', $author_id ) . '">' . wp_parse_url( get_the_author_meta( 'user_url', $author_id ), PHP_URL_HOST ) . '</a>',
|
||||||
|
ENT_QUOTES,
|
||||||
|
'UTF-8'
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue