code organisation & refactor
This commit is contained in:
48
src/shared/database.js
Normal file
48
src/shared/database.js
Normal file
@@ -0,0 +1,48 @@
|
||||
const sqlite3 = require('sqlite3').verbose();
|
||||
const path = require('path');
|
||||
|
||||
const DB_PATH = path.join(__dirname, '..', 'metadata', 'anilist_anime.db');
|
||||
let db = null;
|
||||
|
||||
function initDatabase() {
|
||||
db = new sqlite3.Database(DB_PATH, sqlite3.OPEN_READONLY, (err) => {
|
||||
if (err) {
|
||||
console.error("Database Error:", err.message);
|
||||
} else {
|
||||
console.log("Connected to local AniList database.");
|
||||
}
|
||||
});
|
||||
return db;
|
||||
}
|
||||
|
||||
function getDatabase() {
|
||||
if (!db) {
|
||||
throw new Error("Database not initialized. Call initDatabase() first.");
|
||||
}
|
||||
return db;
|
||||
}
|
||||
|
||||
function queryOne(sql, params = []) {
|
||||
return new Promise((resolve, reject) => {
|
||||
getDatabase().get(sql, params, (err, row) => {
|
||||
if (err) reject(err);
|
||||
else resolve(row);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function queryAll(sql, params = []) {
|
||||
return new Promise((resolve, reject) => {
|
||||
getDatabase().all(sql, params, (err, rows) => {
|
||||
if (err) reject(err);
|
||||
else resolve(rows || []);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
initDatabase,
|
||||
getDatabase,
|
||||
queryOne,
|
||||
queryAll
|
||||
};
|
||||
65
src/shared/extensions.js
Normal file
65
src/shared/extensions.js
Normal file
@@ -0,0 +1,65 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
|
||||
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, skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const files = await fs.promises.readdir(extensionsDir);
|
||||
|
||||
for (const file of files) {
|
||||
if (file.endsWith('.js')) {
|
||||
const filePath = path.join(extensionsDir, file);
|
||||
|
||||
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 && (instance.type === "anime-board" || instance.type === "book-board")) {
|
||||
const name = instance.constructor.name;
|
||||
extensions.set(name, instance);
|
||||
console.log(`📦 Loaded Extension: ${name}`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`❌ Failed to load extension ${file}:`, e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`✅ Loaded ${extensions.size} extensions`);
|
||||
} catch (err) {
|
||||
console.error("❌ Extension Scan Error:", err);
|
||||
}
|
||||
}
|
||||
|
||||
function getExtension(name) {
|
||||
return extensions.get(name);
|
||||
}
|
||||
|
||||
function getAllExtensions() {
|
||||
return extensions;
|
||||
}
|
||||
|
||||
function getExtensionsList() {
|
||||
return Array.from(extensions.keys());
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
loadExtensions,
|
||||
getExtension,
|
||||
getAllExtensions,
|
||||
getExtensionsList
|
||||
};
|
||||
47
src/shared/proxy/proxy.routes.js
Normal file
47
src/shared/proxy/proxy.routes.js
Normal file
@@ -0,0 +1,47 @@
|
||||
const { proxyRequest, processM3U8Content, streamToReadable } = require('./proxy.service');
|
||||
|
||||
async function proxyRoutes(fastify, options) {
|
||||
|
||||
fastify.get('/proxy', async (req, reply) => {
|
||||
const { url, referer, origin, userAgent } = req.query;
|
||||
|
||||
if (!url) {
|
||||
return reply.code(400).send({ error: "No URL provided" });
|
||||
}
|
||||
|
||||
try {
|
||||
const { response, contentType, isM3U8 } = await proxyRequest(url, {
|
||||
referer,
|
||||
origin,
|
||||
userAgent
|
||||
});
|
||||
|
||||
reply.header('Access-Control-Allow-Origin', '*');
|
||||
reply.header('Access-Control-Allow-Methods', 'GET, OPTIONS');
|
||||
|
||||
if (contentType) {
|
||||
reply.header('Content-Type', contentType);
|
||||
}
|
||||
|
||||
if (isM3U8) {
|
||||
const text = await response.text();
|
||||
const baseUrl = new URL(response.url);
|
||||
const processedContent = processM3U8Content(text, baseUrl, {
|
||||
referer,
|
||||
origin,
|
||||
userAgent
|
||||
});
|
||||
|
||||
return processedContent;
|
||||
} else {
|
||||
return reply.send(streamToReadable(response.body));
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
fastify.log.error(err);
|
||||
return reply.code(500).send({ error: "Internal Server Error" });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = proxyRoutes;
|
||||
58
src/shared/proxy/proxy.service.js
Normal file
58
src/shared/proxy/proxy.service.js
Normal file
@@ -0,0 +1,58 @@
|
||||
const { Readable } = require('stream');
|
||||
|
||||
async function proxyRequest(url, { referer, origin, userAgent }) {
|
||||
const headers = {
|
||||
'User-Agent': userAgent || "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||||
'Accept': '*/*',
|
||||
'Accept-Language': 'en-US,en;q=0.9'
|
||||
};
|
||||
|
||||
if (referer) headers['Referer'] = referer;
|
||||
if (origin) headers['Origin'] = origin;
|
||||
|
||||
const response = await fetch(url, { headers, redirect: 'follow' });
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Proxy Error: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const contentType = response.headers.get('content-type');
|
||||
const isM3U8 = (contentType && contentType.includes('mpegurl')) || url.includes('.m3u8');
|
||||
|
||||
return {
|
||||
response,
|
||||
contentType,
|
||||
isM3U8
|
||||
};
|
||||
}
|
||||
|
||||
function processM3U8Content(text, baseUrl, { referer, origin, userAgent }) {
|
||||
return text.replace(/^(?!#)(?!\s*$).+/gm, (line) => {
|
||||
line = line.trim();
|
||||
let absoluteUrl;
|
||||
|
||||
try {
|
||||
absoluteUrl = new URL(line, baseUrl).href;
|
||||
} catch(e) {
|
||||
return line;
|
||||
}
|
||||
|
||||
const proxyParams = new URLSearchParams();
|
||||
proxyParams.set('url', absoluteUrl);
|
||||
if (referer) proxyParams.set('referer', referer);
|
||||
if (origin) proxyParams.set('origin', origin);
|
||||
if (userAgent) proxyParams.set('userAgent', userAgent);
|
||||
|
||||
return `/api/proxy?${proxyParams.toString()}`;
|
||||
});
|
||||
}
|
||||
|
||||
function streamToReadable(webStream) {
|
||||
return Readable.fromWeb(webStream);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
proxyRequest,
|
||||
processM3U8Content,
|
||||
streamToReadable
|
||||
};
|
||||
Reference in New Issue
Block a user