aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--Digitigrade/Job/PeriodicFetchRemoteFeed.php37
-rw-r--r--Digitigrade/Job/ProcessIncomingPush.php14
-rw-r--r--Digitigrade/Model/Actor.php17
-rw-r--r--locale/en_GB.json4
-rw-r--r--settings.global.ini8
5 files changed, 76 insertions, 4 deletions
diff --git a/Digitigrade/Job/PeriodicFetchRemoteFeed.php b/Digitigrade/Job/PeriodicFetchRemoteFeed.php
new file mode 100644
index 0000000..e84757f
--- /dev/null
+++ b/Digitigrade/Job/PeriodicFetchRemoteFeed.php
@@ -0,0 +1,37 @@
+<?php
+namespace Digitigrade\Job;
+
+use Digitigrade\GlobalSettings;
+use Digitigrade\Job;
+use Digitigrade\JobQueue;
+use Digitigrade\Logger;
+use Digitigrade\Model\Actor;
+
+class PeriodicFetchRemoteFeed extends Job {
+ public int $actorId;
+
+ public function __construct(Actor $targetActor) {
+ $this->actorId = $targetActor->id;
+ $this->remainingTries = 8;
+ }
+
+ public function run() {
+ $actor = Actor::find($this->actorId);
+ $logger = Logger::getInstance();
+ // only bother if someone local still follows the actor
+ if (count(array_filter($actor->findFollowers(), fn(Actor $a) => $a->isLocal)) > 0) {
+ $logger->info('Fetching feed for simple remote actor ' . $actor->getFullHandle());
+ $actor->backfill(GlobalSettings::getInstance()->get('instance.feedUpdateLimit') ?? 50);
+ $this->submit();
+ } else {
+ $logger->info('No longer fetching feed for ' . $actor->getFullHandle() . ' as nobody follows them anymore');
+ }
+ }
+
+ public function submit() {
+ JobQueue::getInstance()->submitDelayedUntil(
+ $this,
+ (new \DateTimeImmutable)->add(new \DateInterval(GlobalSettings::getInstance()->get('instance.feedUpdateInterval') ?? 'PT1H'))
+ );
+ }
+} \ No newline at end of file
diff --git a/Digitigrade/Job/ProcessIncomingPush.php b/Digitigrade/Job/ProcessIncomingPush.php
index 600972c..16dd1ce 100644
--- a/Digitigrade/Job/ProcessIncomingPush.php
+++ b/Digitigrade/Job/ProcessIncomingPush.php
@@ -34,15 +34,21 @@ class ProcessIncomingPush extends Job {
break;
case 'note':
$log->info('importing note with uri ' . $this->object->self);
+ $alreadyExisted = Note::countWhere('uri = ?', [$this->object->self]) > 0;
$note = Note::importFromReceivedObject($this->object);
- $note?->processTimelineAdditions();
- $note?->processNotifications();
+ if (!$alreadyExisted) {
+ $note?->processTimelineAdditions();
+ $note?->processNotifications();
+ }
break;
case 'interaction':
$log->info('importing interaction with uri ' . $this->object->self);
+ $alreadyExisted = Interaction::countWhere('uri = ?', [$this->object->self]) > 0;
$interaction = Interaction::importFromReceivedObject($this->object);
- $interaction?->processTimelineAdditions();
- $interaction?->processNotifications();
+ if (!$alreadyExisted) {
+ $interaction?->processTimelineAdditions();
+ $interaction?->processNotifications();
+ }
break;
case 'extension':
throw new \RuntimeException('object type ' . $this->object->type . ' not yet implemented :(');
diff --git a/Digitigrade/Model/Actor.php b/Digitigrade/Model/Actor.php
index ffaf4e8..3fb7e4e 100644
--- a/Digitigrade/Model/Actor.php
+++ b/Digitigrade/Model/Actor.php
@@ -2,6 +2,7 @@
namespace Digitigrade\Model;
use Digitigrade\Db;
+use Digitigrade\Job\PeriodicFetchRemoteFeed;
use Digitigrade\Job\ProcessIncomingPush;
use Digitigrade\Job\ProcessIncomingSimplePush;
use Digitigrade\Notification\PendingFollowActionedNotif;
@@ -254,10 +255,26 @@ class Actor extends PushableModel implements RpcReceiver {
* @return FollowRelationStatus PENDING if a request was sent or ACTIVE if the follow went through immediately
*/
public function follow(self $target): FollowRelationStatus {
+ // handle actors with no follow endpoint
+ if (!$target->isLocal && !isset($target->endpoints->follow, $target->endpoints->unfollow)) {
+ // if this is the first time someone local followed this actor, put a job in to update them
+ if (count(array_filter($target->findFollowers(), fn(Actor $a) => $a->isLocal)) == 0) {
+ (new PeriodicFetchRemoteFeed($target))->submit();
+ }
+ FollowRelation::create($this, $target, FollowRelationStatus::ACTIVE);
+ // no need to processNotifications because it's always going to be a remote user & not pending
+ return FollowRelationStatus::ACTIVE;
+ }
+
return $target->rpcCall('follow', [$this]);
}
public function unfollow(self $target) {
+ // handle actors with no follow endpoint
+ if (!$target->isLocal && !isset($target->endpoints->follow, $target->endpoints->unfollow)) {
+ FollowRelation::findByActors($this, $target)?->remove();
+ return;
+ }
$target->rpcCall('unfollow', [$this]);
}
diff --git a/locale/en_GB.json b/locale/en_GB.json
index 7c7b61f..f0d8bd5 100644
--- a/locale/en_GB.json
+++ b/locale/en_GB.json
@@ -197,6 +197,10 @@
"globalSettings.s.instance.description.description": "Briefly describe your instance's purpose or tagline",
"globalSettings.s.instance.openRegistrations.name": "Open registrations",
"globalSettings.s.instance.openRegistrations.description": "Allow new users to sign up?",
+ "globalSettings.s.instance.feedUpdateInterval.name": "Feed update interval",
+ "globalSettings.s.instance.feedUpdateInterval.description": "Time between fetching updates for simple remote actors that don't support pushing their updates, in ISO 8601 interval format (e.g. 1 hour is PT1H, 2 days is P2D)",
+ "globalSettings.s.instance.feedUpdateLimit.name": "Feed update limit",
+ "globalSettings.s.instance.feedUpdateLimit.description": "Maximum number of new objects to fetch from simple remote actors at each interval",
"globalSettings.s.about.description.name": "Description",
"globalSettings.s.about.description.description": "Describe your instance's purpose in more detail. You can use HTML formatting",
"globalSettings.s.about.terms.name": "Terms of use",
diff --git a/settings.global.ini b/settings.global.ini
index baa4b53..464cb8f 100644
--- a/settings.global.ini
+++ b/settings.global.ini
@@ -12,6 +12,14 @@ type = "longtext"
default = "false"
type = "boolean"
+[instance.feedUpdateInterval]
+default = "PT1H"
+type = "text"
+
+[instance.feedUpdateLimit]
+default = "50"
+type = "number"
+
[about.description]
default = ""
type = "longtext"