ani-auto 1.4.4 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/anime/LICENSE +21 -0
- package/anime/api/index.js +110 -0
- package/anime/app.js +79 -0
- package/anime/controllers/animeInfoController.js +48 -0
- package/anime/controllers/animeListController.js +21 -0
- package/anime/controllers/homeController.js +42 -0
- package/anime/controllers/playController.js +140 -0
- package/anime/controllers/queueController.js +20 -0
- package/anime/controllers/testController.js +667 -0
- package/anime/index.js +139 -0
- package/anime/middleware/cache.js +64 -0
- package/anime/middleware/errorHandler.js +33 -0
- package/anime/middleware/rateLimiter.js +73 -0
- package/anime/models/animeInfoModel.js +193 -0
- package/anime/models/animeListModel.js +69 -0
- package/anime/models/homeModel.js +33 -0
- package/anime/models/playModel.js +528 -0
- package/anime/models/queueModel.js +22 -0
- package/anime/package.json +27 -0
- package/anime/pnpm-lock.yaml +1774 -0
- package/anime/readme.md +236 -0
- package/anime/routes/animeInfoRoutes.js +9 -0
- package/anime/routes/animeListRoutes.js +9 -0
- package/anime/routes/homeRoutes.js +10 -0
- package/anime/routes/playRoutes.js +11 -0
- package/anime/routes/queueRoutes.js +8 -0
- package/anime/routes/testRoutes.js +325 -0
- package/anime/routes/webhookRoutes.js +23 -0
- package/anime/scrapers/animepahe.js +1053 -0
- package/anime/test-server.js +10 -0
- package/anime/test.sh +275 -0
- package/anime/utils/browser.js +172 -0
- package/anime/utils/config.js +209 -0
- package/anime/utils/dataProcessor.js +172 -0
- package/anime/utils/diskCache.js +228 -0
- package/anime/utils/flaresolverr.js +361 -0
- package/anime/utils/jsParser.js +19 -0
- package/anime/utils/redis.js +64 -0
- package/anime/utils/requestManager.js +706 -0
- package/anime/utils/urlConverter.js +88 -0
- package/anime/utils/webhookNotifier.js +190 -0
- package/cookiereader.py +291 -0
- package/package.json +14 -6
- package/src/anilist.js +15 -2
- package/src/api/anilist.js +32 -0
- package/src/api/anime.js +181 -0
- package/src/api/cf-bypass.js +131 -0
- package/src/api/config.js +134 -0
- package/src/api/daemon.js +62 -0
- package/src/api/download.js +98 -0
- package/src/api/match.js +58 -0
- package/src/api/runs.js +15 -0
- package/src/api/update.js +96 -0
- package/src/cli.js +18 -2
- package/src/config.js +14 -2
- package/src/daemon.js +1 -1
- package/src/db.js +38 -11
- package/src/engine.js +62 -11
- package/src/scraper.js +2 -2
- package/src/server.js +108 -0
- package/utils.js +21 -4
- package/web/index.html +13 -0
- package/web/package-lock.json +1420 -0
- package/web/package.json +24 -0
- package/web/src/App.vue +200 -0
- package/web/src/api/client.js +75 -0
- package/web/src/assets/styles/base.css +961 -0
- package/web/src/components/UpdateModal.vue +157 -0
- package/web/src/components/sidebar/SidebarResizable.vue +170 -0
- package/web/src/components/sidebar/sidebarConfig.js +24 -0
- package/web/src/main.js +104 -0
- package/web/src/router/index.js +44 -0
- package/web/src/stores/config.js +27 -0
- package/web/src/stores/download.js +107 -0
- package/web/src/stores/update.js +73 -0
- package/web/src/views/About.vue +42 -0
- package/web/src/views/AniListSync.vue +122 -0
- package/web/src/views/AnimeDetail.vue +202 -0
- package/web/src/views/AnimeList.vue +110 -0
- package/web/src/views/CFResult.vue +312 -0
- package/web/src/views/DaemonControl.vue +83 -0
- package/web/src/views/Dashboard.vue +187 -0
- package/web/src/views/DownloadCenter.vue +153 -0
- package/web/src/views/MatchFinder.vue +131 -0
- package/web/src/views/OAuthCallback.vue +44 -0
- package/web/src/views/Onboarding.vue +93 -0
- package/web/src/views/RunHistory.vue +122 -0
- package/web/src/views/Settings.vue +410 -0
- package/web/vite.config.js +23 -0
package/anime/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 ElijahCodes12345
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
const cors = require("cors");
|
|
3
|
+
const Config = require("../utils/config");
|
|
4
|
+
const { errorHandler, CustomError } = require("../middleware/errorHandler");
|
|
5
|
+
const rateLimiter = require("../middleware/rateLimiter");
|
|
6
|
+
const homeRoutes = require("../routes/homeRoutes");
|
|
7
|
+
const queueRoutes = require("../routes/queueRoutes");
|
|
8
|
+
const animeListRoutes = require("../routes/animeListRoutes");
|
|
9
|
+
const animeInfoRoutes = require("../routes/animeInfoRoutes");
|
|
10
|
+
const playRoutes = require("../routes/playRoutes");
|
|
11
|
+
const webhookRoutes = require("../routes/webhookRoutes");
|
|
12
|
+
const PlayController = require("../controllers/playController");
|
|
13
|
+
const cache = require("../middleware/cache");
|
|
14
|
+
const testRoutes = require("../routes/testRoutes");
|
|
15
|
+
const { startWebhookNotifier } = require("../utils/webhookNotifier");
|
|
16
|
+
|
|
17
|
+
const app = express();
|
|
18
|
+
|
|
19
|
+
// Load environment variables into Config
|
|
20
|
+
try {
|
|
21
|
+
Config.validate();
|
|
22
|
+
Config.loadFromEnv();
|
|
23
|
+
console.log("\x1b[36m%s\x1b[0m", "Configuration set!.");
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.error(error.message);
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
startWebhookNotifier();
|
|
30
|
+
|
|
31
|
+
// CORS Configuration
|
|
32
|
+
const corsOptions = {
|
|
33
|
+
origin: function (origin, callback) {
|
|
34
|
+
if (!origin) return callback(null, true);
|
|
35
|
+
|
|
36
|
+
const allowedOrigins = process.env.ALLOWED_ORIGINS
|
|
37
|
+
? process.env.ALLOWED_ORIGINS.split(",").map((o) => o.trim())
|
|
38
|
+
: ["*"]; // Default: allow all origins
|
|
39
|
+
|
|
40
|
+
if (allowedOrigins.includes("*")) {
|
|
41
|
+
return callback(null, true);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (allowedOrigins.includes(origin)) {
|
|
45
|
+
callback(null, true);
|
|
46
|
+
} else {
|
|
47
|
+
callback(new Error("Not allowed by CORS"));
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
credentials: true,
|
|
51
|
+
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
|
52
|
+
allowedHeaders: ["Content-Type", "Authorization", "X-Requested-With"],
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
app.use(cors(corsOptions));
|
|
56
|
+
|
|
57
|
+
// Global request/response timeout — prevents slow upstream requests from hanging
|
|
58
|
+
app.use((req, res, next) => {
|
|
59
|
+
req.setTimeout(150000);
|
|
60
|
+
res.setTimeout(150000);
|
|
61
|
+
next();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Middleware to set hostUrl
|
|
65
|
+
app.use((req, res, next) => {
|
|
66
|
+
const protocol = req.headers["x-forwarded-proto"] || "https";
|
|
67
|
+
const host = req.headers.host;
|
|
68
|
+
Config.setHostUrl(protocol, host);
|
|
69
|
+
next();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Apply rate limiting only if RATE_LIMIT_SECRET is set (only affects your deployment)
|
|
73
|
+
app.use(rateLimiter);
|
|
74
|
+
|
|
75
|
+
// Test routes - only expose in development, not production
|
|
76
|
+
if (process.env.NODE_ENV !== "production") {
|
|
77
|
+
app.use("/api", testRoutes);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
app.use("/api", homeRoutes); // caching done in homeRoutes (30s airing, 120s search)
|
|
81
|
+
app.use("/api", webhookRoutes);
|
|
82
|
+
app.use("/api", cache(30), queueRoutes); // 30 seconds
|
|
83
|
+
app.use("/api", cache(18000), animeListRoutes); // 5 hours
|
|
84
|
+
app.use("/api", cache(86400), animeInfoRoutes); // 24 hours
|
|
85
|
+
app.use("/api", cache(3600), playRoutes); // 1 hour
|
|
86
|
+
|
|
87
|
+
app.use((req, res, next) => {
|
|
88
|
+
if (!req.route) {
|
|
89
|
+
next(
|
|
90
|
+
new CustomError(
|
|
91
|
+
"Route not found. Please check the API documentation at https://github.com/ElijahCodes12345/animepahe-api",
|
|
92
|
+
404,
|
|
93
|
+
),
|
|
94
|
+
);
|
|
95
|
+
} else {
|
|
96
|
+
next();
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Error handling middleware
|
|
101
|
+
app.use(errorHandler);
|
|
102
|
+
|
|
103
|
+
module.exports = app.handler = (req, res) => {
|
|
104
|
+
let path = req.url.replace(/^\/api/, "");
|
|
105
|
+
if (!path.startsWith("/")) {
|
|
106
|
+
path = "/" + path;
|
|
107
|
+
}
|
|
108
|
+
req.url = "/api" + path;
|
|
109
|
+
return app(req, res);
|
|
110
|
+
};
|
package/anime/app.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
const Config = require("./utils/config");
|
|
3
|
+
const homeRoutes = require("./routes/homeRoutes");
|
|
4
|
+
const queueRoutes = require("./routes/queueRoutes");
|
|
5
|
+
const animeListRoutes = require("./routes/animeListRoutes");
|
|
6
|
+
const animeInfoRoutes = require("./routes/animeInfoRoutes");
|
|
7
|
+
const playRoutes = require("./routes/playRoutes");
|
|
8
|
+
const PlayController = require("./controllers/playController");
|
|
9
|
+
const cache = require("./middleware/cache");
|
|
10
|
+
const Animepahe = require("./scrapers/animepahe");
|
|
11
|
+
const flaresolverr = require("./utils/flaresolverr");
|
|
12
|
+
const { startWebhookNotifier, runTick } = require("./utils/webhookNotifier");
|
|
13
|
+
|
|
14
|
+
// Load environment variables into Config
|
|
15
|
+
try {
|
|
16
|
+
Config.validate();
|
|
17
|
+
Config.loadFromEnv();
|
|
18
|
+
console.log("\x1b[36m%s\x1b[0m", "Anime configuration loaded.");
|
|
19
|
+
} catch (error) {
|
|
20
|
+
console.error("Anime config error:", error.message);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ─── S1+S4: Pre-fetch cookies and solve DDoS-Guard at startup ───
|
|
24
|
+
// This launches a browser ONCE, solves the DDoS-Guard challenge,
|
|
25
|
+
// saves cookies to disk, and keeps the browser alive for fallback.
|
|
26
|
+
// All subsequent requests will use axios + saved cookies (~1-2s)
|
|
27
|
+
// instead of launching a new browser per request (~40-60s).
|
|
28
|
+
//
|
|
29
|
+
// This is non-blocking — the server starts immediately and cookies
|
|
30
|
+
// are ready in the background. If pre-fetch fails, requests fall back
|
|
31
|
+
// to Playwright automatically.
|
|
32
|
+
console.log("\x1b[36m%s\x1b[0m", "Starting cookie pre-fetch (non-blocking)...");
|
|
33
|
+
if (!flaresolverr.isEnabled()) {
|
|
34
|
+
console.warn(
|
|
35
|
+
"[anime] FlareSolverr is not enabled. Browser cookie fallback is disabled for this deployment.",
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
Animepahe.initialize().catch((err) =>
|
|
39
|
+
console.error("[Startup] Cookie pre-fetch error:", err.message),
|
|
40
|
+
);
|
|
41
|
+
startWebhookNotifier();
|
|
42
|
+
|
|
43
|
+
const router = express.Router();
|
|
44
|
+
|
|
45
|
+
// Global request/response timeout — prevents slow upstream requests from hanging
|
|
46
|
+
router.use((req, res, next) => {
|
|
47
|
+
req.setTimeout(150000);
|
|
48
|
+
res.setTimeout(150000);
|
|
49
|
+
next();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
router.get("/download-proxy", PlayController.proxyDownload);
|
|
53
|
+
router.post("/webhook-check", express.json(), async (req, res) => {
|
|
54
|
+
const expected = process.env.WEBHOOK_SECRET || "";
|
|
55
|
+
const provided = req.headers["x-webhook-secret"] || "";
|
|
56
|
+
|
|
57
|
+
if (expected && provided !== expected) {
|
|
58
|
+
return res.status(401).json({ error: "unauthorized" });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const animeSession = String(req.body?.anime_session || "").trim() || null;
|
|
63
|
+
const result = await runTick({ animeSession });
|
|
64
|
+
return res.json({ ok: true, ...result });
|
|
65
|
+
} catch (err) {
|
|
66
|
+
return res.status(500).json({ error: err.message || "webhook-check failed" });
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// ─── Anime routes ───
|
|
71
|
+
// NOTE: animeInfoRoutes (/:id, /:id/releases) MUST come before playRoutes (/:id/:ep)
|
|
72
|
+
// so that /:id/releases is matched before /:id/:ep treats "releases" as an episode ID.
|
|
73
|
+
router.use("", homeRoutes);
|
|
74
|
+
router.use("", cache(30), queueRoutes); // 30 seconds
|
|
75
|
+
router.use("", cache(18000), animeListRoutes); // 5 hours
|
|
76
|
+
router.use("", cache(86400), animeInfoRoutes); // 1 day
|
|
77
|
+
router.use("", cache(3600), playRoutes); // 1 hour
|
|
78
|
+
|
|
79
|
+
module.exports = router;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const AnimeInfoModel = require('../models/animeInfoModel');
|
|
2
|
+
const { CustomError } = require('../middleware/errorHandler');
|
|
3
|
+
|
|
4
|
+
class AnimeInfoController {
|
|
5
|
+
static async getAnimeInfo(req, res, next) {
|
|
6
|
+
try {
|
|
7
|
+
const animeId = req.params.id;
|
|
8
|
+
|
|
9
|
+
if (!animeId) {
|
|
10
|
+
throw new CustomError('Anime ID is required', 400);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const animeInfo = await AnimeInfoModel.getAnimeInfo(animeId);
|
|
14
|
+
|
|
15
|
+
if (!animeInfo) {
|
|
16
|
+
throw new CustomError('Anime not found', 404);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return res.json(animeInfo);
|
|
20
|
+
} catch (error) {
|
|
21
|
+
next(error);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
static async getAnimeReleases(req, res, next) {
|
|
26
|
+
try {
|
|
27
|
+
const animeId = req.params.id;
|
|
28
|
+
const sort = req.query.sort || 'episode_desc';
|
|
29
|
+
const page = parseInt(req.query.page, 10) || 1;
|
|
30
|
+
|
|
31
|
+
if (!animeId) {
|
|
32
|
+
throw new CustomError('Anime ID is required', 400);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const animeReleases = await AnimeInfoModel.getAnimeReleases(animeId, sort, page);
|
|
36
|
+
|
|
37
|
+
if (!animeReleases) {
|
|
38
|
+
throw new CustomError('No releases found', 404);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return res.json(animeReleases);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
next(error);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = AnimeInfoController;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const AnimeListModel = require("../models/animeListModel");
|
|
2
|
+
const { CustomError } = require("../middleware/errorHandler");
|
|
3
|
+
|
|
4
|
+
class AnimeListController {
|
|
5
|
+
static async getAllAnime(req, res, next) {
|
|
6
|
+
try {
|
|
7
|
+
const { page, tab, genre } = req.query;
|
|
8
|
+
const animeList = await AnimeListModel.getAnimeList(page, tab, genre);
|
|
9
|
+
|
|
10
|
+
if (!animeList) {
|
|
11
|
+
throw new CustomError("Failed to fetch anime list", 404);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return res.json(animeList);
|
|
15
|
+
} catch (error) {
|
|
16
|
+
next(error);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = AnimeListController;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const HomeModel = require('../models/homeModel');
|
|
2
|
+
const { CustomError } = require('../middleware/errorHandler');
|
|
3
|
+
|
|
4
|
+
class HomeController {
|
|
5
|
+
static async getAiringAnime(req, res, next) {
|
|
6
|
+
try {
|
|
7
|
+
const page = parseInt(req.query.page, 10) || 1;
|
|
8
|
+
const airingAnime = await HomeModel.getAiringAnime(page);
|
|
9
|
+
|
|
10
|
+
if (!airingAnime) {
|
|
11
|
+
throw new CustomError('No airing anime found', 404);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return res.json(airingAnime);
|
|
15
|
+
} catch (error) {
|
|
16
|
+
next(error);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static async searchAnime(req, res, next) {
|
|
21
|
+
try {
|
|
22
|
+
const query = req.query.q;
|
|
23
|
+
const page = parseInt(req.query.page, 10) || 1;
|
|
24
|
+
|
|
25
|
+
if (!query) {
|
|
26
|
+
throw new CustomError('Search query is required', 400);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const searchResults = await HomeModel.searchAnime(query, page);
|
|
30
|
+
|
|
31
|
+
if (!searchResults) {
|
|
32
|
+
throw new CustomError('No results found', 404);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return res.json(searchResults);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
next(error);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = HomeController;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
const axios = require("axios");
|
|
2
|
+
const RequestManager = require("../utils/requestManager");
|
|
3
|
+
const PlayModel = require("../models/playModel");
|
|
4
|
+
const Animepahe = require("../scrapers/animepahe");
|
|
5
|
+
const flaresolverr = require("../utils/flaresolverr");
|
|
6
|
+
const Config = require("../utils/config");
|
|
7
|
+
const { CustomError } = require("../middleware/errorHandler");
|
|
8
|
+
|
|
9
|
+
class PlayController {
|
|
10
|
+
static async getStreamingLinks(req, res, next) {
|
|
11
|
+
try {
|
|
12
|
+
const { id, ep } = req.params;
|
|
13
|
+
const { downloads } = req.query;
|
|
14
|
+
|
|
15
|
+
if (!id || !ep) {
|
|
16
|
+
throw new CustomError("Both id and ep are required", 400);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Parse downloads query parameter (defaults to true)
|
|
20
|
+
// user can pass ?downloads=false or ?downloads=0 to skip downloads
|
|
21
|
+
const includeDownloads =
|
|
22
|
+
downloads === undefined || downloads === "true" || downloads === "1";
|
|
23
|
+
|
|
24
|
+
// console.log(`[PlayController] Request query downloads: '${downloads}' -> includeDownloads: ${includeDownloads}`);
|
|
25
|
+
|
|
26
|
+
const links = await PlayModel.getStreamingLinks(id, ep, includeDownloads);
|
|
27
|
+
|
|
28
|
+
return res.json(links);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
next(error);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static async proxyDownload(req, res, next) {
|
|
35
|
+
try {
|
|
36
|
+
const target = String(req.query.url || '').trim();
|
|
37
|
+
if (!target) {
|
|
38
|
+
throw new CustomError('url is required', 400);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let parsed;
|
|
42
|
+
try {
|
|
43
|
+
parsed = new URL(target);
|
|
44
|
+
} catch (_) {
|
|
45
|
+
throw new CustomError('invalid url', 400);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const host = parsed.hostname.toLowerCase();
|
|
49
|
+
const allowedHosts = [
|
|
50
|
+
'uwucdn', 'owocdn', 'hakunaymatata', 'animepahe', 'pahe', 'kwik'
|
|
51
|
+
];
|
|
52
|
+
if (!allowedHosts.some((token) => host.includes(token))) {
|
|
53
|
+
throw new CustomError('domain not allowed', 403);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const headers = {
|
|
57
|
+
'User-Agent': Config.userAgent,
|
|
58
|
+
Referer: 'https://kwik.cx/',
|
|
59
|
+
Origin: 'https://kwik.cx',
|
|
60
|
+
};
|
|
61
|
+
if (req.headers.range) {
|
|
62
|
+
headers.Range = req.headers.range;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const response = await RequestManager.cloudscraperGet(target, {
|
|
66
|
+
headers,
|
|
67
|
+
encoding: null,
|
|
68
|
+
timeout: 60000,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (response.statusCode >= 400) {
|
|
72
|
+
const preview = String(response.body || '').slice(0, 160).replace(/\s+/g, ' ');
|
|
73
|
+
console.error(`[download-proxy] upstream blocked: status=${response.statusCode} host=${host} preview=${preview}`);
|
|
74
|
+
throw new CustomError('download upstream blocked', 502);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const passthrough = [
|
|
78
|
+
'content-type', 'content-length', 'content-disposition',
|
|
79
|
+
'accept-ranges', 'content-range', 'etag', 'last-modified'
|
|
80
|
+
];
|
|
81
|
+
for (const key of passthrough) {
|
|
82
|
+
const value = response.headers[key];
|
|
83
|
+
if (value) res.setHeader(key, value);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const body = response.body;
|
|
87
|
+
if (Buffer.isBuffer(body)) {
|
|
88
|
+
res.status(response.statusCode);
|
|
89
|
+
res.end(body);
|
|
90
|
+
} else if (typeof body === 'string') {
|
|
91
|
+
const buf = Buffer.from(body, 'utf8');
|
|
92
|
+
if (!res.getHeader('content-length')) {
|
|
93
|
+
res.setHeader('content-length', buf.length);
|
|
94
|
+
}
|
|
95
|
+
res.status(response.statusCode);
|
|
96
|
+
res.end(buf);
|
|
97
|
+
} else {
|
|
98
|
+
res.status(response.statusCode);
|
|
99
|
+
body.pipe(res);
|
|
100
|
+
}
|
|
101
|
+
} catch (error) {
|
|
102
|
+
next(error);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// @deprecated — Broken since kwik.cx added Cloudflare protection.
|
|
107
|
+
// Direct MP4 download URLs are already available in /api/play/:id?episodeId=
|
|
108
|
+
// under sources[].download and downloads[].download.
|
|
109
|
+
// To fix: replace cloudscraper with Playwright in extractKwikUrl/getKwikDownloadUrl.
|
|
110
|
+
static getDownloadLinks(req, res) {
|
|
111
|
+
const { url } = req.query;
|
|
112
|
+
|
|
113
|
+
if (!url) {
|
|
114
|
+
return res.status(400).json({
|
|
115
|
+
status: 400,
|
|
116
|
+
message: "Url is required",
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return res.json({
|
|
121
|
+
status: 501,
|
|
122
|
+
message:
|
|
123
|
+
"DEPRECATED: This endpoint is broken due to Cloudflare protection on kwik.cx.",
|
|
124
|
+
workaround:
|
|
125
|
+
"Use /api/play/:id?episodeId= instead. Direct MP4 download URLs are provided under sources[].download and downloads[].download.",
|
|
126
|
+
fix_hint:
|
|
127
|
+
"To restore this endpoint, replace cloudscraper with Playwright in scrapers/animepahe.js → extractKwikUrl() and getKwikDownloadUrl().",
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Original code preserved below for future restoration:
|
|
131
|
+
// try {
|
|
132
|
+
// const links = await PlayModel.getDownloadLinks(url);
|
|
133
|
+
// return res.json(links);
|
|
134
|
+
// } catch (error) {
|
|
135
|
+
// next(error);
|
|
136
|
+
// }
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
module.exports = PlayController;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const QueueModel = require('../models/queueModel');
|
|
2
|
+
const { CustomError } = require('../middleware/errorHandler');
|
|
3
|
+
|
|
4
|
+
class QueueController {
|
|
5
|
+
static async getQueue(req, res, next) {
|
|
6
|
+
try {
|
|
7
|
+
const queue = await QueueModel.getQueue();
|
|
8
|
+
|
|
9
|
+
if (!queue) {
|
|
10
|
+
throw new CustomError('Failed to fetch queue data', 404);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return res.json(queue);
|
|
14
|
+
} catch (error) {
|
|
15
|
+
next(error);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = QueueController;
|