diff --git a/doc/tools.md b/doc/tools.md index 8746e9c150..1c3b8e119c 100644 --- a/doc/tools.md +++ b/doc/tools.md @@ -27,6 +27,7 @@ The console provides the following commands: * typo: Checks for parse errors in Friendica files * postupdate: Execute pending post update scripts (can last days) * storage: Manage storage backend +* relay: Manage ActivityPub relay servers Please consult *bin/console help* on the command line interface of your server for details about the commands. diff --git a/src/Console/Relay.php b/src/Console/Relay.php new file mode 100644 index 0000000000..5d7c8b3f70 --- /dev/null +++ b/src/Console/Relay.php @@ -0,0 +1,134 @@ +. + * + */ + +namespace Friendica\Console; + +use Asika\SimpleConsole\CommandArgsException; +use Friendica\Model\APContact; +use Friendica\Model\Contact; +use Friendica\Protocol\ActivityPub\Transmitter; + +/** + * tool to control the list of ActivityPub relay servers from the CLI + * + * With this script you can access the relay servers of your node from + * the CLI. + */ +class Relay extends \Asika\SimpleConsole\Console +{ + protected $helpOptions = ['h', 'help', '?']; + + /** + * @var $dba Friendica\Database\Database + */ + private $dba; + + + protected function getHelp() + { + $help = << [-h|--help|-?] [-v] + bin/console relay remove [-h|--help|-?] [-v] + +Description + bin/console relay + Lists all active relay servers + + bin/console relay add + Add a relay actor in the format https://relayserver.tld/actor + + bin/console relay remove + Remove a relay actor in the format https://relayserver.tld/actor + +Options + -h|--help|-? Show help information + -v Show more debug information. +HELP; + return $help; + } + + public function __construct(\Friendica\Database\Database $dba, array $argv = null) + { + parent::__construct($argv); + + $this->dba = $dba; + } + + protected function doExecute() + { + if ($this->getOption('v')) { + $this->out('Executable: ' . $this->executable); + $this->out('Class: ' . __CLASS__); + $this->out('Arguments: ' . var_export($this->args, true)); + $this->out('Options: ' . var_export($this->options, true)); + } + + if (count($this->args) > 2) { + throw new CommandArgsException('Too many arguments'); + } + + if (count($this->args) == 1) { + throw new CommandArgsException('Too few arguments'); + } + + if (count($this->args) == 0) { + $contacts = $this->dba->select('apcontact', ['url'], + ["`type` = ? AND `url` IN (SELECT `url` FROM `contact` WHERE `uid` = ? AND `rel` IN (?, ?))", + 'Application', 0, Contact::FOLLOWER, Contact::FRIEND]); + while ($contact = $this->dba->fetch($contacts)) { + $this->out($contact['url']); + } + $this->dba->close($contacts); + } + + if (count($this->args) == 2) { + $mode = $this->getArgument(0); + $actor = $this->getArgument(1); + + $apcontact = APContact::getByURL($actor); + if (empty($apcontact) || ($apcontact['type'] != 'Application')) { + $this->out($actor . ' is no relay actor'); + return 1; + } + + if ($mode == 'add') { + if (Transmitter::sendRelayFollow($actor)) { + $this->out('Successfully added ' . $actor); + } else { + $this->out($actor . " couldn't be added"); + } + } elseif ($mode == 'remove') { + if (Transmitter::sendRelayUndoFollow($actor)) { + $this->out('Successfully removed ' . $actor); + } else { + $this->out($actor . " couldn't be removed"); + } + } else { + throw new CommandArgsException($mode . ' is no valid command'); + } + } + + return 0; + } +} diff --git a/src/Core/Console.php b/src/Core/Console.php index e08ea7f422..4a4dc13ef7 100644 --- a/src/Core/Console.php +++ b/src/Core/Console.php @@ -64,6 +64,7 @@ Commands: postupdate Execute pending post update scripts (can last days) serverblock Manage blocked servers storage Manage storage backend + relay Manage ActivityPub relay servers Options: -h|--help|-? Show help information @@ -92,6 +93,7 @@ HELP; 'postupdate' => Friendica\Console\PostUpdate::class, 'serverblock' => Friendica\Console\ServerBlock::class, 'storage' => Friendica\Console\Storage::class, + 'relay' => Friendica\Console\Relay::class, ]; /** diff --git a/src/Protocol/ActivityPub/Receiver.php b/src/Protocol/ActivityPub/Receiver.php index 05c3abc01b..ea5251438d 100644 --- a/src/Protocol/ActivityPub/Receiver.php +++ b/src/Protocol/ActivityPub/Receiver.php @@ -163,11 +163,13 @@ class Receiver if ($type != 'as:Announce') { Logger::info('Not an announcement', ['activity' => $activity]); + return; } $object_id = JsonLD::fetchElement($activity, 'as:object', '@id'); if (empty($object_id)) { Logger::info('No object id found', ['activity' => $activity]); + return; } Logger::info('Got relayed message id', ['id' => $object_id]); @@ -179,6 +181,13 @@ class Receiver } Processor::fetchMissingActivity($object_id); + + $item_id = Item::searchByLink($object_id); + if ($item_id) { + Logger::info('Relayed message had been fetched and stored', ['id' => $object_id, 'item' => $item_id]); + } else { + Logger::notice('Relayed message had not been stored', ['id' => $object_id]); + } } /** diff --git a/src/Protocol/ActivityPub/Transmitter.php b/src/Protocol/ActivityPub/Transmitter.php index 91042c06bb..dae783bd74 100644 --- a/src/Protocol/ActivityPub/Transmitter.php +++ b/src/Protocol/ActivityPub/Transmitter.php @@ -61,6 +61,68 @@ require_once 'mod/share.php'; */ class Transmitter { + /** + * Add relay servers to the list of inboxes + * + * @param array $inboxes + * @return array inboxes with added relay servers + */ + public static function addRelayServerInboxes(array $inboxes) + { + $contacts = DBA::select('apcontact', ['inbox'], + ["`type` = ? AND `url` IN (SELECT `url` FROM `contact` WHERE `uid` = ? AND `rel` IN (?, ?))", + 'Application', 0, Contact::FOLLOWER, Contact::FRIEND]); + while ($contact = DBA::fetch($contacts)) { + $inboxes[] = $contact['inbox']; + } + DBA::close($contacts); + + return $inboxes; + } + + /** + * Subscribe to a relay + * + * @param string $url Subscribe actor url + * @return bool success + */ + public static function sendRelayFollow(string $url) + { + $contact_id = Contact::getIdForURL($url); + if (!$contact_id) { + return false; + } + + $activity_id = ActivityPub\Transmitter::activityIDFromContact($contact_id); + $success = ActivityPub\Transmitter::sendActivity('Follow', $url, 0, $activity_id); + if ($success) { + DBA::update('contact', ['rel' => Contact::FRIEND], ['id' => $contact_id]); + } + + return $success; + } + + /** + * Unsubscribe from a relay + * + * @param string $url Subscribe actor url + * @return bool success + */ + public static function sendRelayUndoFollow(string $url) + { + $contact_id = Contact::getIdForURL($url); + if (!$contact_id) { + return false; + } + + $success = self::sendContactUndo($url, $contact_id, 0); + if ($success) { + DBA::update('contact', ['rel' => Contact::SHARING], ['id' => $contact_id]); + } + + return $success; + } + /** * Collects a list of contacts of the given owner * @@ -1917,18 +1979,19 @@ class Transmitter * @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \ImagickException * @throws \Exception + * @return bool success */ public static function sendContactUndo($target, $cid, $uid) { $profile = APContact::getByURL($target); if (empty($profile['inbox'])) { Logger::warning('No inbox found for target', ['target' => $target, 'profile' => $profile]); - return; + return false; } $object_id = self::activityIDFromContact($cid); if (empty($object_id)) { - return; + return false; } $id = DI::baseUrl() . '/activity/' . System::createGUID(); @@ -1947,7 +2010,7 @@ class Transmitter Logger::log('Sending undo to ' . $target . ' for user ' . $uid . ' with id ' . $id, Logger::DEBUG); $signed = LDSignature::sign($data, $owner); - HTTPSignature::transmit($signed, $profile['inbox'], $uid); + return HTTPSignature::transmit($signed, $profile['inbox'], $uid); } private static function prependMentions($body, int $uriid) diff --git a/src/Worker/Notifier.php b/src/Worker/Notifier.php index 3ee75bbb0a..69b684feb7 100644 --- a/src/Worker/Notifier.php +++ b/src/Worker/Notifier.php @@ -786,6 +786,11 @@ class Notifier if ($target_item['origin']) { $inboxes = ActivityPub\Transmitter::fetchTargetInboxes($target_item, $uid); + + if (in_array($target_item['private'], [Item::PUBLIC])) { + $inboxes = ActivityPub\Transmitter::addRelayServerInboxes($inboxes); + } + Logger::log('Origin item ' . $target_item['id'] . ' with URL ' . $target_item['uri'] . ' will be distributed.', Logger::DEBUG); } elseif (Item::isForumPost($target_item, $owner)) { $inboxes = ActivityPub\Transmitter::fetchTargetInboxes($target_item, $uid, false, 0, true);