diff --git a/assets/app.js b/assets/app.js
deleted file mode 100644
index edc04bf..0000000
--- a/assets/app.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import './bootstrap.js';
-
-import '@fortawesome/fontawesome-free/js/all.js';
-/*
- * Welcome to your app's main JavaScript file!
- *
- * We recommend including the built version of this JavaScript file
- * (and its CSS file) in your base layout (base.html.twig).
- */
-
-// any CSS you import will output into a single css file (app.css in this case)
-import './styles/app.scss';
-
-// start the Stimulus application
-import './bootstrap';
-
-// La ligne registerReactControllerComponents a déjà été commentée
diff --git a/assets/bootstrap.js b/assets/bootstrap.js
deleted file mode 100644
index cfa3957..0000000
--- a/assets/bootstrap.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import { startStimulusApp } from '@symfony/stimulus-bridge';
-
-// Registers Stimulus controllers from controllers.json and in the controllers/ directory
-export const app = startStimulusApp(require.context(
- '@symfony/stimulus-bridge/lazy-controller-loader!./controllers',
- true,
- /\.[jt]sx?$/
-));
-
-// register any custom, 3rd party controllers here
-// app.register('some_controller_name', SomeImportedController);
-
-//DEBUG TURBO
-// import * as Turbo from "@hotwired/turbo"
-//
-// Turbo.session.drive = false
-// Turbo.start()
-//
-// // Écouteurs existants
-// document.addEventListener("turbo:before-stream-render", (event) => {
-// console.log("Before stream render", event.target);
-// });
-//
-// document.addEventListener("turbo:stream-render", (event) => {
-// console.log("Stream rendered", event.target);
-// });
-//
-// // Nouvel écouteur pour les événements de création
-// document.addEventListener("turbo:before-fetch-request", (event) => {
-// console.log("Before fetch request", event.detail.fetchOptions);
-// });
-//
-// document.addEventListener("turbo:before-fetch-response", (event) => {
-// console.log("Before fetch response", event.detail.fetchResponse);
-// });
diff --git a/assets/controllers.json b/assets/controllers.json
deleted file mode 100644
index 3dfe062..0000000
--- a/assets/controllers.json
+++ /dev/null
@@ -1,30 +0,0 @@
-{
- "controllers": {
- "@symfony/ux-live-component": {
- "live": {
- "enabled": true,
- "fetch": "eager",
- "autoimport": {
- "@symfony/ux-live-component/dist/live.min.css": true
- }
- }
- },
- "@symfony/ux-react": {
- "react": {
- "enabled": true,
- "fetch": "eager"
- }
- },
- "@symfony/ux-turbo": {
- "turbo-core": {
- "enabled": true,
- "fetch": "eager"
- },
- "mercure-turbo-stream": {
- "enabled": true,
- "fetch": "eager"
- }
- }
- },
- "entrypoints": []
-}
diff --git a/assets/controllers/activity_controller.js b/assets/controllers/activity_controller.js
deleted file mode 100644
index 259a8f7..0000000
--- a/assets/controllers/activity_controller.js
+++ /dev/null
@@ -1,54 +0,0 @@
-import {Controller} from '@hotwired/stimulus';
-
-/*
-* The following line makes this controller "lazy": it won't be downloaded until needed
-* See https://github.com/symfony/stimulus-bridge#lazy-controllers
-*/
-/* stimulusFetch: 'lazy' */
-export default class extends Controller {
- static targets = ['activity']
-
- // ...
- async connect() {
- try {
- const response = await fetch(`/activity/status`, {
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- 'X-Requested-With': 'XMLHttpRequest'
- }
- });
-
- const data = await response.json();
- // Handle the response data as needed
- this.activityTarget.innerHTML = data.length;
- if (data.length > 0) {
- this.activityTarget.classList.remove('hidden');
- }
- } catch (error) {
- console.error('Error:', error);
- }
-
-
- const mercureHubUrl = 'https://mangarr.test.nestor-server.fr/.well-known/mercure';
- const eventSource = new EventSource(`${mercureHubUrl}?topic=activity`, {withCredentials: true});
-
- eventSource.onmessage = (event) => {
- const data = JSON.parse(event.data);
- if (data.processing !== undefined && data.pending !== undefined) {
- let totalActivities = data.processing.length + data.pending.length;
- this.activityTarget.innerHTML = totalActivities;
- if (totalActivities > 0) {
- this.activityTarget.classList.remove('hidden');
- }else if (totalActivities === 0) {
- this.activityTarget.classList.add('hidden');
- }
- }
- };
-
- eventSource
- .onerror = (event) => {
- console.error('EventSource failed:', event);
- };
- }
-}
diff --git a/assets/controllers/addmanga_controller.js b/assets/controllers/addmanga_controller.js
deleted file mode 100644
index b78e347..0000000
--- a/assets/controllers/addmanga_controller.js
+++ /dev/null
@@ -1,14 +0,0 @@
-// assets/controllers/addmanga_controller.js
-import { Controller } from "@hotwired/stimulus"
-
-export default class extends Controller {
- static values = {
- index: Number
- }
-
- openModal(event) {
- event.preventDefault()
- const openEvent = new CustomEvent(`openAddMangaModal${this.indexValue}`)
- document.dispatchEvent(openEvent)
- }
-}
diff --git a/assets/controllers/alert_controller.js b/assets/controllers/alert_controller.js
deleted file mode 100644
index ae5b8d9..0000000
--- a/assets/controllers/alert_controller.js
+++ /dev/null
@@ -1,60 +0,0 @@
-import {Controller} from '@hotwired/stimulus';
-
-/*
-* The following line makes this controller "lazy": it won't be downloaded until needed
-* See https://github.com/symfony/stimulus-bridge#lazy-controllers
-*/
-/* stimulusFetch: 'lazy' */
-export default class extends Controller {
- static targets = ['alert', 'icon', 'message']
-
- connect() {
- window.addEventListener('alert:show', this.showAlert.bind(this));
- }
-
- // ...
- showAlert(event) {
- const detail = event.detail;
- const message = detail.message;
- const level = detail.level;
-
- let alertClass = "";
- let iconClass = "";
- switch (level) {
- case 'success':
- alertClass = "bg-green-500";
- iconClass = "fa-circle-check";
- break;
- case 'warning':
- alertClass = "bg-yellow-500";
- iconClass = "fa-circle-exclamation";
- break;
- case 'error':
- alertClass = "bg-red-500";
- iconClass = "fa-circle-xmark";
- break;
- case 'info':
- default:
- alertClass = "bg-blue-500";
- iconClass = "fa-circle-info";
- break;
- }
-
- this.messageTarget.innerHTML = message;
- this.alertTarget.classList.add(alertClass);
- this.iconTarget.classList.add(iconClass);
- this.alertTarget.style.display = "block";
-
- setTimeout(() => {
- this.alertTarget.style.opacity = 0;
-
- setTimeout(() => {
- this.alertTarget.style.display = 'none';
- this.alertTarget.classList.remove(alertClass);
- this.alertTarget.style.opacity = 1;
- this.iconTarget.classList.remove(iconClass);
- this.messageTarget.innerHTML = message;
- }, 1000);
- }, 3000);
- }
-}
diff --git a/assets/controllers/chapter_progress_controller.js b/assets/controllers/chapter_progress_controller.js
deleted file mode 100644
index 76b9d7a..0000000
--- a/assets/controllers/chapter_progress_controller.js
+++ /dev/null
@@ -1,45 +0,0 @@
-import { Controller } from '@hotwired/stimulus';
-
-/* stimulusFetch: 'lazy' */
-export default class extends Controller {
- static targets = ['progressBar', 'progressText']
- static values = {
- chapterId: Number
- }
-
- connect() {
- this.currentPage = 0;
- this.totalPages = 0;
-
- const mercureHubUrl = 'https://mangarr.test.nestor-server.fr/.well-known/mercure';
- this.eventSource = new EventSource(`${mercureHubUrl}?topic=activity`, {withCredentials: true});
-
- this.eventSource.onmessage = this.handleMessage.bind(this);
- }
-
- disconnect() {
- if (this.eventSource) {
- this.eventSource.close();
- }
- }
-
- handleMessage(event) {
- const data = JSON.parse(event.data);
- if (data.status === "scrapping.progress" && data.chapterId === this.chapterIdValue) {
- this.handleProgressUpdate(data);
- }
- }
-
- handleProgressUpdate(data) {
- this.currentPage = data.pageIndex;
- this.totalPages = data.totalPages;
-
- this.updateProgressBar();
- }
-
- updateProgressBar() {
- const progress = (this.currentPage / this.totalPages) * 100;
- this.progressBarTarget.style.width = `${progress}%`;
- this.progressTextTarget.textContent = `${this.currentPage} / ${this.totalPages}`;
- }
-}
diff --git a/assets/controllers/collection_controller.js b/assets/controllers/collection_controller.js
deleted file mode 100644
index ac5a6a1..0000000
--- a/assets/controllers/collection_controller.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import {Controller} from '@hotwired/stimulus';
-
-/*
-* The following line makes this controller "lazy": it won't be downloaded until needed
-* See https://github.com/symfony/stimulus-bridge#lazy-controllers
-*/
-/* stimulusFetch: 'lazy' */
-export default class extends Controller {
- static targets = ['container', 'template', 'item'];
-
- connect() {
- this.index = this.itemTargets.length;
- }
-
- add(event) {
- event.preventDefault();
- const template = this.templateTarget.innerHTML.replace(/__name__/g, this.index);
- this.containerTarget.insertAdjacentHTML('beforeend', template);
- this.index++;
- }
-
- remove(event) {
- event.preventDefault();
- event.target.closest('.collection-item').remove();
- }
-}
diff --git a/assets/controllers/download_controller.js b/assets/controllers/download_controller.js
deleted file mode 100644
index 685c4b9..0000000
--- a/assets/controllers/download_controller.js
+++ /dev/null
@@ -1,69 +0,0 @@
-import { Controller } from '@hotwired/stimulus';
-
-/* stimulusFetch: 'lazy' */
-export default class extends Controller {
- static targets = ['icon']
- static values = {
- url: String
- }
-
- connect() {
- this.defaultIconClass = this.iconTarget.classList.value;
- }
-
- async download(event) {
- event.preventDefault();
-
- // Change the icon to a loader
- this.iconTarget.classList.remove("fa-download", "fa-search");
- this.iconTarget.classList.add("fa-spinner", "fa-spin");
-
- try {
- const response = await fetch(this.urlValue, {
- method: 'GET',
- headers: {
- 'Accept': 'application/json',
- 'X-Requested-With': 'XMLHttpRequest'
- }
- });
-
- const contentType = response.headers.get("Content-Type");
- if (contentType && contentType.includes("application/json")) {
- const data = await response.json();
- if (data.error) {
- this.dispatchAlert(data.error, 'error');
- } else if (data.success) {
- this.dispatchAlert(data.success, 'success');
- }
- } else {
- // C'est un fichier à télécharger
- const blob = await response.blob();
- const url = window.URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.style.display = 'none';
- a.href = url;
- const contentDisposition = response.headers.get('Content-Disposition');
- const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
- const matches = filenameRegex.exec(contentDisposition);
- let filename = 'download';
- if (matches != null && matches[1]) {
- filename = matches[1].replace(/['"]/g, '');
- }
- a.download = filename;
- document.body.appendChild(a);
- a.click();
- window.URL.revokeObjectURL(url);
- }
- } finally {
- // Revert the icon back to the original one
- this.iconTarget.classList.value = this.defaultIconClass;
- }
- }
-
- dispatchAlert(message, level) {
- const event = new CustomEvent('alert:show', {
- detail: { message: message, level: level }
- });
- window.dispatchEvent(event);
- }
-}
diff --git a/assets/controllers/dropdown_controller.js b/assets/controllers/dropdown_controller.js
deleted file mode 100644
index 25989ea..0000000
--- a/assets/controllers/dropdown_controller.js
+++ /dev/null
@@ -1,45 +0,0 @@
-// assets/controllers/dropdown_controller.js
-import {Controller} from "@hotwired/stimulus"
-import {useClickOutside} from "stimulus-use"
-
-export default class extends Controller {
- static targets = ["button", "menu"]
-
- connect() {
- useClickOutside(this)
- }
-
- toggle(event) {
- this.menuTarget.classList.toggle('hidden')
- if (!this.menuTarget.classList.contains('hidden')) {
- this.positionMenu()
- }
- }
-
- clickOutside(event) {
- this.menuTarget.classList.add('hidden')
- }
-
- positionMenu() {
- const buttonRect = this.buttonTarget.getBoundingClientRect()
- const menuRect = this.menuTarget.getBoundingClientRect()
- const spaceRight = window.innerWidth - buttonRect.right
- const spaceBottom = window.innerHeight - buttonRect.bottom
-
- if (spaceRight < menuRect.width && buttonRect.left > menuRect.width) {
- this.menuTarget.style.left = 'auto'
- this.menuTarget.style.right = '0'
- } else {
- this.menuTarget.style.left = '0'
- this.menuTarget.style.right = 'auto'
- }
-
- if (spaceBottom < menuRect.height && buttonRect.top > menuRect.height) {
- this.menuTarget.style.top = 'auto'
- this.menuTarget.style.bottom = '100%'
- } else {
- this.menuTarget.style.top = '100%'
- this.menuTarget.style.bottom = 'auto'
- }
- }
-}
diff --git a/assets/controllers/hello_controller.js b/assets/controllers/hello_controller.js
deleted file mode 100644
index e847027..0000000
--- a/assets/controllers/hello_controller.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import { Controller } from '@hotwired/stimulus';
-
-/*
- * This is an example Stimulus controller!
- *
- * Any element with a data-controller="hello" attribute will cause
- * this controller to be executed. The name "hello" comes from the filename:
- * hello_controller.js -> "hello"
- *
- * Delete this file or adapt it for your use!
- */
-export default class extends Controller {
- connect() {
- this.element.textContent = 'Hello Stimulus! Edit me in assets/controllers/hello_controller.js';
- }
-}
diff --git a/assets/controllers/import_match_controller.js b/assets/controllers/import_match_controller.js
deleted file mode 100644
index e53d91d..0000000
--- a/assets/controllers/import_match_controller.js
+++ /dev/null
@@ -1,51 +0,0 @@
-import { Controller } from '@hotwired/stimulus';
-
-export default class extends Controller {
- static targets = ["checkbox", "modal", "modalContent"]
-
- toggleAllCheckboxes(event) {
- this.checkboxTargets.forEach(checkbox => {
- checkbox.checked = event.target.checked;
- });
- }
-
- updateMangaInfo(event) {
- const select = event.target;
- const selectedOption = select.options[select.selectedIndex];
- const mangaInfo = JSON.parse(selectedOption.dataset.mangaInfo);
- }
-
- showDetails(event) {
- const fileId = event.currentTarget.dataset.fileId;
- const select = document.querySelector(`select[name="manga_slug[${fileId}]"]`);
- const mangaInfo = JSON.parse(select.options[select.selectedIndex].dataset.mangaInfo);
-
- this.modalContentTarget.innerHTML = `
-
${mangaInfo.title}
-
-
Author: ${mangaInfo.author || 'N/A'}
-
Publication Year: ${mangaInfo.publicationYear || 'N/A'}
-
Genres: ${mangaInfo.genres ? mangaInfo.genres.join(', ') : 'N/A'}
-
Description: ${this.truncate(mangaInfo.description || 'N/A', 200)}
-
- `;
-
- this.modalTarget.classList.remove('hidden');
- }
-
- closeModal() {
- this.modalTarget.classList.add('hidden');
- }
-
- confirmSelected(event) {
- const selectedFiles = this.checkboxTargets.filter(checkbox => checkbox.checked).map(checkbox => checkbox.value);
- if (selectedFiles.length === 0) {
- event.preventDefault();
- alert('Veuillez sélectionner au moins un fichier à importer.');
- }
- }
-
- truncate(str, length) {
- return str.length > length ? str.substring(0, length) + '...' : str;
- }
-}
diff --git a/assets/controllers/loading_button_controller.js b/assets/controllers/loading_button_controller.js
deleted file mode 100644
index a2a3255..0000000
--- a/assets/controllers/loading_button_controller.js
+++ /dev/null
@@ -1,21 +0,0 @@
-// assets/controllers/loading_button_controller.js
-import {Controller} from "@hotwired/stimulus"
-
-export default class extends Controller {
- static targets = ["text", "loader"];
- static values = {form: String};
-
- startLoading(event) {
- event.preventDefault();
- this.textTarget.classList.add("hidden");
- this.loaderTarget.classList.remove("hidden");
- this.element.disabled = true;
-
- if (this.hasFormValue) {
- const form = document.getElementById(this.formValue);
- if (form) {
- form.submit();
- }
- }
- }
-}
diff --git a/assets/controllers/menu_controller.js b/assets/controllers/menu_controller.js
deleted file mode 100644
index b0bd6d2..0000000
--- a/assets/controllers/menu_controller.js
+++ /dev/null
@@ -1,10 +0,0 @@
-// assets/controllers/menu_controller.js
-import { Controller } from '@hotwired/stimulus';
-
-export default class extends Controller {
- static targets = ["sidebar"]
-
- toggleMenu() {
- this.sidebarTarget.classList.toggle('-translate-x-full')
- }
-}
diff --git a/assets/controllers/mercure_controller.js b/assets/controllers/mercure_controller.js
deleted file mode 100644
index 27983fb..0000000
--- a/assets/controllers/mercure_controller.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import {Controller} from '@hotwired/stimulus';
-
-/*
-* The following line makes this controller "lazy": it won't be downloaded until needed
-* See https://github.com/symfony/stimulus-bridge#lazy-controllers
-*/
-/* stimulusFetch: 'lazy' */
-export default class extends Controller {
- // ...
- connect() {
- const topic = this.data.get('topic');
- const mercureHubUrl = 'https://mangarr.test.nestor-server.fr/.well-known/mercure';
- const eventSource = new EventSource(`${mercureHubUrl}?topic=${topic}`, {withCredentials: true});
-
- eventSource.onmessage = (event) => {
- const data = JSON.parse(event.data);
- console.log('Received Mercure update:', data);
-
- this.dispatchAlert(data.message, data.status);
- };
-
- eventSource.onerror = (event) => {
- console.error('EventSource failed:', event);
- };
- }
-
- dispatchAlert(message, level) {
- const event = new CustomEvent('alert:show', {
- detail: { message: message, level: level }
- });
- window.dispatchEvent(event);
- }
-}
diff --git a/assets/controllers/modal_controller.js b/assets/controllers/modal_controller.js
deleted file mode 100644
index c8c1dac..0000000
--- a/assets/controllers/modal_controller.js
+++ /dev/null
@@ -1,37 +0,0 @@
-// assets/controllers/modal_controller.js
-import { Controller } from "@hotwired/stimulus"
-
-export default class extends Controller {
- static targets = ["modal"]
- static values = {
- openTrigger: String,
- closeTrigger: String
- }
-
- connect() {
- if (this.hasOpenTriggerValue) {
- document.addEventListener(this.openTriggerValue, this.open.bind(this))
- }
- if (this.hasCloseTriggerValue) {
- document.addEventListener(this.closeTriggerValue, this.close.bind(this))
- }
- }
-
- disconnect() {
- if (this.hasOpenTriggerValue) {
- document.removeEventListener(this.openTriggerValue, this.open.bind(this))
- }
- if (this.hasCloseTriggerValue) {
- document.removeEventListener(this.closeTriggerValue, this.close.bind(this))
- }
- }
-
- open() {
- console.log("Opening modal...")
- this.modalTarget.classList.remove('hidden')
- }
-
- close() {
- this.modalTarget.classList.add('hidden')
- }
-}
diff --git a/assets/controllers/preferred_sources_controller.js b/assets/controllers/preferred_sources_controller.js
deleted file mode 100644
index 3a4c6e1..0000000
--- a/assets/controllers/preferred_sources_controller.js
+++ /dev/null
@@ -1,101 +0,0 @@
-// assets/controllers/preferred-sources_controller.js
-
-import {Controller} from "@hotwired/stimulus"
-import Sortable from 'sortablejs'
-
-export default class extends Controller {
- static targets = ["preferredList", "availableList"]
- static values = {
- mangaId: Number,
- preferredSources: Array,
- allSources: Array
- }
-
- connect() {
- this.initSortable()
- }
-
- initSortable() {
- new Sortable(this.preferredListTarget, {
- animation: 150,
- ghostClass: 'bg-gray-300',
- onEnd: this.handleDragEnd.bind(this)
- })
- }
-
- handleDragEnd() {
- this.updatePreferredSources()
- }
-
- addSource(event) {
- const sourceId = parseInt(event.currentTarget.dataset.sourceId)
- if (!this.preferredSourcesValue.includes(sourceId)) {
- this.preferredSourcesValue = [...this.preferredSourcesValue, sourceId]
- this.updateLists()
- this.save()
- }
- }
-
- removeSource(event) {
- const sourceId = parseInt(event.currentTarget.dataset.sourceId)
- this.preferredSourcesValue = this.preferredSourcesValue.filter(id => id !== sourceId)
- this.updateLists()
- this.save()
- }
-
- updatePreferredSources() {
- this.preferredSourcesValue = Array.from(this.preferredListTarget.children).map(li => parseInt(li.dataset.id))
- this.save()
- }
-
- updateLists() {
- this.preferredListTarget.innerHTML = this.preferredSourcesValue
- .map(id => this.allSourcesValue.find(s => s.id === id))
- .map(source => this.sourceTemplate(source, true))
- .join('')
-
- this.availableListTarget.innerHTML = this.allSourcesValue
- .filter(source => !this.preferredSourcesValue.includes(source.id))
- .map(source => this.sourceTemplate(source, false))
- .join('')
-
- this.initSortable()
- }
-
- sourceTemplate(source, isPreferred) {
- return `
-
- ${source.name}
-
-
-
-
- `
- }
-
- async save() {
- try {
- const response = await fetch(`/manga/${this.mangaIdValue}/preferred-sources`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'X-Requested-With': 'XMLHttpRequest'
- },
- body: JSON.stringify({
- preferredSources: this.preferredSourcesValue
- })
- })
-
- if (response.ok) {
- console.log('Preferred sources saved successfully')
- // Optionally show a success message
- } else {
- console.error('Error saving preferred sources')
- // Optionally show an error message
- }
- } catch (error) {
- console.error('Error:', error)
- // Optionally show an error message
- }
- }
-}
diff --git a/assets/controllers/reader_controller.js b/assets/controllers/reader_controller.js
deleted file mode 100644
index 771244c..0000000
--- a/assets/controllers/reader_controller.js
+++ /dev/null
@@ -1,127 +0,0 @@
-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 =>
- `
- Chapitre ${chapter.number}
- `
- ).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();
- }
-}
diff --git a/assets/controllers/scrapper_configure_controller.js b/assets/controllers/scrapper_configure_controller.js
deleted file mode 100644
index ad157d1..0000000
--- a/assets/controllers/scrapper_configure_controller.js
+++ /dev/null
@@ -1,76 +0,0 @@
-import { Controller } from '@hotwired/stimulus';
-
-export default class extends Controller {
- static targets = ['form', 'testForm', 'imageSelector', 'nextPageSelector', 'testResults', 'scrapingType']
-
- connect() {
- }
-
- async saveConfiguration(event) {
- event.preventDefault();
- this.formTarget.submit();
- }
-
- async testConfiguration(event) {
- event.preventDefault();
- const formData = new FormData(this.formTarget);
- const testFormData = new FormData(this.testFormTarget);
-
- for (let [key, value] of formData.entries()) {
- const cleanKey = key.replace(/^content_source\[(.+)]$/, '$1');
- testFormData.append(`content_source[${cleanKey}]`, value);
- }
-
- try {
- const response = await fetch(this.testFormTarget.action, {
- method: 'POST',
- body: testFormData
- });
-
- const result = await response.json();
-
- if (result.success) {
- this.displayTestResults(result.data);
- } else {
- this.displayError(result.message, result.errors);
- }
- } catch (error) {
- console.log(error)
- this.displayError('An error occurred while testing the configuration');
- }
- }
-
- displayTestResults(data) {
- let html = 'Test Results ';
- html += '';
- data.forEach(page => {
- html += `
-
-
-
Page ${page.page_number}
-
- `;
- });
- html += '
';
- this.testResultsTarget.innerHTML = html;
- }
-
- displayError(message, errors = []) {
- let errorHtml = `
-
-
Error:
-
${message}
- `;
-
- if (errors.length > 0) {
- errorHtml += '
';
- errors.forEach(error => {
- errorHtml += `${error} `;
- });
- errorHtml += ' ';
- }
-
- errorHtml += '
';
- this.testResultsTarget.innerHTML = errorHtml;
- }
-}
diff --git a/assets/controllers/scrapper_import_controller.js b/assets/controllers/scrapper_import_controller.js
deleted file mode 100644
index 98b5adc..0000000
--- a/assets/controllers/scrapper_import_controller.js
+++ /dev/null
@@ -1,81 +0,0 @@
-import { Controller } from '@hotwired/stimulus';
-
-/*
-* The following line makes this controller "lazy": it won't be downloaded until needed
-* See https://github.com/symfony/stimulus-bridge#lazy-controllers
-*/
-/* stimulusFetch: 'lazy' */
-export default class extends Controller {
- // ...
- static targets = ["textarea", "submitButton"]
-
- connect() {
- document.addEventListener('openImportModal', this.prepareImportModal.bind(this));
- document.addEventListener('openExportModal', this.prepareExportModal.bind(this));
- }
-
- disconnect() {
- document.removeEventListener('openImportModal', this.prepareImportModal.bind(this));
- document.removeEventListener('openExportModal', this.prepareExportModal.bind(this));
- }
-
- async prepareExportModal() {
- try {
- const response = await fetch('/settings/export_scrappers');
- const data = await response.json();
- this.textareaTarget.value = JSON.stringify(data, null, 2);
- this.submitButtonTarget.textContent = 'Copy to Clipboard';
- this.submitButtonTarget.dataset.action = 'scrapper-import#copyToClipboard';
- this.openModal('Export Scrapper Configurations');
- } catch (error) {
- console.error('Error:', error);
- }
- }
-
- prepareImportModal() {
- this.textareaTarget.value = '';
- this.submitButtonTarget.textContent = 'Import';
- this.submitButtonTarget.dataset.action = 'scrapper-import#submitImport';
- this.openModal('Import Scrapper Configurations');
- }
-
- openModal(title) {
- const event = new CustomEvent('openScrapperModal', { detail: { title: title } });
- document.dispatchEvent(event);
- }
-
- async submitImport() {
- const jsonData = this.textareaTarget.value;
-
- try {
- const response = await fetch('/settings/import_scrappers', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: jsonData
- });
-
- const result = await response.json();
-
- if (response.ok) {
- console.log(result.message);
- document.dispatchEvent(new CustomEvent('closeScrapperModal'));
- window.location.reload();
- } else {
- console.error(result.error);
- }
- } catch (error) {
- console.error('Error:', error);
- }
- }
-
- copyToClipboard() {
- navigator.clipboard.writeText(this.textareaTarget.value).then(() => {
- console.log('Copied to clipboard');
- document.dispatchEvent(new CustomEvent('closeScrapperModal'));
- }, (err) => {
- console.error('Could not copy text: ', err);
- });
- }
-}
diff --git a/assets/controllers/search_controller.js b/assets/controllers/search_controller.js
deleted file mode 100644
index e29e332..0000000
--- a/assets/controllers/search_controller.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import { Controller } from '@hotwired/stimulus';
-
-/*
-* The following line makes this controller "lazy": it won't be downloaded until needed
-* See https://github.com/symfony/stimulus-bridge#lazy-controllers
-*/
-/* stimulusFetch: 'lazy' */
-export default class extends Controller {
- static targets = ['input']
-
- clearSearch() {
- this.inputTarget.value = '';
- this.inputTarget.focus();
- }
-}
diff --git a/assets/controllers/table_controller.js b/assets/controllers/table_controller.js
deleted file mode 100644
index 534bdd5..0000000
--- a/assets/controllers/table_controller.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import {Controller} from '@hotwired/stimulus';
-
-/*
-* The following line makes this controller "lazy": it won't be downloaded until needed
-* See https://github.com/symfony/stimulus-bridge#lazy-controllers
-*/
-/* stimulusFetch: 'lazy' */
-export default class extends Controller {
- static targets = ["body", "toggleIcon"]
- static values = { open: Boolean }
-
- connect() {
- if (!this.openValue) {
- this.close()
- }
- }
-
- toggle() {
- if (this.bodyTarget.style.display === "none") {
- this.open()
- } else {
- this.close()
- }
- }
-
- open() {
- this.bodyTarget.style.display = "block"
- this.toggleIconTarget.classList.replace("fa-chevron-down", "fa-chevron-up")
- }
-
- close() {
- this.bodyTarget.style.display = "none"
- this.toggleIconTarget.classList.replace("fa-chevron-up", "fa-chevron-down")
- }
-}
diff --git a/assets/controllers/toolbar_controller.js b/assets/controllers/toolbar_controller.js
deleted file mode 100644
index dc068e8..0000000
--- a/assets/controllers/toolbar_controller.js
+++ /dev/null
@@ -1,198 +0,0 @@
-// assets/controllers/toolbar_controller.js
-import { Controller } from "@hotwired/stimulus"
-import { visit } from "@hotwired/turbo"
-
-export default class extends Controller {
- static targets = ["dropdown", "icon", "text"]
- static values = {
- currentSort: String,
- currentOrder: String,
- currentStatus: String,
- mangaId: Number
- }
-
- connect() {
- window.addEventListener('alert:show', this.stopLoading.bind(this));
- }
-
- stopLoading(event) {
- if(event.currentTarget.dataset !== undefined){
- this.iconTarget.classList.remove('fa-spin');
- }
- }
-
- refreshMetadata(event) {
- const mangaId = event.currentTarget.dataset.mangaid;
- const url = `/refresh_metadata`;
-
- this.iconTarget.classList.add('fa-spin');
-
- fetch(url, {
- method: 'POST',
- headers: {
- 'X-Requested-With': 'XMLHttpRequest',
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({ mangaId: mangaId })
- })
- .then(response => {
- if (!response.ok) {
- throw new Error('Network response was not ok');
- }
- return response.json();
- });
- }
-
- searchLastChapter() {
- console.log("Searching last chapter...");
- }
-
- import() {
- console.log("Importing...");
- }
-
- monitoring(event){
- const mangaId = event.currentTarget.dataset.mangaid;
- const currentTarget = event.currentTarget;
-
- const url = `/toggle_monitored`;
-
- fetch(url, {
- method: 'POST',
- headers: {
- 'X-Requested-With': 'XMLHttpRequest',
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({ mangaId: mangaId })
- })
- .then(response => {
- if (!response.ok) {
- throw new Error('Network response was not ok');
- }
- return response.json();
- }).then(data => {
- if(data.isMonitored === true){
- currentTarget.classList.remove('text-white');
- currentTarget.classList.add('text-green-500');
- this.textTarget.innerHTML = "Monitored";
- }else if(data.isMonitored === false){
- currentTarget.classList.remove('text-green-500');
- currentTarget.classList.add('text-white');
- this.textTarget.innerHTML = "Monitoring";
- }
- // console.log(data.isMonitored);
- });
-
- }
-
- editMangas() {
- console.log("Editing mangas...");
- }
-
- editManga() {
- const event = new CustomEvent('openEditModal');
- document.dispatchEvent(event);
- }
-
- editPreferredSources() {
- const event = new CustomEvent('openPreferredSourcesModal');
- document.dispatchEvent(event);
- }
-
- openImportModal() {
- const importEvent = new CustomEvent('openImportModal');
- document.dispatchEvent(importEvent);
- }
-
- openExportModal() {
- const exportEvent = new CustomEvent('openExportModal');
- document.dispatchEvent(exportEvent);
- }
-
- deleteMangas() {
- console.log("Deleting mangas...");
- }
-
- deleteManga() {
- const event = new CustomEvent('openDeleteModal');
- document.dispatchEvent(event);
- }
-
- confirmDelete(event) {
- event.preventDefault();
- const url = `/manga/delete/${this.mangaIdValue}`;
-
- fetch(url, {
- method: 'DELETE',
- headers: {
- 'X-Requested-With': 'XMLHttpRequest',
- 'Content-Type': 'application/json',
- }
- })
- .then(response => {
- if (!response.ok) {
- throw new Error('Network response was not ok');
- }
- return response.json();
- })
- .then(data => {
- if (data.success) {
- visit('/', {});
- } else {
- throw new Error(data.error);
- }
- })
- .catch(error => {
- console.error('Error:', error);
- // Show error message to user
- });
- }
-
- showOptions() {
- console.log("Showing options...");
- }
-
- expandAll() {
- console.log("Expanding all...");
- }
-
- changeView(event) {
- event.preventDefault();
- const viewOption = event.currentTarget.dataset.view;
-
- const url = new URL(window.location);
- url.searchParams.set('view', viewOption);
-
- window.location = url.toString();
- }
-
- sort(event) {
- event.preventDefault()
- const sortOption = event.currentTarget.dataset.sort;
- let order = 'asc';
-
- if (sortOption === this.currentSortValue && this.currentOrderValue === 'asc') {
- order = 'desc';
- }
-
- const url = new URL(window.location);
- url.searchParams.set('sort', sortOption);
- url.searchParams.set('order', order);
-
- window.location = url.toString();
- }
-
- filter(event) {
- event.preventDefault();
- const filterOption = event.currentTarget.dataset.filter;
-
- const url = new URL(window.location);
- url.searchParams.set('status', filterOption);
-
- // Réinitialiser la page à 1 si on utilise la pagination
- // url.searchParams.set('page', '1');
-
- window.location = url.toString();
- }
-
-}
diff --git a/config/packages/messenger.yaml b/config/packages/messenger.yaml
index 4e3572a..5329ad6 100644
--- a/config/packages/messenger.yaml
+++ b/config/packages/messenger.yaml
@@ -37,10 +37,6 @@ framework:
'App\Domain\Shared\Domain\Event\VolumeImported': events
'App\Domain\Shared\Domain\Event\ChapterScraped': events
- # Legacy messages (à garder si nécessaire)
- 'App\Message\DownloadChapter': commands
- 'App\Message\RefreshMetadata': commands
- 'App\Message\RefreshAndDownloadChapters': commands
# when@test:
# framework:
diff --git a/config/routes.yaml b/config/routes.yaml
index 365bdb7..c61f8dd 100644
--- a/config/routes.yaml
+++ b/config/routes.yaml
@@ -7,8 +7,3 @@ vue_app:
requirements:
req: "^(?!api/|legacy).*"
-controllers:
- resource:
- path: ../src/Controller/
- namespace: App\Controller
- type: attribute
diff --git a/config/services.yaml b/config/services.yaml
index 7713181..29f5dca 100644
--- a/config/services.yaml
+++ b/config/services.yaml
@@ -26,10 +26,6 @@ services:
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
- App\EventListener\ExceptionListener:
- tags:
- - { name: kernel.event_listener, event: kernel.exception, method: onKernelException }
-
GuzzleHttp\Client:
class: GuzzleHttp\Client
arguments:
@@ -43,63 +39,11 @@ services:
protocols: [ 'http', 'https' ]
track_redirects: true
- App\Service\MangaScraperService:
- arguments:
- $projectDir: '%kernel.project_dir%'
-
- App\Controller\TestController:
- arguments:
- $projectDir: '%kernel.project_dir%'
-
App\Domain\Conversion\Infrastructure\Service\ConversionService:
arguments:
$projectDir: '%kernel.project_dir%'
- App\Service\CbrToCbzConverter:
- arguments:
- $projectDir: '%kernel.project_dir%'
-
- App\Manager\FileSystemManager:
- arguments:
- $projectDir: '%kernel.project_dir%'
-
- App\EventSubscriber\QueueStatusSubscriber:
- tags:
- - { name: kernel.event_subscriber }
-
- App\Client\MangadexClient:
- arguments:
- $httpClient: '@GuzzleHttp\Client'
- $clientId: '%env(MANGADEX_CLIENT_ID)%'
- $clientSecret: '%env(MANGADEX_CLIENT_SECRET)%'
- $username: '%env(MANGADEX_USERNAME)%'
- $password: '%env(MANGADEX_PASSWORD)%'
-
- App\Service\MangadexProvider:
- arguments:
- $client: '@App\Client\MangadexClient'
-
- # Scraper Service
- App\Service\Scraper\HtmlScraper:
- tags: [ 'app.scraper' ]
-
- App\Service\Scraper\JavascriptScraper:
- tags: [ 'app.scraper' ]
-
- App\Service\Scraper\MangadexScraper:
- tags: [ 'app.scraper' ]
-
- # Scraper Factory
- App\Service\Scraper\ScraperFactory:
- arguments:
- $scrapers: !tagged_iterator app.scraper
-
- # Manga Scraper Service
- App\Service\Scraper\MangaScraperService:
- arguments:
- $scraperFactory: '@App\Service\Scraper\ScraperFactory'
-
- # New Scrapers Factory for Domain Layer
+ # Scrapers Factory for Domain Layer
App\Domain\Scraping\Infrastructure\Service\ScraperFactory:
arguments:
$projectDir: '%kernel.project_dir%'
@@ -187,20 +131,6 @@ services:
App\Domain\Scraping\Domain\Contract\Repository\ContentSourceHealthRepositoryInterface:
alias: App\Domain\Setting\Infrastructure\Persistence\Repository\DoctrineContentSourceForHealthCheckRepository
- # Import Domain Services
- App\Domain\Import\Infrastructure\Service\FilenameAnalyzer: ~
-
- App\Domain\Import\Domain\Service\FilenameAnalyzerInterface:
- alias: App\Domain\Import\Infrastructure\Service\FilenameAnalyzer
-
- # Import Domain Query/Command Handlers
- App\Domain\Import\Application\QueryHandler\AnalyzeFilenameQueryHandler: ~
- App\Domain\Import\Application\CommandHandler\ImportFileCommandHandler: ~
-
- # Import Domain API Platform Services
- App\Domain\Import\Infrastructure\ApiPlatform\State\Processor\AnalyzeFilenameStateProcessor: ~
- App\Domain\Import\Infrastructure\ApiPlatform\State\Processor\ImportFileStateProcessor: ~
-
# System Domain
App\Domain\System\Domain\Contract\Repository\SystemStatusRepositoryInterface:
alias: App\Domain\System\Infrastructure\Persistence\Repository\DoctrineSystemStatusRepository
diff --git a/src/ApiResource/.gitignore b/src/ApiResource/.gitignore
deleted file mode 100644
index e69de29..0000000
diff --git a/src/Client/MangadexClient.php b/src/Client/MangadexClient.php
deleted file mode 100644
index f830a7f..0000000
--- a/src/Client/MangadexClient.php
+++ /dev/null
@@ -1,86 +0,0 @@
-httpClient = $httpClient;
- $this->clientId = $clientId;
- $this->clientSecret = $clientSecret;
- $this->username = $username;
- $this->password = $password;
- $this->authenticate();
- }
-
- public function authenticate(): void
- {
- $response = $this->httpClient->request('POST', self::AUTHENTICATION_URL, [
- 'form_params' => [
- 'grant_type' => 'password',
- 'username' => $this->username,
- 'password' => $this->password,
- 'client_id' => $this->clientId,
- 'client_secret' => $this->clientSecret,
- ],
- ]);
-
- $data = json_decode($response->getBody()->getContents(), true);
- $this->accessToken = $data['access_token'];
- $this->refreshToken = $data['refresh_token'];
- }
-
- public function refresh(): void
- {
- $response = $this->httpClient->request('POST', self::AUTHENTICATION_URL, [
- 'form_params' => [
- 'grant_type' => 'refresh_token',
- 'refresh_token' => $this->refreshToken,
- 'client_id' => $this->clientId,
- 'client_secret' => $this->clientSecret,
- ],
- ]);
-
- $data = json_decode($response->getBody()->getContents(), true);
- $this->accessToken = $data['access_token'];
- }
-
- private function request(string $method, string $endpoint, array $options = []): array
- {
- $options['headers']['Authorization'] = 'Bearer ' . $this->accessToken;
-
- $response = $this->httpClient->request($method, self::API_URL . $endpoint, $options);
-
- if ($response->getStatusCode() === 429) {
- $this->refresh();
- $options['headers']['Authorization'] = 'Bearer ' . $this->accessToken;
- $response = $this->httpClient->request($method, self::API_URL . $endpoint, $options);
- }
-
- return json_decode($response->getBody()->getContents(), true);
- }
-
- public function get(string $endpoint, array $params = []): array
- {
- return $this->request('GET', $endpoint, ['query' => $params]);
- }
-
- public function post(string $endpoint, array $data): array
- {
- return $this->request('POST', $endpoint, ['json' => $data]);
- }
-}
diff --git a/src/Controller/ActivityController.php b/src/Controller/ActivityController.php
deleted file mode 100644
index 2cfd630..0000000
--- a/src/Controller/ActivityController.php
+++ /dev/null
@@ -1,120 +0,0 @@
-getQueueStatus();
- $decodedPending = $this->decodeMessages($queueStatus['pending']);
- $decodedProcessing = $this->decodeMessages($queueStatus['processing']);
-
- $status = array_merge(
- $this->buildStatusActivity($decodedPending),
- $this->buildStatusActivity($decodedProcessing)
- );
-
- return $this->render('activity/index.html.twig', [
- 'controller_name' => 'ActivityController',
- 'status' => $status,
- 'toolbar' => $this->toolbarFactory->createToolbar('activity')->getGroups(),
- ]);
- }
-
- #[Route('/activity/status', name: 'app_activity_status', methods: ['GET'])]
- public function getStatus(): JsonResponse
- {
- $queueStatus = $this->getQueueStatus();
- $decodedPending = $this->decodeMessages($queueStatus['pending']);
- $decodedProcessing = $this->decodeMessages($queueStatus['processing']);
- $status = array_merge(
- $this->buildStatusActivity($decodedPending),
- $this->buildStatusActivity($decodedProcessing)
- );
-
- return new JsonResponse($status);
- }
-
- // TODO refactorer ce code avec celui du QueueStatusSubscriber
- private function getQueueStatus(): array
- {
- // Requête pour récupérer les messages en attente
- $sqlPending = 'SELECT * FROM messenger_messages WHERE queue_name = :queue AND available_at IS NULL';
- $pending = $this->connection->fetchAllAssociative($sqlPending, ['queue' => 'default']);
-
- // Requête pour récupérer les messages en cours de traitement
- $sqlProcessing = 'SELECT * FROM messenger_messages WHERE queue_name = :queue AND available_at IS NOT NULL';
- $processing = $this->connection->fetchAllAssociative($sqlProcessing, ['queue' => 'default']);
-
- return [
- 'pending' => $pending,
- 'processing' => $processing
- ];
- }
-
- private function buildStatusActivity(array $activity): array
- {
- $status = [];
- foreach ($activity as $envelope) {
- $envelope = $envelope['body'];
- if ($envelope instanceof Envelope) {
- if (!$envelope->getMessage() instanceof DownloadChapter) {
- continue;
- }
-
- $chapter = $this->chapterRepository->find($envelope->getMessage()->getChapterId());
- $manga = $chapter->getManga();
- $status[] = [
- 'manga' => $manga->getTitle(),
- 'volume' => $chapter->getVolume(),
- 'chapter' => $chapter->getNumber(),
- 'chapterId' => $chapter->getId(),
- 'title' => $chapter->getTitle(),
- ];
- }
- }
-
- return $status;
- }
-
- private function decodeMessages(array $messages): array
- {
- $decodedMessages = [];
-
- foreach ($messages as $message) {
- $decodedMessages[] = [
- 'id' => $message['id'],
- 'body' => $this->decodeMessageBody($message['body']),
- 'headers' => json_decode($message['headers'], true),
- ];
- }
-
- return $decodedMessages;
- }
-
- private function decodeMessageBody(string $body)
- {
- return unserialize(stripcslashes($body));
- }
-}
diff --git a/src/Controller/CalendarController.php b/src/Controller/CalendarController.php
deleted file mode 100644
index 2e2ca99..0000000
--- a/src/Controller/CalendarController.php
+++ /dev/null
@@ -1,18 +0,0 @@
-render('calendar/index.html.twig', [
- 'controller_name' => 'CalendarController',
- ]);
- }
-}
diff --git a/src/Controller/ConversionController.php b/src/Controller/ConversionController.php
deleted file mode 100644
index bd04d72..0000000
--- a/src/Controller/ConversionController.php
+++ /dev/null
@@ -1,64 +0,0 @@
-isMethod('POST')) {
- /** @var UploadedFile $file */
- $file = $request->files->get('file');
-
- if ($file && $file->getClientOriginalExtension() === 'cbr') {
- $originalFileName = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME);
- $tempFilePath = $file->getPathname();
-
- try {
- $cbzPath = $this->cbrToCbzConverter->convert($tempFilePath);
-
- $response = new BinaryFileResponse($cbzPath);
- $response->setContentDisposition(
- ResponseHeaderBag::DISPOSITION_ATTACHMENT,
- $originalFileName . '.cbz'
- );
- $response->headers->set('Content-Type', 'application/x-cbz');
- $response->headers->set('Turbo-Visit-Control', 'reload');
-
- $response->deleteFileAfterSend(true);
-
- return $response;
- } catch (\Exception $e) {
- $this->notificationService->sendUpdate([
- 'status' => 'error',
- 'message' => 'Une erreur est survenue lors de la conversion : ' . $e->getMessage()
- ]);
- }
- } else {
- $this->notificationService->sendUpdate([
- 'status' => 'error',
- 'message' => 'Veuillez sélectionner un fichier CBR valide.'
- ]);
- }
- }
-
- return $this->render('conversion/index.html.twig');
- }
-}
diff --git a/src/Controller/ImportController.php b/src/Controller/ImportController.php
deleted file mode 100644
index 6c0d1d9..0000000
--- a/src/Controller/ImportController.php
+++ /dev/null
@@ -1,220 +0,0 @@
-isMethod('POST')) {
- $files = $request->files->get('files');
- if ($files) {
- $importFiles = [];
- foreach ($files as $file) {
- if ($file && in_array($file->getClientOriginalExtension(), ['cbz', 'cbr'])) {
- $originalFileName = $file->getClientOriginalName();
-
- try {
- $tmpPath = $this->fileSystemManager->moveUploadedFile(
- $file->getPathname(),
- $this->fileSystemManager->getUploadsDirectory(),
- $file->getClientOriginalName()
- );
- $importFiles[] = [
- 'id' => uniqid(),
- 'path' => $tmpPath,
- 'original_name' => $originalFileName,
- ];
- } catch (FileException $e) {
- $this->notificationService->sendUpdate([
- 'status' => 'error',
- 'message' => 'Une erreur est survenue lors de l\'import du fichier ' . $originalFileName,
- ]);
- }
- } else {
- $this->notificationService->sendUpdate([
- 'status' => 'error',
- 'message' => 'Le fichier ' . $file->getClientOriginalName() . ' doit être au format CBZ ou CBR.',
- ]);
- }
- }
-
- if (!empty($importFiles)) {
- $session->set('import_files', $importFiles);
- return $this->redirectToRoute('import_match');
- }
- } else {
- $this->notificationService->sendUpdate([
- 'status' => 'error',
- 'message' => 'Aucun fichier n\'a été sélectionné.',
- ]);
- }
- }
-
- return $this->render('import/index.html.twig');
- }
-
- /**
- * @throws Exception
- */
- #[Route('/import/match', name: 'import_match')]
- public function match(SessionInterface $session): Response
- {
- $files = $session->get('import_files', []);
- if (empty($files)) {
- return $this->redirectToRoute('app_manga_import');
- }
-
- $processedFiles = [];
- foreach ($files as $fileId => $fileInfo) {
- $filePath = $fileInfo['path'];
- $originalFileName = $fileInfo['original_name'];
-
- $fileExtension = pathinfo($filePath, PATHINFO_EXTENSION);
- if (strtolower($fileExtension) === 'cbr') {
- $cbzPath = $this->cbrToCbzConverter->convert($filePath);
- $filePath = $cbzPath;
- $originalFileName = pathinfo($originalFileName, PATHINFO_FILENAME) . '.cbz';
- $files[$fileId]['path'] = $filePath;
- $files[$fileId]['original_name'] = $originalFileName;
- }
-
- $metadata = $this->cbzService->extractMetadata($filePath, $originalFileName);
- $mangas = $this->mangaRepository->findBySlug($metadata['title']);
-
- $mangaOptions = [];
- foreach ($mangas as $manga) {
- $mangaOptions[] = [
- 'slug' => $manga->getSlug(),
- 'title' => $manga->getTitle(),
- 'author' => $manga->getAuthor(),
- 'publicationYear' => $manga->getPublicationYear(),
- 'genres' => $manga->getGenres(),
- 'description' => $manga->getDescription()
- ];
- }
-
- $processedFiles[] = [
- 'id' => $fileId,
- 'originalFileName' => $originalFileName,
- 'fileSize' => $this->formatBytes(filesize($filePath)),
- 'metadata' => $metadata,
- 'mangaOptions' => $mangaOptions
- ];
- }
-
- $session->set('import_files', $files);
-
- return $this->render('import/match.html.twig', [
- 'files' => $processedFiles
- ]);
- }
-
- private function formatBytes($bytes, $precision = 2)
- {
- $units = ['B', 'KB', 'MB', 'GB', 'TB'];
- $bytes = max($bytes, 0);
- $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
- $pow = min($pow, count($units) - 1);
- $bytes /= (1 << (10 * $pow));
- return round($bytes, $precision) . ' ' . $units[$pow];
- }
-
- #[Route('/import/confirm', name: 'import_confirm', methods: ['POST'])]
- public function confirm(Request $request, SessionInterface $session): Response
- {
- $files = $session->get('import_files', []);
- $selectedFiles = $request->request->all('selected');
- $mangaSlugs = $request->request->all('manga_slug');
- $volumes = $request->request->all('volume');
- $chapters = $request->request->all('chapter');
-
- $importedFiles = [];
- $errors = [];
-
- foreach ($selectedFiles as $fileId) {
- if (!isset($files[$fileId])) {
- continue;
- }
-
- $file = $files[$fileId];
- $mangaSlug = $mangaSlugs[$fileId] ?? null;
- $volume = $volumes[$fileId] ?? null;
- $chapter = $chapters[$fileId] ?? null;
-
- try {
- $manga = $this->mangaRepository->findOneBy(['slug' => $mangaSlug]);
- if (!$manga) {
- throw new \Exception('Manga non trouvé.');
- }
-
- if (!is_null($chapter)) {
- $chapter = $manga->getChapterByNumber($chapter);
- if (!$chapter) {
- throw new \Exception('Chapitre non trouvé.');
- }
- }
-
- $importedFiles[] = $file['original_name'];
- $this->mangaImportService->importFile($manga, $volume, $chapter, $file['path']);
- } catch (\Exception $e) {
- $errors[] = "Erreur lors de l'import de {$file['original_name']} : " . $e->getMessage();
- }
- }
-
- // Nettoyer les fichiers temporaires non importés
- foreach ($files as $file) {
- $this->fileSystemManager->deleteFile($file['path']);
- }
-
- // Nettoyer la session
- $session->remove('import_files');
-
- // Préparer le message de notification
- if (!empty($importedFiles)) {
- $successMessage = 'Fichiers importés avec succès : ' . implode(', ', $importedFiles);
- $this->notificationService->sendUpdate([
- 'status' => 'success',
- 'message' => $successMessage
- ]);
- }
-
- if (!empty($errors)) {
- $errorMessage = implode("\n", $errors);
- $this->notificationService->sendUpdate([
- 'status' => 'error',
- 'message' => $errorMessage
- ]);
- }
-
- return $this->redirectToRoute('app_manga');
- }
-}
diff --git a/src/Controller/MangaController.php b/src/Controller/MangaController.php
deleted file mode 100644
index 492845a..0000000
--- a/src/Controller/MangaController.php
+++ /dev/null
@@ -1,475 +0,0 @@
-imageManager = new ImageManager(new Driver());
- }
-
- #[Route('/legacy', name: 'app_legacy')]
- public function index(Request $request): Response
- {
- $sort = $request->query->get('sort', 'title');
- $order = $request->query->get('order', 'asc');
- $status = $request->query->get('status', 'all');
- $view = $request->query->get('view', 'poster');
-
- $mangas = $this->mangaRepository->findAllSortedAndFiltered($sort, $order, $status);
-
- return $this->render('manga/index.html.twig', [
- 'mangas' => $mangas,
- 'toolbar' => $this->toolbarFactory->createToolbar('manga_list')->getGroups(),
- 'currentStatus' => $status,
- 'currentView' => $view,
- ]);
- }
-
- #[Route('/manga/chapters/{mangaSlug}', name: 'app_manga_show')]
- public function showChapters(string $mangaSlug, Request $request): Response
- {
- // $manga = $this->mangaRepository->findOneWithChapterBy(['slug' => $mangaSlug]);
- $manga = $this->mangaRepository->findOneBy(['slug' => $mangaSlug]);
-
- if (!$manga) {
- throw new NotFoundHttpException("Le manga demandé n'existe pas.");
- }
-
- $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(),
- 'form' => $form->createView(),
- 'contentSources' => $contentSources,
- ]);
- }
-
- #[Route('/manga/delete/{id}', name: 'app_manga_delete', methods: ['DELETE'])]
- public function deleteManga(Manga $manga): JsonResponse
- {
- try {
- foreach ($manga->getChapters() as $chapter) {
- file_exists($chapter->getCbzPath()) ?? unlink($chapter->getCbzPath());
- $this->entityManager->remove($chapter);
- }
- $this->entityManager->remove($manga);
- $this->entityManager->flush();
-
- return new JsonResponse(['success' => true]);
- } catch (\Exception $e) {
- return new JsonResponse(['success' => false, 'error' => 'Unable to delete manga.'], 500);
- }
- }
-
- #[Route('/manga/{id}/edit', name: 'app_manga_edit', methods: ['POST'])]
- public function edit(Request $request, Manga $manga, EntityManagerInterface $entityManager): JsonResponse|Response
- {
- $form = $this->createForm(MangaEditType::class, $manga);
- $form->handleRequest($request);
-
- if ($form->isSubmitted() && $form->isValid()) {
- $entityManager->flush();
-
- return $this->redirectToRoute('app_manga_show', ['mangaSlug' => $manga->getSlug()]);
- }
-
- $errors = [];
- foreach ($form->getErrors(true) as $error) {
- $errors[] = $error->getMessage();
- }
-
- 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);
- $chaptersByVolume = [];
- foreach ($manga->getChapters() as $chapter) {
- $volume = $chapter->getVolume() ?? 'Not Found';
- $chaptersByVolume[$volume][] = $chapter;
- }
-
- foreach ($chaptersByVolume as $volume => &$chapters) {
- usort($chapters, function ($a, $b) {
- return $b->getNumber() <=> $a->getNumber();
- });
- }
- unset($chapters);
-
- uksort($chaptersByVolume, function ($a, $b) {
- if ($a == 0) {
- return -1;
- }
- if ($b == 0) {
- return 1;
- }
- return $b <=> $a;
- });
-
- return $this->render('manga/_chapter_list.html.twig', [
- 'manga' => $manga,
- 'chapters_by_volume' => $chaptersByVolume
- ]);
- }
-
- #[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('/chapter/{id}/edit', name: 'app_chapter_edit', methods: ['POST'])]
- public function editChapter(Request $request, Chapter $chapter): JsonResponse
- {
- $data = json_decode($request->getContent(), true);
-
- $chapter->setNumber($data['number']);
- $chapter->setTitle($data['title']);
-
- $this->entityManager->flush();
-
- return new JsonResponse(['success' => true, 'message' => 'Chapter updated successfully']);
- }
-
- #[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/search/{query}', name: 'app_manga_search')]
- public function search(string $query = ''): Response
- {
- return $this->render('manga/add_new.html.twig', [
- 'query' => $query,
- ]);
- }
-
-
- /**
- * @throws GuzzleException
- */
- #[Route('/addManga', name: 'app_manga_add')]
- public function addManga(Request $request): Response
- {
- $manga = $this->mangaRepository->findOneBy(['slug' => $request->request->get('slug')]);
- if ($manga) {
- return $this->redirectToRoute('app_manga_show', ['mangaSlug' => $manga->getSlug()]);
- }
-
- $manga = new Manga();
- $manga->setTitle($request->request->get('title'))
- ->setSlug($request->request->get('slug'))
- ->setDescription($request->request->get('description'))
- ->setStatus($request->request->get('status'))
- ->setGenres(explode(',', $request->request->get('genres')))
- ->setAuthor($request->request->get('author'))
- ->setPublicationYear($request->request->get('publicationYear'))
- ->setRating($request->request->get('rating'))
- ->setExternalId($request->request->get('externalId'))
- ->setMonitored(false);
-
- // Traitement de l'image
- $imageUrl = $request->request->get('imageUrl');
- try {
- $imageUrls = $this->processAndSaveImage($imageUrl);
- $manga->setImageUrl($imageUrls['full']);
- $manga->setThumbnailUrl($imageUrls['thumbnail']);
- } catch (\Exception|GuzzleException $e) {
- throw $e;
- }
-
- $mergedChapters = $this->mangadexProvider->addAllChaptersToManga($manga);
-
- if (empty($mergedChapters)) {
- return $this->redirectToRoute('app_manga_search', ['query' => $manga->getTitle()]);
- }
-
- try {
- foreach ($manga->getChapters() as $chapter) {
- $this->entityManager->persist($chapter);
- }
-
- $this->entityManager->persist($manga);
- $this->entityManager->flush();
- } catch (\Exception $e) {
- if ($e instanceof UniqueConstraintViolationException) {
- return $this->redirectToRoute('app_manga_show', ['mangaSlug' => $manga->getSlug()]);
- }
- throw $e;
- }
-
- return $this->redirectToRoute('app_manga_show', ['mangaSlug' => $manga->getSlug()]);
- }
-
- /**
- * @throws GuzzleException
- */
- private function processAndSaveImage(string $imageUrl): array
- {
- $client = new Client();
- $response = $client->get($imageUrl);
- $tempImage = tmpfile();
- fwrite($tempImage, $response->getBody()->getContents());
- $tempImagePath = stream_get_meta_data($tempImage)['uri'];
-
- // Générer un nom de fichier unique
- $originalFilename = pathinfo($imageUrl, PATHINFO_FILENAME);
- $newFilename = $this->fileSystemManager->generateUniqueImageFilename($imageUrl);
-
- try {
- // Créer et sauvegarder la miniature
- $thumbnail = $this->imageManager->read($tempImagePath);
- $thumbnail->cover(300, 440);
- $thumbnail->save($this->fileSystemManager->getImagePath('thumbnails') . '/' . $newFilename, quality: 85);
-
- // Sauvegarder l'image en taille réelle
- $fullImage = $this->imageManager->read($tempImagePath);
- $fullImage->save($this->fileSystemManager->getImagePath('full') . '/' . $newFilename, quality: 90);
-
- // Fermer et supprimer le fichier temporaire
- fclose($tempImage);
-
- return [
- 'full' => '/images/full/' . $newFilename,
- 'thumbnail' => '/images/thumbnails/' . $newFilename
- ];
-
- } catch (FileException $e) {
- // Fermer le fichier temporaire en cas d'erreur
- fclose($tempImage);
- throw $e;
- }
- }
-
- #[Route('/searchChapter/{id}', name: 'search_chapter')]
- public function addChapterMessenger(int $id): JsonResponse
- {
- $chapter = $this->chapterRepository->find($id);
- if (!$chapter) {
- return new JsonResponse(['error' => 'Chapter Not Found.'], 400);
- } elseif ($chapter->getCbzPath() !== null) {
- return new JsonResponse(['error' => 'Chapter already scraped.'], 400);
- }
-
- $this->bus->dispatch(new DownloadChapter($id));
-
- return new JsonResponse(['success' => 'Scrapping started...'], 200);
- }
-
- #[Route('/searchVolume/{mangaSlug}/{volume}', name: 'search_volume')]
- public function searchVolume(string $mangaSlug, int $volume): JsonResponse
- {
- $manga = $this->mangaRepository->findOneBy(['slug' => $mangaSlug]);
- if (!$manga) {
- return new JsonResponse(['error' => 'Manga Not Found.'], 400);
- }
-
- $volumeChapters = $this->chapterRepository->findBy([
- 'manga' => $manga,
- 'volume' => $volume,
- 'visible' => true
- ]);
-
- if (empty($volumeChapters)) {
- $this->notificationService->sendUpdate(['status' => 'error', 'message' => 'No chapters found for this volume.']);
- return new JsonResponse(['error' => 'No chapters found for this volume.'], 200);
- }
-
- foreach ($volumeChapters as $chapter) {
- if ($chapter->getCbzPath() === null) {
- $this->bus->dispatch(new DownloadChapter($chapter->getId()));
- }
- }
-
- return new JsonResponse(['success' => 'Scrapping started...'], 200);
- }
-
- #[Route('/download-cbz/{chapterId}', name: 'download_cbz')]
- public function downloadChapter(int $chapterId): BinaryFileResponse|JsonResponse
- {
- $chapter = $this->chapterRepository->find($chapterId);
- if (!$chapter) {
- $this->notificationService->sendUpdate(['status' => 'error', 'message' => 'Chapitre non trouvé.']);
- return new JsonResponse(['error' => 'Chapitre non trouvé.'], 200);
- }
-
- $cbzPath = $chapter->getCbzPath();
- if (!$cbzPath || !file_exists($cbzPath)) {
- $this->notificationService->sendUpdate(['status' => 'error', 'message' => 'Le fichier CBZ n\'existe pas.']);
- return new JsonResponse(['error' => 'Le fichier CBZ n\'existe pas.'], 200);
- }
-
- $isFullVolume = $this->isFullVolume($chapter);
- $fileName = $isFullVolume
- ? $this->cbzService->generateFileName($chapter->getManga(), $chapter->getVolume())
- : $this->cbzService->generateFileName($chapter->getManga(), null, $chapter->getNumber());
-
- return $this->cbzService->createBinaryFileResponse($cbzPath, $fileName);
- }
-
- #[Route('/download-volume/{mangaSlug}/{volume}', name: 'download_volume')]
- public function downloadVolume(string $mangaSlug, int $volume): BinaryFileResponse|JsonResponse
- {
- $manga = $this->mangaRepository->findOneBy(['slug' => $mangaSlug]);
-
- $volumeChapters = $this->chapterRepository->findBy([
- 'manga' => $manga,
- 'volume' => $volume,
- 'visible' => true
- ], ['number' => 'ASC']);
-
- if (empty($volumeChapters)) {
- $this->notificationService->sendUpdate(['status' => 'error', 'message' => 'Aucun chapitre trouvé pour ce volume.']);
- }
-
- if (!$this->cbzService->doAllChaptersHaveCbz($volumeChapters)) {
- $this->notificationService->sendUpdate(['status' => 'error', 'message' => 'Tous les chapitres du volume ne sont pas scrapés.']);
- return new JsonResponse(['error' => 'Tous les chapitres du volume ne sont pas scrapés.'], 200);
- }
-
- $fileName = $this->cbzService->generateFileName($manga, $volume);
-
- if ($this->cbzService->areAllChaptersCbzIdentical($volumeChapters)) {
- return $this->cbzService->createBinaryFileResponse($volumeChapters[0]->getCbzPath(), $fileName);
- } else {
- $tempFile = $this->cbzService->createVolumeArchive($volumeChapters);
- $response = $this->cbzService->createBinaryFileResponse($tempFile, $fileName);
- $response->deleteFileAfterSend(true);
- return $response;
- }
- }
-
- #[Route('/refresh_metadata', name: 'refresh_metadata')]
- public function refreshMetadata(Request $request): JsonResponse
- {
- $mangaId = json_decode($request->getContent(), true)['mangaId'];
- $manga = $this->mangaRepository->find($mangaId);
- if (!$manga) {
- return new JsonResponse(['error' => 'Manga Not Found.'], 400);
- }
- $this->bus->dispatch(new RefreshMetadata($mangaId));
-
- return new JsonResponse(['success' => 'Metadata refresh started...'], 200);
- }
-
- #[Route('/toggle_monitored', name: 'toggle_monitored')]
- public function toogleMonitored(Request $request): JsonResponse
- {
- $id = json_decode($request->getContent(), true)['mangaId'];
- $manga = $this->mangaRepository->find($id);
- if (!$manga) {
- return new JsonResponse(['error' => 'Manga Not Found.'], 400);
- }
-
- $manga->setMonitored(!$manga->isMonitored());
- $this->entityManager->persist($manga);
- $this->entityManager->flush();
-
- return new JsonResponse(['success' => 'Monitored status updated.', 'isMonitored' => $manga->isMonitored()], 200);
- }
-
- private function isFullVolume(Chapter $chapter): bool
- {
- $volumeChapters = $this->chapterRepository->findBy([
- 'manga' => $chapter->getManga(),
- 'volume' => $chapter->getVolume()
- ]);
-
- $firstChapterPath = $volumeChapters[0]->getCbzPath();
- foreach ($volumeChapters as $volumeChapter) {
- if ($volumeChapter->getCbzPath() !== $firstChapterPath) {
- return false;
- }
- }
-
- return true;
- }
-}
diff --git a/src/Controller/ReaderController.php b/src/Controller/ReaderController.php
deleted file mode 100644
index f7e4945..0000000
--- a/src/Controller/ReaderController.php
+++ /dev/null
@@ -1,138 +0,0 @@
-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())) {
- $this->notificationService->sendUpdate([
- 'status' => 'error',
- 'message' => 'Le chapitre demandé n\'est pas encore disponible.',
- ]);
- return $this->redirectToRoute('app_manga_show', ['mangaSlug' => $mangaSlug]);
- }
-
- $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.");
- }
-
- $chapters = $manga->getChapters()
- ->filter(fn ($chapter) => $chapter->isVisible() && $chapter->getNumber() < $currentChapterNumber)
- ->toArray();
-
- usort($chapters, fn ($a, $b) => $b->getNumber() <=> $a->getNumber());
-
- $previousChapter = reset($chapters) ?: null;
-
- 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);
- }
-}
diff --git a/src/Controller/SettingsController.php b/src/Controller/SettingsController.php
deleted file mode 100644
index 6b1f100..0000000
--- a/src/Controller/SettingsController.php
+++ /dev/null
@@ -1,203 +0,0 @@
-render('settings/index.html.twig', [
- 'controller_name' => 'SettingsController',
- ]);
- }
-
- #[Route('/settings/general', name: 'app_settings_general')]
- public function general(): Response
- {
- return $this->render('settings/index.html.twig', [
- 'controller_name' => 'SettingsController',
- ]);
- }
-
- #[Route('/settings/folders', name: 'app_settings_folders')]
- public function folders(Request $request, AppSettingsManager $settingsManager): Response
- {
- $currentSettings = $settingsManager->getSettings();
-
- $form = $this->createForm(AppSettingsType::class, $currentSettings);
- $form->handleRequest($request);
-
- if ($form->isSubmitted() && $form->isValid()) {
- $newSettings = $form->getData();
- $settingsManager->updateSettings($newSettings);
-
- $this->notificationService->sendUpdate(['status' => 'success', 'message' => 'Settings updated successfully.']);
- return $this->json(['success' => true]);
- }
-
- return $this->render('settings/folders.html.twig', [
- 'form' => $form->createView(),
- ]);
- }
-
- #[Route('/settings/scrappers/list', name: 'app_settings_scrappers_list')]
- public function list(ContentSourceRepository $repository, ToolbarFactory $toolbarFactory): Response
- {
- $contentSources = $repository->findAll();
-
- return $this->render('settings/scrapper_list.html.twig', [
- 'contentSources' => $contentSources,
- 'toolbar' => $toolbarFactory->createToolbar('scraper_list')->getGroups(),
- ]);
- }
-
- #[Route('/settings/scrappers/{id}', name: 'app_settings_scrappers', defaults: ['id' => null])]
- public function scrappers(Request $request, ?ContentSource $contentSource): Response
- {
- $isNew = $contentSource === null;
- $contentSource = $contentSource ?? new ContentSource();
-
- $form = $this->createForm(ContentSourceType::class, $contentSource);
- $form->handleRequest($request);
-
- if ($form->isSubmitted() && $form->isValid()) {
- $this->entityManager->persist($contentSource);
- $this->entityManager->flush();
- $this->notificationService->sendUpdate(['status' => 'success', 'message' => ($isNew ? 'New scrapper configuration saved' : 'Scrapper configuration updated') . ' successfully.']);
- return $this->redirectToRoute('app_settings_scrappers_list');
- }
-
- return $this->render('settings/scrappers.html.twig', [
- 'form' => $form->createView(),
- 'isNew' => $isNew,
- ]);
- }
-
- /**
- * @throws GuzzleException
- */
- #[Route('/settings/scrappers_test', name: 'app_settings_scrappers_test', methods: ['POST'])]
- public function scrapperTest(Request $request): JsonResponse
- {
- $contentSource = new ContentSource();
- $form = $this->createForm(ContentSourceType::class, $contentSource);
- $form->submit($request->request->all()['content_source']);
-
- if ($form->isValid()) {
- $mangaSlug = $request->request->get('mangaSlug');
- $chapterNumber = $request->request->get('chapterNumber');
-
- try {
- $scrapedData = $this->mangaScraperService->testScraping($mangaSlug, $chapterNumber, $contentSource);
- } catch (\Exception $e) {
- $this->notificationService->sendUpdate(['status' => 'error', 'message' => $e->getMessage()]);
- return new JsonResponse([
- 'success' => false,
- 'message' => $e->getMessage(),
- ]);
- }
-
- return new JsonResponse([
- 'success' => true,
- 'message' => 'Test successful',
- 'data' => $scrapedData
- ]);
- } else {
- return new JsonResponse([
- 'success' => false,
- 'message' => 'Invalid form submission',
- 'errors' => $this->getFormErrors($form)
- ]);
- }
- }
-
- private function getFormErrors($form): array
- {
- $errors = [];
- foreach ($form->getErrors(true) as $error) {
- $errors[] = $error->getMessage();
- }
- return $errors;
- }
-
- #[Route('/settings/ui', name: 'app_settings_ui')]
- public function ui(): Response
- {
- return $this->render('settings/index.html.twig', [
- 'controller_name' => 'SettingsController',
- ]);
- }
-
- #[Route('/settings/export_scrappers', name: 'app_settings_scrappers_export', methods: ['GET'])]
- public function exportScrappers(): JsonResponse
- {
- $contentSources = $this->contentSourceRepository->findAll();
- $data = [];
-
- foreach ($contentSources as $source) {
- $data[] = [
- 'baseUrl' => $source->getBaseUrl(),
- 'imageSelector' => $source->getImageSelector(),
- 'nextPageSelector' => $source->getNextPageSelector(),
- 'chapterUrlFormat' => $source->getChapterUrlFormat(),
- 'scrapingType' => $source->getScrapingType(),
- 'chapterSelector' => $source->getChapterSelector(), //TODO à renommer en chapterListSelector
- ];
- }
-
- return new JsonResponse($data);
- }
-
- #[Route('/settings/import_scrappers', name: 'app_settings_scrappers_import', methods: ['POST'])]
- public function importScrappers(Request $request): JsonResponse
- {
- $content = $request->getContent();
- $data = json_decode($content, true);
-
- if (json_last_error() !== JSON_ERROR_NONE) {
- $this->notificationService->sendUpdate(['status' => 'error', 'message' => 'Invalid JSON data']);
- return new JsonResponse(['error' => 'Invalid JSON data'], 400);
- }
-
- foreach ($data as $sourceData) {
- $contentSource = new ContentSource();
- $contentSource->setBaseUrl($sourceData['baseUrl']);
- $contentSource->setImageSelector($sourceData['imageSelector']);
- $contentSource->setNextPageSelector($sourceData['nextPageSelector']);
- $contentSource->setChapterUrlFormat($sourceData['chapterUrlFormat']);
- $contentSource->setScrapingType($sourceData['scrapingType']);
-
- $this->entityManager->persist($contentSource);
- }
-
- $this->entityManager->flush();
-
- return new JsonResponse(['message' => 'Content sources imported successfully']);
- }
-}
diff --git a/src/Controller/SystemController.php b/src/Controller/SystemController.php
deleted file mode 100644
index c2b4fa8..0000000
--- a/src/Controller/SystemController.php
+++ /dev/null
@@ -1,50 +0,0 @@
-render('system/index.html.twig', [
- 'controller_name' => 'SystemController',
- ]);
- }
-
- #[Route('/system/status', name: 'app_system_status')]
- public function status(): Response
- {
- return $this->render('system/index.html.twig', [
- 'controller_name' => 'SettingsController',
- ]);
- }
-
- #[Route('/system/backup', name: 'app_system_backup')]
- public function backup(): Response
- {
- return $this->render('system/index.html.twig', [
- 'controller_name' => 'SettingsController',
- ]);
- }
-
- #[Route('/system/logs', name: 'app_system_logs')]
- public function logs(): Response
- {
- return $this->render('system/index.html.twig', [
- 'controller_name' => 'SettingsController',
- ]);
- }
-
- #[Route('/system/updates', name: 'app_system_updates')]
- public function update(): Response
- {
- return $this->render('system/index.html.twig', [
- 'controller_name' => 'SettingsController',
- ]);
- }
-}
diff --git a/src/Controller/TestController.php b/src/Controller/TestController.php
deleted file mode 100644
index af3fdef..0000000
--- a/src/Controller/TestController.php
+++ /dev/null
@@ -1,100 +0,0 @@
-imageManager = new ImageManager(new Driver());
- }
-
- #[Route('/test', name: 'test')]
- public function test(): Response
- {
- $mangas = $this->mangaRepository->findAll();
-
- $changed = 0;
- foreach ($mangas as $manga) {
- //si getImageUrl() retourne un lien sous la forme d'une URL (https ou http)
- if ($manga->getImageUrl()) {
- $imageUrls = $this->processAndSaveImage($manga->getImageUrl());
- $manga->setThumbnailUrl($imageUrls['thumbnail']);
- $this->mangaRepository->save($manga, true);
- $changed++;
- }
- }
-
-
- return new JsonResponse(['changed' => $changed]);
- }
-
- /**
- * @throws GuzzleException
- */
- private function processAndSaveImage(string $imageUrl): array
- {
- $image = file_get_contents($this->projectDir . '/public' .$imageUrl);
- $tempImage = tmpfile();
- fwrite($tempImage, $image);
- $tempImagePath = stream_get_meta_data($tempImage)['uri'];
-
- // Générer un nom de fichier unique
- $originalFilename = pathinfo($imageUrl, PATHINFO_FILENAME);
- $safeFilename = $this->slugger->slug($originalFilename);
- $newFilename = $safeFilename . '-' . uniqid() . '.' . 'jpg';
-
- try {
- // Créer et sauvegarder la miniature
- $thumbnail = $this->imageManager->read($tempImagePath);
- $thumbnail->cover(300, 440);
- $thumbnail->save($this->projectDir . '/public/images/thumbnails/' . $newFilename, quality: 85);
-
- // Sauvegarder l'image en taille réelle
- // $fullImage = $this->imageManager->read($tempImagePath);
- // $fullImage->save($this->projectDir . '/public/images/full/' . $newFilename, quality: 90);
-
- // Fermer et supprimer le fichier temporaire
- fclose($tempImage);
-
- return [
- 'full' => '/images/full/' . $newFilename,
- 'thumbnail' => '/images/thumbnails/' . $newFilename
- ];
-
- } catch (FileException $e) {
- // Fermer le fichier temporaire en cas d'erreur
- fclose($tempImage);
- throw $e;
- }
- }
-}
diff --git a/src/DataFixtures/AppFixtures.php b/src/DataFixtures/AppFixtures.php
deleted file mode 100644
index 115ae6d..0000000
--- a/src/DataFixtures/AppFixtures.php
+++ /dev/null
@@ -1,45 +0,0 @@
- UserFactory::random()
- ];
- });
-
- $mangas = MangaFactory::createMany(25);
-
- foreach ($mangas as $manga) {
-
- for ($i = 1; $i <= 5; $i++) {
- $manga->addChapter(ChapterFactory::createOne([
- 'manga' => $manga,
- 'number' => $i
- ])->object());
- }
-
- foreach ($manga->getChapters() as $chapter) {
- for ($i = 1; $i <= 5; $i++) {
- $chapter->addPagesLink(PageFactory::createOne([
- 'chapter' => $chapter,
- 'number' => $i
- ])->object());
- }
- }
- }
- }
-}
diff --git a/src/Entity/Chapter.php b/src/Entity/Chapter.php
index de4cfef..cfb1552 100644
--- a/src/Entity/Chapter.php
+++ b/src/Entity/Chapter.php
@@ -6,10 +6,8 @@ use App\Repository\ChapterRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
-use Symfony\UX\Turbo\Attribute\Broadcast;
#[ORM\Entity(repositoryClass: ChapterRepository::class)]
-#[Broadcast()]
class Chapter
{
#[ORM\Id]
diff --git a/src/Event/PageScrappingProgressEvent.php b/src/Event/PageScrappingProgressEvent.php
deleted file mode 100644
index bd2e9b7..0000000
--- a/src/Event/PageScrappingProgressEvent.php
+++ /dev/null
@@ -1,36 +0,0 @@
-chapterId = $chapterId;
- $this->pageIndex = $pageIndex;
- $this->totalPages = $totalPages;
- }
-
- public function getChapterId(): int
- {
- return $this->chapterId;
- }
-
- public function getPageIndex(): int
- {
- return $this->pageIndex;
- }
-
- public function getTotalPages(): int
- {
- return $this->totalPages;
- }
-}
diff --git a/src/EventListener/ExceptionListener.php b/src/EventListener/ExceptionListener.php
deleted file mode 100644
index 47cc9e2..0000000
--- a/src/EventListener/ExceptionListener.php
+++ /dev/null
@@ -1,49 +0,0 @@
-getThrowable();
- //
- // $response = match(true) {
- // $exception instanceof FilterValidationException,
- // $exception instanceof BadRequestException => $this->createResponse($exception, Response::HTTP_BAD_REQUEST),
- // $exception instanceof NotFoundHttpException,
- // $exception instanceof ItemNotFoundException => $this->createResponse($exception, Response::HTTP_NOT_FOUND),
- // $exception instanceof AccessDeniedHttpException => $this->createResponse($exception, Response::HTTP_FORBIDDEN),
- // $exception instanceof ValidationException,
- // $exception instanceof NotNormalizableValueException => $this->createResponse($exception, Response::HTTP_UNPROCESSABLE_ENTITY),
- // default => null,
- // };
- //
- // if ($response) {
- // $event->setResponse($response);
- // }else{
- // $this->logger->error($exception->getMessage(), ['exception' => $exception]);
- // }
- }
-
- private function createResponse(\Throwable $exception, int $statusCode): Response
- {
- $this->logger->info($exception->getMessage(), ['exception' => $exception]);
- return new Response(json_encode(['message' => $exception->getMessage()]), $statusCode, ['Content-Type' => 'application/json']);
- }
-}
diff --git a/src/EventSubscriber/QueueStatusSubscriber.php b/src/EventSubscriber/QueueStatusSubscriber.php
deleted file mode 100644
index a9e7264..0000000
--- a/src/EventSubscriber/QueueStatusSubscriber.php
+++ /dev/null
@@ -1,145 +0,0 @@
- 'onMessageReceived',
- WorkerMessageHandledEvent::class => 'onMessageHandled',
- WorkerMessageFailedEvent::class => 'onMessageFailed',
- PageScrappingProgressEvent::NAME => 'onPageScrapingProgress',
- ];
- }
-
- public function onMessageReceived(WorkerMessageReceivedEvent $event): void
- {
- $envelope = $event->getEnvelope();
- $message = $envelope->getMessage();
-
- if ($message instanceof DownloadChapter) {
- $this->activityService->sendUpdate($this->getActivity());
- }
- }
-
- public function onMessageHandled(WorkerMessageHandledEvent $event): void
- {
- $envelope = $event->getEnvelope();
- $message = $envelope->getMessage();
-
- if ($message instanceof DownloadChapter) {
- $this->activityService->sendUpdate($this->getActivity());
- }
- }
-
- public function onMessageFailed(WorkerMessageFailedEvent $event): void
- {
- $envelope = $event->getEnvelope();
- $message = $envelope->getMessage();
-
- if ($message instanceof DownloadChapter) {
- $this->activityService->sendUpdate($this->getActivity());
- }
- }
-
- public function onPageScrapingProgress(PageScrappingProgressEvent $event): void
- {
- $data = [
- 'status' => 'scrapping.progress',
- 'chapterId' => $event->getChapterId(),
- 'pageIndex' => $event->getPageIndex(),
- 'totalPages' => $event->getTotalPages(),
- ];
- $this->activityService->sendUpdate($data);
- }
-
- private function getActivity(): array
- {
- $queueStatus = $this->getQueueStatus();
- return [
- 'processing' => $this->buildStatusActivity($this->decodeMessages($queueStatus['processing'])),
- 'pending' => $this->buildStatusActivity($this->decodeMessages($queueStatus['pending']))
- ];
- }
-
- //TODO refactorer ce code avec celui du ActivityController
- private function buildStatusActivity(array $activity): array
- {
- $status = [];
- foreach ($activity as $envelope) {
- $envelope = $envelope['body'];
- if ($envelope instanceof Envelope) {
- if (!$envelope->getMessage() instanceof DownloadChapter) {
- continue;
- }
-
- $chapter = $this->chapterRepository->find($envelope->getMessage()->getChapterId());
- $manga = $chapter->getManga();
- $status[] = [
- 'manga' => $manga->getTitle(),
- 'volume' => $chapter->getVolume(),
- 'chapter' => $chapter->getNumber(),
- 'title' => $chapter->getTitle(),
- ];
- }
- }
-
- return $status;
- }
-
- private function getQueueStatus(): array
- {
- // Requête pour récupérer les messages en attente
- $sqlPending = 'SELECT * FROM messenger_messages WHERE queue_name = :queue AND available_at IS NULL';
- $pending = $this->connection->fetchAllAssociative($sqlPending, ['queue' => 'default']);
-
- // Requête pour récupérer les messages en cours de traitement
- $sqlProcessing = 'SELECT * FROM messenger_messages WHERE queue_name = :queue AND available_at IS NOT NULL';
- $processing = $this->connection->fetchAllAssociative($sqlProcessing, ['queue' => 'default']);
-
- return [
- 'pending' => $pending,
- 'processing' => $processing
- ];
- }
-
- private function decodeMessages(array $messages): array
- {
- $decodedMessages = [];
-
- foreach ($messages as $message) {
- $decodedMessages[] = [
- 'id' => $message['id'],
- 'body' => $this->decodeMessageBody($message['body']),
- 'headers' => json_decode($message['headers'], true),
- ];
- }
-
- return $decodedMessages;
- }
-
- private function decodeMessageBody(string $body)
- {
- return unserialize(stripcslashes($body));
- }
-}
diff --git a/src/Form/AppSettingsType.php b/src/Form/AppSettingsType.php
deleted file mode 100644
index d6e7371..0000000
--- a/src/Form/AppSettingsType.php
+++ /dev/null
@@ -1,30 +0,0 @@
-add('mangaDirectory', TextType::class, [
- 'label' => 'Manga Directory',
- ])
- ->add('imageDirectory', TextType::class, [
- 'label' => 'Image Directory',
- ]);
- }
-
- public function configureOptions(OptionsResolver $resolver): void
- {
- $resolver->setDefaults([
- 'data_class' => AppSettings::class,
- ]);
- }
-}
diff --git a/src/Form/ContentSourceType.php b/src/Form/ContentSourceType.php
deleted file mode 100644
index 9fa3b28..0000000
--- a/src/Form/ContentSourceType.php
+++ /dev/null
@@ -1,50 +0,0 @@
-add('baseUrl', UrlType::class, [
- 'label' => 'Base URL',
- ])
- ->add('imageSelector', TextType::class, [
- 'label' => 'Image Selector',
- ])
- ->add('chapterUrlFormat', TextType::class, [
- 'label' => 'Chapter URL Format ({slug}, {chapterNumber})',
- ])
- ->add('nextPageSelector', TextType::class, [
- 'label' => 'Next Page Selector (let empty if vertical reader)',
- 'required' => false,
- ])
- ->add('ChapterSelector', TextType::class, [
- 'label' => 'Chapter Selector (required for Javascript scraping)',
- 'required' => false,
- ])
- ->add('scrapingType', ChoiceType::class, [
- 'label' => 'Scraping Type',
- 'choices' => [
- 'HTML' => 'html',
- 'JavaScript' => 'javascript'
- ],
- ]);
- }
-
- public function configureOptions(OptionsResolver $resolver): void
- {
- $resolver->setDefaults([
- 'data_class' => ContentSource::class,
- ]);
- }
-}
diff --git a/src/Form/MangaEditType.php b/src/Form/MangaEditType.php
deleted file mode 100644
index 5a48968..0000000
--- a/src/Form/MangaEditType.php
+++ /dev/null
@@ -1,95 +0,0 @@
-add('title', TextType::class, [
- 'label' => 'Titre',
- 'attr' => ['class' => 'w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500']
- ])
- ->add('slug', TextType::class, [
- 'label' => 'Slug',
- 'attr' => [
- 'readonly' => true,
- 'class' => 'bg-gray-100 w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500'
- ],
- ])
- ->add('alternativeSlugs', CollectionType::class, [
- 'entry_type' => TextType::class,
- 'allow_add' => true,
- 'allow_delete' => true,
- 'by_reference' => false,
- 'label' => false,
- 'prototype' => true,
- 'entry_options' => ['attr' => ['class' => 'w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500'], 'label' => false],
- 'required' => false,
- ])
- ->add('publicationYear', NumberType::class, [
- 'label' => 'Année de publication',
- 'attr' => ['class' => 'w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500']
- ])
- ->add('description', TextareaType::class, [
- 'label' => 'Description',
- 'attr' => ['class' => 'w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500', 'rows' => 8]
- ])
- ->add('genres', CollectionType::class, [
- 'entry_type' => TextType::class,
- 'allow_add' => true,
- 'allow_delete' => true,
- 'by_reference' => false,
- 'label' => 'Genres',
- 'entry_options' => ['attr' => ['class' => 'w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500']],
- 'required' => false,
- ])
- ->add('rating', NumberType::class, [
- 'label' => 'Note',
- 'attr' => ['class' => 'w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500'],
- 'required' => false,
- ])
- ->add('author', TextType::class, [
- 'label' => 'Auteur',
- 'attr' => ['class' => 'w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500'],
- 'required' => false,
- ])
- ->add('status', TextType::class, [
- 'label' => 'Statut',
- 'attr' => ['class' => 'w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500'],
- 'required' => false,
- ])
- ;
-
- $builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
- $data = $event->getData();
- $manga = $event->getForm()->getData();
-
- if ($manga && $manga->getSlug()) {
- $data['slug'] = $manga->getSlug();
- }
-
- $event->setData($data);
- });
- }
-
- public function configureOptions(OptionsResolver $resolver): void
- {
- $resolver->setDefaults([
- 'data_class' => Manga::class,
- ]);
- }
-}
diff --git a/src/Interface/ClientInterface.php b/src/Interface/ClientInterface.php
deleted file mode 100644
index f38aa92..0000000
--- a/src/Interface/ClientInterface.php
+++ /dev/null
@@ -1,9 +0,0 @@
-entityManager->getRepository(AppSettings::class)->findOneBy([]);
- if (!$settings) {
- $settings = $this->createDefaultSettings();
- }
-
- return $settings;
- }
-
- public function updateSettings(AppSettings $newSettings): void
- {
- $settings = $this->entityManager->getRepository(AppSettings::class)->findOneBy([]);
- if (!$settings) {
- $settings = new AppSettings();
- }
-
- $settings->setMangaDirectory($newSettings->getMangaDirectory());
- $settings->setImageDirectory($newSettings->getImageDirectory());
-
- $this->entityManager->persist($settings);
- $this->entityManager->flush();
- }
-
- private function createDefaultSettings(): AppSettings
- {
- $settings = new AppSettings();
- $settings->setMangaDirectory(self::DEFAULT_MANGA_DIRECTORY);
- $settings->setImageDirectory(self::DEFAULT_IMAGE_DIRECTORY);
-
- $this->entityManager->persist($settings);
- $this->entityManager->flush();
-
- return $settings;
- }
-}
diff --git a/src/Manager/FileSystemManager.php b/src/Manager/FileSystemManager.php
deleted file mode 100644
index df11a6d..0000000
--- a/src/Manager/FileSystemManager.php
+++ /dev/null
@@ -1,120 +0,0 @@
-loadSettings();
- }
-
- private function loadSettings(): void
- {
- $settings = $this->appSettingsManager->getSettings();
- $this->mangaDirectory = $settings->getMangaDirectory();
- $this->imageDirectory = $settings->getImageDirectory();
- }
-
- public function getMangaDirectory(): string
- {
- return $this->mangaDirectory;
- }
-
- public function getImageDirectory(): string
- {
- return $this->imageDirectory;
- }
-
- public function getImagePath(string $subDir = ''): string
- {
- if (!$this->filesystem->exists($this->projectDir.'/'.self::IMAGES_DIRECTORY.($subDir ? "/$subDir" : ''))) {
- $this->filesystem->mkdir($this->projectDir.'/'.self::IMAGES_DIRECTORY.($subDir ? "/$subDir" : ''), 0755);
- }
-
- return $this->projectDir.'/'.self::IMAGES_DIRECTORY.($subDir ? "/$subDir" : '');
- }
-
- public function createMangaDirectory(string $mangaSlug, ?int $year): string
- {
- $year = $year ?? 'unknown';
- $directoryPath = $this->projectDir.'/'.self::CBZ_DIRECTORY.'/'.ucfirst($mangaSlug)." ($year)";
- $this->filesystem->mkdir($directoryPath, 0755);
-
- return $directoryPath;
- }
-
- public function createVolumeDirectory(string $mangaDir, int $volume): string
- {
- $volumeDir = sprintf('%s/volume_%02d', $mangaDir, $volume);
- $this->filesystem->mkdir($volumeDir, 0755);
-
- return $volumeDir;
- }
-
- public function moveUploadedFile(string $sourcePath, string $destinationDir, string $originalFilename): string
- {
- $newFilename = $this->generateUniqueFilename($originalFilename);
- $destinationPath = $destinationDir.'/'.$newFilename;
- $this->filesystem->rename($sourcePath, $destinationPath, true);
-
- return $destinationPath;
- }
-
- public function deleteFile(string $filePath): void
- {
- if ($this->filesystem->exists($filePath)) {
- $this->filesystem->remove($filePath);
- }
- }
-
- public function deleteDirectory(string $directoryPath): void
- {
- if ($this->filesystem->exists($directoryPath)) {
- $this->filesystem->remove($directoryPath);
- }
- }
-
- public function fileExists(string $filePath): bool
- {
- return $this->filesystem->exists($filePath);
- }
-
- public function moveFile(string $sourcePath, string $destinationPath): void
- {
- $this->filesystem->rename($sourcePath, $destinationPath, true);
- }
-
- public function getUploadsDirectory(): string
- {
- return $this->projectDir.'/'.self::UPLOADS_DIRECTORY;
- }
-
- private function generateUniqueFilename(string $originalFilename): string
- {
- $safeFilename = $this->slugger->slug(pathinfo($originalFilename, PATHINFO_FILENAME));
-
- return $safeFilename.'-'.uniqid().'.'.pathinfo($originalFilename, PATHINFO_EXTENSION);
- }
-
- public function generateUniqueImageFilename(string $originalFilename): string
- {
- $safeFilename = $this->slugger->slug(pathinfo($originalFilename, PATHINFO_FILENAME));
-
- return $safeFilename.'-'.uniqid().'.jpg';
- }
-}
diff --git a/src/Manager/Toolbar/Definition/ActivityToolbar.php b/src/Manager/Toolbar/Definition/ActivityToolbar.php
deleted file mode 100644
index 3c6b666..0000000
--- a/src/Manager/Toolbar/Definition/ActivityToolbar.php
+++ /dev/null
@@ -1,20 +0,0 @@
-addToLeftGroup(new ToolbarButton('arrows-rotate', 'Refresh', 'toolbar#refreshActivity'))
- ->addToLeftGroup(new ToolbarDivider())
- ->addToLeftGroup(new ToolbarButton('trash-can', 'Remove Selected', 'toolbar#removeActivity'))
-
- ->addToRightGroup(new ToolbarButton('th-large', 'Options', 'toolbar#optionActivity'))
- ;
- }
-}
diff --git a/src/Manager/Toolbar/Definition/ChapterListToolbar.php b/src/Manager/Toolbar/Definition/ChapterListToolbar.php
deleted file mode 100644
index 77a586d..0000000
--- a/src/Manager/Toolbar/Definition/ChapterListToolbar.php
+++ /dev/null
@@ -1,29 +0,0 @@
-addToLeftGroup(new ToolbarButton('arrows-rotate', 'Refresh metadata', 'toolbar#refreshMetadata', $contextData))
- ->addToLeftGroup(new ToolbarDivider())
- ->addToLeftGroup(new ToolbarButton('keyboard', 'Rename chapters', 'toolbar#renameChapters'))
- ->addToLeftGroup(new ToolbarButton('file-zipper', 'Manage cbz', 'toolbar#manageCbz', $contextData))
- ->addToLeftGroup(new ToolbarButton('gear', 'Preferred Sources', 'toolbar#editPreferredSources', $contextData))
-
-
- ->addToRightGroup(new ToolbarButton('bookmark', $monitoredTitle, 'toolbar#monitoring', array_merge($contextData, ['buttonClass' => $monitoredColor])))
- ->addToRightGroup(new ToolbarButton('wrench', 'Edit', 'toolbar#editManga', $contextData))
- ->addToRightGroup(new ToolbarButton('trash-can', 'Delete', 'toolbar#deleteManga', $contextData))
- ->addToRightGroup(new ToolbarDivider())
- ->addToRightGroup(new ToolbarButton('chevron-down', 'Expand all', 'toolbar#expandAll'));
- }
-}
diff --git a/src/Manager/Toolbar/Definition/MangaListToolbar.php b/src/Manager/Toolbar/Definition/MangaListToolbar.php
deleted file mode 100644
index 7f3c561..0000000
--- a/src/Manager/Toolbar/Definition/MangaListToolbar.php
+++ /dev/null
@@ -1,35 +0,0 @@
-addToLeftGroup(new ToolbarButton('arrows-rotate', 'Refresh', 'toolbar#refreshMetadata'))
- ->addToLeftGroup(new ToolbarButton('search', 'Search', 'toolbar#searchLastChapter'))
-
- ->addToRightGroup(new ToolbarButton('th-large', 'Options', 'toolbar#options'))
- ->addToRightGroup(new ToolbarDivider())
- ->addToRightGroup(new ToolbarDropdown('eye', 'View', 'changeView', [
- ['text' => 'Poster View', 'action' => 'changeView', 'data' => ['view' => 'poster']],
- ['text' => 'Table View', 'action' => 'changeView', 'data' => ['view' => 'table']],
- ['text' => 'Resume View', 'action' => 'changeView', 'data' => ['view' => 'resume']]
- ]))
- ->addToRightGroup(new ToolbarDropdown('sort', 'Sort', 'sort', [
- ['text' => 'Par titre', 'action' => 'sort', 'data' => ['sort' => 'title']],
- ['text' => 'Par année de publication', 'action' => 'sort', 'data' => ['sort' => 'publicationYear']],
- ['text' => 'Par date d\'ajout', 'action' => 'sort', 'data' => ['sort' => 'createdAt']]
- ]))
- ->addToRightGroup(new ToolbarDropdown('filter', 'Filter', 'filter', [
- ['text' => 'Tous les mangas', 'action' => 'filter', 'data' => ['filter' => 'all']],
- ['text' => 'Mangas en cours', 'action' => 'filter', 'data' => ['filter' => 'ongoing']],
- ['text' => 'Mangas terminés', 'action' => 'filter', 'data' => ['filter' => 'completed']]
- ]))
- ;
- }
-}
diff --git a/src/Manager/Toolbar/Definition/ScraperListToolbar.php b/src/Manager/Toolbar/Definition/ScraperListToolbar.php
deleted file mode 100644
index a27b2ce..0000000
--- a/src/Manager/Toolbar/Definition/ScraperListToolbar.php
+++ /dev/null
@@ -1,16 +0,0 @@
-addToRightGroup(new ToolbarButton('file-import', 'Import Json', 'toolbar#openImportModal'))
- ->addToRightGroup(new ToolbarDivider())
- ->addToRightGroup(new ToolbarButton('file-export', 'Export Json', 'toolbar#openExportModal'));
- }
-}
diff --git a/src/Manager/Toolbar/Definition/Toolbar.php b/src/Manager/Toolbar/Definition/Toolbar.php
deleted file mode 100644
index 3c60582..0000000
--- a/src/Manager/Toolbar/Definition/Toolbar.php
+++ /dev/null
@@ -1,31 +0,0 @@
-leftGroup[] = $element;
- return $this;
- }
-
- public function addToRightGroup(ToolbarElement $element): self
- {
- $this->rightGroup[] = $element;
- return $this;
- }
-
- public function getGroups(): array
- {
- return [
- 'leftGroup' => $this->leftGroup,
- 'rightGroup' => $this->rightGroup,
- ];
- }
-}
diff --git a/src/Manager/Toolbar/Element/AbstractToolbarElement.php b/src/Manager/Toolbar/Element/AbstractToolbarElement.php
deleted file mode 100644
index f47de3d..0000000
--- a/src/Manager/Toolbar/Element/AbstractToolbarElement.php
+++ /dev/null
@@ -1,37 +0,0 @@
-icon = $icon;
- $this->text = $text;
- $this->action = $action;
- }
-
- public function getIcon(): string
- {
- return $this->icon;
- }
-
- public function getText(): string|array
- {
- return $this->text;
- }
-
- public function getAction(): string
- {
- return $this->action;
- }
-
- public function getAdditionalProperties(): array
- {
- return [];
- }
-}
diff --git a/src/Manager/Toolbar/Element/ToolbarButton.php b/src/Manager/Toolbar/Element/ToolbarButton.php
deleted file mode 100644
index fceaae6..0000000
--- a/src/Manager/Toolbar/Element/ToolbarButton.php
+++ /dev/null
@@ -1,24 +0,0 @@
-data = $data;
- }
-
- public function getType(): string
- {
- return 'button';
- }
-
- public function getAdditionalProperties(): array
- {
- return ['data' => $this->data];
- }
-}
diff --git a/src/Manager/Toolbar/Element/ToolbarDivider.php b/src/Manager/Toolbar/Element/ToolbarDivider.php
deleted file mode 100644
index 5fbd3bb..0000000
--- a/src/Manager/Toolbar/Element/ToolbarDivider.php
+++ /dev/null
@@ -1,15 +0,0 @@
-items = $items;
- }
-
- public function getType(): string
- {
- return 'dropdown';
- }
-
- public function getAdditionalProperties(): array
- {
- return ['items' => $this->items];
- }
-}
diff --git a/src/Manager/Toolbar/Element/ToolbarElement.php b/src/Manager/Toolbar/Element/ToolbarElement.php
deleted file mode 100644
index db3bcd4..0000000
--- a/src/Manager/Toolbar/Element/ToolbarElement.php
+++ /dev/null
@@ -1,12 +0,0 @@
- new MangaListToolbar(),
- 'chapter_list' => new ChapterListToolbar($context),
- 'activity' => new ActivityToolbar($context),
- 'scraper_list' => new ScraperListToolbar($context),
- default => throw new \InvalidArgumentException("Unknown toolbar type: $type"),
- };
- }
-}
diff --git a/src/Message/DownloadChapter.php b/src/Message/DownloadChapter.php
deleted file mode 100644
index 243b3bd..0000000
--- a/src/Message/DownloadChapter.php
+++ /dev/null
@@ -1,15 +0,0 @@
-chapterId;
- }
-}
diff --git a/src/Message/RefreshAndDownloadChapters.php b/src/Message/RefreshAndDownloadChapters.php
deleted file mode 100644
index 2631a8e..0000000
--- a/src/Message/RefreshAndDownloadChapters.php
+++ /dev/null
@@ -1,23 +0,0 @@
-name = $name;
- // }
-
- // public function getName(): string
- // {
- // return $this->name;
- // }
-}
diff --git a/src/Message/RefreshMetadata.php b/src/Message/RefreshMetadata.php
deleted file mode 100644
index 0d3dd29..0000000
--- a/src/Message/RefreshMetadata.php
+++ /dev/null
@@ -1,15 +0,0 @@
-mangaId;
- }
-}
diff --git a/src/MessageHandler/DownloadChapterHandler.php b/src/MessageHandler/DownloadChapterHandler.php
deleted file mode 100644
index ba91a75..0000000
--- a/src/MessageHandler/DownloadChapterHandler.php
+++ /dev/null
@@ -1,103 +0,0 @@
-chapterRepository->find($message->getChapterId());
- if (!$chapter) {
- $this->notificationService->sendUpdate(['status' => 'error', 'message' => 'Chapter not found.']);
- throw new BadRequestHttpException('Chapter not found');
- } elseif (null !== $chapter->getCbzPath()) {
- $this->notificationService->sendUpdate(['status' => 'error', 'message' => 'Chapter already scraped.']);
- throw new BadRequestHttpException('Chapter already downloaded');
- }
-
- $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);
-
- if (count($preferredSources) > 0) {
- $sources = $preferredSources;
- } else {
- $sources = $allSources;
- }
-
- // $sources[] =
- // (new ContentSource())
- // ->setBaseUrl('https://api.mangadex.org/')
- // ->setImageSelector('img')
- // ->setChapterUrlFormat('at-home/server/%s')
- // ->setScrapingType('mangadex');
-
- // (new ContentSource())
- // ->setBaseUrl('https://lelscans.net')
- // ->setImageSelector('#image img')
- // ->setChapterUrlFormat('https://lelscans.net/scan-%s/%s')
- // ->setNextPageSelector('a[title="Suivant"]')
- // ->setScrapingType('html'),
- // (new ContentSource())
- // ->setBaseUrl('https://darkscans.net/')
- // ->setImageSelector('.reading-content img')
- // ->setChapterUrlFormat('https://darkscans.net/mangas/%s/chapter-%s/')
- // ->setNextPageSelector(null)
- // ->setScrapingType('html')
-
- $scrapedSuccessfully = false;
-
- foreach ($sources as $source) {
- try {
- $this->mangaScraperService->scrapeChapter($chapter, $source);
- $scrapedSuccessfully = true;
- break;
- } catch (\Exception $e) {
- $this->notificationService->sendUpdate([
- 'status' => 'warning',
- 'message' => 'An error occurred while scraping with source: '.$source->getBaseUrl().'. Trying next source...',
- ]);
- } catch (GuzzleException $e) {
-
- }
- }
-
- if (!$scrapedSuccessfully) {
- $this->notificationService->sendUpdate([
- 'status' => 'error',
- 'message' => 'All sources failed to scrape the chapter '.$chapter->getManga()->getTitle().' '.$chapter->getNumber().'.',
- ]);
- throw new \Exception('All sources failed to scrape the chapter '.$chapter->getManga()->getTitle().' '.$chapter->getNumber().'.');
- }
-
- $this->notificationService->sendUpdate(['status' => 'success', 'message' => 'Chapter scraped successfully.']);
- }
-}
diff --git a/src/MessageHandler/RefreshAndDownloadChaptersHandler.php b/src/MessageHandler/RefreshAndDownloadChaptersHandler.php
deleted file mode 100644
index cb655f3..0000000
--- a/src/MessageHandler/RefreshAndDownloadChaptersHandler.php
+++ /dev/null
@@ -1,60 +0,0 @@
-mangaRepository->findBy(['monitored' => true]);
-
- foreach ($mangas as $manga) {
- $chapters = $this->refreshMangas($manga);
-
- if (empty($chapters)) {
- continue;
- }
-
- /** @var Chapter $chapter */
- foreach ($chapters as $chapter) {
- $this->bus->dispatch(new DownloadChapter($chapter->getId()));
- }
- }
-
- }
-
- private function refreshMangas(Manga $manga): array
- {
- $lastChapters = $this->mangadexProvider->addAllChaptersToManga($manga);
-
- foreach ($lastChapters as $chapter) {
- $this->entityManager->persist($chapter);
- }
-
- $this->entityManager->persist($manga);
- $this->entityManager->flush();
-
- return $lastChapters;
- }
-}
diff --git a/src/MessageHandler/RefreshMetadataHandler.php b/src/MessageHandler/RefreshMetadataHandler.php
deleted file mode 100644
index d63abd7..0000000
--- a/src/MessageHandler/RefreshMetadataHandler.php
+++ /dev/null
@@ -1,49 +0,0 @@
-mangaRepository->find($message->getMangaId());
- if (!$manga) {
- return;
- }
-
- $lastChapters = $this->mangadexProvider->addAllChaptersToManga($manga);
-
- try {
- foreach ($lastChapters as $chapter) {
- $this->entityManager->persist($chapter);
- }
-
- $this->entityManager->persist($manga);
- $this->entityManager->flush();
- } catch (\Exception $e) {
- if ($e instanceof UniqueConstraintViolationException) {
- $this->notificationService->sendUpdate(['status' => 'error', 'message' => 'An error occurred while refreshing ' . $manga->getTitle() . '.']);
- return;
- }
- }
-
- $this->notificationService->sendUpdate(['status' => 'success', 'message' => $manga->getTitle() . ' refreshed, ' . count($lastChapters) . ' new chapters added.']);
- }
-}
diff --git a/src/Scheduler/MainSchedule.php b/src/Scheduler/MainSchedule.php
deleted file mode 100644
index 539f7e0..0000000
--- a/src/Scheduler/MainSchedule.php
+++ /dev/null
@@ -1,25 +0,0 @@
-add(
- RecurringMessage::every('6 hours', new RefreshAndDownloadChapters())
- )
- ->stateful($this->cache);
- }
-}
diff --git a/src/Service/ActivityService.php b/src/Service/ActivityService.php
deleted file mode 100644
index e87b8cf..0000000
--- a/src/Service/ActivityService.php
+++ /dev/null
@@ -1,20 +0,0 @@
-hub->publish($update);
- }
-}
diff --git a/src/Service/CbrToCbzConverter.php b/src/Service/CbrToCbzConverter.php
deleted file mode 100644
index 7fe41cb..0000000
--- a/src/Service/CbrToCbzConverter.php
+++ /dev/null
@@ -1,67 +0,0 @@
-tempDir = $projectDir . '/public/tmp';
- $this->filesystem = new Filesystem();
- }
-
- public function convert(string $cbrPath): string
- {
- $tempDir = $this->tempDir . '/' . uniqid('cbr_conversion_');
- $this->filesystem->mkdir($tempDir);
-
- $extractDir = $tempDir . '/extract';
- $this->filesystem->mkdir($extractDir);
-
- $process = new Process(['unrar-free', 'x', $cbrPath, $extractDir]);
- $process->run();
-
- // Si unrar échoue, essayer avec 7z
- if (!$process->isSuccessful()) {
- $process = new Process(['7z', 'x', $cbrPath, "-o$extractDir"]);
- $process->run();
-
- if (!$process->isSuccessful()) {
- throw new \RuntimeException("Extraction failed: " . $process->getErrorOutput());
- }
- }
-
- // Créer le CBZ
- $cbzFileName = pathinfo($cbrPath, PATHINFO_FILENAME) . '.cbz';
- $cbzPath = $this->tempDir . '/' . $cbzFileName;
- $zip = new \ZipArchive();
- if ($zip->open($cbzPath, \ZipArchive::CREATE) !== true) {
- throw new \RuntimeException("Cannot create ZIP file");
- }
-
- $files = new \RecursiveIteratorIterator(
- new \RecursiveDirectoryIterator($extractDir),
- \RecursiveIteratorIterator::LEAVES_ONLY
- );
-
- foreach ($files as $file) {
- if (!$file->isDir()) {
- $filePath = $file->getRealPath();
- $relativePath = substr($filePath, strlen($extractDir) + 1);
- $zip->addFile($filePath, $relativePath);
- }
- }
-
- $zip->close();
-
- $this->filesystem->remove($tempDir);
-
- return $cbzPath;
- }
-}
diff --git a/src/Service/CbzService.php b/src/Service/CbzService.php
deleted file mode 100644
index 569ccde..0000000
--- a/src/Service/CbzService.php
+++ /dev/null
@@ -1,221 +0,0 @@
-extractInfoFromFileName($originalFileName);
-
- $metadata['title'] = $fileInfo['title'];
- $metadata['volume'] = null !== $fileInfo['volume'] ? (int) $fileInfo['volume'] : null;
- $metadata['chapter'] = null !== $fileInfo['chapter'] ? (int) $fileInfo['chapter'] : null;
-
- if (is_null($metadata['chapter'])) {
- try {
- $zip->open($filePath);
- $chapterNumbers = [];
-
- for ($i = 0; $i < $zip->numFiles; ++$i) {
- $stat = $zip->statIndex($i);
- $fileName = $stat['name'];
-
- $chapterNumbers[] = $this->extractChapter($fileName);
- }
-
- $chapterNumbers = array_unique($chapterNumbers);
-
- if (1 === count($chapterNumbers)) {
- $metadata['chapter'] = '' === array_values($chapterNumbers)[0] ? null : (int) array_values($chapterNumbers)[0];
- } elseif (count($chapterNumbers) > 1) {
- $metadata['chapter'] = min($chapterNumbers);
- }
-
- $zip->close();
- } catch (\Exception $e) {
- throw new \Exception("Impossible d'ouvrir le fichier CBZ. ".$e->getMessage());
- }
- }
-
- return $metadata;
- }
-
- public function getPageContent(string $cbzPath, int $pageNumber): ?string
- {
- $zip = new \ZipArchive();
- if (true === $zip->open($cbzPath)) {
- $images = $this->getImageList($zip);
- if (isset($images[$pageNumber - 1])) {
- $content = $zip->getFromName($images[$pageNumber - 1]);
- $zip->close();
-
- return $content;
- }
- $zip->close();
- }
-
- return null;
- }
-
- public function getPageCount(string $cbzPath): int
- {
- $zip = new \ZipArchive();
- if (true === $zip->open($cbzPath)) {
- $count = count($this->getImageList($zip));
- $zip->close();
-
- return $count;
- }
-
- return 0;
- }
-
- private function extractInfoFromFileName(string $fileName): array
- {
- $title = $this->extractTitle($fileName);
- $volume = $this->extractVolume($fileName);
- $chapter = $this->extractChapter($fileName);
-
- return [
- 'title' => '' === $title ? null : $title,
- 'volume' => '' === $volume ? null : $volume,
- 'chapter' => '' === $chapter ? null : $chapter,
- ];
- }
-
- private function extractTitle(string $fileName): string
- {
- $titlePattern = '/^(?P.+?)(?:\s*-\s*|\s+)?(?:(?:[Tt]ome|[Vv]ol\.?|[Tt]|[Cc]hap(?:itre|ter)?)\s*\d+)/';
- if (preg_match($titlePattern, $fileName, $matches)) {
- return $this->slugger->slug(trim($matches['title']), '-')->lower()->toString();
- }
-
- $newFormatPattern = '/^(?P.*?)_\d+/';
- if (preg_match($newFormatPattern, $fileName, $matches)) {
- return $this->slugger->slug(trim($matches['title']), '-')->lower()->toString();
- }
-
- return $this->slugger->slug(pathinfo($fileName, PATHINFO_FILENAME), '-')->lower()->toString();
- }
-
- private function extractVolume(string $fileName): string
- {
- $volumePattern = '/(?:[Tt]ome|[Vv]ol\.?|[Tt])\s*(?P\d+)/';
- if (preg_match($volumePattern, $fileName, $matches)) {
- return str_pad($matches['volume'], 2, '0', STR_PAD_LEFT);
- }
-
- return '';
- }
-
- private function extractChapter(string $fileName): string
- {
- $chapterPattern = '/[Cc]hap(?:itre|ter)?\s*(?P\d+)/';
- if (preg_match($chapterPattern, $fileName, $matches)) {
- return $matches['chapter'];
- }
-
- $newFormatPattern = '/_(?P\d+)(?:\.\w+)?$/';
- if (preg_match($newFormatPattern, $fileName, $matches)) {
- return $matches['chapter'];
- }
-
- return '';
- }
-
- private function getImageList(\ZipArchive $zip): array
- {
- $images = [];
- for ($i = 0; $i < $zip->numFiles; ++$i) {
- $filename = $zip->getNameIndex($i);
- if (preg_match('/\.(jpg|jpeg|png|gif)$/i', $filename)) {
- $images[] = $filename;
- }
- }
- sort($images);
-
- return $images;
- }
-
- public function createVolumeArchive(array $chapters): string
- {
- $tempFile = tempnam(sys_get_temp_dir(), 'volume_cbz_');
- $zip = new \ZipArchive();
- if (true !== $zip->open($tempFile, \ZipArchive::CREATE)) {
- throw new \RuntimeException('Impossible de créer le fichier ZIP temporaire.');
- }
-
- foreach ($chapters as $chapter) {
- $chapterZip = new \ZipArchive();
- if (true === $chapterZip->open($chapter->getCbzPath())) {
- for ($i = 0; $i < $chapterZip->numFiles; ++$i) {
- $filename = $chapterZip->getNameIndex($i);
- $fileContent = $chapterZip->getFromIndex($i);
- $zip->addFromString('Chapter '.$chapter->getNumber().'/'.$filename, $fileContent);
- }
- $chapterZip->close();
- }
- }
-
- $zip->close();
-
- return $tempFile;
- }
-
- public function generateFileName(Manga $manga, int $volume = null, float $chapterNumber = null): string
- {
- $sluggedTitle = $this->slugger->slug($manga->getTitle())->lower();
- if (null !== $volume) {
- return sprintf('%s_volume_%02d.cbz', $sluggedTitle, $volume);
- } elseif (null !== $chapterNumber) {
- return sprintf('%s_chapter_%s.cbz', $sluggedTitle, number_format($chapterNumber, 2));
- } else {
- throw new \InvalidArgumentException('Either volume or chapter number must be provided');
- }
- }
-
- public function createBinaryFileResponse(string $filePath, string $fileName): BinaryFileResponse
- {
- $response = new BinaryFileResponse($filePath);
- $response->setContentDisposition(
- ResponseHeaderBag::DISPOSITION_ATTACHMENT,
- $fileName
- );
-
- return $response;
- }
-
- public function areAllChaptersCbzIdentical(array $chapters): bool
- {
- if (empty($chapters)) {
- return false;
- }
- $firstCbzPath = $chapters[0]->getCbzPath();
-
- return array_reduce($chapters, function ($carry, $chapter) use ($firstCbzPath) {
- return $carry && $chapter->getCbzPath() === $firstCbzPath;
- }, true);
- }
-
- public function doAllChaptersHaveCbz(array $chapters): bool
- {
- return array_reduce($chapters, function ($carry, $chapter) {
- return $carry && null !== $chapter->getCbzPath();
- }, true);
- }
-}
diff --git a/src/Service/ChapterUrlGenerator.php b/src/Service/ChapterUrlGenerator.php
deleted file mode 100644
index 5ec6829..0000000
--- a/src/Service/ChapterUrlGenerator.php
+++ /dev/null
@@ -1,34 +0,0 @@
-chapterUrlFormat = $chapterUrlFormat;
- $this->validateUrlFormat($chapterUrlFormat);
- }
-
- public function getChapterUrl(string $mangaTitle, float $chapterNumber): string
- {
- $placeholders = [
- '{chapterNumber}' => $chapterNumber,
- '{slug}' => $mangaTitle,
- ];
-
- return str_replace(array_keys($placeholders), array_values($placeholders), $this->chapterUrlFormat);
- }
-
- private function validateUrlFormat(string $format): void
- {
- if (!str_contains($format, '{slug}')) {
- throw new InvalidArgumentException("The URL format must contain both {slug} and {chapterNumber} placeholders.");
- }
- }
-
-}
diff --git a/src/Service/MangaImportService.php b/src/Service/MangaImportService.php
deleted file mode 100644
index 25411a4..0000000
--- a/src/Service/MangaImportService.php
+++ /dev/null
@@ -1,103 +0,0 @@
-importChapter($manga, $chapter, $tempFilePath);
- } elseif ($volume !== null) {
- $this->importVolume($manga, $volume, $tempFilePath);
- } else {
- throw new \RuntimeException("Impossible de déterminer s'il s'agit d'un volume ou d'un chapitre.");
- }
- }
-
- /**
- * @throws Exception
- */
- private function importVolume(Manga $manga, int $volume, string $tempFilePath): void
- {
- $permanentFileName = $this->createPermanentFileName($manga, $volume);
- $mangaDirectory = $this->fileSystemManager->createMangaDirectory($manga->getSlug(), $manga->getPublicationYear());
- $volumeDirectory = $this->fileSystemManager->createVolumeDirectory($mangaDirectory, $volume);
- $permanentFilePath = $volumeDirectory . '/' . $permanentFileName;
-
- if ($this->fileSystemManager->fileExists($permanentFilePath)) {
- throw new \RuntimeException("Un fichier pour ce volume existe déjà.");
- }
-
- $this->fileSystemManager->moveFile($tempFilePath, $permanentFilePath);
-
- $this->updateVolumeChapters($manga, $volume, $permanentFilePath);
- $this->entityManager->flush();
- }
-
- /**
- * @throws Exception
- */
- private function importChapter(Manga $manga, Chapter $chapter, string $tempFilePath): void
- {
- $volume = $chapter->getVolume();
- $permanentFileName = $this->createPermanentFileName($manga, $volume, $chapter->getNumber());
- $mangaDirectory = $this->fileSystemManager->createMangaDirectory($manga->getSlug(), $manga->getPublicationYear());
- $volumeDirectory = $this->fileSystemManager->createVolumeDirectory($mangaDirectory, $chapter->getVolume());
- $permanentFilePath = $volumeDirectory . '/' . $permanentFileName;
-
- if ($this->fileSystemManager->fileExists($permanentFilePath)) {
- throw new \RuntimeException("Un fichier pour ce chapitre existe déjà.");
- }
-
- $this->fileSystemManager->moveFile($tempFilePath, $permanentFilePath);
-
- $chapter->setCbzPath($permanentFilePath);
-
- $this->entityManager->flush();
- }
-
- private function createPermanentFileName(Manga $manga, int $volume, ?float $chapterNumber = null): string
- {
- $baseFileName = $this->slugger->slug($manga->getTitle()) . '_vol' . sprintf('%02d', $volume);
- if ($chapterNumber !== null) {
- $baseFileName .= '_ch' . $chapterNumber;
- }
- return $baseFileName . '.cbz';
- }
-
- private function updateVolumeChapters(Manga $manga, int $volume, string $cbzPath): void
- {
- $chapters = $this->chapterRepository->findBy([
- 'manga' => $manga,
- 'volume' => $volume
- ]);
-
- if (empty($chapters)) {
- throw new \RuntimeException("Aucun chapitre trouvé pour le volume $volume en base de données.");
- }
-
- foreach ($chapters as $chapter) {
- $chapter->setCbzPath($cbzPath);
- }
- }
-}
diff --git a/src/Service/MangaScraperService.php b/src/Service/MangaScraperService.php
deleted file mode 100644
index d68d3f7..0000000
--- a/src/Service/MangaScraperService.php
+++ /dev/null
@@ -1,625 +0,0 @@
-filter($mangaSource->getImageSelector())->attr('src')
- ?? $crawler->filter($mangaSource->getImageSelector())->attr('data-src');
-
- // dd($imgUrl);
-
- // if (empty($imgUrl)) {
- // throw new \Exception('No valid image found on the page.');
- // }
-
- $nextLink = $crawler->filter($mangaSource->getNextPageSelector());
- $nextUrl = $nextLink->count() > 0 ? $nextLink->attr('href') : null;
-
- // Convert relative URLs to absolute URLs
- if (!preg_match('/^https?:\/\//', $imgUrl)) {
- $urlComponents = parse_url($mangaSource->getBaseUrl());
- $scheme = $urlComponents['scheme'];
- $host = $urlComponents['host'];
- $imgUrl = $scheme . '://' . $host . '/' . ltrim($imgUrl, '/');
- }
-
- return [
- 'image_url' => $imgUrl,
- 'next_page_url' => $nextUrl,
- ];
- }
-
- /**
- * @throws GuzzleException
- */
- public function scrapeManga(Manga $manga, ContentSource $mangaSource): array
- {
- $allChaptersData = [];
-
- foreach ($manga->getChapters() as $chapter) {
- $chapterData = $this->scrapeChapter($chapter, $mangaSource);
- if ($chapterData !== false) {
- $allChaptersData[$chapter->getNumber()] = $chapterData;
- }
- }
-
- return $allChaptersData;
- }
-
- /**
- * @throws GuzzleException
- * @throws Exception
- */
- public function scrapeChapter(Chapter $chapter, ContentSource $mangaSource): array|bool
- {
- return match ($mangaSource->getScrapingType()) {
- 'html' => $this->scrapeChapterHtml($chapter->getManga(), $chapter, $mangaSource),
- 'javascript' => $this->scrapeChapterJavaScript($chapter->getManga(), $chapter, $mangaSource),
- 'mangadex' => $this->scrapeChapterMangadex($chapter, $mangaSource),
- default => throw new Exception('Unsupported scraping type: ' . $mangaSource->getScrapingType()),
- };
- }
-
- /**
- * @throws GuzzleException
- * @throws Exception
- */
- private function scrapeChapterMangadex(Chapter $chapter, ContentSource $mangaSource): bool
- {
- $client = new Client();
- $chapterUrl = $mangaSource->getBaseUrl() . sprintf($mangaSource->getChapterUrlFormat(), $chapter->getExternalId());
- $manga = $chapter->getManga();
- $pageData = [];
-
- $response = $client->get($chapterUrl);
- $results = json_decode($response->getBody()->getContents(), true);
-
- if ($results['result'] !== 'ok' || count($results['chapter']['dataSaver']) === 0) {
- throw new Exception('Error while fetching chapter data from Mangadex ' . $manga->getTitle() . ' ' . $chapter->getNumber());
- }
-
- $tempDir = sys_get_temp_dir() . '/' . uniqid('manga_scraper_');
- mkdir($tempDir);
-
- foreach ($results['chapter']['dataSaver'] as $index => $page) {
- $pageUrl = $results['baseUrl'] . '/data-saver/' . $results['chapter']['hash'] . '/' . $page;
- $imagePath = $tempDir . '/' . sprintf('%03d.%s', $index + 1, pathinfo($page, PATHINFO_EXTENSION));
-
- $this->downloadAndSaveImage($pageUrl, $imagePath);
-
- $event = new PageScrappingProgressEvent($chapter->getId(), $index + 1, count($results['chapter']['dataSaver']));
- $this->eventDispatcher->dispatch($event, PageScrappingProgressEvent::NAME);
-
- $pageData[] = [
- 'image_url' => $pageUrl,
- 'local_image_url' => $imagePath,
- 'page_number' => $index + 1,
- ];
- }
-
- $cbzFilePath = $this->generateCbzPath($manga, $chapter);
- $this->createCbzFile($tempDir, $pageData, $cbzFilePath);
-
- $chapter->setCbzPath($cbzFilePath);
- $this->entityManager->persist($chapter);
- $this->entityManager->flush();
-
- // Nettoyage du répertoire temporaire
- $this->cleanupTempFiles($tempDir);
-
- return true;
- }
-
- private function scrapeChapterJavascript(Manga $manga, Chapter $chapter, ContentSource $mangaSource): array|bool
- {
- $pantherClient = PantherClient::createChromeClient();
- $chapterUrl = $mangaSource->getChapterUrl($manga->getSlug(), $chapter->getNumber());
-
- $pantherClient->request('GET', $chapterUrl);
-
- // Sélection du chapitre dans le menu déroulant
- try {
- $crawler = $pantherClient->waitFor('body');
- $select = $crawler->filter('#selectChapitres');
-
- if ($select->count() > 0) {
- $chapterNumber = $chapter->getNumber();
- $options = $select->filter('option');
- $targetindex = null;
-
- /** @var RemoteWebElement $option */
- foreach ($options->getIterator() as $index => $option) {
- $optionText = $option->getText();
- // Recherche plus flexible du numéro de chapitre
- if (preg_match("/\b{$chapterNumber}\b/", $optionText)) {
- $targetIndex = $index;
- break;
- }
- }
-
-
- if ($targetIndex !== null) {
- $pantherClient->executeScript("
- var select = document.querySelector('#selectChapitres');
- select.selectedIndex = $targetIndex;
- select.dispatchEvent(new Event('change'));
- ");
-
- // Attendre que la page se mette à jour après la sélection
- $pantherClient->wait(60000)->until( // 60 secondes de timeout
- function ($driver) {
- return $driver->executeScript("
- var scansPlacement = document.querySelector('#scansPlacement');
- if (!scansPlacement) return false;
-
- var lazyImages = scansPlacement.querySelectorAll('img.lazy');
- var loadingGif = scansPlacement.querySelector('img[src*=\"loading_scans.gif\"]');
-
- // Vérifier que toutes les images lazy sont chargées et que le GIF de chargement n'est plus présent
- var allImagesLoaded = Array.from(lazyImages).every(img => img.complete && img.naturalWidth > 0);
-
- return lazyImages.length > 0 && allImagesLoaded && !loadingGif;
- ");
- }
- );
- } else {
- throw new \Exception("Chapitre $chapterNumber non trouvé dans le menu déroulant");
- }
- }
- } catch (\Exception $e) {
- // $this->logger->warning('Erreur lors de la sélection du chapitre : ' . $e->getMessage());
- $pantherClient->close();
- return false;
- }
-
- $pageData = [];
-
- try {
- if ($mangaSource->getNextPageSelector() === null) {
- // Lecteur vertical
- $pageData = $this->scrapeVerticalReaderJavascript($pantherClient, $mangaSource, $chapter);
- } else {
- // Lecteur horizontal
- $pageData = $this->scrapeHorizontalReaderJavascript($pantherClient, $mangaSource, $chapter);
- }
- } catch (\Exception $e) {
- throw $e;
- // $this->logger->warning('Erreur lors du scraping du chapitre ' . $chapter->getNumber() . ' du manga ' . $manga->getTitle() . ': ' . $e->getMessage());
- } finally {
- $pantherClient->close();
- }
-
- return $pageData;
- }
-
- private function scrapeVerticalReaderJavascript(PantherClient $pantherClient, ContentSource $mangaSource, Chapter $chapter): array
- {
- $pageData = [];
- $pageNumber = 1;
-
- $crawler = $pantherClient->waitFor($mangaSource->getImageSelector());
- $images = $crawler->filter($mangaSource->getImageSelector());
-
- foreach ($images->getIterator() as $image) {
- $imageUrl = $image->getAttribute('src') ?: $image->getAttribute('data-src');
-
- $pageData[] = [
- 'image_url' => $this->cleanImageUrl($imageUrl),
- 'page_number' => $pageNumber,
- ];
-
- $event = new PageScrappingProgressEvent($chapter->getId(), $pageNumber, $images->count());
- $this->eventDispatcher->dispatch($event, PageScrappingProgressEvent::NAME);
-
- $pageNumber++;
- }
-
- return $pageData;
- }
-
- private function scrapeHorizontalReaderJavascript(PantherClient $pantherClient, ContentSource $mangaSource, Chapter $chapter): array
- {
- $pageData = [];
- $pageNumber = 1;
-
- while (true) {
- try {
- $crawler = $pantherClient->waitFor($mangaSource->getImageSelector());
-
- $imageElement = $crawler->filter($mangaSource->getImageSelector())->first();
- if ($imageElement->count() === 0) {
- break; // Fin du chapitre
- }
-
- $imageUrl = $imageElement->attr('src') ?: $imageElement->attr('data-src');
-
- $pageData[] = [
- 'image_url' => $this->cleanImageUrl($imageUrl),
- 'page_number' => $pageNumber,
- ];
-
- $event = new PageScrappingProgressEvent($chapter->getId(), $pageNumber, 0);
- $this->eventDispatcher->dispatch($event, PageScrappingProgressEvent::NAME);
-
- // Passer à la page suivante
- $nextButton = $pantherCrawler->filter($mangaSource->getNextPageSelector());
- if ($nextButton->count() === 0) {
- break; // Pas de bouton suivant, fin du chapitre
- }
-
- $nextButton->click();
-
- // Attendre que la page change
- $pantherClient->waitFor($mangaSource->getImageSelector(), 10);
-
- // Mettre à jour le crawler avec le nouveau contenu de la page
- $pantherCrawler = $pantherClient->refreshCrawler();
-
- $pageNumber++;
- } catch (\Exception $e) {
- throw $e;
- // $this->logger->warning('Erreur lors du scraping de la page ' . $pageNumber . ' du chapitre ' . $chapter->getNumber() . ': ' . $e->getMessage());
- break;
- }
- }
-
- return $pageData;
- }
-
- private function fetchImagesUsingPuppeteer(string $url, string $imageSelector, string $nextButtonSelector): array
- {
- // Appeler le script Puppeteer avec les paramètres nécessaires
- $output = [];
- $command = sprintf('node puppeteer-script.js "%s" "%s" "%s" 2>&1', $url, $imageSelector, $nextButtonSelector); // Redirect stderr to stdout
- // dump($command);
- // exec($command, $output, $return_var);
-
- // dd($command, $output);
-
- // Convertir la sortie JSON en tableau PHP
- return json_decode(implode("", $output), true);
- }
-
- public function testScraping(string $mangaSlug, string $chapterNumber, ContentSource $contentSource): array
- {
- return match ($contentSource->getScrapingType()) {
- 'html' => $this->testScrapingHtml($mangaSlug, $chapterNumber, $contentSource),
- 'javascript' => $this->testScrapingJavascript($mangaSlug, $chapterNumber, $contentSource),
- default => throw new Exception('Unsupported scraping type: ' . $contentSource->getScrapingType()),
- };
- }
-
- /**
- * @throws Exception
- */
- public function testScrapingJavascript(string $mangaSlug, string $chapterNumber, ContentSource $contentSource): array
- {
- $manga = $this->mangaRepository->findOneBy(['slug' => $mangaSlug]);
- $chapter = $manga->getChapterByNumber($chapterNumber);
-
- return $this->scrapeChapterJavascript($manga, $chapter, $contentSource);
- }
-
- /**
- * @throws GuzzleException
- */
- public function testScrapingHtml(string $mangaSlug, string $chapterNumber, ContentSource $contentSource): array
- {
- $chapterUrl = $contentSource->getChapterUrl($mangaSlug, $chapterNumber);
- $html = $this->fetchHtml($chapterUrl);
-
- if ($contentSource->getNextPageSelector() === null) {
- return $this->scrapeVerticalReader($html, $contentSource);
- } else {
- return $this->scrapeHorizontalReader($chapterUrl, $contentSource);
- }
- }
-
- /**
- * @throws GuzzleException
- */
- private function scrapeChapterHtml(Manga $manga, Chapter $chapter, ContentSource $mangaSource): array|bool
- {
- $chapterUrl = $mangaSource->getChapterUrl($manga->getSlug(), $chapter->getNumber());
-
- $tempDir = sys_get_temp_dir() . '/' . uniqid('manga_scraper_');
- mkdir($tempDir);
-
- $pageData = [];
-
- if ($mangaSource->getNextPageSelector() === null) {
- // Lecteur vertical
- $html = $this->fetchHtml($chapterUrl);
- $pageData = $this->scrapeVerticalReader($html, $mangaSource);
- } else {
- // Lecteur horizontal (paginé)
- $pageData = $this->scrapeHorizontalReader($chapterUrl, $mangaSource);
- }
-
- // Télécharger et sauvegarder les images
- foreach ($pageData as $index => &$page) {
- $imageName = sprintf('%03d.%s', $index + 1, pathinfo(parse_url($page['image_url'], PHP_URL_PATH), PATHINFO_EXTENSION));
- $imagePath = $tempDir . '/' . $imageName;
-
- $this->downloadAndSaveImage($page['image_url'], $imagePath);
-
- $event = new PageScrappingProgressEvent($chapter->getId(), $index + 1, count($pageData));
- $this->eventDispatcher->dispatch($event, PageScrappingProgressEvent::NAME);
-
- $page['local_image_url'] = $imagePath;
- }
-
- $cbzFilePath = $this->generateCbzPath($manga, $chapter);
- $this->createCbzFile($tempDir, $pageData, $cbzFilePath);
-
- $chapter->setCbzPath($cbzFilePath);
- $this->entityManager->persist($chapter);
- $this->entityManager->flush();
-
- // Nettoyage du répertoire temporaire
- $this->cleanupTempFiles($tempDir);
-
- return $pageData;
- }
-
- private function scrapeVerticalReader(string $html, ContentSource $contentSource): array
- {
- $crawler = new Crawler($html);
- $images = $crawler->filter($contentSource->getImageSelector());
-
- $pageData = [];
- foreach ($images as $index => $image) {
- if ($image->getAttribute('src') === '') {
- $imgUrl = $image->getAttribute('data-src');
- } else {
- $imgUrl = $image->getAttribute('src');
- }
- $pageData[] = [
- 'image_url' => $this->cleanImageUrl($imgUrl),
- 'page_number' => $index + 1,
- ];
- }
-
- return $pageData;
- }
-
- /**
- * @throws GuzzleException
- */
- private function scrapeHorizontalReader(string $chapterUrl, ContentSource $contentSource): array
- {
- $pageData = [];
- $currentPageUrl = $chapterUrl;
-
- do {
- $html = $this->fetchHtml($currentPageUrl);
- $page = $this->extractMangaPageData($html, $contentSource);
-
- $pageData[] = [
- 'image_url' => $this->cleanImageUrl($page['image_url']),
- 'page_number' => count($pageData) + 1,
- ];
-
- $currentPageUrl = $page['next_page_url'];
- } while ($currentPageUrl);
-
- return $pageData;
- }
-
- /**
- * Processes a single image
- * @throws GuzzleException
- */
- private function processImage(string $imgUrl, string $tempDir, array &$pageData, int $index, Chapter $chapter): void
- {
- $imgUrl = $this->cleanImageUrl($imgUrl);
- $imageName = sprintf('%03d.%s', $index + 1, pathinfo(parse_url($imgUrl, PHP_URL_PATH), PATHINFO_EXTENSION));
- $imagePath = $tempDir . '/' . $imageName;
-
- $this->downloadAndSaveImage($imgUrl, $imagePath);
-
- // $event = new PageScrappingProgressEvent($chapter->getId(), $index + 1, 0);
- // $this->eventDispatcher->dispatch($event, PageScrappingProgressEvent::NAME);
-
- $pageData[] = [
- 'image_url' => $imgUrl,
- 'local_image_url' => $imagePath,
- 'page_number' => $index + 1,
- ];
- }
-
- private function cleanImageUrl(string $url): string
- {
- return preg_replace('/[\x00-\x1F\x7F]/', '', trim($url));
- }
-
- /**
- * @throws GuzzleException
- * @throws Exception
- */
- private function fetchHtml(string $url): string
- {
- $client = new Client();
-
- try {
- $response = $client->get($url, [
- 'http_errors' => true,
- 'allow_redirects' => false
- ]);
-
- $statusCode = $response->getStatusCode();
-
- if ($statusCode >= 300 && $statusCode < 400) {
- throw new Exception('Chapter Not Found at ' . $url);
- } elseif ($statusCode == 404) {
- throw new Exception('Chapter Not Found at ' . $url);
- }
-
- return (string)$response->getBody();
- } catch (Exception $e) {
- throw new Exception('Bad Request: ' . $e->getMessage());
- }
- }
-
- /**
- * @throws GuzzleException
- */
- private function downloadAndSaveImage(string $imageUrl, string $destinationPath): void
- {
- $client = new Client();
- $startTime = microtime(true);
-
- try {
- $response = $client->get($imageUrl);
- $endTime = microtime(true);
- $contentType = $response->getHeaderLine('Content-Type');
- $xCacheHeader = $response->getHeaderLine('X-Cache');
- $isCached = str_starts_with($xCacheHeader, 'HIT');
- $contentLength = $response->getHeaderLine('Content-Length');
-
- if (str_starts_with($contentType, 'image/')) {
- file_put_contents($destinationPath, $response->getBody()->getContents());
- // if ($this->scrapingType === 'mangadex') {
- // $this->sendReport($imageUrl, true, $isCached, (int)$contentLength, ($endTime - $startTime) * 1000);
- // }
- } else {
- // if ($this->scrapingType === 'mangadex') {
- // $this->sendReport($imageUrl, false, $isCached, (int)$contentLength, ($endTime - $startTime) * 1000);
- // }
- throw new \Exception('Le contenu récupéré n\'est pas une image. Type de contenu : ' . $contentType);
- }
- } catch (RequestException $e) {
- throw new \Exception('Erreur lors de la récupération de l\'image : ' . $e->getMessage());
- }
- }
-
- /**
- * @throws GuzzleException
- */
- private function isChapterAvailable(string $chapterUrl, float $chapterNumber, ContentSource $mangaSource): bool
- {
- $html = $this->fetchHtml($chapterUrl);
- $crawler = new Crawler($html);
- $nextLink = $crawler->filter($mangaSource->getNextPageSelector());
-
- if ($nextLink->count() === 0) {
- return false;
- }
-
- $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);
-
- return (float)$parameters['chapter'] === $chapterNumber;
- }
-
- private function sendReport(string $imageUrl, bool $success, bool $cached, int $bytes, float $duration): void
- {
- $client = new Client();
-
- try {
- $client->post('https://api.mangadex.network/report', [
- 'headers' => [
- 'Content-Type' => 'application/json',
- ],
- 'json' => [
- 'url' => $imageUrl,
- 'success' => $success,
- 'cached' => $cached,
- 'bytes' => $bytes,
- 'duration' => $duration,
- ],
- ]);
- } catch (RequestException $e) {
- // Gérer les exceptions de requête pour le rapport
- throw new \Exception('Erreur lors de l\'envoi du rapport : ' . $e->getMessage());
- }
- }
-
- private function createCbzFile(string $tempDir, array $pageData, string $cbzFilePath): void
- {
- $zip = new \ZipArchive();
-
- if ($zip->open($cbzFilePath, \ZipArchive::CREATE) === true) {
- foreach ($pageData as $page) {
- $zip->addFile($page['local_image_url'], basename($page['local_image_url']));
- }
- $zip->close();
- }
- }
-
- private function generateCbzPath(Manga $manga, Chapter $chapter): string
- {
- $volumeDir = $this->createDirectories($manga, $chapter->getVolume());
- $fileName = sprintf(
- '%s_vol%d_ch%s.cbz',
- $manga->getSlug(),
- $chapter->getVolume(),
- $chapter->getNumber()
- );
- return $volumeDir . '/' . $fileName;
- }
-
- private function createDirectories(Manga $manga, int $volume): string
- {
- $mangaYear = $manga->getPublicationYear() ?? 'unknown';
- $mangaDir = sprintf('%s/%s (%s)', $this->projectDir . self::PUBLIC_CBZ, ucfirst($manga->getSlug()), $mangaYear);
- $volumeDir = sprintf('%s/volume_%d', $mangaDir, sprintf('%02d', $volume));
-
- if (!is_dir($volumeDir)) {
- mkdir($volumeDir, 0755, true);
- }
-
- return $volumeDir;
- }
-
- private function cleanupTempFiles(string $directory): void
- {
- $files = glob($directory . '/*');
- foreach ($files as $file) {
- if (is_file($file)) {
- unlink($file);
- }
- }
- rmdir($directory);
- }
-}
diff --git a/src/Service/MangaUpdatesMetadataProvider.php b/src/Service/MangaUpdatesMetadataProvider.php
deleted file mode 100644
index 1cfaff8..0000000
--- a/src/Service/MangaUpdatesMetadataProvider.php
+++ /dev/null
@@ -1,77 +0,0 @@
-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,
- 'licensed' => 'yes',
- 'type' => ['Manga'],
- 'exclude_genre' => ['Doujinshi', 'Adult', 'Hentai', 'Ecchi', 'Yaoi', 'Yuri', 'Josei', 'Smut', 'Gender Bender'],
- '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'];
-
- $genres = [];
- foreach ($record['genres'] as $genre) {
- $genres[] = $genre['genre'];
- }
-
- $mangas[] = (new Manga())
- ->setTitle($record['title'])
- ->setSlug($this->slugger->slug($record['title'])->lower())
- ->setDescription($record['description'])
- ->setImageUrl($record['image']['url']['original'])
- ->setGenres($genres)
- ->setPublicationYear((int)$record['year'])
- ->setRating((float)$record['bayesian_rating'])
- ;
- }
-
- return new ArrayCollection($mangas);
- } catch (GuzzleException $e) {
- throw new Exception($e->getMessage());
- }
- }
-}
diff --git a/src/Service/MangadexProvider.php b/src/Service/MangadexProvider.php
deleted file mode 100644
index 34d7af4..0000000
--- a/src/Service/MangadexProvider.php
+++ /dev/null
@@ -1,252 +0,0 @@
-client->get('/manga', [
- 'title' => $title,
- 'contentRating' => ['safe', 'suggestive', 'erotica'],
- 'includes' => ['cover_art', 'author'],
- 'limit' => 50,
- ]);
- } catch (\Exception $e) {
- $this->notificationService->sendUpdate('notification', ['status' => 'error', 'message' => 'An error occurred while fetching data from Mangadex.']);
-
- return new ArrayCollection();
- }
-
- $mangas = [];
- foreach ($results['data'] as $result) {
- $mangas[] = (new Manga())
- ->setExternalId($result['id'])
- ->setTitle($result['attributes']['title']['en'])
- ->setSlug($this->slugger->slug($result['attributes']['title']['en'])->lower())
- ->setDescription($result['attributes']['description']['fr'] ?? $result['attributes']['description']['en'] ?? '')
- ->setPublicationYear($result['attributes']['year'])
- ->setStatus($result['attributes']['status']);
- $tags = [];
- foreach ($result['attributes']['tags'] as $tag) {
- $tags[] = $tag['attributes']['name']['en'];
- }
-
- $mangas[count($mangas) - 1]->setGenres($tags);
-
- foreach ($result['relationships'] as $relationship) {
- if ('author' === $relationship['type']) {
- $mangas[count($mangas) - 1]->setAuthor($relationship['attributes']['name']);
- }
-
- if ('cover_art' === $relationship['type']) {
- $mangas[count($mangas) - 1]->setImageUrl('https://mangadex.org/covers/'.$result['id'].'/'.$relationship['attributes']['fileName']);
- }
- }
- }
-
- $test = array_map(fn ($manga) => $manga->getExternalId(), $mangas);
-
- $ratings = $this->client->get('/statistics/manga', [
- 'manga' => $test,
- ]);
-
- foreach ($mangas as $manga) {
- $manga->setRating($ratings['statistics'][$manga->getExternalId()]['rating']['average']);
- }
-
- usort($mangas, fn ($a, $b) => $b->getRating() <=> $a->getRating());
-
- return new ArrayCollection($mangas);
- }
-
- public function getFeed(Manga $manga): array
- {
- if (null === $manga->getExternalId()) {
- return [];
- }
-
- $chapters = [];
- $page = 0;
-
- do {
- $results = $this->getFeedWithPagination($manga->getExternalId(), $page);
- if (isset($results['data'])) {
- $chapters = array_merge($chapters, $results['data']);
- } else {
- break;
- }
- ++$page;
- } while (count($chapters) < $results['total']);
-
- return $this->getChaptersFromFeed($chapters, $manga);
- }
-
- public function getLastFeed(Manga $manga, int $limit = 100): array
- {
- if (null === $manga->getExternalId()) {
- return [];
- }
-
- $chapters = [];
-
- try {
- $results = $this->getFeedWithPagination($manga->getExternalId(), 0, $limit, 'desc');
- if (isset($results['data'])) {
- $chapters = $results['data'];
- }
- } catch (\Exception $e) {
- $this->notificationService->sendUpdate(['status' => 'error', 'message' => 'An error occurred while fetching recent chapters from Mangadex.']);
-
- return [];
- }
-
- return $this->getChaptersFromFeed($chapters, $manga);
- }
-
- private function getFeedWithPagination(string $externalId, int $page, int $limit = 500, string $order = 'asc'): array
- {
- try {
- $response = $this->client->get('/manga/'.$externalId.'/feed', [
- 'limit' => $limit,
- 'translatedLanguage' => ['en', 'fr'],
- 'order' => ['chapter' => $order],
- 'offset' => $page * $limit,
- ]);
- } catch (\Exception $e) {
- $this->notificationService->sendUpdate(['status' => 'error', 'message' => 'An error occurred while fetching data from Mangadex.']);
-
- return [];
- }
-
- return $response;
- }
-
- public function getMangaAggregate(Manga $manga): array
- {
- if (null === $manga->getExternalId()) {
- return [];
- }
-
- try {
- $response = $this->client->get('/manga/'.$manga->getExternalId().'/aggregate');
- } catch (\Exception $e) {
- // $this->notificationService->sendUpdate(['status' => 'error', 'message' => 'An error occurred while fetching data from Mangadex.']);
- return [];
- }
-
- $chapterEntities = [];
- if ('ok' === $response['result']) {
- foreach ($response['volumes'] as $volume) {
- $volumeNumber = 'none' === $volume['volume'] ? 0 : (float) $volume['volume'];
- foreach ($volume['chapters'] as $chapter) {
- $chapterEntity = new Chapter();
- $chapterEntity->setNumber((float) $chapter['chapter'])
- ->setTitle('Chapter '.$chapter['chapter'])
- ->setVolume($volumeNumber)
- ->setExternalId('');
-
- $chapterEntities[] = $chapterEntity;
- // $manga->addChapter($chapterEntity);
- }
- }
- }
-
- return $chapterEntities;
- }
-
- public function getChaptersFromFeed(mixed $chapters, Manga $manga): array
- {
- $chapterEntities = [];
- $uniqueChapterNumbers = [];
-
- foreach ($chapters as $result) {
- $chapterNumber = (float) $result['attributes']['chapter'];
-
- // Vérifiez si le chapitre existe déjà dans la base de données
- $chapterExists = $manga->getChapters()->exists(function ($key, $existingChapter) use ($chapterNumber) {
- return $existingChapter->getNumber() === $chapterNumber;
- });
-
- // Si le chapitre existe déjà dans la base de données ou dans notre nouvelle liste, on skip
- if ($chapterExists || in_array($chapterNumber, $uniqueChapterNumbers)) {
- continue;
- }
-
- // Créez et ajoutez le nouveau chapitre
- $chapter = new Chapter();
- $chapter->setNumber($chapterNumber)
- ->setTitle($result['attributes']['title'])
- ->setVolume((int) $result['attributes']['volume'] ?? null)
- ->setExternalId($result['id']);
-
- $chapterEntities[] = $chapter;
- $uniqueChapterNumbers[] = $chapterNumber;
- }
-
- // Trier les chapitres par numéro
- usort($chapterEntities, function ($a, $b) {
- return $a->getNumber() <=> $b->getNumber();
- });
-
- return $chapterEntities;
- }
-
- public function addAllChaptersToManga(Manga $manga): array
- {
- $mangaFeed = $this->getFeed($manga);
- $mangaAggregate = $this->getMangaAggregate($manga);
-
- $allChapters = array_merge($mangaFeed, $mangaAggregate);
-
- if (empty($allChapters)) {
- $this->notificationService->sendUpdate([
- 'status' => 'error',
- 'message' => 'No chapters found for this manga.',
- ]);
-
- return [];
- }
-
- $mergedChapters = [];
- foreach ($allChapters as $chapter) {
- $number = $chapter->getNumber();
- $existingChapter = $manga->getChapterByNumber($number);
- if ($existingChapter) {
- if ($existingChapter->getExternalId() !== $chapter->getExternalId() && is_null($existingChapter->getExternalId())) {
- $this->updateChapter($existingChapter, $chapter);
- $mergedChapters[$number] = $existingChapter;
- }
- } else {
- // Add new chapter
- $manga->addChapter($chapter);
- $mergedChapters[$number] = $chapter;
- }
- }
-
- return array_values($mergedChapters);
- }
-
- private function updateChapter(Chapter $existingChapter, Chapter $newChapter): void
- {
- $existingChapter->setVolume($newChapter->getVolume());
- $existingChapter->setExternalId($newChapter->getExternalId());
- }
-}
diff --git a/src/Service/NotificationService.php b/src/Service/NotificationService.php
deleted file mode 100644
index 9563163..0000000
--- a/src/Service/NotificationService.php
+++ /dev/null
@@ -1,20 +0,0 @@
-hub->publish($update);
- }
-}
diff --git a/src/Service/Scraper/AbstractScraper.php b/src/Service/Scraper/AbstractScraper.php
deleted file mode 100644
index f16febe..0000000
--- a/src/Service/Scraper/AbstractScraper.php
+++ /dev/null
@@ -1,160 +0,0 @@
-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';
- }
-}
diff --git a/src/Service/Scraper/HtmlScraper.php b/src/Service/Scraper/HtmlScraper.php
deleted file mode 100644
index 419951f..0000000
--- a/src/Service/Scraper/HtmlScraper.php
+++ /dev/null
@@ -1,170 +0,0 @@
-getManga();
- $chapterUrl = $this->getValidChapterUrl($contentSource, $manga, $chapter->getNumber());
-
- if (!$chapterUrl) {
- throw new \Exception("Aucune URL valide trouvée pour le chapitre {$chapter->getNumber()} du manga {$manga->getTitle()}");
- }
-
- $tempDir = sys_get_temp_dir().'/'.uniqid('manga_scraper_');
- mkdir($tempDir);
-
- $pageData = [];
-
- if (null === $contentSource->getNextPageSelector()) {
- // Lecteur vertical
- $html = $this->fetchHtml($chapterUrl);
- $pageData = $this->scrapeVerticalReader($html, $contentSource);
- } else {
- // Lecteur horizontal (paginé)
- $pageData = $this->scrapeHorizontalReader($chapterUrl, $contentSource);
- }
-
- // Télécharger et sauvegarder les images
- foreach ($pageData as $index => &$page) {
- $imageName = sprintf('%03d.%s', $index + 1, pathinfo(parse_url($page['image_url'], PHP_URL_PATH), PATHINFO_EXTENSION));
- $imagePath = $tempDir.'/'.$imageName;
-
- $destinationPath = $this->downloadAndSaveImage($page['image_url'], $imagePath);
-
- $this->dispatchProgressEvent($chapter, $index + 1, count($pageData));
-
- $page['local_image_url'] = $destinationPath;
- }
-
- $cbzFilePath = $this->generateCbzPath($manga, $chapter);
- $this->createCbzFile($pageData, $cbzFilePath);
-
- $chapter->setCbzPath($cbzFilePath);
- $this->entityManager->persist($chapter);
- $this->entityManager->flush();
-
- $this->cleanupTempFiles($tempDir);
-
- return $pageData;
- }
-
- /**
- * @throws \Exception
- */
- public function testScraping(string $mangaSlug, string $chapterNumber, ContentSource $contentSource): array
- {
- $chapterUrl = $contentSource->getChapterUrl($mangaSlug, $chapterNumber);
-
- if (!$this->isChapterUrlValid($chapterUrl)) {
- throw new \Exception('Invalid URL, check format and slug');
- }
-
- $html = $this->fetchHtml($chapterUrl);
-
- if (null === $contentSource->getNextPageSelector()) {
- return $this->scrapeVerticalReader($html, $contentSource);
- } else {
- return $this->scrapeHorizontalReader($chapterUrl, $contentSource);
- }
- }
-
- public function supports(string $scrapingType): bool
- {
- return 'html' === $scrapingType;
- }
-
- private function scrapeVerticalReader(string $html, ContentSource $contentSource): array
- {
- $crawler = new Crawler($html);
- $images = $crawler->filter($contentSource->getImageSelector());
-
- $pageData = [];
- foreach ($images as $index => $image) {
- $imgUrl = $image->getAttribute('src') ?: $image->getAttribute('data-src');
- $pageData[] = [
- 'image_url' => $this->cleanImageUrl($imgUrl),
- 'page_number' => $index + 1,
- ];
- }
-
- return $pageData;
- }
-
- /**
- * @throws \Exception
- */
- private function scrapeHorizontalReader(string $chapterUrl, ContentSource $contentSource): array
- {
- $pageData = [];
- $currentPageUrl = $chapterUrl;
-
- do {
- $html = $this->fetchHtml($currentPageUrl);
- $page = $this->extractMangaPageData($html, $contentSource);
-
- $pageData[] = [
- 'image_url' => $this->cleanImageUrl($page['image_url']),
- 'page_number' => count($pageData) + 1,
- ];
-
- $currentPageUrl = $page['next_page_url'];
- } while ($currentPageUrl);
-
- return $pageData;
- }
-
- private function fetchHtml(string $url): string
- {
- try {
- $response = $this->httpClient->get($url, [
- 'http_errors' => true,
- 'allow_redirects' => false,
- ]);
-
- $statusCode = $response->getStatusCode();
-
- if ($statusCode >= 300 && $statusCode < 400 || 404 == $statusCode) {
- throw new \Exception('Chapter Not Found at '.$url);
- }
-
- return (string) $response->getBody();
- } catch (\Exception $e) {
- throw new \Exception('Bad Request: '.$e->getMessage());
- }
- }
-
- private function extractMangaPageData(string $html, ContentSource $mangaSource): array
- {
- $crawler = new Crawler($html);
- $imgUrl = $crawler->filter($mangaSource->getImageSelector())->attr('src')
- ?? $crawler->filter($mangaSource->getImageSelector())->attr('data-src');
-
- $nextLink = $crawler->filter($mangaSource->getNextPageSelector());
- $nextUrl = $nextLink->count() > 0 ? $nextLink->attr('href') : null;
-
- // Convert relative URLs to absolute URLs
- if (!preg_match('/^https?:\/\//', $imgUrl)) {
- $urlComponents = parse_url($mangaSource->getBaseUrl());
- $scheme = $urlComponents['scheme'];
- $host = $urlComponents['host'];
- $imgUrl = $scheme.'://'.$host.'/'.ltrim($imgUrl, '/');
- }
-
- return [
- 'image_url' => $imgUrl,
- 'next_page_url' => $nextUrl,
- ];
- }
-}
diff --git a/src/Service/Scraper/JavascriptScraper.php b/src/Service/Scraper/JavascriptScraper.php
deleted file mode 100644
index 775fde5..0000000
--- a/src/Service/Scraper/JavascriptScraper.php
+++ /dev/null
@@ -1,190 +0,0 @@
-getManga();
- $pantherClient = PantherClient::createChromeClient();
- $chapterUrl = $this->getValidChapterUrl($contentSource, $manga, $chapter->getNumber());
-
- if (!$chapterUrl) {
- throw new Exception("Aucune URL valide trouvée pour le chapitre {$chapter->getNumber()} du manga {$manga->getTitle()}");
- }
-
- $pantherClient->request('GET', $chapterUrl);
-
- try {
- $this->selectChapter($pantherClient, $chapter, $contentSource);
-
- $pageData = $contentSource->getNextPageSelector() === null
- ? $this->scrapeVerticalReaderJavascript($pantherClient, $contentSource, $chapter)
- : $this->scrapeHorizontalReaderJavascript($pantherClient, $contentSource, $chapter);
-
- $tempDir = sys_get_temp_dir() . '/' . uniqid('manga_scraper_');
- mkdir($tempDir);
-
- // Télécharger et sauvegarder les images
- foreach ($pageData as $index => &$page) {
- $imageName = sprintf('%03d.%s', $index + 1, pathinfo(parse_url($page['image_url'], PHP_URL_PATH), PATHINFO_EXTENSION));
- $imagePath = $tempDir . '/' . $imageName;
-
- $destinationPath = $this->downloadAndSaveImage($page['image_url'], $imagePath);
- $this->dispatchProgressEvent($chapter, $index + 1, count($pageData));
-
- $page['local_image_url'] = $destinationPath;
- }
-
- $cbzFilePath = $this->generateCbzPath($manga, $chapter);
- $this->createCbzFile($pageData, $cbzFilePath);
-
- $chapter->setCbzPath($cbzFilePath);
- $this->entityManager->persist($chapter);
- $this->entityManager->flush();
-
- $this->cleanupTempFiles($tempDir);
-
- return $pageData;
- } finally {
- $pantherClient->close();
- }
- }
-
- public function testScraping(string $mangaSlug, string $chapterNumber, ContentSource $contentSource): array
- {
- $chapterUrl = $contentSource->getChapterUrl($mangaSlug, $chapterNumber);
-
- if (!$this->isChapterUrlValid($chapterUrl)) {
- throw new \Exception("Invalid URL, check format and slug");
- }
-
- $pantherClient = PantherClient::createChromeClient();
- $pantherClient->request('GET', $chapterUrl);
-
- try {
- $chapter = new Chapter();
- $chapter->setNumber((float)$chapterNumber);
-
- $this->selectChapter($pantherClient, $chapter, $contentSource);
-
- return $contentSource->getNextPageSelector() === null
- ? $this->scrapeVerticalReaderJavascript($pantherClient, $contentSource, $chapter)
- : $this->scrapeHorizontalReaderJavascript($pantherClient, $contentSource, $chapter);
- } catch (Exception $e) {
- throw $e;
- } finally {
- $pantherClient->close();
- }
- }
-
- public function supports(string $scrapingType): bool
- {
- return $scrapingType === 'javascript';
- }
-
- private function selectChapter(PantherClient $pantherClient, Chapter $chapter, ContentSource $contentSource): void
- {
- $chapterSelector = $contentSource->getChapterSelector();
- if (!$chapterSelector) {
- return;
- }
-
- $crawler = $pantherClient->waitFor($chapterSelector);
- $select = $crawler->filter($chapterSelector);
-
- if ($select->count() > 0) {
- $chapterNumber = $chapter->getNumber();
- $options = $select->filter('option');
- $targetIndex = null;
-
- foreach ($options as $index => $option) {
- if (preg_match("/\b{$chapterNumber}\b/", $option->getText())) {
- $targetIndex = $index;
- break;
- }
- }
-
- if ($targetIndex !== null) {
- $pantherClient->executeScript("
- var select = document.querySelector('$chapterSelector');
- select.selectedIndex = $targetIndex;
- select.dispatchEvent(new Event('change'));
- ");
-
- $this->waitForImagesLoaded($pantherClient, $contentSource);
- } else {
- throw new Exception("Chapitre $chapterNumber non trouvé dans le menu déroulant");
- }
- }
- }
-
- private function waitForImagesLoaded(PantherClient $pantherClient, ContentSource $contentSource): void
- {
- $imageSelector = $contentSource->getImageSelector();
- $pantherClient->wait(30)->until(
- function ($driver) use ($imageSelector) {
- return $driver->executeScript("
- return new Promise((resolve) => {
- let lastImageCount = 0;
- let stableCount = 0;
- const stableThreshold = 10;
-
- function checkImages() {
- const images = document.querySelectorAll('$imageSelector');
- const loadedImages = Array.from(images).filter(img => img.complete && img.naturalWidth > 0);
-
- if (loadedImages.length === lastImageCount) {
- stableCount++;
- } else {
- stableCount = 0;
- lastImageCount = loadedImages.length;
- }
-
- if (stableCount >= stableThreshold) {
- resolve(true);
- } else {
- setTimeout(checkImages, 200);
- }
- }
-
- checkImages();
- });
- ");
- }
- );
- }
-
- private function scrapeVerticalReaderJavascript(PantherClient $pantherClient, ContentSource $contentSource, Chapter $chapter): array
- {
- $pageData = [];
- $crawler = $pantherClient->waitFor($contentSource->getImageSelector());
- $images = $crawler->filter($contentSource->getImageSelector());
-
- foreach ($images as $index => $image) {
- $imageUrl = $image->getAttribute('src') ?: $image->getAttribute('data-src');
- $pageData[] = [
- 'image_url' => $this->cleanImageUrl($imageUrl),
- 'page_number' => $index + 1,
- ];
- }
-
- return $pageData;
- }
-
- private function scrapeHorizontalReaderJavascript(PantherClient $pantherClient, ContentSource $contentSource, Chapter $chapter): array
- {
- $pageData = [];
- return $pageData;
- }
-}
diff --git a/src/Service/Scraper/MangaScraperService.php b/src/Service/Scraper/MangaScraperService.php
deleted file mode 100644
index a99c465..0000000
--- a/src/Service/Scraper/MangaScraperService.php
+++ /dev/null
@@ -1,28 +0,0 @@
-scraperFactory = $scraperFactory;
- }
-
- public function scrapeChapter(Chapter $chapter, ContentSource $contentSource): array|bool
- {
- $scraper = $this->scraperFactory->createScraper($contentSource);
- return $scraper->scrapeChapter($chapter, $contentSource);
- }
-
- public function testScraping(string $mangaSlug, string $chapterNumber, ContentSource $contentSource): array
- {
- $scraper = $this->scraperFactory->createScraper($contentSource);
- return $scraper->testScraping($mangaSlug, $chapterNumber, $contentSource);
- }
-}
diff --git a/src/Service/Scraper/MangadexScraper.php b/src/Service/Scraper/MangadexScraper.php
deleted file mode 100644
index 3101390..0000000
--- a/src/Service/Scraper/MangadexScraper.php
+++ /dev/null
@@ -1,72 +0,0 @@
-getBaseUrl() . sprintf($contentSource->getChapterUrlFormat(), $chapter->getExternalId());
- $manga = $chapter->getManga();
- $pageData = [];
-
- try {
- $response = $this->httpClient->get($chapterUrl);
- $results = json_decode($response->getBody()->getContents(), true);
-
- if ($results['result'] !== 'ok' || count($results['chapter']['dataSaver']) === 0) {
- throw new \Exception('Error while fetching chapter data from Mangadex ' . $manga->getTitle() . ' ' . $chapter->getNumber());
- }
-
- $tempDir = sys_get_temp_dir() . '/' . uniqid('manga_scraper_');
- mkdir($tempDir);
-
- foreach ($results['chapter']['dataSaver'] as $index => $page) {
- $pageUrl = $results['baseUrl'] . '/data-saver/' . $results['chapter']['hash'] . '/' . $page;
- $imagePath = $tempDir . '/' . sprintf('%03d.%s', $index + 1, pathinfo($page, PATHINFO_EXTENSION));
-
- $this->downloadAndSaveImage($pageUrl, $imagePath);
-
- $this->dispatchProgressEvent($chapter, $index + 1, count($results['chapter']['dataSaver']));
-
- $pageData[] = [
- 'image_url' => $pageUrl,
- 'local_image_url' => $imagePath,
- 'page_number' => $index + 1,
- ];
- }
-
- $cbzFilePath = $this->generateCbzPath($manga, $chapter);
- $this->createCbzFile($pageData, $cbzFilePath);
-
- $chapter->setCbzPath($cbzFilePath);
- $this->entityManager->persist($chapter);
- $this->entityManager->flush();
-
- $this->cleanupTempFiles($tempDir);
-
- return $pageData;
- } catch (\Exception $e) {
- // Log the error
- return false;
- }
- }
-
- public function testScraping(string $mangaSlug, string $chapterNumber, ContentSource $contentSource): array
- {
- // For Mangadex, we need the chapter's external ID, which we don't have in this context.
- // We could potentially fetch it first, but for simplicity, let's return an empty array.
- return [];
- }
-
- public function supports(string $scrapingType): bool
- {
- return $scrapingType === 'mangadex';
- }
-}
diff --git a/src/Service/Scraper/ScraperFactory.php b/src/Service/Scraper/ScraperFactory.php
deleted file mode 100644
index cd7ddaa..0000000
--- a/src/Service/Scraper/ScraperFactory.php
+++ /dev/null
@@ -1,25 +0,0 @@
-scrapers = iterator_to_array($scrapers);
- }
-
- public function createScraper(ContentSource $contentSource): ScraperInterface
- {
- foreach ($this->scrapers as $scraper) {
- if ($scraper->supports($contentSource->getScrapingType())) {
- return $scraper;
- }
- }
- throw new \InvalidArgumentException('Unsupported scraping type: '.$contentSource->getScrapingType());
- }
-}
diff --git a/src/Service/Scraper/ScraperInterface.php b/src/Service/Scraper/ScraperInterface.php
deleted file mode 100644
index 3cf27ed..0000000
--- a/src/Service/Scraper/ScraperInterface.php
+++ /dev/null
@@ -1,13 +0,0 @@
-manga = $manga;
- }
-
- public function close(): void
- {
- $this->manga = null;
- }
-}
diff --git a/src/Twig/Components/BootstrapModal.php b/src/Twig/Components/BootstrapModal.php
deleted file mode 100644
index 331c5da..0000000
--- a/src/Twig/Components/BootstrapModal.php
+++ /dev/null
@@ -1,11 +0,0 @@
-mangaSlug;
- // $chapter = $this->chapter;
- // $manga = $mangaRepository->findOneBy(['slug' => $mangaSlug]);
- // $chapter = $chapterRepository->findOneBy(['manga' => $manga, 'number' => $chapter]);
-
-
- return 0;
-
- }
-}
diff --git a/src/Twig/Components/DropdownMenu.php b/src/Twig/Components/DropdownMenu.php
deleted file mode 100644
index 8b5cba2..0000000
--- a/src/Twig/Components/DropdownMenu.php
+++ /dev/null
@@ -1,16 +0,0 @@
-query === null || $this->query === '') {
- return null;
- }
-
- return $this->mangadexProvider->search($this->query);
- }
-}
diff --git a/src/Twig/Components/Search.php b/src/Twig/Components/Search.php
deleted file mode 100644
index 9ddfede..0000000
--- a/src/Twig/Components/Search.php
+++ /dev/null
@@ -1,28 +0,0 @@
-query ? $this->mangaRepository->findByTitle($this->query) : [];
- }
-}
diff --git a/src/Twig/Components/ToolBarButton.php b/src/Twig/Components/ToolBarButton.php
deleted file mode 100644
index 23b5676..0000000
--- a/src/Twig/Components/ToolBarButton.php
+++ /dev/null
@@ -1,15 +0,0 @@
- 'https://example.com',
- 'imageSelector' => '.manga-image img',
- 'chapterUrlFormat' => 'https://example.com/manga/{slug}/chapter-{number}',
- 'nextPageSelector' => '.next-page',
- 'scrapingType' => 'Select scraping type',
- default => '',
- };
- }
-}
diff --git a/src/Twig/Extension/TruncateExtension.php b/src/Twig/Extension/TruncateExtension.php
deleted file mode 100644
index 56786ff..0000000
--- a/src/Twig/Extension/TruncateExtension.php
+++ /dev/null
@@ -1,24 +0,0 @@
- $limit ? substr($value, 0, $limit) . '...' : $value;
- }
-}
diff --git a/src/Twig/Runtime/TruncateExtensionRuntime.php b/src/Twig/Runtime/TruncateExtensionRuntime.php
deleted file mode 100644
index 20b23f0..0000000
--- a/src/Twig/Runtime/TruncateExtensionRuntime.php
+++ /dev/null
@@ -1,18 +0,0 @@
-
- {% endif %}
-{% endblock %}
-{% block body %}
-
-
-{% endblock %}
diff --git a/templates/base.html.twig b/templates/base.html.twig
deleted file mode 100644
index 4f2e8ad..0000000
--- a/templates/base.html.twig
+++ /dev/null
@@ -1,86 +0,0 @@
-
-
-
-
-
- {% block title %}Mangarr{% endblock %}
-
- {% block stylesheets %}
- {{ encore_entry_link_tags('app') }}
- {% endblock %}
- {% block javascripts %}
- {{ encore_entry_script_tags('app') }}
- {# {{ encore_entry_script_tags('turbo') }} #}
- {% endblock %}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {% include 'menu/menu.html.twig' %}
-
-
-
-
-
-
-
- {% block toolbar %}
- {% endblock %}
-
-
-
-
- {% block body %}
- {% endblock %}
-
-
-
-
-
diff --git a/templates/broadcast/Chapter.stream.html.twig b/templates/broadcast/Chapter.stream.html.twig
deleted file mode 100644
index 6c2d2e1..0000000
--- a/templates/broadcast/Chapter.stream.html.twig
+++ /dev/null
@@ -1,21 +0,0 @@
-{% block create %}
-
-
- {% include 'manga/_chapter_row.html.twig' with { chapter: entity, manga: entity.manga } %}
-
-
-{% endblock %}
-
-{% block update %}
-
-
-
-
- {% include 'manga/_chapter_row.html.twig' with { chapter: entity, manga: entity.manga } %}
-
-
-
-{% endblock %}
-
-{% block remove %}
-{% endblock %}
diff --git a/templates/bundles/TwigBundle/Exception/error404.html.twig b/templates/bundles/TwigBundle/Exception/error404.html.twig
deleted file mode 100644
index 45cf13f..0000000
--- a/templates/bundles/TwigBundle/Exception/error404.html.twig
+++ /dev/null
@@ -1,9 +0,0 @@
-{# templates/bundles/TwigBundle/Exception/error404.html.twig #}
-{% extends 'base.html.twig' %}
-
-{% block title %}Page non trouvée{% endblock %}
-
-{% block body %}
- Page non trouvée
- La page que vous cherchez n'existe pas.
-{% endblock %}
diff --git a/templates/calendar/index.html.twig b/templates/calendar/index.html.twig
deleted file mode 100644
index b5e2de2..0000000
--- a/templates/calendar/index.html.twig
+++ /dev/null
@@ -1,20 +0,0 @@
-{% extends 'base.html.twig' %}
-
-{% block title %}Hello CalendarController!{% endblock %}
-
-{% block body %}
-
-
-
-
Hello {{ controller_name }}! ✅
-
- This friendly message is coming from:
-
-
-{% endblock %}
diff --git a/templates/components/AddMangaModal.html.twig b/templates/components/AddMangaModal.html.twig
deleted file mode 100644
index 370c77b..0000000
--- a/templates/components/AddMangaModal.html.twig
+++ /dev/null
@@ -1,12 +0,0 @@
-{# templates/components/manga_modal.html.twig #}
-
-
-
×
- {% if manga %}
-
{{ manga.title }}
-
Year: {{ manga.publicationYear }}
-
{{ manga.description }}
-
Save Manga
- {% endif %}
-
-
diff --git a/templates/components/Divider.html.twig b/templates/components/Divider.html.twig
deleted file mode 100644
index 6ce2b59..0000000
--- a/templates/components/Divider.html.twig
+++ /dev/null
@@ -1,2 +0,0 @@
-{# templates/components/Divider.html.twig #}
-
diff --git a/templates/components/DownloadChapter.html.twig b/templates/components/DownloadChapter.html.twig
deleted file mode 100644
index df74fce..0000000
--- a/templates/components/DownloadChapter.html.twig
+++ /dev/null
@@ -1,5 +0,0 @@
-
diff --git a/templates/components/DropdownMenu.html.twig b/templates/components/DropdownMenu.html.twig
deleted file mode 100644
index 723b2a2..0000000
--- a/templates/components/DropdownMenu.html.twig
+++ /dev/null
@@ -1,30 +0,0 @@
-{# templates/components/DropdownMenu.html.twig #}
-
diff --git a/templates/components/LoadingButton.html.twig b/templates/components/LoadingButton.html.twig
deleted file mode 100644
index cb97a60..0000000
--- a/templates/components/LoadingButton.html.twig
+++ /dev/null
@@ -1,17 +0,0 @@
-{# templates/components/LoadingButton.html.twig #}
-
- {{ text }}
-
-
-
-
-
-
-
diff --git a/templates/components/MangaSearch.html.twig b/templates/components/MangaSearch.html.twig
deleted file mode 100644
index 281ef91..0000000
--- a/templates/components/MangaSearch.html.twig
+++ /dev/null
@@ -1,114 +0,0 @@
-{# templates/components/MangaSearch.html.twig #}
-
-
-
-
- {% if this.mangas %}
-
- {% for manga in this.mangas %}
-
-
-
-
-
-
-
-
-
- {{ manga.title }}
- ({{ manga.publicationYear }})
-
-
-
-
-
- {% for genre in manga.genres %}
-
- {{ genre }}
-
- {% endfor %}
-
-
-
{{ manga.description|truncate(250) }}
-
-
-
-
- {{ manga.rating }}
-
-
-
-
-
-
-
- {% block content %}
-
- {% endblock %}
- {% block footer %}
-
- Cancel
-
- {{ component('LoadingButton', {
- text: 'Add ' ~ manga.title,
- type: 'submit',
- form: 'manga-' ~ loop.index,
- color: 'green'
- }) }}
- {% endblock %}
-
-
- {% endfor %}
-
- {% endif %}
-
diff --git a/templates/components/Modal.html.twig b/templates/components/Modal.html.twig
deleted file mode 100644
index e228f28..0000000
--- a/templates/components/Modal.html.twig
+++ /dev/null
@@ -1,33 +0,0 @@
-{# templates/components/Modal.html.twig #}
-
-
-
- {# Backdrop #}
-
-
- {# Centrage vertical trick #}
-
-
- {# Modal panel #}
-
-
-
- {{ title }}
-
-
- {{ block('content') }}
-
-
-
- {{ block('footer') }}
-
-
-
-
-
diff --git a/templates/components/NewMangaForm.html.twig b/templates/components/NewMangaForm.html.twig
deleted file mode 100644
index b01c084..0000000
--- a/templates/components/NewMangaForm.html.twig
+++ /dev/null
@@ -1,40 +0,0 @@
-
- {% component BootstrapModal with {id: 'mangaModal' ~ index ~ '-' ~ manga.slug } %}
- {% block modal_header %}
-
-
{{ manga.title }} ({{ manga.publicationYear }})
-
-
-
-
- {% endblock %}
- {% block modal_body %}
-
- {% endblock %}
-
- {% block modal_footer %}
-
- Add {{ manga.title }}
-
- {% endblock %}
- {% endcomponent %}
-
diff --git a/templates/components/Search.html.twig b/templates/components/Search.html.twig
deleted file mode 100644
index 444928e..0000000
--- a/templates/components/Search.html.twig
+++ /dev/null
@@ -1,38 +0,0 @@
-{# templates/components/Search.html.twig #}
-
-
-
-
-
-
- {% if query %}
-
- {% endif %}
-
diff --git a/templates/components/ToolBarButton.html.twig b/templates/components/ToolBarButton.html.twig
deleted file mode 100644
index df2bb18..0000000
--- a/templates/components/ToolBarButton.html.twig
+++ /dev/null
@@ -1,25 +0,0 @@
-{# templates/components/ToolbarButton.html.twig #}
-{% set buttonAttributes = {} %}
-{% set buttonClass = 'text-white' %}
-{% if data is defined and data is not empty %}
- {% for key, value in data %}
- {% set dataKey = 'data-' ~ key|replace({'_': '-'})|lower %}
- {% set buttonAttributes = buttonAttributes|merge({ (dataKey): value }) %}
- {% endfor %}
- {% if data['buttonClass'] is defined %}
- {% set buttonClass = data['buttonClass'] %}
- {% endif %}
-{% endif %}
-
-
-
-
- {{ text }}
-
-
diff --git a/templates/components/Toolbar.html.twig b/templates/components/Toolbar.html.twig
deleted file mode 100644
index 199f701..0000000
--- a/templates/components/Toolbar.html.twig
+++ /dev/null
@@ -1,52 +0,0 @@
-{# templates/components/Toolbar.html.twig #}
-
-
-
- {% for element in toolbar.leftGroup %}
- {% if element.type == 'button' %}
- {% set actionParts = element.action|split('#') %}
-
- {% elseif element.type == 'divider' %}
-
- {% elseif element.type == 'dropdown' %}
-
- {% endif %}
- {% endfor %}
-
-
- {% for element in toolbar.rightGroup %}
- {% if element.type == 'button' %}
- {% set actionParts = element.action|split('#') %}
-
- {% elseif element.type == 'divider' %}
-
- {% elseif element.type == 'dropdown' %}
-
- {% endif %}
- {% endfor %}
-
-
-
-
diff --git a/templates/conversion/index.html.twig b/templates/conversion/index.html.twig
deleted file mode 100644
index f103f7e..0000000
--- a/templates/conversion/index.html.twig
+++ /dev/null
@@ -1,43 +0,0 @@
-{% extends 'base.html.twig' %}
-
-{% block body %}
-
-
-
-
- Convertir CBR en CBZ
-
-
-
-
-
-{% endblock %}
diff --git a/templates/import/confirm.html.twig b/templates/import/confirm.html.twig
deleted file mode 100644
index 9c494e4..0000000
--- a/templates/import/confirm.html.twig
+++ /dev/null
@@ -1,13 +0,0 @@
-{% extends 'base.html.twig' %}
-
-{% block body %}
-
-
Confirmer l'Importation
-
Titre: {{ title }}
-
Volume: {{ volume }}
-
-
-{% endblock %}
-
diff --git a/templates/import/index.html.twig b/templates/import/index.html.twig
deleted file mode 100644
index b0cdb51..0000000
--- a/templates/import/index.html.twig
+++ /dev/null
@@ -1,43 +0,0 @@
-{% extends 'base.html.twig' %}
-
-{% block body %}
-
-
-
-
- Importer des Mangas
-
-
-
-
-
-{% endblock %}
diff --git a/templates/import/match.html.twig b/templates/import/match.html.twig
deleted file mode 100644
index 28fa31c..0000000
--- a/templates/import/match.html.twig
+++ /dev/null
@@ -1,103 +0,0 @@
-{% extends 'base.html.twig' %}
-
-{% block body %}
-
-{% endblock %}
diff --git a/templates/manga/_chapter_list.html.twig b/templates/manga/_chapter_list.html.twig
deleted file mode 100644
index 57f8b7a..0000000
--- a/templates/manga/_chapter_list.html.twig
+++ /dev/null
@@ -1,111 +0,0 @@
-
-
- {% for volume, chapters in chapters_by_volume %}
- {% set is_first = loop.first %}
- {% set volume_cbz_path = chapters|first.cbzPath %}
- {% set all_chapters_same_cbz = chapters|reduce((carry, chapter) => carry and chapter.cbzPath == volume_cbz_path, true) %}
- {% set available_chapters = chapters|filter(chapter => chapter.cbzPath is not null) %}
- {% set total_chapters = chapters|filter(chapter => chapter.visible)|length %}
-
-
-
-
-
-
-
-
Volume {{ '%02d'|format(volume) }}
-
-
- {{ available_chapters|length }} / {{ total_chapters }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- #
- Title
- Actions
-
-
-
- {% if all_chapters_same_cbz and volume_cbz_path is not null %}
-
-
-
- {{ '%02d'|format(volume) }}
-
-
-
-
- Volume {{ '%02d'|format(volume) }}
-
-
-
-
-
-
-
-
- {% else %}
- {% for chapter in chapters %}
- {% include 'manga/_chapter_row.html.twig' with {'chapter': chapter, 'manga': manga} %}
- {% endfor %}
- {% endif %}
-
-
-
-
-
-
- {% endfor %}
-
-
diff --git a/templates/manga/_chapter_row.html.twig b/templates/manga/_chapter_row.html.twig
deleted file mode 100644
index 1045d2d..0000000
--- a/templates/manga/_chapter_row.html.twig
+++ /dev/null
@@ -1,65 +0,0 @@
-{% if chapter.visible %}
-
- {% if chapter.cbzPath is not null %}
-
-
- {{ chapter.number < 10 ? '0' ~ chapter.number : chapter.number }}
-
-
- {% else %}
- {{ chapter.number < 10 ? '0' ~ chapter.number : chapter.number }}
- {% endif %}
-
-
- {% if chapter.cbzPath is not null %}
-
- {{ chapter.title ?? 'No title' }}
-
- {% else %}
- {{ chapter.title ?? 'No title' }}
- {% endif %}
-
-
- {% if chapter.cbzPath is null %}
-
-
-
-
-
- {% else %}
-
-
-
-
-
- {% endif %}
-
-
-
-
-
-
-
-
-
-
-{% endif %}
diff --git a/templates/manga/_list.html.twig b/templates/manga/_list.html.twig
deleted file mode 100644
index 8aa43e4..0000000
--- a/templates/manga/_list.html.twig
+++ /dev/null
@@ -1,92 +0,0 @@
-{% block body %}
- {% if currentView == 'poster' %}
-
- {% for manga in mangas %}
-
-
-
-
-
-
-
{{ manga.title }}
-
{{ manga.publicationYear }}
-
-
Added: {{ manga.createdAt|date('M d, Y') }}
-
-
- {% else %}
-
Aucun manga trouvé.
- {% endfor %}
-
- {% elseif currentView == 'resume' %}
-
- {% for manga in mangas %}
-
- {% else %}
-
Aucun manga trouvé.
- {% endfor %}
-
- {% elseif currentView == 'table' %}
-
-
-
-
- Manga Title
- {# Volumes #}
- Chapters
-
-
-
- {% for manga in mangas %}
-
-
-
- {{ manga.title }}
-
-
- {# #}
- {# {{ manga.volumes|length }} #}
- {# #}
-
- {% set total_chapters = manga.chapters|length %}
- {% set available_chapters = manga.chapters|filter(chapter => chapter.cbzPath is not null)|length %}
-
-
-
{{ available_chapters }} / {{ total_chapters }}
-
-
-
- {% else %}
-
- Aucun manga trouvé.
-
- {% endfor %}
-
-
-
- {% endif %}
-{% endblock %}
diff --git a/templates/manga/_manga_details.html.twig b/templates/manga/_manga_details.html.twig
deleted file mode 100644
index 6a7370a..0000000
--- a/templates/manga/_manga_details.html.twig
+++ /dev/null
@@ -1,240 +0,0 @@
-{% block body %}
-
-
-
-
-
-
-
-
-
-
-
-
{{ manga.title }}
-
-
- {{ manga.publicationYear }}
- Chapters: {{ manga.chapters.count }}
-
-
-
- /media/mangas/{{ manga.title }} ({{ manga.publicationYear }})
- {{ manga.status ?? 'Terminé' }}
-
-
- {% set genre_count = 0 %}
- {% for genre in manga.genres %}
- {% if genre_count < 5 %}
- {{ genre }}
- {% set genre_count = genre_count + 1 %}
- {% endif %}
- {% endfor %}
- {% if genre_count == 5 and manga.genres|length > 5 %}
- ...
- {% endif %}
-
-
-
-
- {{ manga.rating|round(2) }}
-
-
{{ manga.description|truncate(500) }}
-
-
-
-
-
-
-
-
-
- {# Modal d'édition #}
-
- {% block content %}
- {{ form_start(form, {'action': path('app_manga_edit', {'id': manga.id}), 'attr': {'id': 'editForm', 'data-turbo-form': 'true'}}) }}
- {% do form.alternativeSlugs.setRendered() %}
-
-
- {{ form_row(form.title, {'label_attr': {'class': 'block text-sm font-medium text-gray-700'}, 'row_attr': {'class': 'mt-1'}}) }}
- {{ form_row(form.slug, {
- 'label_attr': {'class': 'block text-sm font-medium text-gray-700'},
- 'row_attr': {'class': 'mt-1'},
- 'attr': {
- 'class': 'w-full px-3 py-2 bg-gray-100 border border-gray-300 rounded-md focus:outline-none text-gray-500',
- 'readonly': true
- }
- }) }}
- {{ form_row(form.publicationYear, {'label_attr': {'class': 'block text-sm font-medium text-gray-700'}, 'row_attr': {'class': 'mt-1'}}) }}
- {{ form_row(form.description, {'label_attr': {'class': 'block text-sm font-medium text-gray-700'}, 'row_attr': {'class': 'mt-1'}}) }}
- {{ form_row(form.author, {'label_attr': {'class': 'block text-sm font-medium text-gray-700'}, 'row_attr': {'class': 'mt-1'}}) }}
- {{ form_row(form.status, {'label_attr': {'class': 'block text-sm font-medium text-gray-700'}, 'row_attr': {'class': 'mt-1'}}) }}
- {{ form_row(form.rating, {'label_attr': {'class': 'block text-sm font-medium text-gray-700'}, 'row_attr': {'class': 'mt-1'}}) }}
-
-
-
Slugs alternatifs
-
- {% for slug in form.alternativeSlugs %}
-
- {{ form_widget(slug, {'attr': {'class': 'bg-transparent border-none focus:outline-none focus:border-b focus:border-green-500 p-0 w-full'}}) }}
-
-
-
-
- {% endfor %}
-
-
- + Ajouter un slug alternatif
-
-
-
- {{ form_widget(form.alternativeSlugs.vars.prototype, {'attr': {'class': 'bg-transparent border-none focus:outline-none focus:border-b focus:border-green-500 p-0 w-full'}}) }}
-
-
-
-
-
-
-
-
-
{{ form_label(form.genres) }}
-
- {% for genre in form.genres %}
-
- {{ form_widget(genre, {'attr': {'class': 'bg-transparent border-none focus:outline-none focus:border-b focus:border-green-500 p-0 w-full'}}) }}
-
-
-
-
- {% endfor %}
-
-
- + Ajouter un genre
-
-
-
- {{ form_widget(form.genres.vars.prototype, {'attr': {'class': 'bg-transparent border-none focus:outline-none focus:border-b focus:border-green-500 p-0 w-full'}}) }}
-
-
-
-
-
-
-
- {{ form_end(form) }}
- {% endblock %}
- {% block footer %}
-
- Cancel
-
-
- Save
-
- {% endblock %}
-
-
- {# Modal de confirmation de suppression #}
-
-
-
- Are you sure you want to delete this manga? This action cannot be undone.
-
-
-
-
- Delete
-
-
- Cancel
-
-
-
-
-
- {% block content %}
- s.id)|json_encode,
- allSources: contentSources|map(s => {
- id: s.id,
- name: s.cleanBaseUrl
- })|json_encode
- }) }}>
-
-
Preferred Sources
-
- {% for source in manga.preferredSources %}
-
- {{ source.cleanBaseUrl }}
-
-
-
-
- {% endfor %}
-
-
-
-
Available Sources
-
- {% for source in contentSources %}
- {% if source not in manga.preferredSources %}
-
- {{ source.cleanBaseUrl }}
-
-
-
-
- {% endif %}
- {% endfor %}
-
-
-
- {% endblock %}
-
- {% block footer %}
-
- Close
-
- {% endblock %}
-
-{% endblock %}
-
diff --git a/templates/manga/add_new.html.twig b/templates/manga/add_new.html.twig
deleted file mode 100644
index c73c410..0000000
--- a/templates/manga/add_new.html.twig
+++ /dev/null
@@ -1,6 +0,0 @@
-{% extends 'base.html.twig' %}
-{% block body %}
-
- {{ component('MangaSearch', {query: query}) }}
-
-{% endblock %}
diff --git a/templates/manga/index.html.twig b/templates/manga/index.html.twig
deleted file mode 100644
index fc05465..0000000
--- a/templates/manga/index.html.twig
+++ /dev/null
@@ -1,9 +0,0 @@
-{% extends 'base.html.twig' %}
-{% block toolbar %}
- {% if toolbar is defined %}
-
- {% endif %}
-{% endblock %}
-{% block body %}
- {% include 'manga/_list.html.twig' %}
-{% endblock %}
diff --git a/templates/manga/manga_reader.html.twig b/templates/manga/manga_reader.html.twig
deleted file mode 100644
index 90b17f4..0000000
--- a/templates/manga/manga_reader.html.twig
+++ /dev/null
@@ -1,61 +0,0 @@
-{% extends 'base.html.twig' %}
-
-{% block title %}{{ manga.title }} - Chapitre {{ chapter.number }}{% endblock %}
-
-{% block body %}
-
-
{{ manga.title }} - Chapitre {{ chapter.number }}
-
- {% set isCbz = chapter.cbzPath is not null %}
- {% set totalPages = isCbz ? totalPages : pages|length %}
- {% set currentPageNumber = isCbz ? currentPage : currentPage.number %}
-
-
- {% if currentPageNumber > 1 %}
-
« Précédent
- {% endif %}
- {% if currentPageNumber < totalPages %}
-
Suivant »
- {% endif %}
-
-
-
- {% if isCbz %}
- {% if currentPageNumber < totalPages %}
-
-
-
- {% else %}
-
- {% endif %}
- {% else %}
- {% if currentPageNumber < totalPages %}
-
-
-
- {% else %}
-
- {% endif %}
- {% endif %}
-
-
-
- {% if currentPageNumber > 1 %}
-
« Précédent
- {% endif %}
- {% if currentPageNumber < totalPages %}
-
Suivant »
- {% endif %}
-
-
-
- Page {{ currentPageNumber }} sur {{ totalPages }}
-
-
-{% endblock %}
diff --git a/templates/manga/show_chapters.html.twig b/templates/manga/show_chapters.html.twig
deleted file mode 100644
index 2e23e2d..0000000
--- a/templates/manga/show_chapters.html.twig
+++ /dev/null
@@ -1,10 +0,0 @@
-{% extends 'base.html.twig' %}
-{% block toolbar %}
- {% if toolbar is defined %}
-
- {% endif %}
-{% endblock %}
-{% block body %}
- {% include 'manga/_manga_details.html.twig' %}
-{% endblock %}
-
diff --git a/templates/menu/menu.html.twig b/templates/menu/menu.html.twig
deleted file mode 100644
index d7db033..0000000
--- a/templates/menu/menu.html.twig
+++ /dev/null
@@ -1,68 +0,0 @@
-
diff --git a/templates/react/index.html.twig b/templates/react/index.html.twig
deleted file mode 100644
index 74bedc4..0000000
--- a/templates/react/index.html.twig
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
- React App
- {{ encore_entry_link_tags('react-app') }}
-
-
-
- {{ encore_entry_script_tags('react-app') }}
-
-
\ No newline at end of file
diff --git a/templates/reader/index.html.twig b/templates/reader/index.html.twig
deleted file mode 100644
index 45fb441..0000000
--- a/templates/reader/index.html.twig
+++ /dev/null
@@ -1,31 +0,0 @@
-{% extends 'base.html.twig' %}
-
-{% block title %}{{ manga.title }} - Chapitre {{ chapter.number }}{% endblock %}
-
-{% block body %}
-
-
{{ manga.title }} - Chapitre {{ chapter.number }}
-
-
- « Chapitre précédent
-
- Chapitre suivant »
-
-
-
- Passer en mode vertical
-
-
-
-
-
-
-
- Page 1 sur {{ totalPages }}
-
-
-{% endblock %}
diff --git a/templates/settings/folders.html.twig b/templates/settings/folders.html.twig
deleted file mode 100644
index 343fcc9..0000000
--- a/templates/settings/folders.html.twig
+++ /dev/null
@@ -1,62 +0,0 @@
-{% extends 'base.html.twig' %}
-
-{% block title %}Application Settings{% endblock %}
-
-{% block body %}
-
-
-
-
- Application Settings
-
-
-
- {{ form_start(form, {'attr': {'class': 'space-y-6'}}) }}
-
- {{ form_label(form.mangaDirectory, 'Manga Directory', {'label_attr': {'class': 'block text-sm font-medium text-gray-700 mb-2'}}) }}
- {{ form_widget(form.mangaDirectory, {'attr': {
- 'class': 'mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-green-500 focus:border-green-500 sm:text-sm',
- 'placeholder': '/path/to/manga/directory'
- }}) }}
- {{ form_errors(form.mangaDirectory) }}
-
-
-
- {{ form_label(form.imageDirectory, 'Image Directory', {'label_attr': {'class': 'block text-sm font-medium text-gray-700 mb-2'}}) }}
- {{ form_widget(form.imageDirectory, {'attr': {
- 'class': 'mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-green-500 focus:border-green-500 sm:text-sm',
- 'placeholder': '/path/to/image/directory'
- }}) }}
- {{ form_errors(form.imageDirectory) }}
-
-
-
-
- Save Settings
-
-
- {{ form_end(form) }}
-
-
-
-
-
-
- Current Settings
-
-
-
-
-
-
Manga Directory
- {{ form.mangaDirectory.vars.value }}
-
-
-
Image Directory
- {{ form.imageDirectory.vars.value }}
-
-
-
-
-
-{% endblock %}
diff --git a/templates/settings/index.html.twig b/templates/settings/index.html.twig
deleted file mode 100644
index a94ff44..0000000
--- a/templates/settings/index.html.twig
+++ /dev/null
@@ -1,20 +0,0 @@
-{% extends 'base.html.twig' %}
-
-{% block title %}Hello SettingsController!{% endblock %}
-
-{% block body %}
-
-
-
-
Hello {{ controller_name }}! ✅
-
- This friendly message is coming from:
-
-
-{% endblock %}
diff --git a/templates/settings/scrapper_list.html.twig b/templates/settings/scrapper_list.html.twig
deleted file mode 100644
index b3b48ca..0000000
--- a/templates/settings/scrapper_list.html.twig
+++ /dev/null
@@ -1,81 +0,0 @@
-{% extends 'base.html.twig' %}
-{% block toolbar %}
- {% if toolbar is defined %}
-
- {% endif %}
-{% endblock %}
-{% block title %}Scrapper Configurations{% endblock %}
-
-{% block body %}
-
-
Scrapper Configurations
-
-
- {% for contentSource in contentSources %}
-
-
-
-
- {{ contentSource.baseUrl|replace({'http://': '', 'https://': ''})|trim('/', 'right') }}
-
-
-
-
-
-
-
-
-
- {{ contentSource.scrapingType }}
-
-
- {{ contentSource.nextPageSelector ? 'Horizontal' : 'Vertical' }}
-
-
-
-
- Edit configuration
-
-
- {% endfor %}
-
-
-
-
-
Add New Configuration
-
-
-
-
-
-
- {% block content %}
-
-
-
-
-
- Cancel
-
-
- Submit
-
-
- {% endblock %}
- {% block footer %}
- {% endblock %}
-
-{% endblock %}
diff --git a/templates/settings/scrappers.html.twig b/templates/settings/scrappers.html.twig
deleted file mode 100644
index 242259c..0000000
--- a/templates/settings/scrappers.html.twig
+++ /dev/null
@@ -1,66 +0,0 @@
-{% extends 'base.html.twig' %}
-
-{% block title %}{{ isNew ? 'Create' : 'Edit' }} Scrapper Configuration{% endblock %}
-
-{% block body %}
-
-
-
-
- {{ isNew ? 'Create' : 'Edit' }} Scrapper Configuration
-
-
-
- {{ form_start(form, {'attr': {'class': 'space-y-6', 'data-scrapper-configure-target': 'form', 'data-action': 'submit->scrapper-configure#saveConfiguration'}}) }}
-
- {% for field in form.children %}
-
- {{ form_label(field, null, {'label_attr': {'class': 'block text-sm font-medium text-gray-700 mb-2'}}) }}
- {{ form_widget(field, {'attr': {
- 'class': 'mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-green-500 focus:border-green-500 sm:text-sm',
- 'placeholder': get_placeholder(field.vars.name)
- }}) }}
-
- {% endfor %}
-
-
-
- {{ isNew ? 'Save' : 'Update' }} Configuration
-
-
-
-
- {{ form_end(form) }}
-
-
-
- Test Configuration
-
-
-
-
-
-
-
-
-{% endblock %}
diff --git a/templates/system/index.html.twig b/templates/system/index.html.twig
deleted file mode 100644
index da85fb6..0000000
--- a/templates/system/index.html.twig
+++ /dev/null
@@ -1,20 +0,0 @@
-{% extends 'base.html.twig' %}
-
-{% block title %}Hello SystemController!{% endblock %}
-
-{% block body %}
-
-
-
-
Hello {{ controller_name }}! ✅
-
- This friendly message is coming from:
-
-
-{% endblock %}
diff --git a/src/Factory/ApiTokenFactory.php b/tests/Factory/ApiTokenFactory.php
similarity index 98%
rename from src/Factory/ApiTokenFactory.php
rename to tests/Factory/ApiTokenFactory.php
index a4b274f..88b190e 100644
--- a/src/Factory/ApiTokenFactory.php
+++ b/tests/Factory/ApiTokenFactory.php
@@ -1,6 +1,6 @@
$title,
- 'slug' => $this->slugger->slug($title)->lower(),
+ 'slug' => strtolower(str_replace(' ', '-', $title)),
'imageUrl' => self::faker()->optional()->imageUrl(),
'publicationYear' => self::faker()->optional()->year(),
'description' => self::faker()->optional()->text(),
diff --git a/src/Factory/PageFactory.php b/tests/Factory/PageFactory.php
similarity index 98%
rename from src/Factory/PageFactory.php
rename to tests/Factory/PageFactory.php
index 4e56d05..1f57ef0 100644
--- a/src/Factory/PageFactory.php
+++ b/tests/Factory/PageFactory.php
@@ -1,6 +1,6 @@