Files
omg-wtf-ai/app/Jobs/MirrorToolLogo.php
T
2026-08-11 00:06:19 +03:00

75 lines
2.2 KiB
PHP

<?php
namespace App\Jobs;
use App\Models\Tool;
use App\Support\FaviconResolver;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Http\Client\Response;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
class MirrorToolLogo implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public Tool $tool) {}
public function handle(FaviconResolver $resolver): void
{
$tool = $this->tool->fresh();
// The tool may have been deleted, or already mirrored by an earlier
// run of this same job, since this was queued.
if (! $tool || $tool->hasLocalLogo()) {
return;
}
// logo_url is a hint, not a guarantee -- it's often a guessed
// `{domain}/favicon.ico` that 404s or isn't actually an image.
// Fall back to properly resolving the site's real favicon.
$response = ($tool->logo_url ? $this->fetchImage($tool->logo_url) : null)
?? $this->fetchImage($resolver->resolve($tool->url));
if (! $response) {
return;
}
$extension = match ($response->header('Content-Type')) {
'image/png' => 'png',
'image/jpeg' => 'jpg',
'image/webp' => 'webp',
'image/x-icon', 'image/vnd.microsoft.icon' => 'ico',
default => 'png',
};
$path = "tools/{$tool->slug}-".Str::random(8).".{$extension}";
Storage::disk('public')->put($path, $response->body());
$tool->update(['logo_url' => Storage::disk('public')->url($path)]);
}
private function fetchImage(?string $url): ?Response
{
if (! $url) {
return null;
}
try {
$response = Http::timeout(10)->get($url);
} catch (\Throwable) {
return null;
}
return $response->successful() && str_starts_with((string) $response->header('Content-Type'), 'image/')
? $response
: null;
}
}