0.1.0
* added basic WebFinger support * added basic NodeInfo support * fully functional "follow" activity * send new posts to your followers * receive comments from your followers
This commit is contained in:
parent
b063220d1c
commit
4e653f5f75
18 changed files with 825 additions and 263 deletions
|
@ -17,7 +17,7 @@ env:
|
|||
- WP_TRAVISCI=travis:phpunit
|
||||
- SVN_REPO: https://plugins.svn.wordpress.org/activitypub/
|
||||
- GH_REF: https://github.com/pfefferle/wordpress-activitypub.git
|
||||
- secure: k6uZL/zVYvuSsG4tUMvVC6+DTYKSueOYj6j9csKSa08EzPl0JLPoNN/5MPihi+zZkBPLL+KjBgUUYfBtdcOh/xwPb+lilg5UnvM7TKQEG583RtBbohADvahQHN4mxZb6yBvrmvbB5jNtf+3L4RcOgdONEb2CpGo5n8RRM3MXxF4WSn9s+F+P0fvWCpcZnM9yqgbTJNUaZHw4tRWQ7eYZ5kFxSxKSnZd5+149UAh2YcKjA+ix3rrK0ClOlGaMVZz+SV4/qoNwamxMTMAQ6Be1wIelXz0n92FIonw6euDfBSgNLg+WLiAYoqaM+tEluLV1DRcx5TLUfmAOGli6pEfqL6XZxf8iZheMtn5Ir0nR4vLbOUKEojqEpLwmlUjiTN6RSZbPMquBNz/lOEQd9S/EzPLrtvlBRYAq0EmI62KtXVG+vHta2TTF+LS0caXjVZaHcpF4SYmuG5WyR6d0KpnVw6czvXu7hyq2sNz6lj1hUt15pPZO8tFkJFTs87pOBfEj/GnjIE4Iab2HA/HgdWqFpB+5ZAWH9QDIa9c3+QUQQf2qA7Z1yS0c5SBn/TE+0O3yomyBTD63Zc7gNG2qqw+THql5fzG3iGV5M6db+yTY0INfsJYuRjQXpn9Q35ZTxgXEEvu7naHh162wa14K18zzXoVEjhywOoW3X1Qiz6VFK8s=
|
||||
- secure: "WaeBLo0MDuK4X7NQvk5Ie5BVnexHtyDfHAV8v7dB8B67d8GCWy9K5I54jTECNRUC+CecMakLq5DOfn+ThtWdkJmoJKnGNFp8ZrWkMsfVJRi3CDED2HkccOrxkmXBj8Z6A8jZjcfVNrEmq/6697xVNRGeaS08l9rokh7pyb8INWY="
|
||||
matrix:
|
||||
include:
|
||||
- php: 7.2
|
||||
|
|
28
README.md
28
README.md
|
@ -3,7 +3,7 @@
|
|||
**Donate link:** https://notiz.blog/donate/
|
||||
**Tags:** OStatus, fediverse, activitypub, activitystream
|
||||
**Requires at least:** 4.7
|
||||
**Tested up to:** 5.0
|
||||
**Tested up to:** 5.0.2
|
||||
**Stable tag:** 0.1.0
|
||||
**Requires PHP:** 5.6
|
||||
**License:** MIT
|
||||
|
@ -13,9 +13,9 @@ The ActivityPub protocol is a decentralized social networking protocol based upo
|
|||
|
||||
## Description ##
|
||||
|
||||
This is **BETA** software, see the FAQ to see what works and what still needs to be implemented or is in planning.
|
||||
This is **BETA** software, see the FAQ to see the current feature set or rather what is still planned.
|
||||
|
||||
This plugin enables ActivityPub for your Blog. Your readers will be able to follow your Blogposts on Mastodon and other Federated Plattforms that support ActivityPub.
|
||||
This plugin implements the ActivityPub for your Blog. Your readers will be able to follow your Blogposts on Mastodon and other Federated Plattforms that support ActivityPub.
|
||||
|
||||
## Frequently Asked Questions ##
|
||||
|
||||
|
@ -26,30 +26,34 @@ Implemented:
|
|||
* profile pages (JSON representation)
|
||||
* custom links
|
||||
* functional inbox/outbox
|
||||
* follow (accept follows)
|
||||
* follow (accept follows)
|
||||
* share posts
|
||||
* receive comments/reactions
|
||||
|
||||
To implement:
|
||||
|
||||
* share posts
|
||||
* share comments
|
||||
* signature verification
|
||||
* better WordPress integration
|
||||
* better configuration possibilities
|
||||
* threaded comments support
|
||||
|
||||
### Why does the plugin not support ...? ###
|
||||
|
||||
*ActivityPub* extends WordPress with some fediverse features, but it does not compete with plattforms like Friendi.ca or Mastodon. If you want to have a **decentralized social network**, please use [Mastodon](https://joinmastodon.org/) or [GNU.social](https://gnu.io/social/).
|
||||
|
||||
### What are the differences to Pterotype? ###
|
||||
### What are the differences between this plugin and Pterotype? ###
|
||||
|
||||
**PHP Version**
|
||||
|
||||
*ActivityPub* needs PHP 5.6, *Pterotype* requires 7.2.x
|
||||
*This plugin* needs PHP 5.6, *Pterotype* requires 7.2.x
|
||||
|
||||
**Compatibility**
|
||||
|
||||
*ActivityPub* is compatible with OStatus and the IndieWeb movement. *Pterotype* implements its own WebFinger endpoint, that is not compatible with the [WebFinger plugin](https://wordpress.org/plugins/webfinger/).
|
||||
*This plugin* is compatible with OStatus and the IndieWeb movement. *Pterotype* implements for example its own WebFinger endpoint, which is not compatible with the [WebFinger plugin](https://wordpress.org/plugins/webfinger/).
|
||||
|
||||
**Custom tables**
|
||||
|
||||
*Pterotype* creates/uses a bunch of custom tables, *ActivityPub* only uses the native tables and adds as few meta data as possible.
|
||||
*Pterotype* creates/uses a bunch of custom tables, *this plugin* only uses the native tables and adds as few meta data as possible.
|
||||
|
||||
## Changelog ##
|
||||
|
||||
|
@ -59,7 +63,9 @@ Project maintained on github at [pfefferle/wordpress-activitypub](https://github
|
|||
|
||||
* added basic WebFinger support
|
||||
* added basic NodeInfo support
|
||||
* fully functional "follow" activity
|
||||
* fully functional "follow" activity
|
||||
* send new posts to your followers
|
||||
* receive comments from your followers
|
||||
|
||||
### 0.0.2 ###
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
function activitypub_init() {
|
||||
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-activity.php';
|
||||
require_once dirname( __FILE__ ) . '/includes/class-db-activitypub-followers.php';
|
||||
require_once dirname( __FILE__ ) . '/includes/functions.php';
|
||||
|
||||
|
@ -29,9 +30,16 @@ function activitypub_init() {
|
|||
// Configure the REST API route
|
||||
require_once dirname( __FILE__ ) . '/includes/class-rest-activitypub-outbox.php';
|
||||
add_action( 'rest_api_init', array( 'Rest_Activitypub_Outbox', 'register_routes' ) );
|
||||
add_action( 'activitypub_send_post_activity', array( 'Rest_Activitypub_Outbox', 'send_post_activity' ) );
|
||||
|
||||
require_once dirname( __FILE__ ) . '/includes/class-rest-activitypub-inbox.php';
|
||||
add_action( 'rest_api_init', array( 'Rest_Activitypub_Inbox', 'register_routes' ) );
|
||||
//add_filter( 'rest_pre_serve_request', array( 'Rest_Activitypub_Inbox', 'serve_request' ), 11, 4 );
|
||||
add_action( 'activitypub_inbox_follow', array( 'Rest_Activitypub_Inbox', 'handle_follow' ), 10, 2 );
|
||||
add_action( 'activitypub_inbox_unfollow', array( 'Rest_Activitypub_Inbox', 'handle_unfollow' ), 10, 2 );
|
||||
add_action( 'activitypub_inbox_like', array( 'Rest_Activitypub_Inbox', 'handle_reaction' ), 10, 2 );
|
||||
add_action( 'activitypub_inbox_announce', array( 'Rest_Activitypub_Inbox', 'handle_reaction' ), 10, 2 );
|
||||
add_action( 'activitypub_inbox_create', array( 'Rest_Activitypub_Inbox', 'handle_create' ), 10, 2 );
|
||||
|
||||
require_once dirname( __FILE__ ) . '/includes/class-rest-activitypub-followers.php';
|
||||
add_action( 'rest_api_init', array( 'Rest_Activitypub_Followers', 'register_routes' ) );
|
||||
|
@ -45,11 +53,17 @@ function activitypub_init() {
|
|||
add_filter( 'nodeinfo_data', array( 'Rest_Activitypub_Nodeinfo', 'add_nodeinfo_discovery' ), 10, 2 );
|
||||
add_filter( 'nodeinfo2_data', array( 'Rest_Activitypub_Nodeinfo', 'add_nodeinfo2_discovery' ), 10 );
|
||||
|
||||
// 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_post_type_support( 'post', 'activitypub' );
|
||||
add_post_type_support( 'page', 'activitypub' );
|
||||
|
||||
$post_types = get_post_types_by_support( 'activitypub' );
|
||||
foreach ( $post_types as $post_type ) {
|
||||
add_action( 'publish_' . $post_type, array( 'Activitypub', 'schedule_post_activity' ) );
|
||||
}
|
||||
|
||||
require_once dirname( __FILE__ ) . '/includes/class-activitypub-admin.php';
|
||||
add_action( 'admin_menu', array( 'Activitypub_Admin', 'admin_menu' ) );
|
||||
add_action( 'admin_init', array( 'Activitypub_Admin', 'register_settings' ) );
|
||||
}
|
||||
add_action( 'plugins_loaded', 'activitypub_init' );
|
||||
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
<?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_Followers::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_Followers::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 ) {
|
||||
|
||||
}
|
||||
}
|
84
includes/class-activitypub-activity.php
Normal file
84
includes/class-activitypub-activity.php
Normal file
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
/**
|
||||
* ActivityPub Post Class
|
||||
*
|
||||
* @author Matthias Pfefferle
|
||||
*/
|
||||
class Activitypub_Activity {
|
||||
private $context = array( 'https://www.w3.org/ns/activitystreams' );
|
||||
private $published = '';
|
||||
private $id = '';
|
||||
private $type = 'Create';
|
||||
private $actor = '';
|
||||
private $to = array( 'https://www.w3.org/ns/activitystreams#Public' );
|
||||
private $cc = array( 'https://www.w3.org/ns/activitystreams#Public' );
|
||||
private $object = null;
|
||||
|
||||
const TYPE_SIMPLE = 'simple';
|
||||
const TYPE_FULL = 'full';
|
||||
const TYPE_NONE = 'none';
|
||||
|
||||
public function __construct( $type = 'Create', $context = self::TYPE_SIMPLE ) {
|
||||
if ( 'none' === $context ) {
|
||||
$this->context = null;
|
||||
} elseif ( 'full' === $context ) {
|
||||
$this->context = get_activitypub_context();
|
||||
}
|
||||
|
||||
$this->type = ucfirst( $type );
|
||||
$this->published = date( 'Y-m-d\TH:i:s\Z', strtotime( 'now' ) );
|
||||
}
|
||||
|
||||
public function __call( $method, $params ) {
|
||||
$var = strtolower( substr( $method, 4 ) );
|
||||
|
||||
if ( strncasecmp( $method, 'get', 3 ) === 0 ) {
|
||||
return $this->$var;
|
||||
}
|
||||
|
||||
if ( strncasecmp( $method, 'set', 3 ) === 0 ) {
|
||||
$this->$var = $params[0];
|
||||
}
|
||||
}
|
||||
|
||||
public function from_post( $object ) {
|
||||
$this->object = $object;
|
||||
$this->published = $object['published'];
|
||||
$this->actor = $object['attributedTo'];
|
||||
$this->id = $object['id'];
|
||||
}
|
||||
|
||||
public function from_comment( $object ) {
|
||||
|
||||
}
|
||||
|
||||
public function to_array() {
|
||||
$array = get_object_vars( $this );
|
||||
|
||||
if ( $this->context ) {
|
||||
$array = array( '@context' => $this->context ) + $array;
|
||||
}
|
||||
|
||||
unset( $array['context'] );
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
public function to_json() {
|
||||
return wp_json_encode( $this->to_array() );
|
||||
}
|
||||
|
||||
public function to_simple_array() {
|
||||
return array(
|
||||
'@context' => $this->context,
|
||||
'type' => $this->type,
|
||||
'actor' => $this->actor,
|
||||
'object' => $this->object,
|
||||
'to' => $this->to,
|
||||
);
|
||||
}
|
||||
|
||||
public function to_simple_json() {
|
||||
return wp_json_encode( $this->to_simple_array() );
|
||||
}
|
||||
}
|
54
includes/class-activitypub-admin.php
Normal file
54
includes/class-activitypub-admin.php
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
/**
|
||||
* ActivityPub Admin Class
|
||||
*/
|
||||
class Activitypub_Admin {
|
||||
/**
|
||||
* Add admin menu entry
|
||||
*/
|
||||
public static function admin_menu() {
|
||||
$settings_page = add_options_page(
|
||||
'ActivityPub',
|
||||
'ActivityPub',
|
||||
'manage_options',
|
||||
'activitypub',
|
||||
array( 'Activitypub_Admin', 'settings_page' )
|
||||
);
|
||||
|
||||
add_action( 'load-' . $settings_page, array( 'Activitypub_Admin', 'add_help_tab' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Load settings page
|
||||
*/
|
||||
public static function settings_page() {
|
||||
load_template( dirname( __FILE__ ) . '/../templates/settings-page.php' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register PubSubHubbub settings
|
||||
*/
|
||||
public static function register_settings() {
|
||||
register_setting( 'activitypub', 'activitypub_feed_use_excerpt' );
|
||||
}
|
||||
|
||||
public static function add_help_tab() {
|
||||
get_current_screen()->add_help_tab(
|
||||
array(
|
||||
'id' => 'overview',
|
||||
'title' => __( 'Overview', 'activitypub' ),
|
||||
'content' =>
|
||||
'<p>' . __( 'ActivityPub is a decentralized social networking protocol based on the ActivityStreams 2.0 data format. ActivityPub is an official W3C recommended standard published by the W3C Social Web Working Group. It provides a client to server API for creating, updating and deleting content, as well as a federated server to server API for delivering notifications and subscribing to content.', 'activitypub' ) . '</p>',
|
||||
)
|
||||
);
|
||||
|
||||
get_current_screen()->set_help_sidebar(
|
||||
'<p><strong>' . __( 'For more information:', 'activitypub' ) . '</strong></p>' .
|
||||
'<p>' . __( '<a href="https://activitypub.rocks/">Test Suite</a>', 'activitypub' ) . '</p>' .
|
||||
'<p>' . __( '<a href="https://www.w3.org/TR/activitypub/">W3C Spec</a>', 'activitypub' ) . '</p>' .
|
||||
'<p>' . __( '<a href="https://github.com/pfefferle/wordpress-activitypub/issues">Give us feedback</a>', 'activitypub' ) . '</p>' .
|
||||
'<hr />' .
|
||||
'<p>' . __( '<a href="https://notiz.blog/donate">Donate</a>', 'activitypub' ) . '</p>'
|
||||
);
|
||||
}
|
||||
}
|
|
@ -8,47 +8,129 @@ class Activitypub_Post {
|
|||
private $post;
|
||||
|
||||
public function __construct( $post = null ) {
|
||||
if ( ! $post ) {
|
||||
$post = get_post();
|
||||
}
|
||||
|
||||
$this->post = $post;
|
||||
$this->post = get_post( $post );
|
||||
}
|
||||
|
||||
public function to_json_array( $with_context = false ) {
|
||||
public function get_post() {
|
||||
return $this->post;
|
||||
}
|
||||
|
||||
public function get_post_author() {
|
||||
return $this->post->post_author;
|
||||
}
|
||||
|
||||
public function to_array() {
|
||||
$post = $this->post;
|
||||
|
||||
$json = new stdClass();
|
||||
setup_postdata( $post );
|
||||
|
||||
if ( $with_context ) {
|
||||
$json->{'@context'} = get_activitypub_context();
|
||||
}
|
||||
|
||||
$json->published = date( 'Y-m-d\TH:i:s\Z', strtotime( $post->post_date ) );
|
||||
$json->id = $post->guid . '&activitypub';
|
||||
$json->type = 'Create';
|
||||
$json->actor = get_author_posts_url( $post->post_author );
|
||||
$json->to = array( 'https://www.w3.org/ns/activitystreams#Public' );
|
||||
$json->cc = array( 'https://www.w3.org/ns/activitystreams#Public' );
|
||||
|
||||
$json->object = array(
|
||||
'id' => $post->guid,
|
||||
$array = array(
|
||||
'id' => get_permalink( $post ),
|
||||
'type' => $this->get_object_type(),
|
||||
'published' => date( 'Y-m-d\TH:i:s\Z', strtotime( $post->post_date ) ),
|
||||
'attributedTo' => get_author_posts_url( $post->post_author ),
|
||||
'summary' => apply_filters( 'the_excerpt', get_post_field( 'post_excerpt', $post->ID ) ),
|
||||
'inReplyTo' => null,
|
||||
'content' => apply_filters( 'the_content', get_post_field( 'post_content', $post->ID ) ),
|
||||
'contentMap' => array(
|
||||
strstr( get_locale(), '_', true ) => apply_filters( 'the_content', get_post_field( 'post_content', $post->ID ) ),
|
||||
),
|
||||
'to' => array( 'https://www.w3.org/ns/activitystreams#Public' ),
|
||||
'cc' => array( 'https://www.w3.org/ns/activitystreams#Public' ),
|
||||
'attributedTo' => get_author_posts_url( $post->post_author ),
|
||||
'summary' => null,
|
||||
'inReplyTo' => null,
|
||||
'content' => esc_html( $post->post_content ),
|
||||
'contentMap' => array(
|
||||
strstr( get_locale(), '_', true ) => esc_html( $post->post_content ) ,
|
||||
),
|
||||
'attachment' => array(),
|
||||
'tag' => array(),
|
||||
'attachment' => $this->get_attachments(),
|
||||
'tag' => $this->get_tags(),
|
||||
);
|
||||
|
||||
return apply_filters( 'activitypub_json_post', $json );
|
||||
wp_reset_postdata();
|
||||
|
||||
return apply_filters( 'activitypub_post', $array );
|
||||
}
|
||||
|
||||
public function to_json() {
|
||||
return wp_json_encode( $this->to_array() );
|
||||
}
|
||||
|
||||
public function get_attachments() {
|
||||
$max_images = apply_filters( 'activitypub_max_images', 3 );
|
||||
|
||||
$images = array();
|
||||
|
||||
// max images can't be negative or zero
|
||||
if ( $max_images <= 0 ) {
|
||||
$max_images = 1;
|
||||
}
|
||||
|
||||
$id = $this->post->ID;
|
||||
|
||||
$image_ids = array();
|
||||
// list post thumbnail first if this post has one
|
||||
if ( function_exists( 'has_post_thumbnail' ) && has_post_thumbnail( $id ) ) {
|
||||
$image_ids[] = get_post_thumbnail_id( $id );
|
||||
$max_images--;
|
||||
}
|
||||
// then list any image attachments
|
||||
$query = new WP_Query(
|
||||
array(
|
||||
'post_parent' => $id,
|
||||
'post_status' => 'inherit',
|
||||
'post_type' => 'attachment',
|
||||
'post_mime_type' => 'image',
|
||||
'order' => 'ASC',
|
||||
'orderby' => 'menu_order ID',
|
||||
'posts_per_page' => $max_images,
|
||||
)
|
||||
);
|
||||
foreach ( $query->get_posts() as $attachment ) {
|
||||
if ( ! in_array( $attachment->ID, $image_ids ) ) {
|
||||
$image_ids[] = $attachment->ID;
|
||||
}
|
||||
}
|
||||
// get URLs for each image
|
||||
foreach ( $image_ids as $id ) {
|
||||
$thumbnail = wp_get_attachment_image_src( $id, 'full' );
|
||||
$mimetype = get_post_mime_type( $id );
|
||||
|
||||
if ( $thumbnail ) {
|
||||
$images[] = array(
|
||||
'url' => $thumbnail[0],
|
||||
'type' => $mimetype
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$attachments = array();
|
||||
|
||||
// add attachments
|
||||
if ( $images ) {
|
||||
foreach ( $images as $image ) {
|
||||
$attachment = array(
|
||||
"type" => "Image",
|
||||
"url" => $image['url'],
|
||||
"mediaType" => $image['type'],
|
||||
);
|
||||
$attachments[] = $attachment;
|
||||
}
|
||||
}
|
||||
|
||||
return $attachments;
|
||||
}
|
||||
|
||||
public function get_tags() {
|
||||
$tags = array();
|
||||
|
||||
$post_tags = get_the_tags( $this->post->ID );
|
||||
if ( $post_tags ) {
|
||||
foreach( $post_tags as $post_tag ) {
|
||||
$tag = array(
|
||||
"type" => "Hashtag",
|
||||
"href" => get_tag_link( $post_tag->term_id ),
|
||||
"name" => '#' . $post_tag->name,
|
||||
);
|
||||
$tags[] = $tag;
|
||||
}
|
||||
}
|
||||
|
||||
return $tags;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -3,37 +3,37 @@
|
|||
class Activitypub_Signature {
|
||||
|
||||
/**
|
||||
* @param int $author_id
|
||||
* @param int $user_id
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function get_public_key( $author_id, $force = false ) {
|
||||
$key = get_user_meta( $author_id, 'magic_sig_public_key' );
|
||||
public static function get_public_key( $user_id, $force = false ) {
|
||||
$key = get_user_meta( $user_id, 'magic_sig_public_key' );
|
||||
|
||||
if ( $key && ! $force ) {
|
||||
return $key[0];
|
||||
}
|
||||
|
||||
self::generate_key_pair( $author_id );
|
||||
$key = get_user_meta( $author_id, 'magic_sig_public_key' );
|
||||
self::generate_key_pair( $user_id );
|
||||
$key = get_user_meta( $user_id, 'magic_sig_public_key' );
|
||||
|
||||
return $key[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $author_id
|
||||
* @param int $user_id
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function get_private_key( $author_id, $force = false ) {
|
||||
$key = get_user_meta( $author_id, 'magic_sig_private_key' );
|
||||
public static function get_private_key( $user_id, $force = false ) {
|
||||
$key = get_user_meta( $user_id, 'magic_sig_private_key' );
|
||||
|
||||
if ( $key && ! $force ) {
|
||||
return $key[0];
|
||||
}
|
||||
|
||||
self::generate_key_pair( $author_id );
|
||||
$key = get_user_meta( $author_id, 'magic_sig_private_key' );
|
||||
self::generate_key_pair( $user_id );
|
||||
$key = get_user_meta( $user_id, 'magic_sig_private_key' );
|
||||
|
||||
return $key[0];
|
||||
}
|
||||
|
@ -41,9 +41,9 @@ class Activitypub_Signature {
|
|||
/**
|
||||
* Generates the pair keys
|
||||
*
|
||||
* @param int $author_id
|
||||
* @param int $user_id
|
||||
*/
|
||||
public static function generate_key_pair( $author_id ) {
|
||||
public static function generate_key_pair( $user_id ) {
|
||||
$config = array(
|
||||
'digest_alg' => 'sha512',
|
||||
'private_key_bits' => 2048,
|
||||
|
@ -56,18 +56,18 @@ class Activitypub_Signature {
|
|||
openssl_pkey_export( $key, $priv_key );
|
||||
|
||||
// private key
|
||||
update_user_meta( $author_id, 'magic_sig_private_key', $priv_key );
|
||||
update_user_meta( $user_id, 'magic_sig_private_key', $priv_key );
|
||||
|
||||
$detail = openssl_pkey_get_details( $key );
|
||||
|
||||
// public key
|
||||
update_user_meta( $author_id, 'magic_sig_public_key', $detail['key'] );
|
||||
update_user_meta( $user_id, 'magic_sig_public_key', $detail['key'] );
|
||||
}
|
||||
|
||||
public static function generate_signature( $author_id, $inbox, $date ) {
|
||||
$key = self::get_private_key( $author_id );
|
||||
public static function generate_signature( $user_id, $url, $date ) {
|
||||
$key = self::get_private_key( $user_id );
|
||||
|
||||
$url_parts = wp_parse_url( $inbox );
|
||||
$url_parts = wp_parse_url( $url );
|
||||
|
||||
$host = $url_parts['host'];
|
||||
$path = '/';
|
||||
|
@ -86,14 +86,14 @@ class Activitypub_Signature {
|
|||
|
||||
$signature = null;
|
||||
openssl_sign( $signed_string, $signature, $key, OPENSSL_ALGO_SHA256 );
|
||||
$signature = base64_encode( $signature );
|
||||
$signature = base64_encode( $signature ); // phpcs:ignore
|
||||
|
||||
$key_id = get_author_posts_url( $author_id ) . '#main-key';
|
||||
$key_id = get_author_posts_url( $user_id ) . '#main-key';
|
||||
|
||||
return sprintf( 'keyId="%s",algorithm="rsa-sha256",headers="(request-target) host date",signature="%s"', $key_id, $signature );
|
||||
}
|
||||
|
||||
public static function verify_signature() {
|
||||
public static function verify_signature( $headers, $signature ) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,4 +70,13 @@ class Activitypub {
|
|||
public static function add_rewrite_endpoint() {
|
||||
add_rewrite_endpoint( 'as2', EP_AUTHORS | EP_PERMALINK | EP_PAGES );
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the post as "no webmentions sent yet"
|
||||
*
|
||||
* @param int $post_id
|
||||
*/
|
||||
public static function schedule_post_activity( $post_id ) {
|
||||
wp_schedule_single_event( time() + wp_rand( 0, 120 ), 'activitypub_send_post_activity', array( $post_id ) );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,68 +1,9 @@
|
|||
<?php
|
||||
|
||||
class Db_Activitypub_Followers {
|
||||
/**
|
||||
* [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 get_followers( $author_id ) {
|
||||
return get_user_option( 'activitypub_followers', $author_id );
|
||||
}
|
||||
|
||||
public static function add_follower( $actor, $author_id ) {
|
||||
|
|
|
@ -17,10 +17,10 @@ class Rest_Activitypub_Followers {
|
|||
}
|
||||
|
||||
public static function get( $request ) {
|
||||
$author_id = $request->get_param( 'id' );
|
||||
$author = get_user_by( 'ID', $author_id );
|
||||
$user_id = $request->get_param( 'id' );
|
||||
$user = get_user_by( 'ID', $user_id );
|
||||
|
||||
if ( ! $author ) {
|
||||
if ( ! $user ) {
|
||||
return new WP_Error( 'rest_invalid_param', __( 'User not found', 'activitypub' ), array(
|
||||
'status' => 404, 'params' => array(
|
||||
'user_id' => __( 'User not found', 'activitypub' )
|
||||
|
@ -39,7 +39,7 @@ class Rest_Activitypub_Followers {
|
|||
|
||||
$json->{'@context'} = get_activitypub_context();
|
||||
|
||||
$followers = get_user_option( 'activitypub_followers', $author_id );
|
||||
$followers = Db_Activitypub_Followers::get_followers( $user_id );
|
||||
|
||||
if ( ! is_array( $followers ) ) {
|
||||
$followers = array();
|
||||
|
|
|
@ -29,6 +29,44 @@ class Rest_Activitypub_Inbox {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks into the REST API request to verify the signature.
|
||||
*
|
||||
* @param bool $served Whether the request has already been served.
|
||||
* @param WP_HTTP_ResponseInterface $result Result to send to the client. Usually a WP_REST_Response.
|
||||
* @param WP_REST_Request $request Request used to generate the response.
|
||||
* @param WP_REST_Server $server Server instance.
|
||||
*
|
||||
* @return true
|
||||
*/
|
||||
public static function serve_request( $served, $result, $request, $server ) {
|
||||
if ( '/activitypub' !== substr( $request->get_route(), 0, 12 ) ) {
|
||||
return $served;
|
||||
}
|
||||
|
||||
if ( 'POST' !== $request->get_method() ) {
|
||||
return $served;
|
||||
}
|
||||
|
||||
$signature = $request->get_header( 'signature' );
|
||||
|
||||
if ( ! $signature ) {
|
||||
return $served;
|
||||
}
|
||||
|
||||
$headers = $request->get_headers();
|
||||
|
||||
//Activitypub_Signature::verify_signature( $headers, $key );
|
||||
|
||||
return $served;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the user-inbox
|
||||
*
|
||||
* @param WP_REST_Request $request
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public static function user_inbox( $request ) {
|
||||
$author_id = $request->get_param( 'id' );
|
||||
$author = get_user_by( 'ID', $author_id );
|
||||
|
@ -40,14 +78,14 @@ class Rest_Activitypub_Inbox {
|
|||
$type = strtolower( $data['type'] );
|
||||
}
|
||||
|
||||
do_action( 'activitypub_inbox', $data, $author_id, $type );
|
||||
do_action( "activitypub_inbox_{$type}", $data, $author_id );
|
||||
|
||||
if ( ! is_array( $data ) || ! array_key_exists( 'type', $data ) ) {
|
||||
return new WP_Error( 'rest_invalid_data', __( 'Invalid payload', 'activitypub' ), array( 'status' => 422 ) );
|
||||
}
|
||||
|
||||
return new WP_REST_Response( null, 202 );
|
||||
do_action( 'activitypub_inbox', $data, $author_id, $type );
|
||||
do_action( "activitypub_inbox_{$type}", $data, $author_id );
|
||||
|
||||
return new WP_REST_Response( array(), 202 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -81,4 +119,120 @@ class Rest_Activitypub_Inbox {
|
|||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles "Follow" requests
|
||||
*
|
||||
* @param array $object The activity-object
|
||||
* @param int $user_id The id of the local blog-user
|
||||
*/
|
||||
public static function handle_follow( $object, $user_id ) {
|
||||
if ( ! array_key_exists( 'actor', $object ) ) {
|
||||
return new WP_Error( 'activitypub_no_actor', __( 'No "Actor" found', 'activitypub' ), $metadata );
|
||||
}
|
||||
|
||||
// save follower
|
||||
Db_Activitypub_Followers::add_follower( $object['actor'], $user_id );
|
||||
|
||||
// get inbox
|
||||
$inbox = activitypub_get_inbox_by_actor( $object['actor'] );
|
||||
|
||||
// send "Accept" activity
|
||||
$activity = new Activitypub_Activity( 'Accept', Activitypub_Activity::TYPE_SIMPLE );
|
||||
$activity->set_object( $object );
|
||||
$activity->set_actor( get_author_posts_url( $user_id ) );
|
||||
$activity->set_to( $object['actor'] );
|
||||
|
||||
$activity = $activity->to_simple_json();
|
||||
|
||||
$response = activitypub_safe_remote_post( $inbox, $activity, $user_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles "Unfollow" requests
|
||||
*
|
||||
* @param array $object The activity-object
|
||||
* @param int $user_id The id of the local blog-user
|
||||
*/
|
||||
public static function handle_unfollow( $object, $user_id ) {
|
||||
if ( ! array_key_exists( 'actor', $object ) ) {
|
||||
return new WP_Error( 'activitypub_no_actor', __( 'No "Actor" found', 'activitypub' ), $metadata );
|
||||
}
|
||||
|
||||
Db_Activitypub_Followers::remove_follower( $object['actor'], $user_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles "Unfollow" requests
|
||||
*
|
||||
* @param array $object The activity-object
|
||||
* @param int $user_id The id of the local blog-user
|
||||
*/
|
||||
public static function handle_reaction( $object, $user_id ) {
|
||||
if ( ! array_key_exists( 'actor', $object ) ) {
|
||||
return new WP_Error( 'activitypub_no_actor', __( 'No "Actor" found', 'activitypub' ), $metadata );
|
||||
}
|
||||
|
||||
$meta = activitypub_get_remote_metadata_by_actor( $object['actor'] );
|
||||
|
||||
$commentdata = array(
|
||||
'comment_post_ID' => url_to_postid( $object['object'] ),
|
||||
'comment_author' => esc_attr( $meta['name'] ),
|
||||
'comment_author_email' => '',
|
||||
'comment_author_url' => esc_url_raw( $object['actor'] ),
|
||||
'comment_content' => esc_url_raw( $object['actor'] ),
|
||||
'comment_type' => esc_attr( strtolower( $object['type'] ) ),
|
||||
'comment_parent' => 0,
|
||||
'comment_meta' => array(
|
||||
'source_url' => esc_url_raw( $object['id'] ),
|
||||
'avatar_url' => esc_url_raw( $meta['icon']['url'] ),
|
||||
'protocol' => 'activitypub',
|
||||
),
|
||||
);
|
||||
|
||||
// disable flood control
|
||||
remove_action( 'check_comment_flood', 'check_comment_flood_db', 10 );
|
||||
|
||||
$state = wp_new_comment( $commentdata, true );
|
||||
|
||||
// re-add flood control
|
||||
add_action( 'check_comment_flood', 'check_comment_flood_db', 10, 4 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles "Unfollow" requests
|
||||
*
|
||||
* @param array $object The activity-object
|
||||
* @param int $user_id The id of the local blog-user
|
||||
*/
|
||||
public static function handle_create( $object, $user_id ) {
|
||||
if ( ! array_key_exists( 'actor', $object ) ) {
|
||||
return new WP_Error( 'activitypub_no_actor', __( 'No "Actor" found', 'activitypub' ), $metadata );
|
||||
}
|
||||
|
||||
$meta = activitypub_get_remote_metadata_by_actor( $object['actor'] );
|
||||
|
||||
$commentdata = array(
|
||||
'comment_post_ID' => url_to_postid( $object['object']['inReplyTo'] ),
|
||||
'comment_author' => esc_attr( $meta['name'] ),
|
||||
'comment_author_url' => esc_url_raw( $object['actor'] ),
|
||||
'comment_content' => wp_filter_kses( $object['object']['content'] ),
|
||||
'comment_type' => '',
|
||||
'comment_author_email' => '',
|
||||
'comment_parent' => 0,
|
||||
'comment_meta' => array(
|
||||
'source_url' => esc_url_raw( $object['object']['url'] ),
|
||||
'avatar_url' => esc_url_raw( $meta['icon']['url'] ),
|
||||
'protocol' => 'activitypub',
|
||||
),
|
||||
);
|
||||
|
||||
// disable flood control
|
||||
remove_action( 'check_comment_flood', 'check_comment_flood_db', 10 );
|
||||
|
||||
$state = wp_new_comment( $commentdata, true );
|
||||
|
||||
// re-add flood control
|
||||
add_action( 'check_comment_flood', 'check_comment_flood_db', 10, 4 );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* @author Matthias Pfefferle
|
||||
*/
|
||||
class Rest_Activitypub_Outbox {
|
||||
|
||||
/**
|
||||
* Register routes
|
||||
*/
|
||||
|
@ -13,16 +14,22 @@ class Rest_Activitypub_Outbox {
|
|||
'activitypub/1.0', '/users/(?P<id>\d+)/outbox', array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( 'Rest_Activitypub_Outbox', 'get' ),
|
||||
'callback' => array( 'Rest_Activitypub_Outbox', 'user_outbox' ),
|
||||
'args' => self::request_parameters(),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static function get( $request ) {
|
||||
$author_id = $request->get_param( 'id' );
|
||||
$author = get_user_by( 'ID', $author_id );
|
||||
/**
|
||||
* Renders the user-outbox
|
||||
*
|
||||
* @param WP_REST_Request $request
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public static function user_outbox( $request ) {
|
||||
$user_id = $request->get_param( 'id' );
|
||||
$author = get_user_by( 'ID', $user_id );
|
||||
|
||||
if ( ! $author ) {
|
||||
return new WP_Error( 'rest_invalid_param', __( 'User not found', 'activitypub' ), array(
|
||||
|
@ -44,16 +51,16 @@ class Rest_Activitypub_Outbox {
|
|||
$json->{'@context'} = get_activitypub_context();
|
||||
$json->id = home_url( add_query_arg( NULL, NULL ) );
|
||||
$json->generator = 'http://wordpress.org/?v=' . get_bloginfo_rss( 'version' );
|
||||
$json->actor = get_author_posts_url( $author_id );
|
||||
$json->actor = get_author_posts_url( $user_id );
|
||||
$json->type = 'OrderedCollectionPage';
|
||||
$json->partOf = get_rest_url( null, "/activitypub/1.0/users/$author_id/outbox" ); // phpcs:ignore
|
||||
$json->partOf = get_rest_url( null, "/activitypub/1.0/users/$user_id/outbox" ); // phpcs:ignore
|
||||
|
||||
$count_posts = wp_count_posts();
|
||||
$json->totalItems = intval( $count_posts->publish );
|
||||
|
||||
$posts = get_posts( array(
|
||||
'posts_per_page' => 10,
|
||||
'author' => $author_id,
|
||||
'author' => $user_id,
|
||||
'offset' => $page * 10,
|
||||
) );
|
||||
|
||||
|
@ -66,7 +73,9 @@ class Rest_Activitypub_Outbox {
|
|||
|
||||
foreach ( $posts as $post ) {
|
||||
$activitypub_post = new Activitypub_Post( $post );
|
||||
$json->orderedItems[] = $activitypub_post->to_json_array(); // phpcs:ignore
|
||||
$activitypub_activity = new Activitypub_Activity( 'Create', Activitypub_Activity::TYPE_NONE );
|
||||
$activitypub_activity->from_post( $activitypub_post->to_array() );
|
||||
$json->orderedItems[] = $activitypub_activity->to_array(); // phpcs:ignore
|
||||
}
|
||||
|
||||
// filter output
|
||||
|
@ -98,4 +107,21 @@ class Rest_Activitypub_Outbox {
|
|||
|
||||
return $params;
|
||||
}
|
||||
|
||||
public static function send_post_activity( $post_id ) {
|
||||
$post = get_post( $post_id );
|
||||
$user_id = $post->post_author;
|
||||
|
||||
$activitypub_post = new Activitypub_Post( $post );
|
||||
$activitypub_activity = new Activitypub_Activity( 'Create', Activitypub_Activity::TYPE_FULL );
|
||||
$activitypub_activity->from_post( $activitypub_post->to_array() );
|
||||
|
||||
$activity = $activitypub_activity->to_json(); // phpcs:ignore
|
||||
|
||||
$followers = Db_Activitypub_Followers::get_followers( $user_id );
|
||||
|
||||
foreach ( activitypub_get_follower_inboxes( $user_id, $followers ) as $inbox ) {
|
||||
$response = activitypub_safe_remote_post( $inbox, $activity, $user_id );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,34 +39,9 @@ function get_activitypub_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 ), '+/', '-_' ); // phpcs:ignore
|
||||
}
|
||||
}
|
||||
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, '-_', '+/' ) ); // phpcs:ignore
|
||||
}
|
||||
}
|
||||
|
||||
function activitypub_safe_remote_post( $url, $body, $author_id ) {
|
||||
function activitypub_safe_remote_post( $url, $body, $user_id ) {
|
||||
$date = gmdate( 'D, d M Y H:i:s T' );
|
||||
$signature = Activitypub_Signature::generate_signature( $author_id, $url, $date );
|
||||
$signature = Activitypub_Signature::generate_signature( $user_id, $url, $date );
|
||||
|
||||
$wp_version = get_bloginfo( 'version' );
|
||||
$user_agent = apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . get_bloginfo( 'url' ) );
|
||||
|
@ -84,5 +59,127 @@ function activitypub_safe_remote_post( $url, $body, $author_id ) {
|
|||
'body' => $body,
|
||||
);
|
||||
|
||||
$response = wp_safe_remote_post( $url, $args );
|
||||
return wp_safe_remote_post( $url, $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a users WebFinger "resource"
|
||||
*
|
||||
* @param int $user_id
|
||||
*
|
||||
* @return string The user-resource
|
||||
*/
|
||||
function activitypub_get_webfinger_resource( $user_id ) {
|
||||
// use WebFinger plugin if installed
|
||||
if ( function_exists( 'get_webfinger_resource' ) ) {
|
||||
return get_webfinger_resource( $user_id, false );
|
||||
}
|
||||
|
||||
$user = get_user_by( 'id', $user_id );
|
||||
|
||||
return $user->user_login . '@' . wp_parse_url( home_url(), PHP_URL_HOST );
|
||||
}
|
||||
|
||||
/**
|
||||
* [get_metadata_by_actor description]
|
||||
*
|
||||
* @param [type] $actor [description]
|
||||
* @return [type] [description]
|
||||
*/
|
||||
function activitypub_get_remote_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;
|
||||
}
|
||||
|
||||
/**
|
||||
* [get_inbox_by_actor description]
|
||||
* @param [type] $actor [description]
|
||||
* @return [type] [description]
|
||||
*/
|
||||
function activitypub_get_inbox_by_actor( $actor ) {
|
||||
$metadata = activitypub_get_remote_metadata_by_actor( $actor );
|
||||
|
||||
if ( is_wp_error( $metadata ) ) {
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
if ( isset( $metadata['endpoints'] ) && isset( $metadata['endpoints']['sharedInbox'] ) ) {
|
||||
return $metadata['endpoints']['sharedInbox'];
|
||||
}
|
||||
|
||||
if ( array_key_exists( 'inbox', $metadata ) ) {
|
||||
return $metadata['inbox'];
|
||||
}
|
||||
|
||||
return new WP_Error( 'activitypub_no_inbox', __( 'No "Inbox" found', 'activitypub' ), $metadata );
|
||||
}
|
||||
|
||||
/**
|
||||
* [get_inbox_by_actor description]
|
||||
* @param [type] $actor [description]
|
||||
* @return [type] [description]
|
||||
*/
|
||||
function activitypub_get_publickey_by_actor( $actor, $key_id ) {
|
||||
$metadata = activitypub_get_remote_metadata_by_actor( $actor );
|
||||
|
||||
if ( is_wp_error( $metadata ) ) {
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
if (
|
||||
isset( $metadata['publicKey'] ) &&
|
||||
isset( $metadata['publicKey']['id'] ) &&
|
||||
isset( $metadata['publicKey']['owner'] ) &&
|
||||
isset( $metadata['publicKey']['publicKeyPem'] ) &&
|
||||
$key_id === $metadata['publicKey']['id'] &&
|
||||
$actor === $metadata['publicKey']['owner']
|
||||
) {
|
||||
return $metadata['publicKey']['publicKeyPem'];
|
||||
}
|
||||
|
||||
return new WP_Error( 'activitypub_no_public_key', __( 'No "Public-Key" found', 'activitypub' ), $metadata );
|
||||
}
|
||||
|
||||
function activitypub_get_follower_inboxes( $user_id, $followers ) {
|
||||
$inboxes = array();
|
||||
foreach ( $followers as $follower ) {
|
||||
$inboxes[] = activitypub_get_inbox_by_actor( $follower );
|
||||
}
|
||||
|
||||
return array_unique( $inboxes );
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: ActivityPub 0.1.0\n"
|
||||
"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/activitypub\n"
|
||||
"POT-Creation-Date: 2018-12-09 21:09:03+00:00\n"
|
||||
"POT-Creation-Date: 2018-12-20 10:32:52+00:00\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
@ -13,39 +13,65 @@ msgstr ""
|
|||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"X-Generator: grunt-wp-i18n1.0.2\n"
|
||||
|
||||
#: includes/class-activitypub-activities.php:13
|
||||
#: includes/class-activitypub-activities.php:39
|
||||
msgid "No \"Actor\" found"
|
||||
#: includes/class-activitypub-admin.php:39
|
||||
msgid "Overview"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-db-activitypub-actor.php:20
|
||||
msgid "No \"Inbox\" found"
|
||||
#: includes/class-activitypub-admin.php:41
|
||||
msgid ""
|
||||
"ActivityPub is a decentralized social networking protocol based on the "
|
||||
"ActivityStreams 2.0 data format. ActivityPub is an official W3C recommended "
|
||||
"standard published by the W3C Social Web Working Group. It provides a "
|
||||
"client to server API for creating, updating and deleting content, as well "
|
||||
"as a federated server to server API for delivering notifications and "
|
||||
"subscribing to content."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-db-activitypub-actor.php:36
|
||||
msgid "The \"actor\" is no valid URL"
|
||||
#: includes/class-activitypub-admin.php:46
|
||||
msgid "For more information:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-db-activitypub-actor.php:60
|
||||
msgid "No valid JSON data"
|
||||
#: includes/class-activitypub-admin.php:47
|
||||
msgid "<a href=\"https://activitypub.rocks/\">Test Suite</a>"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-activitypub-admin.php:48
|
||||
msgid "<a href=\"https://www.w3.org/TR/activitypub/\">W3C Spec</a>"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-activitypub-admin.php:49
|
||||
msgid ""
|
||||
"<a href=\"https://github.com/pfefferle/wordpress-activitypub/issues\">Give "
|
||||
"us feedback</a>"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-activitypub-admin.php:51
|
||||
msgid "<a href=\"https://notiz.blog/donate\">Donate</a>"
|
||||
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
|
||||
#: includes/class-rest-activitypub-outbox.php:35
|
||||
#: includes/class-rest-activitypub-outbox.php:37
|
||||
#: includes/class-rest-activitypub-webfinger.php:45
|
||||
msgid "User not found"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-rest-activitypub-inbox.php:47
|
||||
#: includes/class-rest-activitypub-inbox.php:82
|
||||
msgid "Invalid payload"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-rest-activitypub-inbox.php:62
|
||||
#: includes/class-rest-activitypub-inbox.php:100
|
||||
msgid "This method is not yet implemented"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-rest-activitypub-inbox.php:131
|
||||
#: includes/class-rest-activitypub-inbox.php:159
|
||||
#: includes/class-rest-activitypub-inbox.php:173
|
||||
#: includes/class-rest-activitypub-inbox.php:210
|
||||
msgid "No \"Actor\" found"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-rest-activitypub-webfinger.php:32
|
||||
msgid "Resouce is invalid"
|
||||
msgstr ""
|
||||
|
@ -54,11 +80,27 @@ msgstr ""
|
|||
msgid "Resouce host does not match blog host"
|
||||
msgstr ""
|
||||
|
||||
#: includes/functions.php:97
|
||||
msgid "The \"actor\" is no valid URL"
|
||||
msgstr ""
|
||||
|
||||
#: includes/functions.php:121
|
||||
msgid "No valid JSON data"
|
||||
msgstr ""
|
||||
|
||||
#: includes/functions.php:149
|
||||
msgid "No \"Inbox\" found"
|
||||
msgstr ""
|
||||
|
||||
#: includes/functions.php:175
|
||||
msgid "No \"Public-Key\" found"
|
||||
msgstr ""
|
||||
|
||||
#: templates/json-author.php:48
|
||||
msgid "Blog"
|
||||
msgstr ""
|
||||
|
||||
#: templates/json-author.php:58
|
||||
#: templates/json-author.php:58 templates/settings-page.php:9
|
||||
msgid "Profile"
|
||||
msgstr ""
|
||||
|
||||
|
@ -66,6 +108,51 @@ msgstr ""
|
|||
msgid "Website"
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings-page.php:2
|
||||
msgid "ActivityPub Settings"
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings-page.php:4
|
||||
msgid ""
|
||||
"ActivityPub turns your blog into a federated social network. This means you "
|
||||
"can share and talk to everyone using the ActivityPub protocol, including "
|
||||
"users of Friendi.ca, Pleroma and Mastodon."
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings-page.php:11
|
||||
msgid "All profile related settings."
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings-page.php:17
|
||||
msgid "Profile identifier"
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings-page.php:21
|
||||
msgid "Try to follow \"@%s\" in the mastodon/friendi.ca search field."
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings-page.php:29
|
||||
msgid "Followers"
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings-page.php:31
|
||||
msgid "All follower related settings."
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings-page.php:37
|
||||
msgid "List of followers"
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings-page.php:47
|
||||
msgid "No followers yet"
|
||||
msgstr ""
|
||||
|
||||
#: templates/settings-page.php:62
|
||||
msgid ""
|
||||
"If you like this plugin, what about a small <a "
|
||||
"href=\"https://notiz.blog/donate\">donation</a>?"
|
||||
msgstr ""
|
||||
|
||||
#. Plugin Name of the plugin/theme
|
||||
msgid "ActivityPub"
|
||||
msgstr ""
|
||||
|
|
28
readme.txt
28
readme.txt
|
@ -3,7 +3,7 @@ Contributors: pfefferle
|
|||
Donate link: https://notiz.blog/donate/
|
||||
Tags: OStatus, fediverse, activitypub, activitystream
|
||||
Requires at least: 4.7
|
||||
Tested up to: 5.0
|
||||
Tested up to: 5.0.2
|
||||
Stable tag: 0.1.0
|
||||
Requires PHP: 5.6
|
||||
License: MIT
|
||||
|
@ -13,9 +13,9 @@ The ActivityPub protocol is a decentralized social networking protocol based upo
|
|||
|
||||
== Description ==
|
||||
|
||||
This is **BETA** software, see the FAQ to see what works and what still needs to be implemented or is in planning.
|
||||
This is **BETA** software, see the FAQ to see the current feature set or rather what is still planned.
|
||||
|
||||
This plugin enables ActivityPub for your Blog. Your readers will be able to follow your Blogposts on Mastodon and other Federated Plattforms that support ActivityPub.
|
||||
This plugin implements the ActivityPub for your Blog. Your readers will be able to follow your Blogposts on Mastodon and other Federated Plattforms that support ActivityPub.
|
||||
|
||||
== Frequently Asked Questions ==
|
||||
|
||||
|
@ -26,30 +26,34 @@ Implemented:
|
|||
* profile pages (JSON representation)
|
||||
* custom links
|
||||
* functional inbox/outbox
|
||||
* follow (accept follows)
|
||||
* follow (accept follows)
|
||||
* share posts
|
||||
* receive comments/reactions
|
||||
|
||||
To implement:
|
||||
|
||||
* share posts
|
||||
* share comments
|
||||
* signature verification
|
||||
* better WordPress integration
|
||||
* better configuration possibilities
|
||||
* threaded comments support
|
||||
|
||||
= Why does the plugin not support ...? =
|
||||
|
||||
*ActivityPub* extends WordPress with some fediverse features, but it does not compete with plattforms like Friendi.ca or Mastodon. If you want to have a **decentralized social network**, please use [Mastodon](https://joinmastodon.org/) or [GNU.social](https://gnu.io/social/).
|
||||
|
||||
= What are the differences to Pterotype? =
|
||||
= What are the differences between this plugin and Pterotype? =
|
||||
|
||||
**PHP Version**
|
||||
|
||||
*ActivityPub* needs PHP 5.6, *Pterotype* requires 7.2.x
|
||||
*This plugin* needs PHP 5.6, *Pterotype* requires 7.2.x
|
||||
|
||||
**Compatibility**
|
||||
|
||||
*ActivityPub* is compatible with OStatus and the IndieWeb movement. *Pterotype* implements its own WebFinger endpoint, that is not compatible with the [WebFinger plugin](https://wordpress.org/plugins/webfinger/).
|
||||
*This plugin* is compatible with OStatus and the IndieWeb movement. *Pterotype* implements for example its own WebFinger endpoint, which is not compatible with the [WebFinger plugin](https://wordpress.org/plugins/webfinger/).
|
||||
|
||||
**Custom tables**
|
||||
|
||||
*Pterotype* creates/uses a bunch of custom tables, *ActivityPub* only uses the native tables and adds as few meta data as possible.
|
||||
*Pterotype* creates/uses a bunch of custom tables, *this plugin* only uses the native tables and adds as few meta data as possible.
|
||||
|
||||
== Changelog ==
|
||||
|
||||
|
@ -59,7 +63,9 @@ Project maintained on github at [pfefferle/wordpress-activitypub](https://github
|
|||
|
||||
* added basic WebFinger support
|
||||
* added basic NodeInfo support
|
||||
* fully functional "follow" activity
|
||||
* fully functional "follow" activity
|
||||
* send new posts to your followers
|
||||
* receive comments from your followers
|
||||
|
||||
= 0.0.2 =
|
||||
|
||||
|
|
|
@ -2,9 +2,11 @@
|
|||
$post = get_post();
|
||||
|
||||
$activitypub_post = new Activitypub_Post( $post );
|
||||
$activitypub_activity = new Activitypub_Activity( 'Create', Activitypub_Activity::TYPE_FULL );
|
||||
$activitypub_activity->from_post( $activitypub_post->to_array() );
|
||||
|
||||
// filter output
|
||||
$json = apply_filters( 'activitypub_json_post_array', $activitypub_post->to_json_array( true ) );
|
||||
$json = apply_filters( 'activitypub_json_post_array', $activitypub_activity->to_array() );
|
||||
|
||||
/*
|
||||
* Action triggerd prior to the ActivityPub profile being created and sent to the client
|
||||
|
|
64
templates/settings-page.php
Normal file
64
templates/settings-page.php
Normal file
|
@ -0,0 +1,64 @@
|
|||
<div class="wrap">
|
||||
<h1><?php esc_html_e( 'ActivityPub Settings', 'activitypub' ); ?></h1>
|
||||
|
||||
<p><?php esc_html_e( 'ActivityPub turns your blog into a federated social network. This means you can share and talk to everyone using the ActivityPub protocol, including users of Friendi.ca, Pleroma and Mastodon.', 'activitypub' ); ?></p>
|
||||
|
||||
<form method="post" action="options.php">
|
||||
<?php settings_fields( 'activitypub' ); ?>
|
||||
|
||||
<h2><?php esc_html_e( 'Profile', 'activitypub' ); ?></h2>
|
||||
|
||||
<p><?php esc_html_e( 'All profile related settings.', 'activitypub' ); ?></p>
|
||||
|
||||
<table class="form-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label><?php esc_html_e( 'Profile identifier', 'activitypub' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<p><code><?php echo activitypub_get_webfinger_resource( get_current_user_id() ); ?></code> or <code><?php echo get_author_posts_url( get_current_user_id() ); ?></code></p>
|
||||
<p class="description"><?php printf( __( 'Try to follow "@%s" in the mastodon/friendi.ca search field.', 'activitypub' ), activitypub_get_webfinger_resource( get_current_user_id() ) ); ?></p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<?php do_settings_fields( 'activitypub', 'profile' ); ?>
|
||||
|
||||
<h2><?php esc_html_e( 'Followers', 'activitypub' ); ?></h2>
|
||||
|
||||
<p><?php esc_html_e( 'All follower related settings.', 'activitypub' ); ?></p>
|
||||
|
||||
<table class="form-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label><?php esc_html_e( 'List of followers', 'activitypub' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<?php if ( Db_Activitypub_Followers::get_followers( get_current_user_id() ) ) { ?>
|
||||
<ul>
|
||||
<?php foreach( Db_Activitypub_Followers::get_followers( get_current_user_id() ) as $follower ) { ?>
|
||||
<li><?php echo esc_attr( $follower ); ?></li>
|
||||
<?php } ?>
|
||||
</ul>
|
||||
<?php } else { ?>
|
||||
<p><?php esc_html_e( 'No followers yet', 'activitypub' ); ?></p>
|
||||
<?php } ?>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<?php do_settings_fields( 'activitypub', 'followers' ); ?>
|
||||
|
||||
<?php do_settings_sections( 'activitypub' ); ?>
|
||||
|
||||
<?php // submit_button(); ?>
|
||||
</form>
|
||||
|
||||
<p>
|
||||
<small><?php _e( 'If you like this plugin, what about a small <a href="https://notiz.blog/donate">donation</a>?', 'activitypub' ); ?></small>
|
||||
</p>
|
||||
</div>
|
Loading…
Reference in a new issue