diff --git a/electron/api/anime/anime.service.js b/electron/api/anime/anime.service.js index 9cd05ac..969375c 100644 --- a/electron/api/anime/anime.service.js +++ b/electron/api/anime/anime.service.js @@ -230,8 +230,21 @@ async function getAnimeInfoExtension(ext, id) { try { const match = await ext.getMetadata(id); if (match) { - await (0, queries_1.cacheExtension)(extName, id, match.title, match); - return match; + const normalized = { + title: match.title ?? "Unknown", + summary: match.summary ?? "No summary available", + episodes: Number(match.episodes) || 0, + characters: Array.isArray(match.characters) ? match.characters : [], + season: match.season ?? null, + status: match.status ?? "Unknown", + studio: match.studio ?? "Unknown", + score: Number(match.score) || 0, + year: match.year ?? null, + genres: Array.isArray(match.genres) ? match.genres : [], + image: match.image ?? "" + }; + await (0, queries_1.cacheExtension)(extName, id, normalized.title, normalized); + return normalized; } } catch (e) { diff --git a/electron/api/books/books.service.js b/electron/api/books/books.service.js index f32ef67..a79fd86 100644 --- a/electron/api/books/books.service.js +++ b/electron/api/books/books.service.js @@ -226,15 +226,26 @@ async function getBookInfoExtension(ext, id) { try { return JSON.parse(cached.metadata); } - catch { - } + catch { } } if (ext.type === 'book-board' && ext.getMetadata) { try { const info = await ext.getMetadata(id); if (info) { - await (0, queries_1.cacheExtension)(extName, id, info.title, info); - return info; + const normalized = { + id: info.id ?? id, + title: info.title ?? "", + format: info.format ?? "", + score: typeof info.score === "number" ? info.score : null, + genres: Array.isArray(info.genres) ? info.genres : [], + status: info.status ?? "", + published: info.published ?? "", + summary: info.summary ?? "", + chapters: Number.isFinite(info.chapters) ? info.chapters : 1, + image: typeof info.image === "string" ? info.image : "" + }; + await (0, queries_1.cacheExtension)(extName, id, normalized.title, normalized); + return [normalized]; } } catch (e) { diff --git a/electron/api/gallery/gallery.controller.js b/electron/api/gallery/gallery.controller.js index 8f403e1..776958f 100644 --- a/electron/api/gallery/gallery.controller.js +++ b/electron/api/gallery/gallery.controller.js @@ -33,7 +33,6 @@ var __importStar = (this && this.__importStar) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -exports.search = search; exports.searchInExtension = searchInExtension; exports.getInfo = getInfo; exports.getFavorites = getFavorites; @@ -41,24 +40,6 @@ exports.getFavoriteById = getFavoriteById; exports.addFavorite = addFavorite; exports.removeFavorite = removeFavorite; const galleryService = __importStar(require("./gallery.service")); -async function search(req, reply) { - try { - const query = req.query.q || ''; - const page = parseInt(req.query.page) || 1; - const perPage = parseInt(req.query.perPage) || 48; - return await galleryService.searchGallery(query, page, perPage); - } - catch (err) { - const error = err; - console.error("Gallery Search Error:", error.message); - return { - results: [], - total: 0, - page: 1, - hasNextPage: false - }; - } -} async function searchInExtension(req, reply) { try { const provider = req.query.provider; diff --git a/electron/api/gallery/gallery.routes.js b/electron/api/gallery/gallery.routes.js index 3f2ad41..a1c73f8 100644 --- a/electron/api/gallery/gallery.routes.js +++ b/electron/api/gallery/gallery.routes.js @@ -35,7 +35,6 @@ var __importStar = (this && this.__importStar) || (function () { Object.defineProperty(exports, "__esModule", { value: true }); const controller = __importStar(require("./gallery.controller")); async function galleryRoutes(fastify) { - fastify.get('/gallery/search', controller.search); fastify.get('/gallery/fetch/:id', controller.getInfo); fastify.get('/gallery/search/provider', controller.searchInExtension); fastify.get('/gallery/favorites', controller.getFavorites); diff --git a/electron/api/gallery/gallery.service.js b/electron/api/gallery/gallery.service.js index 5552b73..ce359e2 100644 --- a/electron/api/gallery/gallery.service.js +++ b/electron/api/gallery/gallery.service.js @@ -1,6 +1,5 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.searchGallery = searchGallery; exports.getGalleryInfo = getGalleryInfo; exports.searchInExtension = searchInExtension; exports.getFavorites = getFavorites; @@ -9,26 +8,6 @@ exports.addFavorite = addFavorite; exports.removeFavorite = removeFavorite; const extensions_1 = require("../../shared/extensions"); const database_1 = require("../../shared/database"); -async function searchGallery(query, page = 1, perPage = 48) { - const extensions = (0, extensions_1.getAllExtensions)(); - for (const [name, ext] of extensions) { - if (ext.type === 'image-board' && ext.search) { - const result = await searchInExtension(name, query, page, perPage); - if (result.results.length > 0) { - return result; - } - } - } - return { - total: 0, - next: 0, - previous: 0, - pages: 0, - page, - hasNextPage: false, - results: [] - }; -} async function getGalleryInfo(id, providerName) { const extensions = (0, extensions_1.getAllExtensions)(); if (providerName) { @@ -38,8 +17,12 @@ async function getGalleryInfo(id, providerName) { console.log(`[Gallery] Getting info from ${providerName} for: ${id}`); const info = await ext.getInfo(id); return { - ...info, - provider: providerName + id: info.id ?? id, + provider: providerName, + image: info.image, + tags: info.tags, + title: info.title, + headers: info.headers }; } catch (e) { @@ -69,19 +52,21 @@ async function getGalleryInfo(id, providerName) { } async function searchInExtension(providerName, query, page = 1, perPage = 48) { const ext = (0, extensions_1.getExtension)(providerName); - if (!ext || ext.type !== 'image-board' || !ext.search) { - throw new Error(`La extensión "${providerName}" no existe o no soporta búsqueda.`); - } try { console.log(`[Gallery] Searching ONLY in ${providerName} for: ${query}`); const results = await ext.search(query, page, perPage); - const enrichedResults = (results?.results ?? []).map((r) => ({ - ...r, + const normalizedResults = (results?.results ?? []).map((r) => ({ + id: r.id, + image: r.image, + tags: r.tags, + title: r.title, + headers: r.headers, provider: providerName })); return { - ...results, - results: enrichedResults + page: results.page ?? page, + hasNextPage: !!results.hasNextPage, + results: normalizedResults }; } catch (e) { diff --git a/electron/shared/extensions.js b/electron/shared/extensions.js index a851160..e80872b 100644 --- a/electron/shared/extensions.js +++ b/electron/shared/extensions.js @@ -2,6 +2,7 @@ const fs = require('fs'); const path = require('path'); const os = require('os'); +const cheerio = require("cheerio"); const { queryAll, run } = require('./database'); const { scrape } = require("./headless"); const extensions = new Map(); @@ -62,6 +63,7 @@ async function loadExtension(fileName) { } const name = instance.constructor.name; instance.scrape = scrape; + instance.cheerio = cheerio; extensions.set(name, instance); console.log(`📦 Installed & loaded: ${name}`); return name; diff --git a/electron/shared/headless.js b/electron/shared/headless.js index 95e4a20..27230e0 100644 --- a/electron/shared/headless.js +++ b/electron/shared/headless.js @@ -23,7 +23,6 @@ async function initHeadless() { "--mute-audio", "--no-first-run", "--no-zygote", - "--single-process" ] }); context = await browser.newContext({ diff --git a/package-lock.json b/package-lock.json index 2488f61..15caa4b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,11 +13,13 @@ "@ryuziii/discord-rpc": "^1.0.1-rc.1", "bcrypt": "^6.0.0", "bindings": "^1.5.0", + "cheerio": "^1.1.2", "dotenv": "^17.2.3", "fastify": "^5.6.2", "jsonwebtoken": "^9.0.3", "node-addon-api": "^8.5.0", - "playwright-chromium": "1.57.0", + "playwright": "^1.57.0", + "playwright-chromium": "^1.57.0", "sqlite3": "^5.1.7" }, "devDependencies": { @@ -2005,6 +2007,12 @@ "readable-stream": "^3.4.0" } }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, "node_modules/boolean": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", @@ -2280,6 +2288,48 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/cheerio": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.1.2.tgz", + "integrity": "sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg==", + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "encoding-sniffer": "^0.2.1", + "htmlparser2": "^10.0.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^7.12.0", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=20.18.1" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/chownr": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", @@ -2789,6 +2839,34 @@ "node": ">= 8" } }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -3087,6 +3165,61 @@ "license": "MIT", "optional": true }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/dotenv": { "version": "17.2.3", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", @@ -3409,6 +3542,19 @@ "iconv-lite": "^0.6.2" } }, + "node_modules/encoding-sniffer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", + "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, "node_modules/end-of-stream": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", @@ -3418,6 +3564,18 @@ "once": "^1.4.0" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", @@ -3862,6 +4020,20 @@ "devOptional": true, "license": "ISC" }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -4221,6 +4393,37 @@ "node": ">=10" } }, + "node_modules/htmlparser2": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", + "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.1", + "entities": "^6.0.0" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/http-cache-semantics": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", @@ -4330,7 +4533,6 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "devOptional": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -5311,6 +5513,18 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -5448,6 +5662,55 @@ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "license": "BlueOak-1.0.0" }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "license": "MIT", + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "license": "MIT", + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -5562,6 +5825,24 @@ "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", "license": "MIT" }, + "node_modules/playwright": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", + "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.57.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, "node_modules/playwright-chromium": { "version": "1.57.0", "resolved": "https://registry.npmjs.org/playwright-chromium/-/playwright-chromium-1.57.0.tgz", @@ -6044,7 +6325,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "devOptional": true, "license": "MIT" }, "node_modules/sanitize-filename": { @@ -7243,6 +7523,15 @@ "node": ">=14.17" } }, + "node_modules/undici": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.16.0.tgz", + "integrity": "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==", + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/undici-types": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", @@ -7341,6 +7630,27 @@ "defaults": "^1.0.3" } }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 38f2425..baadcfb 100644 --- a/package.json +++ b/package.json @@ -18,11 +18,12 @@ "@ryuziii/discord-rpc": "^1.0.1-rc.1", "bcrypt": "^6.0.0", "bindings": "^1.5.0", + "cheerio": "^1.1.2", "dotenv": "^17.2.3", "fastify": "^5.6.2", "jsonwebtoken": "^9.0.3", "node-addon-api": "^8.5.0", - "playwright-chromium": "1.57.0", + "playwright-chromium": "^1.57.0", "sqlite3": "^5.1.7" }, "devDependencies": { diff --git a/src/api/anime/anime.service.ts b/src/api/anime/anime.service.ts index b04d512..a7216f5 100644 --- a/src/api/anime/anime.service.ts +++ b/src/api/anime/anime.service.ts @@ -273,9 +273,22 @@ export async function getAnimeInfoExtension(ext: Extension | null, id: string): const match = await ext.getMetadata(id); if (match) { + const normalized: any = { + title: match.title ?? "Unknown", + summary: match.summary ?? "No summary available", + episodes: Number(match.episodes) || 0, + characters: Array.isArray(match.characters) ? match.characters : [], + season: match.season ?? null, + status: match.status ?? "Unknown", + studio: match.studio ?? "Unknown", + score: Number(match.score) || 0, + year: match.year ?? null, + genres: Array.isArray(match.genres) ? match.genres : [], + image: match.image ?? "" + }; - await cacheExtension(extName, id, match.title, match); - return match; + await cacheExtension(extName, id, normalized.title, normalized); + return normalized; } } catch (e) { console.error(`Extension getMetadata failed:`, e); diff --git a/src/api/books/books.service.ts b/src/api/books/books.service.ts index e1c0b1d..6869cf8 100644 --- a/src/api/books/books.service.ts +++ b/src/api/books/books.service.ts @@ -262,7 +262,7 @@ export async function searchBooksAniList(query: string): Promise { return []; } -export async function getBookInfoExtension(ext: Extension | null, id: string): Promise { +export async function getBookInfoExtension(ext: Extension | null, id: string): Promise { if (!ext) return []; const extName = ext.constructor.name; @@ -271,18 +271,29 @@ export async function getBookInfoExtension(ext: Extension | null, id: string): P if (cached) { try { return JSON.parse(cached.metadata); - } catch { - } + } catch {} } if (ext.type === 'book-board' && ext.getMetadata) { try { - const info = await ext.getMetadata(id); if (info) { - await cacheExtension(extName, id, info.title, info); - return info; + const normalized = { + id: info.id ?? id, + title: info.title ?? "", + format: info.format ?? "", + score: typeof info.score === "number" ? info.score : null, + genres: Array.isArray(info.genres) ? info.genres : [], + status: info.status ?? "", + published: info.published ?? "", + summary: info.summary ?? "", + chapters: Number.isFinite(info.chapters) ? info.chapters : 1, + image: typeof info.image === "string" ? info.image : "" + }; + + await cacheExtension(extName, id, normalized.title, normalized); + return [normalized]; } } catch (e) { console.error(`Extension getInfo failed:`, e); diff --git a/src/api/gallery/gallery.controller.ts b/src/api/gallery/gallery.controller.ts index 4972e08..63c007e 100644 --- a/src/api/gallery/gallery.controller.ts +++ b/src/api/gallery/gallery.controller.ts @@ -1,25 +1,6 @@ import {FastifyReply, FastifyRequest} from 'fastify'; import * as galleryService from './gallery.service'; -export async function search(req: any, reply: FastifyReply) { - try { - const query = req.query.q || ''; - const page = parseInt(req.query.page as string) || 1; - const perPage = parseInt(req.query.perPage as string) || 48; - - return await galleryService.searchGallery(query, page, perPage); - } catch (err) { - const error = err as Error; - console.error("Gallery Search Error:", error.message); - return { - results: [], - total: 0, - page: 1, - hasNextPage: false - }; - } -} - export async function searchInExtension(req: any, reply: FastifyReply) { try { const provider = req.query.provider; diff --git a/src/api/gallery/gallery.routes.ts b/src/api/gallery/gallery.routes.ts index 5e04a7f..5da3ac5 100644 --- a/src/api/gallery/gallery.routes.ts +++ b/src/api/gallery/gallery.routes.ts @@ -2,7 +2,6 @@ import { FastifyInstance } from 'fastify'; import * as controller from './gallery.controller'; async function galleryRoutes(fastify: FastifyInstance) { - fastify.get('/gallery/search', controller.search); fastify.get('/gallery/fetch/:id', controller.getInfo); fastify.get('/gallery/search/provider', controller.searchInExtension); fastify.get('/gallery/favorites', controller.getFavorites); diff --git a/src/api/gallery/gallery.service.ts b/src/api/gallery/gallery.service.ts index efe8718..1b51165 100644 --- a/src/api/gallery/gallery.service.ts +++ b/src/api/gallery/gallery.service.ts @@ -2,30 +2,7 @@ import { getAllExtensions, getExtension } from '../../shared/extensions'; import { GallerySearchResult, GalleryInfo, Favorite, FavoriteResult } from '../types'; import { getDatabase } from '../../shared/database'; -export async function searchGallery(query: string, page: number = 1, perPage: number = 48): Promise { - const extensions = getAllExtensions(); - - for (const [name, ext] of extensions) { - if (ext.type === 'image-board' && ext.search) { - const result = await searchInExtension(name, query, page, perPage); - if (result.results.length > 0) { - return result; - } - } - } - - return { - total: 0, - next: 0, - previous: 0, - pages: 0, - page, - hasNextPage: false, - results: [] - }; -} - -export async function getGalleryInfo(id: string, providerName?: string): Promise { +export async function getGalleryInfo(id: string, providerName?: string): Promise { const extensions = getAllExtensions(); if (providerName) { @@ -35,8 +12,12 @@ export async function getGalleryInfo(id: string, providerName?: string): Promise console.log(`[Gallery] Getting info from ${providerName} for: ${id}`); const info = await ext.getInfo(id); return { - ...info, - provider: providerName + id: info.id ?? id, + provider: providerName, + image: info.image, + tags: info.tags, + title: info.title, + headers: info.headers }; } catch (e) { const error = e as Error; @@ -65,25 +46,26 @@ export async function getGalleryInfo(id: string, providerName?: string): Promise throw new Error("Gallery item not found in any extension"); } -export async function searchInExtension(providerName: string, query: string, page: number = 1, perPage: number = 48): Promise { +export async function searchInExtension(providerName: string, query: string, page: number = 1, perPage: number = 48): Promise { const ext = getExtension(providerName); - if (!ext || ext.type !== 'image-board' || !ext.search) { - throw new Error(`La extensión "${providerName}" no existe o no soporta búsqueda.`); - } - try { console.log(`[Gallery] Searching ONLY in ${providerName} for: ${query}`); const results = await ext.search(query, page, perPage); - const enrichedResults = (results?.results ?? []).map((r: any) => ({ - ...r, + const normalizedResults = (results?.results ?? []).map((r: any) => ({ + id: r.id, + image: r.image, + tags: r.tags, + title: r.title, + headers: r.headers, provider: providerName })); return { - ...results, - results: enrichedResults + page: results.page ?? page, + hasNextPage: !!results.hasNextPage, + results: normalizedResults }; } catch (e) { diff --git a/src/scripts/books/book.js b/src/scripts/books/book.js index 42b132b..c649e84 100644 --- a/src/scripts/books/book.js +++ b/src/scripts/books/book.js @@ -51,9 +51,10 @@ async function loadBookMetadata() { return; } - bookData = data; + const raw = Array.isArray(data) ? data[0] : data; + bookData = raw; - const metadata = MediaMetadataUtils.formatBookData(data, !!extensionName); + const metadata = MediaMetadataUtils.formatBookData(raw, !!extensionName); updatePageTitle(metadata.title); updateMetadata(metadata); diff --git a/src/scripts/gallery/gallery.js b/src/scripts/gallery/gallery.js index adbbc28..4469637 100644 --- a/src/scripts/gallery/gallery.js +++ b/src/scripts/gallery/gallery.js @@ -332,11 +332,6 @@ async function loadExtensions() { providerSelector.innerHTML = ''; - const global = document.createElement('option'); - global.value = ''; - global.textContent = 'Global Search'; - providerSelector.appendChild(global); - const favoritesOption = document.createElement('option'); favoritesOption.value = 'favorites'; favoritesOption.textContent = 'Favorites'; diff --git a/src/scripts/gallery/image.js b/src/scripts/gallery/image.js index f0d041e..3a60d8c 100644 --- a/src/scripts/gallery/image.js +++ b/src/scripts/gallery/image.js @@ -106,7 +106,7 @@ function copyLink() { async function loadSimilarImages(item) { if (!item.tags || item.tags.length === 0) { - document.getElementById('similar-section').innerHTML = '

No tags available to search for similar images.

'; + document.getElementById('similar-section').innerHTML = '

No tags available to search for similar images.

'; return; } @@ -123,13 +123,13 @@ async function loadSimilarImages(item) { .slice(0, 15); if (results.length === 0) { - container.innerHTML = '

No similar images found.

'; + container.innerHTML = '

No similar images found.

'; return; } container.innerHTML = `

- More images tagged with "${firstTag}" + More images tagged with "${firstTag}"

${results.map(img => { @@ -147,7 +147,7 @@ async function loadSimilarImages(item) { `; } catch (err) { - container.innerHTML = '

Could not load similar images.

'; + container.innerHTML = '

Could not load similar images.

'; } } @@ -198,7 +198,7 @@ function renderItem(item) { ? item.tags.map(tag => ` ${tag} `).join('') - : 'No tags' + : 'No tags' }
@@ -216,9 +216,9 @@ function renderError(msg) { itemMainContentContainer.innerHTML = ` @@ -268,13 +268,14 @@ async function loadFromProvider(provider, id) { if (!res.ok) throw new Error(); const data = await res.json(); - if (!data.fullImage) throw new Error(); + + if (!data.image) throw new Error(); const item = { id, - title: data.title || data.publishedBy || 'Beautiful Art', - fullImage: data.fullImage, - originalImage: data.fullImage, + title: data.title || 'Beautiful Art', + fullImage: data.image, + originalImage: data.image, tags: data.tags || [], provider, headers: data.headers diff --git a/src/shared/extensions.js b/src/shared/extensions.js index 804ec35..d59b4a6 100644 --- a/src/shared/extensions.js +++ b/src/shared/extensions.js @@ -1,6 +1,7 @@ const fs = require('fs'); const path = require('path'); const os = require('os'); +const cheerio = require("cheerio"); const { queryAll, run } = require('./database'); const { scrape } = require("./headless"); @@ -77,6 +78,7 @@ async function loadExtension(fileName) { const name = instance.constructor.name; instance.scrape = scrape; + instance.cheerio = cheerio; extensions.set(name, instance); console.log(`📦 Installed & loaded: ${name}`); diff --git a/src/shared/headless.js b/src/shared/headless.js index 9d23cfb..8dee9d5 100644 --- a/src/shared/headless.js +++ b/src/shared/headless.js @@ -21,7 +21,6 @@ async function initHeadless() { "--mute-audio", "--no-first-run", "--no-zygote", - "--single-process" ] }); context = await browser.newContext({