organisation & minor fixes

This commit is contained in:
2025-12-03 17:24:24 +01:00
parent 2cad7ff6ea
commit 8e20743e8b
30 changed files with 158 additions and 497 deletions

View File

@@ -8,7 +8,6 @@ const databases = new Map();
const DEFAULT_PATHS = {
anilist: path.join(__dirname, '..', 'metadata', 'anilist_anime.db'),
favorites: path.join(os.homedir(), "WaifuBoards", "favorites.db"),
cache: path.join(os.homedir(), "WaifuBoards", "cache.db")
};
@@ -21,7 +20,6 @@ async function ensureExtensionsTable(db) {
title TEXT NOT NULL,
metadata TEXT NOT NULL,
updated_at INTEGER NOT NULL,
PRIMARY KEY(ext_name, id)
);
`, (err) => {
@@ -157,7 +155,6 @@ function initDatabase(name = 'anilist', dbPath = null, readOnly = false) {
function getDatabase(name = 'anilist') {
if (!databases.has(name)) {
const readOnly = (name === 'anilist');
return initDatabase(name, null, readOnly);
}
@@ -208,73 +205,11 @@ function closeDatabase(name = null) {
}
}
async function getCachedExtension(extName, id) {
return queryOne(
"SELECT metadata FROM extension WHERE ext_name = ? AND id = ?",
[extName, id]
);
}
async function cacheExtension(extName, id, title, metadata) {
return run(
`
INSERT INTO extension (ext_name, id, title, metadata, updated_at)
VALUES (?, ?, ?, ?, ?)
ON CONFLICT(ext_name, id)
DO UPDATE SET
title = excluded.title,
metadata = excluded.metadata,
updated_at = ?
`,
[extName, id, title, JSON.stringify(metadata), Date.now(), Date.now()]
);
}
async function getExtensionTitle(extName, id) {
const sql = "SELECT title FROM extension WHERE ext_name = ? AND id = ?";
const row = await queryOne(sql, [extName, id], 'anilist');
return row ? row.title : null;
}
async function deleteExtension(extName) {
return run(
"DELETE FROM extension WHERE ext_name = ?",
[extName]
);
}
async function getCache(key) {
return queryOne("SELECT result, created_at, ttl_ms FROM cache WHERE key = ?", [key], "cache");
}
async function setCache(key, result, ttl_ms) {
return run(
`
INSERT INTO cache (key, result, created_at, ttl_ms)
VALUES (?, ?, ?, ?)
ON CONFLICT(key)
DO UPDATE SET result = excluded.result, created_at = excluded.created_at, ttl_ms = excluded.ttl_ms
`,
[key, JSON.stringify(result), Date.now(), ttl_ms],
"cache"
);
}
module.exports = {
initDatabase,
getDatabase,
queryOne,
queryAll,
run,
getCachedExtension,
cacheExtension,
getExtensionTitle,
deleteExtension,
closeDatabase,
getCache,
setCache
closeDatabase
};

View File

@@ -1,48 +0,0 @@
import { FastifyInstance, FastifyReply } from 'fastify';
import { proxyRequest, processM3U8Content, streamToReadable } from './proxy.service';
import { ProxyRequest } from '../../types';
async function proxyRoutes(fastify: FastifyInstance) {
fastify.get('/proxy', async (req: ProxyRequest, reply: FastifyReply) => {
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" });
}
});
}
export default proxyRoutes;

View File

@@ -1,68 +0,0 @@
import { Readable } from 'stream';
interface ProxyHeaders {
referer?: string;
origin?: string;
userAgent?: string;
}
interface ProxyResponse {
response: Response;
contentType: string | null;
isM3U8: boolean;
}
export async function proxyRequest(url: string, { referer, origin, userAgent }: ProxyHeaders): Promise<ProxyResponse> {
const headers: Record<string, string> = {
'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
};
}
export function processM3U8Content(
text: string,
baseUrl: URL,
{ referer, origin, userAgent }: ProxyHeaders
): string {
return text.replace(/^(?!#)(?!\s*$).+/gm, (line) => {
line = line.trim();
let absoluteUrl: string;
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()}`;
});
}
export function streamToReadable(webStream: ReadableStream): Readable {
return Readable.fromWeb(webStream as any);
}

62
src/shared/queries.js Normal file
View File

@@ -0,0 +1,62 @@
const { queryOne, run } = require('./database');
async function getCachedExtension(extName, id) {
return queryOne(
"SELECT metadata FROM extension WHERE ext_name = ? AND id = ?",
[extName, id]
);
}
async function cacheExtension(extName, id, title, metadata) {
return run(
`
INSERT INTO extension (ext_name, id, title, metadata, updated_at)
VALUES (?, ?, ?, ?, ?)
ON CONFLICT(ext_name, id)
DO UPDATE SET
title = excluded.title,
metadata = excluded.metadata,
updated_at = ?
`,
[extName, id, title, JSON.stringify(metadata), Date.now(), Date.now()]
);
}
async function getExtensionTitle(extName, id) {
const sql = "SELECT title FROM extension WHERE ext_name = ? AND id = ?";
const row = await queryOne(sql, [extName, id], 'anilist');
return row ? row.title : null;
}
async function deleteExtension(extName) {
return run(
"DELETE FROM extension WHERE ext_name = ?",
[extName]
);
}
async function getCache(key) {
return queryOne("SELECT result, created_at, ttl_ms FROM cache WHERE key = ?", [key], "cache");
}
async function setCache(key, result, ttl_ms) {
return run(
`
INSERT INTO cache (key, result, created_at, ttl_ms)
VALUES (?, ?, ?, ?)
ON CONFLICT(key)
DO UPDATE SET result = excluded.result, created_at = excluded.created_at, ttl_ms = excluded.ttl_ms
`,
[key, JSON.stringify(result), Date.now(), ttl_ms],
"cache"
);
}
module.exports = {
getCachedExtension,
cacheExtension,
getExtensionTitle,
deleteExtension,
getCache,
setCache
};