diff --git a/assets/app.js b/assets/app.js index c8451ef..2b5d790 100644 --- a/assets/app.js +++ b/assets/app.js @@ -10,5 +10,3 @@ import './styles/app.scss'; // start the Stimulus application import './bootstrap'; - -import 'tw-elements'; diff --git a/assets/styles/app.scss b/assets/styles/app.scss index a918848..9b49d48 100644 --- a/assets/styles/app.scss +++ b/assets/styles/app.scss @@ -3,5 +3,6 @@ @import "tailwindcss/utilities"; body { - background-color: lightgray; + background-color: white; } + diff --git a/migrations/Version20240604185153.php b/migrations/Version20240604185153.php new file mode 100644 index 0000000..75dce12 --- /dev/null +++ b/migrations/Version20240604185153.php @@ -0,0 +1,39 @@ +addSql('ALTER TABLE manga ADD image_url VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE manga ADD publication_year INT DEFAULT NULL'); + $this->addSql('ALTER TABLE manga ADD description TEXT DEFAULT NULL'); + $this->addSql('ALTER TABLE manga ADD genres TEXT DEFAULT NULL'); + $this->addSql('COMMENT ON COLUMN manga.genres IS \'(DC2Type:array)\''); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('ALTER TABLE manga DROP image_url'); + $this->addSql('ALTER TABLE manga DROP publication_year'); + $this->addSql('ALTER TABLE manga DROP description'); + $this->addSql('ALTER TABLE manga DROP genres'); + } +} diff --git a/package-lock.json b/package-lock.json index e09a42b..5457366 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,7 @@ "alpinejs": "^3.13.3", "autoprefixer": "^10.4.14", "postcss-loader": "^7.1.0", - "tailwindcss": "^3.2.7", - "tw-elements": "^1.0.0-beta1" + "tailwindcss": "^3.2.7" }, "devDependencies": { "@babel/core": "^7.17.0", @@ -2028,15 +2027,6 @@ "node": ">=14" } }, - "node_modules/@popperjs/core": { - "version": "2.11.8", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", - "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -3382,19 +3372,6 @@ "node": ">=0.8.0" } }, - "node_modules/chart.js": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.9.1.tgz", - "integrity": "sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w==" - }, - "node_modules/chartjs-plugin-datalabels": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/chartjs-plugin-datalabels/-/chartjs-plugin-datalabels-2.2.0.tgz", - "integrity": "sha512-14ZU30lH7n89oq+A4bWaJPnAG8a7ZTk7dKf48YAzMvJjQtjrgg5Dpk9f+LbjCF6bpx3RAGTeL13IXpKQYyRvlw==", - "peerDependencies": { - "chart.js": ">=3.0.0" - } - }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -4031,11 +4008,6 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, - "node_modules/custom-event-polyfill": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/custom-event-polyfill/-/custom-event-polyfill-1.0.7.tgz", - "integrity": "sha512-TDDkd5DkaZxZFM8p+1I3yAlvM3rSr1wbrOliG4yJiwinMZN8z/iGL7BTlDkrJcYTmgUSb4ywVCc3ZaUtOtC76w==" - }, "node_modules/daisyui": { "version": "4.11.1", "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.11.1.tgz", @@ -4072,14 +4044,6 @@ } } }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/default-gateway": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", @@ -4155,14 +4119,6 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/detect-autofill": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/detect-autofill/-/detect-autofill-1.1.4.tgz", - "integrity": "sha512-utCBQwCR/beSnADQmBC7C4tTueBBkYCl6WSpfGUkYKO/+MzPxqYGj6G4MvHzcKmH1gCTK+VunX2vaagvkRXPvA==", - "dependencies": { - "custom-event-polyfill": "^1.0.7" - } - }, "node_modules/detect-node": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", @@ -6433,11 +6389,6 @@ "node": ">=8" } }, - "node_modules/perfect-scrollbar": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/perfect-scrollbar/-/perfect-scrollbar-1.5.5.tgz", - "integrity": "sha512-dzalfutyP3e/FOpdlhVryN4AJ5XDVauVWxybSkLZmakFE2sS3y3pc4JnSprw8tGmHvkaG5Edr5T7LBTZ+WWU2g==" - }, "node_modules/picocolors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", @@ -7288,17 +7239,6 @@ } ] }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -8582,155 +8522,6 @@ "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" }, - "node_modules/tw-elements": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tw-elements/-/tw-elements-1.1.0.tgz", - "integrity": "sha512-IUr9YW2l99oTVZxuVjQg6rpuCpo6VZqbaKGYmZUNVVsoSVU/ljPpkJAY2Pn/morlXwKPhP9MTPNQMlWosqHL4w==", - "dependencies": { - "@popperjs/core": "^2.6.0", - "chart.js": "^3.7.1", - "chartjs-plugin-datalabels": "^2.0.0", - "deepmerge": "^4.2.2", - "detect-autofill": "^1.1.3", - "perfect-scrollbar": "^1.5.5", - "tailwindcss": "3.3.0" - } - }, - "node_modules/tw-elements/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/tw-elements/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/tw-elements/node_modules/lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", - "engines": { - "node": ">=10" - } - }, - "node_modules/tw-elements/node_modules/postcss-import": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz", - "integrity": "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==", - "dependencies": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "postcss": "^8.0.0" - } - }, - "node_modules/tw-elements/node_modules/postcss-load-config": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", - "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", - "dependencies": { - "lilconfig": "^2.0.5", - "yaml": "^1.10.2" - }, - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": ">=8.0.9", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "postcss": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/tw-elements/node_modules/postcss-nested": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.0.tgz", - "integrity": "sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==", - "dependencies": { - "postcss-selector-parser": "^6.0.10" - }, - "engines": { - "node": ">=12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.2.14" - } - }, - "node_modules/tw-elements/node_modules/tailwindcss": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.0.tgz", - "integrity": "sha512-hOXlFx+YcklJ8kXiCAfk/FMyr4Pm9ck477G0m/us2344Vuj355IpoEDB5UmGAsSpTBmr+4ZhjzW04JuFXkb/fw==", - "dependencies": { - "arg": "^5.0.2", - "chokidar": "^3.5.3", - "color-name": "^1.1.4", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.2.12", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "jiti": "^1.17.2", - "lilconfig": "^2.0.6", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "object-hash": "^3.0.0", - "picocolors": "^1.0.0", - "postcss": "^8.0.9", - "postcss-import": "^14.1.0", - "postcss-js": "^4.0.0", - "postcss-load-config": "^3.1.4", - "postcss-nested": "6.0.0", - "postcss-selector-parser": "^6.0.11", - "postcss-value-parser": "^4.2.0", - "quick-lru": "^5.1.1", - "resolve": "^1.22.1", - "sucrase": "^3.29.0" - }, - "bin": { - "tailwind": "lib/cli.js", - "tailwindcss": "lib/cli.js" - }, - "engines": { - "node": ">=12.13.0" - }, - "peerDependencies": { - "postcss": "^8.0.9" - } - }, - "node_modules/tw-elements/node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "engines": { - "node": ">= 6" - } - }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", diff --git a/package.json b/package.json index 5ca0ff9..807d8ea 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,6 @@ "alpinejs": "^3.13.3", "autoprefixer": "^10.4.14", "postcss-loader": "^7.1.0", - "tailwindcss": "^3.2.7", - "tw-elements": "^1.0.0-beta1" + "tailwindcss": "^3.2.7" } } diff --git a/src/Controller/MangaController.php b/src/Controller/MangaController.php index ac6228e..5205d39 100644 --- a/src/Controller/MangaController.php +++ b/src/Controller/MangaController.php @@ -7,203 +7,227 @@ use App\Repository\MangaRepository; use App\Service\MangaExportService; use App\Service\LelScansProviderService; use App\Service\MangaScraperService; +use App\Service\MangaUpdatesDbProvider; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\ResponseHeaderBag; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\String\Slugger\AsciiSlugger; class MangaController extends AbstractController { - private MangaScraperService $mangaScraperService; - private MangaExportService $mangaExportService; - private LelScansProviderService $mangaProviderService; - private MangaRepository $mangaRepository; - - public function __construct(MangaScraperService $mangaScraperService, MangaExportService $mangaExportService, LelScansProviderService $mangaProviderService, MangaRepository $mangaRepository) - { - $this->mangaScraperService = $mangaScraperService; - $this->mangaExportService = $mangaExportService; - $this->mangaProviderService = $mangaProviderService; - $this->mangaRepository = $mangaRepository; - } + public function __construct( + private readonly MangaScraperService $mangaScraperService, + private readonly MangaExportService $mangaExportService, + private readonly LelScansProviderService $mangaProviderService, + private readonly MangaRepository $mangaRepository, + private MangaUpdatesDbProvider $mangaUpdatesDbProvider + ) + { + } #[Route('/manga', name: 'app_manga')] public function index(): Response { -// $this->breadcrumbs->addItem("Accueil", $this->generateUrl("app_manga")); -// $this->breadcrumbs->addItem("Mangas", $this->generateUrl("manga_show")); - - $mangas = $this->mangaRepository->findAll(); + $mangas = $this->mangaRepository->findAll(); return $this->render('manga/index.html.twig', [ 'controller_name' => 'MangaController', - 'mangas' => $mangas, + 'mangas' => $mangas, ]); } - #[Route('/manga/{mangaSlug}', name: 'manga_show')] - public function showChapters(string $mangaSlug): Response - { - $manga = $this->mangaRepository->findOneBy(['slug' => $mangaSlug]); + #[Route('/manga/{mangaSlug}', name: 'manga_show')] + public function showChapters(string $mangaSlug): Response + { + $manga = $this->mangaRepository->findOneBy(['slug' => $mangaSlug]); - if (!$manga) { - $manga = new Manga(); - $manga->setSlug($mangaSlug); - $manga->setTitle($this->slugToTitle($mangaSlug)); - $this->mangaRepository->save($manga, true); - } + if (!$manga) { + throw new NotFoundHttpException("Le manga demandé n'existe pas."); + } - $availableChapters = $this->mangaProviderService->getChapterList($mangaSlug); + $availableChapters = $this->mangaProviderService->getChapterList($mangaSlug); - return $this->render('manga/show_chapters.html.twig', [ - 'controller_name' => 'MangaController', - 'manga' => $manga, - 'availableChapters' => $availableChapters, - ]); - } + return $this->render('manga/show_chapters.html.twig', [ + 'controller_name' => 'MangaController', + 'manga' => $manga, + 'availableChapters' => $availableChapters, + ]); + } #[Route('/manga/{mangaSlug}/{chapterNumber}/{pageNumber}', name: 'read_chapter_page')] - public function readChapterPage(string $mangaSlug, float $chapterNumber, int $pageNumber = 0): Response - { - $manga = $this->mangaRepository->findOneBy(['slug' => $mangaSlug]); - if (!$manga) { - throw $this->createNotFoundException("Le manga demandé n'existe pas."); - } + public function readChapterPage(string $mangaSlug, float $chapterNumber, int $pageNumber = 0): 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."); - } + $chapter = $manga->getChapterByNumber($chapterNumber); + if (!$chapter) { + throw $this->createNotFoundException("Le chapitre demandé n'existe pas."); + } - $currentPage = $chapter->getPageByNumber($pageNumber); - if (!$currentPage) { - throw $this->createNotFoundException("La page demandée n'existe pas."); - } + $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, - ]); - } + return $this->render('manga/manga_reader.html.twig', [ + 'manga' => $manga, + 'chapter' => $chapter, + 'pages' => $chapter->getPagesLink(), + 'currentPage' => $currentPage, + ]); + } + + #[Route('/addNew', name: 'add_new_manga')] + public function addNew(): Response + { + $availableManga = $this->mangaProviderService->getMangaList(); + + foreach ($availableManga as $key => $manga) { + $availableManga[$key]['slug'] = $this->titleToSlug($manga['name']); + } + + $mangas = $this->mangaRepository->findAll(); + return $this->render('manga/add_new.html.twig', [ + 'availableManga' => $availableManga, + 'mangas' => $mangas, + ]); + } + + public function search(string $title): Response + { + $mangas = $this->mangaUpdatesDbProvider->search($title); + + return $this->render('manga/add_new.html.twig', [ + 'mangas' => $mangas, + ]); + } #[Route('/manga/{mangaSlug}/chapter/{chapterNumber}/download', name: 'download_chapter')] - public function downloadChapter(string $mangaSlug, float $chapterNumber): BinaryFileResponse - { - $response = $this->mangaExportService->downloadCbz($this->slugToTitle($mangaSlug), $chapterNumber); + public function downloadChapter(string $mangaSlug, float $chapterNumber): BinaryFileResponse + { + $response = $this->mangaExportService->downloadCbz($this->slugToTitle($mangaSlug), $chapterNumber); - if($response === false){ - throw $this->createNotFoundException("Le chapitre demandé n'existe pas."); - } + if ($response === false) { + throw $this->createNotFoundException("Le chapitre demandé n'existe pas."); + } - // Définir les en-têtes pour le téléchargement - $response->headers->set('Content-Type', 'application/x-cbz'); - $response->setContentDisposition( - ResponseHeaderBag::DISPOSITION_ATTACHMENT, - "{$mangaSlug}_{$chapterNumber}.cbz" - ); + // Définir les en-têtes pour le téléchargement + $response->headers->set('Content-Type', 'application/x-cbz'); + $response->setContentDisposition( + ResponseHeaderBag::DISPOSITION_ATTACHMENT, + "{$mangaSlug}_{$chapterNumber}.cbz" + ); - return $response; - } + return $response; + } - #[Route('/scrape', name: 'manga_scrape', methods: 'POST')] - public function scrapeByMangaAndChapter(Request $request): Response - { - $mangaSlug = $request->request->get('mangaSlug'); - $chapterNumber = $request->request->get('chapterNumber'); + #[Route('/scrape', name: 'manga_scrape', methods: 'POST')] + public function scrapeByMangaAndChapter(Request $request): Response + { + $mangaSlug = $request->request->get('mangaSlug'); + $chapterNumber = $request->request->get('chapterNumber'); - $response = $this->scrapeChapter($mangaSlug, $chapterNumber); + $response = $this->scrapeChapter($mangaSlug, $chapterNumber); - $manga = $this->mangaRepository->findOneBy(['slug' => $mangaSlug]); + $manga = $this->mangaRepository->findOneBy(['slug' => $mangaSlug]); - $availableChapters = $this->mangaProviderService->getChapterList($mangaSlug); + $availableChapters = $this->mangaProviderService->getChapterList($mangaSlug); - return $this->render('manga/show_chapters.html.twig', [ - 'controller_name' => 'MangaController', - 'manga' => $manga, - 'availableChapters' => $availableChapters, - ]); - } + return $this->render('manga/show_chapters.html.twig', [ + 'controller_name' => 'MangaController', + 'manga' => $manga, + 'availableChapters' => $availableChapters, + ]); + } - #[Route('/scrapeFrom', name: 'manga_scrape_from_chapter', methods: 'POST')] - public function scrapeByMangaFromChapter(Request $request): Response - { - $mangaSlug = $request->request->get('mangaSlug'); - $chapterNumber = $request->request->get('chapterNumber'); + #[Route('/scrapeFrom', name: 'manga_scrape_from_chapter', methods: 'POST')] + public function scrapeByMangaFromChapter(Request $request): Response + { + $mangaSlug = $request->request->get('mangaSlug'); + $chapterNumber = $request->request->get('chapterNumber'); - do{ - $response = $this->scrapeChapter($mangaSlug, $chapterNumber); - $chapterNumber++; - }while($response !== false); + do { + $response = $this->scrapeChapter($mangaSlug, $chapterNumber); + $chapterNumber++; + } while ($response !== false); - $availableChapters = $this->mangaProviderService->getChapterList($mangaSlug); + $availableChapters = $this->mangaProviderService->getChapterList($mangaSlug); - return $this->redirectToRoute('manga_show', ['mangaSlug' => $mangaSlug, 'availableChapters' => $availableChapters]); - } + return $this->redirectToRoute('manga_show', ['mangaSlug' => $mangaSlug, 'availableChapters' => $availableChapters]); + } - #[Route('/manga/exportFrom/{mangaSlug}/{chapterNumber}', name: 'manga_export')] - public function exportMangaCbz(string $mangaSlug, float $chapterNumber) - { - $response = $this->exportCbz($this->slugToTitle($mangaSlug), $chapterNumber); + #[Route('/manga/exportFrom/{mangaSlug}/{chapterNumber}', name: 'manga_export')] + public function exportMangaCbz(string $mangaSlug, float $chapterNumber) + { + $response = $this->exportCbz($this->slugToTitle($mangaSlug), $chapterNumber); - dd($response); - } + dd($response); + } - #[Route('/getList', name: 'get_manga_list')] - public function getMangaList() - { - $list = $this->mangaProviderService->getMangaList(); + #[Route('/getList', name: 'get_manga_list')] + public function getMangaList() + { + $list = $this->mangaProviderService->getMangaList(); - } + } - private function scrapeChapter(string $mangaSlug, float $chapterNumber): array|bool - { - $url = 'https://lelscans.net/scan-' . $mangaSlug . '/' . $chapterNumber; + private function scrapeChapter(string $mangaSlug, float $chapterNumber): array|bool + { + $url = 'https://lelscans.net/scan-' . $mangaSlug . '/' . $chapterNumber; - $manga = $this->mangaRepository->findOneBy(['slug' => $mangaSlug]); + $manga = $this->mangaRepository->findOneBy(['slug' => $mangaSlug]); - if(!is_null($manga)){ - $scrapedManga = $this->mangaScraperService->scrapeMangaChapter($url, $manga->getTitle(), $chapterNumber); - }else{ - $title = $this->slugToTitle($mangaSlug); - $manga = new Manga(); - $manga->setTitle($title); - $manga->setSlug($mangaSlug); - $this->mangaRepository->save($manga); - $scrapedManga = $this->mangaScraperService->scrapeMangaChapter($url, $title, $chapterNumber); - } + if (!is_null($manga)) { + $scrapedManga = $this->mangaScraperService->scrapeMangaChapter($url, $manga->getTitle(), $chapterNumber); + } else { + $title = $this->slugToTitle($mangaSlug); + $manga = new Manga(); + $manga->setTitle($title); + $manga->setSlug($mangaSlug); + $this->mangaRepository->save($manga); + $scrapedManga = $this->mangaScraperService->scrapeMangaChapter($url, $title, $chapterNumber); + } - return $scrapedManga; - } + return $scrapedManga; + } - private function exportCbz(string $mangaSlug, float $chapterNumber):array - { - $exported = []; - do{ - $response = $this->mangaExportService->exportMangaChapter($mangaSlug, $chapterNumber); + private function exportCbz(string $mangaSlug, float $chapterNumber): array + { + $exported = []; + do { + $response = $this->mangaExportService->exportMangaChapter($mangaSlug, $chapterNumber); - if($response === 'already_exported'){ - $exported[] = $mangaSlug . ' - ' . $chapterNumber . ' ' . $response; - }elseif($response === true){ - $exported[] = $mangaSlug . ' - ' . $chapterNumber . ' exported'; - }else{ - $exported[] = $mangaSlug . ' - ' . $chapterNumber . ' something went wrong'; - } + if ($response === 'already_exported') { + $exported[] = $mangaSlug . ' - ' . $chapterNumber . ' ' . $response; + } elseif ($response === true) { + $exported[] = $mangaSlug . ' - ' . $chapterNumber . ' exported'; + } else { + $exported[] = $mangaSlug . ' - ' . $chapterNumber . ' something went wrong'; + } - $chapterNumber++; - }while($response !== false); + $chapterNumber++; + } while ($response !== false); - return $exported; - } + return $exported; + } - private function slugToTitle(string $slug): string - { - $slugger = new AsciiSlugger(); - $title = $slugger->slug($slug)->replace('-', ' ')->title(true)->toString(); + private function slugToTitle(string $slug): string + { + $slugger = new AsciiSlugger(); + $title = $slugger->slug($slug)->replace('-', ' ')->title(true)->toString(); - return $title; - } + return $title; + } + + private function titleToSlug(string $title): string + { + $slugger = new AsciiSlugger(); + return $slugger->slug($title)->lower()->toString(); + } } diff --git a/src/Controller/MenuController.php b/src/Controller/MenuController.php index b25f3c3..eff2264 100644 --- a/src/Controller/MenuController.php +++ b/src/Controller/MenuController.php @@ -27,7 +27,7 @@ class MenuController extends AbstractController } $mangas = $this->mangaRepository->findAll(); - return $this->render('menu/menu.html.twig', [ + return $this->render('menu/menu_old.html.twig', [ 'availableManga' => $availableManga, 'mangas' => $mangas, ]); @@ -44,4 +44,4 @@ class MenuController extends AbstractController $slugger = new AsciiSlugger(); return $slugger->slug($title)->lower()->toString(); } -} \ No newline at end of file +} diff --git a/src/DataFixtures/AppFixtures.php b/src/DataFixtures/AppFixtures.php index c0af250..115ae6d 100644 --- a/src/DataFixtures/AppFixtures.php +++ b/src/DataFixtures/AppFixtures.php @@ -21,11 +21,11 @@ class AppFixtures extends Fixture ]; }); - $mangas = MangaFactory::createMany(5); + $mangas = MangaFactory::createMany(25); foreach ($mangas as $manga) { - for ($i = 1; $i <= 10; $i++) { + for ($i = 1; $i <= 5; $i++) { $manga->addChapter(ChapterFactory::createOne([ 'manga' => $manga, 'number' => $i @@ -33,7 +33,7 @@ class AppFixtures extends Fixture } foreach ($manga->getChapters() as $chapter) { - for ($i = 1; $i <= 15; $i++) { + for ($i = 1; $i <= 5; $i++) { $chapter->addPagesLink(PageFactory::createOne([ 'chapter' => $chapter, 'number' => $i diff --git a/src/Entity/Manga.php b/src/Entity/Manga.php index 9f762ed..48b11e3 100644 --- a/src/Entity/Manga.php +++ b/src/Entity/Manga.php @@ -5,6 +5,7 @@ namespace App\Entity; use App\Repository\MangaRepository; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; #[ORM\Entity(repositoryClass: MangaRepository::class)] @@ -24,6 +25,18 @@ class Manga #[ORM\Column(length: 255)] private ?string $slug = null; + #[ORM\Column(length: 255, nullable: true)] + private ?string $imageUrl = null; + + #[ORM\Column(nullable: true)] + private ?int $publicationYear = null; + + #[ORM\Column(type: Types::TEXT, nullable: true)] + private ?string $description = null; + + #[ORM\Column(type: Types::ARRAY, nullable: true)] + private ?array $genres = null; + public function __construct() { $this->chapters = new ArrayCollection(); @@ -55,14 +68,14 @@ class Manga } public function getChapterByNumber(float $number): ?Chapter - { - foreach ($this->chapters as $chapter) { - if ($chapter->getNumber() === $number) { - return $chapter; - } - } - return null; - } + { + foreach ($this->chapters as $chapter) { + if ($chapter->getNumber() === $number) { + return $chapter; + } + } + return null; + } public function addChapter(Chapter $chapter): self { @@ -97,4 +110,52 @@ class Manga return $this; } + + public function getImageUrl(): ?string + { + return $this->imageUrl; + } + + public function setImageUrl(?string $imageUrl): static + { + $this->imageUrl = $imageUrl; + + return $this; + } + + public function getPublicationYear(): ?int + { + return $this->publicationYear; + } + + public function setPublicationYear(?int $publicationYear): static + { + $this->publicationYear = $publicationYear; + + return $this; + } + + public function getDescription(): ?string + { + return $this->description; + } + + public function setDescription(?string $description): static + { + $this->description = $description; + + return $this; + } + + public function getGenres(): ?array + { + return $this->genres; + } + + public function setGenres(?array $genres): static + { + $this->genres = $genres; + + return $this; + } } diff --git a/src/EventListener/MangaScrapedListener.php b/src/EventListener/MangaScrapedListener.php index 090ddca..60f40e3 100644 --- a/src/EventListener/MangaScrapedListener.php +++ b/src/EventListener/MangaScrapedListener.php @@ -5,7 +5,7 @@ namespace App\EventListener; use App\Entity\Chapter; use App\Entity\Manga; use App\Entity\Page; -use App\Event\MangaScrapedEvent; +use App\EventSubscriber\MangaScrapedEvent; use Doctrine\ORM\EntityManagerInterface; class MangaScrapedListener @@ -50,4 +50,4 @@ class MangaScrapedListener } $this->entityManager->flush(); } -} \ No newline at end of file +} diff --git a/src/Factory/MangaFactory.php b/src/Factory/MangaFactory.php index 4d03328..8e5dd87 100644 --- a/src/Factory/MangaFactory.php +++ b/src/Factory/MangaFactory.php @@ -4,6 +4,7 @@ namespace App\Factory; use App\Entity\Manga; use App\Repository\MangaRepository; +use Symfony\Component\String\Slugger\SluggerInterface; use Zenstruck\Foundry\ModelFactory; use Zenstruck\Foundry\Proxy; use Zenstruck\Foundry\RepositoryProxy; @@ -34,7 +35,7 @@ final class MangaFactory extends ModelFactory * * @todo inject services if required */ - public function __construct() + public function __construct(private SluggerInterface $slugger) { parent::__construct(); } @@ -46,9 +47,11 @@ final class MangaFactory extends ModelFactory */ protected function getDefaults(): array { + $title = self::faker()->words(rand(1, 3), true); + return [ - 'slug' => self::faker()->text(255), - 'title' => self::faker()->text(255), + 'slug' => $this->slugger->slug($title)->lower(), + 'title' => $title, ]; } diff --git a/src/Service/MangaDbProviderInterface.php b/src/Service/MangaDbProviderInterface.php new file mode 100644 index 0000000..293ac88 --- /dev/null +++ b/src/Service/MangaDbProviderInterface.php @@ -0,0 +1,10 @@ +filter('img')->attr('src'); + $imgUrl = $crawler->filter($selector)->attr('src'); $nextLink = $crawler->filter('a[title="Suivant"]'); if (!preg_match('/^https?:\/\//', $imgUrl)) { @@ -54,7 +54,10 @@ class MangaScraperService ]; } - public function scrapeMangaChapter(string $chapterUrl, string $mangaTitle, float $chapterNumber): array|bool + /** + * @throws GuzzleException + */ + public function scrapeMangaChapter(string $chapterUrl, string $mangaTitle, float $chapterNumber): array|bool { if(!$this->isChapterAvailable($chapterUrl, $chapterNumber)){ return false; @@ -101,7 +104,10 @@ class MangaScraperService return $pageData; } - private function fetchHtml(string $url): string + /** + * @throws GuzzleException + */ + private function fetchHtml(string $url): string { $client = new Client(); $response = $client->get($url); @@ -109,7 +115,10 @@ class MangaScraperService return (string) $response->getBody(); } - private function downloadAndSaveImage(string $imageUrl, string $destinationPath): void + /** + * @throws GuzzleException + */ + private function downloadAndSaveImage(string $imageUrl, string $destinationPath): void { $client = new Client(); $response = $client->get($imageUrl); @@ -117,7 +126,10 @@ class MangaScraperService file_put_contents($destinationPath, $response->getBody()->getContents()); } - private function isChapterAvailable(string $chapterUrl, float $chapterNumber): bool + /** + * @throws GuzzleException + */ + private function isChapterAvailable(string $chapterUrl, float $chapterNumber): bool { $html = $this->fetchHtml($chapterUrl); $crawler = new Crawler($html); @@ -142,4 +154,4 @@ class MangaScraperService return true; } -} \ No newline at end of file +} diff --git a/src/Service/MangaUpdatesDbProvider.php b/src/Service/MangaUpdatesDbProvider.php new file mode 100644 index 0000000..9ba7965 --- /dev/null +++ b/src/Service/MangaUpdatesDbProvider.php @@ -0,0 +1,67 @@ +client = new Client(); + } + + /** + * @throws Exception + */ + public function search(string $title): Collection + { + try { + $response = $this->client->request('PUT', 'https://api.mangaupdates.com/v1/account/login', [ + 'json' => [ + 'username' => 'Colgora', + 'password' => '7TK5jv33NDn*SLV', + ] + ]) + ->withHeader('Content-Type', 'application/json'); + + $jwt = json_decode($response->getBody()->getContents(), true)['context']['session_token']; + + $results = $this->client->request('POST', 'https://api.mangaupdates.com/v1/series/search', [ + 'json' => [ + 'search' => $title, + 'orderby' => 'score', + ] + ])->withHeader('Authorization', 'Bearer ' . $jwt) + ->withHeader('Content-Type', 'application/json') + ->getBody() + ->getContents(); + + $mangas = []; + foreach (json_decode($results, true)['results'] as $record) { + $record = $record['record']; + $mangas[] = (new Manga()) + ->setTitle($record['title']) + ->setSlug($this->slugger->slug($record['title'])->lower()) + ->setDescription($record['description']) + ->setImageUrl($record['image']['url']['original']) + ->setGenres($record['genres']) + ->setPublicationYear((int)$record['year']); + } + + return new ArrayCollection($mangas); + } catch (GuzzleException $e) { + throw new Exception($e->getMessage()); + } + } +} diff --git a/tailwind.config.js b/tailwind.config.js index c226c0b..2282bb8 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,14 +1,12 @@ /** @type {import('tailwindcss').Config} */ module.exports = { content: [ - './templates/**/*.html.twig', - './node_modules/tw-elements/dist/js/**/*.js' + './templates/**/*.html.twig' ], theme: { extend: {}, }, plugins: [ - require('tw-elements/dist/plugin'), require("daisyui"), ], } diff --git a/templates/base.html.twig b/templates/base.html.twig index acee69c..c87a2dd 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -1,59 +1,31 @@ - +
-{{ manga.title }}
+Auteur
+Ajouter: Jun 2 2024
+Aucun manga trouvé.
{% endfor %} - +