Add New notice and reblog delivery events, various bug fixes and improvements

This commit is contained in:
Diogo Cordeiro 2018-07-15 02:13:46 +01:00
parent a5b56b3089
commit b693dab832
9 changed files with 311 additions and 30 deletions

View File

@ -52,7 +52,7 @@ class ActivityPubPlugin extends Plugin
['action' => 'showstream'],
['nickname' => Nickname::DISPLAY_FMT],
'apActorProfile');
$m->connect (':nickname/liked.json',
['action' => 'apActorLikedCollection'],
['nickname' => Nickname::DISPLAY_FMT]);
@ -101,7 +101,7 @@ class ActivityPubPlugin extends Plugin
$schema->ensureTable ('Activitypub_profile', Activitypub_profile::schemaDef());
return true;
}
/********************************************************
* Delivery Events *
********************************************************/
@ -176,13 +176,41 @@ class ActivityPubPlugin extends Plugin
}
$other = array ();
foreach ($notice->getAttentionProfileIDs () as $to_id) {
try {
$other[] = Activitypub_profile::from_profile($notice->getProfile ());
} catch (Exception $e) {
// Local user can be ignored
}
foreach ($notice->getAttentionProfiles() as $to_profile) {
try {
$other[] = Activitypub_profile::from_profile (Profile::getById ($to_id));
$other[] = Activitypub_profile::from_profile ($to_profile);
} catch (Exception $e) {
// Local user can be ignored
}
}
if ($notice->reply_to) {
try {
$other[] = Activitypub_profile::from_profile ($notice->getParent ()->getProfile ());
} catch (Exception $e) {
// Local user can be ignored
}
try {
$mentions = $notice->getParent ()->getAttentionProfiles ();
foreach ($mentions as $to_profile) {
try {
$other[] = Activitypub_profile::from_profile ($to_profile);
} catch (Exception $e) {
// Local user can be ignored
}
}
} catch (NoParentNoticeException $e) {
// This is not a reply to something (has no parent)
} catch (NoResultException $e) {
// Parent author's profile not found! Complain louder?
common_log(LOG_ERR, "Parent notice's author not found: ".$e->getMessage());
}
}
$postman = new Activitypub_postman ($profile, $other);
$postman->like ($notice);
@ -206,13 +234,41 @@ class ActivityPubPlugin extends Plugin
}
$other = array ();
foreach ($notice->getAttentionProfileIDs () as $to_id) {
try {
$other[] = Activitypub_profile::from_profile($notice->getProfile ());
} catch (Exception $e) {
// Local user can be ignored
}
foreach ($notice->getAttentionProfiles() as $to_profile) {
try {
$other[] = Activitypub_profile::from_profile (Profile::getById ($to_id));
$other[] = Activitypub_profile::from_profile ($to_profile);
} catch (Exception $e) {
// Local user can be ignored
}
}
if ($notice->reply_to) {
try {
$other[] = Activitypub_profile::from_profile ($notice->getParent ()->getProfile ());
} catch (Exception $e) {
// Local user can be ignored
}
try {
$mentions = $notice->getParent ()->getAttentionProfiles ();
foreach ($mentions as $to_profile) {
try {
$other[] = Activitypub_profile::from_profile ($to_profile);
} catch (Exception $e) {
// Local user can be ignored
}
}
} catch (NoParentNoticeException $e) {
// This is not a reply to something (has no parent)
} catch (NoResultException $e) {
// Parent author's profile not found! Complain louder?
common_log(LOG_ERR, "Parent notice's author not found: ".$e->getMessage());
}
}
$postman = new Activitypub_postman ($profile, $other);
$postman->undo_like ($notice);
@ -236,17 +292,123 @@ class ActivityPubPlugin extends Plugin
}
$other = array ();
foreach ($notice->getAttentionProfileIDs () as $to_id) {
foreach ($notice->getAttentionProfiles() as $to_profile) {
try {
$other[] = Activitypub_profile::from_profile (Profile::getById ($to_id));
$other[] = Activitypub_profile::from_profile ($to_profile);
} catch (Exception $e) {
// Local user can be ignored
}
}
if ($notice->reply_to) {
try {
$other[] = Activitypub_profile::from_profile ($notice->getParent ()->getProfile ());
} catch (Exception $e) {
// Local user can be ignored
}
try {
$mentions = $notice->getParent ()->getAttentionProfiles ();
foreach ($mentions as $to_profile) {
try {
$other[] = Activitypub_profile::from_profile ($to_profile);
} catch (Exception $e) {
// Local user can be ignored
}
}
} catch (NoParentNoticeException $e) {
// This is not a reply to something (has no parent)
} catch (NoResultException $e) {
// Parent author's profile not found! Complain louder?
common_log(LOG_ERR, "Parent notice's author not found: ".$e->getMessage());
}
}
$postman = new Activitypub_postman ($profile, $other);
$postman->delete ($notice);
return true;
}
/**
* Insert notifications for replies, mentions and repeats
*
* @return boolean hook flag
*/
function onStartNoticeDistribute ($notice)
{
assert ($notice->id > 0); // Ignore if not a valid notice
$profile = Profile::getKV ($notice->profile_id);
$other = array ();
try {
$other[] = Activitypub_profile::from_profile($notice->getProfile ());
} catch (Exception $e) {
// Local user can be ignored
}
foreach ($notice->getAttentionProfiles() as $to_profile) {
try {
$other[] = Activitypub_profile::from_profile ($to_profile);
} catch (Exception $e) {
// Local user can be ignored
}
}
// Is Announce
if ($notice->isRepeat ()) {
$repeated_notice = Notice::getKV ('id', $notice->repeat_of);
if ($repeated_notice instanceof Notice) {
try {
$other[] = Activitypub_profile::from_profile ($repeated_notice->getProfile ());
} catch (Exception $e) {
// Local user can be ignored
}
$postman = new Activitypub_postman ($profile, $other);
// That was it
$postman->announce ($repeated_notice);
return true;
}
}
// Ignore for activity/non-post-verb notices
if (method_exists ('ActivityUtils', 'compareVerbs')) {
$is_post_verb = ActivityUtils::compareVerbs ($notice->verb,
array (ActivityVerb::POST));
} else {
$is_post_verb = ($notice->verb == ActivityVerb::POST ? true : false);
}
if ($notice->source == 'activity' || !$is_post_verb) {
return true;
}
// Create
if ($notice->reply_to) {
try {
$other[] = Activitypub_profile::from_profile ($notice->getParent ()->getProfile ());
} catch (Exception $e) {
// Local user can be ignored
}
try {
$mentions = $notice->getParent ()->getAttentionProfiles ();
foreach ($mentions as $to_profile) {
try {
$other[] = Activitypub_profile::from_profile ($to_profile);
} catch (Exception $e) {
// Local user can be ignored
}
}
} catch (NoParentNoticeException $e) {
// This is not a reply to something (has no parent)
} catch (NoResultException $e) {
// Parent author's profile not found! Complain louder?
common_log(LOG_ERR, "Parent notice's author not found: ".$e->getMessage());
}
}
$postman = new Activitypub_postman ($profile, $other);
$postman->delete ($notice);
// That was it
$postman->create ($notice);
return true;
}
}

View File

@ -58,13 +58,13 @@ class apSharedInboxAction extends ManagedAction
$data = json_decode (file_get_contents ('php://input'));
// Validate data
if (!isset($data->type)) {
if (!isset ($data->type)) {
ActivityPubReturn::error ("Type was not specified.");
}
if (!isset($data->actor)) {
if (!isset ($data->actor)) {
ActivityPubReturn::error ("Actor was not specified.");
}
if (!isset($data->object)) {
if (!isset ($data->object)) {
ActivityPubReturn::error ("Object was not specified.");
}
@ -91,7 +91,7 @@ class apSharedInboxAction extends ManagedAction
if (!isset($data->to)) {
ActivityPubReturn::error ("To was not specified.");
}
$discovery = new Activitypub_Discovery;
$discovery = new Activitypub_explorer;
$to_profiles = array ();
// Generate To objects
if (is_array ($data->to)) {
@ -128,6 +128,9 @@ class apSharedInboxAction extends ManagedAction
case "Undo":
require_once __DIR__ . DIRECTORY_SEPARATOR . "inbox" . DIRECTORY_SEPARATOR . "Undo.php";
break;
case "Delete":
require_once __DIR__ . DIRECTORY_SEPARATOR . "inbox" . DIRECTORY_SEPARATOR . "Delete.php";
break;
default:
ActivityPubReturn::error ("Invalid type value.");
}

View File

@ -38,6 +38,9 @@ if (!(isset ($data->object->type) && in_array ($data->object->type, $valid_objec
if (!isset ($data->object->content)) {
ActivityPubReturn::error ("Object content was not specified.");
}
if (!isset ($data->object->url)) {
ActivityPubReturn::error ("Object url was not specified.");
}
$content = $data->object->content;
@ -50,9 +53,13 @@ $act->context = new ActivityContext ();
// Is this a reply?
if (isset ($data->object->reply_to)) {
$reply_to = Notice::getByUri ($data->object->reply_to);
$act->context->replyToID = $reply_to->getUri ();
$act->context->replyToUrl = $data->object->reply_to;
try {
$reply_to = Notice::getByUri ($data->object->reply_to);
} catch (Exception $e) {
ActivityPubReturn::error ("Invalid Object reply_to value.");
}
$act->context->replyToID = $reply_to->getUri ();
$act->context->replyToUrl = $reply_to->getUrl ();
} else {
$reply_to = null;
}
@ -70,7 +77,7 @@ if (Notice::contentTooLong ($content)) {
ActivityPubReturn::error ("That's too long. Maximum notice size is %d character.");
}
$options = array ('source' => 'ActivityPub', 'uri' => $data->id);
$options = array ('source' => 'ActivityPub', 'uri' => $data->id, 'url' => $data->object->url);
// $options gets filled with possible scoping settings
ToSelector::fillActivity ($this, $act, $options);
@ -84,6 +91,7 @@ $act->objects[] = $actobj;
try {
$res = array ("@context" => "https://www.w3.org/ns/activitystreams",
"id" => $data->id,
"url" => $data->object->url,
"type" => "Create",
"actor" => $data->actor,
"object" => Activitypub_notice::notice_to_array (Notice::saveActivity ($act, $actor_profile, $options)));

View File

@ -30,7 +30,7 @@ if (!defined ('GNUSOCIAL')) {
}
try {
Activitypub_notice::getKV ("url", $data->object)->deleteAs ($actor_profile);
Notice::getByUri ($data->object)->deleteAs ($actor_profile);
$res = array ("@context" => "https://www.w3.org/ns/activitystreams",
"type" => "Delete",
"actor" => $data->actor,

View File

@ -30,7 +30,7 @@ if (!defined ('GNUSOCIAL')) {
}
try {
Fave::addNew ($actor_profile, Notice::getKV ("url", $data->object));
Fave::addNew ($actor_profile, Notice::getByUri ($data->object));
$res = array ("@context" => "https://www.w3.org/ns/activitystreams",
"type" => "Like",
"actor" => $data->actor,

View File

@ -41,8 +41,7 @@ case "Like":
if (!isset ($data->object->object)) {
ActivityPubReturn::error ("Object Notice URL was not specified.");
}
Fave::removeEntry ($actor_profile, Notice::getKV ("url", $data->object->object));
Fave::removeEntry ($actor_profile, Notice::getByUri ($data->object->object));
ActivityPubReturn::answer ("Notice disfavorited successfully.");
} catch (Exception $e) {
ActivityPubReturn::error ($e->getMessage (), 403);

View File

@ -1,4 +1,5 @@
<?php
require_once dirname (__DIR__) . DIRECTORY_SEPARATOR . "utils" . DIRECTORY_SEPARATOR . "explorer.php";
/**
* GNU social - a federating social network
*
@ -178,7 +179,13 @@ class Activitypub_profile extends Profile
$aprofile = Activitypub_profile::getKV ('profile_id', $profile_id);
if (!$aprofile instanceof Activitypub_profile) {
throw new Exception ('No Activitypub_profile for Profile ID: '.$profile_id);
// No Activitypub_profile for this profile_id,
if (!$profile->isLocal ()) {
// create one!
$aprofile = self::create_from_local_profile ($profile);
} else {
throw new Exception ('No Activitypub_profile for Profile ID: '.$profile_id. ', this probably is a local profile.');
}
}
foreach ($profile as $key => $value) {
@ -188,6 +195,35 @@ class Activitypub_profile extends Profile
return $aprofile;
}
/**
* Given an existent local profile creates an ActivityPub profile.
* One must be careful not to give a user profile to this function
* as only remote users have ActivityPub_profiles on local instance
*
* @param Profile $profile
* @return Activitypub_profile
*/
private static function create_from_local_profile (Profile $profile)
{
$url = $profile->getURL ();
$inboxes = Activitypub_explorer::get_actor_inboxes_uri ($url);
$aprofile->created = $aprofile->modified = common_sql_now ();
$aprofile = new Activitypub_profile;
$aprofile->profile_id = $profile->getID ();
$aprofile->uri = $url;
$aprofile->nickname = $profile->getNickname ();
$aprofile->fullname = $profile->getFullname ();
$aprofile->bio = substr ($profile->getDescription (), 0, 1000);
$aprofile->inboxuri = $inboxes["inbox"];
$aprofile->sharedInboxuri = $inboxes["sharedInbox"];
$aprofile->insert ();
return $aprofile;
}
/**
* Returns sharedInbox if possible, inbox otherwise
*

View File

@ -141,7 +141,7 @@ class Activitypub_explorer
$this->_lookup ($res["next"]);
}
return true;
} else if ($this->validate_remote_response ($res)) {
} else if (self::validate_remote_response ($res)) {
$this->discovered_actor_profiles[]= $this->store_profile ($res);
return true;
}
@ -163,7 +163,7 @@ class Activitypub_explorer
$aprofile->fullname = $res["display_name"];
$aprofile->bio = substr ($res["summary"], 0, 1000);
$aprofile->inboxuri = $res["inbox"];
$aprofile->sharedInboxuri = $res["sharedInbox"];
$aprofile->sharedInboxuri = isset ($res["sharedInbox"]) ? $res["sharedInbox"] : $res["inbox"];
$aprofile->do_insert ();
@ -177,9 +177,9 @@ class Activitypub_explorer
* @param array $res remote response
* @return boolean success state
*/
private function validate_remote_response ($res)
private static function validate_remote_response ($res)
{
if (!isset ($res["url"], $res["nickname"], $res["display_name"], $res["summary"], $res["inbox"], $res["sharedInbox"])) {
if (!isset ($res["url"], $res["nickname"], $res["display_name"], $res["summary"], $res["inbox"])) {
return false;
}
@ -210,4 +210,29 @@ class Activitypub_explorer
}
return $i;
}
/**
* Given a valid actor profile url returns its inboxes
*
* @param string $url of Actor profile
* @return boolean|array false if fails | array with inbox and shared inbox if successful
*/
static function get_actor_inboxes_uri ($url)
{
$client = new HTTPClient ();
$headers = array();
$headers[] = 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
$headers[] = 'User-Agent: GNUSocialBot v0.1 - https://gnu.io/social';
$response = $client->get ($url, $headers);
if (!$response->isOk ()) {
throw new Exception ("Invalid Actor URL.");
}
$res = json_decode ($response->getBody (), JSON_UNESCAPED_SLASHES);
if (self::validate_remote_response ($res)) {
return array ("inbox" => $res["inbox"],
"sharedInbox" => isset ($res["sharedInbox"]) ? $res["sharedInbox"] : $res["inbox"]);
}
return false;
}
}

View File

@ -104,7 +104,7 @@ class Activitypub_postman
$data = array ("@context" => "https://www.w3.org/ns/activitystreams",
"type" => "Like",
"actor" => $this->actor->getUrl (),
"object" => $notice->getUrl ());
"object" => $notice->getUri ());
$this->client->setBody (json_encode ($data));
foreach ($this->to_inbox () as $inbox) {
$this->client->post ($inbox, $this->headers);
@ -123,7 +123,7 @@ class Activitypub_postman
"actor" => $this->actor->getUrl (),
"object" => array (
"type" => "Like",
"object" => $notice->getUrl ()
"object" => $notice->getUri ()
)
);
$this->client->setBody (json_encode ($data));
@ -132,6 +132,54 @@ class Activitypub_postman
}
}
/**
* Send a Announce notification to remote instances
*
* @param Notice $notice
*/
public function announce ($notice)
{
$data = array ("@context" => "https://www.w3.org/ns/activitystreams",
"id" => $notice->getUri (),
"url" => $notice->getUrl (),
"type" => "Announce",
"actor" => $this->actor->getUrl (),
"to" => "https://www.w3.org/ns/activitystreams#Public",
"object" => $notice->getUri ()
);
$this->client->setBody (json_encode ($data));
foreach ($this->to_inbox () as $inbox) {
$this->client->post ($inbox, $this->headers);
}
}
/**
* Send a Create notification to remote instances
*
* @param Notice $notice
*/
public function create ($notice)
{
$data = array ("@context" => "https://www.w3.org/ns/activitystreams",
"id" => $notice->getUri (),
"type" => "Create",
"actor" => $this->actor->getUrl (),
"to" => "https://www.w3.org/ns/activitystreams#Public",
"object" => array (
"type" => "Note",
"url" => $notice->getUrl (),
"content" => $notice->getContent ()
)
);
if (isset ($notice->reply_to)) {
$data["object"]["reply_to"] = $notice->getParent ()->getUri ();
}
$this->client->setBody (json_encode ($data));
foreach ($this->to_inbox () as $inbox) {
$this->client->post ($inbox, $this->headers);
}
}
/**
* Send a Delete notification to remote instances holding the notice
*
@ -142,7 +190,7 @@ class Activitypub_postman
$data = array ("@context" => "https://www.w3.org/ns/activitystreams",
"type" => "Delete",
"actor" => $this->actor->getUrl (),
"object" => $notice->getUrl ()
"object" => $notice->getUri ()
);
$this->client->setBody (json_encode ($data));
foreach ($this->to_inbox () as $inbox) {