diff --git a/php/federator/api/fedusers/inbox.php b/php/federator/api/fedusers/inbox.php index e951add..9e5dd71 100644 --- a/php/federator/api/fedusers/inbox.php +++ b/php/federator/api/fedusers/inbox.php @@ -77,6 +77,16 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface $user = $inboxActivity->getAActor(); // url of the sender https://contentnation.net/username $username = basename((string) (parse_url($user, PHP_URL_PATH) ?? '')); $domain = parse_url($user, PHP_URL_HOST); + $userId = $username . '@' . $domain; + $user = \Federator\DIO\FedUser::getUserByName( + $dbh, + $userId, + $cache + ); + if ($user === null || $user->id === null) { + error_log("Inbox::post couldn't find user: $userId"); + throw new \Federator\Exceptions\ServerError("Inbox::post couldn't find user: $userId"); + } $users = []; @@ -97,9 +107,26 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface && (filter_var($receiver, FILTER_VALIDATE_URL) !== false); }); - if (!in_array($username, $receivers, true)) { - $receivers[] = $username; + // Special handling for Follow and Undo follow activities + if (strtolower($inboxActivity->getType()) === 'follow') { + // For Follow, the object should hold the target + $object = $inboxActivity->getObject(); + if ($object !== null && is_string($object)) { + $receivers[] = $object; + } + } elseif (strtolower($inboxActivity->getType()) === 'undo') { + $object = $inboxActivity->getObject(); + if ($object !== null && is_object($object)) { + // For Undo, the objects object should hold the target + if (strtolower($object->getType()) === 'follow') { + $objObject = $object->getObject(); + if ($objObject !== null && is_string($objObject)) { + $receivers[] = $objObject; + } + } + } } + foreach ($receivers as $receiver) { if ($receiver === '' || !is_string($receiver)) { continue; @@ -136,14 +163,14 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface continue; } $receiverName = basename((string) (parse_url($receiver, PHP_URL_PATH) ?? '')); - $domain = parse_url($receiver, PHP_URL_HOST); - if ($receiverName === null || $domain === null) { + $ourDomain = parse_url($receiver, PHP_URL_HOST); + if ($receiverName === null || $ourDomain === null) { error_log("Inbox::post no receiverName or domain found for receiver: " . $receiver); continue; } - $receiver = $receiverName . '@' . $domain; + $receiver = $receiverName . '@' . $ourDomain; try { - $user = \Federator\DIO\User::getUserByName( + $localUser = \Federator\DIO\User::getUserByName( $dbh, $receiver, $connector, @@ -153,11 +180,11 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface error_log("Inbox::post get user by name: " . $receiver . ". Exception: " . $e->getMessage()); continue; } - if ($user === null || $user->id === null) { + if ($localUser === null || $localUser->id === null) { error_log("Inbox::post couldn't find user: $receiver"); continue; } - $users[] = $user->id; + $users[] = $localUser->id; } } @@ -172,15 +199,15 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface } foreach ($users as $receiver) { - if (!isset($user)) { + if (!isset($receiver)) { continue; } $token = \Resque::enqueue('inbox', 'Federator\\Jobs\\InboxJob', [ - 'user' => $username . '@' . $domain, + 'user' => $user->id, 'recipientId' => $receiver, 'activity' => $inboxActivity->toObject(), ]); - error_log("Inbox::post enqueued job for user: $user with token: $token"); + error_log("Inbox::post enqueued job for user: $user->id with token: $token"); } return "success"; } @@ -241,12 +268,7 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface $success = false; $actor = $inboxActivity->getAActor(); if ($actor !== '') { - $followerUsername = basename((string) (parse_url($actor, PHP_URL_PATH) ?? '')); - $followerDomain = parse_url($actor, PHP_URL_HOST); - if (is_string($followerDomain)) { - $followerId = "{$followerUsername}@{$followerDomain}"; - $success = \Federator\DIO\Followers::addFollow($dbh, $followerId, $user->id, $followerDomain); - } + $success = \Federator\DIO\Followers::addExternalFollow($dbh, $inboxActivity->getID(), $user->id, $recipient->id); } if ($success === false) { error_log("Inbox::postForUser: Failed to add follower for user $user->id"); @@ -273,12 +295,7 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface if ($object instanceof \Federator\Data\ActivityPub\Common\Activity) { $actor = $object->getAActor(); if ($actor !== '') { - $followerUsername = basename((string) (parse_url($actor, PHP_URL_PATH) ?? '')); - $followerDomain = parse_url($actor, PHP_URL_HOST); - if (is_string($followerDomain)) { - $followerId = "{$followerUsername}@{$followerDomain}"; - $success = \Federator\DIO\Followers::removeFollow($dbh, $followerId, $user->id); - } + $success = \Federator\DIO\Followers::removeFollow($dbh, $user->id, $recipient->id); } } if ($success === false) { diff --git a/php/federator/api/v1/newcontent.php b/php/federator/api/v1/newcontent.php index 9efd176..e5f63f7 100644 --- a/php/federator/api/v1/newcontent.php +++ b/php/federator/api/v1/newcontent.php @@ -276,21 +276,21 @@ class NewContent implements \Federator\Api\APIInterface switch ($type) { case 'Follow': - $success = false; + // $success = false; $actor = $newActivity->getAActor(); if ($actor !== '') { - $followerUsername = basename((string) (parse_url($actor, PHP_URL_PATH) ?? '')); - $followerDomain = parse_url($actor, PHP_URL_HOST); + // $followerUsername = basename((string) (parse_url($actor, PHP_URL_PATH) ?? '')); + // $followerDomain = parse_url($actor, PHP_URL_HOST); $newIdUrl = \Federator\DIO\Followers::generateNewFollowId($dbh, $host); $newActivity->setID($newIdUrl); - if (is_string($followerDomain)) { + /* if (is_string($followerDomain)) { $followerId = "{$followerUsername}@{$followerDomain}"; - // $success = \Federator\DIO\Followers::sendFollowRequest($dbh, $connector, $cache, $user->id, $followerId, $followerDomain); - } + $success = \Federator\DIO\Followers::sendFollowRequest($dbh, $connector, $cache, $user->id, $followerId, $followerDomain); + } */ } - if ($success === false) { + /* if ($success === false) { error_log("NewContent::postForUser Failed to add follower for user $user->id"); - } + } */ break; case 'Delete': diff --git a/php/federator/data/activitypub/common/follow.php b/php/federator/data/activitypub/common/follow.php index cafcfcf..a1afa2c 100644 --- a/php/federator/data/activitypub/common/follow.php +++ b/php/federator/data/activitypub/common/follow.php @@ -21,6 +21,11 @@ class Follow extends Activity $this->object = $object; } + public function getObject(): string + { + return $this->object; + } + public function __construct() { parent::__construct("Follow"); diff --git a/php/federator/dio/followers.php b/php/federator/dio/followers.php index f7358d5..ce0161e 100644 --- a/php/federator/dio/followers.php +++ b/php/federator/dio/followers.php @@ -388,6 +388,47 @@ class Followers return $idurl; // Return the generated follow ID } + /** + * add follow + * + * @param \mysqli $dbh database handle + * @param string $followId the follow ID to use (should be an external url) + * @param string $sourceUser source user id + * @param string $targetUserId target user id + * @return boolean true on success, false on failure + */ + public static function addExternalFollow($dbh, $followId, $sourceUser, $targetUserId) + { + // Check if we already follow this user + $sql = 'select id from follows where source_user = ? and target_user = ?'; + $stmt = $dbh->prepare($sql); + if ($stmt === false) { + throw new \Federator\Exceptions\ServerError("Followers::addExternalFollow Failed to prepare statement"); + } + $stmt->bind_param("ss", $sourceUser, $targetUserId); + $foundId = 0; + $ret = $stmt->bind_result($foundId); + $stmt->execute(); + if ($ret) { + $stmt->fetch(); + } + $stmt->close(); + if ($foundId != 0) { + return false; // Already following this user + } + + // Add follow with created_at timestamp + $sql = 'insert into follows (id, source_user, target_user, created_at) values (?, ?, ?, NOW())'; + $stmt = $dbh->prepare($sql); + if ($stmt === false) { + throw new \Federator\Exceptions\ServerError("Followers::addExternalFollow Failed to prepare insert statement"); + } + $stmt->bind_param("sss", $followId, $sourceUser, $targetUserId); + $stmt->execute(); + $stmt->close(); + return true; + } + /** * generate new follow id *