aboutsummaryrefslogtreecommitdiffhomepage
path: root/Digitigrade
diff options
context:
space:
mode:
authorwinter2025-01-15 20:33:38 +0000
committerwinter2025-01-15 20:33:38 +0000
commit75b3778dccccddb5cc90b2b4e0a3f958826ad8ba (patch)
tree6240a96338f58b5f175e97132a4ce5d804e3b2da /Digitigrade
parent07df9da970ceaa54c734d73bdb148fa36d42db63 (diff)
implement home timeline
Diffstat (limited to 'Digitigrade')
-rw-r--r--Digitigrade/Job/ProcessIncomingPush.php6
-rw-r--r--Digitigrade/Model/Interaction.php20
-rw-r--r--Digitigrade/Model/Note.php37
-rw-r--r--Digitigrade/Model/UserAccount.php4
-rw-r--r--Digitigrade/Timeline/GlobalTimeline.php2
-rw-r--r--Digitigrade/Timeline/HomeTimeline.php26
-rw-r--r--Digitigrade/Timeline/HomeTimelineItem.php52
-rw-r--r--Digitigrade/Timeline/LocalTimeline.php2
-rw-r--r--Digitigrade/Timeline/ReasonReshared.php24
-rw-r--r--Digitigrade/Timeline/Timeline.php (renamed from Digitigrade/Timeline.php)4
-rw-r--r--Digitigrade/Timeline/TimelineIncludeable.php8
-rw-r--r--Digitigrade/Timeline/TimelineInclusionReason.php8
-rw-r--r--Digitigrade/Timeline/TimelineItem.php (renamed from Digitigrade/TimelineItem.php)2
-rw-r--r--Digitigrade/TimelineIncludeable.php6
-rw-r--r--Digitigrade/TimelineInclusionReason.php6
-rw-r--r--Digitigrade/TimelineInclusionReason/Reshared.php17
16 files changed, 179 insertions, 45 deletions
diff --git a/Digitigrade/Job/ProcessIncomingPush.php b/Digitigrade/Job/ProcessIncomingPush.php
index cd297ca..2d27f80 100644
--- a/Digitigrade/Job/ProcessIncomingPush.php
+++ b/Digitigrade/Job/ProcessIncomingPush.php
@@ -33,11 +33,13 @@ class ProcessIncomingPush extends Job {
break;
case 'note':
$log->info('importing note with uri ' . $this->object->self);
- Note::importFromReceivedObject($this->object);
+ $note = Note::importFromReceivedObject($this->object);
+ $note->processTimelineAdditions();
break;
case 'interaction':
$log->info('importing interaction with uri ' . $this->object->self);
- Interaction::importFromReceivedObject($this->object);
+ $interaction = Interaction::importFromReceivedObject($this->object);
+ $interaction->processTimelineAdditions();
break;
case 'extension':
throw new \RuntimeException('object type ' . $this->object->type . ' not yet implemented :(');
diff --git a/Digitigrade/Model/Interaction.php b/Digitigrade/Model/Interaction.php
index 4ca1a0c..ed4ef45 100644
--- a/Digitigrade/Model/Interaction.php
+++ b/Digitigrade/Model/Interaction.php
@@ -1,6 +1,10 @@
<?php
namespace Digitigrade\Model;
+use Digitigrade\Timeline\HomeTimelineItem;
+use Digitigrade\Timeline\ReasonReshared;
+use Digitigrade\Timeline\TimelineItem;
+
class Interaction extends PushableModel {
public ?int $id;
public string $uri;
@@ -57,6 +61,22 @@ class Interaction extends PushableModel {
return array_unique($instances);
}
+ public function processTimelineAdditions() {
+ // basically, if this is a reshare, we need to put a home timeline item
+ // for all followers who can see the shared note
+ if ($this->kind != InteractionKind::RESHARE)
+ return;
+ $reason = new ReasonReshared($this->author);
+ $item = new TimelineItem($this->target, $reason);
+
+ foreach ($this->target->getRelevantActors() as $actor) {
+ if (!$actor->isLocal)
+ return;
+ $user = UserAccount::findByLinkedActor($actor);
+ HomeTimelineItem::fromTimelineItem($item, $user)->save();
+ }
+ }
+
public function jsonSerialize(): array {
if ($this->deleted) {
return [
diff --git a/Digitigrade/Model/Note.php b/Digitigrade/Model/Note.php
index 71b25ae..7fb1221 100644
--- a/Digitigrade/Model/Note.php
+++ b/Digitigrade/Model/Note.php
@@ -2,8 +2,9 @@
namespace Digitigrade\Model;
use Digitigrade\Db;
-use Digitigrade\Job\PushObject;
-use Digitigrade\TimelineIncludeable;
+use Digitigrade\Timeline\HomeTimelineItem;
+use Digitigrade\Timeline\TimelineIncludeable;
+use Digitigrade\Timeline\TimelineItem;
class Note extends PushableModel implements TimelineIncludeable {
public ?int $id;
@@ -139,13 +140,19 @@ class Note extends PushableModel implements TimelineIncludeable {
return self::countWhere('in_reply_to = ? AND deleted = false', [$this->id]);
}
- protected function getRelevantServers(): array {
- $recipientActors = match ($this->privacy->scope) {
+ /**
+ * @return Actor[] actors who this note can be seen by, are mentioned, etc
+ */
+ public function getRelevantActors(): array {
+ return array_unique(array_merge(match ($this->privacy->scope) {
NotePrivacyScope::NONE => [],
NotePrivacyScope::MUTUALS => $this->author->findMutualFollows(),
default => $this->author->findFollowers()
- };
- $recipientActors = array_merge($recipientActors, $this->mentions, $this->privacy->alsoVisibleTo);
+ }, $this->mentions, $this->privacy->alsoVisibleTo, [$this->author]));
+ }
+
+ protected function getRelevantServers(): array {
+ $recipientActors = $this->getRelevantActors();
// reply-to and thread apex author should be covered by mentions but in case they're not:
if (isset($this->inReplyTo))
$recipientActors[] = $this->inReplyTo->author;
@@ -170,6 +177,24 @@ class Note extends PushableModel implements TimelineIncludeable {
render_template('note', ['note' => $this]);
}
+ public function toJsonReference(): mixed {
+ return $this->id;
+ }
+
+ public static function fromJsonReference(mixed $reference): self {
+ return self::find($reference);
+ }
+
+ public function processTimelineAdditions() {
+ $item = new TimelineItem($this);
+ foreach ($this->getRelevantActors() as $actor) {
+ if (!$actor->isLocal)
+ continue;
+ $user = UserAccount::findByLinkedActor($actor);
+ HomeTimelineItem::fromTimelineItem($item, $user)->save();
+ }
+ }
+
public function jsonSerialize(): array {
if ($this->deleted) {
return [
diff --git a/Digitigrade/Model/UserAccount.php b/Digitigrade/Model/UserAccount.php
index 72a9ce9..d0fa94b 100644
--- a/Digitigrade/Model/UserAccount.php
+++ b/Digitigrade/Model/UserAccount.php
@@ -59,6 +59,10 @@ class UserAccount extends Model {
return $user;
}
+ public static function findByLinkedActor(Actor $actor): ?self {
+ return self::findWhere('actor = ?', [$actor->id]);
+ }
+
public function verifyPassword(#[\SensitiveParameter] $attemptedPassword): bool {
return password_verify($attemptedPassword, $this->passwordHash);
}
diff --git a/Digitigrade/Timeline/GlobalTimeline.php b/Digitigrade/Timeline/GlobalTimeline.php
index 278b557..81eccfb 100644
--- a/Digitigrade/Timeline/GlobalTimeline.php
+++ b/Digitigrade/Timeline/GlobalTimeline.php
@@ -3,8 +3,6 @@ namespace Digitigrade\Timeline;
use Digitigrade\Db;
use Digitigrade\Model\Note;
-use Digitigrade\Timeline;
-use Digitigrade\TimelineItem;
class GlobalTimeline extends Timeline {
protected function getItems(int $limit, int $offset): array {
diff --git a/Digitigrade/Timeline/HomeTimeline.php b/Digitigrade/Timeline/HomeTimeline.php
new file mode 100644
index 0000000..c2baf3d
--- /dev/null
+++ b/Digitigrade/Timeline/HomeTimeline.php
@@ -0,0 +1,26 @@
+<?php
+namespace Digitigrade\Timeline;
+
+use Digitigrade\Model\UserAccount;
+
+class HomeTimeline extends Timeline {
+ private UserAccount $user;
+
+ public function __construct(UserAccount $user) {
+ $this->user = $user;
+ }
+
+ protected function getItems(int $limit, int $offset): array {
+ return array_map(
+ fn(HomeTimelineItem $hti) => $hti->toTimelineItem(),
+ HomeTimelineItem::findAllForUser($this->user, $limit, $offset)
+ );
+ }
+
+ public function getItemsSince(\DateTimeInterface $when, ?int $limit = null, int $offset = 0): array {
+ return array_map(
+ fn(HomeTimelineItem $hti) => $hti->toTimelineItem(),
+ HomeTimelineItem::findAllForUser($this->user, $limit, $offset, $when)
+ );
+ }
+} \ No newline at end of file
diff --git a/Digitigrade/Timeline/HomeTimelineItem.php b/Digitigrade/Timeline/HomeTimelineItem.php
new file mode 100644
index 0000000..cb2b6e4
--- /dev/null
+++ b/Digitigrade/Timeline/HomeTimelineItem.php
@@ -0,0 +1,52 @@
+<?php
+namespace Digitigrade\Timeline;
+
+use Digitigrade\Model;
+use Digitigrade\Model\UserAccount;
+
+class HomeTimelineItem extends Model {
+ public ?int $id;
+ public UserAccount $userAccount;
+ public ?string $reasonKind;
+ public ?string $reasonData;
+ public string $itemKind;
+ public string $itemData;
+ public \DateTimeImmutable $modified;
+
+ /**
+ * Creates a new home timeline entry for a particular user, but does not save it.
+ */
+ public static function fromTimelineItem(TimelineItem $item, UserAccount $user): self {
+ $hti = new self();
+ $hti->userAccount = $user;
+ $reason = $item->getReason();
+ if ($reason != null) {
+ $hti->reasonKind = $reason::class;
+ $hti->reasonData = json_encode($reason->toJsonReference());
+ }
+ $object = $item->getObject();
+ $hti->itemKind = $object::class;
+ $hti->itemData = json_encode($object->toJsonReference());
+ $hti->modified = new \DateTimeImmutable();
+ return $hti;
+ }
+
+ public function toTimelineItem(): TimelineItem {
+ $reason = null;
+ if (isset($this->reasonKind)) {
+ $reason = $this->reasonKind::fromJsonReference(json_decode($this->reasonData));
+ }
+ $object = $this->itemKind::fromJsonReference(json_decode($this->itemData));
+ return new TimelineItem($object, $reason);
+ }
+
+ public static function findAllForUser(UserAccount $user, ?int $limit = null, int $offset = 0, ?\DateTimeInterface $since = null) {
+ $where = 'user_account = ?';
+ $params = [$user->id];
+ if (isset($since)) {
+ $where .= ' AND modified > ?';
+ $params[] = $since->format('c');
+ }
+ return self::findAllWhere($where, $params, $limit, $offset, 'id DESC');
+ }
+} \ No newline at end of file
diff --git a/Digitigrade/Timeline/LocalTimeline.php b/Digitigrade/Timeline/LocalTimeline.php
index c20ada6..6129576 100644
--- a/Digitigrade/Timeline/LocalTimeline.php
+++ b/Digitigrade/Timeline/LocalTimeline.php
@@ -3,8 +3,6 @@ namespace Digitigrade\Timeline;
use Digitigrade\Db;
use Digitigrade\Model\Note;
-use Digitigrade\Timeline;
-use Digitigrade\TimelineItem;
class LocalTimeline extends Timeline {
protected function getItems(int $limit, int $offset): array {
diff --git a/Digitigrade/Timeline/ReasonReshared.php b/Digitigrade/Timeline/ReasonReshared.php
new file mode 100644
index 0000000..2ffe1b2
--- /dev/null
+++ b/Digitigrade/Timeline/ReasonReshared.php
@@ -0,0 +1,24 @@
+<?php
+namespace Digitigrade\Timeline;
+
+use Digitigrade\Model\Actor;
+
+class ReasonReshared extends TimelineInclusionReason {
+ private Actor $resharer;
+
+ public function __construct(Actor $resharer) {
+ $this->resharer = $resharer;
+ }
+
+ public function renderAsHtml() {
+ render_template('reason_reshared', ['actor' => $this->resharer]);
+ }
+
+ public function toJsonReference(): mixed {
+ return $this->resharer->id;
+ }
+
+ public static function fromJsonReference(mixed $reference): self {
+ return new self(Actor::find($reference));
+ }
+} \ No newline at end of file
diff --git a/Digitigrade/Timeline.php b/Digitigrade/Timeline/Timeline.php
index 64aabe1..b3def28 100644
--- a/Digitigrade/Timeline.php
+++ b/Digitigrade/Timeline/Timeline.php
@@ -1,7 +1,5 @@
<?php
-namespace Digitigrade;
-
-use Digitigrade\Model\Note;
+namespace Digitigrade\Timeline;
abstract class Timeline {
// TODO: currently making this number up. probably should be modifiable somewhere
diff --git a/Digitigrade/Timeline/TimelineIncludeable.php b/Digitigrade/Timeline/TimelineIncludeable.php
new file mode 100644
index 0000000..5406543
--- /dev/null
+++ b/Digitigrade/Timeline/TimelineIncludeable.php
@@ -0,0 +1,8 @@
+<?php
+namespace Digitigrade\Timeline;
+
+interface TimelineIncludeable {
+ public function renderAsHtml();
+ public function toJsonReference(): mixed;
+ public static function fromJsonReference(mixed $reference): self;
+} \ No newline at end of file
diff --git a/Digitigrade/Timeline/TimelineInclusionReason.php b/Digitigrade/Timeline/TimelineInclusionReason.php
new file mode 100644
index 0000000..6ffe76d
--- /dev/null
+++ b/Digitigrade/Timeline/TimelineInclusionReason.php
@@ -0,0 +1,8 @@
+<?php
+namespace Digitigrade\Timeline;
+
+abstract class TimelineInclusionReason {
+ abstract public function renderAsHtml();
+ abstract public function toJsonReference(): mixed;
+ abstract public static function fromJsonReference(mixed $reference): self;
+} \ No newline at end of file
diff --git a/Digitigrade/TimelineItem.php b/Digitigrade/Timeline/TimelineItem.php
index e59fbec..d6ba563 100644
--- a/Digitigrade/TimelineItem.php
+++ b/Digitigrade/Timeline/TimelineItem.php
@@ -1,5 +1,5 @@
<?php
-namespace Digitigrade;
+namespace Digitigrade\Timeline;
class TimelineItem {
private ?TimelineInclusionReason $reason;
diff --git a/Digitigrade/TimelineIncludeable.php b/Digitigrade/TimelineIncludeable.php
deleted file mode 100644
index 44433b4..0000000
--- a/Digitigrade/TimelineIncludeable.php
+++ /dev/null
@@ -1,6 +0,0 @@
-<?php
-namespace Digitigrade;
-
-interface TimelineIncludeable {
- public function renderAsHtml();
-} \ No newline at end of file
diff --git a/Digitigrade/TimelineInclusionReason.php b/Digitigrade/TimelineInclusionReason.php
deleted file mode 100644
index e9840d1..0000000
--- a/Digitigrade/TimelineInclusionReason.php
+++ /dev/null
@@ -1,6 +0,0 @@
-<?php
-namespace Digitigrade;
-
-abstract class TimelineInclusionReason {
- abstract public function renderAsHtml();
-} \ No newline at end of file
diff --git a/Digitigrade/TimelineInclusionReason/Reshared.php b/Digitigrade/TimelineInclusionReason/Reshared.php
deleted file mode 100644
index f185149..0000000
--- a/Digitigrade/TimelineInclusionReason/Reshared.php
+++ /dev/null
@@ -1,17 +0,0 @@
-<?php
-namespace Digitigrade\TimelineInclusionReason;
-
-use Digitigrade\Model\Actor;
-use Digitigrade\TimelineInclusionReason;
-
-class Reshared extends TimelineInclusionReason {
- private Actor $resharer;
-
- public function __construct(Actor $resharer) {
- $this->resharer = $resharer;
- }
-
- public function renderAsHtml() {
- render_template('reason_reshared', ['actor' => $this->resharer]);
- }
-} \ No newline at end of file