Ayogiakbar

DramaBox Apps Snippets

Jan 9th, 2026 (edited)
820
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /**
  2.  * Dramabox Scraper CLI Engine
  3.  * Ultimate Developer Edition
  4.  * Features: Cloud Crypto Signature, Akamai WAF Bypass (TLS Socket), SOCKS5 Auto-Rotation
  5.  * Author: Gienetic
  6.  */
  7.  
  8. import readline from "readline";
  9. import fs from "fs";
  10. import tls from "tls";
  11. import crypto from "crypto";
  12. import zlib from "zlib";
  13. import axios from "axios";
  14. import { SocksClient } from "socks";
  15.  
  16. // ============================================================================
  17. // 1. SYSTEM CONFIGURATION
  18. // ============================================================================
  19. const CONFIG = {
  20.     API_BASE: "https://nb-dramabox-gentoken.vercel.app",
  21.     // Kamu harus memasukkan URL API SOCKS5 Pool di sini
  22.     PROXY_API_URL: "https://exsalapi.my.id/api/network/socks5-pool?apikey=YOUR_API_KEY_HERE",
  23.     TIMEOUT_MS: 15000,
  24.     MAX_RETRIES: 5
  25. };
  26.  
  27. let currentProxy = null;
  28. const session = {
  29.     token: "",
  30.     deviceid: "",
  31.     androidid: "",
  32.     instanceid: crypto.randomBytes(16).toString("hex"),
  33.     afid: `${Date.now()}-${Math.floor(Math.random() * 9999999999999999)}`,
  34.     ins: Date.now().toString(),
  35.     st: "cK4n10B_0tTQBrxFyyBWnOKD",
  36.     cookies: []
  37. };
  38.  
  39. // ============================================================================
  40. // 2. UI & TERMINAL LOGGING UTILS
  41. // ============================================================================
  42. const c = {
  43.     rst: "\x1b[0m", dim: "\x1b[2m", bld: "\x1b[1m",
  44.     red: "\x1b[31m", grn: "\x1b[32m", ylw: "\x1b[33m",
  45.     blu: "\x1b[34m", mag: "\x1b[35m", cyn: "\x1b[36m", wht: "\x1b[37m"
  46. };
  47.  
  48. const log = {
  49.     info: (msg) => console.log(`${c.cyn}[*]${c.rst} ${msg}`),
  50.     ok: (msg) => console.log(`${c.grn}[+]${c.rst} ${msg}`),
  51.     err: (msg) => console.log(`${c.red}[-]${c.rst} ${msg}`),
  52.     warn: (msg) => console.log(`${c.ylw}[!]${c.rst} ${msg}`),
  53.     step: (msg) => process.stdout.write(`${c.blu}[~]${c.rst} ${msg}`),
  54.     header: (title) => console.log(`\n${c.mag}${c.bld}--- [ ${title} ] ---${c.rst}\n`)
  55. };
  56.  
  57. const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
  58. const ask = (q) => new Promise((resolve) => rl.question(`${c.ylw}?${c.rst} ${q}`, resolve));
  59.  
  60. function printBanner() {
  61.     console.clear();
  62.     console.log(`${c.cyn}${c.bld}`);
  63.     console.log(`██████╗ ██████╗ █████╗ ███╗   ███╗█████╗ `);
  64.     console.log(`██╔══██╗██╔══██╗██╔══██╗████╗ ████║██╔══██╗`);
  65.     console.log(`██║  ██║██████╔╝███████║██╔████╔██║███████║`);
  66.     console.log(`██║  ██║██╔══██╗██╔══██║██║╚██╔╝██║██╔══██║`);
  67.     console.log(`██████╔╝██║  ██║██║  ██║██║ ╚═╝ ██║██║  ██║`);
  68.     console.log(`╚═════╝ ╚═╝  ╚═╝╚═╝  ╚═╝╚═╝     ╚═╝╚═╝  ╚═╝`);
  69.     console.log(`██████╗  ██████╗ ██╗  ██╗`);
  70.     console.log(`██╔══██╗██╔═══██╗╚██╗██╔╝`);
  71.     console.log(`██████╔╝██║   ██║ ╚███╔╝ `);
  72.     console.log(`██╔══██╗██║   ██║ ██╔██╗ `);
  73.     console.log(`██████╔╝╚██████╔╝██╔╝ ██╗`);
  74.     console.log(`╚═════╝  ╚═════╝ ╚═╝  ╚═╝${c.rst}`);
  75.     console.log(`\n${c.dim}:: Core Engine V5.9.0 | SOCKS5 Auto-Rotation | Coded by Gienetic ::${c.rst}\n`);
  76. }
  77.  
  78. // ============================================================================
  79. // 3. CORE HELPERS
  80. // ============================================================================
  81. function getLocalTime() {
  82.     const now = new Date();
  83.     const offset = 7 * 60 * 60 * 1000; // GMT+7
  84.     const bt = new Date(now.getTime() + offset);
  85.     const pad = (n) => n.toString().padStart(2, "0");
  86.     return `${bt.getUTCFullYear()}-${pad(bt.getUTCMonth() + 1)}-${pad(bt.getUTCDate())} ${pad(bt.getUTCHours())}:${pad(bt.getUTCMinutes())}:${pad(bt.getUTCSeconds())}.${bt.getUTCMilliseconds().toString().padStart(3, "0")} +0700`;
  87. }
  88.  
  89. function saveToFile(filename, data) {
  90.     try {
  91.         const safeFilename = filename.replace(/[^a-z0-9_.-]/gi, "_").toLowerCase();
  92.         fs.writeFileSync(safeFilename, JSON.stringify(data, null, 4));
  93.         log.ok(`Dump saved: ${c.bld}${safeFilename}${c.rst}`);
  94.     } catch (e) {
  95.         log.err(`Failed to write file ${filename}`);
  96.     }
  97. }
  98.  
  99. const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
  100.  
  101. // ============================================================================
  102. // 4. NETWORK & CRYPTO ENGINE
  103. // ============================================================================
  104. async function fetchNewProxy() {
  105.     if (CONFIG.PROXY_API_URL.includes("YOUR_API_KEY")) {
  106.         log.warn("Proxy API Key not set. Proceeding without proxy rotation.");
  107.         return false;
  108.     }
  109.  
  110.     try {
  111.         const res = await axios.get(CONFIG.PROXY_API_URL, { timeout: 10000 });
  112.         if (res.data?.status && res.data.data?.proxies) {
  113.             const available = res.data.data.proxies.filter(p => p.status === "standby" && p.is_ready);
  114.            
  115.             if (available.length > 0) {
  116.                 const randomProxy = available[Math.floor(Math.random() * available.length)];
  117.                 const [ip, port] = randomProxy.address.split(":");
  118.                 currentProxy = { ip, port: parseInt(port, 10), loc: randomProxy.location };
  119.                 console.log(`\n${c.grn}[Proxy Rotated] ${currentProxy.ip}:${currentProxy.port} (${currentProxy.loc})${c.rst}`);
  120.                 return true;
  121.             }
  122.         }
  123.         return false;
  124.     } catch (error) {
  125.         log.err(`Proxy Error: ${error.message}`);
  126.         return false;
  127.     }
  128. }
  129.  
  130. async function generateToken() {
  131.     try {
  132.         log.step("Initializing Session via Vercel... ");
  133.         const res = await axios.get(`${CONFIG.API_BASE}/generate-token`, { timeout: CONFIG.TIMEOUT_MS });
  134.        
  135.         if (res.data?.status && res.data?.data) {
  136.             session.token = res.data.data.sn;
  137.             session.deviceid = res.data.data.device_id;
  138.             session.androidid = res.data.data.android_id;
  139.             session.cookies = [];
  140.             console.log(`${c.grn}SUCCESS${c.rst}`);
  141.             return true;
  142.         }
  143.         console.log(`${c.red}FAILED${c.rst}`);
  144.         return false;
  145.     } catch (error) {
  146.         console.log(`${c.red}ERR: ${error.message}${c.rst}`);
  147.         return false;
  148.     }
  149. }
  150.  
  151. async function getRemoteSignature(bodyPayload) {
  152.     try {
  153.         const payload = {
  154.             body: bodyPayload,
  155.             device_id: session.deviceid,
  156.             android_id: session.androidid,
  157.             token: session.token
  158.         };
  159.         const res = await axios.post(`${CONFIG.API_BASE}/sign`, payload, { timeout: CONFIG.TIMEOUT_MS });
  160.         return res.data?.status ? res.data.data : null;
  161.     } catch (error) {
  162.         return null;
  163.     }
  164. }
  165.  
  166. function buildHeaders(signature, tokenStr) {
  167.     return {
  168.         "accept-encoding": "gzip",
  169.         "version": "580",
  170.         "package-name": "com.storymatrix.drama",
  171.         "p": "63",
  172.         "cid": "DRA1000042",
  173.         "apn": "2",
  174.         "country-code": "ID",
  175.         "mchid": "DRA1000042",
  176.         "tz": "-420",
  177.         "language": "in",
  178.         "mcc": "510",
  179.         "locale": "in_ID",
  180.         "is_root": "0",
  181.         "device-id": session.deviceid,
  182.         "nchid": "DRA1000042",
  183.         "instanceid": session.instanceid,
  184.         "md": "Redmi Note 5",
  185.         "store-source": "store_google",
  186.         "mf": "XIAOMI",
  187.         "device-score": "60",
  188.         "local-time": getLocalTime(),
  189.         "time-zone": "+0700",
  190.         "brand": "Xiaomi",
  191.         "lat": "0",
  192.         "is_emulator": "0",
  193.         "current-language": "in",
  194.         "ov": "10",
  195.         "afid": session.afid,
  196.         "android-id": session.androidid,
  197.         "srn": "1080x2160",
  198.         "ins": session.ins,
  199.         "is_vpn": "1",
  200.         "build": "Build/QQ3A.200805.001",
  201.         "pline": "ANDROID",
  202.         "vn": "5.8.0",
  203.         "over-flow": "new-fly",
  204.         "tn": tokenStr ? `Bearer ${tokenStr}` : "",
  205.         "sn": signature,
  206.         "st": session.st,
  207.         "active-time": Math.floor(Math.random() * 20000).toString(),
  208.         "content-type": "application/json; charset=UTF-8",
  209.         "user-agent": "okhttp/4.12.0"
  210.     };
  211. }
  212.  
  213. function wRequest(urlStr, bodyObj, headersInput) {
  214.     return new Promise(async (resolve) => {
  215.         const urlObj = new URL(urlStr);
  216.         const bodyStr = JSON.stringify(bodyObj);
  217.        
  218.         if (session.cookies.length > 0) headersInput["Cookie"] = session.cookies.join("; ");
  219.  
  220.         let requestRaw = `POST ${urlObj.pathname}${urlObj.search} HTTP/1.1\r\nHost: ${urlObj.hostname}\r\n`;
  221.         for (const [k, v] of Object.entries(headersInput)) {
  222.             if (!["host", "content-length", "cookie"].includes(k.toLowerCase())) requestRaw += `${k}: ${v}\r\n`;
  223.         }
  224.         if (headersInput["Cookie"]) requestRaw += `Cookie: ${headersInput["Cookie"]}\r\n`;
  225.         requestRaw += `Content-Length: ${Buffer.byteLength(bodyStr)}\r\nConnection: close\r\n\r\n${bodyStr}`;
  226.  
  227.         const tlsOptions = {
  228.             host: urlObj.hostname,
  229.             servername: urlObj.hostname,
  230.             rejectUnauthorized: false,
  231.             ciphers: "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256",
  232.             ALPNProtocols: ["http/1.1"]
  233.         };
  234.  
  235.         let socket;
  236.         try {
  237.             if (currentProxy?.ip && currentProxy?.port) {
  238.                 const proxyInfo = await SocksClient.createConnection({
  239.                     proxy: { ipaddress: currentProxy.ip, port: currentProxy.port, type: 5 },
  240.                     command: "connect",
  241.                     destination: { host: urlObj.hostname, port: 443 }
  242.                 });
  243.                 tlsOptions.socket = proxyInfo.socket;
  244.             } else {
  245.                 tlsOptions.port = 443;
  246.             }
  247.             socket = tls.connect(tlsOptions, () => socket.write(requestRaw));
  248.         } catch (err) {
  249.             return resolve({ success: false, error: `Socket/Proxy Error: ${err.message}` });
  250.         }
  251.  
  252.         let rawResponse = Buffer.alloc(0);
  253.         socket.on("data", (c) => rawResponse = Buffer.concat([rawResponse, c]));
  254.         socket.on("end", () => {
  255.             const resStr = rawResponse.toString("binary");
  256.             const splitIdx = resStr.indexOf("\r\n\r\n");
  257.             if (splitIdx === -1) return resolve({ success: false, error: "Invalid HTTP Format" });
  258.  
  259.             const headerPart = resStr.substring(0, splitIdx);
  260.             let bodyBuffer = rawResponse.subarray(splitIdx + 4);
  261.             const statusCode = parseInt(headerPart.split("\r\n")[0].split(" ")[1], 10);
  262.  
  263.             const headers = {};
  264.             headerPart.split("\r\n").slice(1).forEach(line => {
  265.                 const parts = line.split(":");
  266.                 if (parts.length > 1) {
  267.                     const key = parts[0].trim().toLowerCase();
  268.                     const val = parts.slice(1).join(":").trim();
  269.                     headers[key] = key === "set-cookie" ? [...(headers[key] || []), val] : val;
  270.                 }
  271.             });
  272.  
  273.             if (headers["st"]) session.st = headers["st"];
  274.             if (headers["set-cookie"]) {
  275.                 headers["set-cookie"].forEach(cStr => {
  276.                     const mainCookie = cStr.split(";")[0];
  277.                     session.cookies = session.cookies.filter(c => !c.startsWith(mainCookie.split("=")[0] + "="));
  278.                     session.cookies.push(mainCookie);
  279.                 });
  280.             }
  281.  
  282.             if (statusCode >= 400) return resolve({ success: false, statusCode, error: `WAF Blocked (${statusCode})` });
  283.  
  284.             if (headers["content-encoding"] === "gzip") {
  285.                 try { bodyBuffer = zlib.gunzipSync(bodyBuffer); } catch (e) {}
  286.             }
  287.  
  288.             let finalDataStr = bodyBuffer.toString("utf8");
  289.             if (headers["transfer-encoding"] === "chunked") {
  290.                 const start = finalDataStr.indexOf("{"), end = finalDataStr.lastIndexOf("}");
  291.                 if (start !== -1 && end !== -1) finalDataStr = finalDataStr.substring(start, end + 1);
  292.             }
  293.  
  294.             try {
  295.                 resolve({ success: true, statusCode, data: JSON.parse(finalDataStr) });
  296.             } catch (e) {
  297.                 resolve({ success: false, statusCode, error: "JSON Parse Error" });
  298.             }
  299.         });
  300.  
  301.         socket.on("error", (err) => resolve({ success: false, error: err.message }));
  302.         socket.setTimeout(CONFIG.TIMEOUT_MS);
  303.         socket.on("timeout", () => { socket.destroy(); resolve({ success: false, error: "Timeout" }); });
  304.     });
  305. }
  306.  
  307. async function postData(endpoint, body) {
  308.     const signData = await getRemoteSignature(body);
  309.     if (!signData) return { success: false, error: "Cloud signature generation failed" };
  310.  
  311.     const headers = buildHeaders(signData.sn, session.token);
  312.     const qs = endpoint.includes("?") ? "&" : "?";
  313.     const fullEndpoint = `${endpoint}${qs}timestamp=${signData.timestamp}`;
  314.  
  315.     const res = await wRequest(fullEndpoint, body, headers);
  316.     return res.success && res.data?.data ? { success: true, data: res.data.data } : { success: false, error: res.error || "Empty Data" };
  317. }
  318.  
  319. // ============================================================================
  320. // 5. SCRAPING MODULES
  321. // ============================================================================
  322. async function doSearch() {
  323.     log.header("SEARCH ENGINE");
  324.     const keyword = await ask("Enter Keyword (Title/Genre): ");
  325.     console.log(`\n${c.dim}Filters:${c.rst} [1] Trending  [2] Latest  [3] Unwatched`);
  326.     const sortChoice = await ask("Select Sort Type (1-3): ");
  327.    
  328.     const sortType = parseInt(sortChoice) || 1;
  329.     const typeName = ["", "Trending", "Latest", "Unwatched"][sortType];
  330.     const searchSource = sortType >= 2 ? "搜索按钮" : "";
  331.     const fromParam = sortType >= 2 ? "search_sug" : "search_result";
  332.  
  333.     let page = 1, allResults = [];
  334.     console.log("");
  335.  
  336.     while (true) {
  337.         process.stdout.write(`\r${c.blu}[~]${c.rst} Fetching page ${page}... `);
  338.         const body = { searchSource, sortType, synSwitch: 1, pageNo: page, pageSize: 20, from: fromParam, keyword };
  339.         const res = await postData("https://sapi.dramaboxvideo.com/drama-box/search/search", body);
  340.  
  341.         if (res.success && res.data?.searchList?.length > 0) {
  342.             allResults.push(...res.data.searchList);
  343.             console.log(`${c.grn}OK!${c.rst} (${res.data.searchList.length} items)`);
  344.             if (res.data.isMore === 0 || res.data.searchList.length < 20) break;
  345.             page++;
  346.             await sleep(800);
  347.         } else {
  348.             console.log(`${c.dim}End of results.${c.rst}`);
  349.             break;
  350.         }
  351.     }
  352.     if (allResults.length) saveToFile(`search_${typeName}_${keyword}.json`, allResults);
  353. }
  354.  
  355. async function doLatest() {
  356.     log.header("LATEST RELEASES");
  357.     let page = 1, allResults = [];
  358.     while (true) {
  359.         process.stdout.write(`\r${c.blu}[~]${c.rst} Fetching page ${page}... `);
  360.         const body = { newChannelStyle: 1, isNeedRank: 1, pageNo: page, index: 1, channelId: 43, recSessionId: crypto.randomBytes(32).toString("hex") };
  361.         const res = await postData("https://sapi.dramaboxvideo.com/drama-box/he001/theater", body);
  362.  
  363.         if (res.success && res.data?.newTheaterList?.records?.length > 0) {
  364.             allResults.push(...res.data.newTheaterList.records);
  365.             console.log(`${c.grn}OK!${c.rst}`);
  366.             if (page >= (res.data.newTheaterList.pages || 1)) break;
  367.             page++;
  368.             await sleep(800);
  369.         } else break;
  370.     }
  371.     if (allResults.length) saveToFile("latest_full_release.json", allResults);
  372. }
  373.  
  374. async function doGetForYou() {
  375.     log.header("FOR YOU (FYP)");
  376.     log.step("Fetching algorithmic recommendations... ");
  377.     const body = { homePageStyle: 0, isNeedRank: 1, isNeedNewChannel: 1, type: 0, index: 0, channelId: 175, recSessionId: crypto.randomBytes(32).toString("hex") };
  378.     const res = await postData("https://sapi.dramaboxvideo.com/drama-box/he001/theater", body);
  379.  
  380.     if (res.success && res.data?.columnVoList) {
  381.         console.log(`${c.grn}SUCCESS${c.rst}`);
  382.         const books = res.data.columnVoList.flatMap(col => col.bookList || []);
  383.         saveToFile("foryou_recommended.json", books);
  384.     } else console.log(`${c.red}FAILED/EMPTY${c.rst}`);
  385. }
  386.  
  387. async function doGetComingSoon() {
  388.     log.header("COMING SOON");
  389.     log.step("Fetching upcoming catalog... ");
  390.     const res = await postData("https://sapi.dramaboxvideo.com/drama-box/he001/reserveBook", {});
  391.  
  392.     if (res.success && res.data?.reserveBookList?.length > 0) {
  393.         console.log(`${c.grn}SUCCESS${c.rst}`);
  394.         const books = res.data.reserveBookList.map(b => ({
  395.             ...b, releaseDateLocal: b.bookShelfTime ? new Date(b.bookShelfTime).toLocaleString("id-ID", { timeZone: "Asia/Jakarta" }) : "TBA"
  396.         }));
  397.         saveToFile("coming_soon.json", books);
  398.     } else console.log(`${c.red}FAILED/EMPTY${c.rst}`);
  399. }
  400.  
  401. async function doGetRank() {
  402.     log.header("LEADERBOARDS");
  403.     console.log(`${c.dim}Categories:${c.rst} [1] Trending  [2] Popular Search  [3] Newest`);
  404.     const type = parseInt(await ask("Select Category (1-3): ")) || 1;
  405.     log.step(`Fetching Rank Type [${type}]... `);
  406.    
  407.     const res = await postData("https://sapi.dramaboxvideo.com/drama-box/he001/rank", { rankType: type });
  408.     if (res.success && res.data?.rankList?.length > 0) {
  409.         console.log(`${c.grn}SUCCESS${c.rst}`);
  410.         saveToFile(`rank_category_${type}.json`, res.data.rankList);
  411.     } else console.log(`${c.red}FAILED/EMPTY${c.rst}`);
  412. }
  413.  
  414. async function doGetVip() {
  415.     log.header("VIP EXCLUSIVES");
  416.     log.step("Fetching VIP & Weekly Selection... ");
  417.     const res = await postData("https://sapi.dramaboxvideo.com/drama-box/he001/theater", { homePageStyle: 0, isNeedRank: 1, index: 4, type: 0, channelId: 205 });
  418.  
  419.     if (res.success && res.data?.columnVoList) {
  420.         console.log(`${c.grn}SUCCESS${c.rst}`);
  421.         const books = res.data.columnVoList.flatMap(col => col.bookList || []);
  422.         saveToFile("vip_exclusive.json", books);
  423.     } else console.log(`${c.red}FAILED/EMPTY${c.rst}`);
  424. }
  425.  
  426. async function doGetClassify() {
  427.     log.header("CLASSIFY EXPLORER");
  428.     let page = 1, allResults = [];
  429.     while (true) {
  430.         process.stdout.write(`\r${c.blu}[~]${c.rst} Fetching page ${page}... `);
  431.         const res = await postData("https://sapi.dramaboxvideo.com/drama-box/he001/classify", { typeList: [], showLabels: true, pageNo: page, pageSize: 15 });
  432.  
  433.         if (res.success && res.data?.classifyBookList?.records?.length > 0) {
  434.             allResults.push(...res.data.classifyBookList.records);
  435.             console.log(`${c.grn}OK!${c.rst}`);
  436.             if (res.data.classifyBookList.isMore === 0) break;
  437.             page++;
  438.             await sleep(800);
  439.         } else break;
  440.     }
  441.     if (allResults.length) saveToFile("classify_full.json", allResults);
  442. }
  443.  
  444. async function doGetEpisodes() {
  445.     log.header("RAW EPISODE FETCHER (SHADOWBAN BYPASS)");
  446.     const bookId = await ask("Target Book ID (e.g. 42000009439): ");
  447.    
  448.     if ((await ask("Use Auto Proxy Rotation for this task? (y/n): ")).toLowerCase() === "y") {
  449.         log.step("Fetching initial proxy... ");
  450.         await fetchNewProxy();
  451.     } else currentProxy = null;
  452.  
  453.     let allEpisodesRaw = [], currentIndex = -1, batchCount = 1, consecutiveSkips = 0;
  454.     console.log("\nInitiating smart extraction...\n");
  455.  
  456.     while (true) {
  457.         process.stdout.write(`\r${c.blu}[~]${c.rst} Fetching batch ${c.bld}#${batchCount}${c.rst} (Cursor: ${currentIndex})... `);
  458.         const body = {
  459.             boundaryIndex: 0, index: parseInt(currentIndex), currencyPlaySource: "discover_175_rec",
  460.             needEndRecommend: 0, currencyPlaySourceName: "首页发现_Untukmu_推荐列表", preLoad: false,
  461.             rid: "", pullCid: "", enterReaderChapterIndex: 0,
  462.             loadDirection: currentIndex === -1 ? 0 : 2, startUpKey: crypto.randomUUID(), bookId: String(bookId)
  463.         };
  464.  
  465.         const res = await postData("https://sapi.dramaboxvideo.com/drama-box/chapterv2/batch/load", body);
  466.         const isKosong = res.success && (!res.data?.chapterList?.length);
  467.         const isProxyErr = !res.success && res.error?.includes("Proxy");
  468.  
  469.         if (!res.success || isKosong) {
  470.             consecutiveSkips++;
  471.             console.log(`\n${c.ylw}[!] Blocked/Empty on index ${currentIndex}. Attempt ${consecutiveSkips}/${CONFIG.MAX_RETRIES}...${c.rst}`);
  472.            
  473.             if (consecutiveSkips >= CONFIG.MAX_RETRIES) {
  474.                 console.log(`${c.red}[-] Max retries reached. Safely exiting.${c.rst}`);
  475.                 break;
  476.             }
  477.             if (consecutiveSkips === 2 || consecutiveSkips === 4) {
  478.                 log.step("Rotating Session Token... ");
  479.                 await generateToken();
  480.             }
  481.             if (currentProxy && (consecutiveSkips === 3 || isProxyErr)) {
  482.                 log.step("Rotating Proxy IP... ");
  483.                 await fetchNewProxy();
  484.             }
  485.  
  486.             if (currentIndex !== -1 && !isProxyErr) currentIndex += 5;
  487.             await sleep(2000 + Math.random() * 2000);
  488.             continue;
  489.         }
  490.  
  491.         consecutiveSkips = 0;
  492.         const newChapters = res.data.chapterList.filter(n => !allEpisodesRaw.some(e => e.chapterId === n.chapterId));
  493.        
  494.         if (!newChapters.length) {
  495.             currentIndex += 5;
  496.             consecutiveSkips++;
  497.         } else {
  498.             allEpisodesRaw.push(...newChapters);
  499.             currentIndex = parseInt(newChapters[newChapters.length - 1].chapterIndex);
  500.             console.log(`${c.grn}OK!${c.rst} Total: ${allEpisodesRaw.length}`);
  501.         }
  502.         batchCount++;
  503.         await sleep(1000 + Math.random() * 800);
  504.     }
  505.    
  506.     currentProxy = null; // Reset
  507.     if (allEpisodesRaw.length) {
  508.         allEpisodesRaw.sort((a, b) => a.chapterIndex - b.chapterIndex);
  509.         log.info(`Extraction complete. Parsed ${c.bld}${allEpisodesRaw.length}${c.rst} episodes.`);
  510.         saveToFile(`raw_episodes_${bookId}.json`, allEpisodesRaw);
  511.     }
  512. }
  513.  
  514. async function doDecryptUrl() {
  515.     log.header("ALIYUN PROXY DECRYPTOR");
  516.     const rawUrl = await ask("Input Target URL (.encrypt.mp4): ");
  517.     if (!rawUrl.trim()) return log.err("URL cannot be empty.");
  518.  
  519.     const decryptedUrl = `${CONFIG.API_BASE}/api/tools/dramabox/decrypt-video?url=${encodeURIComponent(rawUrl.trim())}`;
  520.     log.ok("Decryption Route Generated:");
  521.     console.log(`\n${c.cyn}${c.bld}${decryptedUrl}${c.rst}\n`);
  522.     log.info("Copy this URL to VLC/IDM/Browser to play/download.");
  523. }
  524.  
  525. // ============================================================================
  526. // 6. MAIN EXECUTION
  527. // ============================================================================
  528. async function main() {
  529.     printBanner();
  530.     if (!(await generateToken())) return process.exit(1);
  531.  
  532.     while (true) {
  533.         console.log(`\n${c.wht}${c.bld}--- [ MAIN DASHBOARD ] ---${c.rst}`);
  534.         const menus = [
  535.             "Search Drama (Deep Scan)", "Fetch Latest Releases", "Fetch For You (FYP)",
  536.             "Fetch Coming Soon", "Fetch Leaderboards", "Fetch VIP Exclusives",
  537.             "Classify Category Explorer", `${c.bld}Extract Raw Episodes (SOCKS5 Auto-Rotate)${c.rst}`,
  538.             "Bypass & Decrypt URL (Vercel Node)"
  539.         ];
  540.        
  541.         menus.forEach((m, i) => console.log(`  ${c.cyn}[ 0${i + 1} ]${c.rst} ${m}`));
  542.         console.log(`  ${c.red}[ 00 ]${c.rst} Exit Termux\n`);
  543.  
  544.         const choice = (await ask("Execute module: ")).trim();
  545.         switch (choice) {
  546.             case "1": case "01": await doSearch(); break;
  547.             case "2": case "02": await doLatest(); break;
  548.             case "3": case "03": await doGetForYou(); break;
  549.             case "4": case "04": await doGetComingSoon(); break;
  550.             case "5": case "05": await doGetRank(); break;
  551.             case "6": case "06": await doGetVip(); break;
  552.             case "7": case "07": await doGetClassify(); break;
  553.             case "8": case "08": await doGetEpisodes(); break;
  554.             case "9": case "09": await doDecryptUrl(); break;
  555.             case "0": case "00": log.info("Shutting down core engine... Goodbye!"); return process.exit(0);
  556.             default: log.warn("Invalid module sequence. Select 00 - 09.");
  557.         }
  558.     }
  559. }
  560.  
  561. main();
Advertisement
Add Comment
Please, Sign In to add comment