Inbox.php 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312
  1. <?php
  2. namespace App\Util\ActivityPub;
  3. use Cache, DB, Log, Purify, Redis, Storage, Validator;
  4. use App\{
  5. Activity,
  6. DirectMessage,
  7. Follower,
  8. FollowRequest,
  9. Instance,
  10. Like,
  11. Notification,
  12. Media,
  13. Profile,
  14. Status,
  15. StatusHashtag,
  16. Story,
  17. StoryView,
  18. UserFilter
  19. };
  20. use Carbon\Carbon;
  21. use App\Util\ActivityPub\Helpers;
  22. use Illuminate\Support\Str;
  23. use App\Jobs\LikePipeline\LikePipeline;
  24. use App\Jobs\FollowPipeline\FollowPipeline;
  25. use App\Jobs\DeletePipeline\DeleteRemoteProfilePipeline;
  26. use App\Jobs\StatusPipeline\RemoteStatusDelete;
  27. use App\Jobs\StoryPipeline\StoryExpire;
  28. use App\Jobs\StoryPipeline\StoryFetch;
  29. use App\Jobs\StatusPipeline\StatusRemoteUpdatePipeline;
  30. use App\Jobs\ProfilePipeline\HandleUpdateActivity;
  31. use App\Util\ActivityPub\Validator\Accept as AcceptValidator;
  32. use App\Util\ActivityPub\Validator\Add as AddValidator;
  33. use App\Util\ActivityPub\Validator\Announce as AnnounceValidator;
  34. use App\Util\ActivityPub\Validator\Follow as FollowValidator;
  35. use App\Util\ActivityPub\Validator\Like as LikeValidator;
  36. use App\Util\ActivityPub\Validator\UndoFollow as UndoFollowValidator;
  37. use App\Util\ActivityPub\Validator\UpdatePersonValidator;
  38. use App\Services\AccountService;
  39. use App\Services\PollService;
  40. use App\Services\FollowerService;
  41. use App\Services\ReblogService;
  42. use App\Services\StatusService;
  43. use App\Services\UserFilterService;
  44. use App\Services\NetworkTimelineService;
  45. use App\Models\Conversation;
  46. use App\Models\RemoteReport;
  47. use App\Jobs\ProfilePipeline\IncrementPostCount;
  48. use App\Jobs\ProfilePipeline\DecrementPostCount;
  49. use App\Jobs\HomeFeedPipeline\FeedRemoveRemotePipeline;
  50. class Inbox
  51. {
  52. protected $headers;
  53. protected $profile;
  54. protected $payload;
  55. protected $logger;
  56. public function __construct($headers, $profile, $payload)
  57. {
  58. $this->headers = $headers;
  59. $this->profile = $profile;
  60. $this->payload = $payload;
  61. }
  62. public function handle()
  63. {
  64. $this->handleVerb();
  65. return;
  66. }
  67. public function handleVerb()
  68. {
  69. $verb = (string) $this->payload['type'];
  70. switch ($verb) {
  71. case 'Add':
  72. $this->handleAddActivity();
  73. break;
  74. case 'Create':
  75. $this->handleCreateActivity();
  76. break;
  77. case 'Follow':
  78. if(FollowValidator::validate($this->payload) == false) { return; }
  79. $this->handleFollowActivity();
  80. break;
  81. case 'Announce':
  82. if(AnnounceValidator::validate($this->payload) == false) { return; }
  83. $this->handleAnnounceActivity();
  84. break;
  85. case 'Accept':
  86. if(AcceptValidator::validate($this->payload) == false) { return; }
  87. $this->handleAcceptActivity();
  88. break;
  89. case 'Delete':
  90. $this->handleDeleteActivity();
  91. break;
  92. case 'Like':
  93. if(LikeValidator::validate($this->payload) == false) { return; }
  94. $this->handleLikeActivity();
  95. break;
  96. case 'Reject':
  97. $this->handleRejectActivity();
  98. break;
  99. case 'Undo':
  100. $this->handleUndoActivity();
  101. break;
  102. case 'View':
  103. $this->handleViewActivity();
  104. break;
  105. case 'Story:Reaction':
  106. $this->handleStoryReactionActivity();
  107. break;
  108. case 'Story:Reply':
  109. $this->handleStoryReplyActivity();
  110. break;
  111. case 'Flag':
  112. $this->handleFlagActivity();
  113. break;
  114. case 'Update':
  115. $this->handleUpdateActivity();
  116. break;
  117. default:
  118. // TODO: decide how to handle invalid verbs.
  119. break;
  120. }
  121. }
  122. public function verifyNoteAttachment()
  123. {
  124. $activity = $this->payload['object'];
  125. if(isset($activity['inReplyTo']) &&
  126. !empty($activity['inReplyTo']) &&
  127. Helpers::validateUrl($activity['inReplyTo'])
  128. ) {
  129. // reply detected, skip attachment check
  130. return true;
  131. }
  132. $valid = Helpers::verifyAttachments($activity);
  133. return $valid;
  134. }
  135. public function actorFirstOrCreate($actorUrl)
  136. {
  137. return Helpers::profileFetch($actorUrl);
  138. }
  139. public function handleAddActivity()
  140. {
  141. // stories ;)
  142. if(!isset(
  143. $this->payload['actor'],
  144. $this->payload['object']
  145. )) {
  146. return;
  147. }
  148. $actor = $this->payload['actor'];
  149. $obj = $this->payload['object'];
  150. if(!Helpers::validateUrl($actor)) {
  151. return;
  152. }
  153. if(!isset($obj['type'])) {
  154. return;
  155. }
  156. switch($obj['type']) {
  157. case 'Story':
  158. StoryFetch::dispatch($this->payload);
  159. break;
  160. }
  161. return;
  162. }
  163. public function handleCreateActivity()
  164. {
  165. $activity = $this->payload['object'];
  166. $actor = $this->actorFirstOrCreate($this->payload['actor']);
  167. if(!$actor || $actor->domain == null) {
  168. return;
  169. }
  170. if(!isset($activity['to'])) {
  171. return;
  172. }
  173. $to = isset($activity['to']) ? $activity['to'] : [];
  174. $cc = isset($activity['cc']) ? $activity['cc'] : [];
  175. if($activity['type'] == 'Question') {
  176. $this->handlePollCreate();
  177. return;
  178. }
  179. if( is_array($to) &&
  180. is_array($cc) &&
  181. count($to) == 1 &&
  182. count($cc) == 0 &&
  183. parse_url($to[0], PHP_URL_HOST) == config('pixelfed.domain.app')
  184. ) {
  185. $this->handleDirectMessage();
  186. return;
  187. }
  188. if($activity['type'] == 'Note' && !empty($activity['inReplyTo'])) {
  189. $this->handleNoteReply();
  190. } elseif ($activity['type'] == 'Note' && !empty($activity['attachment'])) {
  191. if(!$this->verifyNoteAttachment()) {
  192. return;
  193. }
  194. $this->handleNoteCreate();
  195. }
  196. return;
  197. }
  198. public function handleNoteReply()
  199. {
  200. $activity = $this->payload['object'];
  201. $actor = $this->actorFirstOrCreate($this->payload['actor']);
  202. if(!$actor || $actor->domain == null) {
  203. return;
  204. }
  205. $inReplyTo = $activity['inReplyTo'];
  206. $url = isset($activity['url']) ? $activity['url'] : $activity['id'];
  207. Helpers::statusFirstOrFetch($url, true);
  208. return;
  209. }
  210. public function handlePollCreate()
  211. {
  212. $activity = $this->payload['object'];
  213. $actor = $this->actorFirstOrCreate($this->payload['actor']);
  214. if(!$actor || $actor->domain == null) {
  215. return;
  216. }
  217. $url = isset($activity['url']) ? $activity['url'] : $activity['id'];
  218. Helpers::statusFirstOrFetch($url);
  219. return;
  220. }
  221. public function handleNoteCreate()
  222. {
  223. $activity = $this->payload['object'];
  224. $actor = $this->actorFirstOrCreate($this->payload['actor']);
  225. if(!$actor || $actor->domain == null) {
  226. return;
  227. }
  228. if( isset($activity['inReplyTo']) &&
  229. isset($activity['name']) &&
  230. !isset($activity['content']) &&
  231. !isset($activity['attachment']) &&
  232. Helpers::validateLocalUrl($activity['inReplyTo'])
  233. ) {
  234. $this->handlePollVote();
  235. return;
  236. }
  237. if($actor->followers_count == 0) {
  238. if(config('federation.activitypub.ingest.store_notes_without_followers')) {
  239. } else if(FollowerService::followerCount($actor->id, true) == 0) {
  240. return;
  241. }
  242. }
  243. $hasUrl = isset($activity['url']);
  244. $url = isset($activity['url']) ? $activity['url'] : $activity['id'];
  245. if($hasUrl) {
  246. if(Status::whereUri($url)->exists()) {
  247. return;
  248. }
  249. } else {
  250. if(Status::whereObjectUrl($url)->exists()) {
  251. return;
  252. }
  253. }
  254. Helpers::storeStatus(
  255. $url,
  256. $actor,
  257. $activity
  258. );
  259. return;
  260. }
  261. public function handlePollVote()
  262. {
  263. $activity = $this->payload['object'];
  264. $actor = $this->actorFirstOrCreate($this->payload['actor']);
  265. if(!$actor) {
  266. return;
  267. }
  268. $status = Helpers::statusFetch($activity['inReplyTo']);
  269. if(!$status) {
  270. return;
  271. }
  272. $poll = $status->poll;
  273. if(!$poll) {
  274. return;
  275. }
  276. if(now()->gt($poll->expires_at)) {
  277. return;
  278. }
  279. $choices = $poll->poll_options;
  280. $choice = array_search($activity['name'], $choices);
  281. if($choice === false) {
  282. return;
  283. }
  284. if(PollVote::whereStatusId($status->id)->whereProfileId($actor->id)->exists()) {
  285. return;
  286. }
  287. $vote = new PollVote;
  288. $vote->status_id = $status->id;
  289. $vote->profile_id = $actor->id;
  290. $vote->poll_id = $poll->id;
  291. $vote->choice = $choice;
  292. $vote->uri = isset($activity['id']) ? $activity['id'] : null;
  293. $vote->save();
  294. $tallies = $poll->cached_tallies;
  295. $tallies[$choice] = $tallies[$choice] + 1;
  296. $poll->cached_tallies = $tallies;
  297. $poll->votes_count = array_sum($tallies);
  298. $poll->save();
  299. PollService::del($status->id);
  300. return;
  301. }
  302. public function handleDirectMessage()
  303. {
  304. $activity = $this->payload['object'];
  305. $actor = $this->actorFirstOrCreate($this->payload['actor']);
  306. $profile = Profile::whereNull('domain')
  307. ->whereUsername(array_last(explode('/', $activity['to'][0])))
  308. ->firstOrFail();
  309. if(!$actor || in_array($actor->id, $profile->blockedIds()->toArray())) {
  310. return;
  311. }
  312. if(AccountService::blocksDomain($profile->id, $actor->domain) == true) {
  313. return;
  314. }
  315. $msg = $activity['content'];
  316. $msgText = strip_tags($activity['content']);
  317. if(Str::startsWith($msgText, '@' . $profile->username)) {
  318. $len = strlen('@' . $profile->username);
  319. $msgText = substr($msgText, $len + 1);
  320. }
  321. if($profile->user->settings->public_dm == false || $profile->is_private) {
  322. if($profile->follows($actor) == true) {
  323. $hidden = false;
  324. } else {
  325. $hidden = true;
  326. }
  327. } else {
  328. $hidden = false;
  329. }
  330. $status = new Status;
  331. $status->profile_id = $actor->id;
  332. $status->caption = $msgText;
  333. $status->rendered = $msg;
  334. $status->visibility = 'direct';
  335. $status->scope = 'direct';
  336. $status->url = $activity['id'];
  337. $status->uri = $activity['id'];
  338. $status->object_url = $activity['id'];
  339. $status->in_reply_to_profile_id = $profile->id;
  340. $status->saveQuietly();
  341. $dm = new DirectMessage;
  342. $dm->to_id = $profile->id;
  343. $dm->from_id = $actor->id;
  344. $dm->status_id = $status->id;
  345. $dm->is_hidden = $hidden;
  346. $dm->type = 'text';
  347. $dm->save();
  348. Conversation::updateOrInsert(
  349. [
  350. 'to_id' => $profile->id,
  351. 'from_id' => $actor->id
  352. ],
  353. [
  354. 'type' => 'text',
  355. 'status_id' => $status->id,
  356. 'dm_id' => $dm->id,
  357. 'is_hidden' => $hidden
  358. ]
  359. );
  360. if(count($activity['attachment'])) {
  361. $photos = 0;
  362. $videos = 0;
  363. $allowed = explode(',', config_cache('pixelfed.media_types'));
  364. $activity['attachment'] = array_slice($activity['attachment'], 0, config_cache('pixelfed.max_album_length'));
  365. foreach($activity['attachment'] as $a) {
  366. $type = $a['mediaType'];
  367. $url = $a['url'];
  368. $valid = Helpers::validateUrl($url);
  369. if(in_array($type, $allowed) == false || $valid == false) {
  370. continue;
  371. }
  372. $media = new Media();
  373. $media->remote_media = true;
  374. $media->status_id = $status->id;
  375. $media->profile_id = $status->profile_id;
  376. $media->user_id = null;
  377. $media->media_path = $url;
  378. $media->remote_url = $url;
  379. $media->mime = $type;
  380. $media->save();
  381. if(explode('/', $type)[0] == 'image') {
  382. $photos = $photos + 1;
  383. }
  384. if(explode('/', $type)[0] == 'video') {
  385. $videos = $videos + 1;
  386. }
  387. }
  388. if($photos && $videos == 0) {
  389. $dm->type = $photos == 1 ? 'photo' : 'photos';
  390. $dm->save();
  391. }
  392. if($videos && $photos == 0) {
  393. $dm->type = $videos == 1 ? 'video' : 'videos';
  394. $dm->save();
  395. }
  396. }
  397. if(filter_var($msgText, FILTER_VALIDATE_URL)) {
  398. if(Helpers::validateUrl($msgText)) {
  399. $dm->type = 'link';
  400. $dm->meta = [
  401. 'domain' => parse_url($msgText, PHP_URL_HOST),
  402. 'local' => parse_url($msgText, PHP_URL_HOST) ==
  403. parse_url(config('app.url'), PHP_URL_HOST)
  404. ];
  405. $dm->save();
  406. }
  407. }
  408. $nf = UserFilter::whereUserId($profile->id)
  409. ->whereFilterableId($actor->id)
  410. ->whereFilterableType('App\Profile')
  411. ->whereFilterType('dm.mute')
  412. ->exists();
  413. if($profile->domain == null && $hidden == false && !$nf) {
  414. $notification = new Notification();
  415. $notification->profile_id = $profile->id;
  416. $notification->actor_id = $actor->id;
  417. $notification->action = 'dm';
  418. $notification->item_id = $dm->id;
  419. $notification->item_type = "App\DirectMessage";
  420. $notification->save();
  421. }
  422. return;
  423. }
  424. public function handleFollowActivity()
  425. {
  426. $actor = $this->actorFirstOrCreate($this->payload['actor']);
  427. $target = $this->actorFirstOrCreate($this->payload['object']);
  428. if(!$actor || !$target) {
  429. return;
  430. }
  431. if($actor->domain == null || $target->domain !== null) {
  432. return;
  433. }
  434. if(AccountService::blocksDomain($target->id, $actor->domain) == true) {
  435. return;
  436. }
  437. if(
  438. Follower::whereProfileId($actor->id)
  439. ->whereFollowingId($target->id)
  440. ->exists() ||
  441. FollowRequest::whereFollowerId($actor->id)
  442. ->whereFollowingId($target->id)
  443. ->exists()
  444. ) {
  445. return;
  446. }
  447. $blocks = UserFilterService::blocks($target->id);
  448. if($blocks && in_array($actor->id, $blocks)) {
  449. return;
  450. }
  451. if($target->is_private == true) {
  452. FollowRequest::updateOrCreate([
  453. 'follower_id' => $actor->id,
  454. 'following_id' => $target->id,
  455. ],[
  456. 'activity' => collect($this->payload)->only(['id','actor','object','type'])->toArray()
  457. ]);
  458. } else {
  459. $follower = new Follower;
  460. $follower->profile_id = $actor->id;
  461. $follower->following_id = $target->id;
  462. $follower->local_profile = empty($actor->domain);
  463. $follower->save();
  464. FollowPipeline::dispatch($follower);
  465. FollowerService::add($actor->id, $target->id);
  466. // send Accept to remote profile
  467. $accept = [
  468. '@context' => 'https://www.w3.org/ns/activitystreams',
  469. 'id' => $target->permalink().'#accepts/follows/' . $follower->id,
  470. 'type' => 'Accept',
  471. 'actor' => $target->permalink(),
  472. 'object' => [
  473. 'id' => $this->payload['id'],
  474. 'actor' => $actor->permalink(),
  475. 'type' => 'Follow',
  476. 'object' => $target->permalink()
  477. ]
  478. ];
  479. Helpers::sendSignedObject($target, $actor->inbox_url, $accept);
  480. Cache::forget('profile:follower_count:'.$target->id);
  481. Cache::forget('profile:follower_count:'.$actor->id);
  482. Cache::forget('profile:following_count:'.$target->id);
  483. Cache::forget('profile:following_count:'.$actor->id);
  484. }
  485. return;
  486. }
  487. public function handleAnnounceActivity()
  488. {
  489. $actor = $this->actorFirstOrCreate($this->payload['actor']);
  490. $activity = $this->payload['object'];
  491. if(!$actor || $actor->domain == null) {
  492. return;
  493. }
  494. $parent = Helpers::statusFetch($activity);
  495. if(!$parent || empty($parent)) {
  496. return;
  497. }
  498. if(AccountService::blocksDomain($parent->profile_id, $actor->domain) == true) {
  499. return;
  500. }
  501. $blocks = UserFilterService::blocks($parent->profile_id);
  502. if($blocks && in_array($actor->id, $blocks)) {
  503. return;
  504. }
  505. $status = Status::firstOrCreate([
  506. 'profile_id' => $actor->id,
  507. 'reblog_of_id' => $parent->id,
  508. 'type' => 'share'
  509. ]);
  510. Notification::firstOrCreate(
  511. [
  512. 'profile_id' => $parent->profile_id,
  513. 'actor_id' => $actor->id,
  514. 'action' => 'share',
  515. 'item_id' => $parent->id,
  516. 'item_type' => 'App\Status',
  517. ]
  518. );
  519. $parent->reblogs_count = $parent->reblogs_count + 1;
  520. $parent->save();
  521. ReblogService::addPostReblog($parent->profile_id, $status->id);
  522. return;
  523. }
  524. public function handleAcceptActivity()
  525. {
  526. $actor = $this->payload['object']['actor'];
  527. $obj = $this->payload['object']['object'];
  528. $type = $this->payload['object']['type'];
  529. if($type !== 'Follow') {
  530. return;
  531. }
  532. $actor = Helpers::validateLocalUrl($actor);
  533. $target = Helpers::validateUrl($obj);
  534. if(!$actor || !$target) {
  535. return;
  536. }
  537. $actor = Helpers::profileFetch($actor);
  538. $target = Helpers::profileFetch($target);
  539. if(!$actor || !$target) {
  540. return;
  541. }
  542. if(AccountService::blocksDomain($target->id, $actor->domain) == true) {
  543. return;
  544. }
  545. $request = FollowRequest::whereFollowerId($actor->id)
  546. ->whereFollowingId($target->id)
  547. ->whereIsRejected(false)
  548. ->first();
  549. if(!$request) {
  550. return;
  551. }
  552. $follower = Follower::firstOrCreate([
  553. 'profile_id' => $actor->id,
  554. 'following_id' => $target->id,
  555. ]);
  556. FollowPipeline::dispatch($follower);
  557. $request->delete();
  558. return;
  559. }
  560. public function handleDeleteActivity()
  561. {
  562. if(!isset(
  563. $this->payload['actor'],
  564. $this->payload['object']
  565. )) {
  566. return;
  567. }
  568. $actor = $this->payload['actor'];
  569. $obj = $this->payload['object'];
  570. if(is_string($obj) == true && $actor == $obj && Helpers::validateUrl($obj)) {
  571. $profile = Profile::whereRemoteUrl($obj)->first();
  572. if(!$profile || $profile->private_key != null) {
  573. return;
  574. }
  575. DeleteRemoteProfilePipeline::dispatch($profile)->onQueue('inbox');
  576. return;
  577. } else {
  578. if(!isset(
  579. $obj['id'],
  580. $this->payload['object'],
  581. $this->payload['object']['id'],
  582. $this->payload['object']['type']
  583. )) {
  584. return;
  585. }
  586. $type = $this->payload['object']['type'];
  587. $typeCheck = in_array($type, ['Person', 'Tombstone', 'Story']);
  588. if(!Helpers::validateUrl($actor) || !Helpers::validateUrl($obj['id']) || !$typeCheck) {
  589. return;
  590. }
  591. if(parse_url($obj['id'], PHP_URL_HOST) !== parse_url($actor, PHP_URL_HOST)) {
  592. return;
  593. }
  594. $id = $this->payload['object']['id'];
  595. switch ($type) {
  596. case 'Person':
  597. $profile = Profile::whereRemoteUrl($actor)->first();
  598. if(!$profile || $profile->private_key != null) {
  599. return;
  600. }
  601. DeleteRemoteProfilePipeline::dispatch($profile)->onQueue('inbox');
  602. return;
  603. break;
  604. case 'Tombstone':
  605. $profile = Profile::whereRemoteUrl($actor)->first();
  606. if(!$profile || $profile->private_key != null) {
  607. return;
  608. }
  609. $status = Status::where('object_url', $id)->first();
  610. if(!$status) {
  611. $status = Status::where('url', $id)->first();
  612. if(!$status) {
  613. return;
  614. }
  615. }
  616. if($status->profile_id != $profile->id) {
  617. return;
  618. }
  619. if($status->scope && in_array($status->scope, ['public', 'unlisted', 'private'])) {
  620. if($status->type && !in_array($status->type, ['story:reaction', 'story:reply', 'reply'])) {
  621. FeedRemoveRemotePipeline::dispatch($status->id, $status->profile_id)->onQueue('feed');
  622. }
  623. }
  624. RemoteStatusDelete::dispatch($status)->onQueue('high');
  625. return;
  626. break;
  627. case 'Story':
  628. $story = Story::whereObjectId($id)
  629. ->first();
  630. if($story) {
  631. StoryExpire::dispatch($story)->onQueue('story');
  632. }
  633. return;
  634. break;
  635. default:
  636. return;
  637. break;
  638. }
  639. }
  640. return;
  641. }
  642. public function handleLikeActivity()
  643. {
  644. $actor = $this->payload['actor'];
  645. if(!Helpers::validateUrl($actor)) {
  646. return;
  647. }
  648. $profile = self::actorFirstOrCreate($actor);
  649. $obj = $this->payload['object'];
  650. if(!Helpers::validateUrl($obj)) {
  651. return;
  652. }
  653. $status = Helpers::statusFirstOrFetch($obj);
  654. if(!$status || !$profile) {
  655. return;
  656. }
  657. if(AccountService::blocksDomain($status->profile_id, $profile->domain) == true) {
  658. return;
  659. }
  660. $blocks = UserFilterService::blocks($status->profile_id);
  661. if($blocks && in_array($profile->id, $blocks)) {
  662. return;
  663. }
  664. $like = Like::firstOrCreate([
  665. 'profile_id' => $profile->id,
  666. 'status_id' => $status->id
  667. ]);
  668. if($like->wasRecentlyCreated == true) {
  669. $status->likes_count = $status->likes_count + 1;
  670. $status->save();
  671. LikePipeline::dispatch($like);
  672. }
  673. return;
  674. }
  675. public function handleRejectActivity()
  676. {
  677. }
  678. public function handleUndoActivity()
  679. {
  680. $actor = $this->payload['actor'];
  681. $profile = self::actorFirstOrCreate($actor);
  682. $obj = $this->payload['object'];
  683. if(!$profile) {
  684. return;
  685. }
  686. // TODO: Some implementations do not inline the object, skip for now
  687. if(!$obj || !is_array($obj) || !isset($obj['type'])) {
  688. return;
  689. }
  690. switch ($obj['type']) {
  691. case 'Accept':
  692. break;
  693. case 'Announce':
  694. if(is_array($obj) && isset($obj['object'])) {
  695. $obj = $obj['object'];
  696. }
  697. if(!is_string($obj)) {
  698. return;
  699. }
  700. if(Helpers::validateLocalUrl($obj)) {
  701. $parsedId = last(explode('/', $obj));
  702. $status = Status::find($parsedId);
  703. } else {
  704. $status = Status::whereUri($obj)->first();
  705. }
  706. if(!$status) {
  707. return;
  708. }
  709. if(AccountService::blocksDomain($status->profile_id, $profile->domain) == true) {
  710. return;
  711. }
  712. FeedRemoveRemotePipeline::dispatch($status->id, $status->profile_id)->onQueue('feed');
  713. Status::whereProfileId($profile->id)
  714. ->whereReblogOfId($status->id)
  715. ->delete();
  716. ReblogService::removePostReblog($profile->id, $status->id);
  717. Notification::whereProfileId($status->profile_id)
  718. ->whereActorId($profile->id)
  719. ->whereAction('share')
  720. ->whereItemId($status->reblog_of_id)
  721. ->whereItemType('App\Status')
  722. ->forceDelete();
  723. break;
  724. case 'Block':
  725. break;
  726. case 'Follow':
  727. $following = self::actorFirstOrCreate($obj['object']);
  728. if(!$following) {
  729. return;
  730. }
  731. if(AccountService::blocksDomain($following->id, $profile->domain) == true) {
  732. return;
  733. }
  734. Follower::whereProfileId($profile->id)
  735. ->whereFollowingId($following->id)
  736. ->delete();
  737. Notification::whereProfileId($following->id)
  738. ->whereActorId($profile->id)
  739. ->whereAction('follow')
  740. ->whereItemId($following->id)
  741. ->whereItemType('App\Profile')
  742. ->forceDelete();
  743. FollowerService::remove($profile->id, $following->id);
  744. break;
  745. case 'Like':
  746. $objectUri = $obj['object'];
  747. if(!is_string($objectUri)) {
  748. if(is_array($objectUri) && isset($objectUri['id']) && is_string($objectUri['id'])) {
  749. $objectUri = $objectUri['id'];
  750. } else {
  751. return;
  752. }
  753. }
  754. $status = Helpers::statusFirstOrFetch($objectUri);
  755. if(!$status) {
  756. return;
  757. }
  758. if(AccountService::blocksDomain($status->profile_id, $profile->domain) == true) {
  759. return;
  760. }
  761. Like::whereProfileId($profile->id)
  762. ->whereStatusId($status->id)
  763. ->forceDelete();
  764. Notification::whereProfileId($status->profile_id)
  765. ->whereActorId($profile->id)
  766. ->whereAction('like')
  767. ->whereItemId($status->id)
  768. ->whereItemType('App\Status')
  769. ->forceDelete();
  770. break;
  771. }
  772. return;
  773. }
  774. public function handleViewActivity()
  775. {
  776. if(!isset(
  777. $this->payload['actor'],
  778. $this->payload['object']
  779. )) {
  780. return;
  781. }
  782. $actor = $this->payload['actor'];
  783. $obj = $this->payload['object'];
  784. if(!Helpers::validateUrl($actor)) {
  785. return;
  786. }
  787. if(!$obj || !is_array($obj)) {
  788. return;
  789. }
  790. if(!isset($obj['type']) || !isset($obj['object']) || $obj['type'] != 'Story') {
  791. return;
  792. }
  793. if(!Helpers::validateLocalUrl($obj['object'])) {
  794. return;
  795. }
  796. $profile = Helpers::profileFetch($actor);
  797. $storyId = Str::of($obj['object'])->explode('/')->last();
  798. $story = Story::whereActive(true)
  799. ->whereLocal(true)
  800. ->find($storyId);
  801. if(!$story) {
  802. return;
  803. }
  804. if(AccountService::blocksDomain($story->profile_id, $profile->domain) == true) {
  805. return;
  806. }
  807. if(!FollowerService::follows($profile->id, $story->profile_id)) {
  808. return;
  809. }
  810. $view = StoryView::firstOrCreate([
  811. 'story_id' => $story->id,
  812. 'profile_id' => $profile->id
  813. ]);
  814. if($view->wasRecentlyCreated == true) {
  815. $story->view_count++;
  816. $story->save();
  817. }
  818. return;
  819. }
  820. public function handleStoryReactionActivity()
  821. {
  822. if(!isset(
  823. $this->payload['actor'],
  824. $this->payload['id'],
  825. $this->payload['inReplyTo'],
  826. $this->payload['content']
  827. )) {
  828. return;
  829. }
  830. $id = $this->payload['id'];
  831. $actor = $this->payload['actor'];
  832. $storyUrl = $this->payload['inReplyTo'];
  833. $to = $this->payload['to'];
  834. $text = Purify::clean($this->payload['content']);
  835. if(parse_url($id, PHP_URL_HOST) !== parse_url($actor, PHP_URL_HOST)) {
  836. return;
  837. }
  838. if(!Helpers::validateUrl($id) || !Helpers::validateUrl($actor)) {
  839. return;
  840. }
  841. if(!Helpers::validateLocalUrl($storyUrl)) {
  842. return;
  843. }
  844. if(!Helpers::validateLocalUrl($to)) {
  845. return;
  846. }
  847. if(Status::whereObjectUrl($id)->exists()) {
  848. return;
  849. }
  850. $storyId = Str::of($storyUrl)->explode('/')->last();
  851. $targetProfile = Helpers::profileFetch($to);
  852. $story = Story::whereProfileId($targetProfile->id)
  853. ->find($storyId);
  854. if(!$story) {
  855. return;
  856. }
  857. if($story->can_react == false) {
  858. return;
  859. }
  860. $actorProfile = Helpers::profileFetch($actor);
  861. if(AccountService::blocksDomain($targetProfile->id, $actorProfile->domain) == true) {
  862. return;
  863. }
  864. if(!FollowerService::follows($actorProfile->id, $targetProfile->id)) {
  865. return;
  866. }
  867. $url = $id;
  868. if(str_ends_with($url, '/activity')) {
  869. $url = substr($url, 0, -9);
  870. }
  871. $status = new Status;
  872. $status->profile_id = $actorProfile->id;
  873. $status->type = 'story:reaction';
  874. $status->url = $url;
  875. $status->uri = $url;
  876. $status->object_url = $url;
  877. $status->caption = $text;
  878. $status->rendered = $text;
  879. $status->scope = 'direct';
  880. $status->visibility = 'direct';
  881. $status->in_reply_to_profile_id = $story->profile_id;
  882. $status->entities = json_encode([
  883. 'story_id' => $story->id,
  884. 'reaction' => $text
  885. ]);
  886. $status->save();
  887. $dm = new DirectMessage;
  888. $dm->to_id = $story->profile_id;
  889. $dm->from_id = $actorProfile->id;
  890. $dm->type = 'story:react';
  891. $dm->status_id = $status->id;
  892. $dm->meta = json_encode([
  893. 'story_username' => $targetProfile->username,
  894. 'story_actor_username' => $actorProfile->username,
  895. 'story_id' => $story->id,
  896. 'story_media_url' => url(Storage::url($story->path)),
  897. 'reaction' => $text
  898. ]);
  899. $dm->save();
  900. Conversation::updateOrInsert(
  901. [
  902. 'to_id' => $story->profile_id,
  903. 'from_id' => $actorProfile->id
  904. ],
  905. [
  906. 'type' => 'story:react',
  907. 'status_id' => $status->id,
  908. 'dm_id' => $dm->id,
  909. 'is_hidden' => false
  910. ]
  911. );
  912. $n = new Notification;
  913. $n->profile_id = $dm->to_id;
  914. $n->actor_id = $dm->from_id;
  915. $n->item_id = $dm->id;
  916. $n->item_type = 'App\DirectMessage';
  917. $n->action = 'story:react';
  918. $n->save();
  919. return;
  920. }
  921. public function handleStoryReplyActivity()
  922. {
  923. if(!isset(
  924. $this->payload['actor'],
  925. $this->payload['id'],
  926. $this->payload['inReplyTo'],
  927. $this->payload['content']
  928. )) {
  929. return;
  930. }
  931. $id = $this->payload['id'];
  932. $actor = $this->payload['actor'];
  933. $storyUrl = $this->payload['inReplyTo'];
  934. $to = $this->payload['to'];
  935. $text = Purify::clean($this->payload['content']);
  936. if(parse_url($id, PHP_URL_HOST) !== parse_url($actor, PHP_URL_HOST)) {
  937. return;
  938. }
  939. if(!Helpers::validateUrl($id) || !Helpers::validateUrl($actor)) {
  940. return;
  941. }
  942. if(!Helpers::validateLocalUrl($storyUrl)) {
  943. return;
  944. }
  945. if(!Helpers::validateLocalUrl($to)) {
  946. return;
  947. }
  948. if(Status::whereObjectUrl($id)->exists()) {
  949. return;
  950. }
  951. $storyId = Str::of($storyUrl)->explode('/')->last();
  952. $targetProfile = Helpers::profileFetch($to);
  953. $story = Story::whereProfileId($targetProfile->id)
  954. ->find($storyId);
  955. if(!$story) {
  956. return;
  957. }
  958. if($story->can_react == false) {
  959. return;
  960. }
  961. $actorProfile = Helpers::profileFetch($actor);
  962. if(AccountService::blocksDomain($targetProfile->id, $actorProfile->domain) == true) {
  963. return;
  964. }
  965. if(!FollowerService::follows($actorProfile->id, $targetProfile->id)) {
  966. return;
  967. }
  968. $url = $id;
  969. if(str_ends_with($url, '/activity')) {
  970. $url = substr($url, 0, -9);
  971. }
  972. $status = new Status;
  973. $status->profile_id = $actorProfile->id;
  974. $status->type = 'story:reply';
  975. $status->caption = $text;
  976. $status->rendered = $text;
  977. $status->url = $url;
  978. $status->uri = $url;
  979. $status->object_url = $url;
  980. $status->scope = 'direct';
  981. $status->visibility = 'direct';
  982. $status->in_reply_to_profile_id = $story->profile_id;
  983. $status->entities = json_encode([
  984. 'story_id' => $story->id,
  985. 'caption' => $text
  986. ]);
  987. $status->save();
  988. $dm = new DirectMessage;
  989. $dm->to_id = $story->profile_id;
  990. $dm->from_id = $actorProfile->id;
  991. $dm->type = 'story:comment';
  992. $dm->status_id = $status->id;
  993. $dm->meta = json_encode([
  994. 'story_username' => $targetProfile->username,
  995. 'story_actor_username' => $actorProfile->username,
  996. 'story_id' => $story->id,
  997. 'story_media_url' => url(Storage::url($story->path)),
  998. 'caption' => $text
  999. ]);
  1000. $dm->save();
  1001. Conversation::updateOrInsert(
  1002. [
  1003. 'to_id' => $story->profile_id,
  1004. 'from_id' => $actorProfile->id
  1005. ],
  1006. [
  1007. 'type' => 'story:comment',
  1008. 'status_id' => $status->id,
  1009. 'dm_id' => $dm->id,
  1010. 'is_hidden' => false
  1011. ]
  1012. );
  1013. $n = new Notification;
  1014. $n->profile_id = $dm->to_id;
  1015. $n->actor_id = $dm->from_id;
  1016. $n->item_id = $dm->id;
  1017. $n->item_type = 'App\DirectMessage';
  1018. $n->action = 'story:comment';
  1019. $n->save();
  1020. return;
  1021. }
  1022. public function handleFlagActivity()
  1023. {
  1024. if(!isset(
  1025. $this->payload['id'],
  1026. $this->payload['type'],
  1027. $this->payload['actor'],
  1028. $this->payload['object']
  1029. )) {
  1030. return;
  1031. }
  1032. $id = $this->payload['id'];
  1033. $actor = $this->payload['actor'];
  1034. if(Helpers::validateLocalUrl($id) || parse_url($id, PHP_URL_HOST) !== parse_url($actor, PHP_URL_HOST)) {
  1035. return;
  1036. }
  1037. $content = isset($this->payload['content']) ? Purify::clean($this->payload['content']) : null;
  1038. $object = $this->payload['object'];
  1039. if(empty($object) || (!is_array($object) && !is_string($object))) {
  1040. return;
  1041. }
  1042. if(is_array($object) && count($object) > 100) {
  1043. return;
  1044. }
  1045. $objects = collect([]);
  1046. $accountId = null;
  1047. foreach($object as $objectUrl) {
  1048. if(!Helpers::validateLocalUrl($objectUrl)) {
  1049. continue;
  1050. }
  1051. if(str_contains($objectUrl, '/users/')) {
  1052. $username = last(explode('/', $objectUrl));
  1053. $profileId = Profile::whereUsername($username)->first();
  1054. if($profileId) {
  1055. $accountId = $profileId->id;
  1056. }
  1057. } else if(str_contains($objectUrl, '/p/')) {
  1058. $postId = last(explode('/', $objectUrl));
  1059. $objects->push($postId);
  1060. } else {
  1061. continue;
  1062. }
  1063. }
  1064. if(!$accountId || !$objects->count()) {
  1065. return;
  1066. }
  1067. $instanceHost = parse_url($id, PHP_URL_HOST);
  1068. $instance = Instance::updateOrCreate([
  1069. 'domain' => $instanceHost
  1070. ]);
  1071. $report = new RemoteReport;
  1072. $report->status_ids = $objects->toArray();
  1073. $report->comment = $content;
  1074. $report->account_id = $accountId;
  1075. $report->uri = $id;
  1076. $report->instance_id = $instance->id;
  1077. $report->report_meta = [
  1078. 'actor' => $actor,
  1079. 'object' => $object
  1080. ];
  1081. $report->save();
  1082. return;
  1083. }
  1084. public function handleUpdateActivity()
  1085. {
  1086. $activity = $this->payload['object'];
  1087. if(!isset($activity['type'], $activity['id'])) {
  1088. return;
  1089. }
  1090. if(!Helpers::validateUrl($activity['id'])) {
  1091. return;
  1092. }
  1093. if($activity['type'] === 'Note') {
  1094. if(Status::whereObjectUrl($activity['id'])->exists()) {
  1095. StatusRemoteUpdatePipeline::dispatch($activity);
  1096. }
  1097. } else if ($activity['type'] === 'Person') {
  1098. if(UpdatePersonValidator::validate($this->payload)) {
  1099. HandleUpdateActivity::dispatch($this->payload)->onQueue('low');
  1100. }
  1101. }
  1102. }
  1103. }