Add subscription node prefixes

This commit is contained in:
ushan0v
2026-07-11 00:10:57 +03:00
parent 53b49cac06
commit 584f72f503
17 changed files with 1133 additions and 934 deletions
@@ -215,6 +215,7 @@ const legacySubSource = childObjects(legacySub, subscriptionUrls)[0];
assert(legacySubSource.user_agent === 'Agent/1.0', 'subscription user-agent setting');
assert(legacySubSource.auto_user_agent === '0', 'subscription user-agent manual mode');
assert(legacySubSource.auto_hwid === '1', 'subscription HWID auto-generation');
assert(legacySubSource.prefix_nodes === '0', 'subscription node prefix disabled by default');
assert(legacySubSource.include_urltest_groups === '1', 'subscription URLTest groups import default');
assert(legacySubSource.hide_urltest_group_outbounds === '1', 'subscription URLTest group member hiding default');
assert(legacySubSource.hide_detour_outbounds === '1', 'subscription detour hiding default');
@@ -314,6 +314,7 @@ cat >"$WORK_DIR/sing-box-signature.json" <<'JSON'
"action": "proxy",
"selector_proxy_links": [ "vless://one", "vless://two" ],
"subscription_urls": [ "https://example.com/sub.txt" ],
"subscription_url_settings": "{\"https://example.com/sub.txt\":{\"prefix_nodes\":\"1\",\"node_prefix\":\"Example\"}}",
"urltest_enabled": "1",
"detect_server_country": "1",
"urltest_tolerance": "75",
@@ -432,7 +433,7 @@ proxy
[rule.proxy1.connection_urls]
[ "vless://one", "vless://two" ]
[rule.proxy1.subscription_urls]
[ { "url": "https://example.com/sub.txt", "subscription_update_enabled": "1", "subscription_update_interval": "1h", "download_via_proxy_section": "", "auto_user_agent": "1", "user_agent": "", "auto_hwid": "1", "hwid": "", "show_dashboard_metadata": "1", "include_urltest_groups": "1", "hide_urltest_group_outbounds": "1", "hide_detour_outbounds": "1" } ]
[ { "url": "https://example.com/sub.txt", "subscription_update_enabled": "1", "subscription_update_interval": "1h", "download_via_proxy_section": "", "auto_user_agent": "1", "user_agent": "", "auto_hwid": "1", "hwid": "", "show_dashboard_metadata": "1", "prefix_nodes": "1", "node_prefix": "Example", "include_urltest_groups": "1", "hide_urltest_group_outbounds": "1", "hide_detour_outbounds": "1" } ]
[rule.proxy1.interfaces]
[ ]
[rule.proxy1.outbound_jsons]
@@ -1136,11 +1137,11 @@ local.lst https://example.com/mixed.lst
[lists.urltest_custom.domain_ip_lists]
[subscription.list_proxy.subscription_urls]
[ { "url": "https://example.com/sub1.txt", "subscription_update_enabled": "1", "subscription_update_interval": "1h", "download_via_proxy_section": "", "auto_user_agent": "1", "user_agent": "", "auto_hwid": "1", "hwid": "", "show_dashboard_metadata": "1", "include_urltest_groups": "1", "hide_urltest_group_outbounds": "1", "hide_detour_outbounds": "1" }, { "url": "https://example.com/sub2.txt", "subscription_update_enabled": "1", "subscription_update_interval": "1h", "download_via_proxy_section": "", "auto_user_agent": "1", "user_agent": "", "auto_hwid": "1", "hwid": "", "show_dashboard_metadata": "1", "include_urltest_groups": "1", "hide_urltest_group_outbounds": "1", "hide_detour_outbounds": "1" } ]
[ { "url": "https://example.com/sub1.txt", "subscription_update_enabled": "1", "subscription_update_interval": "1h", "download_via_proxy_section": "", "auto_user_agent": "1", "user_agent": "", "auto_hwid": "1", "hwid": "", "show_dashboard_metadata": "1", "prefix_nodes": "0", "node_prefix": "", "include_urltest_groups": "1", "hide_urltest_group_outbounds": "1", "hide_detour_outbounds": "1" }, { "url": "https://example.com/sub2.txt", "subscription_update_enabled": "1", "subscription_update_interval": "1h", "download_via_proxy_section": "", "auto_user_agent": "1", "user_agent": "", "auto_hwid": "1", "hwid": "", "show_dashboard_metadata": "1", "prefix_nodes": "0", "node_prefix": "", "include_urltest_groups": "1", "hide_urltest_group_outbounds": "1", "hide_detour_outbounds": "1" } ]
[subscription.list_proxy.subscription_update_interval]
1h
[subscription.sub_paused.subscription_urls]
[ { "url": "https://example.com/off.txt", "subscription_update_enabled": "0", "subscription_update_interval": "1h", "download_via_proxy_section": "", "auto_user_agent": "1", "user_agent": "", "auto_hwid": "1", "hwid": "", "show_dashboard_metadata": "1", "include_urltest_groups": "1", "hide_urltest_group_outbounds": "1", "hide_detour_outbounds": "1" } ]
[ { "url": "https://example.com/off.txt", "subscription_update_enabled": "0", "subscription_update_interval": "1h", "download_via_proxy_section": "", "auto_user_agent": "1", "user_agent": "", "auto_hwid": "1", "hwid": "", "show_dashboard_metadata": "1", "prefix_nodes": "0", "node_prefix": "", "include_urltest_groups": "1", "hide_urltest_group_outbounds": "1", "hide_detour_outbounds": "1" } ]
[subscription.sub_paused.subscription_update_interval]
EOF_CRON_SIG
@@ -399,6 +399,81 @@ JSON
singbox_config="$WORK_DIR/singbox-config.json"
generate_config "$WORK_DIR/singbox-fixture.json" "$singbox_config"
cat >"$WORK_DIR/singbox-prefix-fixture.json" <<'JSON'
{
"settings": {
".name": "settings",
".type": "settings",
"log_level": "warn"
},
"section": [
{
".name": "proxy",
".type": "section",
"enabled": "1",
"action": "proxy",
"urltest_enabled": "1",
"urltest_check_interval": "3m",
"urltest_tolerance": "50",
"urltest_filter_mode": "include",
"urltest_include_regex": [ "^Provider " ],
"detect_server_country": "country_is",
"subscription_urls": [ "https://singbox.example/sub" ],
"subscription_url_settings": "{\"https://singbox.example/sub\":{\"prefix_nodes\":\"1\",\"node_prefix\":\"Provider\"}}"
}
]
}
JSON
singbox_prefix_config="$WORK_DIR/singbox-prefix-config.json"
generate_config "$WORK_DIR/singbox-prefix-fixture.json" "$singbox_prefix_config"
ucode -e '
let fs = require("fs");
function object_or_empty(value) { return type(value) == "object" ? value : {}; }
function outbound_by_tag(config, tag) {
for (let outbound in config.outbounds || [])
if (outbound && outbound.tag == tag)
return outbound;
return null;
}
function contains(values, needle) {
for (let value in values || [])
if (value == needle)
return true;
return false;
}
let config = json(fs.readfile(ARGV[0]));
let cache = json(fs.readfile(ARGV[1]));
let names = object_or_empty(object_or_empty(cache.outboundMetadata).names);
let groups = object_or_empty(cache.urltestGroups);
let imported = outbound_by_tag(config, "Provider Native Group");
let native = outbound_by_tag(config, "Provider Native A");
let detour = outbound_by_tag(config, "Provider Detour Only");
let uses_detour = outbound_by_tag(config, "Provider Uses Detour");
let builtin = outbound_by_tag(config, "proxy-urltest-out");
if (!imported || !native || !detour || !uses_detour)
die("subscription prefix was not applied to every imported outbound\n");
if (length(imported.outbounds || []) != 1 || imported.outbounds[0] != "Provider Native A")
die("subscription prefix did not rewrite imported URLTest group membership\n");
if (uses_detour.detour != "Provider Detour Only")
die("subscription prefix did not rewrite detour references\n");
if (!builtin || length(builtin.outbounds || []) != 3)
die("prefixed node names did not match the configured URLTest filter\n");
for (let tag in [ "Provider Native A", "Provider Detour Only", "Provider Uses Detour" ])
if (!contains(builtin.outbounds, tag))
die("built-in URLTest is missing a prefixed node\n");
if (contains(cache.urltestCandidateTags || [], "Provider Native Group"))
die("prefixed imported URLTest group must not become a URLTest candidate\n");
if (object_or_empty(groups["Provider Native Group"]).displayName != "Provider Native Group")
die("prefixed imported URLTest group display name was not retained\n");
if (names["Provider Native A"] != "Provider Native A" ||
names["Provider Native Group"] != "Provider Native Group")
die("prefixed outbound metadata names were not retained\n");
if (names["Native A"] != null || names["Native Group"] != null)
die("unprefixed outbound metadata names must not remain\n");
' "$singbox_prefix_config" "$singbox_prefix_config.section-cache/proxy.json" || fail "subscription node prefix behavior"
cat >"$WORK_DIR/singbox-reveal-urltest-fixture.json" <<'JSON'
{
"settings": {