Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion Controllers/api/v1/newsfeed.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,28 @@
use Minds\Core\Security;
use Minds\Entities;
use Minds\Entities\Activity;
use Minds\Entities\User;
use Minds\Helpers;
use Minds\Entities\Factory as EntitiesFactory;
use Minds\Helpers\Counters;
use Minds\Interfaces;
use Minds\Interfaces\Flaggable;
use Minds\Core\Di\Di;
use Minds\Core\Newsfeed\ActivityPubClient;
use Minds\Interfaces\ActivityPubClient as iActivityPubClient;

class newsfeed implements Interfaces\Api
{
/** @var iActivityPubClient */
protected $pubSubClient;

public function __construct(iActivityPubClient $pubSubClient = null)
{
$this->pubSubClient = $pubSubClient ?? new ActivityPubClient();
// See https://project.hubzilla.org for how to set your own ActivityPub server.
$this->pubSubClient->setActivityPubServer('https://project.hubzilla.org');
}

/**
* Returns the newsfeed
* @param array $pages
Expand Down Expand Up @@ -420,6 +433,14 @@ public function post($pages)
Helpers\Wallet::createTransaction($embeded->owner_guid, 5, $activity->guid, 'Remind');
}

// Post via ActivityPub:
/** @var User $user */
$user = Core\Session::getLoggedinUser();

$this->pubSubClient->setActor($user->name, "https://www.minds.com/{$user->username}");

$this->pubSubClient->postArticle($embeded);

// Follow activity
(new Core\Notification\PostSubscriptions\Manager())
->setEntityGuid($activity->guid)
Expand Down Expand Up @@ -746,7 +767,7 @@ public function delete($pages)
if (!$activity->canEdit()) {
return Factory::response(array('status' => 'error', 'message' => 'you don\'t have permission'));
}
/** @var Entities\User $owner */
/** @var User $owner */
$owner = $activity->getOwnerEntity();

if (
Expand Down
142 changes: 142 additions & 0 deletions Core/Newsfeed/ActivityPubClient.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
<?php declare(strict_types=1);

namespace Minds\Core\Newsfeed;

use GuzzleHttp\Client as Guzzle_Client;
use GuzzleHttp\Psr7\Response;
use LogicException;
use Minds\Core\Blogs\Blog;
use Minds\Core\Config;
use Minds\Core\Di\Di;
use Minds\Interfaces\ActivityPubClient as iActivityPubClient;

class ActivityPubClient implements iActivityPubClient
{
/** @var Config */
protected $config;

/** @var Guzzle_Client */
protected $client;

/** @var Response */
public $response;

/** @var string */
protected $activityPubURI;

/** @var string */
protected $actorName;

/** @var string */
protected $actorURI;

public function __construct(Guzzle_Client $client = null)
{
$this->config = Di::_()->get('Config');
$this->client = $client ?? new Guzzle_Client();
}

public function setActivityPubServer(string $serverURL)
{
$this->activityPubURI = $serverURL;
}

public function setActor(string $actorName, string $actorURI)
{
$this->actorName = $actorName;
$this->actorURI = $actorURI;
}

private function assertPubSubURI()
{
if (!$this->activityPubURI) {
throw new LogicException('The PubSub URI has not been specified.');
}
}

private function assertActor()
{
if (!$this->actorURI) {
throw new LogicException('The PubSub actor has not been specified.');
}
}

protected function validate()
{
$this->assertPubSubURI();
$this->assertActor();
}

/**
* See: https://w3c.github.io/activitypub/#create-activity-outbox
*
* @param string $title
* @param string $body
* @param string $to
* @param string[]|null $cc
* @return int The HTTP Status code of the request.
*/
public function postArticle(Blog $article, ?string $to, ?array $cc = null)
{
$this->validate();

// Default the "To" to the user's subscribers, although it could be any other user,
// even a user on another ActivityPub site.
$to = $to ?? $this->actorURI . '/subscribers';

$params = [
'@context' => [
'https://www.w3.org/ns/activitystreams',
'@language' => 'en-US'
],
'id' => $this->config->site_url . "newsfeed/{$article->guid}",
'type' => 'Article',
'name' => $article->getTitle(),
'content' => $article->getBody(),
'attributedTo' => $this->actorURI,
'to' => $to,
'cc' => $cc,
];

$this->response = $this->client->post($this->activityPubURI, [
'Content-Type' => 'application/json',
'json' => $params,
]);

return $this->response->getStatusCode();
}

/**
* See: https://w3c.github.io/activitypub/#create-activity-outbox
*/
public function like(string $refObjectURI, ?string $to, ?string $summary = null, ?array $cc = null)
{
$this->validate();

// Default the "To" to the user's subscribers, although it could be any other user,
// even a user on another ActivityPub site.
$to = $to ?? $this->actorURI . '/subscribers';

$params = [
'@context' => [
'https://www.w3.org/ns/activitystreams',
'@language' => 'en-US'
],
// Use the item's GUID as the basis for the unique ActivityPub ID.
'id' => $this->config->site_url . $this->actorURI . '/activitypub/' . $refObjectURI,
'type' => 'Like',
'actor' => $this->actorURI,
'summary' => $summary ?? "{$this->actorName} liked the post",
'object' => $refObjectURI,
'to' => $to,
'cc' => $cc,
];

$this->response = $this->client->post($this->activityPubURI, [
'Content-Type' => 'application/json',
'json' => $params,
]);

return $this->response->getStatusCode();
}
}
27 changes: 27 additions & 0 deletions Interfaces/ActivityPubClient.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php declare(strict_types=1);

namespace Minds\Interfaces;
use Minds\Core\Blogs\Blog;

/**
* The basic interface for creating a client/server Activity for Posting articles via the PubSub spec.
* See: https://w3c.github.io/activitypub/#client-to-server-interactions
*/
interface ActivityPubClient
{
public function setActivityPubServer(string $serverURL);

public function setActor(string $actorName, string $actorURI);

/**
* See: https://w3c.github.io/activitypub/#create-activity-outbox
* @param $to string
* @param $cc string[]
*/
public function postArticle(Blog $article, ?string $to, ?array $cc = null);

/**
* See: https://w3c.github.io/activitypub/#create-activity-outbox
*/
public function like(string $refObjectURI, ?string $to, ?string $sumary = null, ?array $cc = null);
}
95 changes: 95 additions & 0 deletions Spec/Core/Newsfeed/ActivityPubClientSpec.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php

namespace Spec\Minds\Core\Newsfeed;

use GuzzleHttp\Client as Guzzle_Client;
use GuzzleHttp\Psr7\Response;
use Minds\Core\Blogs\Blog;
use Minds\Helpers\ActivityPubClient;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;

/**
* Class NewsfeedActivityPubClientSpec
* @package Spec\Minds\Helpers
* @mixin ActivityPubClient
*/
class ActivityPubClientSpec extends ObjectBehavior
{
protected function buildTestBlog(): Blog
{
$blog = new Blog();
$blog->setTitle('Test Blog');
$blog->setBody('Test body.');

return $blog;
}

protected function buildGuzzleMock(): Guzzle_Client
{
$guzzleMock = new class extends Guzzle_Client {
public function post($uri, $params): Response
{
return new class extends Response {
public function getStatusCode()
{
return 200;
}
};
}
};

return $guzzleMock;
}

public function it_is_initializable()
{
$this->shouldHaveType('Minds\Core\Newsfeed\ActivityPubClient');
}

public function it_should_not_post_blog_without_a_pub_server()
{
$this->shouldThrow(new \LogicException('The PubSub URI has not been specified.'))
->duringPostArticle($this->buildTestBlog(), null);
}

public function it_should_not_post_blog_without_an_actor()
{
$this->setActivityPubServer('https://test');
$this->shouldThrow(new \LogicException('The PubSub actor has not been specified.'))
->duringPostArticle($this->buildTestBlog(), null);
}

public function it_should_post_blog_to_pub_server()
{
$this->beConstructedWith($this->buildGuzzleMock());

$this->setActivityPubServer('http://localhost');
$this->setActor('testuser', 'https://minds.com/testuser');
$this->postArticle($this->buildTestBlog(), null)
->shouldBe(200);
}

public function it_should_not_like_an_entity_without_a_pub_server()
{
$this->shouldThrow(new \LogicException('The PubSub URI has not been specified.'))
->duringLike('https://minds.com/user/1', null);
}

public function it_should_not_like_an_entity_without_an_actor()
{
$this->setActivityPubServer('https://test');
$this->shouldThrow(new \LogicException('The PubSub actor has not been specified.'))
->duringLike('https://minds.com/user/1', null);
}

public function it_should_send_like_to_pub_server()
{
$this->beConstructedWith($this->buildGuzzleMock());

$this->setActivityPubServer('http://localhost');
$this->setActor('testuser', 'https://minds.com/testuser');
$this->like('https://minds.com/user/1', null)
->shouldBe(200);
}
}