- Portage des fonctionnalités de la branche main
- Ajout de node et npm dans la Dockerfile - Ajout des Factories et Fixtures - Ajout de npm-install dans Make install
This commit is contained in:
56
src/Service/LelScansProviderService.php
Normal file
56
src/Service/LelScansProviderService.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
namespace App\Service;
|
||||
|
||||
use Symfony\Component\BrowserKit\HttpBrowser as Client;
|
||||
use Symfony\Component\DomCrawler\Crawler;
|
||||
|
||||
class LelScansProviderService implements MangaProviderInterface
|
||||
{
|
||||
const PROVIDER_URL = 'https://lelscans.net/';
|
||||
const MANGA_SLUG = '/{manga}/{chapter}/{page}';
|
||||
|
||||
private Client $client;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->client = new Client();
|
||||
}
|
||||
|
||||
public function getMangaList(): array
|
||||
{
|
||||
$crawler = $this->client->request('GET', self::PROVIDER_URL);
|
||||
$mangaList = [];
|
||||
|
||||
$crawler->filter('select > option')->each(function (Crawler $node) use (&$mangaList) {
|
||||
$mangaName = $node->text();
|
||||
$mangaUrl = $node->attr('value');
|
||||
if ($mangaName && $mangaUrl && !preg_match('/^\d+(\.\d+)?$/', $mangaName)) {
|
||||
$mangaList[] = [
|
||||
'name' => $mangaName,
|
||||
'url' => $mangaUrl,
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
return $mangaList;
|
||||
}
|
||||
|
||||
public function getChapterList($mangaSlug): array
|
||||
{
|
||||
$crawler = $this->client->request('GET', self::PROVIDER_URL . 'lecture-en-ligne-' . $mangaSlug . '.php');
|
||||
$chapterList = [];
|
||||
|
||||
$crawler->filter('select > option')->each(function (Crawler $node) use (&$chapterList) {
|
||||
$chapterName = $node->text();
|
||||
$chapterUrl = $node->attr('value');
|
||||
if ($chapterName && $chapterUrl && preg_match('/^\d+(\.\d+)?$/', $chapterName)) {
|
||||
$chapterList[] = [
|
||||
'number' => $chapterName,
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
return $chapterList;
|
||||
}
|
||||
|
||||
}
|
||||
100
src/Service/MangaExportService.php
Normal file
100
src/Service/MangaExportService.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
|
||||
use ZipArchive;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
class MangaExportService
|
||||
{
|
||||
const IMG_BASE_DIR = '/public/manga-images';
|
||||
const EXPORT_BASE_DIR = '/public/manga-export';
|
||||
private string $projectDir;
|
||||
|
||||
public function __construct($projectDir)
|
||||
{
|
||||
$this->projectDir = $projectDir;
|
||||
}
|
||||
|
||||
public function exportMangaChapter(string $mangaTitle, int $chapterNumber): bool|string
|
||||
{
|
||||
$chapterDir = $this->getMangaDir($mangaTitle, $chapterNumber);
|
||||
$cbzFilePath = $this->getExportDir($mangaTitle, $chapterNumber);
|
||||
|
||||
if(!is_dir($chapterDir)){
|
||||
return false;
|
||||
}
|
||||
|
||||
$cbzDirectory = dirname($cbzFilePath);
|
||||
if (!is_dir($cbzDirectory)) {
|
||||
mkdir($cbzDirectory, 0755, true);
|
||||
}
|
||||
|
||||
$fileSystem = new Filesystem();
|
||||
if($fileSystem->exists($cbzFilePath)){
|
||||
return 'already_exported';
|
||||
}
|
||||
|
||||
return $this->createCbzFromDirectory($chapterDir, $cbzFilePath);
|
||||
}
|
||||
|
||||
public function downloadCbz(string $mangaTitle, int $chapterNumber): BinaryFileResponse|bool
|
||||
{
|
||||
$filePathCbz = $this->getExportDir($mangaTitle, $chapterNumber);
|
||||
|
||||
$fileSystem = new Filesystem();
|
||||
if($fileSystem->exists($filePathCbz)){
|
||||
return new BinaryFileResponse($filePathCbz);
|
||||
}
|
||||
|
||||
$chapterDir = $this->getMangaDir($mangaTitle, $chapterNumber);
|
||||
if(is_dir($chapterDir)){
|
||||
if($this->exportMangaChapter($mangaTitle, $chapterNumber)){
|
||||
return new BinaryFileResponse($filePathCbz);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function createCbzFromDirectory(string $sourceDirectory, string $cbzFilePath): bool
|
||||
{
|
||||
$zip = new ZipArchive();
|
||||
|
||||
// Ouvre le fichier .cbz en écriture
|
||||
if ($zip->open($cbzFilePath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$files = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($sourceDirectory),
|
||||
RecursiveIteratorIterator::LEAVES_ONLY
|
||||
);
|
||||
|
||||
// Ajoute les fichiers d'image au fichier .cbz
|
||||
foreach ($files as $file) {
|
||||
if (!$file->isDir()) {
|
||||
$filePath = $file->getRealPath();
|
||||
$relativePath = substr($filePath, strlen($sourceDirectory) + 1);
|
||||
$zip->addFile($filePath, $relativePath);
|
||||
}
|
||||
}
|
||||
|
||||
$zip->close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function getMangaDir(string $mangaTitle, int $chapterNumber): string
|
||||
{
|
||||
return sprintf('%s/%s/%d', $this->projectDir . self::IMG_BASE_DIR, $mangaTitle, $chapterNumber);
|
||||
}
|
||||
|
||||
private function getExportDir(string $mangaTitle, int $chapterNumber): string
|
||||
{
|
||||
return sprintf('%s/%s/%d', $this->projectDir . self::EXPORT_BASE_DIR, $mangaTitle, $chapterNumber) . '.cbz';
|
||||
}
|
||||
}
|
||||
15
src/Service/MangaProviderFactory.php
Normal file
15
src/Service/MangaProviderFactory.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
class MangaProviderFactory
|
||||
{
|
||||
public static function create($providerName): MangaProviderInterface
|
||||
{
|
||||
return match ($providerName) {
|
||||
'LelScans' => new LelScansProviderService(),
|
||||
'AutreManga' => new AutreMangaProviderService(),
|
||||
default => throw new \Exception("Provider {$providerName} non supporté."),
|
||||
};
|
||||
}
|
||||
}
|
||||
9
src/Service/MangaProviderInterface.php
Normal file
9
src/Service/MangaProviderInterface.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
interface MangaProviderInterface
|
||||
{
|
||||
public function getMangaList(): array;
|
||||
public function getChapterList(string $mangaSlug): array;
|
||||
}
|
||||
145
src/Service/MangaScraperService.php
Normal file
145
src/Service/MangaScraperService.php
Normal file
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Event\MangaScrapedEvent;
|
||||
use GuzzleHttp\Client;
|
||||
use PHPUnit\Util\PHP\AbstractPhpProcess;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Component\DomCrawler\Crawler;
|
||||
use Symfony\Component\Routing\Matcher\UrlMatcher;
|
||||
use Symfony\Component\Routing\RequestContext;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
class MangaScraperService
|
||||
{
|
||||
const IMG_BASE_DIR = '/public/manga-images';
|
||||
private string $projectDir;
|
||||
private EventDispatcherInterface $eventDispatcher;
|
||||
|
||||
public function __construct($projectDir, EventDispatcherInterface $eventDispatcher)
|
||||
{
|
||||
$this->projectDir = $projectDir;
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
}
|
||||
|
||||
public function extractMangaPageData(string $html): array
|
||||
{
|
||||
$baseUrl = 'https://lelscans.net';
|
||||
|
||||
$crawler = new Crawler($html);
|
||||
$imgUrl = $crawler->filter('img')->attr('src');
|
||||
$nextLink = $crawler->filter('a[title="Suivant"]');
|
||||
|
||||
if (!preg_match('/^https?:\/\//', $imgUrl)) {
|
||||
$urlComponents = parse_url($baseUrl);
|
||||
$scheme = $urlComponents['scheme'];
|
||||
$host = $urlComponents['host'];
|
||||
|
||||
// Construit l'URL absolue de l'image
|
||||
$imgUrl = $scheme . '://' . $host . '/' . ltrim($imgUrl, '/');
|
||||
}
|
||||
|
||||
if($nextLink->count() > 0){
|
||||
$nextUrl = $nextLink->attr('href');
|
||||
}else{
|
||||
$nextUrl = null;
|
||||
}
|
||||
|
||||
return [
|
||||
'image_url' => $imgUrl,
|
||||
'next_page_url' => $nextUrl,
|
||||
];
|
||||
}
|
||||
|
||||
public function scrapeMangaChapter(string $chapterUrl, string $mangaTitle, float $chapterNumber): array|bool
|
||||
{
|
||||
if(!$this->isChapterAvailable($chapterUrl, $chapterNumber)){
|
||||
return false;
|
||||
}
|
||||
|
||||
$pageData = [];
|
||||
$currentPageUrl = $chapterUrl;
|
||||
|
||||
$mangaDir = sprintf('%s/%s', $this->projectDir . self::IMG_BASE_DIR, $mangaTitle);
|
||||
if (!is_dir($mangaDir)) {
|
||||
mkdir($mangaDir, 0755, true);
|
||||
}
|
||||
|
||||
// Créez le dossier du chapitre s'il n'existe pas
|
||||
$chapterDir = sprintf('%s/%s', $mangaDir, $chapterNumber);
|
||||
if (!is_dir($chapterDir)) {
|
||||
mkdir($chapterDir, 0755, true);
|
||||
}
|
||||
|
||||
do {
|
||||
$html = $this->fetchHtml($currentPageUrl);
|
||||
$page = $this->extractMangaPageData($html);
|
||||
$pageData[] = $page;
|
||||
$currentPageUrl = $page['next_page_url'];
|
||||
|
||||
// Construisez le nom de fichier de l'image
|
||||
$imageName = sprintf('%03d.jpg', count($pageData));
|
||||
|
||||
// Construisez le chemin du fichier de l'image
|
||||
$imagePath = sprintf('%s/%s', $chapterDir, $imageName);
|
||||
|
||||
// Téléchargez et enregistrez l'image
|
||||
$this->downloadAndSaveImage($page['image_url'], $imagePath);
|
||||
|
||||
// Modifiez les données de la page pour inclure l'URL de l'image stockée localement
|
||||
$pageData[count($pageData) - 1]['local_image_url'] = sprintf('/manga-images/%s/%s/%s', $mangaTitle, $chapterNumber, $imageName);
|
||||
$pageData[count($pageData) - 1]['page_number'] = count($pageData);
|
||||
|
||||
} while ($currentPageUrl);
|
||||
|
||||
$event = new MangaScrapedEvent($mangaTitle, $chapterNumber, $pageData);
|
||||
$this->eventDispatcher->dispatch($event, MangaScrapedEvent::NAME);
|
||||
|
||||
return $pageData;
|
||||
}
|
||||
|
||||
private function fetchHtml(string $url): string
|
||||
{
|
||||
$client = new Client();
|
||||
$response = $client->get($url);
|
||||
|
||||
return (string) $response->getBody();
|
||||
}
|
||||
|
||||
private function downloadAndSaveImage(string $imageUrl, string $destinationPath): void
|
||||
{
|
||||
$client = new Client();
|
||||
$response = $client->get($imageUrl);
|
||||
|
||||
file_put_contents($destinationPath, $response->getBody()->getContents());
|
||||
}
|
||||
|
||||
private function isChapterAvailable(string $chapterUrl, float $chapterNumber): bool
|
||||
{
|
||||
$html = $this->fetchHtml($chapterUrl);
|
||||
$crawler = new Crawler($html);
|
||||
$nextLink = $crawler->filter('a[title="Suivant"]');
|
||||
|
||||
if($nextLink->count() === 0){
|
||||
return false;
|
||||
}else{
|
||||
$nextUrl = $nextLink->attr('href');
|
||||
}
|
||||
|
||||
$routeCollection = new RouteCollection();
|
||||
$routeCollection->add('manga_chapter', new Route('/scan-{manga}/{chapter}/{page}'));
|
||||
$context = new RequestContext('/');
|
||||
$matcher = new UrlMatcher($routeCollection, $context);
|
||||
$path = parse_url($nextUrl, PHP_URL_PATH);
|
||||
$parameters = $matcher->match($path);
|
||||
|
||||
if((float) $parameters['chapter'] !== $chapterNumber){
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
34
src/Service/SushiScanProviderService.php
Normal file
34
src/Service/SushiScanProviderService.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use Goutte\Client;
|
||||
|
||||
class SushiScanProviderService implements MangaProviderInterface
|
||||
{
|
||||
const PROVIDER_URL = 'https://sushiscan.com/';
|
||||
const MANGA_SLUG = '/{manga}/{chapter}/{page}';
|
||||
private Client $client;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->client = new Client();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getMangaList(): array
|
||||
{
|
||||
// TODO: Implement getMangaList() method.
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $mangaSlug
|
||||
* @return array
|
||||
*/
|
||||
public function getChapterList(string $mangaSlug): array
|
||||
{
|
||||
// TODO: Implement getChapterList() method.
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user