🛠 Fixes for GraphQL related schema changes
This commit is contained in:
@@ -1,2 +1,2 @@
|
||||
export const COMICS_DIRECTORY = "./comics";
|
||||
export const USERDATA_DIRECTORY = "./userdata";
|
||||
export const COMICS_DIRECTORY = process.env.COMICS_DIRECTORY || "./comics";
|
||||
export const USERDATA_DIRECTORY = process.env.USERDATA_DIRECTORY || "./userdata";
|
||||
@@ -17,23 +17,15 @@ services:
|
||||
hostname: kafka1
|
||||
container_name: kafka1
|
||||
ports:
|
||||
- "9092:9092"
|
||||
- "29092:29092"
|
||||
- "9999:9999"
|
||||
- "127.0.0.1:9092:9092" # exposed ONLY to host localhost
|
||||
environment:
|
||||
KAFKA_ADVERTISED_LISTENERS: INTERNAL://kafka1:19092,EXTERNAL://${DOCKER_HOST_IP:-127.0.0.1}:9092,DOCKER://host.docker.internal:29092
|
||||
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT,DOCKER:PLAINTEXT
|
||||
KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL
|
||||
KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092
|
||||
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
|
||||
KAFKA_ZOOKEEPER_CONNECT: "zoo1:2181"
|
||||
KAFKA_BROKER_ID: 1
|
||||
KAFKA_LOG4J_LOGGERS: "kafka.controller=INFO,kafka.producer.async.DefaultEventHandler=INFO,state.change.logger=INFO"
|
||||
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
|
||||
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
|
||||
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
|
||||
KAFKA_JMX_PORT: 9999
|
||||
KAFKA_JMX_HOSTNAME: ${DOCKER_HOST_IP:-127.0.0.1}
|
||||
KAFKA_AUTHORIZER_CLASS_NAME: kafka.security.authorizer.AclAuthorizer
|
||||
KAFKA_ALLOW_EVERYONE_IF_NO_ACL_FOUND: "true"
|
||||
depends_on:
|
||||
- zoo1
|
||||
networks:
|
||||
|
||||
@@ -129,6 +129,10 @@ const ComicSchema = mongoose.Schema(
|
||||
wanted: wantedSchema,
|
||||
|
||||
acquisition: {
|
||||
source: {
|
||||
wanted: { type: Boolean, default: false },
|
||||
name: { type: String, default: null },
|
||||
},
|
||||
release: {},
|
||||
directconnect: {
|
||||
downloads: {
|
||||
|
||||
536
package-lock.json
generated
536
package-lock.json
generated
@@ -1734,6 +1734,16 @@
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz",
|
||||
"integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@es-joy/jsdoccomment": {
|
||||
"version": "0.39.4",
|
||||
"resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.39.4.tgz",
|
||||
@@ -1916,6 +1926,32 @@
|
||||
"@img/sharp-libvips-darwin-arm64": "1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-x64": {
|
||||
"version": "0.33.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.3.tgz",
|
||||
"integrity": "sha512-2QeSl7QDK9ru//YBT4sQkoq7L0EAJZA3rtV+v9p8xTKl4U1bUqTIaCnoC7Ctx2kCjQgwFXDasOtPTCT8eCTXvw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"glibc": ">=2.26",
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-x64": "1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-darwin-arm64": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.2.tgz",
|
||||
@@ -1937,6 +1973,382 @@
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-darwin-x64": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.2.tgz",
|
||||
"integrity": "sha512-Ofw+7oaWa0HiiMiKWqqaZbaYV3/UGL2wAPeLuJTx+9cXpCRdvQhCLG0IH8YGwM0yGWGLpsF4Su9vM1o6aer+Fw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"macos": ">=10.13",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-arm": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.2.tgz",
|
||||
"integrity": "sha512-iLWCvrKgeFoglQxdEwzu1eQV04o8YeYGFXtfWU26Zr2wWT3q3MTzC+QTCO3ZQfWd3doKHT4Pm2kRmLbupT+sZw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"glibc": ">=2.28",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-arm64": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.2.tgz",
|
||||
"integrity": "sha512-x7kCt3N00ofFmmkkdshwj3vGPCnmiDh7Gwnd4nUwZln2YjqPxV1NlTyZOvoDWdKQVDL911487HOueBvrpflagw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"glibc": ">=2.26",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-s390x": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.2.tgz",
|
||||
"integrity": "sha512-cmhQ1J4qVhfmS6szYW7RT+gLJq9dH2i4maq+qyXayUSn9/3iY2ZeWpbAgSpSVbV2E1JUL2Gg7pwnYQ1h8rQIog==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"glibc": ">=2.28",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-x64": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.2.tgz",
|
||||
"integrity": "sha512-E441q4Qdb+7yuyiADVi5J+44x8ctlrqn8XgkDTwr4qPJzWkaHwD489iZ4nGDgcuya4iMN3ULV6NwbhRZJ9Z7SQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"glibc": ">=2.26",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.2.tgz",
|
||||
"integrity": "sha512-3CAkndNpYUrlDqkCM5qhksfE+qSIREVpyoeHIU6jd48SJZViAmznoQQLAv4hVXF7xyUB9zf+G++e2v1ABjCbEQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"musl": ">=1.2.2",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.2.tgz",
|
||||
"integrity": "sha512-VI94Q6khIHqHWNOh6LLdm9s2Ry4zdjWJwH56WoiJU7NTeDwyApdZZ8c+SADC8OH98KWNQXnE01UdJ9CSfZvwZw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"musl": ">=1.2.2",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-arm": {
|
||||
"version": "0.33.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.3.tgz",
|
||||
"integrity": "sha512-Q7Ee3fFSC9P7vUSqVEF0zccJsZ8GiiCJYGWDdhEjdlOeS9/jdkyJ6sUSPj+bL8VuOYFSbofrW0t/86ceVhx32w==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"glibc": ">=2.28",
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm": "1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-arm64": {
|
||||
"version": "0.33.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.3.tgz",
|
||||
"integrity": "sha512-Zf+sF1jHZJKA6Gor9hoYG2ljr4wo9cY4twaxgFDvlG0Xz9V7sinsPp8pFd1XtlhTzYo0IhDbl3rK7P6MzHpnYA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"glibc": ">=2.26",
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm64": "1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-s390x": {
|
||||
"version": "0.33.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.3.tgz",
|
||||
"integrity": "sha512-vFk441DKRFepjhTEH20oBlFrHcLjPfI8B0pMIxGm3+yilKyYeHEVvrZhYFdqIseSclIqbQ3SnZMwEMWonY5XFA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"glibc": ">=2.28",
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-s390x": "1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-x64": {
|
||||
"version": "0.33.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.3.tgz",
|
||||
"integrity": "sha512-Q4I++herIJxJi+qmbySd072oDPRkCg/SClLEIDh5IL9h1zjhqjv82H0Seupd+q2m0yOfD+/fJnjSoDFtKiHu2g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"glibc": ">=2.26",
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-x64": "1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linuxmusl-arm64": {
|
||||
"version": "0.33.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.3.tgz",
|
||||
"integrity": "sha512-qnDccehRDXadhM9PM5hLvcPRYqyFCBN31kq+ErBSZtZlsAc1U4Z85xf/RXv1qolkdu+ibw64fUDaRdktxTNP9A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"musl": ">=1.2.2",
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linuxmusl-x64": {
|
||||
"version": "0.33.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.3.tgz",
|
||||
"integrity": "sha512-Jhchim8kHWIU/GZ+9poHMWRcefeaxFIs9EBqf9KtcC14Ojk6qua7ghKiPs0sbeLbLj/2IGBtDcxHyjCdYWkk2w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"musl": ">=1.2.2",
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-wasm32": {
|
||||
"version": "0.33.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.3.tgz",
|
||||
"integrity": "sha512-68zivsdJ0koE96stdUfM+gmyaK/NcoSZK5dV5CAjES0FUXS9lchYt8LAB5rTbM7nlWtxaU/2GON0HVN6/ZYJAQ==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/runtime": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-ia32": {
|
||||
"version": "0.33.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.3.tgz",
|
||||
"integrity": "sha512-CyimAduT2whQD8ER4Ux7exKrtfoaUiVr7HG0zZvO0XTFn2idUWljjxv58GxNTkFb8/J9Ub9AqITGkJD6ZginxQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-x64": {
|
||||
"version": "0.33.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.3.tgz",
|
||||
"integrity": "sha512-viT4fUIDKnli3IfOephGnolMzhz5VaTvDRkYqtZxOMIoMQ4MrAziO7pT1nVnOt2FAm7qW5aa+CCc13aEY6Le0g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@ioredis/commands": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz",
|
||||
@@ -1981,10 +2393,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": {
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
||||
"version": "3.14.2",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
|
||||
"integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^1.0.7",
|
||||
"esprima": "^4.0.0"
|
||||
@@ -2435,6 +2848,71 @@
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.2.tgz",
|
||||
"integrity": "sha512-lwriRAHm1Yg4iDf23Oxm9n/t5Zpw1lVnxYU3HnJPTi2lJRkKTrps1KVgvL6m7WvmhYVt/FIsssWay+k45QHeuw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.2.tgz",
|
||||
"integrity": "sha512-MOI9Dlfrpi2Cuc7i5dXdxPbFIgbDBGgKR5F2yWEa6FVEtSWncfVNKW5AKjImAQ6CZlBK9tympdsZJ2xThBiWWA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.2.tgz",
|
||||
"integrity": "sha512-FU20Bo66/f7He9Fp9sP2zaJ1Q8L9uLPZQDub/WlUip78JlPeMbVL8546HbZfcW9LNciEXc8d+tThSJjSC+tmsg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.2.tgz",
|
||||
"integrity": "sha512-gsWNDCklNy7Ajk0vBBf9jEx04RUxuDQfBse918Ww+Qb9HCPoGzS+XJTLe96iN3BVK7grnLiYghP/M4L8VsaHeA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.2.tgz",
|
||||
"integrity": "sha512-O+6Gs8UeDbyFpbSh2CPEz/UOrrdWPTBYNblZK5CxxLisYt4kGX3Sc+czffFonyjiGSq3jWLwJS/CCJc7tBr4sQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
@@ -4297,13 +4775,13 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
|
||||
"integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
|
||||
"integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"form-data": "^4.0.4",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
@@ -6030,13 +6508,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz",
|
||||
"integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==",
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.1.3",
|
||||
"has": "^1.0.3",
|
||||
"has-tostringtag": "^1.0.0"
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -7253,12 +7733,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
||||
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
@@ -9519,9 +10002,10 @@
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
},
|
||||
@@ -14360,14 +14844,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/r2-shared-js": {
|
||||
"version": "1.0.84",
|
||||
"resolved": "https://registry.npmjs.org/r2-shared-js/-/r2-shared-js-1.0.84.tgz",
|
||||
"integrity": "sha512-Nm5ywbfndertd0zgu/CjSXcfW8T5Sm0ZnLIL/b9enrFw+9Xy2NAiHjz0Yu3pFLLD+zjvVKLUrFj6MnQpJwLEPw==",
|
||||
"version": "1.0.85",
|
||||
"resolved": "https://registry.npmjs.org/r2-shared-js/-/r2-shared-js-1.0.85.tgz",
|
||||
"integrity": "sha512-5+0gdzG85fD8bmyDg3CBWGQzbfuZXqZC2bC13ML6Tr29kKY/I+6TcfAv5IxE04bxoY3o6rMzFPhq6D2PDZNUWA==",
|
||||
"hasInstallScript": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@xmldom/xmldom": "^0.9.8",
|
||||
"debug": "^4.4.1",
|
||||
"debug": "^4.4.3",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"he": "^1.2.0",
|
||||
"image-size": "^2.0.2",
|
||||
@@ -14390,9 +14874,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/r2-shared-js/node_modules/debug": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
||||
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
|
||||
@@ -191,7 +191,9 @@ export default class JobQueueService extends Service {
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`An error occurred processing Job ID ${ctx.locals.job.id}`
|
||||
`An error occurred processing Job ID ${ctx.locals.job.id}:`,
|
||||
error instanceof Error ? error.message : error,
|
||||
error instanceof Error ? error.stack : ""
|
||||
);
|
||||
throw new MoleculerError(
|
||||
error,
|
||||
|
||||
@@ -174,8 +174,13 @@ export default class ImportService extends Service {
|
||||
try {
|
||||
// Get params to be passed to the import jobs
|
||||
const { sessionId } = ctx.params;
|
||||
const resolvedPath = path.resolve(COMICS_DIRECTORY);
|
||||
console.log(`Walking comics directory: ${resolvedPath}`);
|
||||
// 1. Walk the Source folder
|
||||
klaw(path.resolve(COMICS_DIRECTORY))
|
||||
klaw(resolvedPath)
|
||||
.on("error", (err) => {
|
||||
console.error(`Error walking directory ${resolvedPath}:`, err);
|
||||
})
|
||||
// 1.1 Filter on .cb* extensions
|
||||
.pipe(
|
||||
through2.obj(function (item, enc, next) {
|
||||
|
||||
@@ -174,14 +174,16 @@ export const getMimeType = async (filePath: string) => {
|
||||
*/
|
||||
export const createDirectory = async (options: any, directoryPath: string) => {
|
||||
try {
|
||||
await fse.ensureDir(directoryPath, options);
|
||||
await fse.ensureDir(directoryPath);
|
||||
console.info(`Directory [ %s ] was created.`, directoryPath);
|
||||
} catch (error) {
|
||||
const errMsg = error instanceof Error ? error.message : String(error);
|
||||
console.error(`Failed to create directory [ ${directoryPath} ]:`, error);
|
||||
throw new Errors.MoleculerError(
|
||||
"Failed to create directory",
|
||||
`Failed to create directory: ${directoryPath} - ${errMsg}`,
|
||||
500,
|
||||
"FileOpsError",
|
||||
error
|
||||
{ directoryPath, originalError: errMsg }
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -32,6 +32,7 @@ SOFTWARE.
|
||||
*/
|
||||
|
||||
import { createReadStream, createWriteStream, existsSync, statSync } from "fs";
|
||||
import { execFile } from "child_process";
|
||||
import { isEmpty, isNil, isUndefined, remove, each, map, reject } from "lodash";
|
||||
import * as p7zip from "p7zip-threetwo";
|
||||
import path from "path";
|
||||
@@ -88,124 +89,335 @@ export const extractComicInfoXMLFromRar = async (
|
||||
)}`;
|
||||
await createDirectory(directoryOptions, targetDirectory);
|
||||
|
||||
const archive = new Unrar({
|
||||
path: path.resolve(filePath),
|
||||
bin: `${UNRAR_BIN_PATH}`, // this will change depending on Docker base OS
|
||||
arguments: ["-v"],
|
||||
});
|
||||
const filesInArchive: [RarFile] = await new Promise(
|
||||
(resolve, reject) => {
|
||||
return archive.list((err, entries) => {
|
||||
if (err) {
|
||||
console.log(`DEBUG: ${JSON.stringify(err, null, 2)}`);
|
||||
reject(err);
|
||||
}
|
||||
resolve(entries);
|
||||
});
|
||||
}
|
||||
);
|
||||
// Try unrar-based extraction first, fall back to p7zip if it fails
|
||||
let unrarError: Error | null = null;
|
||||
try {
|
||||
const result = await extractComicInfoXMLFromRarUsingUnrar(
|
||||
filePath,
|
||||
mimeType,
|
||||
targetDirectory,
|
||||
fileNameWithoutExtension,
|
||||
extension
|
||||
);
|
||||
return result;
|
||||
} catch (err) {
|
||||
unrarError = err;
|
||||
console.warn(
|
||||
`unrar-based extraction failed for ${filePath}: ${err.message}. Falling back to p7zip.`
|
||||
);
|
||||
}
|
||||
|
||||
remove(filesInArchive, ({ type }) => type === "Directory");
|
||||
const comicInfoXML = remove(
|
||||
filesInArchive,
|
||||
({ name }) => path.basename(name).toLowerCase() === "comicinfo.xml"
|
||||
);
|
||||
try {
|
||||
const result = await extractComicInfoXMLFromRarUsingP7zip(
|
||||
filePath,
|
||||
mimeType,
|
||||
targetDirectory,
|
||||
fileNameWithoutExtension,
|
||||
extension
|
||||
);
|
||||
return result;
|
||||
} catch (p7zipError) {
|
||||
console.error(
|
||||
`p7zip-based extraction also failed for ${filePath}: ${p7zipError.message}`
|
||||
);
|
||||
throw new Error(
|
||||
`Failed to extract RAR archive: ${filePath}. ` +
|
||||
`unrar error: ${unrarError?.message}. ` +
|
||||
`p7zip error: ${p7zipError.message}. ` +
|
||||
`Ensure 'unrar' is installed at ${UNRAR_BIN_PATH} or '7z' is available via SEVENZ_BINARY_PATH.`
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
remove(
|
||||
filesInArchive,
|
||||
({ name }) =>
|
||||
!IMPORT_IMAGE_FILE_FORMATS.includes(
|
||||
path.extname(name).toLowerCase()
|
||||
)
|
||||
);
|
||||
const files = filesInArchive.sort((a, b) => {
|
||||
if (!isUndefined(a) && !isUndefined(b)) {
|
||||
return path
|
||||
.basename(a.name)
|
||||
.toLowerCase()
|
||||
.localeCompare(path.basename(b.name).toLowerCase());
|
||||
}
|
||||
});
|
||||
const comicInfoXMLFilePromise = new Promise((resolve, reject) => {
|
||||
let comicinfostring = "";
|
||||
if (!isUndefined(comicInfoXML[0])) {
|
||||
const comicInfoXMLFileName = path.basename(
|
||||
comicInfoXML[0].name
|
||||
);
|
||||
const writeStream = createWriteStream(
|
||||
`${targetDirectory}/${comicInfoXMLFileName}`
|
||||
);
|
||||
|
||||
archive.stream(comicInfoXML[0]["name"]).pipe(writeStream);
|
||||
writeStream.on("finish", async () => {
|
||||
console.log(`Attempting to write comicInfo.xml...`);
|
||||
const readStream = createReadStream(
|
||||
`${targetDirectory}/${comicInfoXMLFileName}`
|
||||
);
|
||||
readStream.on("data", (data) => {
|
||||
comicinfostring += data;
|
||||
});
|
||||
readStream.on("error", (error) => reject(error));
|
||||
readStream.on("end", async () => {
|
||||
if (
|
||||
existsSync(
|
||||
`${targetDirectory}/${comicInfoXMLFileName}`
|
||||
)
|
||||
) {
|
||||
const comicInfoJSON = await convertXMLToJSON(
|
||||
comicinfostring.toString()
|
||||
);
|
||||
console.log(
|
||||
`comicInfo.xml successfully written: ${comicInfoJSON.comicinfo}`
|
||||
);
|
||||
resolve({ comicInfoJSON: comicInfoJSON.comicinfo });
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
resolve({ comicInfoJSON: null });
|
||||
}
|
||||
});
|
||||
|
||||
const coverFilePromise = new Promise((resolve, reject) => {
|
||||
const coverFile = path.basename(files[0].name);
|
||||
const sharpStream = sharp().resize(275).toFormat("png");
|
||||
const coverExtractionStream = archive.stream(files[0].name);
|
||||
const resizeStream = coverExtractionStream.pipe(sharpStream);
|
||||
resizeStream.toFile(
|
||||
`${targetDirectory}/${coverFile}`,
|
||||
(err, info) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
checkFileExists(`${targetDirectory}/${coverFile}`).then(
|
||||
(bool) => {
|
||||
console.log(`${coverFile} exists: ${bool}`);
|
||||
// orchestrate result
|
||||
resolve({
|
||||
filePath,
|
||||
name: fileNameWithoutExtension,
|
||||
extension,
|
||||
containedIn: targetDirectory,
|
||||
fileSize: fse.statSync(filePath).size,
|
||||
mimeType,
|
||||
cover: {
|
||||
filePath: path.relative(
|
||||
process.cwd(),
|
||||
`${targetDirectory}/${coverFile}`
|
||||
),
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* List files in a RAR archive using the unrar binary directly.
|
||||
* Uses `unrar lb` (bare list) for reliable output — one filename per line.
|
||||
*/
|
||||
const listRarFiles = (filePath: string): Promise<string[]> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile(
|
||||
UNRAR_BIN_PATH,
|
||||
["lb", path.resolve(filePath)],
|
||||
{ maxBuffer: 10 * 1024 * 1024 },
|
||||
(err, stdout, stderr) => {
|
||||
if (err) {
|
||||
return reject(
|
||||
new Error(
|
||||
`unrar lb failed for ${filePath}: ${err.message}${stderr ? ` (stderr: ${stderr})` : ""}`
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
const files = stdout
|
||||
.split(/\r?\n/)
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line.length > 0);
|
||||
resolve(files);
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
return Promise.all([comicInfoXMLFilePromise, coverFilePromise]);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
/**
|
||||
* Extract a single file from a RAR archive to stdout as a Buffer.
|
||||
* Uses `unrar p -inul` (print to stdout, no messages).
|
||||
*/
|
||||
const extractRarFileToBuffer = (
|
||||
filePath: string,
|
||||
entryName: string
|
||||
): Promise<Buffer> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile(
|
||||
UNRAR_BIN_PATH,
|
||||
["p", "-inul", path.resolve(filePath), entryName],
|
||||
{ maxBuffer: 50 * 1024 * 1024, encoding: "buffer" },
|
||||
(err, stdout, stderr) => {
|
||||
if (err) {
|
||||
return reject(
|
||||
new Error(
|
||||
`unrar p failed for ${entryName} in ${filePath}: ${err.message}`
|
||||
)
|
||||
);
|
||||
}
|
||||
resolve(stdout as unknown as Buffer);
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract comic info and cover from a RAR archive using the unrar binary directly.
|
||||
* Bypasses the `unrar` npm package which has parsing bugs.
|
||||
*/
|
||||
const extractComicInfoXMLFromRarUsingUnrar = async (
|
||||
filePath: string,
|
||||
mimeType: string,
|
||||
targetDirectory: string,
|
||||
fileNameWithoutExtension: string,
|
||||
extension: string
|
||||
): Promise<any> => {
|
||||
// List all files in the archive using bare listing
|
||||
const allFiles = await listRarFiles(filePath);
|
||||
|
||||
console.log(
|
||||
`RAR (unrar direct): ${allFiles.length} total entries in ${filePath}`
|
||||
);
|
||||
|
||||
// Find ComicInfo.xml
|
||||
const comicInfoXMLEntry = allFiles.find(
|
||||
(name) => path.basename(name).toLowerCase() === "comicinfo.xml"
|
||||
);
|
||||
|
||||
// Filter to image files only
|
||||
const imageFiles = allFiles
|
||||
.filter((name) =>
|
||||
IMPORT_IMAGE_FILE_FORMATS.includes(
|
||||
path.extname(name).toLowerCase()
|
||||
)
|
||||
)
|
||||
.sort((a, b) =>
|
||||
path
|
||||
.basename(a)
|
||||
.toLowerCase()
|
||||
.localeCompare(path.basename(b).toLowerCase())
|
||||
);
|
||||
|
||||
if (imageFiles.length === 0) {
|
||||
throw new Error(
|
||||
`No image files found via unrar in RAR archive: ${filePath}`
|
||||
);
|
||||
}
|
||||
|
||||
// Extract and parse ComicInfo.xml if present
|
||||
let comicInfoResult: { comicInfoJSON: any } = { comicInfoJSON: null };
|
||||
if (comicInfoXMLEntry) {
|
||||
try {
|
||||
const xmlBuffer = await extractRarFileToBuffer(
|
||||
filePath,
|
||||
comicInfoXMLEntry
|
||||
);
|
||||
const comicInfoJSON = await convertXMLToJSON(
|
||||
xmlBuffer.toString("utf-8")
|
||||
);
|
||||
console.log(
|
||||
`comicInfo.xml successfully extracted: ${comicInfoJSON.comicinfo}`
|
||||
);
|
||||
comicInfoResult = { comicInfoJSON: comicInfoJSON.comicinfo };
|
||||
} catch (xmlErr) {
|
||||
console.warn(
|
||||
`Failed to extract ComicInfo.xml from ${filePath}: ${xmlErr.message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Extract and resize cover image (first image file)
|
||||
const coverEntryName = imageFiles[0];
|
||||
const coverFile = path.basename(coverEntryName);
|
||||
const coverBaseName = sanitize(path.basename(coverFile, path.extname(coverFile)));
|
||||
const coverOutputFile = `${targetDirectory}/${coverBaseName}.png`;
|
||||
|
||||
const coverBuffer = await extractRarFileToBuffer(
|
||||
filePath,
|
||||
coverEntryName
|
||||
);
|
||||
|
||||
await sharp(coverBuffer)
|
||||
.resize(275)
|
||||
.toFormat("png")
|
||||
.toFile(coverOutputFile);
|
||||
|
||||
console.log(`${coverFile} cover written to: ${coverOutputFile}`);
|
||||
|
||||
const relativeCoverPath = path.relative(process.cwd(), coverOutputFile);
|
||||
console.log(`RAR cover path (relative): ${relativeCoverPath}`);
|
||||
console.log(`RAR cover file exists: ${existsSync(coverOutputFile)}`);
|
||||
|
||||
const coverResult = {
|
||||
filePath,
|
||||
name: fileNameWithoutExtension,
|
||||
extension,
|
||||
containedIn: targetDirectory,
|
||||
fileSize: fse.statSync(filePath).size,
|
||||
mimeType,
|
||||
cover: {
|
||||
filePath: relativeCoverPath,
|
||||
},
|
||||
};
|
||||
|
||||
return [comicInfoResult, coverResult];
|
||||
};
|
||||
|
||||
/**
|
||||
* Fallback: Extract comic info and cover from a RAR archive using p7zip (7z).
|
||||
* Uses the same approach as extractComicInfoXMLFromZip since p7zip handles RAR files.
|
||||
*/
|
||||
const extractComicInfoXMLFromRarUsingP7zip = async (
|
||||
filePath: string,
|
||||
mimeType: string,
|
||||
targetDirectory: string,
|
||||
fileNameWithoutExtension: string,
|
||||
extension: string
|
||||
): Promise<any> => {
|
||||
let filesToWriteToDisk = { coverFile: null, comicInfoXML: null };
|
||||
const extractionTargets = [];
|
||||
|
||||
// read the archive using p7zip (supports RAR)
|
||||
let filesFromArchive = await p7zip.read(path.resolve(filePath));
|
||||
|
||||
console.log(
|
||||
`RAR (p7zip): ${filesFromArchive.files.length} total entries in ${filePath}`
|
||||
);
|
||||
|
||||
// detect ComicInfo.xml
|
||||
const comicInfoXMLFileObject = remove(
|
||||
filesFromArchive.files,
|
||||
(file) => path.basename(file.name.toLowerCase()) === "comicinfo.xml"
|
||||
);
|
||||
// only allow allowed image formats
|
||||
remove(
|
||||
filesFromArchive.files,
|
||||
({ name }) =>
|
||||
!IMPORT_IMAGE_FILE_FORMATS.includes(
|
||||
path.extname(name).toLowerCase()
|
||||
)
|
||||
);
|
||||
|
||||
// Natural sort
|
||||
const files = filesFromArchive.files.sort((a, b) => {
|
||||
if (!isUndefined(a) && !isUndefined(b)) {
|
||||
return path
|
||||
.basename(a.name)
|
||||
.toLowerCase()
|
||||
.localeCompare(path.basename(b.name).toLowerCase());
|
||||
}
|
||||
});
|
||||
|
||||
if (files.length === 0) {
|
||||
throw new Error(`No image files found in RAR archive: ${filePath}`);
|
||||
}
|
||||
|
||||
// Push the first file (cover) to our extraction target
|
||||
extractionTargets.push(files[0].name);
|
||||
filesToWriteToDisk.coverFile = path.basename(files[0].name);
|
||||
|
||||
if (!isEmpty(comicInfoXMLFileObject)) {
|
||||
filesToWriteToDisk.comicInfoXML = comicInfoXMLFileObject[0].name;
|
||||
extractionTargets.push(filesToWriteToDisk.comicInfoXML);
|
||||
}
|
||||
// Extract the files.
|
||||
await p7zip.extract(
|
||||
filePath,
|
||||
targetDirectory,
|
||||
extractionTargets,
|
||||
"",
|
||||
false
|
||||
);
|
||||
|
||||
// ComicInfoXML detection, parsing and conversion to JSON
|
||||
const comicInfoXMLPromise = new Promise((resolve, reject) => {
|
||||
if (
|
||||
!isNil(filesToWriteToDisk.comicInfoXML) &&
|
||||
existsSync(
|
||||
`${targetDirectory}/${path.basename(
|
||||
filesToWriteToDisk.comicInfoXML
|
||||
)}`
|
||||
)
|
||||
) {
|
||||
let comicinfoString = "";
|
||||
const comicInfoXMLStream = createReadStream(
|
||||
`${targetDirectory}/${path.basename(
|
||||
filesToWriteToDisk.comicInfoXML
|
||||
)}`
|
||||
);
|
||||
comicInfoXMLStream.on(
|
||||
"data",
|
||||
(data) => (comicinfoString += data)
|
||||
);
|
||||
comicInfoXMLStream.on("end", async () => {
|
||||
const comicInfoJSON = await convertXMLToJSON(
|
||||
comicinfoString.toString()
|
||||
);
|
||||
resolve({
|
||||
comicInfoJSON: comicInfoJSON.comicinfo,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
resolve({
|
||||
comicInfoJSON: null,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Write the cover to disk
|
||||
const coverBaseName = sanitize(path.basename(
|
||||
filesToWriteToDisk.coverFile,
|
||||
path.extname(filesToWriteToDisk.coverFile)
|
||||
));
|
||||
const coverOutputFile = `${targetDirectory}/${coverBaseName}.png`;
|
||||
const coverInputFile = `${targetDirectory}/${filesToWriteToDisk.coverFile}`;
|
||||
|
||||
await sharp(coverInputFile)
|
||||
.resize(275)
|
||||
.toFormat("png")
|
||||
.toFile(coverOutputFile);
|
||||
|
||||
const comicInfoResult = await comicInfoXMLPromise;
|
||||
|
||||
const coverResult = {
|
||||
filePath,
|
||||
name: fileNameWithoutExtension,
|
||||
extension,
|
||||
mimeType,
|
||||
containedIn: targetDirectory,
|
||||
fileSize: fse.statSync(filePath).size,
|
||||
cover: {
|
||||
filePath: path.relative(process.cwd(), coverOutputFile),
|
||||
},
|
||||
};
|
||||
|
||||
return [comicInfoResult, coverResult];
|
||||
};
|
||||
|
||||
export const extractComicInfoXMLFromZip = async (
|
||||
@@ -252,6 +464,11 @@ export const extractComicInfoXMLFromZip = async (
|
||||
.localeCompare(path.basename(b.name).toLowerCase());
|
||||
}
|
||||
});
|
||||
|
||||
if (files.length === 0) {
|
||||
throw new Error(`No image files found in ZIP archive: ${filePath}`);
|
||||
}
|
||||
|
||||
// Push the first file (cover) to our extraction target
|
||||
extractionTargets.push(files[0].name);
|
||||
filesToWriteToDisk.coverFile = path.basename(files[0].name);
|
||||
@@ -306,45 +523,32 @@ export const extractComicInfoXMLFromZip = async (
|
||||
}
|
||||
});
|
||||
// Write the cover to disk
|
||||
const coverFilePromise = new Promise((resolve, reject) => {
|
||||
const sharpStream = sharp().resize(275).toFormat("png");
|
||||
const coverStream = createReadStream(
|
||||
`${targetDirectory}/${filesToWriteToDisk.coverFile}`
|
||||
);
|
||||
coverStream
|
||||
.pipe(sharpStream)
|
||||
.toFile(
|
||||
`${targetDirectory}/${path.basename(
|
||||
filesToWriteToDisk.coverFile
|
||||
)}`,
|
||||
(err, info) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
// Update metadata
|
||||
resolve({
|
||||
filePath,
|
||||
name: fileNameWithoutExtension,
|
||||
extension,
|
||||
mimeType,
|
||||
containedIn: targetDirectory,
|
||||
fileSize: fse.statSync(filePath).size,
|
||||
cover: {
|
||||
filePath: path.relative(
|
||||
process.cwd(),
|
||||
`${targetDirectory}/${path.basename(
|
||||
filesToWriteToDisk.coverFile
|
||||
)}`
|
||||
),
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
const coverBaseName = sanitize(path.basename(filesToWriteToDisk.coverFile, path.extname(filesToWriteToDisk.coverFile)));
|
||||
const coverOutputFile = `${targetDirectory}/${coverBaseName}.png`;
|
||||
const coverInputFile = `${targetDirectory}/${filesToWriteToDisk.coverFile}`;
|
||||
|
||||
return Promise.all([comicInfoXMLPromise, coverFilePromise]);
|
||||
await sharp(coverInputFile)
|
||||
.resize(275)
|
||||
.toFormat("png")
|
||||
.toFile(coverOutputFile);
|
||||
|
||||
const comicInfoResult = await comicInfoXMLPromise;
|
||||
|
||||
const coverResult = {
|
||||
filePath,
|
||||
name: fileNameWithoutExtension,
|
||||
extension,
|
||||
mimeType,
|
||||
containedIn: targetDirectory,
|
||||
fileSize: fse.statSync(filePath).size,
|
||||
cover: {
|
||||
filePath: path.relative(process.cwd(), coverOutputFile),
|
||||
},
|
||||
};
|
||||
|
||||
return [comicInfoResult, coverResult];
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -361,6 +565,9 @@ export const extractFromArchive = async (filePath: string) => {
|
||||
filePath,
|
||||
mimeType
|
||||
);
|
||||
if (!Array.isArray(cbzResult)) {
|
||||
throw new Error(`extractComicInfoXMLFromZip returned a non-iterable result for: ${filePath}`);
|
||||
}
|
||||
return Object.assign({}, ...cbzResult);
|
||||
|
||||
case "application/x-rar; charset=binary":
|
||||
@@ -368,6 +575,9 @@ export const extractFromArchive = async (filePath: string) => {
|
||||
filePath,
|
||||
mimeType
|
||||
);
|
||||
if (!Array.isArray(cbrResult)) {
|
||||
throw new Error(`extractComicInfoXMLFromRar returned a non-iterable result for: ${filePath}`);
|
||||
}
|
||||
return Object.assign({}, ...cbrResult);
|
||||
|
||||
default:
|
||||
@@ -419,7 +629,7 @@ export const uncompressZipArchive = async (filePath: string, options: any) => {
|
||||
mode: 0o2775,
|
||||
};
|
||||
const { fileNameWithoutExtension } = getFileConstituents(filePath);
|
||||
const targetDirectory = `${USERDATA_DIRECTORY}/expanded/${options.purpose}/${fileNameWithoutExtension}`;
|
||||
const targetDirectory = `${USERDATA_DIRECTORY}/expanded/${options.purpose}/${sanitize(fileNameWithoutExtension)}`;
|
||||
await createDirectory(directoryOptions, targetDirectory);
|
||||
await p7zip.extract(filePath, targetDirectory, [], "", false);
|
||||
|
||||
@@ -433,7 +643,7 @@ export const uncompressRarArchive = async (filePath: string, options: any) => {
|
||||
};
|
||||
const { fileNameWithoutExtension, extension } =
|
||||
getFileConstituents(filePath);
|
||||
const targetDirectory = `${USERDATA_DIRECTORY}/expanded/${options.purpose}/${fileNameWithoutExtension}`;
|
||||
const targetDirectory = `${USERDATA_DIRECTORY}/expanded/${options.purpose}/${sanitize(fileNameWithoutExtension)}`;
|
||||
await createDirectory(directoryOptions, targetDirectory);
|
||||
|
||||
const archive = new Unrar({
|
||||
|
||||
Reference in New Issue
Block a user