Files
clash-rules/config-sub-converter/scripts/convert-awg-to-clash.js

254 lines
6.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**********************
* Defaults (AmneziaWG)
* Если в исходнике нет параметра, берём отсюда.
* Если итоговое значение == 0, параметр пропускаем в amnezia-wg-option.
**********************/
const AMZ_DEFAULTS = {
Jc: 3,
Jmin: 10,
Jmax: 50,
S1: 0,
S2: 0,
H1: 0,
H2: 0,
H3: 0,
H4: 0,
};
/**********************
* Options from Sub Store
* Example URL:
* .../convert-awg-to-clash.js#dns=false&ipv6=false#noCache
*
* Требования:
* - dns=false => remote-dns-resolve: false (вне зависимости от входа)
* - ipv6=false => удалить IPv6 из allowed-ips (и вообще не добавлять ipv6-части)
**********************/
function normalizeOptions(rawOptions) {
// rawOptions может быть
// 1) {}
// 2) { meta: { url: "..." } }
// 3) { url: "..." }
const urlStr =
(rawOptions && rawOptions.meta && rawOptions.meta.url) ||
rawOptions.url ||
"";
// Ищем part после #
const hashIdx = urlStr.indexOf("#");
const qstr = hashIdx >= 0 ? urlStr.slice(hashIdx + 1) : "";
const params = {};
for (const part of qstr.split("&")) {
const [k, v] = part.split("=");
if (!k) continue;
params[k.trim().toLowerCase()] = (v ?? "").trim();
}
const asBool = (v, def) => {
if (v === undefined || v === "") return def;
const s = String(v).toLowerCase();
if (["1","true","yes","on"].includes(s)) return true;
if (["0","false","no","off"].includes(s)) return false;
return def;
};
return {
dns: asBool(params.dns, true),
ipv6: asBool(params.ipv6, true),
};
}
/**********************
* Parsing WG INI blocks
**********************/
function cleanLines(text) {
return String(text ?? "")
.replace(/\r\n/g, "\n")
.split("\n");
}
// Парсим один INI-фрагмент с [Interface] и [Peer] (один peer)
function parseIniOne(text) {
const lines = cleanLines(text)
.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 === "Interface") data.Interface[key] = value;
else if (section === "Peer") data.Peer[key] = value;
}
return data;
}
// Разбиваем весь файл на блоки по заголовкам "##### ..."
function splitByHeaders(fullText) {
const lines = cleanLines(fullText);
const blocks = [];
let current = { name: "amz-wg", buf: [] };
const headerRe = /^#{5}\s*(.+)\s*$/;
for (const line of lines) {
const mh = line.match(headerRe);
if (mh) {
// закрываем предыдущий блок, если там что-то есть
if (current.buf.join("\n").trim().length > 0) blocks.push(current);
current = { name: mh[1].trim(), buf: [] };
continue;
}
current.buf.push(line);
}
if (current.buf.join("\n").trim().length > 0) blocks.push(current);
return blocks;
}
function splitList(val) {
return String(val || "")
.split(",")
.map((s) => s.trim())
.filter(Boolean);
}
function parseEndpoint(endpoint) {
// Поддержка:
// - host:port
// - [ipv6]:port
const s = String(endpoint || "").trim();
const v6 = s.match(/^\[(.+?)\]:(\d+)$/);
if (v6) return { host: v6[1], port: Number(v6[2]) };
const v4 = s.match(/^(.+?):(\d+)$/);
if (v4) return { host: v4[1], port: Number(v4[2]) };
return { host: "", port: 0 };
}
function toNumberOrNull(v) {
const s = String(v ?? "").trim();
if (s === "") return null;
if (/^-?\d+$/.test(s)) return Number(s);
return null;
}
function buildAmzOptions(interfaceObj) {
// Правило:
// - если в файле есть параметр => используем его
// - иначе берём из AMZ_DEFAULTS
// - если итог == 0 => пропускаем
const out = {};
const keys = Object.keys(AMZ_DEFAULTS);
for (const K of keys) {
const fromFile = interfaceObj[K];
const fileNum = toNumberOrNull(fromFile);
const fallback = AMZ_DEFAULTS[K];
const finalVal =
fileNum !== null ? fileNum : (fallback ?? 0);
if (Number(finalVal) !== 0) {
out[K.toLowerCase()] = Number(finalVal);
}
}
return out;
}
function filterAllowedIPs(allowed, enableIPv6) {
if (enableIPv6) return allowed;
// выкидываем всё, что похоже на IPv6
return allowed.filter((cidr) => !cidr.includes(":"));
}
function buildProxy(blockName, wg, options) {
const i = wg.Interface || {};
const p = wg.Peer || {};
const address = i.Address || "";
const dnsList = splitList(i.DNS);
const ep = parseEndpoint(p.Endpoint);
let allowed = splitList(p.AllowedIPs);
allowed = filterAllowedIPs(allowed, options.ipv6);
const proxy = {
name: blockName || "amz-wg",
type: "wireguard",
ip: address,
// ipv6 поле в твоём примере закомментировано, так что не добавляем вообще
"private-key": i.PrivateKey || "",
peers: [
{
server: ep.host,
port: ep.port,
"public-key": p.PublicKey || "",
...(p.PresharedKey ? { "pre-shared-key": p.PresharedKey } : {}),
"allowed-ips": allowed,
},
],
udp: true,
// dns=false => принудительно false
"remote-dns-resolve": options.dns ? true : false,
...(dnsList.length ? { dns: dnsList } : {}),
};
const amz = buildAmzOptions(i);
if (Object.keys(amz).length) {
proxy["amnezia-wg-option"] = amz;
}
return proxy;
}
/**********************
* ENTRYPOINT
**********************/
const opts = normalizeOptions($options);
// Вход: чаще всего $content, но на всякий пожарный берём $files[0]
const input = String($content ?? ($files && $files[0]) ?? "");
// Разбиваем по заголовкам ##### ...
const blocks = splitByHeaders(input);
// Для каждого блока парсим INI и строим proxy
const proxies = [];
for (const b of blocks) {
const iniText = b.buf.join("\n").trim();
if (!iniText) continue;
const wg = parseIniOne(iniText);
// минимальная валидация: нужны ключи
if (!wg.Interface?.PrivateKey || !wg.Peer?.PublicKey || !wg.Peer?.Endpoint) {
// пропускаем мусорные блоки, чтобы не ронять весь конвертер
continue;
}
proxies.push(buildProxy(b.name, wg, opts));
}
// Финальный YAML
$content = ProxyUtils.yaml.safeDump({ proxies });