aboutsummaryrefslogtreecommitdiffhomepage
path: root/Digitigrade/Model
diff options
context:
space:
mode:
Diffstat (limited to 'Digitigrade/Model')
-rw-r--r--Digitigrade/Model/Actor.php67
-rw-r--r--Digitigrade/Model/FetchableModel.php6
-rw-r--r--Digitigrade/Model/FollowRelation.php37
-rw-r--r--Digitigrade/Model/FollowRelationStatus.php7
4 files changed, 112 insertions, 5 deletions
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 @@
<?php
namespace Digitigrade\Model;
-class Actor extends PushableModel {
+use Digitigrade\RpcException;
+use Digitigrade\RpcReceiver;
+
+class Actor extends PushableModel implements RpcReceiver {
public ?int $id;
public string $uri;
public bool $isLocal = false;
@@ -83,6 +86,64 @@ class Actor extends PushableModel {
return ActorWebfinger::findByAcct($acct, $autoSave, $forceRefetch)->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 @@
+<?php
+namespace Digitigrade\Model;
+
+use Digitigrade\Model;
+
+class FollowRelation extends Model {
+ public Actor $subject;
+ public Actor $object;
+ public FollowRelationStatus $status;
+
+ /**
+ * Creates and saves a new follower/following relationship
+ * @param Actor $subject the follower
+ * @param Actor $object the followee
+ * @param FollowRelationStatus $status whether the relationship is pending or active
+ * @return FollowRelation
+ */
+ public static function create(Actor $subject, Actor $object, FollowRelationStatus $status): self {
+ $rel = new self();
+ $rel->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 @@
+<?php
+namespace Digitigrade\Model;
+
+enum FollowRelationStatus: string {
+ case PENDING = 'pending';
+ case ACTIVE = 'active';
+} \ No newline at end of file