forked from grumpydevelop/federator
WIP inbox-support
- includes hacky following-mechanic in order to simulate a follow on mastodon (not properly working, need to also inject the user this creates into the followers db for the target mastodon-user) - created endpoint for inbox. SharedInbox is used when no user is provided (/api/federator/fedusers/inbox'), the regular inbox link now works (/users/username/inbox). - Retrieve all followers of sender and, if they're part of our system, send the activity into their personal inbox - Support Announce and Undo Activity-Types - Inbox currently converts to proper ActPub-objects and saves data to log-files
This commit is contained in:
parent
823283183e
commit
305ded4986
12 changed files with 1044 additions and 25 deletions
|
@ -1,5 +1,5 @@
|
|||
[contentnation]
|
||||
service-uri = https://contentnation.net
|
||||
service-uri = http://local.contentnation.net
|
||||
|
||||
[userdata]
|
||||
path = '/home/net/contentnation/userdata/htdocs/' // need to download local copy of image and put img-path here
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[mastodon]
|
||||
service-uri = https://mastodon.social
|
||||
service-uri = http://mastodon.local
|
||||
|
||||
[userdata]
|
||||
path = '/home/net/contentnation/userdata/htdocs/' // need to download local copy of image and put img-path here
|
||||
|
|
|
@ -46,7 +46,7 @@ class Api extends Main
|
|||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->contentType = "application/json";
|
||||
$this->contentType = "application/activity+json";
|
||||
Main::__construct();
|
||||
}
|
||||
|
||||
|
|
|
@ -52,12 +52,20 @@ class FedUsers implements APIInterface
|
|||
switch (sizeof($paths)) {
|
||||
case 2:
|
||||
if ($method === 'GET') {
|
||||
// /users/username or /@username
|
||||
// /fedusers/username or /@username
|
||||
return $this->returnUserProfile($paths[1]);
|
||||
} else {
|
||||
switch ($paths[1]) {
|
||||
case 'inbox':
|
||||
$handler = new FedUsers\Inbox($this->main);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
// /users/username/(inbox|outbox|following|followers)
|
||||
// /fedusers/username/(inbox|outbox|following|followers)
|
||||
switch ($paths[2]) {
|
||||
case 'following':
|
||||
// $handler = new FedUsers\Following();
|
||||
|
@ -66,7 +74,19 @@ class FedUsers implements APIInterface
|
|||
// $handler = new FedUsers\Followers();
|
||||
break;
|
||||
case 'inbox':
|
||||
// $handler = new FedUsers\Inbox();
|
||||
$handler = new FedUsers\Inbox($this->main);
|
||||
$user = $paths[1];
|
||||
if (!preg_match("#^([^@]+)@([^/]+)#", $user, $matches) === 1) {
|
||||
$hostUrl = $this->main->getHost();
|
||||
if ($hostUrl !== false) {
|
||||
$host = parse_url($hostUrl, PHP_URL_HOST);
|
||||
$port = parse_url($hostUrl, PHP_URL_PORT);
|
||||
if ($port !== null) {
|
||||
$host .= `:$port`;
|
||||
}
|
||||
$user = `$user@$host`;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'outbox':
|
||||
$handler = new FedUsers\Outbox($this->main);
|
||||
|
@ -86,7 +106,7 @@ class FedUsers implements APIInterface
|
|||
}
|
||||
break;
|
||||
case 4:
|
||||
// /users/username/collections/(features|tags)
|
||||
// /fedusers/username/collections/(features|tags)
|
||||
// not yet implemented
|
||||
break;
|
||||
}
|
||||
|
|
401
php/federator/api/fedusers/inbox.php
Normal file
401
php/federator/api/fedusers/inbox.php
Normal file
|
@ -0,0 +1,401 @@
|
|||
<?php
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Sascha Nitsch (grumpydeveloper) https://contentnation.net/@grumpydevelop
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* @author Sascha Nitsch (grumpydeveloper)
|
||||
**/
|
||||
|
||||
namespace Federator\Api\FedUsers;
|
||||
|
||||
/**
|
||||
* handle activitypub outbox requests
|
||||
*/
|
||||
class Inbox implements \Federator\Api\FedUsers\FedUsersInterface
|
||||
{
|
||||
/**
|
||||
* main instance
|
||||
*
|
||||
* @var \Federator\Main $main
|
||||
*/
|
||||
private $main;
|
||||
|
||||
/**
|
||||
* constructor
|
||||
* @param \Federator\Main $main main instance
|
||||
*/
|
||||
public function __construct($main)
|
||||
{
|
||||
$this->main = $main;
|
||||
}
|
||||
|
||||
/**
|
||||
* handle get call
|
||||
*
|
||||
* @param string $_user user to fetch inbox for @unused-param
|
||||
* @return string|false response
|
||||
*/
|
||||
public function get($_user)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* handle post call
|
||||
*
|
||||
* @param string $_user user to add data to inbox
|
||||
* @return string|false response
|
||||
*/
|
||||
public function post($_user)
|
||||
{
|
||||
$inboxActivity = null;
|
||||
$_rawInput = file_get_contents('php://input');
|
||||
|
||||
$activity = json_decode($_rawInput, true);
|
||||
$host = $_SERVER['SERVER_NAME'];
|
||||
|
||||
$sendTo = [];
|
||||
|
||||
switch ($activity['type']) {
|
||||
case 'Create':
|
||||
if (!isset($activity['object'])) {
|
||||
break;
|
||||
}
|
||||
|
||||
$obj = $activity['object'];
|
||||
$create = new \Federator\Data\ActivityPub\Common\Create();
|
||||
$create->setID($activity['id'])
|
||||
->setURL($activity['id'])
|
||||
->setPublished(strtotime($activity['published'] ?? $obj['published'] ?? 'now'))
|
||||
->setAActor($activity['actor']);
|
||||
|
||||
if (array_key_exists('cc', $activity)) {
|
||||
foreach ($activity['cc'] as $cc) {
|
||||
$create->addCC($cc);
|
||||
}
|
||||
}
|
||||
|
||||
if (array_key_exists('to', $activity)) {
|
||||
foreach ($activity['to'] as $to) {
|
||||
$create->addTo($to);
|
||||
}
|
||||
}
|
||||
|
||||
switch ($obj['type']) {
|
||||
case 'Note':
|
||||
$apNote = new \Federator\Data\ActivityPub\Common\Note();
|
||||
$apNote->setID($obj['id'])
|
||||
->setPublished(strtotime($obj['published'] ?? 'now'))
|
||||
->setContent($obj['content'] ?? '')
|
||||
->setSummary($obj['summary'])
|
||||
->setURL($obj['url'])
|
||||
->setAttributedTo($obj['attributedTo'] ?? $activity['actor'])
|
||||
->addTo("https://www.w3.org/ns/activitystreams#Public");
|
||||
|
||||
if (!empty($obj['sensitive'])) {
|
||||
$apNote->setSensitive($obj['sensitive']);
|
||||
}
|
||||
if (!empty($obj['conversation'])) {
|
||||
$apNote->setConversation($obj['conversation']);
|
||||
}
|
||||
if (!empty($obj['inReplyTo'])) {
|
||||
$apNote->setInReplyTo($obj['inReplyTo']);
|
||||
}
|
||||
|
||||
// Handle attachments
|
||||
if (!empty($obj['attachment']) && is_array($obj['attachment'])) {
|
||||
foreach ($obj['attachment'] as $media) {
|
||||
if (!isset($media['type'], $media['url']))
|
||||
continue;
|
||||
$mediaObj = new \Federator\Data\ActivityPub\Common\APObject($media['type']);
|
||||
$mediaObj->setURL($media['url']);
|
||||
$apNote->addAttachment($mediaObj);
|
||||
}
|
||||
}
|
||||
|
||||
if (array_key_exists('tag', $obj)) {
|
||||
foreach ($obj['tag'] as $tag) {
|
||||
$tagName = is_array($tag) && isset($tag['name']) ? $tag['name'] : (string) $tag;
|
||||
$cleanName = preg_replace('/\s+/', '', ltrim($tagName, '#')); // Remove space and leading #
|
||||
$tagObj = new \Federator\Data\ActivityPub\Common\Tag();
|
||||
$tagObj->setName('#' . $cleanName)
|
||||
->setHref("https://$host/tags/" . urlencode($cleanName))
|
||||
->setType('Hashtag');
|
||||
$apNote->addTag($tagObj);
|
||||
}
|
||||
}
|
||||
|
||||
$create->setObject($apNote);
|
||||
break;
|
||||
default:
|
||||
error_log("Inbox::post we currently don't support the obj type " . $obj['type'] . "\n");
|
||||
break;
|
||||
}
|
||||
|
||||
$inboxActivity = $create;
|
||||
|
||||
break;
|
||||
case 'Announce':
|
||||
if (!isset($activity['object'])) {
|
||||
break;
|
||||
}
|
||||
|
||||
$objectURL = is_array($activity['object']) ? $activity['object']['id'] : $activity['object'];
|
||||
|
||||
// Fetch the original object (e.g. Note)
|
||||
[$response, $info] = \Federator\Main::getFromRemote($objectURL, ['Accept: application/activity+json']);
|
||||
if ($info['http_code'] != 200) {
|
||||
print_r($info);
|
||||
error_log("Inbox::post Failed to fetch original object for Announce: $objectURL\n");
|
||||
break;
|
||||
}
|
||||
$objData = json_decode($response, true);
|
||||
if ($objData === false || $objData === null || !is_array($objData)) {
|
||||
break;
|
||||
}
|
||||
|
||||
$announce = new \Federator\Data\ActivityPub\Common\Announce();
|
||||
$announce->setID($activity['id'])
|
||||
->setURL($activity['id'])
|
||||
->setPublished(strtotime($activity['published'] ?? 'now'))
|
||||
->setAActor($activity['actor']);
|
||||
|
||||
if (array_key_exists('cc', $activity)) {
|
||||
foreach ($activity['cc'] as $cc) {
|
||||
$announce->addCC($cc);
|
||||
}
|
||||
}
|
||||
if (array_key_exists('to', $activity)) {
|
||||
foreach ($activity['to'] as $to) {
|
||||
$announce->addTo($to);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the shared object as a Note or something else
|
||||
switch ($objData['type']) {
|
||||
case 'Note':
|
||||
$note = new \Federator\Data\ActivityPub\Common\Note();
|
||||
$note->setID($objData['id'])
|
||||
->setContent($objData['content'] ?? '')
|
||||
->setPublished(strtotime($objData['published'] ?? 'now'))
|
||||
->setURL($objData['url'] ?? $objData['id'])
|
||||
->setAttributedTo($objData['attributedTo'] ?? null)
|
||||
->addTo("https://www.w3.org/ns/activitystreams#Public");
|
||||
|
||||
if (array_key_exists('cc', $objData)) {
|
||||
foreach ($objData['cc'] as $cc) {
|
||||
$note->addCC($cc);
|
||||
}
|
||||
}
|
||||
$announce->setObject($note);
|
||||
break;
|
||||
default:
|
||||
// fallback object
|
||||
$fallback = new \Federator\Data\ActivityPub\Common\APObject($objData['type']);
|
||||
$fallback->setID($objData['id'] ?? $objectURL);
|
||||
$announce->setObject($fallback);
|
||||
break;
|
||||
}
|
||||
|
||||
$inboxActivity = $announce;
|
||||
break;
|
||||
case 'Undo':
|
||||
if (!isset($activity['object'])) {
|
||||
break;
|
||||
}
|
||||
|
||||
$undo = new \Federator\Data\ActivityPub\Common\Undo();
|
||||
$undo->setID($activity['id'] ?? "test")
|
||||
->setURL($activity['url'] ?? $activity['id'])
|
||||
->setActor($activity['actor'] ?? null);
|
||||
|
||||
if (array_key_exists('cc', $activity)) {
|
||||
foreach ($activity['cc'] as $cc) {
|
||||
$undo->addCC($cc);
|
||||
}
|
||||
}
|
||||
if (array_key_exists('to', $activity)) {
|
||||
foreach ($activity['to'] as $to) {
|
||||
$undo->addTo($to);
|
||||
}
|
||||
}
|
||||
|
||||
// what was undone
|
||||
$undone = $activity['object'];
|
||||
if (is_array($undone) && isset($undone['type'])) {
|
||||
switch ($undone['type']) {
|
||||
case 'Announce':
|
||||
$announce = new \Federator\Data\ActivityPub\Common\Announce();
|
||||
$announce->setID($undone['id'] ?? null)
|
||||
->setAActor($undone['actor'] ?? null)
|
||||
->setURL($undone['url'] ?? $undone['id'])
|
||||
->setPublished(strtotime($undone['published'] ?? 'now'));
|
||||
|
||||
if (array_key_exists('cc', $undone)) {
|
||||
foreach ($undone['cc'] as $cc) {
|
||||
$announce->addCC($cc);
|
||||
}
|
||||
}
|
||||
$undo->setObject($announce);
|
||||
break;
|
||||
case 'Follow':
|
||||
// Implement if needed
|
||||
break;
|
||||
default:
|
||||
// Fallback for unknown types
|
||||
$apObject = new \Federator\Data\ActivityPub\Common\APObject($undone['type']);
|
||||
$apObject->setID($undone['id'] ?? null);
|
||||
$undo->setObject($apObject);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$inboxActivity = $undo;
|
||||
break;
|
||||
default:
|
||||
error_log("Inbox::post we currently don't support the activity type " . $activity['type'] . "\n");
|
||||
break;
|
||||
}
|
||||
|
||||
// Shared inbox
|
||||
if (!$_user) {
|
||||
$rootDir = $_SERVER['DOCUMENT_ROOT'] . '../';
|
||||
file_put_contents(
|
||||
$rootDir . 'logs/inbox.log',
|
||||
date('Y-m-d H:i:s') . ": ==== WILL TRY WORK WITH ACTIVITY ====\n" . json_encode($activity, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . "\n\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
|
||||
// Save the raw input and parsed JSON to a file for inspection
|
||||
file_put_contents(
|
||||
$rootDir . 'logs/inbox.log',
|
||||
date('Y-m-d H:i:s') . ": ==== POST Inbox Activity ====\n" . json_encode($inboxActivity, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . "\n\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
}
|
||||
|
||||
$sendTo = $inboxActivity->getCC();
|
||||
if ($inboxActivity->getType() === 'Undo') {
|
||||
$sendTo = $inboxActivity->getObject()->getCC();
|
||||
}
|
||||
|
||||
$users = [];
|
||||
|
||||
foreach ($sendTo as $receiver) {
|
||||
if (!$receiver || !is_string($receiver)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str_ends_with($receiver, '/followers')) {
|
||||
$users = array_merge($users, $this->fetchAllFollowers($receiver, $host));
|
||||
}
|
||||
}
|
||||
if ($_user !== false && in_array($_user, $users)) {
|
||||
$users[] = $_user;
|
||||
}
|
||||
foreach ($users as $user) {
|
||||
if (!$user)
|
||||
continue;
|
||||
|
||||
$this->postForUser($user, $inboxActivity);
|
||||
}
|
||||
return json_encode($inboxActivity, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
/**
|
||||
* handle post call for specific user
|
||||
*
|
||||
* @param string $_user user to add data to inbox
|
||||
* @param \Federator\Data\ActivityPub\Common\Activity $inboxActivity the activity that we received
|
||||
* @return string|false response
|
||||
*/
|
||||
private function postForUser($_user, $inboxActivity)
|
||||
{
|
||||
if ($_user) {
|
||||
$dbh = $this->main->getDatabase();
|
||||
$cache = $this->main->getCache();
|
||||
$connector = $this->main->getConnector();
|
||||
|
||||
// get user
|
||||
$user = \Federator\DIO\User::getUserByName(
|
||||
$dbh,
|
||||
$_user,
|
||||
$connector,
|
||||
$cache
|
||||
);
|
||||
if ($user->id === null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$rootDir = $_SERVER['DOCUMENT_ROOT'] . '../';
|
||||
// Save the raw input and parsed JSON to a file for inspection
|
||||
file_put_contents(
|
||||
$rootDir . 'logs/inbox_' . $_user . '.log',
|
||||
date('Y-m-d H:i:s') . ": ==== POST " . $_user . " Inbox Activity ====\n" . json_encode($inboxActivity, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . "\n\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* fetch all followers from url and return the ones that belong to our server
|
||||
*
|
||||
* @param string $collectionUrl The url of f.e. the posters followers
|
||||
* @param string $host our current host-url
|
||||
* @return array|false the names of the followers that are hosted on our server
|
||||
*/
|
||||
private function fetchAllFollowers(string $collectionUrl, string $host): array
|
||||
{
|
||||
$users = [];
|
||||
|
||||
[$collectionResponse, $collectionInfo] = \Federator\Main::getFromRemote($collectionUrl, ['Accept: application/activity+json']);
|
||||
if ($collectionInfo['http_code'] !== 200) {
|
||||
error_log("Inbox::fetchAllFollowers Failed to fetch follower collection metadata from $collectionUrl");
|
||||
return [];
|
||||
}
|
||||
|
||||
$collectionData = json_decode($collectionResponse, true);
|
||||
$nextPage = $collectionData['first'] ?? $collectionData['current'] ?? null;
|
||||
|
||||
if (!$nextPage) {
|
||||
error_log("Inbox::fetchAllFollowers No 'first' or 'current' page in collection at $collectionUrl");
|
||||
return [];
|
||||
}
|
||||
|
||||
// Loop through all pages
|
||||
while ($nextPage) {
|
||||
[$pageResponse, $pageInfo] = \Federator\Main::getFromRemote($nextPage, ['Accept: application/activity+json']);
|
||||
if ($pageInfo['http_code'] !== 200) {
|
||||
error_log("Inbox::fetchAllFollowers Failed to fetch follower page at $nextPage");
|
||||
break;
|
||||
}
|
||||
|
||||
$pageData = json_decode($pageResponse, true);
|
||||
$items = $pageData['orderedItems'] ?? $pageData['items'] ?? [];
|
||||
|
||||
foreach ($items as $followerUrl) {
|
||||
$parts = parse_url($followerUrl);
|
||||
if (!isset($parts['host']) || !str_ends_with($parts['host'], $host)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[$actorResponse, $actorInfo] = \Federator\Main::getFromRemote($followerUrl, ['Accept: application/activity+json']);
|
||||
if ($actorInfo['http_code'] !== 200) {
|
||||
error_log("Inbox::fetchAllFollowers Failed to fetch actor data for follower: $followerUrl");
|
||||
continue;
|
||||
}
|
||||
|
||||
$actorData = json_decode($actorResponse, true);
|
||||
if (isset($actorData['preferredUsername'])) {
|
||||
$users[] = $actorData['preferredUsername'];
|
||||
}
|
||||
}
|
||||
|
||||
$nextPage = $pageData['next'] ?? null;
|
||||
}
|
||||
|
||||
return $users;
|
||||
}
|
||||
}
|
|
@ -23,7 +23,7 @@ class Dummy implements \Federator\Api\APIInterface
|
|||
/**
|
||||
* internal message to output
|
||||
*
|
||||
* @var array<string, mixed> $message
|
||||
* @var string $response
|
||||
*/
|
||||
private $message = [];
|
||||
|
||||
|
@ -44,29 +44,100 @@ class Dummy implements \Federator\Api\APIInterface
|
|||
* @param \Federator\Data\User|false $user user who is calling us
|
||||
* @return bool true on success
|
||||
*/
|
||||
public function exec($paths, $user) : bool
|
||||
public function exec($paths, $user): bool
|
||||
{
|
||||
// only for user with the 'publish' permission
|
||||
if ($user === false || $user->hasPermission('publish') === false) {
|
||||
throw new \Federator\Exceptions\PermissionDenied();
|
||||
}
|
||||
// if ($user === false || $user->hasPermission('publish') === false) {
|
||||
// throw new \Federator\Exceptions\PermissionDenied();
|
||||
// }
|
||||
$method = $_SERVER["REQUEST_METHOD"];
|
||||
switch ($method) {
|
||||
case 'GET':
|
||||
switch (sizeof($paths)) {
|
||||
case 3:
|
||||
if ($paths[2] === 'moo') {
|
||||
return $this->getDummy();
|
||||
switch ($paths[2]) {
|
||||
case 'moo':
|
||||
return $this->getDummy();
|
||||
case 'sharedInbox':
|
||||
return $this->getSharedInbox();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
case 5:
|
||||
switch ($paths[2]) {
|
||||
case 'inbox':
|
||||
return $this->getInbox($paths[3]);
|
||||
case 'follow':
|
||||
return $this->followAdmin($paths[3]);
|
||||
case 'users':
|
||||
switch (sizeof($paths)) {
|
||||
case 4:
|
||||
return $this->getUser($paths[3]);
|
||||
case 5:
|
||||
switch ($paths[4]) {
|
||||
case 'inbox':
|
||||
return $this->getInbox($paths[3]);
|
||||
case 'outbox':
|
||||
return $this->getOutbox($paths[3]);
|
||||
case 'following':
|
||||
return $this->getFollowing($paths[3]);
|
||||
case 'followers':
|
||||
return $this->getFollowing($paths[3]);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'POST':
|
||||
switch (sizeof($paths)) {
|
||||
case 3:
|
||||
if ($paths[2] === 'moo') {
|
||||
return $this->postDummy();
|
||||
switch ($paths[2]) {
|
||||
case 'moo':
|
||||
return $this->postDummy();
|
||||
case 'sharedInbox':
|
||||
return $this->postSharedInbox();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
case 5:
|
||||
switch ($paths[2]) {
|
||||
case 'inbox':
|
||||
return $this->postInbox($paths[3]);
|
||||
case 'follow':
|
||||
return $this->followAdmin($paths[3]);
|
||||
case 'users':
|
||||
switch (sizeof($paths)) {
|
||||
case 5:
|
||||
switch ($paths[4]) {
|
||||
case 'inbox':
|
||||
return $this->postInbox($paths[3]);
|
||||
case 'outbox':
|
||||
return $this->postOutbox($paths[3]);
|
||||
case 'following':
|
||||
return $this->postFollowing($paths[3]);
|
||||
case 'followers':
|
||||
return $this->postFollowing($paths[3]);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->main->setResponseCode(404);
|
||||
|
@ -80,16 +151,190 @@ class Dummy implements \Federator\Api\APIInterface
|
|||
*/
|
||||
public function getDummy()
|
||||
{
|
||||
$this->message = [
|
||||
$this->message = json_encode([
|
||||
'r1' => ' (__) ',
|
||||
'r2' => ' `------(oo) ',
|
||||
'r3' => ' || __ (__) ',
|
||||
'r4' => ' ||w || ',
|
||||
'r5' => ' '
|
||||
];
|
||||
], JSON_PRETTY_PRINT);
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getUser($_name)
|
||||
{
|
||||
error_log("Someone tried to get user: " . $_name);
|
||||
$user = \Federator\DIO\User::getUserByName(
|
||||
$this->main->getDatabase(),
|
||||
$_name,
|
||||
$this->main->getConnector(),
|
||||
$this->main->getCache()
|
||||
);
|
||||
if ($user === false || $user->id === null) {
|
||||
throw new \Federator\Exceptions\FileNotFound();
|
||||
}
|
||||
$publicKeyPem = <<<PEM
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1MDmIPDcTey9lNYicfho
|
||||
u3EeVKeQkm1FkFl4Yoj1FW0SFyGkgtPr8hgAL1JIqyrFokgbPRtihmhTUHaQNoV8
|
||||
Uj5UIKG6zM1y1dHWizqwQw13pSMWri3IcSf08GSiolYBb19A98EMzIyGZHzjlfw8
|
||||
VAhW6qL6ML5YAR2YvckRRpS4pPVseQLHfDzkWlyXePJQInMai0kdrH39XiXw8B0C
|
||||
ver+7I1Z3rzJu+iOLmlblekFJtWDiipjuMedzluL3mNwV9Lk1ka1m7vHrtyqjtv1
|
||||
X5FLRXVzpFziJsIpWZ6ojU9KRX8l4yvv9FL4dZIn7edbcosvnNDpnvEl+NsGnf4R
|
||||
1wIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
PEM;
|
||||
$publicKeyPemJsonSafe = json_encode($publicKeyPem); // gives string with \n inside
|
||||
$data = [
|
||||
'iconMediaType' => $user->iconMediaType,
|
||||
'iconURL' => $user->iconURL,
|
||||
'imageMediaType' => $user->imageMediaType,
|
||||
'imageURL' => $user->imageURL,
|
||||
'fqdn' => '192.168.178.143',
|
||||
'name' => $user->name,
|
||||
'username' => $user->id,
|
||||
'publickey' => "<placeholderPublicKey>",
|
||||
'registered' => gmdate('Y-m-d\TH:i:s\Z', $user->registered), // 2021-03-25T00:00:00Z
|
||||
'summary' => $user->summary,
|
||||
'type' => "Person"
|
||||
];
|
||||
$this->message = $this->main->renderTemplate('user.json', $data);
|
||||
$fixedJson = str_replace(
|
||||
'https://192.168.178.143/users/yannis_test',
|
||||
'https://192.168.178.143/api/federator/v1/dummy/users/yannis_test',
|
||||
$this->message
|
||||
);
|
||||
$fixedJson = preg_replace(
|
||||
'/"id"\s*:\s*"[^"]+"/',
|
||||
'"id": "http://192.168.178.143/api/federator/v1/dummy/users/yannis_test"',
|
||||
$fixedJson
|
||||
);
|
||||
$fixedJson = preg_replace(
|
||||
'/"inbox"\s*:\s*"[^"]+"/',
|
||||
'"inbox": "http://192.168.178.143/users/yannis_test/inbox"',
|
||||
$fixedJson
|
||||
);
|
||||
$fixedJson = str_replace(
|
||||
'https://192.168.178.143',
|
||||
'http://192.168.178.143',
|
||||
$fixedJson
|
||||
);
|
||||
$fixedJson = str_replace(
|
||||
'"<placeholderPublicKey>"',
|
||||
$publicKeyPemJsonSafe,
|
||||
$fixedJson
|
||||
);
|
||||
$fixedJson = str_replace(
|
||||
'http://192.168.178.143/inbox',
|
||||
'http://192.168.178.143/api/federator/fedusers/inbox',
|
||||
$fixedJson
|
||||
);
|
||||
// $fixedJson = str_replace(
|
||||
// 'http://192.168.178.143/api/federator/v1/dummy/users/yannis_test@192.168.178.143#main-key',
|
||||
// 'http://192.168.178.143/api/federator/v1/dummy/users/yannis_test@192.168.178.143/key#main-key',
|
||||
// $fixedJson
|
||||
// );
|
||||
|
||||
$this->message = $fixedJson;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function followAdmin($_name)
|
||||
{
|
||||
$user = \Federator\DIO\User::getUserByName(
|
||||
$this->main->getDatabase(),
|
||||
$_name,
|
||||
$this->main->getConnector(),
|
||||
$this->main->getCache()
|
||||
);
|
||||
if ($user === false || $user->id === null) {
|
||||
throw new \Federator\Exceptions\FileNotFound();
|
||||
}
|
||||
|
||||
|
||||
// Step 2: Prepare the Follow activity
|
||||
$activityData = [
|
||||
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||
'type' => 'Follow',
|
||||
'actor' => 'http://192.168.178.143/api/federator/v1/dummy/users/' . $_name, // Your user URL
|
||||
'object' => 'http://mastodon.local/users/admin' // Mastodon user to follow (e.g., http://mastodon.local/users/admin)
|
||||
];
|
||||
|
||||
// Step 3: Send the Follow activity to Mastodon
|
||||
$inboxUrl = 'http://mastodon.local/users/admin/inbox'; // The inbox URL for the Mastodon user
|
||||
$this->sendFollowActivityToMastodon($inboxUrl, $activityData);
|
||||
|
||||
$this->message = "\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
private function sendFollowActivityToMastodon($url, $data)
|
||||
{
|
||||
$json = json_encode($data, JSON_UNESCAPED_SLASHES);
|
||||
$digest = 'SHA-256=' . base64_encode(hash('sha256', $json, true));
|
||||
$date = gmdate('D, d M Y H:i:s') . ' GMT';
|
||||
$parsed = parse_url($url);
|
||||
$host = $parsed['host'];
|
||||
$path = $parsed['path'];
|
||||
|
||||
// Build the signature string
|
||||
$signatureString = "(request-target): post {$path}\n" .
|
||||
"host: {$host}\n" .
|
||||
"date: {$date}\n" .
|
||||
"digest: {$digest}";
|
||||
|
||||
// Load your private key here (replace with how you store keys)
|
||||
$privateKey = "REDACTED"; // OR from DB
|
||||
$pkeyId = openssl_pkey_get_private($privateKey);
|
||||
|
||||
if (!$pkeyId) {
|
||||
throw new \Exception('Invalid private key');
|
||||
}
|
||||
|
||||
openssl_sign($signatureString, $signature, $pkeyId, OPENSSL_ALGO_SHA256);
|
||||
$signature_b64 = base64_encode($signature);
|
||||
|
||||
// Build keyId (public key ID from your actor object)
|
||||
$keyId = 'http://192.168.178.143/api/federator/v1/dummy/users/yannis_test#main-key';
|
||||
|
||||
$signatureHeader = 'keyId="' . $keyId . '",algorithm="rsa-sha256",headers="(request-target) host date digest",signature="' . $signature_b64 . '"';
|
||||
|
||||
$headers = [
|
||||
'Host: ' . $host,
|
||||
'Date: ' . $date,
|
||||
'Digest: ' . $digest,
|
||||
'Content-Type: application/activity+json',
|
||||
'Signature: ' . $signatureHeader,
|
||||
'Accept: application/activity+json',
|
||||
];
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $json);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$err = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
// Log the response for debugging if needed
|
||||
if ($response === false) {
|
||||
error_log("Failed to send Follow activity to Mastodon: " . curl_error($ch));
|
||||
echo "Failed to send Follow activity to Mastodon: " . curl_error($ch);
|
||||
} else {
|
||||
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
if ($httpcode !== 200 && $httpcode !== 202) {
|
||||
throw new \Exception("Unexpected HTTP code $httpcode: $response");
|
||||
}
|
||||
error_log("Follow activity response from Mastodon: " . $response);
|
||||
echo "Follow activity response from Mastodon: " . $response;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* post function for /v1/dummy/moo"
|
||||
*
|
||||
|
@ -100,6 +345,216 @@ class Dummy implements \Federator\Api\APIInterface
|
|||
return $this->getDummy();
|
||||
}
|
||||
|
||||
public function getInbox($_name)
|
||||
{
|
||||
$_rawInput = file_get_contents('php://input');
|
||||
|
||||
// Decode if it's JSON (as Mastodon usually sends JSON)
|
||||
$jsonData = json_decode($_rawInput, true);
|
||||
error_log("=== Masto GET Inbox Raw ===\n" . $_rawInput);
|
||||
error_log("=== Masto GET Inbox JSON ===\n" . print_r($jsonData, true));
|
||||
// Save the raw input and parsed JSON to a file for inspection
|
||||
file_put_contents(
|
||||
__DIR__ . '/inbox_log.txt',
|
||||
time() . ": ==== Masto GET Inbox RAW ====\n" . $_rawInput . "\n\n==== Masto GET Inbox JSON ====\n" . print_r($jsonData, true) . "\n\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
|
||||
$this->message = json_encode([
|
||||
'status' => 'received',
|
||||
]);
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getOutbox($_name)
|
||||
{
|
||||
$_rawInput = file_get_contents('php://input');
|
||||
|
||||
// Decode if it's JSON (as Mastodon usually sends JSON)
|
||||
$jsonData = json_decode($_rawInput, true);
|
||||
error_log("=== Masto GET Outbox Raw ===\n" . $_rawInput);
|
||||
error_log("=== Masto GET Outbox JSON ===\n" . print_r($jsonData, true));
|
||||
// Save the raw input and parsed JSON to a file for inspection
|
||||
file_put_contents(
|
||||
__DIR__ . '/outbox_log.txt',
|
||||
time() . ": ==== Masto GET Outbox RAW ====\n" . $_rawInput . "\n\n==== Masto GET Outbox JSON ====\n" . print_r($jsonData, true) . "\n\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
|
||||
$this->message = json_encode([
|
||||
'status' => 'received',
|
||||
]);
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getFollowing($_name)
|
||||
{
|
||||
$_rawInput = file_get_contents('php://input');
|
||||
|
||||
// Decode if it's JSON (as Mastodon usually sends JSON)
|
||||
$jsonData = json_decode($_rawInput, true);
|
||||
error_log("=== Masto GET Following Raw ===\n" . $_rawInput);
|
||||
error_log("=== Masto GET Following JSON ===\n" . print_r($jsonData, true));
|
||||
// Save the raw input and parsed JSON to a file for inspection
|
||||
file_put_contents(
|
||||
__DIR__ . '/following_log.txt',
|
||||
time() . ": ==== Masto GET Following RAW ====\n" . $_rawInput . "\n\n==== Masto GET Following JSON ====\n" . print_r($jsonData, true) . "\n\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
|
||||
$this->message = json_encode([
|
||||
'status' => 'received',
|
||||
]);
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getFollowers($_name)
|
||||
{
|
||||
$_rawInput = file_get_contents('php://input');
|
||||
|
||||
// Decode if it's JSON (as Mastodon usually sends JSON)
|
||||
$jsonData = json_decode($_rawInput, true);
|
||||
error_log("=== Masto GET Followers Raw ===\n" . $_rawInput);
|
||||
error_log("=== Masto GET Followers JSON ===\n" . print_r($jsonData, true));
|
||||
// Save the raw input and parsed JSON to a file for inspection
|
||||
file_put_contents(
|
||||
__DIR__ . '/followers_log.txt',
|
||||
time() . ": ==== Masto GET Followers RAW ====\n" . $_rawInput . "\n\n==== Masto GET Followers JSON ====\n" . print_r($jsonData, true) . "\n\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
|
||||
$this->message = json_encode([
|
||||
'status' => 'received',
|
||||
]);
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getSharedInbox()
|
||||
{
|
||||
$_rawInput = file_get_contents('php://input');
|
||||
|
||||
// Decode if it's JSON (as Mastodon usually sends JSON)
|
||||
$jsonData = json_decode($_rawInput, true);
|
||||
error_log("=== Masto GET SharedInbox Raw ===\n" . $_rawInput);
|
||||
error_log("=== Masto GET SharedInbox JSON ===\n" . print_r($jsonData, true));
|
||||
// Save the raw input and parsed JSON to a file for inspection
|
||||
file_put_contents(
|
||||
__DIR__ . '/sharedInbox_log.txt',
|
||||
time() . ": ==== Masto GET SharedInbox RAW ====\n" . $_rawInput . "\n\n==== Masto GET SharedInbox JSON ====\n" . print_r($jsonData, true) . "\n\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
|
||||
$this->message = json_encode([
|
||||
'status' => 'received',
|
||||
]);
|
||||
return true;
|
||||
}
|
||||
|
||||
public function postInbox($_name)
|
||||
{
|
||||
$_rawInput = file_get_contents('php://input');
|
||||
|
||||
// Decode if it's JSON (as Mastodon usually sends JSON)
|
||||
$jsonData = json_decode($_rawInput, true);
|
||||
error_log("=== Masto POST Inbox Raw ===\n" . $_rawInput);
|
||||
error_log("=== Masto POST Inbox JSON ===\n" . print_r($jsonData, true));
|
||||
// Save the raw input and parsed JSON to a file for inspection
|
||||
file_put_contents(
|
||||
__DIR__ . '/inbox_log.txt',
|
||||
time() . ": ==== Masto POST Inbox RAW ====\n" . $_rawInput . "\n\n==== Masto POST Inbox JSON ====\n" . print_r($jsonData, true) . "\n\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
|
||||
$this->message = json_encode([
|
||||
'status' => 'received',
|
||||
]);
|
||||
return true;
|
||||
}
|
||||
|
||||
public function postOutbox($_name)
|
||||
{
|
||||
$_rawInput = file_get_contents('php://input');
|
||||
|
||||
// Decode if it's JSON (as Mastodon usually sends JSON)
|
||||
$jsonData = json_decode($_rawInput, true);
|
||||
error_log("=== Masto POST Outbox Raw ===\n" . $_rawInput);
|
||||
error_log("=== Masto POST Outbox JSON ===\n" . print_r($jsonData, true));
|
||||
// Save the raw input and parsed JSON to a file for inspection
|
||||
file_put_contents(
|
||||
__DIR__ . '/outbox_log.txt',
|
||||
time() . ": ==== Masto POST Outbox RAW ====\n" . $_rawInput . "\n\n==== Masto POST Outbox JSON ====\n" . print_r($jsonData, true) . "\n\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
|
||||
$this->message = json_encode([
|
||||
'status' => 'received',
|
||||
]);
|
||||
return true;
|
||||
}
|
||||
|
||||
public function postFollowing($_name)
|
||||
{
|
||||
$_rawInput = file_get_contents('php://input');
|
||||
|
||||
// Decode if it's JSON (as Mastodon usually sends JSON)
|
||||
$jsonData = json_decode($_rawInput, true);
|
||||
error_log("=== Masto POST Following Raw ===\n" . $_rawInput);
|
||||
error_log("=== Masto POST Following JSON ===\n" . print_r($jsonData, true));
|
||||
// Save the raw input and parsed JSON to a file for inspection
|
||||
file_put_contents(
|
||||
__DIR__ . '/following_log.txt',
|
||||
time() . ": ==== Masto POST Following RAW ====\n" . $_rawInput . "\n\n==== Masto POST Following JSON ====\n" . print_r($jsonData, true) . "\n\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
|
||||
$this->message = json_encode([
|
||||
'status' => 'received',
|
||||
]);
|
||||
return true;
|
||||
}
|
||||
|
||||
public function postFollowers($_name)
|
||||
{
|
||||
$_rawInput = file_get_contents('php://input');
|
||||
|
||||
// Decode if it's JSON (as Mastodon usually sends JSON)
|
||||
$jsonData = json_decode($_rawInput, true);
|
||||
error_log("=== Masto POST Followers Raw ===\n" . $_rawInput);
|
||||
error_log("=== Masto POST Followers JSON ===\n" . print_r($jsonData, true));
|
||||
// Save the raw input and parsed JSON to a file for inspection
|
||||
file_put_contents(
|
||||
__DIR__ . '/followers_log.txt',
|
||||
time() . ": ==== Masto POST Followers RAW ====\n" . $_rawInput . "\n\n==== Masto POST Followers JSON ====\n" . print_r($jsonData, true) . "\n\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
|
||||
$this->message = json_encode([
|
||||
'status' => 'received',
|
||||
]);
|
||||
return true;
|
||||
}
|
||||
|
||||
public function postSharedInbox()
|
||||
{
|
||||
$_rawInput = file_get_contents('php://input');
|
||||
|
||||
// Decode if it's JSON (as Mastodon usually sends JSON)
|
||||
$jsonData = json_decode($_rawInput, true);
|
||||
error_log("=== Masto POST SharedInbox Raw ===\n" . $_rawInput);
|
||||
error_log("=== Masto POST SharedInbox JSON ===\n" . print_r($jsonData, true));
|
||||
// Save the raw input and parsed JSON to a file for inspection
|
||||
file_put_contents(
|
||||
__DIR__ . '/sharedInbox_log.txt',
|
||||
time() . ": ==== Masto POST SharedInbox RAW ====\n" . $_rawInput . "\n\n==== Masto POST SharedInbox JSON ====\n" . print_r($jsonData, true) . "\n\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
|
||||
$this->message = json_encode([
|
||||
'status' => 'received',
|
||||
]);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* get internal represenation as json string
|
||||
*
|
||||
|
@ -107,6 +562,6 @@ class Dummy implements \Federator\Api\APIInterface
|
|||
*/
|
||||
public function toJson()
|
||||
{
|
||||
return json_encode($this->message, JSON_PRETTY_PRINT) . "\n";
|
||||
return $this->message;
|
||||
}
|
||||
}
|
||||
|
|
43
php/federator/data/activitypub/common/Announce.php
Normal file
43
php/federator/data/activitypub/common/Announce.php
Normal file
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Sascha Nitsch (grumpydeveloper) https://contentnation.net/@grumpydevelop
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* @author Sascha Nitsch (grumpydeveloper)
|
||||
**/
|
||||
|
||||
namespace Federator\Data\ActivityPub\Common;
|
||||
|
||||
class Announce extends Activity
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('Announce');
|
||||
parent::addContext('https://www.w3.org/ns/activitystreams');
|
||||
}
|
||||
|
||||
/**
|
||||
* convert internal state to php array
|
||||
*
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public function toObject()
|
||||
{
|
||||
$return = parent::toObject();
|
||||
$return['type'] = 'Announce';
|
||||
// overwrite id from url
|
||||
$return['id'] = $this->getURL();
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* create object from json
|
||||
*
|
||||
* @param array<string,mixed> $json input json
|
||||
* @return bool true on success
|
||||
*/
|
||||
public function fromJson($json)
|
||||
{
|
||||
return parent::fromJson($json);
|
||||
}
|
||||
}
|
43
php/federator/data/activitypub/common/Undo.php
Normal file
43
php/federator/data/activitypub/common/Undo.php
Normal file
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Sascha Nitsch (grumpydeveloper) https://contentnation.net/@grumpydevelop
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* @author Sascha Nitsch (grumpydeveloper)
|
||||
**/
|
||||
|
||||
namespace Federator\Data\ActivityPub\Common;
|
||||
|
||||
class Undo extends Activity
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('Undo');
|
||||
parent::addContext('https://www.w3.org/ns/activitystreams');
|
||||
}
|
||||
|
||||
/**
|
||||
* convert internal state to php array
|
||||
*
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public function toObject()
|
||||
{
|
||||
$return = parent::toObject();
|
||||
$return['type'] = 'Undo';
|
||||
// overwrite id from url
|
||||
$return['id'] = $this->getURL();
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* create object from json
|
||||
*
|
||||
* @param array<string,mixed> $json input json
|
||||
* @return bool true on success
|
||||
*/
|
||||
public function fromJson($json)
|
||||
{
|
||||
return parent::fromJson($json);
|
||||
}
|
||||
}
|
|
@ -262,7 +262,7 @@ class Main
|
|||
public function setConnector(Connector\Connector $connector) : void
|
||||
{
|
||||
if ($this->connector) {
|
||||
echo "main::setConnector Setting new connector will override old one.\n"; // TODO CHANGE TO LOG WARNING
|
||||
# echo "main::setConnector Setting new connector will override old one.\n"; // TODO CHANGE TO LOG WARNING
|
||||
}
|
||||
$this->connector = $connector;
|
||||
}
|
||||
|
@ -275,7 +275,7 @@ class Main
|
|||
public function setHost(string $host) : void
|
||||
{
|
||||
if ($this->host) {
|
||||
echo "main::setHost Setting new host will override old one.\n"; // TODO CHANGE TO LOG WARNING
|
||||
# echo "main::setHost Setting new host will override old one.\n"; // TODO CHANGE TO LOG WARNING
|
||||
}
|
||||
$this->host = $host;
|
||||
}
|
||||
|
|
|
@ -310,6 +310,6 @@ namespace Federator;
|
|||
function contentnation_load($main)
|
||||
{
|
||||
$cn = new Connector\ContentNation($main);
|
||||
echo "contentnation::contentnation_load Loaded new connector, adding to main\n"; // TODO change to proper log
|
||||
# echo "contentnation::contentnation_load Loaded new connector, adding to main\n"; // TODO change to proper log
|
||||
$main->setConnector($cn);
|
||||
}
|
||||
|
|
|
@ -87,6 +87,6 @@ namespace Federator;
|
|||
function dummy_load($main)
|
||||
{
|
||||
$dummy = new Connector\DummyConnector();
|
||||
echo "dummyconnector::dummy_load Loaded new connector, adding to main\n"; // TODO change to proper log
|
||||
# echo "dummyconnector::dummy_load Loaded new connector, adding to main\n"; // TODO change to proper log
|
||||
$main->setConnector($dummy);
|
||||
}
|
||||
|
|
|
@ -62,7 +62,10 @@ class Mastodon implements Connector
|
|||
{
|
||||
if (preg_match("#^([^@]+)@([^/]+)#", $userId, $matches) === 1) {
|
||||
$name = $matches[1];
|
||||
} else {
|
||||
$name = $userId;
|
||||
}
|
||||
|
||||
$remoteURL = $this->service . '/users/' . $name . '/outbox';
|
||||
if ($min !== '') {
|
||||
$remoteURL .= '&minTS=' . urlencode($min);
|
||||
|
@ -209,6 +212,61 @@ class Mastodon implements Connector
|
|||
|
||||
$posts[] = $create;
|
||||
|
||||
break;
|
||||
case 'Announce':
|
||||
if (!isset($activity['object'])) {
|
||||
break;
|
||||
}
|
||||
|
||||
$objectURL = is_array($activity['object']) ? $activity['object']['id'] : $activity['object'];
|
||||
|
||||
// Fetch the original object (e.g. Note)
|
||||
[$response, $info] = \Federator\Main::getFromRemote($objectURL, ['Accept: application/activity+json']);
|
||||
if ($info['http_code'] != 200) {
|
||||
print_r($info);
|
||||
echo "MastodonConnector::getRemotePostsByUser Failed to fetch original object for Announce: $objectURL\n";
|
||||
break;
|
||||
}
|
||||
$objData = json_decode($response, true);
|
||||
if ($objData === false || $objData === null || !is_array($objData)) {
|
||||
break;
|
||||
}
|
||||
|
||||
$announce = new \Federator\Data\ActivityPub\Common\Announce();
|
||||
$announce->setID($activity['id'])
|
||||
->setURL($activity['id'])
|
||||
->setPublished(strtotime($activity['published'] ?? 'now'))
|
||||
->setAActor($activity['actor'])
|
||||
->addTo("https://www.w3.org/ns/activitystreams#Public");
|
||||
|
||||
if (array_key_exists('to', $activity)) {
|
||||
foreach ($activity['to'] as $to) {
|
||||
$announce->addTo($to);
|
||||
}
|
||||
}
|
||||
|
||||
// Optionally parse the shared object as a Note or something else
|
||||
switch ($objData['type']) {
|
||||
case 'Note':
|
||||
$note = new \Federator\Data\ActivityPub\Common\Note();
|
||||
$note->setID($objData['id'])
|
||||
->setContent($objData['content'] ?? '')
|
||||
->setPublished(strtotime($objData['published'] ?? 'now'))
|
||||
->setURL($objData['url'] ?? $objData['id'])
|
||||
->setAttributedTo($objData['attributedTo'] ?? null)
|
||||
->addTo("https://www.w3.org/ns/activitystreams#Public");
|
||||
|
||||
$announce->setObject($note);
|
||||
break;
|
||||
default:
|
||||
// fallback object
|
||||
$fallback = new \Federator\Data\ActivityPub\Common\APObject($objData['type']);
|
||||
$fallback->setID($objData['id'] ?? $objectURL);
|
||||
$announce->setObject($fallback);
|
||||
break;
|
||||
}
|
||||
|
||||
$posts[] = $announce;
|
||||
break;
|
||||
default:
|
||||
echo "MastodonConnector::getRemotePostsByUser we currently don't support the activity type " . $activity['type'] . "\n";
|
||||
|
@ -269,7 +327,6 @@ class Mastodon implements Connector
|
|||
|
||||
// Mastodon lookup API endpoint
|
||||
$remoteURL = $this->service . '/api/v1/accounts/lookup?acct=' . urlencode($_name);
|
||||
|
||||
// Set headers
|
||||
$headers = ['Accept: application/json'];
|
||||
|
||||
|
@ -354,6 +411,6 @@ namespace Federator;
|
|||
function mastodon_load($main)
|
||||
{
|
||||
$mast = new Connector\Mastodon($main);
|
||||
echo "mastodon::mastodon_load Loaded new connector, adding to main\n"; // TODO change to proper log
|
||||
# echo "mastodon::mastodon_load Loaded new connector, adding to main\n"; // TODO change to proper log
|
||||
$main->setConnector($mast);
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue