Added:
- Updated Reader - fix image download for JavascriptScraper.php
This commit is contained in:
127
assets/controllers/reader_controller.js
Normal file
127
assets/controllers/reader_controller.js
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
import { Controller } from '@hotwired/stimulus';
|
||||||
|
|
||||||
|
export default class extends Controller {
|
||||||
|
static targets = ['pageContainer', 'currentPage', 'chapterSelect', 'readingModeButton']
|
||||||
|
static values = {
|
||||||
|
mangaSlug: String,
|
||||||
|
chapterNumber: Number,
|
||||||
|
totalPages: Number,
|
||||||
|
currentPage: { type: Number, default: 1 },
|
||||||
|
readingMode: { type: String, default: 'horizontal' }
|
||||||
|
}
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
this.loadChapters();
|
||||||
|
this.loadPages();
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadChapters() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/chapters/${this.mangaSlugValue}`);
|
||||||
|
const chapters = await response.json();
|
||||||
|
|
||||||
|
this.chapterSelectTarget.innerHTML = chapters.map(chapter =>
|
||||||
|
`<option value="${chapter.number}" ${chapter.number === this.chapterNumberValue ? 'selected' : ''}>
|
||||||
|
Chapitre ${chapter.number}
|
||||||
|
</option>`
|
||||||
|
).join('');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading chapters:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadPages() {
|
||||||
|
this.pageContainerTarget.innerHTML = '';
|
||||||
|
if (this.readingModeValue === 'horizontal') {
|
||||||
|
await this.loadPage(this.currentPageValue);
|
||||||
|
} else {
|
||||||
|
for (let i = 1; i <= this.totalPagesValue; i++) {
|
||||||
|
await this.loadPage(i, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadPage(pageNumber, isVertical = false) {
|
||||||
|
const response = await fetch(`/api/read/${this.mangaSlugValue}/${this.chapterNumberValue}/${pageNumber}`);
|
||||||
|
const pageContent = await response.text();
|
||||||
|
|
||||||
|
const img = document.createElement('img');
|
||||||
|
img.src = `data:image/jpeg;base64,${pageContent}`;
|
||||||
|
img.alt = `Page ${pageNumber}`;
|
||||||
|
img.classList.add('shadow-lg', 'w-full', 'h-auto');
|
||||||
|
|
||||||
|
if (this.readingModeValue === 'horizontal') {
|
||||||
|
img.classList.add('cursor-pointer');
|
||||||
|
img.dataset.action = 'click->reader#pageClick';
|
||||||
|
this.pageContainerTarget.innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isVertical) {
|
||||||
|
img.loading = 'lazy';
|
||||||
|
img.classList.add('mb-4');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pageContainerTarget.appendChild(img);
|
||||||
|
|
||||||
|
if (!isVertical) {
|
||||||
|
this.currentPageTarget.textContent = pageNumber;
|
||||||
|
this.currentPageValue = pageNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pageClick(event) {
|
||||||
|
if (this.readingModeValue === 'horizontal') {
|
||||||
|
const pageWidth = event.target.offsetWidth;
|
||||||
|
const clickX = event.offsetX;
|
||||||
|
|
||||||
|
if (clickX < pageWidth / 2) {
|
||||||
|
this.previousPage();
|
||||||
|
} else {
|
||||||
|
this.nextPage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
previousPage() {
|
||||||
|
if (this.currentPageValue > 1) {
|
||||||
|
this.loadPage(this.currentPageValue - 1);
|
||||||
|
} else {
|
||||||
|
this.previousChapter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nextPage() {
|
||||||
|
if (this.currentPageValue < this.totalPagesValue) {
|
||||||
|
this.loadPage(this.currentPageValue + 1);
|
||||||
|
} else {
|
||||||
|
this.nextChapter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async previousChapter() {
|
||||||
|
const response = await fetch(`/api/previous-chapter/${this.mangaSlugValue}/${this.chapterNumberValue}`);
|
||||||
|
const previousChapter = await response.json();
|
||||||
|
if (previousChapter) {
|
||||||
|
window.location.href = `/read/${this.mangaSlugValue}/${previousChapter.number}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async nextChapter() {
|
||||||
|
const response = await fetch(`/api/next-chapter/${this.mangaSlugValue}/${this.chapterNumberValue}`);
|
||||||
|
const nextChapter = await response.json();
|
||||||
|
if (nextChapter) {
|
||||||
|
window.location.href = `/read/${this.mangaSlugValue}/${nextChapter.number}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
changeChapter(event) {
|
||||||
|
const selectedChapterNumber = event.target.value;
|
||||||
|
window.location.href = `/read/${this.mangaSlugValue}/${selectedChapterNumber}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleReadingMode() {
|
||||||
|
this.readingModeValue = this.readingModeValue === 'horizontal' ? 'vertical' : 'horizontal';
|
||||||
|
this.readingModeButtonTarget.textContent = this.readingModeValue === 'horizontal' ? 'Passer en mode vertical' : 'Passer en mode horizontal';
|
||||||
|
this.loadPages();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -213,38 +213,38 @@ class MangaController extends AbstractController
|
|||||||
return new JsonResponse(['success' => 'Chapter hidden.'], 200);
|
return new JsonResponse(['success' => 'Chapter hidden.'], 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/manga/read/{mangaSlug}/{chapterNumber}/{pageNumber}', name: 'app_manga_read')]
|
// #[Route('/manga/read/{mangaSlug}/{chapterNumber}/{pageNumber}', name: 'app_manga_read')]
|
||||||
public function readChapterPage(string $mangaSlug, float $chapterNumber, int $pageNumber = 1): Response
|
// public function readChapterPage(string $mangaSlug, float $chapterNumber, int $pageNumber = 1): Response
|
||||||
{
|
// {
|
||||||
$manga = $this->mangaRepository->findOneBy(['slug' => $mangaSlug]);
|
// $manga = $this->mangaRepository->findOneBy(['slug' => $mangaSlug]);
|
||||||
if (!$manga) {
|
// if (!$manga) {
|
||||||
throw $this->createNotFoundException("Le manga demandé n'existe pas.");
|
// throw $this->createNotFoundException("Le manga demandé n'existe pas.");
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
$chapter = $manga->getChapterByNumber($chapterNumber);
|
// $chapter = $manga->getChapterByNumber($chapterNumber);
|
||||||
if (!$chapter) {
|
// if (!$chapter) {
|
||||||
throw $this->createNotFoundException("Le chapitre demandé n'existe pas.");
|
// throw $this->createNotFoundException("Le chapitre demandé n'existe pas.");
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
if (is_null($chapter->getCbzPath())) {
|
// if (is_null($chapter->getCbzPath())) {
|
||||||
throw $this->createNotFoundException("Le chapitre demandé n'a pas été scrapé.");
|
// throw $this->createNotFoundException("Le chapitre demandé n'a pas été scrapé.");
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
$pageContent = $this->cbzService->getPageContent($chapter->getCbzPath(), $pageNumber);
|
// $pageContent = $this->cbzService->getPageContent($chapter->getCbzPath(), $pageNumber);
|
||||||
if (!$pageContent) {
|
// if (!$pageContent) {
|
||||||
throw $this->createNotFoundException("La page demandée n'existe pas.");
|
// throw $this->createNotFoundException("La page demandée n'existe pas.");
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
$totalPages = $this->cbzService->getPageCount($chapter->getCbzPath());
|
// $totalPages = $this->cbzService->getPageCount($chapter->getCbzPath());
|
||||||
|
//
|
||||||
return $this->render('manga/manga_reader.html.twig', [
|
// return $this->render('manga/manga_reader.html.twig', [
|
||||||
'manga' => $manga,
|
// 'manga' => $manga,
|
||||||
'chapter' => $chapter,
|
// 'chapter' => $chapter,
|
||||||
'currentPage' => $pageNumber,
|
// 'currentPage' => $pageNumber,
|
||||||
'totalPages' => $totalPages,
|
// 'totalPages' => $totalPages,
|
||||||
'pageContent' => base64_encode($pageContent),
|
// 'pageContent' => base64_encode($pageContent),
|
||||||
]);
|
// ]);
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[Route('/manga/search/{query}', name: 'app_manga_search')]
|
#[Route('/manga/search/{query}', name: 'app_manga_search')]
|
||||||
public function search(string $query = ''): Response
|
public function search(string $query = ''): Response
|
||||||
|
|||||||
130
src/Controller/ReaderController.php
Normal file
130
src/Controller/ReaderController.php
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Repository\MangaRepository;
|
||||||
|
use App\Service\CbzService;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
|
||||||
|
class ReaderController extends AbstractController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly MangaRepository $mangaRepository,
|
||||||
|
private readonly CbzService $cbzService
|
||||||
|
)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/read/{mangaSlug}/{chapterNumber}', name: 'app_reader')]
|
||||||
|
public function read(string $mangaSlug, float $chapterNumber): Response
|
||||||
|
{
|
||||||
|
$manga = $this->mangaRepository->findOneBy(['slug' => $mangaSlug]);
|
||||||
|
if (!$manga) {
|
||||||
|
throw $this->createNotFoundException("Le manga demandé n'existe pas.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$chapter = $manga->getChapterByNumber($chapterNumber);
|
||||||
|
if (!$chapter) {
|
||||||
|
throw $this->createNotFoundException("Le chapitre demandé n'existe pas.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_null($chapter->getCbzPath())) {
|
||||||
|
throw $this->createNotFoundException("Le chapitre demandé n'a pas été scrapé.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$totalPages = $this->cbzService->getPageCount($chapter->getCbzPath());
|
||||||
|
|
||||||
|
return $this->render('reader/index.html.twig', [
|
||||||
|
'manga' => $manga,
|
||||||
|
'chapter' => $chapter,
|
||||||
|
'totalPages' => $totalPages,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/api/read/{mangaSlug}/{chapterNumber}/{pageNumber}', name: 'app_reader_page')]
|
||||||
|
public function getPage(string $mangaSlug, float $chapterNumber, int $pageNumber): Response
|
||||||
|
{
|
||||||
|
$manga = $this->mangaRepository->findOneBy(['slug' => $mangaSlug]);
|
||||||
|
if (!$manga) {
|
||||||
|
throw $this->createNotFoundException("Le manga demandé n'existe pas.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$chapter = $manga->getChapterByNumber($chapterNumber);
|
||||||
|
if (!$chapter) {
|
||||||
|
throw $this->createNotFoundException("Le chapitre demandé n'existe pas.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$pageContent = $this->cbzService->getPageContent($chapter->getCbzPath(), $pageNumber);
|
||||||
|
if (!$pageContent) {
|
||||||
|
throw $this->createNotFoundException("La page demandée n'existe pas.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(base64_encode($pageContent), 200, ['Content-Type' => 'text/plain']);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/api/chapters/{mangaSlug}', name: 'app_reader_chapters')]
|
||||||
|
public function getChapters(string $mangaSlug): JsonResponse
|
||||||
|
{
|
||||||
|
$manga = $this->mangaRepository->findOneBy(['slug' => $mangaSlug]);
|
||||||
|
if (!$manga) {
|
||||||
|
throw $this->createNotFoundException("Le manga demandé n'existe pas.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$chapters = $manga->getChapters()
|
||||||
|
->filter(fn($chapter) => $chapter->isVisible() && !is_null($chapter->getCbzPath()))
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
usort($chapters, fn($a, $b) => $b->getNumber() <=> $a->getNumber());
|
||||||
|
|
||||||
|
$chapters = array_values(array_map(fn($chapter) => [
|
||||||
|
'number' => $chapter->getNumber(),
|
||||||
|
'title' => $chapter->getTitle()
|
||||||
|
], $chapters));
|
||||||
|
|
||||||
|
|
||||||
|
return $this->json($chapters);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/api/previous-chapter/{mangaSlug}/{currentChapterNumber}', name: 'app_reader_previous_chapter')]
|
||||||
|
public function getPreviousChapter(string $mangaSlug, float $currentChapterNumber): JsonResponse
|
||||||
|
{
|
||||||
|
$manga = $this->mangaRepository->findOneBy(['slug' => $mangaSlug]);
|
||||||
|
if (!$manga) {
|
||||||
|
throw $this->createNotFoundException("Le manga demandé n'existe pas.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$previousChapter = $manga->getChapters()
|
||||||
|
->filter(fn($chapter) => $chapter->isVisible() && $chapter->getNumber() < $currentChapterNumber)
|
||||||
|
->last();
|
||||||
|
|
||||||
|
return $this->json($previousChapter ? [
|
||||||
|
'number' => $previousChapter->getNumber(),
|
||||||
|
'title' => $previousChapter->getTitle()
|
||||||
|
] : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/api/next-chapter/{mangaSlug}/{currentChapterNumber}', name: 'app_reader_next_chapter')]
|
||||||
|
public function getNextChapter(string $mangaSlug, float $currentChapterNumber): JsonResponse
|
||||||
|
{
|
||||||
|
$manga = $this->mangaRepository->findOneBy(['slug' => $mangaSlug]);
|
||||||
|
if (!$manga) {
|
||||||
|
throw $this->createNotFoundException("Le manga demandé n'existe pas.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$nextChapter = $manga->getChapters()
|
||||||
|
->filter(fn($chapter) => $chapter->isVisible() && $chapter->getNumber() > $currentChapterNumber)
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
usort($nextChapter, fn($a, $b) => $a->getNumber() <=> $b->getNumber());
|
||||||
|
|
||||||
|
$nextChapter = reset($nextChapter) ?: null;
|
||||||
|
|
||||||
|
return $this->json($nextChapter ? [
|
||||||
|
'number' => $nextChapter->getNumber(),
|
||||||
|
'title' => $nextChapter->getTitle()
|
||||||
|
] : null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -57,6 +57,7 @@ readonly class DownloadChapterHandler
|
|||||||
->setChapterUrlFormat('at-home/server/%s')
|
->setChapterUrlFormat('at-home/server/%s')
|
||||||
->setScrapingType('mangadex');
|
->setScrapingType('mangadex');
|
||||||
|
|
||||||
|
|
||||||
// (new ContentSource())
|
// (new ContentSource())
|
||||||
// ->setBaseUrl('https://lelscans.net')
|
// ->setBaseUrl('https://lelscans.net')
|
||||||
// ->setImageSelector('#image img')
|
// ->setImageSelector('#image img')
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use App\Entity\ContentSource;
|
|||||||
use App\Entity\Manga;
|
use App\Entity\Manga;
|
||||||
use App\Event\PageScrappingProgressEvent;
|
use App\Event\PageScrappingProgressEvent;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Exception;
|
||||||
use GuzzleHttp\Client;
|
use GuzzleHttp\Client;
|
||||||
use GuzzleHttp\Exception\GuzzleException;
|
use GuzzleHttp\Exception\GuzzleException;
|
||||||
use GuzzleHttp\Exception\RequestException;
|
use GuzzleHttp\Exception\RequestException;
|
||||||
@@ -107,4 +108,24 @@ abstract class AbstractScraper implements ScraperInterface
|
|||||||
$event = new PageScrappingProgressEvent($chapter->getId(), $currentPage, $totalPages);
|
$event = new PageScrappingProgressEvent($chapter->getId(), $currentPage, $totalPages);
|
||||||
$this->eventDispatcher->dispatch($event, PageScrappingProgressEvent::NAME);
|
$this->eventDispatcher->dispatch($event, PageScrappingProgressEvent::NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws GuzzleException
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
protected function downloadAndSaveImage(string $imageUrl, string $destinationPath): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$response = $this->httpClient->get($imageUrl);
|
||||||
|
$contentType = $response->getHeaderLine('Content-Type');
|
||||||
|
|
||||||
|
if (str_starts_with($contentType, 'image/')) {
|
||||||
|
file_put_contents($destinationPath, $response->getBody()->getContents());
|
||||||
|
} else {
|
||||||
|
throw new Exception('Le contenu récupéré n\'est pas une image. Type de contenu : ' . $contentType);
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
throw new Exception('Erreur lors de la récupération de l\'image : ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use App\Entity\ContentSource;
|
|||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Exception;
|
use Exception;
|
||||||
use GuzzleHttp\Client;
|
use GuzzleHttp\Client;
|
||||||
|
use GuzzleHttp\Exception\GuzzleException;
|
||||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||||
use Symfony\Component\DomCrawler\Crawler;
|
use Symfony\Component\DomCrawler\Crawler;
|
||||||
|
|
||||||
@@ -25,6 +26,7 @@ class HtmlScraper extends AbstractScraper
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
|
* @throws GuzzleException
|
||||||
*/
|
*/
|
||||||
public function scrapeChapter(Chapter $chapter, ContentSource $contentSource): array|bool
|
public function scrapeChapter(Chapter $chapter, ContentSource $contentSource): array|bool
|
||||||
{
|
{
|
||||||
@@ -116,6 +118,9 @@ class HtmlScraper extends AbstractScraper
|
|||||||
return $pageData;
|
return $pageData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
private function scrapeHorizontalReader(string $chapterUrl, ContentSource $contentSource): array
|
private function scrapeHorizontalReader(string $chapterUrl, ContentSource $contentSource): array
|
||||||
{
|
{
|
||||||
$pageData = [];
|
$pageData = [];
|
||||||
@@ -156,22 +161,6 @@ class HtmlScraper extends AbstractScraper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function downloadAndSaveImage(string $imageUrl, string $destinationPath): void
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$response = $this->client->get($imageUrl);
|
|
||||||
$contentType = $response->getHeaderLine('Content-Type');
|
|
||||||
|
|
||||||
if (str_starts_with($contentType, 'image/')) {
|
|
||||||
file_put_contents($destinationPath, $response->getBody()->getContents());
|
|
||||||
} else {
|
|
||||||
throw new Exception('Le contenu récupéré n\'est pas une image. Type de contenu : ' . $contentType);
|
|
||||||
}
|
|
||||||
} catch (Exception $e) {
|
|
||||||
throw new Exception('Erreur lors de la récupération de l\'image : ' . $e->getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function extractMangaPageData(string $html, ContentSource $mangaSource): array
|
private function extractMangaPageData(string $html, ContentSource $mangaSource): array
|
||||||
{
|
{
|
||||||
$crawler = new Crawler($html);
|
$crawler = new Crawler($html);
|
||||||
|
|||||||
@@ -5,10 +5,14 @@ namespace App\Service\Scraper;
|
|||||||
use App\Entity\Chapter;
|
use App\Entity\Chapter;
|
||||||
use App\Entity\ContentSource;
|
use App\Entity\ContentSource;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
use GuzzleHttp\Exception\GuzzleException;
|
||||||
use Symfony\Component\Panther\Client as PantherClient;
|
use Symfony\Component\Panther\Client as PantherClient;
|
||||||
|
|
||||||
class JavascriptScraper extends AbstractScraper
|
class JavascriptScraper extends AbstractScraper
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
public function scrapeChapter(Chapter $chapter, ContentSource $contentSource): array|bool
|
public function scrapeChapter(Chapter $chapter, ContentSource $contentSource): array|bool
|
||||||
{
|
{
|
||||||
$manga = $chapter->getManga();
|
$manga = $chapter->getManga();
|
||||||
@@ -36,7 +40,7 @@ class JavascriptScraper extends AbstractScraper
|
|||||||
$imageName = sprintf('%03d.%s', $index + 1, pathinfo(parse_url($page['image_url'], PHP_URL_PATH), PATHINFO_EXTENSION));
|
$imageName = sprintf('%03d.%s', $index + 1, pathinfo(parse_url($page['image_url'], PHP_URL_PATH), PATHINFO_EXTENSION));
|
||||||
$imagePath = $tempDir . '/' . $imageName;
|
$imagePath = $tempDir . '/' . $imageName;
|
||||||
|
|
||||||
file_put_contents($imagePath, file_get_contents($page['image_url']));
|
$this->downloadAndSaveImage($page['image_url'], $imagePath);
|
||||||
$this->dispatchProgressEvent($chapter, $index + 1, count($pageData));
|
$this->dispatchProgressEvent($chapter, $index + 1, count($pageData));
|
||||||
|
|
||||||
$page['local_image_url'] = $imagePath;
|
$page['local_image_url'] = $imagePath;
|
||||||
@@ -52,9 +56,6 @@ class JavascriptScraper extends AbstractScraper
|
|||||||
$this->cleanupTempFiles($tempDir);
|
$this->cleanupTempFiles($tempDir);
|
||||||
|
|
||||||
return $pageData;
|
return $pageData;
|
||||||
} catch (Exception $e) {
|
|
||||||
// Log the error
|
|
||||||
return false;
|
|
||||||
} finally {
|
} finally {
|
||||||
$pantherClient->close();
|
$pantherClient->close();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,10 +80,4 @@ class MangadexScraper extends AbstractScraper
|
|||||||
{
|
{
|
||||||
return $scrapingType === 'mangadex';
|
return $scrapingType === 'mangadex';
|
||||||
}
|
}
|
||||||
|
|
||||||
private function downloadAndSaveImage(string $imageUrl, string $destinationPath): void
|
|
||||||
{
|
|
||||||
$response = $this->client->get($imageUrl);
|
|
||||||
file_put_contents($destinationPath, $response->getBody()->getContents());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,12 +67,12 @@
|
|||||||
{% if all_chapters_same_cbz and volume_cbz_path is not null %}
|
{% if all_chapters_same_cbz and volume_cbz_path is not null %}
|
||||||
<tr class="border-t hover:bg-green-100">
|
<tr class="border-t hover:bg-green-100">
|
||||||
<td class="px-4 py-2 text-green-500">
|
<td class="px-4 py-2 text-green-500">
|
||||||
<a data-turbo-frame="_top" href="{{ path('app_manga_read', { mangaSlug: manga.slug, chapterNumber: chapters|first.number, pageNumber: 1 }) }}">
|
<a data-turbo-frame="_top" href="{{ path('app_reader', { mangaSlug: manga.slug, chapterNumber: chapters|first.number, pageNumber: 1 }) }}">
|
||||||
{{ '%02d'|format(volume) }}
|
{{ '%02d'|format(volume) }}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-2 w-full text-left">
|
<td class="px-4 py-2 w-full text-left">
|
||||||
<a data-turbo-frame="_top" href="{{ path('app_manga_read', { mangaSlug: manga.slug, chapterNumber: chapters|first.number, pageNumber: 1 }) }}">
|
<a data-turbo-frame="_top" href="{{ path('app_reader', { mangaSlug: manga.slug, chapterNumber: chapters|first.number, pageNumber: 1 }) }}">
|
||||||
Volume {{ '%02d'|format(volume) }}
|
Volume {{ '%02d'|format(volume) }}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
{% if chapter.cbzPath is not null %}
|
{% if chapter.cbzPath is not null %}
|
||||||
<td class="px-4 py-2 text-green-500">
|
<td class="px-4 py-2 text-green-500">
|
||||||
<a data-turbo-frame="_top"
|
<a data-turbo-frame="_top"
|
||||||
href="{{ path('app_manga_read', { mangaSlug: manga.slug, chapterNumber: chapter.number, pageNumber: 1 }) }}">
|
href="{{ path('app_reader', { mangaSlug: manga.slug, chapterNumber: chapter.number, pageNumber: 1 }) }}">
|
||||||
{{ chapter.number < 10 ? '0' ~ chapter.number : chapter.number }}
|
{{ chapter.number < 10 ? '0' ~ chapter.number : chapter.number }}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
<td class="px-4 py-2 w-full text-left">
|
<td class="px-4 py-2 w-full text-left">
|
||||||
{% if chapter.cbzPath is not null %}
|
{% if chapter.cbzPath is not null %}
|
||||||
<a data-turbo-frame="_top"
|
<a data-turbo-frame="_top"
|
||||||
href="{{ path('app_manga_read', { mangaSlug: manga.slug, chapterNumber: chapter.number, pageNumber: 1 }) }}">
|
href="{{ path('app_reader', { mangaSlug: manga.slug, chapterNumber: chapter.number, pageNumber: 1 }) }}">
|
||||||
{{ chapter.title ?? 'No title' }}
|
{{ chapter.title ?? 'No title' }}
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|||||||
31
templates/reader/index.html.twig
Normal file
31
templates/reader/index.html.twig
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %}{{ manga.title }} - Chapitre {{ chapter.number }}{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<div class="w-full mx-auto p-4" {{ stimulus_controller('reader', {
|
||||||
|
mangaSlug: manga.slug,
|
||||||
|
chapterNumber: chapter.number,
|
||||||
|
totalPages: totalPages
|
||||||
|
}) }}>
|
||||||
|
<h1 class="text-center text-3xl my-4">{{ manga.title }} - Chapitre {{ chapter.number }}</h1>
|
||||||
|
|
||||||
|
<div class="flex justify-center my-4">
|
||||||
|
<button class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 mr-4" data-action="reader#previousChapter">« Chapitre précédent</button>
|
||||||
|
<select class="px-4 py-2 rounded" data-action="reader#changeChapter" data-reader-target="chapterSelect"></select>
|
||||||
|
<button class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 ml-4" data-action="reader#nextChapter">Chapitre suivant »</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-center my-4">
|
||||||
|
<button class="bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600 mr-4" data-action="reader#toggleReadingMode" data-reader-target="readingModeButton">Passer en mode vertical</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="page-container flex flex-col items-center min-h-[80vh]" data-reader-target="pageContainer">
|
||||||
|
<!-- Les pages seront injectées ici par le JavaScript -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center mt-4" data-reader-target="pageInfo">
|
||||||
|
Page <span data-reader-target="currentPage">1</span> sur {{ totalPages }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user