✨ Sync 2026-10-11 20:48:25

This commit is contained in:
github-actions[bot] committed 2026-10-11 20:48:25 +08:00
1 parent 78f0ba8251
commit 5ce75c9f2b
45 files changed
+54 -879

No files matched your search

@@ -26,11 +26,9 @@ function parseShareLink(uri, features) {
if (uri[0] && uri[1]) {
switch (uri[0]) {
case 'anytls':
/* https://github.com/anytls/anytls-go/blob/v0.0.8/docs/uri_scheme.md */
url = new URL('http://' + uri[1]);
params = url.searchParams;
/* Check if password exists */
if (!url.username)
return null;
@@ -62,11 +60,9 @@ function parseShareLink(uri, features) {
break;
case 'hysteria':
/* https://github.com/HyNetwork/hysteria/wiki/URI-Scheme */
url = new URL('http://' + uri[1]);
params = url.searchParams;
/* WeChat-Video / FakeTCP are unsupported by sing-box currently */
if (!features.with_quic || (params.get('protocol') && params.get('protocol') !== 'udp'))
return null;
@@ -90,7 +86,6 @@ function parseShareLink(uri, features) {
break;
case 'hysteria2':
case 'hy2':
/* https://v2.hysteria.network/docs/developers/URI-Scheme/ */
url = new URL('http://' + uri[1]);
params = url.searchParams;
@@ -114,7 +109,6 @@ function parseShareLink(uri, features) {
break;
case 'snell':
/* Surge Snell share link: snell://host:port?psk=..&obfs=http&obfs-host=..#name */
url = new URL('http://' + uri[1]);
params = url.searchParams;
@@ -153,7 +147,6 @@ function parseShareLink(uri, features) {
break;
case 'ss':
try {
/* "Lovely" Shadowrocket format */
try {
let suri = uri[1].split('#'), slabel = '';
if (suri.length <= 2) {
@@ -163,15 +156,12 @@ function parseShareLink(uri, features) {
}
} catch(e) { }
/* SIP002 format https://shadowsocks.org/guide/sip002.html */
url = new URL('http://' + uri[1]);
let userinfo;
if (url.username && url.password) {
/* User info encoded with URIComponent */
userinfo = [url.username, decodeURIComponent(url.password)];
} else if (url.username) {
/* User info encoded with base64 */
userinfo = hp.decodeBase64Str(decodeURIComponent(url.username)).split(':');
if (userinfo.length > 1)
userinfo = [userinfo[0], userinfo.slice(1).join(':')]
@@ -184,9 +174,6 @@ function parseShareLink(uri, features) {
if (url.search && url.searchParams.get('plugin')) {
let plugin_info = url.searchParams.get('plugin').split(';');
plugin = plugin_info[0];
/* sing-box only knows obfs-local; simple-obfs (an old
alias providers still emit) would otherwise be rejected
as "plugin not found" and take the whole config with it */
if (plugin === 'simple-obfs')
plugin = 'obfs-local';
plugin_opts = (plugin_info.length > 1) ? plugin_info.slice(1).join(';') : null;
@@ -203,7 +190,6 @@ function parseShareLink(uri, features) {
shadowsocks_plugin_opts: plugin_opts
};
} catch(e) {
/* Legacy format https://github.com/shadowsocks/shadowsocks-org/commit/78ca46cd6859a4e9475953ed34a2d301454f579e */
uri = uri[1].split('@');
if (uri.length < 2)
return null;
@@ -221,11 +207,9 @@ function parseShareLink(uri, features) {
break;
case 'trojan':
/* https://p4gefau1t.github.io/trojan-go/developer/url/ */
url = new URL('http://' + uri[1]);
params = url.searchParams;
/* Check if password exists */
if (!url.username)
return null;
@@ -256,11 +240,9 @@ function parseShareLink(uri, features) {
break;
case 'tuic':
/* https://github.com/daeuniverse/dae/discussions/182 */
url = new URL('http://' + uri[1]);
params = url.searchParams;
/* Check if uuid exists */
if (!url.username)
return null;
@@ -280,16 +262,13 @@ function parseShareLink(uri, features) {
break;
case 'vless':
/* https://github.com/XTLS/Xray-core/discussions/716 */
url = new URL('http://' + uri[1]);
params = url.searchParams;
/* Unsupported protocol */
if (params.get('type') === 'kcp')
return null;
else if (params.get('type') === 'quic' && ((params.get('quicSecurity') && params.get('quicSecurity') !== 'none') || !features.with_quic))
return null;
/* Check if uuid and type exist */
if (!url.username || !params.get('type'))
return null;
@@ -307,9 +286,6 @@ function parseShareLink(uri, features) {
tls_reality_public_key: params.get('pbk') ? decodeURIComponent(params.get('pbk')) : null,
tls_reality_short_id: params.get('sid'),
tls_utls: features.with_utls ? params.get('fp') : null,
/* only the value sing-box supports: any other flow is
rejected with "unsupported flow" and one bad node breaks
the whole generated config */
vless_flow: (['tls', 'reality'].includes(params.get('security')) && params.get('flow') === 'xtls-rprx-vision') ? params.get('flow') : null
};
switch (params.get('type')) {
@@ -340,26 +316,17 @@ function parseShareLink(uri, features) {
break;
case 'vmess':
/* "Lovely" shadowrocket format; uri is the split scheme/rest
array, so this has to test the link body (testing the array
itself compared '&' against its elements and never matched) */
if (uri[1].includes('&'))
return null;
/* https://github.com/2dust/v2rayN/wiki/Description-of-VMess-share-link */
uri = JSON.parse(hp.decodeBase64Str(uri[1]));
if (uri.v != '2')
return null;
/* Unsupported protocols */
else if (uri.net === 'kcp')
return null;
else if (uri.net === 'quic' && ((uri.type && uri.type !== 'none') || !features.with_quic))
return null;
/* https://www.v2fly.org/config/protocols/vmess.html#vmess-md5-%E8%AE%A4%E8%AF%81%E4%BF%A1%E6%81%AF-%E6%B7%98%E6%B1%B0%E6%9C%BA%E5%88%B6
* else if (uri.aid && parseInt(uri.aid) !== 0)
* return null;
*/
config = {
label: uri.ps,
@@ -524,7 +491,6 @@ function renderNodeSettings(section, data, features, main_node, routing_mode) {
}
o.modalonly = true;
/* Direct config */
o = s.option(form.ListValue, 'proxy_protocol', _('Proxy protocol'),
_('Write proxy protocol in the connection header.'));
o.value('', _('Disable'));
@@ -533,7 +499,6 @@ function renderNodeSettings(section, data, features, main_node, routing_mode) {
o.depends('type', 'direct');
o.modalonly = true;
/* AnyTLS config start */
o = s.option(form.Value, 'anytls_idle_session_check_interval', _('Idle session check interval'),
_('Interval checking for idle sessions, in seconds.'));
o.datatype = 'uinteger';
@@ -554,9 +519,7 @@ function renderNodeSettings(section, data, features, main_node, routing_mode) {
o.placeholder = '0';
o.depends('type', 'anytls');
o.modalonly = true;
/* AnyTLS config end */
/* Hysteria (2) config start */
o = s.option(form.DynamicList, 'hysteria_hopping_port', _('Hopping port'));
o.depends('type', 'hysteria');
o.depends('type', 'hysteria2');
@@ -580,10 +543,6 @@ function renderNodeSettings(section, data, features, main_node, routing_mode) {
o = s.option(form.ListValue, 'hysteria_protocol', _('Protocol'));
o.value('udp');
/* WeChat-Video / FakeTCP are unsupported by sing-box currently
* o.value('wechat-video');
* o.value('faketcp');
*/
o.default = 'udp';
o.depends('type', 'hysteria');
o.rmempty = false;
@@ -655,13 +614,10 @@ function renderNodeSettings(section, data, features, main_node, routing_mode) {
_('Disable Chrome QUIC handshake parroting, which is enabled by default since sing-box 1.14. Turn this on only when the server uses an Ed25519 certificate or the handshake otherwise fails.'));
o.depends('type', 'hysteria2');
o.modalonly = true;
/* Hysteria (2) config end */
/* Shadowsocks config start */
o = s.option(form.ListValue, 'shadowsocks_encrypt_method', _('Encrypt method'));
for (let i of hp.shadowsocks_encrypt_methods)
o.value(i);
/* Stream ciphers */
o.value('aes-128-ctr');
o.value('aes-192-ctr');
o.value('aes-256-ctr');
@@ -687,9 +643,7 @@ function renderNodeSettings(section, data, features, main_node, routing_mode) {
o.depends('shadowsocks_plugin', 'obfs-local');
o.depends('shadowsocks_plugin', 'v2ray-plugin');
o.modalonly = true;
/* Shadowsocks config end */
/* ShadowTLS config */
o = s.option(form.ListValue, 'shadowtls_version', _('ShadowTLS version'));
o.value('1', _('v1'));
o.value('2', _('v2'));
@@ -699,7 +653,6 @@ function renderNodeSettings(section, data, features, main_node, routing_mode) {
o.rmempty = false;
o.modalonly = true;
/* Socks config */
o = s.option(form.ListValue, 'socks_version', _('Socks version'));
o.value('4', _('Socks4'));
o.value('4a', _('Socks4A'));
@@ -709,7 +662,6 @@ function renderNodeSettings(section, data, features, main_node, routing_mode) {
o.rmempty = false;
o.modalonly = true;
/* SSH config start */
o = s.option(form.Value, 'ssh_client_version', _('Client version'),
_('Random version will be used if empty.'));
o.depends('type', 'ssh');
@@ -733,9 +685,7 @@ function renderNodeSettings(section, data, features, main_node, routing_mode) {
o.password = true;
o.depends('type', 'ssh');
o.modalonly = true;
/* SSH config end */
/* TUIC config start */
o = s.option(form.Value, 'uuid', _('UUID'));
o.password = true;
o.depends('type', 'tuic');
@@ -779,9 +729,7 @@ function renderNodeSettings(section, data, features, main_node, routing_mode) {
o.default = '10';
o.depends('type', 'tuic');
o.modalonly = true;
/* Tuic config end */
/* VMess / VLESS config start */
o = s.option(form.ListValue, 'vless_flow', _('Flow'));
o.value('', _('None'));
o.value('xtls-rprx-vision');
@@ -816,13 +764,9 @@ function renderNodeSettings(section, data, features, main_node, routing_mode) {
_('Protocol parameter. Enable length block encryption.'));
o.depends('type', 'vmess');
o.modalonly = true;
/* VMess config end */
/* Transport config start */
hp.renderTransportOptions(s, { features: features, side: 'client' });
/* Transport config end */
/* Wireguard config start */
o = s.option(form.DynamicList, 'wireguard_local_address', _('Local address'),
_('List of IP (v4 or v6) addresses prefixes to be assigned to the interface.'));
o.datatype = 'cidr';
@@ -868,9 +812,7 @@ function renderNodeSettings(section, data, features, main_node, routing_mode) {
o.datatype = 'uinteger';
o.depends('type', 'wireguard');
o.modalonly = true;
/* Wireguard config end */
/* Mux config start */
o = s.option(form.Flag, 'multiplex', _('Multiplex'));
o.depends('type', 'shadowsocks');
o.depends('type', 'trojan');
@@ -927,9 +869,7 @@ function renderNodeSettings(section, data, features, main_node, routing_mode) {
o.datatype = 'uinteger';
o.depends('multiplex_brutal', '1');
o.modalonly = true;
/* Mux config end */
/* Snell config start */
o = s.option(form.ListValue, 'snell_version', _('Snell version'),
_('sing-box implements Snell v4/v5 wire as v4 and v6. The pre-shared key (Password above) must be 12-255 bytes for v6.'));
o.value('4', _('v4'));
@@ -967,9 +907,7 @@ function renderNodeSettings(section, data, features, main_node, routing_mode) {
o.value('unsafe-raw', _('unsafe-raw'));
o.depends({'type': 'snell', 'snell_version': '6'});
o.modalonly = true;
/* Snell config end */
/* TLS config start */
hp.renderTlsOptions(s, {
side: 'client',
type_depends: [ 'anytls', 'http', 'hysteria', 'hysteria2', 'shadowtls', 'trojan', 'tuic', 'vless', 'vmess' ],
@@ -1063,9 +1001,7 @@ function renderNodeSettings(section, data, features, main_node, routing_mode) {
o.depends('tls_reality', '1');
o.modalonly = true;
}
/* TLS config end */
/* Extra settings start */
o = s.option(form.Flag, 'tcp_fast_open', _('TCP fast open'));
o.modalonly = true;
@@ -1088,7 +1024,6 @@ function renderNodeSettings(section, data, features, main_node, routing_mode) {
o.default = '2';
o.depends('udp_over_tcp', '1');
o.modalonly = true;
/* Extra settings end */
return s;
}
@@ -1107,7 +1042,6 @@ return view.extend({
let routing_mode = uci.get(data[0], 'config', 'routing_mode');
let features = data[1];
/* Cache subscription information, it will be called multiple times */
let subinfo = [];
for (let suburl of (uci.get(data[0], 'subscription', 'subscription_url') || [])) {
const url = new URL(suburl);
@@ -1120,8 +1054,6 @@ return view.extend({
s = m.section(form.NamedSection, 'subscription', 'homeproxy');
/* Node settings start */
/* User nodes start */
s.tab('node', _('Nodes'));
o = s.taboption('node', form.SectionValue, '_node', form.GridSection, 'node');
ss = renderNodeSettings(o.subsection, data, features, main_node, routing_mode);
@@ -1133,8 +1065,6 @@ return view.extend({
return true;
}
/* Import subscription links start */
/* Thanks to luci-app-shadowsocks-libev */
ss.handleLinkImport = function() {
let textarea = new ui.Textarea();
ui.showModal(_('Import share links'), [
@@ -1151,7 +1081,6 @@ return view.extend({
click: ui.createHandlerFn(this, () => {
let input_links = textarea.getValue().trim().split('\n');
if (input_links && input_links[0]) {
/* Remove duplicate lines */
input_links = input_links.reduce((pre, cur) =>
(!pre.includes(cur) && pre.push(cur), pre), []);
@@ -1163,9 +1092,6 @@ return view.extend({
try {
config = parseShareLink(l, features);
} catch (e) {
/* a single unparsable link must not abort
the whole import (the changes made so
far were left unsaved before) */
config = null;
}
if (config) {
@@ -1177,10 +1103,6 @@ return view.extend({
let nameHash = hp.calcStringMD5(config.label);
let previous = uci.get(data[0], nameHash) || {};
let sid = uci.add(data[0], 'node', nameHash);
/* drop options the node no longer has:
uci.set() only writes the keys below,
so re-importing a changed protocol
used to leave the old fields behind */
Object.keys(previous).forEach((k) => {
if (k.charAt(0) !== '.' && !(k in config))
uci.unset(data[0], sid, k);
@@ -1239,10 +1161,7 @@ return view.extend({
return el;
}
/* Import subscription links end */
/* User nodes end */
/* Subscription nodes start */
for (const info of subinfo) {
s.tab('sub_' + info.hash, _('Sub (%s)').format(info.title));
o = s.taboption('sub_' + info.hash, form.SectionValue, '_sub_' + info.hash, form.GridSection, 'node');
@@ -1251,10 +1170,7 @@ return view.extend({
return (uci.get(data[0], section_id, 'grouphash') === info.hash);
}
}
/* Subscription nodes end */
/* Node settings end */
/* Subscriptions settings start */
s.tab('subscription', _('Subscriptions'));
o = s.taboption('subscription', form.Flag, 'auto_update', _('Auto update'),
@@ -1383,7 +1299,6 @@ return view.extend({
return this.map.save(null, true);
}
/* Subscriptions settings end */
return m.render();
}