StoryFetch.php 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. <?php
  2. namespace App\Jobs\StoryPipeline;
  3. use Cache, Log;
  4. use App\Story;
  5. use Illuminate\Bus\Queueable;
  6. use Illuminate\Contracts\Queue\ShouldQueue;
  7. use Illuminate\Foundation\Bus\Dispatchable;
  8. use Illuminate\Queue\InteractsWithQueue;
  9. use Illuminate\Queue\SerializesModels;
  10. use App\Util\ActivityPub\Helpers;
  11. use App\Services\FollowerService;
  12. use App\Util\Lexer\Bearcap;
  13. use Illuminate\Support\Facades\Http;
  14. use Illuminate\Http\Client\RequestException;
  15. use Illuminate\Http\Client\ConnectionException;
  16. use App\Util\ActivityPub\Validator\StoryValidator;
  17. use App\Services\StoryService;
  18. use App\Services\MediaPathService;
  19. use Illuminate\Support\Str;
  20. use Illuminate\Http\File;
  21. use Illuminate\Support\Facades\Storage;
  22. class StoryFetch implements ShouldQueue
  23. {
  24. use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
  25. protected $activity;
  26. /**
  27. * Create a new job instance.
  28. *
  29. * @return void
  30. */
  31. public function __construct($activity)
  32. {
  33. $this->activity = $activity;
  34. }
  35. /**
  36. * Execute the job.
  37. *
  38. * @return void
  39. */
  40. public function handle()
  41. {
  42. $activity = $this->activity;
  43. $activityId = $activity['id'];
  44. $activityActor = $activity['actor'];
  45. if(parse_url($activityId, PHP_URL_HOST) !== parse_url($activityActor, PHP_URL_HOST)) {
  46. return;
  47. }
  48. $bearcap = Bearcap::decode($activity['object']['object']);
  49. if(!$bearcap) {
  50. return;
  51. }
  52. $url = $bearcap['url'];
  53. $token = $bearcap['token'];
  54. if(parse_url($activityId, PHP_URL_HOST) !== parse_url($url, PHP_URL_HOST)) {
  55. return;
  56. }
  57. $version = config('pixelfed.version');
  58. $appUrl = config('app.url');
  59. $headers = [
  60. 'Accept' => 'application/json',
  61. 'Authorization' => 'Bearer ' . $token,
  62. 'User-Agent' => "(Pixelfed/{$version}; +{$appUrl})",
  63. ];
  64. try {
  65. $res = Http::withHeaders($headers)
  66. ->timeout(30)
  67. ->get($url);
  68. } catch (RequestException $e) {
  69. return false;
  70. } catch (ConnectionException $e) {
  71. return false;
  72. } catch (\Exception $e) {
  73. return false;
  74. }
  75. $payload = $res->json();
  76. if(StoryValidator::validate($payload) == false) {
  77. return;
  78. }
  79. if(Helpers::validateUrl($payload['attachment']['url']) == false) {
  80. return;
  81. }
  82. $type = $payload['attachment']['type'] == 'Image' ? 'photo' : 'video';
  83. $profile = Helpers::profileFetch($payload['attributedTo']);
  84. $ext = pathinfo($payload['attachment']['url'], PATHINFO_EXTENSION);
  85. $storagePath = MediaPathService::story($profile);
  86. $fileName = Str::random(random_int(2, 12)) . '_' . Str::random(random_int(32, 35)) . '_' . Str::random(random_int(1, 14)) . '.' . $ext;
  87. $contextOptions = [
  88. 'ssl' => [
  89. 'verify_peer' => false,
  90. 'verify_peername' => false
  91. ]
  92. ];
  93. $ctx = stream_context_create($contextOptions);
  94. $data = file_get_contents($payload['attachment']['url'], false, $ctx);
  95. $tmpBase = storage_path('app/remcache/');
  96. $tmpPath = $profile->id . '-' . $fileName;
  97. $tmpName = $tmpBase . $tmpPath;
  98. file_put_contents($tmpName, $data);
  99. $disk = Storage::disk(config('filesystems.default'));
  100. $path = $disk->putFileAs($storagePath, new File($tmpName), $fileName, 'public');
  101. $size = filesize($tmpName);
  102. unlink($tmpName);
  103. $story = new Story;
  104. $story->profile_id = $profile->id;
  105. $story->object_id = $payload['id'];
  106. $story->size = $size;
  107. $story->mime = $payload['attachment']['mediaType'];
  108. $story->duration = $payload['duration'];
  109. $story->media_url = $payload['attachment']['url'];
  110. $story->type = $type;
  111. $story->public = false;
  112. $story->local = false;
  113. $story->active = true;
  114. $story->path = $path;
  115. $story->view_count = 0;
  116. $story->can_reply = $payload['can_reply'];
  117. $story->can_react = $payload['can_react'];
  118. $story->created_at = now()->parse($payload['published']);
  119. $story->expires_at = now()->parse($payload['expiresAt']);
  120. $story->save();
  121. StoryService::delLatest($story->profile_id);
  122. }
  123. }