changed some files to ts
This commit is contained in:
223
package-lock.json
generated
223
package-lock.json
generated
@@ -12,6 +12,24 @@
|
||||
"@fastify/static": "^8.3.0",
|
||||
"fastify": "^5.6.2",
|
||||
"sqlite3": "^5.1.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.0.0",
|
||||
"ts-node": "^10.9.0",
|
||||
"typescript": "^5.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@cspotcode/source-map-support": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
||||
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/trace-mapping": "0.3.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@fastify/accept-negotiator": {
|
||||
@@ -233,6 +251,34 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.9",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
|
||||
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.0.3",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@lukeed/ms": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz",
|
||||
@@ -284,6 +330,44 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsconfig/node10": {
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz",
|
||||
"integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tsconfig/node12": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
|
||||
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tsconfig/node14": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
|
||||
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tsconfig/node16": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
|
||||
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz",
|
||||
"integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.16.0"
|
||||
}
|
||||
},
|
||||
"node_modules/abbrev": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||
@@ -297,6 +381,32 @@
|
||||
"integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.15.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn-walk": {
|
||||
"version": "8.3.4",
|
||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
|
||||
"integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"acorn": "^8.11.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||
@@ -416,6 +526,13 @@
|
||||
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/arg": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
|
||||
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/atomic-sleep": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
|
||||
@@ -694,6 +811,13 @@
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/create-require": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
|
||||
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
@@ -784,6 +908,16 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/diff": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/eastasianwidth": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||
@@ -1432,6 +1566,13 @@
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/make-error": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
||||
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/make-fetch-happen": {
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz",
|
||||
@@ -2679,6 +2820,50 @@
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-node": {
|
||||
"version": "10.9.2",
|
||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
|
||||
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cspotcode/source-map-support": "^0.8.0",
|
||||
"@tsconfig/node10": "^1.0.7",
|
||||
"@tsconfig/node12": "^1.0.7",
|
||||
"@tsconfig/node14": "^1.0.0",
|
||||
"@tsconfig/node16": "^1.0.2",
|
||||
"acorn": "^8.4.1",
|
||||
"acorn-walk": "^8.1.1",
|
||||
"arg": "^4.1.0",
|
||||
"create-require": "^1.1.0",
|
||||
"diff": "^4.0.1",
|
||||
"make-error": "^1.1.1",
|
||||
"v8-compile-cache-lib": "^3.0.1",
|
||||
"yn": "3.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"ts-node": "dist/bin.js",
|
||||
"ts-node-cwd": "dist/bin-cwd.js",
|
||||
"ts-node-esm": "dist/bin-esm.js",
|
||||
"ts-node-script": "dist/bin-script.js",
|
||||
"ts-node-transpile-only": "dist/bin-transpile.js",
|
||||
"ts-script": "dist/bin-script-deprecated.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@swc/core": ">=1.2.50",
|
||||
"@swc/wasm": ">=1.2.50",
|
||||
"@types/node": "*",
|
||||
"typescript": ">=2.7"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@swc/core": {
|
||||
"optional": true
|
||||
},
|
||||
"@swc/wasm": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
@@ -2691,6 +2876,27 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
|
||||
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unique-filename": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz",
|
||||
@@ -2717,6 +2923,13 @@
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/v8-compile-cache-lib": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
||||
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
@@ -2889,6 +3102,16 @@
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/yn": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
||||
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
10
package.json
10
package.json
@@ -2,9 +2,10 @@
|
||||
"name": "waifu-board-(server)",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"build": "tsc",
|
||||
"dev": "ts-node server.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
@@ -14,5 +15,10 @@
|
||||
"@fastify/static": "^8.3.0",
|
||||
"fastify": "^5.6.2",
|
||||
"sqlite3": "^5.1.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.0.0",
|
||||
"typescript": "^5.3.0",
|
||||
"ts-node": "^10.9.0"
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,25 @@
|
||||
const animeService = require('./anime.service');
|
||||
const { getExtension, getExtensionsList } = require('../shared/extensions');
|
||||
import {FastifyReply, FastifyRequest} from 'fastify';
|
||||
import * as animeService from './anime.service';
|
||||
import { getExtension, getExtensionsList } from '../shared/extensions';
|
||||
import {AnimeRequest, SearchRequest, ExtensionNameRequest, WatchStreamRequest, Anime} from '../types';
|
||||
|
||||
async function getAnime(req, reply) {
|
||||
export async function getAnime(req: AnimeRequest, reply: FastifyReply) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const source = req.query.ext || 'anilist';
|
||||
|
||||
let anime;
|
||||
let anime: Anime | { error: string };
|
||||
if (source === 'anilist') {
|
||||
anime = await animeService.getAnimeById(id);
|
||||
} else {
|
||||
const extensionName = source;
|
||||
const ext = getExtension(extensionName);
|
||||
|
||||
const results = await animeService.searchAnimeInExtension(ext, extensionName, id.replaceAll("-", " "));
|
||||
const results = await animeService.searchAnimeInExtension(
|
||||
ext,
|
||||
extensionName,
|
||||
id.replaceAll("-", " ")
|
||||
);
|
||||
anime = results[0] || null;
|
||||
}
|
||||
|
||||
@@ -23,7 +29,7 @@ async function getAnime(req, reply) {
|
||||
}
|
||||
}
|
||||
|
||||
async function getTrending(req, reply) {
|
||||
export async function getTrending(req: FastifyRequest, reply: FastifyReply) {
|
||||
try {
|
||||
const results = await animeService.getTrendingAnime();
|
||||
return { results };
|
||||
@@ -32,7 +38,7 @@ async function getTrending(req, reply) {
|
||||
}
|
||||
}
|
||||
|
||||
async function getTopAiring(req, reply) {
|
||||
export async function getTopAiring(req: FastifyRequest, reply: FastifyReply) {
|
||||
try {
|
||||
const results = await animeService.getTopAiringAnime();
|
||||
return { results };
|
||||
@@ -41,7 +47,7 @@ async function getTopAiring(req, reply) {
|
||||
}
|
||||
}
|
||||
|
||||
async function search(req, reply) {
|
||||
export async function search(req: SearchRequest, reply: FastifyReply) {
|
||||
try {
|
||||
const query = req.query.q;
|
||||
const results = await animeService.searchAnimeLocal(query);
|
||||
@@ -58,11 +64,11 @@ async function search(req, reply) {
|
||||
}
|
||||
}
|
||||
|
||||
async function getExtensions(req, reply) {
|
||||
export async function getExtensions(req: FastifyRequest, reply: FastifyReply) {
|
||||
return { extensions: getExtensionsList() };
|
||||
}
|
||||
|
||||
async function getExtensionSettings(req, reply) {
|
||||
export async function getExtensionSettings(req: ExtensionNameRequest, reply: FastifyReply) {
|
||||
const { name } = req.params;
|
||||
const ext = getExtension(name);
|
||||
|
||||
@@ -77,19 +83,18 @@ async function getExtensionSettings(req, reply) {
|
||||
return ext.getSettings();
|
||||
}
|
||||
|
||||
async function getWatchStream(req, reply) {
|
||||
export async function getWatchStream(req: WatchStreamRequest, reply: FastifyReply) {
|
||||
try {
|
||||
const { animeId, episode, server, category, ext } = req.query;
|
||||
|
||||
const extension = getExtension(ext);
|
||||
if (!extension) return { error: "Extension not found" };
|
||||
|
||||
let anime;
|
||||
let anime: Anime | { error: string };
|
||||
if (!isNaN(Number(animeId))) {
|
||||
anime = await animeService.getAnimeById(animeId);
|
||||
if (anime.error) return { error: "Anime metadata not found" };
|
||||
}
|
||||
else {
|
||||
if ('error' in anime) return { error: "Anime metadata not found" };
|
||||
} else {
|
||||
const results = await animeService.searchAnimeInExtension(
|
||||
extension,
|
||||
ext,
|
||||
@@ -107,16 +112,7 @@ async function getWatchStream(req, reply) {
|
||||
category
|
||||
);
|
||||
} catch (err) {
|
||||
return { error: err.message };
|
||||
const error = err as Error;
|
||||
return { error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getAnime,
|
||||
getTrending,
|
||||
getTopAiring,
|
||||
search,
|
||||
getExtensions,
|
||||
getExtensionSettings,
|
||||
getWatchStream
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
const controller = require('./anime.controller');
|
||||
import { FastifyInstance } from 'fastify';
|
||||
import * as controller from './anime.controller';
|
||||
|
||||
async function animeRoutes(fastify, options) {
|
||||
async function animeRoutes(fastify: FastifyInstance) {
|
||||
fastify.get('/anime/:id', controller.getAnime);
|
||||
fastify.get('/trending', controller.getTrending);
|
||||
fastify.get('/top-airing', controller.getTopAiring);
|
||||
@@ -10,4 +11,4 @@ async function animeRoutes(fastify, options) {
|
||||
fastify.get('/watch/stream', controller.getWatchStream);
|
||||
}
|
||||
|
||||
module.exports = animeRoutes;
|
||||
export default animeRoutes;
|
||||
@@ -1,7 +1,8 @@
|
||||
const { queryOne, queryAll } = require('../shared/database');
|
||||
const {getAllExtensions} = require("../shared/extensions");
|
||||
import { queryOne, queryAll } from '../shared/database';
|
||||
import { getAllExtensions } from '../shared/extensions';
|
||||
import { Anime, Extension, StreamData } from '../types';
|
||||
|
||||
async function getAnimeById(id) {
|
||||
export async function getAnimeById(id: string | number): Promise<Anime | { error: string }> {
|
||||
const row = await queryOne("SELECT full_data FROM anime WHERE id = ?", [id]);
|
||||
|
||||
if (!row) {
|
||||
@@ -11,17 +12,17 @@ async function getAnimeById(id) {
|
||||
return JSON.parse(row.full_data);
|
||||
}
|
||||
|
||||
async function getTrendingAnime() {
|
||||
export async function getTrendingAnime(): Promise<Anime[]> {
|
||||
const rows = await queryAll("SELECT full_data FROM trending ORDER BY rank ASC LIMIT 10");
|
||||
return rows.map(r => JSON.parse(r.full_data));
|
||||
return rows.map((r: { full_data: string; }) => JSON.parse(r.full_data));
|
||||
}
|
||||
|
||||
async function getTopAiringAnime() {
|
||||
export async function getTopAiringAnime(): Promise<Anime[]> {
|
||||
const rows = await queryAll("SELECT full_data FROM top_airing ORDER BY rank ASC LIMIT 10");
|
||||
return rows.map(r => JSON.parse(r.full_data));
|
||||
return rows.map((r: { full_data: string; }) => JSON.parse(r.full_data));
|
||||
}
|
||||
|
||||
async function searchAnimeLocal(query) {
|
||||
export async function searchAnimeLocal(query: string): Promise<Anime[]> {
|
||||
if (!query || query.length < 2) {
|
||||
return [];
|
||||
}
|
||||
@@ -29,7 +30,7 @@ async function searchAnimeLocal(query) {
|
||||
const sql = `SELECT full_data FROM anime WHERE full_data LIKE ? LIMIT 50`;
|
||||
const rows = await queryAll(sql, [`%${query}%`]);
|
||||
|
||||
const results = rows.map(row => JSON.parse(row.full_data));
|
||||
const results: Anime[] = rows.map((row: { full_data: string; }) => JSON.parse(row.full_data));
|
||||
|
||||
const cleanResults = results.filter(anime => {
|
||||
const q = query.toLowerCase();
|
||||
@@ -38,7 +39,7 @@ async function searchAnimeLocal(query) {
|
||||
anime.title.romaji,
|
||||
anime.title.native,
|
||||
...(anime.synonyms || [])
|
||||
].filter(Boolean).map(t => t.toLowerCase());
|
||||
].filter(Boolean).map(t => t!.toLowerCase());
|
||||
|
||||
return titles.some(t => t.includes(q));
|
||||
});
|
||||
@@ -46,7 +47,13 @@ async function searchAnimeLocal(query) {
|
||||
return cleanResults.slice(0, 10);
|
||||
}
|
||||
|
||||
async function searchAnimeInExtension(ext, name, query) {
|
||||
export async function searchAnimeInExtension(
|
||||
ext: Extension | null,
|
||||
name: string,
|
||||
query: string
|
||||
): Promise<Anime[]> {
|
||||
if (!ext) return [];
|
||||
|
||||
if ((ext.type === 'anime-board') && ext.search) {
|
||||
try {
|
||||
console.log(`[${name}] Searching for book: ${query}`);
|
||||
@@ -63,7 +70,7 @@ async function searchAnimeInExtension(ext, name, query) {
|
||||
return matches.map(m => ({
|
||||
id: m.id,
|
||||
extensionName: name,
|
||||
title: { romaji: m.title, english: m.title },
|
||||
title: { romaji: m.title, english: m.title, native: null },
|
||||
coverImage: { large: m.image || '' },
|
||||
averageScore: m.rating || m.score || null,
|
||||
format: 'ANIME',
|
||||
@@ -75,9 +82,11 @@ async function searchAnimeInExtension(ext, name, query) {
|
||||
console.error(`Extension search failed for ${name}:`, e);
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
async function searchAnimeExtensions(query) {
|
||||
export async function searchAnimeExtensions(query: string): Promise<Anime[]> {
|
||||
const extensions = getAllExtensions();
|
||||
|
||||
for (const [name, ext] of extensions) {
|
||||
@@ -88,7 +97,13 @@ async function searchAnimeExtensions(query) {
|
||||
return [];
|
||||
}
|
||||
|
||||
async function getStreamData(extension, animeData, episode, server, category) {
|
||||
export async function getStreamData(
|
||||
extension: Extension,
|
||||
animeData: Anime,
|
||||
episode: string,
|
||||
server?: string,
|
||||
category?: string
|
||||
): Promise<StreamData> {
|
||||
const searchOptions = {
|
||||
query: animeData.title.english || animeData.title.romaji,
|
||||
dub: category === 'dub',
|
||||
@@ -99,6 +114,10 @@ async function getStreamData(extension, animeData, episode, server, category) {
|
||||
}
|
||||
};
|
||||
|
||||
if (!extension.search || !extension.findEpisodes || !extension.findEpisodeServer) {
|
||||
throw new Error("Extension doesn't support required methods");
|
||||
}
|
||||
|
||||
const searchResults = await extension.search(searchOptions);
|
||||
|
||||
if (!searchResults || searchResults.length === 0) {
|
||||
@@ -118,13 +137,3 @@ async function getStreamData(extension, animeData, episode, server, category) {
|
||||
|
||||
return streamData;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getAnimeById,
|
||||
getTrendingAnime,
|
||||
getTopAiringAnime,
|
||||
searchAnimeLocal,
|
||||
searchAnimeExtensions,
|
||||
searchAnimeInExtension,
|
||||
getStreamData
|
||||
};
|
||||
@@ -1,14 +1,16 @@
|
||||
const booksService = require('./books.service');
|
||||
const {getExtension} = require("../shared/extensions");
|
||||
import {FastifyReply, FastifyRequest} from 'fastify';
|
||||
import * as booksService from './books.service';
|
||||
import { getExtension } from '../shared/extensions';
|
||||
import { BookRequest, SearchRequest, ChapterRequest } from '../types';
|
||||
|
||||
async function getBook(req, reply) {
|
||||
export async function getBook(req: BookRequest, reply: FastifyReply) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const source = req.query.ext || 'anilist';
|
||||
|
||||
let book;
|
||||
if (source === 'anilist') {
|
||||
book = await booksService.getBookById(id);
|
||||
book = await booksService.getBookById(id);
|
||||
} else {
|
||||
const extensionName = source;
|
||||
const ext = getExtension(extensionName);
|
||||
@@ -20,11 +22,12 @@ async function getBook(req, reply) {
|
||||
return book;
|
||||
|
||||
} catch (err) {
|
||||
return { error: err.toString() };
|
||||
const error = err as Error;
|
||||
return { error: error.toString() };
|
||||
}
|
||||
}
|
||||
|
||||
async function getTrending(req, reply) {
|
||||
export async function getTrending(req: FastifyRequest, reply: FastifyReply) {
|
||||
try {
|
||||
const results = await booksService.getTrendingBooks();
|
||||
return { results };
|
||||
@@ -33,7 +36,7 @@ async function getTrending(req, reply) {
|
||||
}
|
||||
}
|
||||
|
||||
async function getPopular(req, reply) {
|
||||
export async function getPopular(req: FastifyRequest, reply: FastifyReply) {
|
||||
try {
|
||||
const results = await booksService.getPopularBooks();
|
||||
return { results };
|
||||
@@ -42,7 +45,7 @@ async function getPopular(req, reply) {
|
||||
}
|
||||
}
|
||||
|
||||
async function searchBooks(req, reply) {
|
||||
export async function searchBooks(req: SearchRequest, reply: FastifyReply) {
|
||||
try {
|
||||
const query = req.query.q;
|
||||
|
||||
@@ -60,13 +63,14 @@ async function searchBooks(req, reply) {
|
||||
const extResults = await booksService.searchBooksExtensions(query);
|
||||
return { results: extResults };
|
||||
|
||||
} catch(e) {
|
||||
console.error("Search Error:", e.message);
|
||||
} catch (e) {
|
||||
const error = e as Error;
|
||||
console.error("Search Error:", error.message);
|
||||
return { results: [] };
|
||||
}
|
||||
}
|
||||
|
||||
async function getChapters(req, reply) {
|
||||
export async function getChapters(req: BookRequest, reply: FastifyReply) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
return await booksService.getChaptersForBook(id);
|
||||
@@ -75,7 +79,7 @@ async function getChapters(req, reply) {
|
||||
}
|
||||
}
|
||||
|
||||
async function getChapterContent(req, reply) {
|
||||
export async function getChapterContent(req: ChapterRequest, reply: FastifyReply) {
|
||||
try {
|
||||
const { bookId, chapter, provider } = req.params;
|
||||
|
||||
@@ -87,17 +91,9 @@ async function getChapterContent(req, reply) {
|
||||
|
||||
return reply.send(content);
|
||||
} catch (err) {
|
||||
console.error("getChapterContent error:", err.message);
|
||||
const error = err as Error;
|
||||
console.error("getChapterContent error:", error.message);
|
||||
|
||||
return reply.code(500).send({ error: "Error loading chapter" });
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getBook,
|
||||
getTrending,
|
||||
getPopular,
|
||||
searchBooks,
|
||||
getChapters,
|
||||
getChapterContent
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
const controller = require('./books.controller');
|
||||
import { FastifyInstance } from 'fastify';
|
||||
import * as controller from './books.controller';
|
||||
|
||||
async function booksRoutes(fastify, options) {
|
||||
async function booksRoutes(fastify: FastifyInstance) {
|
||||
fastify.get('/book/:id', controller.getBook);
|
||||
fastify.get('/books/trending', controller.getTrending);
|
||||
fastify.get('/books/popular', controller.getPopular);
|
||||
@@ -9,4 +10,4 @@ async function booksRoutes(fastify, options) {
|
||||
fastify.get('/book/:bookId/:chapter/:provider', controller.getChapterContent);
|
||||
}
|
||||
|
||||
module.exports = booksRoutes;
|
||||
export default booksRoutes;
|
||||
@@ -1,7 +1,8 @@
|
||||
const { queryOne, queryAll } = require('../shared/database');
|
||||
const { getAllExtensions } = require('../shared/extensions');
|
||||
import { queryOne, queryAll } from '../shared/database';
|
||||
import { getAllExtensions } from '../shared/extensions';
|
||||
import { Book, Extension, ChapterWithProvider, ChapterContent } from '../types';
|
||||
|
||||
async function getBookById(id) {
|
||||
export async function getBookById(id: string | number): Promise<Book | { error: string }> {
|
||||
const row = await queryOne("SELECT full_data FROM books WHERE id = ?", [id]);
|
||||
|
||||
if (row) {
|
||||
@@ -31,31 +32,31 @@ async function getBookById(id) {
|
||||
const response = await fetch('https://graphql.anilist.co', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
|
||||
body: JSON.stringify({ query, variables: { id: parseInt(id) } })
|
||||
body: JSON.stringify({ query, variables: { id: parseInt(id.toString()) } })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.data && data.data.Media) {
|
||||
return data.data.Media;
|
||||
}
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
console.error("Fetch error:", e);
|
||||
}
|
||||
|
||||
return { error: "Book not found" };
|
||||
}
|
||||
|
||||
async function getTrendingBooks() {
|
||||
export async function getTrendingBooks(): Promise<Book[]> {
|
||||
const rows = await queryAll("SELECT full_data FROM trending_books ORDER BY rank ASC LIMIT 10");
|
||||
return rows.map(r => JSON.parse(r.full_data));
|
||||
return rows.map((r: { full_data: string; }) => JSON.parse(r.full_data));
|
||||
}
|
||||
|
||||
async function getPopularBooks() {
|
||||
export async function getPopularBooks(): Promise<Book[]> {
|
||||
const rows = await queryAll("SELECT full_data FROM popular_books ORDER BY rank ASC LIMIT 10");
|
||||
return rows.map(r => JSON.parse(r.full_data));
|
||||
return rows.map((r: { full_data: string; }) => JSON.parse(r.full_data));
|
||||
}
|
||||
|
||||
async function searchBooksLocal(query) {
|
||||
export async function searchBooksLocal(query: string): Promise<Book[]> {
|
||||
if (!query || query.length < 2) {
|
||||
return [];
|
||||
}
|
||||
@@ -63,7 +64,7 @@ async function searchBooksLocal(query) {
|
||||
const sql = `SELECT full_data FROM books WHERE full_data LIKE ? LIMIT 50`;
|
||||
const rows = await queryAll(sql, [`%${query}%`]);
|
||||
|
||||
const results = rows.map(row => JSON.parse(row.full_data));
|
||||
const results: Book[] = rows.map((row: { full_data: string; }) => JSON.parse(row.full_data));
|
||||
|
||||
const clean = results.filter(book => {
|
||||
const searchTerms = [
|
||||
@@ -71,7 +72,7 @@ async function searchBooksLocal(query) {
|
||||
book.title.romaji,
|
||||
book.title.native,
|
||||
...(book.synonyms || [])
|
||||
].filter(Boolean).map(t => t.toLowerCase());
|
||||
].filter(Boolean).map(t => t!.toLowerCase());
|
||||
|
||||
return searchTerms.some(term => term.includes(query.toLowerCase()));
|
||||
});
|
||||
@@ -79,7 +80,7 @@ async function searchBooksLocal(query) {
|
||||
return clean.slice(0, 10);
|
||||
}
|
||||
|
||||
async function searchBooksAniList(query) {
|
||||
export async function searchBooksAniList(query: string): Promise<Book[]> {
|
||||
const gql = `
|
||||
query ($search: String) {
|
||||
Page(page: 1, perPage: 5) {
|
||||
@@ -107,7 +108,7 @@ async function searchBooksAniList(query) {
|
||||
return [];
|
||||
}
|
||||
|
||||
async function searchBooksInExtension(ext, name, query) {
|
||||
export async function searchBooksInExtension(ext: Extension | null, name: string, query: string): Promise<Book[]> {
|
||||
if (!ext) return [];
|
||||
|
||||
if ((ext.type === 'book-board' || ext.type === 'manga-board') && ext.search) {
|
||||
@@ -126,7 +127,7 @@ async function searchBooksInExtension(ext, name, query) {
|
||||
return matches.map(m => ({
|
||||
id: m.id,
|
||||
extensionName: name,
|
||||
title: { romaji: m.title, english: m.title },
|
||||
title: { romaji: m.title, english: m.title, native: null },
|
||||
coverImage: { large: m.image || '' },
|
||||
averageScore: m.rating || m.score || null,
|
||||
format: 'MANGA',
|
||||
@@ -142,7 +143,7 @@ async function searchBooksInExtension(ext, name, query) {
|
||||
return [];
|
||||
}
|
||||
|
||||
async function searchBooksExtensions(query) {
|
||||
export async function searchBooksExtensions(query: string): Promise<Book[]> {
|
||||
const extensions = getAllExtensions();
|
||||
|
||||
for (const [name, ext] of extensions) {
|
||||
@@ -153,16 +154,17 @@ async function searchBooksExtensions(query) {
|
||||
return [];
|
||||
}
|
||||
|
||||
async function getChaptersForBook(id) {
|
||||
let bookData = null;
|
||||
let searchTitle = null;
|
||||
export async function getChaptersForBook(id: string): Promise<{ chapters: ChapterWithProvider[] }> {
|
||||
let bookData: Book | null = null;
|
||||
let searchTitle: string | null = null;
|
||||
|
||||
if (typeof id === "string" && isNaN(Number(id))) {
|
||||
searchTitle = id.replaceAll("-", " ");
|
||||
} else {
|
||||
bookData = await queryOne("SELECT full_data FROM books WHERE id = ?", [id])
|
||||
.then(row => row ? JSON.parse(row.full_data) : null)
|
||||
.catch(() => null);
|
||||
const result = await getBookById(id);
|
||||
if (!('error' in result)) {
|
||||
bookData = result;
|
||||
}
|
||||
|
||||
if (!bookData) {
|
||||
try {
|
||||
@@ -181,17 +183,16 @@ async function getChaptersForBook(id) {
|
||||
|
||||
const d = await res.json();
|
||||
if (d.data?.Media) bookData = d.data.Media;
|
||||
} catch (e) {}
|
||||
} catch (e) { }
|
||||
}
|
||||
|
||||
if (!bookData) return { chapters: [] };
|
||||
|
||||
const titles = [bookData.title.english, bookData.title.romaji].filter(Boolean);
|
||||
const titles = [bookData.title.english, bookData.title.romaji].filter(Boolean) as string[];
|
||||
searchTitle = titles[0];
|
||||
}
|
||||
|
||||
|
||||
const allChapters = [];
|
||||
const allChapters: ChapterWithProvider[] = [];
|
||||
const extensions = getAllExtensions();
|
||||
|
||||
const searchPromises = Array.from(extensions.entries())
|
||||
@@ -203,25 +204,25 @@ async function getChaptersForBook(id) {
|
||||
try {
|
||||
console.log(`[${name}] Searching chapters for: ${searchTitle}`);
|
||||
|
||||
const matches = await ext.search({
|
||||
query: searchTitle,
|
||||
const matches = await ext.search!({
|
||||
query: searchTitle!,
|
||||
media: bookData ? {
|
||||
romajiTitle: bookData.title.romaji,
|
||||
englishTitle: bookData.title.english,
|
||||
startDate: bookData.startDate
|
||||
} : {}
|
||||
englishTitle: bookData.title.english || "",
|
||||
startDate: bookData.startDate || { year: 0, month: 0, day: 0 }
|
||||
} : { romajiTitle: searchTitle!, englishTitle: searchTitle!, startDate: { year: 0, month: 0, day: 0 } }
|
||||
});
|
||||
|
||||
if (matches?.length) {
|
||||
const best = matches[0];
|
||||
const chaps = await ext.findChapters(best.id);
|
||||
const chaps = await ext.findChapters!(best.id);
|
||||
|
||||
if (chaps?.length) {
|
||||
console.log(`[${name}] Found ${chaps.length} chapters.`);
|
||||
chaps.forEach(ch => {
|
||||
chaps.forEach((ch: { id: any; number: { toString: () => string; }; title: any; releaseDate: any; }) => {
|
||||
allChapters.push({
|
||||
id: ch.id,
|
||||
number: parseFloat(ch.number),
|
||||
number: parseFloat(ch.number.toString()),
|
||||
title: ch.title,
|
||||
date: ch.releaseDate,
|
||||
provider: name
|
||||
@@ -232,16 +233,17 @@ async function getChaptersForBook(id) {
|
||||
console.log(`[${name}] No matches found for book.`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Failed to fetch chapters from ${name}:`, e.message);
|
||||
const error = e as Error;
|
||||
console.error(`Failed to fetch chapters from ${name}:`, error.message);
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(searchPromises);
|
||||
|
||||
return { chapters: allChapters.sort((a, b) => a.number - b.number) };
|
||||
return { chapters: allChapters.sort((a, b) => Number(a.number) - Number(b.number)) };
|
||||
}
|
||||
|
||||
async function getChapterContent(bookId, chapterIndex, providerName) {
|
||||
export async function getChapterContent(bookId: string, chapterIndex: string, providerName: string): Promise<ChapterContent> {
|
||||
const extensions = getAllExtensions();
|
||||
const ext = extensions.get(providerName);
|
||||
|
||||
@@ -273,6 +275,10 @@ async function getChapterContent(bookId, chapterIndex, providerName) {
|
||||
const chapterNumber = typeof selectedChapter.number === 'number' ? selectedChapter.number : index;
|
||||
|
||||
try {
|
||||
if (!ext.findChapterPages) {
|
||||
throw new Error("Extension doesn't support findChapterPages");
|
||||
}
|
||||
|
||||
if (ext.mediaType === "manga") {
|
||||
const pages = await ext.findChapterPages(chapterId);
|
||||
return {
|
||||
@@ -299,19 +305,8 @@ async function getChapterContent(bookId, chapterIndex, providerName) {
|
||||
|
||||
throw new Error("Unknown mediaType");
|
||||
} catch (err) {
|
||||
console.error(`[Chapter] Error loading from ${providerName}:`, err && err.message ? err.message : err);
|
||||
const error = err as Error;
|
||||
console.error(`[Chapter] Error loading from ${providerName}:`, error.message);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getBookById,
|
||||
getTrendingBooks,
|
||||
getPopularBooks,
|
||||
searchBooksLocal,
|
||||
searchBooksAniList,
|
||||
searchBooksExtensions,
|
||||
searchBooksInExtension,
|
||||
getChaptersForBook,
|
||||
getChapterContent
|
||||
};
|
||||
@@ -1,8 +1,9 @@
|
||||
const { proxyRequest, processM3U8Content, streamToReadable } = require('./proxy.service');
|
||||
import { FastifyInstance, FastifyReply } from 'fastify';
|
||||
import { proxyRequest, processM3U8Content, streamToReadable } from './proxy.service';
|
||||
import { ProxyRequest } from '../../types';
|
||||
|
||||
async function proxyRoutes(fastify, options) {
|
||||
|
||||
fastify.get('/proxy', async (req, reply) => {
|
||||
async function proxyRoutes(fastify: FastifyInstance) {
|
||||
fastify.get('/proxy', async (req: ProxyRequest, reply: FastifyReply) => {
|
||||
const { url, referer, origin, userAgent } = req.query;
|
||||
|
||||
if (!url) {
|
||||
@@ -34,7 +35,7 @@ async function proxyRoutes(fastify, options) {
|
||||
|
||||
return processedContent;
|
||||
} else {
|
||||
return reply.send(streamToReadable(response.body));
|
||||
return reply.send(streamToReadable(response.body!));
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
@@ -44,4 +45,4 @@ async function proxyRoutes(fastify, options) {
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = proxyRoutes;
|
||||
export default proxyRoutes;
|
||||
@@ -1,7 +1,19 @@
|
||||
const { Readable } = require('stream');
|
||||
import { Readable } from 'stream';
|
||||
|
||||
async function proxyRequest(url, { referer, origin, userAgent }) {
|
||||
const headers = {
|
||||
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'
|
||||
@@ -26,14 +38,18 @@ async function proxyRequest(url, { referer, origin, userAgent }) {
|
||||
};
|
||||
}
|
||||
|
||||
function processM3U8Content(text, baseUrl, { referer, origin, userAgent }) {
|
||||
export function processM3U8Content(
|
||||
text: string,
|
||||
baseUrl: URL,
|
||||
{ referer, origin, userAgent }: ProxyHeaders
|
||||
): string {
|
||||
return text.replace(/^(?!#)(?!\s*$).+/gm, (line) => {
|
||||
line = line.trim();
|
||||
let absoluteUrl;
|
||||
let absoluteUrl: string;
|
||||
|
||||
try {
|
||||
absoluteUrl = new URL(line, baseUrl).href;
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
return line;
|
||||
}
|
||||
|
||||
@@ -47,12 +63,6 @@ function processM3U8Content(text, baseUrl, { referer, origin, userAgent }) {
|
||||
});
|
||||
}
|
||||
|
||||
function streamToReadable(webStream) {
|
||||
return Readable.fromWeb(webStream);
|
||||
export function streamToReadable(webStream: ReadableStream): Readable {
|
||||
return Readable.fromWeb(webStream as any);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
proxyRequest,
|
||||
processM3U8Content,
|
||||
streamToReadable
|
||||
};
|
||||
204
src/types.ts
Normal file
204
src/types.ts
Normal file
@@ -0,0 +1,204 @@
|
||||
import { FastifyRequest, FastifyReply } from 'fastify';
|
||||
|
||||
export interface AnimeTitle {
|
||||
romaji: string;
|
||||
english: string | null;
|
||||
native: string | null;
|
||||
userPreferred?: string;
|
||||
}
|
||||
|
||||
export interface CoverImage {
|
||||
extraLarge?: string;
|
||||
large: string;
|
||||
medium?: string;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
export interface StartDate {
|
||||
year: number;
|
||||
month: number;
|
||||
day: number;
|
||||
}
|
||||
|
||||
export interface Anime {
|
||||
id: number | string;
|
||||
title: AnimeTitle;
|
||||
coverImage: CoverImage;
|
||||
bannerImage?: string;
|
||||
description?: string;
|
||||
averageScore: number | null;
|
||||
format: string;
|
||||
seasonYear: number | null;
|
||||
startDate?: StartDate;
|
||||
synonyms?: string[];
|
||||
extensionName?: string;
|
||||
isExtensionResult?: boolean;
|
||||
}
|
||||
|
||||
export interface Book {
|
||||
id: number | string;
|
||||
title: AnimeTitle;
|
||||
coverImage: CoverImage;
|
||||
bannerImage?: string;
|
||||
description?: string;
|
||||
averageScore: number | null;
|
||||
format: string;
|
||||
seasonYear: number | null;
|
||||
startDate?: StartDate;
|
||||
synonyms?: string[];
|
||||
extensionName?: string;
|
||||
isExtensionResult?: boolean;
|
||||
}
|
||||
|
||||
export interface ExtensionSearchOptions {
|
||||
query: string;
|
||||
dub?: boolean;
|
||||
media?: {
|
||||
romajiTitle: string;
|
||||
englishTitle: string;
|
||||
startDate: StartDate;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ExtensionSearchResult {
|
||||
id: string;
|
||||
title: string;
|
||||
image?: string;
|
||||
rating?: number;
|
||||
score?: number;
|
||||
}
|
||||
|
||||
export interface Episode {
|
||||
id: string;
|
||||
number: number;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface Chapter {
|
||||
id: string;
|
||||
number: string | number;
|
||||
title?: string;
|
||||
releaseDate?: string;
|
||||
}
|
||||
|
||||
export interface ChapterWithProvider extends Chapter {
|
||||
provider: string;
|
||||
date?: string;
|
||||
}
|
||||
|
||||
export interface Extension {
|
||||
type: 'anime-board' | 'book-board' | 'manga-board';
|
||||
mediaType?: 'manga' | 'ln';
|
||||
search?: (options: ExtensionSearchOptions) => Promise<ExtensionSearchResult[]>;
|
||||
findEpisodes?: (id: string) => Promise<Episode[]>;
|
||||
findEpisodeServer?: (episode: Episode, server: string) => Promise<any>;
|
||||
findChapters?: (id: string) => Promise<Chapter[]>;
|
||||
findChapterPages?: (chapterId: string) => Promise<any>;
|
||||
getSettings?: () => ExtensionSettings;
|
||||
}
|
||||
|
||||
export interface ExtensionSettings {
|
||||
episodeServers: string[];
|
||||
supportsDub: boolean;
|
||||
}
|
||||
|
||||
export interface StreamData {
|
||||
url?: string;
|
||||
sources?: any[];
|
||||
subtitles?: any[];
|
||||
}
|
||||
|
||||
export interface MangaChapterContent {
|
||||
type: 'manga';
|
||||
chapterId: string;
|
||||
title: string | null;
|
||||
number: number;
|
||||
provider: string;
|
||||
pages: any[];
|
||||
}
|
||||
|
||||
export interface LightNovelChapterContent {
|
||||
type: 'ln';
|
||||
chapterId: string;
|
||||
title: string | null;
|
||||
number: number;
|
||||
provider: string;
|
||||
content: any;
|
||||
}
|
||||
|
||||
export type ChapterContent = MangaChapterContent | LightNovelChapterContent;
|
||||
|
||||
export interface AnimeParams {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface AnimeQuery {
|
||||
ext?: string;
|
||||
}
|
||||
|
||||
export interface SearchQuery {
|
||||
q: string;
|
||||
}
|
||||
|
||||
export interface ExtensionNameParams {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface WatchStreamQuery {
|
||||
animeId: string;
|
||||
episode: string;
|
||||
server?: string;
|
||||
category?: string;
|
||||
ext: string;
|
||||
}
|
||||
|
||||
export interface BookParams {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface BookQuery {
|
||||
ext?: string;
|
||||
}
|
||||
|
||||
export interface ChapterParams {
|
||||
bookId: string;
|
||||
chapter: string;
|
||||
provider: string;
|
||||
}
|
||||
|
||||
export interface ProxyQuery {
|
||||
url: string;
|
||||
referer?: string;
|
||||
origin?: string;
|
||||
userAgent?: string;
|
||||
}
|
||||
|
||||
export type AnimeRequest = FastifyRequest<{
|
||||
Params: AnimeParams;
|
||||
Querystring: AnimeQuery;
|
||||
}>;
|
||||
|
||||
export type SearchRequest = FastifyRequest<{
|
||||
Querystring: SearchQuery;
|
||||
}>;
|
||||
|
||||
export type ExtensionNameRequest = FastifyRequest<{
|
||||
Params: ExtensionNameParams;
|
||||
}>;
|
||||
|
||||
export type WatchStreamRequest = FastifyRequest<{
|
||||
Querystring: WatchStreamQuery;
|
||||
}>;
|
||||
|
||||
export type BookRequest = FastifyRequest<{
|
||||
Params: BookParams;
|
||||
Querystring: BookQuery;
|
||||
}>;
|
||||
|
||||
export type ChapterRequest = FastifyRequest<{
|
||||
Params: ChapterParams;
|
||||
}>;
|
||||
|
||||
export type ProxyRequest = FastifyRequest<{
|
||||
Querystring: ProxyQuery;
|
||||
}>;
|
||||
@@ -1,47 +1,48 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
async function viewsRoutes(fastify, options) {
|
||||
async function viewsRoutes(fastify: FastifyInstance) {
|
||||
|
||||
fastify.get('/', (req, reply) => {
|
||||
fastify.get('/', (req: FastifyRequest, reply: FastifyReply) => {
|
||||
const stream = fs.createReadStream(path.join(__dirname, '..', '..', 'views', 'index.html'));
|
||||
reply.type('text/html').send(stream);
|
||||
});
|
||||
|
||||
fastify.get('/books', (req, reply) => {
|
||||
fastify.get('/books', (req: FastifyRequest, reply: FastifyReply) => {
|
||||
const stream = fs.createReadStream(path.join(__dirname, '..', '..', 'views', 'books.html'));
|
||||
reply.type('text/html').send(stream);
|
||||
});
|
||||
|
||||
fastify.get('/anime/:id', (req, reply) => {
|
||||
fastify.get('/anime/:id', (req: FastifyRequest, reply: FastifyReply) => {
|
||||
const stream = fs.createReadStream(path.join(__dirname, '..', '..', 'views', 'anime.html'));
|
||||
reply.type('text/html').send(stream);
|
||||
});
|
||||
|
||||
fastify.get('/anime/:extension/*', (req, reply) => {
|
||||
fastify.get('/anime/:extension/*', (req: FastifyRequest, reply: FastifyReply) => {
|
||||
const stream = fs.createReadStream(path.join(__dirname, '..', '..', 'views', 'anime.html'));
|
||||
reply.type('text/html').send(stream);
|
||||
});
|
||||
|
||||
fastify.get('/watch/:id/:episode', (req, reply) => {
|
||||
fastify.get('/watch/:id/:episode', (req: FastifyRequest, reply: FastifyReply) => {
|
||||
const stream = fs.createReadStream(path.join(__dirname, '..', '..', 'views', 'watch.html'));
|
||||
reply.type('text/html').send(stream);
|
||||
});
|
||||
|
||||
fastify.get('/book/:id', (req, reply) => {
|
||||
fastify.get('/book/:id', (req: FastifyRequest, reply: FastifyReply) => {
|
||||
const stream = fs.createReadStream(path.join(__dirname, '..', '..', 'views', 'book.html'));
|
||||
reply.type('text/html').send(stream);
|
||||
});
|
||||
|
||||
fastify.get('/book/:extension/*', (req, reply) => {
|
||||
fastify.get('/book/:extension/*', (req: FastifyRequest, reply: FastifyReply) => {
|
||||
const stream = fs.createReadStream(path.join(__dirname, '..', '..', 'views', 'book.html'));
|
||||
reply.type('text/html').send(stream);
|
||||
});
|
||||
|
||||
fastify.get('/read/:provider/:chapter/*', (req, reply) => {
|
||||
fastify.get('/read/:provider/:chapter/*', (req: FastifyRequest, reply: FastifyReply) => {
|
||||
const stream = fs.createReadStream(path.join(__dirname, '..', '..', 'views', 'read.html'));
|
||||
reply.type('text/html').send(stream);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = viewsRoutes;
|
||||
export default viewsRoutes;
|
||||
13
tsconfig.json
Normal file
13
tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2023",
|
||||
"module": "CommonJS",
|
||||
"allowJs": true,
|
||||
"checkJs": false,
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
Reference in New Issue
Block a user