123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352 |
- <?php
- namespace App\Util\ActivityPub;
- use Cache, DB, Log, Purify, Redis, Validator;
- use App\{
- Activity,
- Follower,
- FollowRequest,
- Like,
- Notification,
- Profile,
- Status
- };
- use Carbon\Carbon;
- use App\Util\ActivityPub\Helpers;
- use App\Jobs\LikePipeline\LikePipeline;
- use App\Util\ActivityPub\Validator\{
- Follow
- };
- class Inbox
- {
- protected $headers;
- protected $profile;
- protected $payload;
- protected $logger;
- public function __construct($headers, $profile, $payload)
- {
- $this->headers = $headers;
- $this->profile = $profile;
- $this->payload = $payload;
- }
- public function handle()
- {
- $this->handleVerb();
- }
- public function handleVerb()
- {
- $verb = $this->payload['type'];
- switch ($verb) {
- case 'Create':
- $this->handleCreateActivity();
- break;
- case 'Follow':
- $this->handleFollowActivity();
- break;
- case 'Announce':
- $this->handleAnnounceActivity();
- break;
- case 'Accept':
- if(Accept::validate($this->payload) == false) { return; }
- $this->handleAcceptActivity();
- break;
- case 'Delete':
- $this->handleDeleteActivity();
- break;
- case 'Like':
- $this->handleLikeActivity();
- break;
- case 'Reject':
- $this->handleRejectActivity();
- break;
- case 'Undo':
- $this->handleUndoActivity();
- break;
- default:
- // TODO: decide how to handle invalid verbs.
- break;
- }
- }
- public function verifyNoteAttachment()
- {
- $activity = $this->payload['object'];
- if(isset($activity['inReplyTo']) &&
- !empty($activity['inReplyTo']) &&
- Helpers::validateUrl($activity['inReplyTo'])
- ) {
- // reply detected, skip attachment check
- return true;
- }
- $valid = Helpers::verifyAttachments($activity);
- return $valid;
- }
- public function actorFirstOrCreate($actorUrl)
- {
- return Helpers::profileFirstOrNew($actorUrl);
- }
- public function handleCreateActivity()
- {
- $activity = $this->payload['object'];
- if(!$this->verifyNoteAttachment()) {
- return;
- }
- if($activity['type'] == 'Note' && !empty($activity['inReplyTo'])) {
- $this->handleNoteReply();
- } elseif($activity['type'] == 'Note' && !empty($activity['attachment'])) {
- $this->handleNoteCreate();
- }
- }
- public function handleNoteReply()
- {
- $activity = $this->payload['object'];
- $actor = $this->actorFirstOrCreate($this->payload['actor']);
- $inReplyTo = $activity['inReplyTo'];
- $url = $activity['id'];
-
- if(!Helpers::statusFirstOrFetch($url, true)) {
- return;
- }
- }
- public function handleNoteCreate()
- {
- $activity = $this->payload['object'];
- $actor = $this->actorFirstOrCreate($this->payload['actor']);
- if(!$actor || $actor->domain == null) {
- return;
- }
- if(Helpers::userInAudience($this->profile, $this->payload) == false) {
- //Log::error('AP:inbox:userInAudience:false - Activity#'.$this->logger->id);
- return;
- }
- $url = $activity['id'];
- if(Status::whereUrl($url)->exists()) {
- return;
- }
- $status = DB::transaction(function() use($activity, $actor, $url) {
- $caption = str_limit(strip_tags($activity['content']), config('pixelfed.max_caption_length'));
- $status = new Status;
- $status->profile_id = $actor->id;
- $status->caption = strip_tags($caption);
- $status->rendered = Purify::clean($caption);
- $status->visibility = $status->scope = 'public';
- $status->uri = $url;
- $status->url = $url;
- $status->save();
- return $status;
- });
- Helpers::importNoteAttachment($activity, $status);
- }
- public function handleFollowActivity()
- {
- $actor = $this->actorFirstOrCreate($this->payload['actor']);
- if(!$actor || $actor->domain == null) {
- return;
- }
- $target = $this->profile;
- if($target->is_private == true) {
- // make follow request
- FollowRequest::firstOrCreate([
- 'follower_id' => $actor->id,
- 'following_id' => $target->id
- ]);
- // todo: send notification
- } else {
- // store new follower
- $follower = Follower::firstOrCreate([
- 'profile_id' => $actor->id,
- 'following_id' => $target->id,
- 'local_profile' => empty($actor->domain)
- ]);
- if($follower->wasRecentlyCreated == true) {
- // send notification
- Notification::firstOrCreate([
- 'profile_id' => $target->id,
- 'actor_id' => $actor->id,
- 'action' => 'follow',
- 'message' => $follower->toText(),
- 'rendered' => $follower->toHtml(),
- 'item_id' => $target->id,
- 'item_type' => 'App\Profile'
- ]);
- }
- // send Accept to remote profile
- $accept = [
- '@context' => 'https://www.w3.org/ns/activitystreams',
- 'id' => $target->permalink().'#accepts/follows/' . $follower->id,
- 'type' => 'Accept',
- 'actor' => $target->permalink(),
- 'object' => [
- 'id' => $actor->permalink('#follows/'.$target->id),
- 'type' => 'Follow',
- 'actor' => $actor->permalink(),
- 'object' => $target->permalink()
- ]
- ];
- Helpers::sendSignedObject($target, $actor->inbox_url, $accept);
- }
- }
- public function handleAnnounceActivity()
- {
- $actor = $this->actorFirstOrCreate($this->payload['actor']);
- $activity = $this->payload['object'];
- if(!$actor || $actor->domain == null) {
- return;
- }
- if(Helpers::validateLocalUrl($activity) == false) {
- return;
- }
- $parent = Helpers::statusFirstOrFetch($activity, true);
- if(!$parent) {
- return;
- }
- $status = Status::firstOrCreate([
- 'profile_id' => $actor->id,
- 'reblog_of_id' => $parent->id,
- 'type' => 'reply'
- ]);
- Notification::firstOrCreate([
- 'profile_id' => $parent->profile->id,
- 'actor_id' => $actor->id,
- 'action' => 'share',
- 'message' => $status->replyToText(),
- 'rendered' => $status->replyToHtml(),
- 'item_id' => $parent->id,
- 'item_type' => 'App\Status'
- ]);
- }
- public function handleAcceptActivity()
- {
- $actor = $this->payload['actor'];
- $obj = $this->payload['object'];
- switch ($obj['type']) {
- case 'Follow':
- $accept = [
- '@context' => 'https://www.w3.org/ns/activitystreams',
- 'id' => $target->permalink().'#accepts/follows/' . $follower->id,
- 'type' => 'Accept',
- 'actor' => $target->permalink(),
- 'object' => [
- 'id' => $actor->permalink('#follows/'.$target->id),
- 'type' => 'Follow',
- 'actor' => $actor->permalink(),
- 'object' => $target->permalink()
- ]
- ];
- break;
-
- default:
- # code...
- break;
- }
- }
- public function handleDeleteActivity()
- {
- $actor = $this->payload['actor'];
- $obj = $this->payload['object'];
- if(is_string($obj) && Helpers::validateUrl($obj)) {
- // actor object detected
- // todo delete actor
- } else if (is_array($obj) && isset($obj['type']) && $obj['type'] == 'Tombstone') {
- // tombstone detected
- $status = Status::whereUri($obj['id'])->firstOrFail();
- $status->forceDelete();
- }
- }
- public function handleLikeActivity()
- {
- $actor = $this->payload['actor'];
- $profile = self::actorFirstOrCreate($actor);
- $obj = $this->payload['object'];
- if(Helpers::validateLocalUrl($obj) == false) {
- return;
- }
- $status = Helpers::statusFirstOrFetch($obj);
- $like = Like::firstOrCreate([
- 'profile_id' => $profile->id,
- 'status_id' => $status->id
- ]);
- if($like->wasRecentlyCreated == false) {
- return;
- }
- LikePipeline::dispatch($like);
- }
- public function handleRejectActivity()
- {
- }
- public function handleUndoActivity()
- {
- $actor = $this->payload['actor'];
- $profile = self::actorFirstOrCreate($actor);
- $obj = $this->payload['object'];
- switch ($obj['type']) {
- case 'Accept':
- break;
-
- case 'Announce':
- break;
- case 'Block':
- break;
- case 'Follow':
- $following = self::actorFirstOrCreate($obj['object']);
- Follower::whereProfileId($profile->id)
- ->whereFollowingId($following->id)
- ->delete();
- break;
-
- case 'Like':
- $status = Helpers::statusFirstOrFetch($obj['object']);
- Like::whereProfileId($profile->id)
- ->whereStatusId($status->id)
- ->forceDelete();
- Notification::whereProfileId($status->profile->id)
- ->whereActorId($profile->id)
- ->whereAction('like')
- ->whereItemId($status->id)
- ->whereItemType('App\Status')
- ->forceDelete();
- break;
- }
- }
- }
|