Files
Mangarr/src/Service/Scraper/AbstractScraper.php
ext.jeremy.guillot@maxicoffee.domains c55cd62ec7 fix: phpcs-fixer
2025-02-05 21:32:04 +01:00

161 lines
5.1 KiB
PHP

<?php
namespace App\Service\Scraper;
use App\Entity\Chapter;
use App\Entity\ContentSource;
use App\Entity\Manga;
use App\Event\PageScrappingProgressEvent;
use App\Manager\FileSystemManager;
use Doctrine\ORM\EntityManagerInterface;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\RequestException;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
abstract class AbstractScraper implements ScraperInterface
{
protected Client $httpClient;
public function __construct(
protected FileSystemManager $fileSystemManager,
protected EventDispatcherInterface $eventDispatcher,
protected EntityManagerInterface $entityManager
) {
$this->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';
}
}