validate([ 'url' => 'required|url|max:2048', 'viewport_width' => 'integer|min:320|max:3840', 'viewport_height' => 'integer|min:240|max:2160', 'quality' => 'integer|min:1|max:100', 'timeout' => 'integer|min:5|max:300', 'delay' => 'integer|min:0|max:30000', 'block_ads' => 'boolean', 'block_cookie_banners' => 'boolean', 'block_trackers' => 'boolean', 'webhook_url' => 'nullable|url|max:2048', 'webhook_events_filter' => 'nullable|array', 'webhook_events_filter.*' => 'in:queued,processing,completed,failed' ]); $uuid = Str::uuid()->toString(); $job = CrawlShotJob::create([ 'uuid' => $uuid, 'type' => 'shot', 'url' => $validated['url'], 'status' => 'queued', 'webhook_url' => $validated['webhook_url'] ?? null, 'webhook_events_filter' => isset($validated['webhook_events_filter']) ? $validated['webhook_events_filter'] : ['queued', 'processing', 'completed', 'failed'], 'parameters' => array_filter([ 'viewport_width' => $validated['viewport_width'] ?? 1920, 'viewport_height' => $validated['viewport_height'] ?? 1080, 'format' => 'webp', // Force WebP for all screenshots 'quality' => $validated['quality'] ?? 90, 'timeout' => $validated['timeout'] ?? 30, 'delay' => $validated['delay'] ?? 0, 'block_ads' => $validated['block_ads'] ?? true, 'block_cookie_banners' => $validated['block_cookie_banners'] ?? true, 'block_trackers' => $validated['block_trackers'] ?? true ]) ]); ProcessCrawlShotJob::dispatch($uuid); return response()->json([ 'uuid' => $uuid, 'status' => 'queued', 'message' => 'Screenshot job initiated successfully' ], 201); } public function status(string $uuid): JsonResponse { $job = CrawlShotJob::where('uuid', $uuid)->first(); if (!$job) { return response()->json(['error' => 'Job not found'], 404); } $response = [ 'uuid' => $job->uuid, 'status' => $job->status, 'url' => $job->url, 'created_at' => $job->created_at->toISOString() ]; if ($job->started_at) { $response['started_at'] = $job->started_at->toISOString(); } if ($job->completed_at) { $response['completed_at'] = $job->completed_at->toISOString(); } if ($job->status === 'completed' && $job->file_path) { $imageData = Storage::get($job->file_path); $response['result'] = [ 'image' => [ 'url' => url("/api/shot/{$job->uuid}.webp"), 'raw' => base64_encode($imageData), ], 'mime_type' => 'image/webp', 'format' => 'webp', 'width' => $job->parameters['viewport_width'] ?? 1920, 'height' => $job->parameters['viewport_height'] ?? 1080, 'size' => strlen($imageData) ]; } if ($job->status === 'failed' && $job->error_message) { $response['error'] = $job->error_message; } return response()->json($response); } public function serve(string $uuid): Response { $job = CrawlShotJob::where('uuid', $uuid)->where('type', 'shot')->first(); if (!$job || $job->status !== 'completed') { return response('Screenshot not found or not ready', 404); } if (!$job->file_path || !Storage::exists($job->file_path)) { return response('Screenshot file not found', 404); } // Always serve as WebP return response(Storage::get($job->file_path)) ->header('Content-Type', 'image/webp'); } public function index(): JsonResponse { $jobs = CrawlShotJob::where('type', 'shot') ->orderBy('created_at', 'desc') ->paginate(20); $response = [ 'jobs' => $jobs->items(), 'pagination' => [ 'current_page' => $jobs->currentPage(), 'total_pages' => $jobs->lastPage(), 'total_items' => $jobs->total(), 'per_page' => $jobs->perPage() ] ]; return response()->json($response); } private function getMimeType(string $format): string { $mimeTypes = [ 'jpg' => 'image/jpeg', 'png' => 'image/png', 'webp' => 'image/webp' ]; return $mimeTypes[$format] ?? 'image/webp'; } }