ani-auto 1.4.3 → 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 +19 -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 +66 -13
- package/src/notify.js +82 -7
- package/src/scraper.js +2 -2
- package/src/server.js +108 -0
- package/utils.js +21 -21
- 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
|
@@ -0,0 +1,667 @@
|
|
|
1
|
+
const cloudscraper = require("cloudscraper");
|
|
2
|
+
const axios = require("axios");
|
|
3
|
+
const vm = require("vm");
|
|
4
|
+
const { JSDOM } = require("jsdom");
|
|
5
|
+
const { CustomError } = require("../middleware/errorHandler");
|
|
6
|
+
const Config = require("../utils/config");
|
|
7
|
+
|
|
8
|
+
// Helper to wait
|
|
9
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
10
|
+
|
|
11
|
+
class TestController {
|
|
12
|
+
static async resolveKwik(req, res, next) {
|
|
13
|
+
try {
|
|
14
|
+
const { url } = req.query;
|
|
15
|
+
|
|
16
|
+
if (!url) {
|
|
17
|
+
throw new CustomError("Url is required", 400);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const result = await resolveKwik(url);
|
|
21
|
+
console.log("\nOutput snapshot:\n", result);
|
|
22
|
+
|
|
23
|
+
return res.json(result);
|
|
24
|
+
} catch (err) {
|
|
25
|
+
console.error("eval error", err && err.message);
|
|
26
|
+
next(err);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static async download(req, res, next) {
|
|
31
|
+
try {
|
|
32
|
+
const { url } = req.query;
|
|
33
|
+
if (!url) {
|
|
34
|
+
throw new CustomError("Url is required", 400);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// First, extract the actual Kwik URL from the HTML
|
|
38
|
+
const resolvedUrl = await extractKwikUrl(url);
|
|
39
|
+
if (!resolvedUrl) {
|
|
40
|
+
// If can't extract the URL, try the original URL
|
|
41
|
+
const downloadUrl = await getKwikDownloadUrl(url);
|
|
42
|
+
return res.json({ downloadUrl, type: "direct_download" });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
console.log("Found Kwik URL:", resolvedUrl);
|
|
46
|
+
|
|
47
|
+
// Use the extracted URL for getting the download link
|
|
48
|
+
const downloadUrl = await getKwikDownloadUrl(resolvedUrl);
|
|
49
|
+
return res.json({
|
|
50
|
+
downloadUrl,
|
|
51
|
+
type: "redirected_download",
|
|
52
|
+
originalUrl: url,
|
|
53
|
+
resolvedUrl,
|
|
54
|
+
});
|
|
55
|
+
} catch (err) {
|
|
56
|
+
console.error("Error downloading:", err && err.message);
|
|
57
|
+
next(err);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Function to extract Kwik URL from HTML content
|
|
63
|
+
async function extractKwikUrl(url) {
|
|
64
|
+
try {
|
|
65
|
+
console.log("[Step 1] Fetching page to extract Kwik URL:", url);
|
|
66
|
+
|
|
67
|
+
const response = await cloudscraper.get({
|
|
68
|
+
uri: url,
|
|
69
|
+
headers: {
|
|
70
|
+
Referer: "https://animepahe.pw/",
|
|
71
|
+
"User-Agent":
|
|
72
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
73
|
+
Accept:
|
|
74
|
+
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
|
|
75
|
+
"Accept-Language": "en-US,en;q=0.5",
|
|
76
|
+
Connection: "keep-alive",
|
|
77
|
+
},
|
|
78
|
+
resolveWithFullResponse: true,
|
|
79
|
+
timeout: 30000,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
console.log("Page fetched for extraction, status:", response.statusCode);
|
|
83
|
+
|
|
84
|
+
const body = response.body;
|
|
85
|
+
|
|
86
|
+
// Pattern 1: Look for the specific script pattern with redirect href
|
|
87
|
+
const redirectPattern = /href\s*:\s*["']([^"']+)["']/i;
|
|
88
|
+
const redirectMatch = body.match(redirectPattern);
|
|
89
|
+
if (
|
|
90
|
+
redirectMatch &&
|
|
91
|
+
redirectMatch[1] &&
|
|
92
|
+
redirectMatch[1].includes(Config.iframeBaseUrl)
|
|
93
|
+
) {
|
|
94
|
+
console.log("Found redirect URL:", redirectMatch[1]);
|
|
95
|
+
return redirectMatch[1];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Pattern 2...
|
|
99
|
+
// Dynamic construction
|
|
100
|
+
const inputDomain = Config.iframeBaseUrl.replace(".", "\\.");
|
|
101
|
+
const scriptPattern = new RegExp(
|
|
102
|
+
`href["']\\s*,\\s*["']([^"']+\\.(?:${inputDomain}|${inputDomain.replace("\\.", "")}))[^"']*["']`,
|
|
103
|
+
"i",
|
|
104
|
+
);
|
|
105
|
+
const scriptMatch = body.match(scriptPattern);
|
|
106
|
+
if (scriptMatch && scriptMatch[1]) {
|
|
107
|
+
// If it's a relative URL, make it absolute
|
|
108
|
+
let kwikUrl = scriptMatch[1];
|
|
109
|
+
if (kwikUrl.startsWith("/")) {
|
|
110
|
+
const urlObj = new URL(url);
|
|
111
|
+
kwikUrl = urlObj.protocol + "//" + urlObj.host + kwikUrl;
|
|
112
|
+
} else if (!kwikUrl.startsWith("http")) {
|
|
113
|
+
kwikUrl = `https://${Config.iframeBaseUrl}${kwikUrl}`;
|
|
114
|
+
}
|
|
115
|
+
console.log("Found Kwik URL from script:", kwikUrl);
|
|
116
|
+
return kwikUrl;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Pattern 3: Look for kwik.cx URLs in href attributes
|
|
120
|
+
const hrefPattern = new RegExp(
|
|
121
|
+
`href\\s*=\\s*["']([^"']*\\b${inputDomain}\\b[^"']*)["']`,
|
|
122
|
+
"gi",
|
|
123
|
+
);
|
|
124
|
+
const hrefMatches = [...body.matchAll(hrefPattern)];
|
|
125
|
+
if (hrefMatches.length > 0) {
|
|
126
|
+
// Return the first kwik URL found
|
|
127
|
+
let kwikUrl = hrefMatches[0][1];
|
|
128
|
+
if (kwikUrl.startsWith("/")) {
|
|
129
|
+
const urlObj = new URL(url);
|
|
130
|
+
kwikUrl = urlObj.protocol + "//" + urlObj.host + kwikUrl;
|
|
131
|
+
}
|
|
132
|
+
console.log("Found Kwik URL from href:", kwikUrl);
|
|
133
|
+
return kwikUrl;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Pattern 4: Look for kwik.cx URLs in JavaScript redirects
|
|
137
|
+
const jsRedirectPattern = new RegExp(
|
|
138
|
+
`["'](https?:\\/\\/[^"']*${inputDomain}[^"']*)["']`,
|
|
139
|
+
"i",
|
|
140
|
+
);
|
|
141
|
+
const jsMatch = body.match(jsRedirectPattern);
|
|
142
|
+
if (jsMatch && jsMatch[1]) {
|
|
143
|
+
console.log("Found Kwik URL from JavaScript:", jsMatch[1]);
|
|
144
|
+
return jsMatch[1];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Pattern 5: Look for the specific script pattern you mentioned
|
|
148
|
+
const specificPattern = new RegExp(
|
|
149
|
+
`href"\\s*,\\s*"([^"]*${inputDomain}[^"]*)"`,
|
|
150
|
+
);
|
|
151
|
+
const specificMatch = body.match(specificPattern);
|
|
152
|
+
if (specificMatch && specificMatch[1]) {
|
|
153
|
+
console.log("Found Kwik URL from specific pattern:", specificMatch[1]);
|
|
154
|
+
return specificMatch[1];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
console.log("No Kwik URL found in the HTML content");
|
|
158
|
+
return null;
|
|
159
|
+
} catch (error) {
|
|
160
|
+
console.error("Error extracting Kwik URL:", error.message);
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function getKwikDownloadUrl(url) {
|
|
166
|
+
console.log("[Step 2] Fetching page for download link:", url);
|
|
167
|
+
|
|
168
|
+
const getResponse = await cloudscraper.get({
|
|
169
|
+
uri: url,
|
|
170
|
+
headers: {
|
|
171
|
+
Referer: "https://animepahe.pw/",
|
|
172
|
+
"User-Agent":
|
|
173
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
174
|
+
Accept:
|
|
175
|
+
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
|
|
176
|
+
"Accept-Language": "en-US,en;q=0.5",
|
|
177
|
+
Connection: "keep-alive",
|
|
178
|
+
},
|
|
179
|
+
resolveWithFullResponse: true,
|
|
180
|
+
timeout: 30000,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
console.log("[✓] Page fetched, status:", getResponse.statusCode);
|
|
184
|
+
|
|
185
|
+
// Extract cookies
|
|
186
|
+
const setCookieHeaders = getResponse.headers["set-cookie"] || [];
|
|
187
|
+
const cookies = setCookieHeaders
|
|
188
|
+
.map((cookie) => cookie.split(";")[0])
|
|
189
|
+
.join("; ");
|
|
190
|
+
console.log("[Cookies]:", cookies);
|
|
191
|
+
|
|
192
|
+
const body = getResponse.body;
|
|
193
|
+
const scripts = [...body.matchAll(/<script[^>]*>([\s\S]*?)<\/script>/gi)].map(
|
|
194
|
+
(m) => m[1],
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
let foundAction = null;
|
|
198
|
+
let foundToken = null;
|
|
199
|
+
|
|
200
|
+
// JavaScript execution environment
|
|
201
|
+
const dom = new JSDOM(
|
|
202
|
+
"<!doctype html><body><div class='adSense'></div><div class='adSense'></div></body>",
|
|
203
|
+
);
|
|
204
|
+
const { window } = dom;
|
|
205
|
+
const { document } = window;
|
|
206
|
+
|
|
207
|
+
const $ = (sel) => {
|
|
208
|
+
if (typeof sel === "function") {
|
|
209
|
+
try {
|
|
210
|
+
sel.call(window);
|
|
211
|
+
} catch (e) {}
|
|
212
|
+
return $;
|
|
213
|
+
}
|
|
214
|
+
if (typeof sel !== "string")
|
|
215
|
+
return {
|
|
216
|
+
html: () => "",
|
|
217
|
+
attr: () => "",
|
|
218
|
+
click: () => $,
|
|
219
|
+
on: () => $,
|
|
220
|
+
remove: () => $,
|
|
221
|
+
length: 0,
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
let els = [];
|
|
225
|
+
const eqMatch = sel.match(/^(.+):eq\((\d+)\)$/);
|
|
226
|
+
if (eqMatch) {
|
|
227
|
+
const all = document.querySelectorAll(eqMatch[1]);
|
|
228
|
+
els = all[parseInt(eqMatch[2])] ? [all[parseInt(eqMatch[2])]] : [];
|
|
229
|
+
} else {
|
|
230
|
+
try {
|
|
231
|
+
els = Array.from(document.querySelectorAll(sel));
|
|
232
|
+
} catch (e) {}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const el = els[0];
|
|
236
|
+
return {
|
|
237
|
+
html: (v) => {
|
|
238
|
+
if (v !== undefined) {
|
|
239
|
+
const htmlStr = String(v);
|
|
240
|
+
const actionMatch = htmlStr.match(/action=["']([^"']+)["']/i);
|
|
241
|
+
if (actionMatch) foundAction = actionMatch[1];
|
|
242
|
+
const tokenMatch = htmlStr.match(
|
|
243
|
+
/name=["']_token["'][^>]*value=["']([^"']+)["']/i,
|
|
244
|
+
);
|
|
245
|
+
if (tokenMatch) foundToken = tokenMatch[1];
|
|
246
|
+
if (el) el.innerHTML = htmlStr;
|
|
247
|
+
return $;
|
|
248
|
+
}
|
|
249
|
+
return el?.innerHTML || "";
|
|
250
|
+
},
|
|
251
|
+
attr: (n, v) =>
|
|
252
|
+
v !== undefined
|
|
253
|
+
? (el?.setAttribute(n, v), $)
|
|
254
|
+
: el?.getAttribute(n) || "",
|
|
255
|
+
click: (fn) => {
|
|
256
|
+
if (typeof fn === "function")
|
|
257
|
+
try {
|
|
258
|
+
fn.call(el);
|
|
259
|
+
} catch (e) {}
|
|
260
|
+
return $;
|
|
261
|
+
},
|
|
262
|
+
on: () => $,
|
|
263
|
+
remove: () => {
|
|
264
|
+
el?.remove();
|
|
265
|
+
return $;
|
|
266
|
+
},
|
|
267
|
+
length: els.length,
|
|
268
|
+
};
|
|
269
|
+
};
|
|
270
|
+
$.ajax = () => {};
|
|
271
|
+
|
|
272
|
+
const sandbox = {
|
|
273
|
+
window,
|
|
274
|
+
document,
|
|
275
|
+
console,
|
|
276
|
+
navigator: { userAgent: "Mozilla/5.0" },
|
|
277
|
+
MutationObserver: class {
|
|
278
|
+
observe() {}
|
|
279
|
+
},
|
|
280
|
+
XMLHttpRequest: function () {
|
|
281
|
+
this.open = this.send = this.setRequestHeader = () => {};
|
|
282
|
+
},
|
|
283
|
+
fetch: async () => ({
|
|
284
|
+
ok: true,
|
|
285
|
+
text: async () => "",
|
|
286
|
+
json: async () => ({}),
|
|
287
|
+
}),
|
|
288
|
+
atob: (s) => Buffer.from(s, "base64").toString("binary"),
|
|
289
|
+
btoa: (s) => Buffer.from(s, "binary").toString("base64"),
|
|
290
|
+
$,
|
|
291
|
+
setTimeout,
|
|
292
|
+
clearTimeout,
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
for (const s of scripts) {
|
|
296
|
+
if (s && s.length > 100) {
|
|
297
|
+
try {
|
|
298
|
+
vm.runInNewContext(s, sandbox, { timeout: 4000 });
|
|
299
|
+
if (foundAction && foundToken) break;
|
|
300
|
+
} catch (e) {}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (!foundAction || !foundToken) {
|
|
305
|
+
throw new Error("⌠ Could not extract form action or token");
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
console.log("[Step 3] Extracted action:", foundAction);
|
|
309
|
+
console.log("[Step 3] Extracted token:", foundToken);
|
|
310
|
+
|
|
311
|
+
// Wait a bit to simulate human behavior
|
|
312
|
+
console.log("[Step 4] Waiting 2 seconds...");
|
|
313
|
+
await sleep(2000);
|
|
314
|
+
|
|
315
|
+
// Step 3: Submit POST request
|
|
316
|
+
console.log("[Step 5] Submitting POST request...");
|
|
317
|
+
|
|
318
|
+
// Check for serverless deployment platforms (matching browser.js logic)
|
|
319
|
+
const isServerless =
|
|
320
|
+
process.env.VERCEL ||
|
|
321
|
+
process.env.NETLIFY ||
|
|
322
|
+
process.env.AWS_LAMBDA_FUNCTION_NAME;
|
|
323
|
+
|
|
324
|
+
if (isServerless) {
|
|
325
|
+
// On serverless platforms, use cloudscraper to bypass Cloudflare
|
|
326
|
+
console.log(
|
|
327
|
+
"[Running on serverless platform - using cloudscraper for POST]",
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
try {
|
|
331
|
+
const postResponse = await cloudscraper({
|
|
332
|
+
method: "POST",
|
|
333
|
+
uri: foundAction,
|
|
334
|
+
form: {
|
|
335
|
+
_token: foundToken,
|
|
336
|
+
},
|
|
337
|
+
headers: {
|
|
338
|
+
"User-Agent":
|
|
339
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
340
|
+
Accept:
|
|
341
|
+
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
|
|
342
|
+
"Accept-Language": "en-US,en;q=0.5",
|
|
343
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
344
|
+
Origin: `https://${Config.iframeBaseUrl}`,
|
|
345
|
+
Referer: url,
|
|
346
|
+
Cookie: cookies,
|
|
347
|
+
},
|
|
348
|
+
followAllRedirects: false,
|
|
349
|
+
followRedirect: false,
|
|
350
|
+
simple: false,
|
|
351
|
+
resolveWithFullResponse: true,
|
|
352
|
+
timeout: 30000,
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
console.log("[Step 6] Response status:", postResponse.statusCode);
|
|
356
|
+
|
|
357
|
+
if (postResponse.statusCode === 302 || postResponse.statusCode === 301) {
|
|
358
|
+
const downloadUrl = postResponse.headers.location;
|
|
359
|
+
console.log("Final download URL:", downloadUrl);
|
|
360
|
+
return downloadUrl;
|
|
361
|
+
} else if (postResponse.statusCode === 200) {
|
|
362
|
+
// Check for redirects in the body
|
|
363
|
+
const body = postResponse.body;
|
|
364
|
+
const metaMatch = body.match(
|
|
365
|
+
/<meta[^>]*http-equiv=["']refresh["'][^>]*content=["'][^"]*url=([^"']+)["']/i,
|
|
366
|
+
);
|
|
367
|
+
if (metaMatch) {
|
|
368
|
+
console.log("Found meta refresh URL:", metaMatch[1]);
|
|
369
|
+
return metaMatch[1];
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const jsMatch = body.match(
|
|
373
|
+
/window\.location(?:\.href)?\s*=\s*["']([^"']+)["']/i,
|
|
374
|
+
);
|
|
375
|
+
if (jsMatch) {
|
|
376
|
+
console.log("Found JavaScript redirect URL:", jsMatch[1]);
|
|
377
|
+
return jsMatch[1];
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
console.log("[Response body snippet]:", body.substring(0, 800));
|
|
381
|
+
}
|
|
382
|
+
} catch (error) {
|
|
383
|
+
console.log("[Cloudscraper Error]:", error.message);
|
|
384
|
+
if (error.statusCode === 302 || error.statusCode === 301) {
|
|
385
|
+
const downloadUrl = error.response?.headers?.location;
|
|
386
|
+
if (downloadUrl) {
|
|
387
|
+
console.log("Final download URL (from error):", downloadUrl);
|
|
388
|
+
return downloadUrl;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
throw error;
|
|
392
|
+
}
|
|
393
|
+
} else {
|
|
394
|
+
// Local development - use axios which works reliably
|
|
395
|
+
console.log("[Running locally - using axios for POST]");
|
|
396
|
+
|
|
397
|
+
try {
|
|
398
|
+
const postResponse = await axios.post(
|
|
399
|
+
foundAction,
|
|
400
|
+
`_token=${encodeURIComponent(foundToken)}`,
|
|
401
|
+
{
|
|
402
|
+
headers: {
|
|
403
|
+
"User-Agent":
|
|
404
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
405
|
+
Accept:
|
|
406
|
+
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
|
|
407
|
+
"Accept-Language": "en-US,en;q=0.5",
|
|
408
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
409
|
+
Origin: `https://${Config.iframeBaseUrl}`,
|
|
410
|
+
Referer: url,
|
|
411
|
+
Cookie: cookies,
|
|
412
|
+
Connection: "keep-alive",
|
|
413
|
+
"Upgrade-Insecure-Requests": "1",
|
|
414
|
+
},
|
|
415
|
+
maxRedirects: 0,
|
|
416
|
+
validateStatus: (status) => status >= 200 && status < 400,
|
|
417
|
+
timeout: 30000,
|
|
418
|
+
},
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
console.log("[Step 6] Response status:", postResponse.status);
|
|
422
|
+
|
|
423
|
+
if (postResponse.status === 302 || postResponse.status === 301) {
|
|
424
|
+
const downloadUrl = postResponse.headers.location;
|
|
425
|
+
console.log("Final download URL:", downloadUrl);
|
|
426
|
+
return downloadUrl;
|
|
427
|
+
} else if (postResponse.status === 200) {
|
|
428
|
+
const metaMatch = postResponse.data.match(
|
|
429
|
+
/<meta[^>]*http-equiv=["']refresh["'][^>]*content=["'][^"]*url=([^"']+)["']/i,
|
|
430
|
+
);
|
|
431
|
+
if (metaMatch) {
|
|
432
|
+
console.log("Found meta refresh URL:", metaMatch[1]);
|
|
433
|
+
return metaMatch[1];
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const jsMatch = postResponse.data.match(
|
|
437
|
+
/window\.location(?:\.href)?\s*=\s*["']([^"']+)["']/i,
|
|
438
|
+
);
|
|
439
|
+
if (jsMatch) {
|
|
440
|
+
console.log("Found JavaScript redirect URL:", jsMatch[1]);
|
|
441
|
+
return jsMatch[1];
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
console.log(
|
|
445
|
+
"[Response body snippet]:",
|
|
446
|
+
postResponse.data.substring(0, 800),
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
} catch (error) {
|
|
450
|
+
if (error.response) {
|
|
451
|
+
console.log("[Error Response] Status:", error.response.status);
|
|
452
|
+
|
|
453
|
+
if (error.response.status === 302 || error.response.status === 301) {
|
|
454
|
+
const downloadUrl = error.response.headers.location;
|
|
455
|
+
console.log("Final download URL (from error):", downloadUrl);
|
|
456
|
+
return downloadUrl;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
console.log(
|
|
460
|
+
"[Error Response Body]:",
|
|
461
|
+
error.response.data
|
|
462
|
+
? error.response.data.substring(0, 800)
|
|
463
|
+
: "No body",
|
|
464
|
+
);
|
|
465
|
+
} else {
|
|
466
|
+
console.log("[Error]:", error.message);
|
|
467
|
+
}
|
|
468
|
+
throw error;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
throw new Error("Could not extract download URL");
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
async function resolveKwik(url) {
|
|
476
|
+
console.log(`Fetching HTML from ${url}...`);
|
|
477
|
+
|
|
478
|
+
const html = await cloudscraper.get(url, {
|
|
479
|
+
headers: {
|
|
480
|
+
Referer: "https://animepahe.pw/",
|
|
481
|
+
"User-Agent":
|
|
482
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
483
|
+
},
|
|
484
|
+
timeout: 20000,
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
console.log("Fetched HTML successfully.");
|
|
488
|
+
|
|
489
|
+
// collect inline script blocks
|
|
490
|
+
const scriptMatches = [
|
|
491
|
+
...html.matchAll(/<script[^>]*>([\s\S]*?)<\/script>/gi),
|
|
492
|
+
].map((m) => m[1]);
|
|
493
|
+
if (!scriptMatches.length) {
|
|
494
|
+
console.log("No inline <script> blocks found.");
|
|
495
|
+
return null;
|
|
496
|
+
}
|
|
497
|
+
console.log(`Found ${scriptMatches.length} script tags.`);
|
|
498
|
+
|
|
499
|
+
// helper to extract m3u8 from a string
|
|
500
|
+
const findM3u8 = (s) => {
|
|
501
|
+
if (!s) return null;
|
|
502
|
+
const m = s.match(/https?:\/\/[^"'<> \n\r]+\.m3u8[^\s"'<>]*/i);
|
|
503
|
+
return m ? m[0] : null;
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
for (const script of scriptMatches) {
|
|
507
|
+
if (!script.includes("eval(")) continue; // only interested in obfuscated eval scripts
|
|
508
|
+
|
|
509
|
+
console.log("Evaluating candidate script via vm sandbox...");
|
|
510
|
+
|
|
511
|
+
// create minimal DOM with a <video> element that supports .src
|
|
512
|
+
const dom = new JSDOM(`<!DOCTYPE html><video id="player"></video>`);
|
|
513
|
+
const document = dom.window.document;
|
|
514
|
+
const videoEl = document.querySelector("video");
|
|
515
|
+
|
|
516
|
+
// capture storage
|
|
517
|
+
const captured = new Set();
|
|
518
|
+
|
|
519
|
+
// Plyr stub: capture provided source if present in options
|
|
520
|
+
const Plyr = function (el, opts) {
|
|
521
|
+
try {
|
|
522
|
+
if (opts && opts.sources && Array.isArray(opts.sources)) {
|
|
523
|
+
for (const s of opts.sources) {
|
|
524
|
+
if (s && typeof s.src === "string" && s.src.includes(".m3u8"))
|
|
525
|
+
captured.add(s.src);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
} catch (e) {
|
|
529
|
+
/* ignore */
|
|
530
|
+
}
|
|
531
|
+
return {
|
|
532
|
+
on: () => {},
|
|
533
|
+
};
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
// Hls stub: constructor + static isSupported
|
|
537
|
+
const Hls = function (cfg) {
|
|
538
|
+
return {
|
|
539
|
+
loadSource: (src) => {
|
|
540
|
+
try {
|
|
541
|
+
if (typeof src === "string" && src.includes(".m3u8"))
|
|
542
|
+
captured.add(src);
|
|
543
|
+
} catch (e) {}
|
|
544
|
+
},
|
|
545
|
+
attachMedia: (m) => {
|
|
546
|
+
try {
|
|
547
|
+
// if video element has src set later, capture it
|
|
548
|
+
if (
|
|
549
|
+
m &&
|
|
550
|
+
m.src &&
|
|
551
|
+
typeof m.src === "string" &&
|
|
552
|
+
m.src.includes(".m3u8")
|
|
553
|
+
)
|
|
554
|
+
captured.add(m.src);
|
|
555
|
+
} catch (e) {}
|
|
556
|
+
},
|
|
557
|
+
on: () => {},
|
|
558
|
+
};
|
|
559
|
+
};
|
|
560
|
+
Hls.isSupported = () => true;
|
|
561
|
+
|
|
562
|
+
// also intercept assignments to video.src by monitoring JSDOM element after script
|
|
563
|
+
// Sandbox
|
|
564
|
+
const sandbox = {
|
|
565
|
+
console,
|
|
566
|
+
window: dom.window,
|
|
567
|
+
document: dom.window.document,
|
|
568
|
+
navigator: { userAgent: "mozilla" },
|
|
569
|
+
location: { href: url },
|
|
570
|
+
Plyr,
|
|
571
|
+
Hls,
|
|
572
|
+
setTimeout,
|
|
573
|
+
clearTimeout,
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
// Create context
|
|
577
|
+
vm.createContext(sandbox);
|
|
578
|
+
|
|
579
|
+
// Run script and also try to unwrap one level of nested evals if found
|
|
580
|
+
try {
|
|
581
|
+
// Run once
|
|
582
|
+
vm.runInNewContext(script, sandbox, { timeout: 2000 });
|
|
583
|
+
} catch (err) {
|
|
584
|
+
console.log("Eval failed:", err && err.message);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Some pages embed further eval inside strings. Try to detect `eval(function(...` pattern and run inner body(s)
|
|
588
|
+
// We search the script text for eval and then try to extract common packed patterns. This is best-effort.
|
|
589
|
+
const innerEvalBodies = [];
|
|
590
|
+
// pattern to capture eval\\(function...packed...) or eval\\(p,a,c,k,e,d\\)\\(...\\)
|
|
591
|
+
const packedMatch = script.match(/eval\\((function[\s\S]*?)\\)\\s*;?/i);
|
|
592
|
+
if (packedMatch && packedMatch[1]) innerEvalBodies.push(packedMatch[1]);
|
|
593
|
+
// also check for common eval\\(\\(function\\(p,a,c,k,e,d\\)\\{[\\s\\S]*?\\}\\('[\\s\\S]*?'\\)\\)\\)
|
|
594
|
+
const genericMatches = [
|
|
595
|
+
...script.matchAll(/eval\\(([\\s\S]*?)\\)\\s*;?/gi),
|
|
596
|
+
];
|
|
597
|
+
for (const gm of genericMatches) {
|
|
598
|
+
if (gm[1] && !innerEvalBodies.includes(gm[1]))
|
|
599
|
+
innerEvalBodies.push(gm[1]);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
for (const body of innerEvalBodies) {
|
|
603
|
+
try {
|
|
604
|
+
// attempt to run inner body directly
|
|
605
|
+
vm.runInNewContext(body, sandbox, { timeout: 1500 });
|
|
606
|
+
} catch (err) {
|
|
607
|
+
// ignore errors: many packed scripts expect DOM APIs we stubbed
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// After execution, check multiple places for m3u8
|
|
612
|
+
// 1) captured set from Plyr/Hls
|
|
613
|
+
if (captured.size) {
|
|
614
|
+
const arr = Array.from(captured);
|
|
615
|
+
// return first
|
|
616
|
+
console.log("Resolved m3u8 (captured):", arr[0]);
|
|
617
|
+
return arr[0];
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// 2) check video element src
|
|
621
|
+
try {
|
|
622
|
+
const vsrc = videoEl && videoEl.src;
|
|
623
|
+
const found = findM3u8(vsrc);
|
|
624
|
+
if (found) {
|
|
625
|
+
console.log("Resolved m3u8 (video.src):", found);
|
|
626
|
+
return found;
|
|
627
|
+
}
|
|
628
|
+
} catch (e) {
|
|
629
|
+
/* ignore */
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// 3) check sandbox.window / sandbox.document for q or other variables
|
|
633
|
+
try {
|
|
634
|
+
const pkg = JSON.stringify(sandbox);
|
|
635
|
+
const found = findM3u8(pkg);
|
|
636
|
+
if (found) {
|
|
637
|
+
console.log("Resolved m3u8 (sandbox JSON):", found);
|
|
638
|
+
return found;
|
|
639
|
+
}
|
|
640
|
+
} catch (e) {
|
|
641
|
+
/* ignore */
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// 4) finally scan the original script text for direct m3u8 (rare if obfuscated)
|
|
645
|
+
const fromScript = findM3u8(script);
|
|
646
|
+
if (fromScript) {
|
|
647
|
+
console.log("Resolved m3u8 (script literal):", fromScript);
|
|
648
|
+
return fromScript;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
console.log(
|
|
652
|
+
"Could not resolve m3u8 from this script, continuing to next candidate...",
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// fallback: try data-src attribute in html (in case)
|
|
657
|
+
const fallback = html.match(/data-src="([^"]+\.m3u8[^"]*)"/i);
|
|
658
|
+
if (fallback) {
|
|
659
|
+
console.log("FOUND data-src m3u8 (fallback):", fallback[1]);
|
|
660
|
+
return fallback[1];
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
console.log("Could not resolve m3u8 from any Kwik script.");
|
|
664
|
+
return null;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
module.exports = TestController;
|