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

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