This commit is contained in:
shafat-96
2026-02-13 20:46:15 +06:00
parent fefba45a43
commit f7bf4d6cab
8 changed files with 477 additions and 401 deletions

173
src/extractors/kwik.js Normal file
View 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
View 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();