Проверено настоящим подключением (3X-UI 3.8.5, Xray 26.6.27, Mihomo 1.19.31, sing-box 1.14.2, amneziawg-tools 3.1): протокол Xray Mihomo sing-box VLESS REALITY ✓ ✓ ✓ VLESS XHTTP REALITY ✓ ✓ — VLESS WS + TLS ✓ ✓ ✓ Trojan gRPC + TLS ✓ ✓ ✓ VMess WS + TLS ✓ ✓ ✓ Shadowsocks 2022 ✓ ✓ ✓ Hysteria2 ✓ ✓ ✓ TUIC v5 — ✓ ✓ WireGuard ✓ ✓ ✓ AmneziaWG классика — ✓ — (официальный клиент 3.1: ✗) AmneziaWG 3.1 — ✗ — (официальный клиент 3.1: ✓) MTProto mtg doctor: все DC Telegram доступны - Генератор Xray: allowInsecure удалён в Xray 26 и ронял конфиг — теперь отпечаток сертификата (pcs/pinSHA256) или предупреждение на странице - Генераторы: Hysteria2 и WireGuard для Xray; TUIC, WireGuard, AmneziaWG для Mihomo - tools/lib/singbox.js — сборка конфигов sing-box (для будущей единой подписки) - tests/matrix — стенд матрицы для прогона на каждой версии
94 lines
5.2 KiB
JavaScript
94 lines
5.2 KiB
JavaScript
// Сборка конфига sing-box (Hiddify, Karing, NekoBox, sing-box) из разобранных ссылок.
|
|
(function (root) {
|
|
'use strict';
|
|
|
|
function clean(o) {
|
|
Object.keys(o).forEach((k) => (o[k] === undefined || o[k] === '' || (Array.isArray(o[k]) && !o[k].length)) && delete o[k]);
|
|
return o;
|
|
}
|
|
|
|
function tls(p) {
|
|
const s = p.tls;
|
|
if (!s || s.security === 'none') return undefined;
|
|
const t = clean({ enabled: true, server_name: s.sni, alpn: s.alpn, insecure: s.insecure || undefined });
|
|
if (s.fp) t.utls = { enabled: true, fingerprint: s.fp };
|
|
if (s.security === 'reality') t.reality = clean({ enabled: true, public_key: s.pbk, short_id: s.sid });
|
|
return t;
|
|
}
|
|
|
|
function transport(p) {
|
|
const t = p.transport;
|
|
if (!t || t.network === 'tcp') {
|
|
if (t && t.headerType === 'http') return clean({ type: 'http', path: t.path, host: t.host ? [t.host] : undefined });
|
|
return undefined;
|
|
}
|
|
if (t.network === 'ws') {
|
|
const o = { type: 'ws', path: t.path };
|
|
if (t.host) o.headers = { Host: t.host };
|
|
return o;
|
|
}
|
|
if (t.network === 'httpupgrade') return clean({ type: 'httpupgrade', path: t.path, host: t.host });
|
|
if (t.network === 'grpc') return { type: 'grpc', service_name: t.serviceName };
|
|
throw new Error('sing-box не поддерживает транспорт ' + t.network);
|
|
}
|
|
|
|
// Возвращает { outbound } или { endpoint } (WireGuard в sing-box — endpoint).
|
|
function convert(p) {
|
|
const base = { tag: p.name, server: p.server, server_port: p.port };
|
|
switch (p.type) {
|
|
case 'vless':
|
|
return { outbound: clean(Object.assign(base, { type: 'vless', uuid: p.uuid, flow: p.flow, tls: tls(p), transport: transport(p) })) };
|
|
case 'vmess':
|
|
return { outbound: clean(Object.assign(base, { type: 'vmess', uuid: p.uuid, security: p.cipher, alter_id: p.alterId, tls: tls(p), transport: transport(p) })) };
|
|
case 'trojan':
|
|
return { outbound: clean(Object.assign(base, { type: 'trojan', password: p.password, tls: tls(p), transport: transport(p) })) };
|
|
case 'ss':
|
|
return { outbound: Object.assign(base, { type: 'shadowsocks', method: p.method, password: p.password }) };
|
|
case 'hysteria2': {
|
|
// sing-box не умеет закреплять отпечаток сертификата — при pinSHA256 проверку отключаем.
|
|
const t = clean({ enabled: true, server_name: p.sni, alpn: p.alpn, insecure: (p.insecure || !!p.pinSHA256) || undefined });
|
|
const o = clean(Object.assign(base, { type: 'hysteria2', password: p.password, tls: t }));
|
|
if (p.obfs) o.obfs = { type: p.obfs, password: p.obfsPassword };
|
|
return { outbound: o };
|
|
}
|
|
case 'tuic':
|
|
return { outbound: clean(Object.assign(base, { type: 'tuic', uuid: p.uuid, password: p.password, congestion_control: p.congestion,
|
|
udp_relay_mode: p.udpRelay, tls: clean({ enabled: true, server_name: p.sni, alpn: p.alpn, insecure: p.insecure || undefined }) })) };
|
|
case 'wireguard':
|
|
return { endpoint: clean({ type: 'wireguard', tag: p.name, address: p.address.map((a) => a + (a.includes(':') ? '/128' : '/32')),
|
|
private_key: p.privateKey, mtu: p.mtu,
|
|
peers: [clean({ address: p.server, port: p.port, public_key: p.publicKey, pre_shared_key: p.preSharedKey, allowed_ips: ['0.0.0.0/0', '::/0'], reserved: p.reserved || undefined })] }) };
|
|
case 'amneziawg':
|
|
throw new Error('sing-box не поддерживает AmneziaWG');
|
|
default:
|
|
throw new Error('sing-box не поддерживает ' + p.type);
|
|
}
|
|
}
|
|
|
|
// opts: { inbound: {type:'mixed', port} } — для тестов и десктопных клиентов.
|
|
function build(proxies, opts) {
|
|
opts = opts || {};
|
|
const outbounds = [], endpoints = [], skipped = [];
|
|
proxies.filter((p) => p.type !== 'mtproto').forEach((p) => {
|
|
try {
|
|
const r = convert(p);
|
|
if (r.outbound) outbounds.push(r.outbound); else endpoints.push(r.endpoint);
|
|
} catch (e) { skipped.push({ name: p.name, error: e.message }); }
|
|
});
|
|
const tags = outbounds.map((o) => o.tag).concat(endpoints.map((e) => e.tag));
|
|
if (!tags.length) throw new Error(skipped.length ? skipped[0].error : 'Нет подходящих серверов для sing-box.');
|
|
const cfg = { log: { level: 'warn' } };
|
|
if (opts.inbound) cfg.inbounds = [{ type: 'mixed', tag: 'in', listen: '127.0.0.1', listen_port: opts.inbound.port }];
|
|
cfg.outbounds = [{ type: 'selector', tag: 'Прокси', outbounds: ['Авто'].concat(tags) },
|
|
{ type: 'urltest', tag: 'Авто', outbounds: tags, url: 'https://www.gstatic.com/generate_204', interval: '5m' }]
|
|
.concat(outbounds, [{ type: 'direct', tag: 'direct' }]);
|
|
if (endpoints.length) cfg.endpoints = endpoints;
|
|
cfg.route = { final: opts.final || 'Прокси', auto_detect_interface: true };
|
|
return { config: cfg, skipped, count: tags.length };
|
|
}
|
|
|
|
const api = { buildSingbox: build, singboxConvert: convert };
|
|
if (typeof module === 'object' && module.exports) module.exports = api;
|
|
root.PM = Object.assign(root.PM || {}, api);
|
|
})(typeof window !== 'undefined' ? window : globalThis);
|