This commit is contained in:
2025-12-15 16:21:06 +01:00
parent 5d8441bf27
commit f1f95953dd
19 changed files with 429 additions and 144 deletions

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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;

View File

@@ -23,7 +23,6 @@ async function initHeadless() {
"--mute-audio",
"--no-first-run",
"--no-zygote",
"--single-process"
]
});
context = await browser.newContext({

316
package-lock.json generated
View File

@@ -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",

View File

@@ -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": {

View File

@@ -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);

View File

@@ -262,7 +262,7 @@ export async function searchBooksAniList(query: string): Promise<Book[]> {
return [];
}
export async function getBookInfoExtension(ext: Extension | null, id: string): Promise<Book[]> {
export async function getBookInfoExtension(ext: Extension | null, id: string): Promise<any[]> {
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);

View File

@@ -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;

View File

@@ -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);

View File

@@ -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<GallerySearchResult> {
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<GalleryInfo> {
export async function getGalleryInfo(id: string, providerName?: string): Promise<any> {
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<GallerySearchResult> {
export async function searchInExtension(providerName: string, query: string, page: number = 1, perPage: number = 48): Promise<any> {
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) {

View File

@@ -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);

View File

@@ -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';

View File

@@ -106,7 +106,7 @@ function copyLink() {
async function loadSimilarImages(item) {
if (!item.tags || item.tags.length === 0) {
document.getElementById('similar-section').innerHTML = '<p style="text-align:center;color:var(--text-secondary);padding:3rem 0;">No tags available to search for similar images.</p>';
document.getElementById('similar-section').innerHTML = '<p style="text-align:center;color:var(--color-text-secondary);padding:3rem 0;">No tags available to search for similar images.</p>';
return;
}
@@ -123,13 +123,13 @@ async function loadSimilarImages(item) {
.slice(0, 15);
if (results.length === 0) {
container.innerHTML = '<p style="text-align:center;color:var(--text-secondary);padding:3rem 0;">No similar images found.</p>';
container.innerHTML = '<p style="text-align:center;color:var(--color-text-secondary);padding:3rem 0;">No similar images found.</p>';
return;
}
container.innerHTML = `
<h3 style="text-align:center;margin:2rem 0 1.5rem;font-size:1.7rem;">
More images tagged with "<span style="color:var(--accent);">${firstTag}</span>"
More images tagged with "<span style="color:var(--color-primary);">${firstTag}</span>"
</h3>
<div class="similar-grid">
${results.map(img => {
@@ -147,7 +147,7 @@ async function loadSimilarImages(item) {
`;
} catch (err) {
container.innerHTML = '<p style="text-align:center;color:var(--text-secondary);opacity:0.6;padding:3rem 0;">Could not load similar images.</p>';
container.innerHTML = '<p style="text-align:center;color:var(--color-text-secondary);opacity:0.6;padding:3rem 0;">Could not load similar images.</p>';
}
}
@@ -198,7 +198,7 @@ function renderItem(item) {
? item.tags.map(tag => `
<a class="tag-item">${tag}</a>
`).join('')
: '<span style="opacity:0.6;color:var(--text-secondary);">No tags</span>'
: '<span style="opacity:0.6;color:var(--color-text-secondary);">No tags</span>'
}
</div>
</div>
@@ -216,9 +216,9 @@ function renderError(msg) {
itemMainContentContainer.innerHTML = `
<div style="text-align:center;padding:8rem 1rem;">
<i class="fa-solid fa-heart-crack" style="font-size:5rem;color:#ff6b6b;opacity:0.7;"></i>
<h2 style="margin:2rem 0;color:var(--text-primary);">Image Not Available</h2>
<p style="color:var(--text-secondary);max-width:600px;margin:0 auto 2rem;">${msg}</p>
<a href="/gallery" style="padding:1rem 2.5rem;background:var(--accent);color:white;border-radius:99px;text-decoration:none;font-weight:600;">
<h2 style="margin:2rem 0;color:var(--color-text-primary);">Image Not Available</h2>
<p style="color:var(--color-text-secondary);max-width:600px;margin:0 auto 2rem;">${msg}</p>
<a href="/gallery" style="padding:1rem 2.5rem;background:var(--color-primary);color:white;border-radius:99px;text-decoration:none;font-weight:600;">
Back to Gallery
</a>
</div>
@@ -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

View File

@@ -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}`);

View File

@@ -21,7 +21,6 @@ async function initHeadless() {
"--mute-audio",
"--no-first-run",
"--no-zygote",
"--single-process"
]
});
context = await browser.newContext({