From e7cf46852159976755cee8f2e96ee546e8b73436 Mon Sep 17 00:00:00 2001 From: shafat-96 Date: Sat, 26 Jul 2025 11:49:14 +0600 Subject: [PATCH] Update embedHandler.js --- embedHandler.js | 294 +++++++++++++++++++++++------------------------- 1 file changed, 141 insertions(+), 153 deletions(-) diff --git a/embedHandler.js b/embedHandler.js index 4abc31b..9d37c5f 100644 --- a/embedHandler.js +++ b/embedHandler.js @@ -1,159 +1,147 @@ -import axios from 'axios'; +const axios = require('axios'); +const crypto = require('node:crypto'); -const MAIN_URL = "https://megacloud.blog"; -const KEY_URL = "https://raw.githubusercontent.com/yogesh-hacker/MegacloudKeys/refs/heads/main/keys.json"; -const DECODE_URL = "https://script.google.com/macros/s/AKfycbx-yHTwupis_JD0lNzoOnxYcEYeXmJZrg7JeMxYnEZnLBy5V0--UxEvP-y9txHyy1TX9Q/exec"; -const USER_AGENT = - "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Mobile Safari/537.36"; - -export type track = { - file: string; - label?: string; - kind: string; - default?: boolean; -}; - -export type unencryptedSrc = { - file: string; - type: string; -}; - -export type extractedSrc = { - sources: string | unencryptedSrc[]; - tracks: track[]; - t: number; - server: number; -}; - -type ExtractedData = Pick & { - sources: { file: string; type: string }[]; -}; - -// ๐Ÿ›  Decrypt using Google Apps Script -async function decryptWithGoogleScript( - encryptedData: string, - nonce: string, - secret: string -): Promise { - try { - const params = new URLSearchParams({ - encrypted_data: encryptedData, - nonce: nonce, - secret: secret, - }); - - const { data } = await axios.get(`${DECODE_URL}?${params.toString()}`); - - const fileMatch = data.match(/"file":"(.*?)"/)?.[1]; - if (!fileMatch) throw new Error('Video URL not found in decrypted response'); - return fileMatch; - } catch (error: any) { - console.error('Google Apps Script decryption failed:', error.message); - throw error; - } -} - -// ๐Ÿง  Extract nonce from embed HTML -function extractNonce(html: string): string | null { - const match1 = html.match(/\b[a-zA-Z0-9]{48}\b/); - if (match1) return match1[0]; - - 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) return match2[1] + match2[2] + match2[3]; - - return null; -} - -export class MegaCloud { - static async extract(url: string): Promise<{ sources: any[]; tracks?: track[] }> { - try { - const embedUrl = new URL(url); - const instance = new MegaCloud(); - const result = await instance.extract2(embedUrl); - return { - sources: result.sources, - tracks: result.tracks, - }; - } catch (err: any) { - console.error("MegaCloud extraction error:", err.message); - return { sources: [] }; +class EmbedSource { + constructor(file, sourceType) { + this.file = file; + this.type = sourceType; } - } +} - async extract2(embedIframeURL: URL): Promise { - const extractedData: ExtractedData = { - sources: [], - tracks: [], - t: 0, - server: 0, - }; - - try { - const id = embedIframeURL.pathname.split("/").pop()?.split("?")[0] || ""; - let nonce: string | null = null; - - const { data: html } = await axios.get(embedIframeURL.href, { - headers: { - Accept: '*/*', - 'X-Requested-With': 'XMLHttpRequest', - Referer: MAIN_URL, - 'User-Agent': USER_AGENT, - }, - }); - - nonce = extractNonce(html); - if (!nonce) throw new Error("Nonce not found"); - - const apiUrl = `${MAIN_URL}/embed-1/v3/e-1/getSources?id=${id}&_k=${nonce}`; - const headers = { - Accept: '*/*', - 'X-Requested-With': 'XMLHttpRequest', - Referer: MAIN_URL, - 'User-Agent': USER_AGENT, - }; - - const { data } = await axios.get(apiUrl, { headers }); - if (!data) return extractedData; - - // ๐Ÿ” Encrypted source (string) - if (typeof data.sources === 'string') { - try { - if (data.sources.includes('.m3u8')) { - extractedData.sources = [{ file: data.sources, type: 'hls' }]; - } else { - const { data: keyData } = await axios.get(KEY_URL); - const secret = keyData?.mega; - - if (!secret) throw new Error('No decryption key found for MegaCloud'); - - const decryptedUrl = await decryptWithGoogleScript(data.sources, nonce, secret); - - extractedData.sources = [{ file: decryptedUrl, type: 'hls' }]; - } - } catch (err: any) { - console.error("Failed to decrypt video source:", err.message); +class Track { + constructor(file, label, kind, isDefault = false) { + this.file = file; + this.label = label; + this.kind = kind; + if (isDefault) { + this.default = isDefault; } - } - - // ๐Ÿ”“ Unencrypted source (array) - else if (Array.isArray(data.sources)) { - extractedData.sources = data.sources.map(src => ({ - file: src.file, - type: src.type || 'hls', - })); - } - - // ๐Ÿงพ Subtitles/tracks - extractedData.tracks = (data.tracks || []).filter(track => - track.kind === 'captions' || track.kind === 'subtitles' - ); - extractedData.t = data.t || 0; - extractedData.server = data.server || 0; - - return extractedData; - } catch (err: any) { - console.error("Extraction error in extract2:", err.message); - return extractedData; } - } } + +class EmbedSources { + constructor(sources = [], tracks = [], t = 0, server = 1, intro = null, outro = null) { + this.sources = sources; + this.tracks = tracks; + this.t = t; + this.server = server; + if (intro) this.intro = intro; + if (outro) this.outro = outro; + } +} + +// Constants for Megacloud +const MEGACLOUD_URL = 'https://megacloud.blog'; +const KEY_URL = 'https://raw.githubusercontent.com/yogesh-hacker/MegacloudKeys/refs/heads/main/keys.json'; + +// --- OpenSSL-compatible key+IV derivation (same algorithm used by Megacloud) +function opensslKeyIv(password, salt, keyLen = 32, ivLen = 16) { + let d = Buffer.alloc(0); + let prev = Buffer.alloc(0); + + while (d.length < keyLen + ivLen) { + const hash = crypto.createHash('md5'); + hash.update(Buffer.concat([prev, password, salt])); + prev = hash.digest(); + d = Buffer.concat([d, prev]); + } + + return { + key: d.subarray(0, keyLen), + iv: d.subarray(keyLen, keyLen + ivLen), + }; +} + +// --- Decrypt OpenSSL AES-CBC encoded Base64 payloads +function decryptOpenSSL(encBase64, password) { + try { + const data = Buffer.from(encBase64, 'base64'); + if (data.subarray(0, 8).toString() !== 'Salted__') return null; + + const salt = data.subarray(8, 16); + const { key, iv } = opensslKeyIv(Buffer.from(password, 'utf8'), salt); + const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv); + const decrypted = Buffer.concat([ + decipher.update(data.subarray(16)), + decipher.final(), + ]); + return decrypted.toString('utf-8'); + } catch (err) { + console.error('Decryption error:', err.message); + return null; + } +} + +// --- Helpers +function extractId(url) { + return url.split('/').pop().split('?')[0]; +} + +async function getDecryptionKey() { + try { + const res = await axios.get(KEY_URL); + return typeof res.data === 'string' ? JSON.parse(res.data).mega : res.data?.mega; + } catch (e) { + console.error('Failed to fetch key:', e.message); + return null; + } +} + +const handleEmbed = async (embedUrl, referrer = 'https://megacloud.blog') => { + try { + if (!embedUrl) throw new Error('embedUrl is required'); + + const id = extractId(embedUrl); + const apiUrl = `${MEGACLOUD_URL}/embed-2/v2/e-1/getSources?id=${id}`; + + const headers = { + Referer: referrer || embedUrl, + Origin: 'https://megacloud.blog/', + 'User-Agent': 'Mozilla/5.0', + }; + + const { data } = await axios.get(apiUrl, { headers }); + if (!data?.sources) throw new Error('No sources field in response'); + + // Parse/Decrypt sources array + let rawSources; + if (typeof data.sources === 'string') { + try { + rawSources = JSON.parse(data.sources); + } catch (_) { + const key = await getDecryptionKey(); + if (!key) throw new Error('Failed to fetch decryption key'); + const decrypted = decryptOpenSSL(data.sources, key); + if (!decrypted) throw new Error('Decryption failed'); + rawSources = JSON.parse(decrypted); + } + } else if (Array.isArray(data.sources)) { + rawSources = data.sources; + } else { + throw new Error('Unexpected sources format'); + } + + if (!rawSources.length) throw new Error('No valid sources found'); + + const sources = rawSources.map((s) => new EmbedSource(s.file, s.type || s.quality || 'unknown')); + const tracks = (data.tracks || []).map((t) => new Track(t.file, t.label, t.kind, t.default)); + + return new EmbedSources( + sources, + tracks, + data.t ?? 0, + 'megacloud', + data.intro ?? null, + data.outro ?? null + ); + } catch (error) { + throw error; + } +}; + +module.exports = { + handleEmbed, + EmbedSource, + Track, + EmbedSources +};