"use strict"; 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(); async function loadExtensions() { const homeDir = os.homedir(); const extensionsDir = path.join(homeDir, 'WaifuBoards', 'extensions'); if (!fs.existsSync(extensionsDir)) { console.log("๐Ÿ“ Extensions directory not found, creating..."); fs.mkdirSync(extensionsDir, { recursive: true }); } try { const files = await fs.promises.readdir(extensionsDir); for (const file of files) { if (file.endsWith('.js')) { await loadExtension(file); } } console.log(`โœ… Loaded ${extensions.size} extensions`); try { const loaded = Array.from(extensions.keys()); const rows = await queryAll("SELECT DISTINCT ext_name FROM extension"); for (const row of rows) { if (!loaded.includes(row.ext_name)) { console.log(`๐Ÿงน Cleaning cached metadata for removed extension: ${row.ext_name}`); await run("DELETE FROM extension WHERE ext_name = ?", [row.ext_name]); } } } catch (err) { console.error("โŒ Error cleaning extension cache:", err); } } catch (err) { console.error("โŒ Extension Scan Error:", err); } } async function loadExtension(fileName) { const homeDir = os.homedir(); const extensionsDir = path.join(homeDir, 'WaifuBoards', 'extensions'); const filePath = path.join(extensionsDir, fileName); if (!fs.existsSync(filePath)) { console.warn(`โš ๏ธ Extension not found: ${fileName}`); return; } try { delete require.cache[require.resolve(filePath)]; const ExtensionClass = require(filePath); const instance = typeof ExtensionClass === 'function' ? new ExtensionClass() : (ExtensionClass.default ? new ExtensionClass.default() : null); if (!instance) { console.warn(`โš ๏ธ Invalid extension: ${fileName}`); return; } if (!["anime-board", "book-board", "image-board"].includes(instance.type)) { console.warn(`โš ๏ธ Invalid extension (${instance.type}): ${fileName}`); return; } const name = instance.constructor.name; instance.scrape = scrape; instance.cheerio = cheerio; extensions.set(name, instance); console.log(`๐Ÿ“ฆ Installed & loaded: ${name}`); return name; } catch (err) { console.warn(`โš ๏ธ Error loading ${fileName}: ${err.message}`); } } const https = require('https'); async function saveExtensionFile(fileName, downloadUrl) { const homeDir = os.homedir(); const extensionsDir = path.join(homeDir, 'WaifuBoards', 'extensions'); const filePath = path.join(extensionsDir, fileName); const fullUrl = downloadUrl; if (!fs.existsSync(extensionsDir)) { fs.mkdirSync(extensionsDir, { recursive: true }); } return new Promise((resolve, reject) => { const file = fs.createWriteStream(filePath); https.get(fullUrl, async (response) => { if (response.statusCode !== 200) { return reject(new Error(`Download failed: ${response.statusCode}`)); } response.pipe(file); file.on('finish', async () => { file.close(async () => { try { await loadExtension(fileName); resolve(); } catch (err) { if (fs.existsSync(filePath)) { await fs.promises.unlink(filePath); } reject(new Error(`Load failed, file rolled back: ${err.message}`)); } }); }); }).on('error', async (err) => { if (fs.existsSync(filePath)) { await fs.promises.unlink(filePath); } reject(err); }); }); } async function deleteExtensionFile(fileName) { const homeDir = os.homedir(); const extensionsDir = path.join(homeDir, 'WaifuBoards', 'extensions'); const filePath = path.join(extensionsDir, fileName); const extName = fileName.replace(".js", ""); for (const key of extensions.keys()) { if (key.toLowerCase() === extName) { extensions.delete(key); console.log(`๐Ÿ—‘๏ธ Removed from memory: ${key}`); break; } } if (fs.existsSync(filePath)) { await fs.promises.unlink(filePath); console.log(`๐Ÿ—‘๏ธ Deleted file: ${fileName}`); } } function getExtension(name) { return extensions.get(name); } function getAllExtensions() { return extensions; } function getExtensionsList() { return Array.from(extensions.keys()); } function getAnimeExtensionsMap() { const animeExts = new Map(); for (const [name, ext] of extensions) { if (ext.type === 'anime-board') { animeExts.set(name, ext); } } return animeExts; } function getBookExtensionsMap() { const bookExts = new Map(); for (const [name, ext] of extensions) { if (ext.type === 'book-board' || ext.type === 'manga-board') { bookExts.set(name, ext); } } return bookExts; } function getGalleryExtensionsMap() { const galleryExts = new Map(); for (const [name, ext] of extensions) { if (ext.type === 'image-board') { galleryExts.set(name, ext); } } return galleryExts; } module.exports = { loadExtensions, getExtension, getAllExtensions, getExtensionsList, getAnimeExtensionsMap, getBookExtensionsMap, getGalleryExtensionsMap, saveExtensionFile, deleteExtensionFile };