From 0da70f8e6a6ff4ef9790c8e8f2853dd27800358d Mon Sep 17 00:00:00 2001 From: lenafx Date: Fri, 5 Dec 2025 12:47:19 +0100 Subject: [PATCH] added custom titlebar to electron --- main.js | 12 +- preload.js | 10 ++ src/api/books/books.service.ts | 10 -- src/scripts/titlebar.js | 18 ++ src/shared/headless.js | 291 ++++++++++++++++++++------------- views/anime/anime.html | 13 +- views/anime/index.html | 14 +- views/anime/watch.html | 17 +- views/books/book.html | 12 ++ views/books/books.html | 12 ++ views/books/read.html | 13 +- views/css/anime/watch.css | 74 +-------- views/css/books/books.css | 22 --- views/css/books/reader.css | 2 +- views/css/gallery/image.css | 3 + views/css/titlebar.css | 153 +++++++++++++++++ views/gallery/gallery.html | 12 ++ views/gallery/image.html | 12 ++ views/marketplace.html | 13 ++ views/schedule.html | 13 +- 20 files changed, 497 insertions(+), 229 deletions(-) create mode 100644 preload.js create mode 100644 src/scripts/titlebar.js create mode 100644 views/css/titlebar.css diff --git a/main.js b/main.js index b655a9d..adc6ca2 100644 --- a/main.js +++ b/main.js @@ -1,4 +1,4 @@ -const { app, BrowserWindow } = require('electron'); +const { app, BrowserWindow, ipcMain } = require('electron'); const { fork } = require('child_process'); const path = require('path'); @@ -13,17 +13,23 @@ function createWindow() { win = new BrowserWindow({ width: 1200, height: 800, + frame: false, + titleBarStyle: "hidden", webPreferences: { + preload: path.join(__dirname, "preload.js"), nodeIntegration: false, - contextIsolation: true, + contextIsolation: true } }); win.setMenu(null); - win.loadURL('http://localhost:3000'); } +ipcMain.on("win:minimize", () => win.minimize()); +ipcMain.on("win:maximize", () => win.maximize()); +ipcMain.on("win:close", () => win.close()); + app.whenReady().then(() => { startBackend(); createWindow(); diff --git a/preload.js b/preload.js new file mode 100644 index 0000000..47cc6dc --- /dev/null +++ b/preload.js @@ -0,0 +1,10 @@ +const { contextBridge, ipcRenderer } = require("electron"); + +contextBridge.exposeInMainWorld("electronAPI", { + isElectron: true, + win: { + minimize: () => ipcRenderer.send("win:minimize"), + maximize: () => ipcRenderer.send("win:maximize"), + close: () => ipcRenderer.send("win:close") + } +}); diff --git a/src/api/books/books.service.ts b/src/api/books/books.service.ts index f3ab98b..5d05b9a 100644 --- a/src/api/books/books.service.ts +++ b/src/api/books/books.service.ts @@ -145,7 +145,6 @@ export async function searchBooksInExtension(ext: Extension | null, name: string if (!ext) return []; if ((ext.type === 'book-board') && ext.search) { - const start = performance.now(); try { console.log(`[${name}] Searching for book: ${query}`); @@ -159,15 +158,6 @@ export async function searchBooksInExtension(ext: Extension | null, name: string } }); - const end = performance.now(); - console.log(`[${name}] Search time: ${(end - start).toFixed(2)} ms`); - console.log(`[${name}] Search time: ${(end - start).toFixed(2)} ms`); - console.log(`[${name}] Search time: ${(end - start).toFixed(2)} ms`); - console.log(`[${name}] Search time: ${(end - start).toFixed(2)} ms`); - console.log(`[${name}] Search time: ${(end - start).toFixed(2)} ms`); - console.log(`[${name}] Search time: ${(end - start).toFixed(2)} ms`); - console.log(`[${name}] Search time: ${(end - start).toFixed(2)} ms`); - if (matches?.length) { return matches.map(m => ({ id: m.id, diff --git a/src/scripts/titlebar.js b/src/scripts/titlebar.js new file mode 100644 index 0000000..d451bba --- /dev/null +++ b/src/scripts/titlebar.js @@ -0,0 +1,18 @@ +if (window.electronAPI?.isElectron) { + document.documentElement.classList.add("electron"); +} + +document.addEventListener("DOMContentLoaded", () => { + document.documentElement.style.visibility = "visible"; + if (!window.electronAPI?.isElectron) return; + document.body.classList.add("electron"); + + const titlebar = document.getElementById("titlebar"); + if (!titlebar) return; + + titlebar.style.display = "flex"; + + titlebar.querySelector(".min").onclick = () => window.electronAPI.win.minimize(); + titlebar.querySelector(".max").onclick = () => window.electronAPI.win.maximize(); + titlebar.querySelector(".close").onclick = () => window.electronAPI.win.close(); +}); \ No newline at end of file diff --git a/src/shared/headless.js b/src/shared/headless.js index 784bf22..10865af 100644 --- a/src/shared/headless.js +++ b/src/shared/headless.js @@ -8,160 +8,227 @@ const BLOCK_LIST = [ "adsystem", "analytics", "tracker", "pixel", "quantserve", "newrelic", "hotjar", "yandex", "ads", "widgets", "gravatar", "fonts.googleapis", "map", "cdn.ampproject.org", "googletagmanager" - ]; const ALLOWED_SCRIPTS = []; async function initHeadless() { - if (browser) return; + if (browser && browser.isConnected()) return; - browser = await chromium.launch({ - headless: true, - args: [ - "--no-sandbox", - "--disable-setuid-sandbox", - "--disable-dev-shm-usage", - "--disable-gpu", - "--disable-extensions", - "--disable-background-networking", - "--disable-sync", - "--disable-translate", - "--mute-audio", - "--no-first-run", - "--no-zygote", - "--single-process", - "--disable-software-rasterizer", - "--disable-client-side-phishing-detection", - "--no-default-browser-check", - "--no-experiments" - ] - }); + try { + browser = await chromium.launch({ + headless: true, + args: [ + "--no-sandbox", + "--disable-setuid-sandbox", + "--disable-dev-shm-usage", + "--disable-gpu", + "--disable-extensions", + "--disable-background-networking", + "--disable-sync", + "--disable-translate", + "--mute-audio", + "--no-first-run", + "--no-zygote", + "--single-process", + "--disable-software-rasterizer", + "--disable-client-side-phishing-detection", + "--no-default-browser-check", + "--no-experiments" + ] + }); - context = await browser.newContext({ - userAgent: - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/122.0.0.0 Safari/537.36" - }); + context = await browser.newContext({ + userAgent: + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/122.0.0.0 Safari/537.36" + }); + } catch (error) { + console.error("Error al inicializar browser:", error); + throw error; + } } async function turboScroll(page) { - await page.evaluate(() => { - return new Promise((resolve) => { - let last = 0; - let same = 0; - const timer = setInterval(() => { - const h = document.body.scrollHeight; - window.scrollTo(0, h); - if (h === last) { - same++; - if (same >= 5) { - - clearInterval(timer); - resolve(); + try { + await page.evaluate(() => { + return new Promise((resolve) => { + let last = 0; + let same = 0; + const timer = setInterval(() => { + const h = document.body.scrollHeight; + window.scrollTo(0, h); + if (h === last) { + same++; + if (same >= 5) { + clearInterval(timer); + resolve(); + } + } else { + same = 0; + last = h; } - } else { - same = 0; - last = h; - } - }, 20); + }, 20); + // Safety timeout + setTimeout(() => { + clearInterval(timer); + resolve(); + }, 10000); + }); }); - }); + } catch (error) { + console.error("Error en turboScroll:", error.message); + // No lanzamos el error, continuamos + } } async function scrape(url, handler, options = {}) { const { waitUntil = "domcontentloaded", waitSelector = null, - timeout = 10000, + timeout = 15000, scrollToBottom = false, renderWaitTime = 0, loadImages = true, - blockScripts = true - + blockScripts = true, + retries = 3, + retryDelay = 1000 } = options; - if (!browser) await initHeadless(); + let lastError = null; - const page = await context.newPage(); - const requests = []; + for (let attempt = 1; attempt <= retries; attempt++) { + let page = null; - page.on("request", req => { - requests.push({ - url: req.url(), - method: req.method(), - type: req.resourceType() - }); - }); + try { + // Verificar que el browser esté activo + if (!browser || !browser.isConnected()) { + await initHeadless(); + } - await page.route("**/*", (route) => { - const req = route.request(); - const resUrl = req.url().toLowerCase(); - const type = req.resourceType(); + page = await context.newPage(); + const requests = []; - if ( - type === "font" || - type === "stylesheet" || - type === "media" || - type === "manifest" || - type === "other" || + // Listener para requests + page.on("request", req => { + requests.push({ + url: req.url(), + method: req.method(), + type: req.resourceType() + }); + }); - (blockScripts && type === "script" && !ALLOWED_SCRIPTS.some(k => resUrl.includes(k))) - ) { + // Route para bloquear recursos + await page.route("**/*", (route) => { + const req = route.request(); + const resUrl = req.url().toLowerCase(); + const type = req.resourceType(); - return route.abort("blockedbyclient", { timeout: 100 }); - } + if ( + type === "font" || + type === "stylesheet" || + type === "media" || + type === "manifest" || + type === "other" || + (blockScripts && type === "script" && !ALLOWED_SCRIPTS.some(k => resUrl.includes(k))) + ) { + return route.abort("blockedbyclient"); + } - if (BLOCK_LIST.some(k => resUrl.includes(k))) { - return route.abort("blockedbyclient", { timeout: 100 }); - } + if (BLOCK_LIST.some(k => resUrl.includes(k))) { + return route.abort("blockedbyclient"); + } - if (!loadImages && ( - type === "image" || resUrl.match(/\.(jpg|jpeg|png|gif|webp|svg)$/) - )) { - return route.abort("blockedbyclient", { timeout: 100 }); - } + if (!loadImages && ( + type === "image" || resUrl.match(/\.(jpg|jpeg|png|gif|webp|svg)$/) + )) { + return route.abort("blockedbyclient"); + } - route.continue(); - }); + route.continue(); + }); - try { - await page.goto(url, { waitUntil, timeout }); + // Navegar a la URL + await page.goto(url, { waitUntil, timeout }); - if (waitSelector) { - try { - await page.waitForSelector(waitSelector, { timeout }); - } catch (e) { + // Esperar selector si se especifica + if (waitSelector) { + try { + await page.waitForSelector(waitSelector, { timeout: Math.min(timeout, 5000) }); + } catch (e) { + console.warn(`Selector '${waitSelector}' no encontrado, continuando...`); + } + } + // Scroll si es necesario + if (scrollToBottom) { + await turboScroll(page); + } + + // Tiempo de espera adicional para renderizado + if (renderWaitTime > 0) { + await page.waitForTimeout(renderWaitTime); + } + + // Ejecutar el handler personalizado + const result = await handler(page); + + // Cerrar la página antes de retornar + await page.close(); + + return { result, requests }; + + } catch (error) { + lastError = error; + console.error(`[Intento ${attempt}/${retries}] Error durante el scraping de ${url}:`, error.message); + + // Cerrar página si está abierta + if (page && !page.isClosed()) { + try { + await page.close(); + } catch (closeError) { + console.error("Error al cerrar página:", closeError.message); + } + } + + // Si el browser está cerrado, limpiar referencias + if (error.message.includes("closed") || error.message.includes("Target closed")) { + console.log("Browser cerrado detectado, reiniciando..."); + await closeScraper(); + } + + // Si no es el último intento, esperar antes de reintentar + if (attempt < retries) { + const delay = retryDelay * attempt; // Backoff exponencial + console.log(`Reintentando en ${delay}ms...`); + await new Promise(r => setTimeout(r, delay)); } } - - if (scrollToBottom) { - await turboScroll(page); - } - - if (renderWaitTime > 0) { - - await new Promise(r => setTimeout(r, renderWaitTime)); - } - - const result = await handler(page); - return { result, requests }; - } catch (error) { - console.error(`Error durante el scraping de ${url}:`, error); - return null; - - } finally { - - await page.close(); } + + // Si llegamos aquí, todos los intentos fallaron + console.error(`Todos los intentos fallaron para ${url}`); + throw lastError || new Error("Scraping failed after all retries"); } async function closeScraper() { - if (context) await context.close(); - if (browser) await browser.close(); - context = null; - browser = null; + try { + if (context) { + await context.close(); + context = null; + } + } catch (error) { + console.error("Error cerrando context:", error.message); + } + + try { + if (browser) { + await browser.close(); + browser = null; + } + } catch (error) { + console.error("Error cerrando browser:", error.message); + } } module.exports = { diff --git a/views/anime/anime.html b/views/anime/anime.html index bc2d6a6..7516825 100644 --- a/views/anime/anime.html +++ b/views/anime/anime.html @@ -7,8 +7,20 @@ WaifuBoard + + +
+ + WaifuBoard +
+
+ + + +
+
+