We are launching a docker version (server version) today so we want to just organize the repo so its easier to navigate.
209 lines
5.9 KiB
JavaScript
209 lines
5.9 KiB
JavaScript
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
|
|
}; |