Added:
- toogle chapter visibility - delete chapter cbz - preferred ContentSource.php and modal - minor fixes
This commit is contained in:
@@ -9,6 +9,7 @@ use App\Manager\Toolbar\Factory\ToolbarFactory;
|
||||
use App\Message\DownloadChapter;
|
||||
use App\Message\RefreshMetadata;
|
||||
use App\Repository\ChapterRepository;
|
||||
use App\Repository\ContentSourceRepository;
|
||||
use App\Repository\MangaRepository;
|
||||
use App\Service\CbzService;
|
||||
use App\Service\MangadexProvider;
|
||||
@@ -37,16 +38,17 @@ class MangaController extends AbstractController
|
||||
private ImageManager $imageManager;
|
||||
|
||||
public function __construct(
|
||||
private readonly string $projectDir,
|
||||
private readonly MangaRepository $mangaRepository,
|
||||
private readonly ChapterRepository $chapterRepository,
|
||||
private readonly MessageBusInterface $bus,
|
||||
private readonly CbzService $cbzService,
|
||||
private readonly ToolbarFactory $toolbarFactory,
|
||||
private readonly MangadexProvider $mangadexProvider,
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly NotificationService $notificationService,
|
||||
private readonly SluggerInterface $slugger
|
||||
private readonly string $projectDir,
|
||||
private readonly MangaRepository $mangaRepository,
|
||||
private readonly ChapterRepository $chapterRepository,
|
||||
private readonly MessageBusInterface $bus,
|
||||
private readonly CbzService $cbzService,
|
||||
private readonly ToolbarFactory $toolbarFactory,
|
||||
private readonly MangadexProvider $mangadexProvider,
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly NotificationService $notificationService,
|
||||
private readonly SluggerInterface $slugger,
|
||||
private readonly ContentSourceRepository $contentSourceRepository
|
||||
)
|
||||
{
|
||||
$this->imageManager = new ImageManager(new Driver());
|
||||
@@ -81,11 +83,13 @@ class MangaController extends AbstractController
|
||||
}
|
||||
|
||||
$form = $this->createForm(MangaEditType::class, $manga);
|
||||
$contentSources = $this->contentSourceRepository->findAll();
|
||||
|
||||
return $this->render('manga/show_chapters.html.twig', [
|
||||
'manga' => $manga,
|
||||
'toolbar' => $this->toolbarFactory->createToolbar('chapter_list', ['mangaId' => $manga->getId(), 'isMonitored' => (int) $manga->isMonitored()])->getGroups(),
|
||||
'toolbar' => $this->toolbarFactory->createToolbar('chapter_list', ['mangaId' => $manga->getId(), 'isMonitored' => (int)$manga->isMonitored()])->getGroups(),
|
||||
'form' => $form->createView(),
|
||||
'contentSources' => $contentSources,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -94,6 +98,7 @@ class MangaController extends AbstractController
|
||||
{
|
||||
try {
|
||||
foreach ($manga->getChapters() as $chapter) {
|
||||
file_exists($chapter->getCbzPath()) ?? unlink($chapter->getCbzPath());
|
||||
$this->entityManager->remove($chapter);
|
||||
}
|
||||
$this->entityManager->remove($manga);
|
||||
@@ -125,6 +130,30 @@ class MangaController extends AbstractController
|
||||
return new JsonResponse(['errors' => $errors], 400);
|
||||
}
|
||||
|
||||
#[Route('/manga/{id}/preferred-sources', name: 'manga_preferred_sources', methods: ['POST'])]
|
||||
public function updatePreferredSources(
|
||||
Request $request,
|
||||
Manga $manga,
|
||||
ContentSourceRepository $contentSourceRepository
|
||||
): JsonResponse
|
||||
{
|
||||
$data = json_decode($request->getContent(), true);
|
||||
$preferredSourceIds = $data['preferredSources'] ?? [];
|
||||
|
||||
$preferredSources = $contentSourceRepository->findBy(['id' => $preferredSourceIds]);
|
||||
|
||||
// This will maintain the order of the sources as they were sent in the request
|
||||
$orderedPreferredSources = array_map(
|
||||
fn($id) => current(array_filter($preferredSources, fn($s) => $s->getId() == $id)),
|
||||
$preferredSourceIds
|
||||
);
|
||||
|
||||
$manga->setPreferredSources(array_filter($orderedPreferredSources));
|
||||
$this->entityManager->flush();
|
||||
|
||||
return new JsonResponse(['success' => true]);
|
||||
}
|
||||
|
||||
public function _chaptersByManga(int $id): Response
|
||||
{
|
||||
$manga = $this->mangaRepository->find($id);
|
||||
@@ -157,6 +186,33 @@ class MangaController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/delete_cbz/{id}', name: 'app_delete_cbz')]
|
||||
public function deleteChapterCbz(Chapter $chapter): JsonResponse
|
||||
{
|
||||
$cbzPath = $chapter->getCbzPath();
|
||||
if (!$cbzPath) {
|
||||
return new JsonResponse(['error' => 'No CBZ path for this chapter.'], 400);
|
||||
}
|
||||
|
||||
file_exists($cbzPath) ?? unlink($cbzPath);
|
||||
|
||||
$chapter->setCbzPath(null);
|
||||
$this->entityManager->persist($chapter);
|
||||
$this->entityManager->flush();
|
||||
|
||||
return new JsonResponse(['success' => 'CBZ file deleted.'], 200);
|
||||
}
|
||||
|
||||
#[Route('/hide_chapter/{id}', name: 'app_hide_chapter')]
|
||||
public function hideChapter(Chapter $chapter): JsonResponse
|
||||
{
|
||||
$chapter->setVisible(false);
|
||||
$this->entityManager->persist($chapter);
|
||||
$this->entityManager->flush();
|
||||
|
||||
return new JsonResponse(['success' => 'Chapter hidden.'], 200);
|
||||
}
|
||||
|
||||
#[Route('/manga/read/{mangaSlug}/{chapterNumber}/{pageNumber}', name: 'app_manga_read')]
|
||||
public function readChapterPage(string $mangaSlug, float $chapterNumber, int $pageNumber = 1): Response
|
||||
{
|
||||
@@ -171,17 +227,7 @@ class MangaController extends AbstractController
|
||||
}
|
||||
|
||||
if (is_null($chapter->getCbzPath())) {
|
||||
$currentPage = $chapter->getPageByNumber($pageNumber);
|
||||
if (!$currentPage) {
|
||||
throw $this->createNotFoundException("La page demandée n'existe pas.");
|
||||
}
|
||||
|
||||
return $this->render('manga/manga_reader.html.twig', [
|
||||
'manga' => $manga,
|
||||
'chapter' => $chapter,
|
||||
'pages' => $chapter->getPagesLink(),
|
||||
'currentPage' => $currentPage,
|
||||
]);
|
||||
throw $this->createNotFoundException("Le chapitre demandé n'a pas été scrapé.");
|
||||
}
|
||||
|
||||
$pageContent = $this->cbzService->getPageContent($chapter->getCbzPath(), $pageNumber);
|
||||
@@ -226,7 +272,9 @@ class MangaController extends AbstractController
|
||||
->setAuthor($request->request->get('author'))
|
||||
->setPublicationYear($request->request->get('publicationYear'))
|
||||
->setRating($request->request->get('rating'))
|
||||
->setExternalId($request->request->get('externalId'));
|
||||
->setExternalId($request->request->get('externalId'))
|
||||
->setMonitored(false)
|
||||
;
|
||||
|
||||
// Traitement de l'image
|
||||
$imageUrl = $request->request->get('imageUrl');
|
||||
@@ -327,7 +375,8 @@ class MangaController extends AbstractController
|
||||
|
||||
$volumeChapters = $this->chapterRepository->findBy([
|
||||
'manga' => $manga,
|
||||
'volume' => $volume
|
||||
'volume' => $volume,
|
||||
'visible' => true
|
||||
]);
|
||||
|
||||
if (empty($volumeChapters)) {
|
||||
@@ -374,7 +423,8 @@ class MangaController extends AbstractController
|
||||
|
||||
$volumeChapters = $this->chapterRepository->findBy([
|
||||
'manga' => $manga,
|
||||
'volume' => $volume
|
||||
'volume' => $volume,
|
||||
'visible' => true
|
||||
]);
|
||||
|
||||
if (empty($volumeChapters)) {
|
||||
|
||||
@@ -45,6 +45,9 @@ class Chapter
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $cbzPath = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?bool $visible = true;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->pagesLink = new ArrayCollection();
|
||||
@@ -194,4 +197,16 @@ class Chapter
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isVisible(): ?bool
|
||||
{
|
||||
return $this->visible;
|
||||
}
|
||||
|
||||
public function setVisible(bool $visible): static
|
||||
{
|
||||
$this->visible = $visible;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,4 +118,13 @@ class ContentSource
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCleanBaseUrl(): string
|
||||
{
|
||||
return preg_replace(
|
||||
'/^(https?:\/\/)?(www\.)?|\/+$/',
|
||||
'',
|
||||
$this->baseUrl
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,10 +62,14 @@ class Manga
|
||||
#[ORM\Column(type: Types::JSON, nullable: true)]
|
||||
private ?array $AlternativeSlugs = null;
|
||||
|
||||
#[ORM\ManyToMany(targetEntity: ContentSource::class)]
|
||||
private Collection $preferredSources;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->chapters = new ArrayCollection();
|
||||
$this->createdAt = new \DateTimeImmutable();
|
||||
$this->preferredSources = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
@@ -280,4 +284,38 @@ class Manga
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, ContentSource>
|
||||
*/
|
||||
public function getPreferredSources(): Collection
|
||||
{
|
||||
return $this->preferredSources;
|
||||
}
|
||||
|
||||
public function addPreferredSource(ContentSource $preferredSource): static
|
||||
{
|
||||
if (!$this->preferredSources->contains($preferredSource)) {
|
||||
$this->preferredSources->add($preferredSource);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removePreferredSource(ContentSource $preferredSource): static
|
||||
{
|
||||
$this->preferredSources->removeElement($preferredSource);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setPreferredSources(array $sources): self
|
||||
{
|
||||
$this->preferredSources->clear();
|
||||
foreach ($sources as $source) {
|
||||
$this->addPreferredSource($source);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ class ChapterListToolbar extends Toolbar
|
||||
->addToLeftGroup(new ToolbarDivider())
|
||||
->addToLeftGroup(new ToolbarButton('keyboard', 'Rename chapters', 'toolbar#renameChapters'))
|
||||
->addToLeftGroup(new ToolbarButton('file-zipper', 'Manage cbz', 'toolbar#manageCbz', $contextData))
|
||||
->addToLeftGroup(new ToolbarButton('history', 'History', 'toolbar#history', $contextData))
|
||||
->addToLeftGroup(new ToolbarButton('gear', 'Preferred Sources', 'toolbar#editPreferredSources', $contextData))
|
||||
|
||||
|
||||
->addToRightGroup(new ToolbarButton('bookmark', $monitoredTitle, 'toolbar#monitoring', array_merge($contextData, ['buttonClass' => $monitoredColor])))
|
||||
|
||||
@@ -6,8 +6,8 @@ use App\Entity\ContentSource;
|
||||
use App\Message\DownloadChapter;
|
||||
use App\Repository\ChapterRepository;
|
||||
use App\Repository\ContentSourceRepository;
|
||||
use App\Service\MangaScraperService;
|
||||
use App\Service\NotificationService;
|
||||
use App\Service\Scraper\MangaScraperService;
|
||||
use Exception;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
@@ -40,7 +40,16 @@ readonly class DownloadChapterHandler
|
||||
throw new BadRequestHttpException('Chapter already downloaded');
|
||||
}
|
||||
|
||||
$sources = $this->contentSourceRepository->findAll();
|
||||
$manga = $chapter->getManga();
|
||||
$preferredSources = $manga->getPreferredSources()->toArray();
|
||||
$allSources = $this->contentSourceRepository->findAll();
|
||||
|
||||
$filteredSources = array_udiff($allSources, $preferredSources, function ($a, $b) {
|
||||
return $a->getId() - $b->getId();
|
||||
});
|
||||
|
||||
$sources = array_merge($preferredSources, $filteredSources);
|
||||
|
||||
$sources[] =
|
||||
(new ContentSource())
|
||||
->setBaseUrl('https://api.mangadex.org/')
|
||||
|
||||
@@ -227,7 +227,6 @@ readonly class MangadexProvider implements MetadataProviderInterface
|
||||
$mergedChapters = [];
|
||||
foreach ($allChapters as $chapter) {
|
||||
$number = $chapter->getNumber();
|
||||
|
||||
$existingChapter = $manga->getChapterByNumber($number);
|
||||
if ($existingChapter) {
|
||||
if ($existingChapter->getExternalId() !== $chapter->getExternalId() && is_null($existingChapter->getExternalId())) {
|
||||
|
||||
@@ -37,6 +37,7 @@ class JavascriptScraper extends AbstractScraper
|
||||
$imagePath = $tempDir . '/' . $imageName;
|
||||
|
||||
file_put_contents($imagePath, file_get_contents($page['image_url']));
|
||||
$this->dispatchProgressEvent($chapter, $index + 1, count($pageData));
|
||||
|
||||
$page['local_image_url'] = $imagePath;
|
||||
}
|
||||
@@ -95,7 +96,7 @@ class JavascriptScraper extends AbstractScraper
|
||||
{
|
||||
$chapterSelector = $contentSource->getChapterSelector();
|
||||
if (!$chapterSelector) {
|
||||
return; // Si aucun sélecteur n'est défini, on ne fait rien
|
||||
return;
|
||||
}
|
||||
|
||||
$crawler = $pantherClient->waitFor($chapterSelector);
|
||||
|
||||
Reference in New Issue
Block a user