mirror of
https://github.com/shafat-96/anime-mapper
synced 2026-04-17 15:51:45 +00:00
fix
This commit is contained in:
173
src/extractors/kwik.js
Normal file
173
src/extractors/kwik.js
Normal file
@@ -0,0 +1,173 @@
|
||||
import axios from 'axios';
|
||||
|
||||
const kwikUserAgent = "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Mobile Safari/537.36";
|
||||
|
||||
export async function extractKwik(kwikUrl, referer) {
|
||||
if (!kwikUrl) {
|
||||
throw new Error("missing kwik URL");
|
||||
}
|
||||
|
||||
try {
|
||||
const urlObj = new URL(kwikUrl);
|
||||
// Always use the origin of the kwik URL as Referer, regardless of passed-in value
|
||||
// mimicking: if u, err := url.Parse(kwikURL); err == nil { referer = u.Scheme + "://" + u.Host + "/" }
|
||||
const refinedReferer = `${urlObj.protocol}//${urlObj.host}/`;
|
||||
|
||||
const response = await axios.get(kwikUrl, {
|
||||
headers: {
|
||||
'User-Agent': kwikUserAgent,
|
||||
'Referer': refinedReferer,
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'
|
||||
}
|
||||
});
|
||||
|
||||
const html = response.data;
|
||||
|
||||
// Find the packed eval JS - look for eval(...) containing m3u8
|
||||
const jsMatch = html.match(/;(eval\(function\(p,a,c,k,e,d\).*?m3u8.*?\)\))/);
|
||||
if (!jsMatch || jsMatch.length < 2) {
|
||||
throw new Error("could not find eval JS pattern in Kwik page");
|
||||
}
|
||||
|
||||
const jsCode = jsMatch[1];
|
||||
|
||||
const lastBraceIdx = jsCode.lastIndexOf("}(");
|
||||
if (lastBraceIdx === -1) {
|
||||
throw new Error("could not find argument start marker '}('");
|
||||
}
|
||||
|
||||
const endIdx = jsCode.lastIndexOf("))");
|
||||
if (endIdx === -1 || endIdx <= lastBraceIdx) {
|
||||
throw new Error("could not find argument end marker '))'");
|
||||
}
|
||||
|
||||
const stripped = jsCode.substring(lastBraceIdx + 2, endIdx);
|
||||
|
||||
const parts = parsePackedArgs(stripped);
|
||||
if (parts.length < 4) {
|
||||
throw new Error(`invalid packed data: expected at least 4 parts, got ${parts.length}`);
|
||||
}
|
||||
|
||||
const p = parts[0];
|
||||
const a = parseInt(parts[1], 10);
|
||||
const c = parseInt(parts[2], 10);
|
||||
|
||||
let kStr = parts[3];
|
||||
kStr = kStr.replace(/\.split\(['"]\|['"]\)$/, "");
|
||||
const k = kStr.split("|");
|
||||
|
||||
let decoded = unpackKwik(p, a, c, k);
|
||||
|
||||
decoded = decoded.replace(/\\/g, "");
|
||||
decoded = decoded.replace("https.split(://", "https://");
|
||||
decoded = decoded.replace("http.split(://", "http://");
|
||||
|
||||
const srcMatch = decoded.match(/source=(https?:\/\/[^;]+)/);
|
||||
if (!srcMatch || srcMatch.length < 2) {
|
||||
throw new Error("could not find video URL in unpacked code");
|
||||
}
|
||||
|
||||
const videoURL = cleanKwikURL(srcMatch[1]);
|
||||
return {
|
||||
url: videoURL,
|
||||
isM3U8: videoURL.includes(".m3u8"),
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function unpackKwik(p, a, c, k) {
|
||||
const digits = "0123456789abcdefghijklmnopqrstuvwxyz";
|
||||
const dict = {};
|
||||
|
||||
function baseEncode(n) {
|
||||
const rem = n % a;
|
||||
let digit;
|
||||
if (rem > 35) {
|
||||
digit = String.fromCharCode(rem + 29);
|
||||
} else {
|
||||
digit = digits[rem];
|
||||
}
|
||||
|
||||
if (n < a) {
|
||||
return digit;
|
||||
}
|
||||
return baseEncode(Math.floor(n / a)) + digit;
|
||||
}
|
||||
|
||||
for (let i = c - 1; i >= 0; i--) {
|
||||
const key = baseEncode(i);
|
||||
if (i < k.length && k[i] !== "") {
|
||||
dict[key] = k[i];
|
||||
} else {
|
||||
dict[key] = key;
|
||||
}
|
||||
}
|
||||
|
||||
// Use regex to replace words
|
||||
return p.replace(/\b\w+\b/g, (w) => {
|
||||
if (Object.prototype.hasOwnProperty.call(dict, w)) {
|
||||
return dict[w];
|
||||
}
|
||||
return w;
|
||||
});
|
||||
}
|
||||
|
||||
function parsePackedArgs(input) {
|
||||
const result = [];
|
||||
let inQuote = false;
|
||||
let quoteChar = null;
|
||||
let depth = 0;
|
||||
let current = "";
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
const r = input[i];
|
||||
|
||||
if (!inQuote) {
|
||||
if (r === '\'' || r === '"') {
|
||||
inQuote = true;
|
||||
quoteChar = r;
|
||||
// Don't add quote to current, mimicking Go logic 'continue'
|
||||
continue;
|
||||
}
|
||||
if (r === ',' && depth === 0) {
|
||||
result.push(current.trim());
|
||||
current = "";
|
||||
continue;
|
||||
}
|
||||
if (r === '(' || r === '[' || r === '{') {
|
||||
depth++;
|
||||
} else if (r === ')' || r === ']' || r === '}') {
|
||||
if (depth > 0) {
|
||||
depth--;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (r === quoteChar) {
|
||||
inQuote = false;
|
||||
// Don't add quote to current
|
||||
continue;
|
||||
}
|
||||
}
|
||||
current += r;
|
||||
}
|
||||
if (current !== "") {
|
||||
result.push(current.trim());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function cleanKwikURL(u) {
|
||||
u = u.replace(/\\\//g, "/");
|
||||
u = u.replace(/^["']|["']$/g, ''); // Trim quotes
|
||||
u = u.replace(/[\n\r\t ]/g, ''); // Trim whitespace chars
|
||||
|
||||
// Remove semicolon and anything after it
|
||||
const idx = u.indexOf(";");
|
||||
if (idx !== -1) {
|
||||
u = u.substring(0, idx);
|
||||
}
|
||||
return u;
|
||||
}
|
||||
124
src/extractors/megacloud.js
Normal file
124
src/extractors/megacloud.js
Normal file
@@ -0,0 +1,124 @@
|
||||
import axios from 'axios';
|
||||
import { client } from '../utils/client.js';
|
||||
|
||||
class MegaCloudExtractor {
|
||||
constructor() {
|
||||
this.mainUrl = "https://megacloud.blog";
|
||||
this.scriptUrl = "https://script.google.com/macros/s/AKfycbxHbYHbrGMXYD2-bC-C43D3njIbU-wGiYQuJL61H4vyy6YVXkybMNNEPJNPPuZrD1gRVA/exec";
|
||||
this.keysUrl = "https://raw.githubusercontent.com/yogesh-hacker/MegacloudKeys/refs/heads/main/keys.json";
|
||||
}
|
||||
|
||||
async extract(videoUrl) {
|
||||
try {
|
||||
const embedUrl = new URL(videoUrl);
|
||||
const headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0",
|
||||
"Accept": "*/*",
|
||||
"Accept-Language": "en-US,en;q=0.5",
|
||||
"Origin": this.mainUrl,
|
||||
"Referer": `${this.mainUrl}/`,
|
||||
};
|
||||
|
||||
// 1. Fetch Embed Page
|
||||
const { data: html } = await client.get(videoUrl, { headers });
|
||||
|
||||
// 2. Extract Nonce
|
||||
let nonce = null;
|
||||
const match1 = html.match(/\b[a-zA-Z0-9]{48}\b/);
|
||||
if (match1) {
|
||||
nonce = match1[0];
|
||||
} else {
|
||||
const match2 = html.match(/\b([a-zA-Z0-9]{16})\b.*?\b([a-zA-Z0-9]{16})\b.*?\b([a-zA-Z0-9]{16})\b/);
|
||||
if (match2) {
|
||||
nonce = match2[1] + match2[2] + match2[3];
|
||||
}
|
||||
}
|
||||
|
||||
if (!nonce) throw new Error("Nonce not found");
|
||||
|
||||
// 3. Get Sources
|
||||
// e.g. https://megacloud.blog/embed-2/e-1/VJq4nDSaJyzH?k=1 -> ID: VJq4nDSaJyzH
|
||||
const id = embedUrl.pathname.split('/').pop();
|
||||
|
||||
const apiUrl = `${this.mainUrl}/embed-2/v3/e-1/getSources?id=${id}&_k=${nonce}`;
|
||||
const { data: response } = await client.get(apiUrl, {
|
||||
headers: {
|
||||
...headers,
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"Referer": this.mainUrl
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.sources || response.sources.length === 0) {
|
||||
throw new Error("No sources found");
|
||||
}
|
||||
|
||||
const encodedFile = response.sources[0].file;
|
||||
let m3u8Url = "";
|
||||
|
||||
if (encodedFile.includes(".m3u8")) {
|
||||
m3u8Url = encodedFile;
|
||||
} else {
|
||||
// 4. Decrypt via Google Script
|
||||
const { data: keyData } = await axios.get(this.keysUrl);
|
||||
const secret = keyData.mega;
|
||||
|
||||
const params = new URLSearchParams();
|
||||
params.append("encrypted_data", encodedFile);
|
||||
params.append("nonce", nonce);
|
||||
params.append("secret", secret);
|
||||
|
||||
const decryptUrl = `${this.scriptUrl}?${params.toString()}`;
|
||||
|
||||
// Fetch text response
|
||||
const { data: decryptedResponse } = await axios.get(decryptUrl, { responseType: 'text' });
|
||||
|
||||
// Kotlin Regex: "\"file\":\"(.*?)\""
|
||||
// Handling potentially weird JSON structure or escaped strings
|
||||
const textContent = typeof decryptedResponse === 'string' ? decryptedResponse : JSON.stringify(decryptedResponse);
|
||||
const fileMatch = textContent.match(/"file":"(.*?)"/);
|
||||
|
||||
if (fileMatch && fileMatch[1]) {
|
||||
// Clean up URL if needed (remove escape slashes)
|
||||
m3u8Url = fileMatch[1].replace(/\\/g, '');
|
||||
} else {
|
||||
throw new Error("Video URL not found in decrypted response");
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Build Result
|
||||
const tracks = [];
|
||||
if (response.tracks) {
|
||||
response.tracks.forEach(track => {
|
||||
if (track.kind === "captions" || track.kind === "subtitles") {
|
||||
tracks.push({
|
||||
url: track.file,
|
||||
lang: track.label || track.kind,
|
||||
label: track.label
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
sources: [{
|
||||
url: m3u8Url,
|
||||
isM3U8: true
|
||||
}],
|
||||
tracks: tracks,
|
||||
intro: response.intro || { start: 0, end: 0 },
|
||||
outro: response.outro || { start: 0, end: 0 },
|
||||
headers: {
|
||||
Referer: this.mainUrl,
|
||||
"User-Agent": headers["User-Agent"]
|
||||
}
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error("MegaCloud extraction failed:", error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const megaCloudExtractor = new MegaCloudExtractor();
|
||||
Reference in New Issue
Block a user