From 72b07245f44c73ed41bfcca2465a9ee34426a922 Mon Sep 17 00:00:00 2001 From: winter Date: Thu, 19 Dec 2024 20:59:18 +0000 Subject: implement follow relationships (untested) does NOT implement the actual behaviour behind follows (receiving posts and such) --- Digitigrade/Model/Actor.php | 67 +++++++++++++++++++++++++++++- Digitigrade/Model/FetchableModel.php | 6 +-- Digitigrade/Model/FollowRelation.php | 37 +++++++++++++++++ Digitigrade/Model/FollowRelationStatus.php | 7 ++++ 4 files changed, 112 insertions(+), 5 deletions(-) create mode 100644 Digitigrade/Model/FollowRelation.php create mode 100644 Digitigrade/Model/FollowRelationStatus.php (limited to 'Digitigrade/Model') diff --git a/Digitigrade/Model/Actor.php b/Digitigrade/Model/Actor.php index 4f3aab2..fa08463 100644 --- a/Digitigrade/Model/Actor.php +++ b/Digitigrade/Model/Actor.php @@ -1,7 +1,10 @@ actor; } + public static function findByRequestHeaders(): ?self { + $instance = Instance::findByRequestHeaders(); + if ($instance == null || !isset($_SERVER['HTTP_X_PAWPUB_ACTOR'])) + return null; + $uri = $_SERVER['HTTP_X_PAWPUB_ACTOR']; + if (hostname_from_uri($uri) != $instance->domain) + return null; + return self::findByUri($uri); + } + + protected function rpcCall(string $method, array $args) { + switch ($method) { + case 'follow': // $this is the target, $args[0] is the initiator + $status = $this->requestToFollow ? FollowRelationStatus::PENDING : FollowRelationStatus::ACTIVE; + if (!$this->isLocal) { + $endpoint = $this->endpoints->follow ?? null; + if (!isset($endpoint, $this->endpoints->unfollow)) { + throw new RpcException("Actor `$this->uri` has no (un)follow endpoint"); + } + $result = send_request_authenticated($endpoint, method: 'POST', actingAs: $args[0]); + if (!str_contains($result->headers[0], ' 20')) { + throw new RpcException('remote call did not indicate success: ' . $result->headers[0]); + } + } + FollowRelation::create($args[0], $this, $status); + return $status; + + case 'unfollow': + if (!$this->isLocal) { + $endpoint = $this->endpoints->unfollow ?? null; + if (!isset($endpoint, $this->endpoints->follow)) { + throw new RpcException("Actor `$this->uri` has no (un)follow endpoint"); + } + $result = send_request_authenticated($endpoint, method: 'POST', actingAs: $args[0]); + if (!str_contains($result->headers[0], ' 20')) { + throw new RpcException('remote call did not indicate success: ' . $result->headers[0]); + } + } + FollowRelation::findByActors($args[0], $this)->remove(); + + default: + throw new \RuntimeException("Actor rpc method $method not (yet?) implemented"); + } + } + + /** + * Tries to follow (or send a follow request to) another actor + * @param Actor $target the actor to follow + * @return FollowRelationStatus PENDING if a request was sent or ACTIVE if the follow went through immediately + */ + public function follow(self $target): FollowRelationStatus { + return $target->rpcCall('follow', [$this]); + } + + public function unfollow(self $target) { + $target->rpcCall('unfollow', [$this]); + } + public function jsonSerialize(): array { return [ 'type' => 'actor', @@ -97,7 +158,9 @@ class Actor extends PushableModel { 'automated' => $this->automated, 'endpoints' => [ 'basicFeed' => path_to_uri("/user/$this->handle/basicFeed"), - 'fullFeed' => path_to_uri("/user/$this->handle/fullFeed") + 'fullFeed' => path_to_uri("/user/$this->handle/fullFeed"), + 'follow' => path_to_uri("/user/$this->handle/follow"), + 'unfollow' => path_to_uri("/user/$this->handle/unfollow"), ] ]; } diff --git a/Digitigrade/Model/FetchableModel.php b/Digitigrade/Model/FetchableModel.php index 1120823..5e4d1e8 100644 --- a/Digitigrade/Model/FetchableModel.php +++ b/Digitigrade/Model/FetchableModel.php @@ -55,10 +55,10 @@ abstract class FetchableModel extends Model implements RemoteFetchable { protected static function fetchFromRemote(string $uri, bool $autoSave): ?static { // fetch the object // janky hack: if this is an Instance then avoid recursively fetching itself - $data = @get_remote_object_authenticated($uri, static::class == Instance::class); - if ($data === false) + $result = @send_request_authenticated($uri, static::class == Instance::class); + if (!str_contains($result->headers[0], '200')) return null; - $data = json_decode($data); + $data = json_decode($result->body); if ($data === null) return null; diff --git a/Digitigrade/Model/FollowRelation.php b/Digitigrade/Model/FollowRelation.php new file mode 100644 index 0000000..fc43a6f --- /dev/null +++ b/Digitigrade/Model/FollowRelation.php @@ -0,0 +1,37 @@ +subject = $subject; + $rel->object = $object; + $rel->status = $status; + $rel->save(); + return $rel; + } + + protected function getUpdateWhereClause(\PDO $db): ?string { + if (self::findWhere('subject = ? and object = ?', [$this->subject->id, $this->object->id]) != null) { + return 'subject = ' . $this->subject->id . ' and object = ' . $this->object->id; + } + return null; + } + + public static function findByActors(Actor $subject, Actor $object): ?self { + return self::findWhere('subject = ? and object = ?', [$subject->id, $object->id]); + } +} \ No newline at end of file diff --git a/Digitigrade/Model/FollowRelationStatus.php b/Digitigrade/Model/FollowRelationStatus.php new file mode 100644 index 0000000..24ba34b --- /dev/null +++ b/Digitigrade/Model/FollowRelationStatus.php @@ -0,0 +1,7 @@ +