[WIP] Implement WP->Mastodon comment syncing

This commit is contained in:
Jeremy Dormitzer 2018-10-23 18:52:36 -04:00
parent d133be2e15
commit 87aae44c72
5 changed files with 169 additions and 11 deletions

View File

@ -4,13 +4,31 @@ namespace pterotype\comments;
require_once plugin_dir_path( __FILE__ ) . '../server/activities/create.php';
require_once plugin_dir_path( __FILE__ ) . '../server/activities/update.php';
require_once plugin_dir_path( __FILE__ ) . '../server/activities/delete.php';
require_once plugin_dir_path( __FILE__ ) . '../server/objects.php';
function handle_transition_comment_status( $old_status, $new_status, $comment ) {
function handle_comment_post( $comment_id, $comment_approved ) {
xdebug_break();
if ( $comment_approved ) {
$comment = \get_comment( $comment_id );
handle_transition_comment_status( 'approve', 'nonexistent', $comment );
}
}
function handle_edit_comment( $comment_id ) {
$comment = \get_comment( $comment_id );
if ( $comment->comment_approved ) {
handle_transition_comment_status( 'approve', 'approve', $comment );
}
}
function handle_transition_comment_status( $new_status, $old_status, $comment ) {
xdebug_break();
// This creates a new commenter actor if necessary
$actor_slug = get_comment_actor_slug( $comment );
$actor_outbox = get_rest_url(
null, sprintf( 'pterotype/v1/actor/%s/outbox', $actor_slug )
);
$comment_object = comment_to_object( $comment );
$comment_object = comment_to_object( $comment, $actor_slug );
$activity = null;
if ( $new_status == 'approve' && $old_status != 'approve' ) {
// Create
@ -24,7 +42,7 @@ function handle_transition_comment_status( $old_status, $new_status, $comment )
}
if ( $activity && ! is_wp_error( $activity ) ) {
$followers = \pterotype\followers\get_followers_collection( $actor_slug );
$activity['to'] = get_comment_to( $comment, $followers['id'] );
$activity['to'] = get_comment_to( $comment_object, $followers['id'] );
$server = rest_get_server();
$request = \WP_REST_Request::from_url( $actor_outbox );
$request->set_method( 'POST' );
@ -43,15 +61,50 @@ function get_comment_actor_slug( $comment ) {
}
function get_comment_user_actor_slug( $user_id ) {
if ( \user_can( $user_id, 'publish_posts' ) ) {
return PTEROTYPE_BLOG_ACTOR_SLUG;
} else {
$user = \get_userdata( $user_id );
return $user->user_nicename;
}
}
function get_comment_email_actor_slug( $email_address ) {
$slug = \pterotype\actors\upsert_commenter_actor( $email_address );
return $slug;
}
function comment_to_object( $comment ) {
function comment_to_object( $comment, $actor_slug ) {
$post = \get_post( $comment->comment_post_ID );
\setup_postdata( $post );
$post_permalink = \get_permalink( $post );
$post_object = \pterotype\objects\get_object_by( 'url', $post_permalink );
$inReplyTo = $post_object['id'];
if ( $comment->comment_parent !== '0' ) {
$parent_comment = \get_comment( $comment->comment_parent );
$parent_object = \pterotype\objects\get_object_by(
'url', \get_comment_link( $parent_comment )
);
if ( $parent_object ) {
$inReplyTo = $parent_object['id'];
}
}
$link = \get_comment_link( $comment );
$object = array(
'@context' => array( 'https://www.w3.org/ns/activitystreams' ),
'type' => 'Note',
'content' => $comment->comment_content,
'attributedTo' => get_rest_url(
null, sprintf( '/pterotype/v1/actor/%s', $actor_slug )
),
'url' => $link,
'inReplyTo' => $inReplyTo,
);
$existing = \pterotype\objects\get_object_by( 'url', $link );
if ( $existing ) {
$object['id'] = $existing['id'];
}
return $object;
}
function get_comment_to( $comment, $followers_id ) {
@ -59,7 +112,34 @@ function get_comment_to( $comment, $followers_id ) {
'https://www.w3.org/ns/activitystreams#Public',
$followers_id,
);
// TODO traverse comment reply chain to retrieve others to address to
$to = array_unique( array_merge( $to, traverse_reply_chain( $comment ) ) );
return $to;
}
function traverse_reply_chain( $comment ) {
return traverse_reply_chain_helper( $comment, 0, array() );
}
function traverse_reply_chain_helper( $object, $depth, $acc ) {
if ( $depth === 50 ) {
return $acc;
}
if ( array_key_exists( 'inReplyTo', $object ) ) {
return $acc;
}
$parent = \pterotype\util\dereference_object( $object['inReplyTo'] );
$recipients = array();
foreach( array( 'to', 'bto', 'cc', 'bcc', 'audience' ) as $field ) {
if ( array_key_exists( $field, $parent ) ) {
$new_recipients = $parent[$field];
if ( ! is_array( $new_recipients ) ) {
$new_recipients = array( $new_recipients );
}
$recipients = array_unique( array_merge( $recipients, $new_recipients ) );
}
}
return traverse_reply_chain_helper(
$parent, $depth + 1, array_unique( array_merge( $acc, $recipients ) )
);
}
?>

View File

@ -7,6 +7,7 @@ require_once plugin_dir_path( __FILE__ ) . 'server/actors.php';
require_once plugin_dir_path( __FILE__ ) . 'schema.php';
require_once plugin_dir_path( __FILE__ ) . 'server/webfinger.php';
require_once plugin_dir_path( __FILE__ ) . 'client/posts.php';
require_once plugin_dir_path( __FILE__ ) . 'client/comments.php';
require_once plugin_dir_path( __FILE__ ) . 'server/async.php';
add_action( 'rest_api_init', function() {
@ -36,5 +37,10 @@ add_action( 'parse_request', '\pterotype\webfinger\parse_request', 111 );
add_filter( 'query_vars', '\pterotype\webfinger\query_vars' );
add_action( 'well_known_webfinger', '\pterotype\webfinger\handle' );
add_action( 'transition_post_status', '\pterotype\posts\handle_post_status_change', 10, 3 );
add_action(
'transition_comment_status', '\pterotype\comments\handle_transition_comment_status', 10, 3
);
add_action( 'comment_post', '\pterotype\comments\handle_comment_post', 10, 2 );
add_action( 'edit_comment', '\pterotype\comments\handle_edit_comment', 10, 1 );
add_action( 'template_redirect', '\pterotype\api\handle_non_api_requests' );
?>

View File

@ -180,6 +180,7 @@ function migration_0_0_1() {
}
function migration_1_1_0() {
global $wpdb;
$wpdb->query(
"
CREATE TABLE {$wpdb->prefix}pterotype_comments (

View File

@ -40,12 +40,54 @@ function get_actor_from_row( $row ) {
$user = get_user_by( 'slug', $row->slug );
return get_user_actor( $user );
case 'commenter':
return new \WP_Error(
'not_implemented', __( 'Commenter actors not yet implemented', 'pterotype' )
);
return get_commenter_actor( $row->slug );
}
}
function get_commenter_actor( $slug ) {
$actor_id = get_actor_id( $slug );
$email_address = str_replace( '[AT]', '@', $slug );
$actor = array(
'@context' => array(
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1',
),
'type' => 'Person',
'id' => get_rest_url(
null, sprintf( '/pterotype/v1/actor/%s', $slug )
),
'following' => get_rest_url(
null, sprintf( '/pterotype/v1/actor/%s/following', $slug )
),
'followers' => get_rest_url(
null, sprintf( '/pterotype/v1/actor/%s/followers', $slug )
),
'liked' => get_rest_url(
null, sprintf( '/pterotype/v1/actor/%s/liked', $slug )
),
'inbox' => get_rest_url(
null, sprintf( '/pterotype/v1/actor/%s/inbox', $slug )
),
'outbox' => get_rest_url(
null, sprintf( '/pterotype/v1/actor/%s/outbox', $slug )
),
'name' => $email_address,
// TODO in the future, make this configurable, both here and in the Webfinger handler
'preferredUsername' => $email_address,
'publicKey' => array(
'id' => get_rest_url(
null, sprintf( '/pterotype/v1/actor/%s#publicKey', $slug )
),
'owner' => get_rest_url(
null, sprintf( '/pterotype/v1/actor/%s', $slug )
),
'publicKeyPem' => \pterotype\pgp\get_public_key( $actor_id ),
),
);
// TODO retrieve commenter name, url, and icon
return $actor;
}
function get_blog_actor() {
$actor_id = get_actor_id( PTEROTYPE_BLOG_ACTOR_SLUG );
$actor = array(
@ -175,4 +217,24 @@ function create_actor( $slug, $type ) {
}
return $res->object;
}
function upsert_commenter_actor( $email_address ) {
global $wpdb;
$slug = str_replace( '@', '[AT]', $email_address );
$existing = $wpdb->get_row( $wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}pterotype_actors WHERE slug = %s",
$slug
) );
if ( $existing !== null ) {
return $slug;
}
create_actor( $slug, 'commenter' );
$actor_id = get_actor_id( $slug );
$keys_created = \pterotype\pgp\get_public_key( $actor_id );
if ( ! $keys_created ) {
$keys = \pterotype\pgp\gen_key( $slug );
\pterotype\pgp\persist_key( $actor_id, $keys['publickey'], $keys['privatekey'] );
}
return $slug;
}
?>

View File

@ -228,6 +228,15 @@ function get_objects_by( $field, $value ) {
);
}
function get_object_by( $field, $value ) {
$objects = get_objects_by( $field, $value );
if ( count( $objects ) === 0 ) {
return null;
} else {
return $objects[0];
}
}
function delete_object( $object ) {
global $wpdb;
$object = \pterotype\util\dereference_object( $object );