From 3c8c23a1438f68373b4c146ab858e73a3c163e34 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Fri, 3 Sep 2021 21:21:17 -0600 Subject: [PATCH] Update AP Inbox --- app/Util/ActivityPub/Helpers.php | 5 +- app/Util/ActivityPub/Inbox.php | 312 +++++++++++++++++++++++++++++-- 2 files changed, 302 insertions(+), 15 deletions(-) diff --git a/app/Util/ActivityPub/Helpers.php b/app/Util/ActivityPub/Helpers.php index 9859bec6a..907097f05 100644 --- a/app/Util/ActivityPub/Helpers.php +++ b/app/Util/ActivityPub/Helpers.php @@ -563,9 +563,12 @@ class Helpers { $profile = Profile::whereRemoteUrl($res['id'])->first(); if(!$profile) { - Instance::firstOrCreate([ + $instance = Instance::firstOrCreate([ 'domain' => $domain ]); + if($instance->wasRecentlyCreated == true) { + \App\Jobs\InstancePipeline\FetchNodeinfoPipeline::dispatch($instance)->onQueue('low'); + } $profileLockKey = 'helpers:profile-lock:' . hash('sha256', $res['id']); $profile = Cache::lock($profileLockKey)->get(function () use($domain, $webfinger, $res, $runJobs) { return DB::transaction(function() use($domain, $webfinger, $res, $runJobs) { diff --git a/app/Util/ActivityPub/Inbox.php b/app/Util/ActivityPub/Inbox.php index 920f6d80a..164ca63da 100644 --- a/app/Util/ActivityPub/Inbox.php +++ b/app/Util/ActivityPub/Inbox.php @@ -2,7 +2,7 @@ namespace App\Util\ActivityPub; -use Cache, DB, Log, Purify, Redis, Validator; +use Cache, DB, Log, Purify, Redis, Storage, Validator; use App\{ Activity, DirectMessage, @@ -14,6 +14,8 @@ use App\{ Profile, Status, StatusHashtag, + Story, + StoryView, UserFilter }; use Carbon\Carbon; @@ -22,6 +24,8 @@ use Illuminate\Support\Str; use App\Jobs\LikePipeline\LikePipeline; use App\Jobs\FollowPipeline\FollowPipeline; use App\Jobs\DeletePipeline\DeleteRemoteProfilePipeline; +use App\Jobs\StoryPipeline\StoryExpire; +use App\Jobs\StoryPipeline\StoryFetch; use App\Util\ActivityPub\Validator\Accept as AcceptValidator; use App\Util\ActivityPub\Validator\Add as AddValidator; @@ -31,6 +35,7 @@ use App\Util\ActivityPub\Validator\Like as LikeValidator; use App\Util\ActivityPub\Validator\UndoFollow as UndoFollowValidator; use App\Services\PollService; +use App\Services\FollowerService; class Inbox { @@ -49,16 +54,7 @@ class Inbox public function handle() { $this->handleVerb(); - - // if(!Activity::where('data->id', $this->payload['id'])->exists()) { - // (new Activity())->create([ - // 'to_id' => $this->profile->id, - // 'data' => json_encode($this->payload) - // ]); - // } - return; - } public function handleVerb() @@ -107,6 +103,18 @@ class Inbox $this->handleUndoActivity(); break; + case 'View': + $this->handleViewActivity(); + break; + + case 'Story:Reaction': + $this->handleStoryReactionActivity(); + break; + + case 'Story:Reply': + $this->handleStoryReplyActivity(); + break; + default: // TODO: decide how to handle invalid verbs. break; @@ -138,6 +146,30 @@ class Inbox public function handleAddActivity() { // stories ;) + + if(!isset( + $this->payload['actor'], + $this->payload['object'] + )) { + return; + } + + $actor = $this->payload['actor']; + $obj = $this->payload['object']; + + if(!Helpers::validateUrl($actor)) { + return; + } + + if(!isset($obj['type'])) { + return; + } + + switch($obj['type']) { + case 'Story': + StoryFetch::dispatch($this->payload)->onQueue('story'); + break; + } } public function handleCreateActivity() @@ -212,8 +244,8 @@ class Inbox if( isset($activity['inReplyTo']) && isset($activity['name']) && !isset($activity['content']) && - !isset($activity['attachment'] && - Helpers::validateLocalUrl($activity['inReplyTo'])) + !isset($activity['attachment']) && + Helpers::validateLocalUrl($activity['inReplyTo']) ) { $this->handlePollVote(); return; @@ -496,7 +528,6 @@ class Inbox public function handleAcceptActivity() { - $actor = $this->payload['object']['actor']; $obj = $this->payload['object']['object']; $type = $this->payload['object']['type']; @@ -556,7 +587,7 @@ class Inbox return; } else { $type = $this->payload['object']['type']; - $typeCheck = in_array($type, ['Person', 'Tombstone']); + $typeCheck = in_array($type, ['Person', 'Tombstone', 'Story']); if(!Helpers::validateUrl($actor) || !Helpers::validateUrl($obj['id']) || !$typeCheck) { return; } @@ -596,6 +627,13 @@ class Inbox return; break; + case 'Story': + $story = Story::whereObjectId($id) + ->first(); + if($story) { + StoryExpire::dispatch($story)->onQueue('story'); + } + default: return; break; @@ -705,4 +743,250 @@ class Inbox } return; } + + public function handleViewActivity() + { + if(!isset( + $this->payload['actor'], + $this->payload['object'] + )) { + return; + } + + $actor = $this->payload['actor']; + $obj = $this->payload['object']; + + if(!Helpers::validateUrl($actor)) { + return; + } + + if(!$obj || !is_array($obj)) { + return; + } + + if(!isset($obj['type']) || !isset($obj['object']) || $obj['type'] != 'Story') { + return; + } + + if(!Helpers::validateLocalUrl($obj['object'])) { + return; + } + + $profile = Helpers::profileFetch($actor); + $storyId = Str::of($obj['object'])->explode('/')->last(); + + $story = Story::whereActive(true) + ->whereLocal(true) + ->find($storyId); + + if(!$story) { + return; + } + + if(!FollowerService::follows($profile->id, $story->profile_id)) { + return; + } + + $view = StoryView::firstOrCreate([ + 'story_id' => $story->id, + 'profile_id' => $profile->id + ]); + + if($view->wasRecentlyCreated == true) { + $story->view_count++; + $story->save(); + } + } + + public function handleStoryReactionActivity() + { + if(!isset( + $this->payload['actor'], + $this->payload['id'], + $this->payload['inReplyTo'], + $this->payload['content'] + )) { + return; + } + + $id = $this->payload['id']; + $actor = $this->payload['actor']; + $storyUrl = $this->payload['inReplyTo']; + $to = $this->payload['to']; + $text = Purify::clean($this->payload['content']); + + if(parse_url($id, PHP_URL_HOST) !== parse_url($actor, PHP_URL_HOST)) { + return; + } + + if(!Helpers::validateUrl($id) || !Helpers::validateUrl($actor)) { + return; + } + + if(!Helpers::validateLocalUrl($storyUrl)) { + return; + } + + if(!Helpers::validateLocalUrl($to)) { + return; + } + + if(Status::whereObjectUrl($id)->exists()) { + return; + } + + $storyId = Str::of($storyUrl)->explode('/')->last(); + $targetProfile = Helpers::profileFetch($to); + + $story = Story::whereProfileId($targetProfile->id) + ->find($storyId); + + if(!$story) { + return; + } + + if($story->can_react == false) { + return; + } + + $actorProfile = Helpers::profileFetch($actor); + + if(!FollowerService::follows($actorProfile->id, $targetProfile->id)) { + return; + } + + $status = new Status; + $status->profile_id = $actorProfile->id; + $status->type = 'story:reaction'; + $status->caption = $text; + $status->rendered = $text; + $status->scope = 'direct'; + $status->visibility = 'direct'; + $status->in_reply_to_profile_id = $story->profile_id; + $status->entities = json_encode([ + 'story_id' => $story->id, + 'reaction' => $text + ]); + $status->save(); + + $dm = new DirectMessage; + $dm->to_id = $story->profile_id; + $dm->from_id = $actorProfile->id; + $dm->type = 'story:react'; + $dm->status_id = $status->id; + $dm->meta = json_encode([ + 'story_username' => $targetProfile->username, + 'story_actor_username' => $actorProfile->username, + 'story_id' => $story->id, + 'story_media_url' => url(Storage::url($story->path)), + 'reaction' => $text + ]); + $dm->save(); + + $n = new Notification; + $n->profile_id = $dm->to_id; + $n->actor_id = $dm->from_id; + $n->item_id = $dm->id; + $n->item_type = 'App\DirectMessage'; + $n->action = 'story:react'; + $n->message = "{$actorProfile->username} reacted to your story"; + $n->rendered = "{$actorProfile->username} reacted to your story"; + $n->save(); + } + + public function handleStoryReplyActivity() + { + if(!isset( + $this->payload['actor'], + $this->payload['id'], + $this->payload['inReplyTo'], + $this->payload['content'] + )) { + return; + } + + $id = $this->payload['id']; + $actor = $this->payload['actor']; + $storyUrl = $this->payload['inReplyTo']; + $to = $this->payload['to']; + $text = Purify::clean($this->payload['content']); + + if(parse_url($id, PHP_URL_HOST) !== parse_url($actor, PHP_URL_HOST)) { + return; + } + + if(!Helpers::validateUrl($id) || !Helpers::validateUrl($actor)) { + return; + } + + if(!Helpers::validateLocalUrl($storyUrl)) { + return; + } + + if(!Helpers::validateLocalUrl($to)) { + return; + } + + if(Status::whereObjectUrl($id)->exists()) { + return; + } + + $storyId = Str::of($storyUrl)->explode('/')->last(); + $targetProfile = Helpers::profileFetch($to); + + $story = Story::whereProfileId($targetProfile->id) + ->find($storyId); + + if(!$story) { + return; + } + + if($story->can_react == false) { + return; + } + + $actorProfile = Helpers::profileFetch($actor); + + if(!FollowerService::follows($actorProfile->id, $targetProfile->id)) { + return; + } + + $status = new Status; + $status->profile_id = $actorProfile->id; + $status->type = 'story:reply'; + $status->caption = $text; + $status->rendered = $text; + $status->scope = 'direct'; + $status->visibility = 'direct'; + $status->in_reply_to_profile_id = $story->profile_id; + $status->entities = json_encode([ + 'story_id' => $story->id, + 'caption' => $text + ]); + $status->save(); + + $dm = new DirectMessage; + $dm->to_id = $story->profile_id; + $dm->from_id = $actorProfile->id; + $dm->type = 'story:comment'; + $dm->status_id = $status->id; + $dm->meta = json_encode([ + 'story_username' => $targetProfile->username, + 'story_actor_username' => $actorProfile->username, + 'story_id' => $story->id, + 'story_media_url' => url(Storage::url($story->path)), + 'caption' => $text + ]); + $dm->save(); + + $n = new Notification; + $n->profile_id = $dm->to_id; + $n->actor_id = $dm->from_id; + $n->item_id = $dm->id; + $n->item_type = 'App\DirectMessage'; + $n->action = 'story:comment'; + $n->message = "{$actorProfile->username} commented on story"; + $n->rendered = "{$actorProfile->username} commented on story"; + $n->save(); + } }