#!/usr/bin/env node // Simple CLI to load a module JS file and run one of its functions. // Made by Cufiy // Works with CommonJS exports or files that define top-level global functions (executed in a VM sandbox). // Usage examples (PowerShell-safe quoting): // node run_module.js --path 'temp_modules/AniCrush_(ENG_SUB).js' --function searchResults --param '"naruto"' // node run_module.js --path 'temp_modules/AniCrush_(ENG_SUB).js' --function extractStreamUrl --param '"_movieId=123&ep=1&sv=4&sc=sub"' const fs = require('fs'); const path = require('path'); const vm = require('vm'); function parseArgs() { const args = process.argv.slice(2); const out = {}; for (let i = 0; i < args.length; i++) { const a = args[i]; if (a === '--path' && args[i+1]) { out.path = args[++i]; } else if (a === '--function' && args[i+1]) { out.function = args[++i]; } else if (a === '--param' && args[i+1]) { out.param = args[++i]; } else if (a === '--quiet' || a === '-q') { out.quiet = true; } else if (a === '--debug' || a === '-d') { out.debug = true; } else if (a === '--debug-full') { out.debugFull = true; } else if (a === '--sandbox') { out.sandbox = true; } else if (a === '--help') { out.help = true; } } return out; } async function loadModuleWithSandbox(filePath, debugLog, opts = {}) { // Read file and execute in a VM context so top-level functions become properties on the sandbox. const code = await fs.promises.readFile(filePath, 'utf8'); // Provide fetchv2 as a wrapper that mimics the module's expected behavior // helper for conditional logging based on flags const pushDebug = (msg, level = 'info', source = 'RUNNER') => { if (opts.quiet && level !== 'error') return; if (!opts.debug && level === 'debug') return; const tag = `[${String(level || 'info').toUpperCase()}]`; const src = `[${String(source || 'RUNNER').toUpperCase()}]`; const entry = `${tag} ${src} ${String(msg)}`; debugLog.push(entry); }; const fetchv2 = async (url, options) => { // fetch calls initiated by the module — mark as MODULE pushDebug('[FETCH REQUEST] ' + url + ' ' + formatArg(options), 'debug', 'MODULE'); try { const response = await fetch(url, options); // Try to read a clone of the body for transparent logging without consuming the original. // Attach an 'error' handler to the clone stream to avoid unhandled stream errors let bodyText = null; try { if (response && typeof response.clone === 'function') { const clone = response.clone(); // If the clone exposes a Node.js stream, capture errors to avoid crashing the process try { if (clone && clone.body && typeof clone.body.on === 'function') { clone.body.on('error', (err) => { pushDebug('[FETCH RESPONSE BODY STREAM ERROR] ' + (err && err.message), 'error', 'MODULE'); }); } } catch (e) { // ignore } try { bodyText = await clone.text(); } catch (e) { // fallback to arrayBuffer -> string conversion try { const buf = await clone.arrayBuffer(); bodyText = Buffer.from(buf).toString('utf8'); } catch (e2) { bodyText = '[unreadable body]'; } } } else if (response && typeof response.text === 'function') { // fallback (may consume body) try { bodyText = await response.text(); } catch (e) { bodyText = '[unreadable body]'; } } } catch (e) { bodyText = '[unreadable body]'; } // truncate response body for logs unless debug+debugFull are set let displayBody = bodyText; try { const isFull = Boolean(opts && opts.debug && opts.debugFull); if (typeof bodyText === 'string' && !isFull) { displayBody = bodyText.length > 100 ? bodyText.slice(0,100) + '…' : bodyText; } } catch (e) { // ignore } pushDebug('[FETCH RESPONSE] ' + url + ' status=' + (response && response.status) + ' body=' + formatArg(displayBody), 'debug', 'MODULE'); return response; } catch (error) { pushDebug('[FETCH ERROR] ' + url + ' ' + (error && error.message), 'error', 'MODULE'); throw error; } }; const formatArg = (arg) => { if (typeof arg === 'object' && arg !== null) { try { return JSON.stringify(arg); } catch (e) { return String(arg); } } return String(arg); }; const sandbox = { console: { log: (...args) => pushDebug(args.map(formatArg).join(' '), 'info', 'MODULE'), error: (...args) => pushDebug('[ERROR] ' + args.map(formatArg).join(' '), 'error', 'MODULE'), warn: (...args) => pushDebug('[WARN] ' + args.map(formatArg).join(' '), 'warn', 'MODULE'), info: (...args) => pushDebug('[INFO] ' + args.map(formatArg).join(' '), 'info', 'MODULE'), debug: (...args) => pushDebug('[DEBUG] ' + args.map(formatArg).join(' '), 'debug', 'MODULE') }, // allow access to require and module/exports in case the script uses CommonJS patterns require: require, module: { exports: {} }, exports: {}, // override fetch with our logging wrapper (and also expose fetchv2) fetch: fetchv2, fetchv2: fetchv2, // Provide soraFetch as an alias in case modules use it soraFetch: fetchv2, // Inject small obfuscated helper if modules expect it _0xB4F2: function() { return "accinrxxxxxxxxxx"; }, // Encoding functions encodeURIComponent: encodeURIComponent, decodeURIComponent: decodeURIComponent, // JSON operations JSON: JSON, // Promise support Promise: Promise, // small helper to avoid accidental infinite loops setTimeout, setInterval, clearTimeout, clearInterval }; const context = vm.createContext(sandbox); try { const script = new vm.Script(code, { filename: filePath }); script.runInContext(context, { timeout: 5000 }); } catch (e) { // running the file might fail if it expects browser globals — that's okay, we still return what we have // silently continue } // Merge module.exports/exports into sandbox for easier lookup const mod = Object.assign({}, sandbox.module && sandbox.module.exports ? sandbox.module.exports : {}, sandbox.exports || {}); // Also attach any top-level functions that were defined (e.g., function searchResults() { ... }) // We need to iterate over sandbox properties, not context for (const k of Object.keys(sandbox)) { if (!(k in mod) && typeof sandbox[k] === 'function') { mod[k] = sandbox[k]; } } return mod; } async function main() { const opts = parseArgs(); const debugLog = []; if (opts.help || !opts.path || !opts.function) { console.log(JSON.stringify({ status: 'error', message: 'Usage: node run_module.js --path --function [--param ]', data: null, debug: [] })); process.exit(opts.help ? 0 : 1); } const filePath = path.resolve(opts.path); if (!fs.existsSync(filePath)) { console.log(JSON.stringify({ status: 'error', message: 'File not found: ' + filePath, data: null, debug: [] })); process.exit(2); } let mod = {}; // Optionally prefer sandbox execution (avoids require side-effects and lets us hook internal functions) // Load module: prefer sandbox first (so we can hook top-level functions). If sandbox doesn't expose the requested // function, fall back to require(). If --sandbox is set, only use sandbox. if (opts.sandbox) { mod = await loadModuleWithSandbox(filePath, debugLog, opts); } else { try { mod = await loadModuleWithSandbox(filePath, debugLog, opts); } catch (e) { // ignore sandbox errors } if (!mod || !(opts.function in mod)) { try { delete require.cache[require.resolve(filePath)]; const required = require(filePath); if (required && (typeof required === 'object' || typeof required === 'function')) { mod = required; } } catch (e) { // ignore require errors } } } // helper for main-scope debug logging (used by wrappers) const pushMainDebug = (msg, level = 'info') => { if (opts.quiet && level !== 'error') return; if (!opts.debug && level === 'debug') return; const tag = `[${String(level || 'info').toUpperCase()}]`; const src = `[RUNNER]`; debugLog.push(`${tag} ${src} ${String(msg)}`); }; // If module exposes multiExtractor, wrap it to log providers when --debug const wrapMulti = (fn) => { const _orig = fn; return async function(providers) { if (opts && opts.debug) { try { pushMainDebug('[multiExtractor providers] ' + (typeof providers === 'object' ? JSON.stringify(providers) : String(providers)), 'debug'); } catch (e) { pushMainDebug('[multiExtractor providers] [unserializable]', 'debug'); } } return await _orig.apply(this, arguments); }; }; if (mod) { if (typeof mod.multiExtractor === 'function') mod.multiExtractor = wrapMulti(mod.multiExtractor); if (typeof mod === 'function' && mod.name === 'multiExtractor') mod = { multiExtractor: wrapMulti(mod) }; if (mod.default && typeof mod.default === 'function' && mod.default.name === 'multiExtractor') { mod.default = wrapMulti(mod.default); mod.multiExtractor = mod.default; } } let fn = mod[opts.function]; // also check default export if (!fn && mod.default && typeof mod.default === 'object') fn = mod.default[opts.function]; if (typeof fn !== 'function') { console.log(JSON.stringify({ status: 'error', message: `Function '${opts.function}' not found in module`, data: null, debug: debugLog })); process.exit(4); } let param = opts.param; // try parse JSON, fallback to string try { if (param) param = JSON.parse(param); } catch (e) { // leave as string } try { const maybeResult = fn.length === 0 ? fn() : fn(param); // Check if it's a Promise - handle cross-context Promises const result = (maybeResult && typeof maybeResult.then === 'function') ? await maybeResult : maybeResult; // Parse the result if it's a JSON string (modules return JSON.stringify'd data) let data = result; if (typeof result === 'string') { try { data = JSON.parse(result); } catch (e) { // if it's not valid JSON, keep it as string data = result; } } // Prepare debug output: if quiet -> empty; if debug -> all entries; otherwise filter out DEBUG-level entries let outDebug = []; if (opts.quiet) outDebug = []; else if (opts.debug) outDebug = debugLog; else outDebug = debugLog.filter(d => !String(d).startsWith('[DEBUG]')); console.log(JSON.stringify({ status: 'success', message: 'Fetch successful', data: data, debug: outDebug })); } catch (e) { console.log(JSON.stringify({ status: 'error', message: 'Error executing function: ' + e.message, data: null, debug: debugLog })); process.exit(5); } } main();