/** * Sub Store JS script: AmneziaWG/ProtonVPN (WG INI) -> Clash (wireguard proxy) * * Expected input: text like: * [Interface] * Address = ... * DNS = ... * PrivateKey = ... * ... * [Peer] * PublicKey = ... * PresharedKey = ... * AllowedIPs = ... * Endpoint = host:port * * Output: YAML fragment: * proxies: * - name: "..." * type: wireguard * ... */ function parseIniWG(text) { const lines = text .replace(/\r\n/g, "\n") .split("\n") .map((l) => l.trim()) .filter((l) => l.length > 0 && !l.startsWith("#") && !l.startsWith(";")); let section = null; const data = { Interface: {}, Peer: {} }; for (const line of lines) { const mSec = line.match(/^\[(.+?)\]$/); if (mSec) { section = mSec[1]; continue; } const mKV = line.match(/^([^=]+?)\s*=\s*(.+)$/); if (!mKV || !section) continue; const key = mKV[1].trim(); const value = mKV[2].trim(); if (section.toLowerCase() === "interface") data.Interface[key] = value; else if (section.toLowerCase() === "peer") data.Peer[key] = value; } return data; } function splitList(val) { // "a, b, c" -> ["a","b","c"] return String(val || "") .split(",") .map((s) => s.trim()) .filter(Boolean); } function parseEndpoint(endpoint) { // supports: host:port (IPv6 in brackets not handled here; can add if needed) const m = String(endpoint || "").match(/^(.+?):(\d+)$/); if (!m) return { host: "", port: 0 }; return { host: m[1], port: Number(m[2]) }; } function toNumIfPossible(v) { const s = String(v ?? "").trim(); if (s === "") return null; if (/^-?\d+$/.test(s)) return Number(s); return s; } function yamlEscapeString(s) { // simplest safe quoting const str = String(s ?? ""); return `"${str.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`; } function yamlScalar(v) { if (v === null || v === undefined) return "null"; if (typeof v === "number" && Number.isFinite(v)) return String(v); if (typeof v === "boolean") return v ? "true" : "false"; return yamlEscapeString(v); } function yamlInlineArray(arr) { const a = Array.isArray(arr) ? arr : []; return `[${a.map((x) => yamlScalar(x)).join(", ")}]`; } function buildClashProxyFromWG(wg, proxyName) { const i = wg.Interface || {}; const p = wg.Peer || {}; const address = i.Address || ""; const dnsList = splitList(i.DNS); const { host, port } = parseEndpoint(p.Endpoint); const allowed = splitList(p.AllowedIPs); // Amnezia fields (case-insensitive keys are NOT guaranteed in input, handle both) const pick = (k) => (i[k] !== undefined ? i[k] : i[k.toLowerCase()] !== undefined ? i[k.toLowerCase()] : undefined); const amzKeys = [ ["jc", pick("Jc")], ["jmin", pick("Jmin")], ["jmax", pick("Jmax")], ["s1", pick("S1")], ["s2", pick("S2")], ["h1", pick("H1")], ["h2", pick("H2")], ["h3", pick("H3")], ["h4", pick("H4")], ]; const amzObj = {}; for (const [k, v] of amzKeys) { if (v !== undefined) amzObj[k] = toNumIfPossible(v); } // Compose YAML (manual, predictable formatting) const name = proxyName && String(proxyName).trim() ? String(proxyName).trim() : "amz-wg"; let out = ""; out += "proxies:\n"; out += ` - name: ${yamlEscapeString(name)}\n`; out += ` type: wireguard\n`; out += ` ip: ${yamlScalar(address)}\n`; out += ` private-key: ${yamlScalar(i.PrivateKey || "")}\n`; out += ` peers:\n`; out += ` - server: ${yamlScalar(host)}\n`; out += ` port: ${yamlScalar(port)}\n`; out += ` public-key: ${yamlScalar(p.PublicKey || "")}\n`; if (p.PresharedKey) out += ` pre-shared-key: ${yamlScalar(p.PresharedKey)}\n`; out += ` allowed-ips: ${yamlInlineArray(allowed)}\n`; out += ` udp: true\n`; out += ` remote-dns-resolve: true\n`; if (dnsList.length) out += ` dns: ${yamlInlineArray(dnsList)}\n`; if (Object.keys(amzObj).length) { out += ` amnezia-wg-option:\n`; for (const k of Object.keys(amzObj)) { out += ` ${k}: ${yamlScalar(amzObj[k])}\n`; } } return out; } /** * Sub Store entrypoint. * Most Sub Store scripts expose a function like: * module.exports = async (ctx) => { ... } * where ctx has raw subscription text in ctx.content / ctx.body / etc. * * To be robust across environments, we try common fields. */ module.exports = async (ctx) => { const input = (ctx && (ctx.content || ctx.body || ctx.data || ctx.text)) ?? ""; const wg = parseIniWG(String(input)); // Try to derive a name if Sub Store provides something (optional) // Fallback: "amz-wg" const derivedName = (ctx && (ctx.name || ctx.profileName || ctx.filename)) || "amz-wg"; const yaml = buildClashProxyFromWG(wg, derivedName); // Return in whatever field Sub Store expects // Common patterns: return string, or set ctx.content if (ctx) ctx.content = yaml; return yaml; };