httpClient = new Client(); } protected function getValidChapterUrl(ContentSource $contentSource, Manga $manga, float $chapterNumber): ?string { $slugs = array_merge([$manga->getSlug()], $manga->getAlternativeSlugs() ?? []); foreach ($slugs as $slug) { $url = $contentSource->getChapterUrl($slug, $chapterNumber); if ($this->isChapterUrlValid($url)) { return $url; } } return null; } protected function isChapterUrlValid(string $url): bool { try { $response = $this->httpClient->head($url); return 200 === $response->getStatusCode(); } catch (RequestException $e) { return false; } } protected function generateCbzPath(Manga $manga, Chapter $chapter): string { $mangaDir = $this->fileSystemManager->createMangaDirectory($manga->getSlug(), $manga->getPublicationYear()); $volumeDir = $this->fileSystemManager->createVolumeDirectory($mangaDir, $chapter->getVolume()); $fileName = sprintf( '%s_vol%d_ch%s.cbz', $manga->getSlug(), $chapter->getVolume(), $chapter->getNumber() ); return $volumeDir.'/'.$fileName; } protected function createCbzFile(array $pageData, string $cbzFilePath): void { $zip = new \ZipArchive(); if (true === $zip->open($cbzFilePath, \ZipArchive::CREATE)) { foreach ($pageData as $page) { $zip->addFile($page['local_image_url'], basename($page['local_image_url'])); } $zip->close(); } } protected function cleanupTempFiles(string $directory): void { $this->fileSystemManager->deleteDirectory($directory); } protected function cleanImageUrl(string $url): string { return preg_replace('/[\x00-\x1F\x7F]/', '', trim($url)); } protected function dispatchProgressEvent(Chapter $chapter, int $currentPage, int $totalPages): void { $event = new PageScrappingProgressEvent($chapter->getId(), $currentPage, $totalPages); $this->eventDispatcher->dispatch($event, PageScrappingProgressEvent::NAME); } /** * @throws GuzzleException * @throws \Exception */ protected function downloadAndSaveImage(string $imageUrl, string $destinationPath): string { try { $response = $this->httpClient->get($imageUrl); $contentType = $response->getHeaderLine('Content-Type'); if (!str_starts_with($contentType, 'image/')) { throw new \Exception('Le contenu récupéré n\'est pas une image. Type de contenu : '.$contentType); } $imageData = $response->getBody()->getContents(); $tempFilePath = $this->saveTempFile($imageData); $image = $this->createImageResource($tempFilePath, $contentType); if (false === $image) { throw new \Exception('Échec de la création de la ressource image.'); } $destinationPath = $this->ensureJpgExtension($destinationPath); if (!imagejpeg($image, $destinationPath)) { imagedestroy($image); unlink($tempFilePath); throw new \Exception('Échec de la sauvegarde de l\'image en JPG.'); } imagedestroy($image); unlink($tempFilePath); return $destinationPath; } catch (\Exception $e) { throw new \Exception('Erreur lors de la récupération de l\'image : '.$e->getMessage()); } } private function saveTempFile(string $data): string { $tempFilePath = tempnam(sys_get_temp_dir(), 'manga_img_'); file_put_contents($tempFilePath, $data); return $tempFilePath; } /** * @throws \Exception */ private function createImageResource(string $filePath, string $contentType) { return match ($contentType) { 'image/webp' => imagecreatefromwebp($filePath), 'image/png' => imagecreatefrompng($filePath), 'image/jpeg', 'image/jpg' => imagecreatefromjpeg($filePath), default => throw new \Exception('Format d\'image non pris en charge : '.$contentType), }; } private function ensureJpgExtension(string $path): string { $info = pathinfo($path); return $info['dirname'].'/'.$info['filename'].'.jpg'; } }