const fs = require('fs'); const CRED_KEYS = new Set(['password', 'apiKey', 'token', 'cookie', 'sessionId']); const REDACTED = ''; function sanitizeConfig(config) { if (!config || typeof config !== 'object') return config; const clone = JSON.parse(JSON.stringify(config)); (function walk(o) { if (!o) return; if (Array.isArray(o)) { for (const e of o) walk(e); return; } if (typeof o !== 'object') return; for (const k of Object.keys(o)) { if (CRED_KEYS.has(k) && typeof o[k] === 'string' && o[k]) o[k] = REDACTED; else walk(o[k]); } })(clone); return clone; } function collectFile(filePath, label, maxBytes) { if (!filePath) return `=== ${label} ===\n\n\n`; let stat; try { stat = fs.statSync(filePath); } catch (err) { if (err && err.code === 'ENOENT') return `=== ${label} (${filePath}) ===\n\n\n`; return `=== ${label} (${filePath}) ===\n\n\n`; } const cap = Number.isFinite(maxBytes) && maxBytes > 0 ? maxBytes : 5 * 1024 * 1024; let content; try { if (stat.size > cap) { const fd = fs.openSync(filePath, 'r'); const buf = Buffer.alloc(cap); fs.readSync(fd, buf, 0, cap, stat.size - cap); fs.closeSync(fd); const skipped = stat.size - cap; content = `\n` + buf.toString('utf-8'); } else { content = fs.readFileSync(filePath, 'utf-8'); } } catch (err) { content = ``; } return `=== ${label} (${filePath}, size=${stat.size} bytes) ===\n${content}\n\n`; } function buildSupportBundleText({ header, sanitizedConfig, files }) { const parts = []; parts.push('=== Multi-Hoster-Upload Support Bundle ===\n'); if (header && typeof header === 'object') { for (const [k, v] of Object.entries(header)) parts.push(`${k}: ${v}\n`); } parts.push('\n'); parts.push('=== Config (sanitized — password/apiKey/token/cookie/sessionId redacted) ===\n'); parts.push(JSON.stringify(sanitizedConfig, null, 2)); parts.push('\n\n'); for (const f of (files || [])) { parts.push(collectFile(f.path, f.label || f.path, f.maxBytes)); } return parts.join(''); } module.exports = { sanitizeConfig, collectFile, buildSupportBundleText, CRED_KEYS, REDACTED };