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
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+11 -2
View File
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: PODKOP PLUS\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-10 04:26+0300\n"
"PO-Revision-Date: 2026-07-10 04:26+0300\n"
"POT-Creation-Date: 2026-07-10 05:05+0300\n"
"PO-Revision-Date: 2026-07-10 05:05+0300\n"
"Last-Translator: ushan0v\n"
"Language-Team: none\n"
"Language: ru\n"
@@ -59,6 +59,9 @@ msgstr "Добавить секцию"
msgid "Add a server inbound"
msgstr "Добавить серверный inbound"
msgid "Add prefix to nodes"
msgstr "Добавлять префикс к узлам"
msgid "Add URLs or local paths to .lst lists"
msgstr "Добавляйте URL или локальные пути к спискам .lst"
@@ -134,6 +137,9 @@ msgstr "Авто-генерация HWID"
msgid "Automatic User-Agent selection"
msgstr "Авто-подбор User-Agent"
msgid "Automatically add text to the name of each server from this subscription for convenient filtering."
msgstr "Автоматически добавлять текст к имени каждого сервера из этой подписки для удобной фильтрации."
msgid "Automatically select the fastest node in the current level"
msgstr "Автовыбор самого быстрого узла уровня"
@@ -1310,6 +1316,9 @@ msgstr "Порты"
msgid "Preferred IP"
msgstr "Предпочитаемый IP"
msgid "Prefix text"
msgstr "Текст префикса"
msgid "Priority"
msgstr "Приоритет"
@@ -255,6 +255,8 @@ function hydrateConfigSections(configSections: Podkop.ConfigSection[]) {
auto_hwid: item.auto_hwid,
hwid: item.hwid,
show_dashboard_metadata: item.show_dashboard_metadata,
prefix_nodes: item.prefix_nodes,
node_prefix: item.node_prefix,
include_urltest_groups: item.include_urltest_groups,
hide_urltest_group_outbounds: item.hide_urltest_group_outbounds,
hide_detour_outbounds: item.hide_detour_outbounds,
+2
View File
@@ -326,6 +326,8 @@ export namespace Podkop {
auto_hwid?: '0' | '1';
hwid?: string;
show_dashboard_metadata?: '0' | '1';
prefix_nodes?: '0' | '1';
node_prefix?: string;
include_urltest_groups?: '0' | '1';
hide_urltest_group_outbounds?: '0' | '1';
hide_detour_outbounds?: '0' | '1';
@@ -3244,6 +3244,8 @@ function hydrateConfigSections(configSections) {
auto_hwid: item.auto_hwid,
hwid: item.hwid,
show_dashboard_metadata: item.show_dashboard_metadata,
prefix_nodes: item.prefix_nodes,
node_prefix: item.node_prefix,
include_urltest_groups: item.include_urltest_groups,
hide_urltest_group_outbounds: item.hide_urltest_group_outbounds,
hide_detour_outbounds: item.hide_detour_outbounds
@@ -1635,6 +1635,8 @@ function subscriptionUrlSettingsKeys() {
"auto_hwid",
"hwid",
"show_dashboard_metadata",
"prefix_nodes",
"node_prefix",
"include_urltest_groups",
"hide_urltest_group_outbounds",
"hide_detour_outbounds",
@@ -1652,6 +1654,8 @@ function defaultSubscriptionUrlSettings() {
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",
@@ -1943,6 +1947,21 @@ function addSubscriptionUrlItemOptions(itemSection, options = {}) {
o.default = "1";
o.rmempty = false;
o = itemSection.option(
form.Flag,
"prefix_nodes",
_("Add prefix to nodes"),
_(
"Automatically add text to the name of each server from this subscription for convenient filtering.",
),
);
o.default = "0";
o.rmempty = false;
o = itemSection.option(form.Value, "node_prefix", _("Prefix text"));
o.depends("prefix_nodes", "1");
o.rmempty = false;
o = itemSection.option(
form.Flag,
"include_urltest_groups",
+11 -2
View File
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: PODKOP PLUS\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-10 04:26+0300\n"
"PO-Revision-Date: 2026-07-10 04:26+0300\n"
"POT-Creation-Date: 2026-07-10 05:05+0300\n"
"PO-Revision-Date: 2026-07-10 05:05+0300\n"
"Last-Translator: ushan0v\n"
"Language-Team: none\n"
"Language: ru\n"
@@ -59,6 +59,9 @@ msgstr "Добавить секцию"
msgid "Add a server inbound"
msgstr "Добавить серверный inbound"
msgid "Add prefix to nodes"
msgstr "Добавлять префикс к узлам"
msgid "Add URLs or local paths to .lst lists"
msgstr "Добавляйте URL или локальные пути к спискам .lst"
@@ -134,6 +137,9 @@ msgstr "Авто-генерация HWID"
msgid "Automatic User-Agent selection"
msgstr "Авто-подбор User-Agent"
msgid "Automatically add text to the name of each server from this subscription for convenient filtering."
msgstr "Автоматически добавлять текст к имени каждого сервера из этой подписки для удобной фильтрации."
msgid "Automatically select the fastest node in the current level"
msgstr "Автовыбор самого быстрого узла уровня"
@@ -1310,6 +1316,9 @@ msgstr "Порты"
msgid "Preferred IP"
msgstr "Предпочитаемый IP"
msgid "Prefix text"
msgstr "Текст префикса"
msgid "Priority"
msgstr "Приоритет"
File diff suppressed because it is too large Load Diff
+2
View File
@@ -49,6 +49,8 @@ config settings 'settings'
# option subscription_update_interval '1h'
# option download_via_proxy_enabled '0'
# option show_dashboard_metadata '1'
# option prefix_nodes '0'
# #option node_prefix 'Provider'
# option include_urltest_groups '1'
# option hide_urltest_group_outbounds '1'
# option hide_detour_outbounds '1'
@@ -526,6 +526,24 @@ function subscription_hide_detour_outbounds(section, value) {
return item_bool(section, "subscription_url_settings", value, "hide_detour_outbounds", true);
}
function subscription_prefix_nodes(section, value) {
let child = child_item_by_value(section, "subscription_url", "url", value);
if (child != null)
return child_bool(child, "prefix_nodes", false);
return item_bool(section, "subscription_url_settings", value, "prefix_nodes", false);
}
function subscription_node_prefix(section, value) {
if (!subscription_prefix_nodes(section, value))
return "";
let child = child_item_by_value(section, "subscription_url", "url", value);
let prefix = child != null
? child_option(child, "node_prefix", "")
: item_option(section, "subscription_url_settings", value, "node_prefix", "");
return trim(as_string(prefix));
}
function subscription_user_agent(section, value) {
if (subscription_auto_user_agent(section, value))
return "";
@@ -884,6 +902,8 @@ return {
subscription_include_urltest_groups,
subscription_hide_urltest_group_outbounds,
subscription_hide_detour_outbounds,
subscription_prefix_nodes,
subscription_node_prefix,
subscription_user_agent,
subscription_hwid,
subscription_download_section,
+1
View File
@@ -738,6 +738,7 @@ function migrate_subscription_url_item_settings(ctx, section) {
set_option(ctx, child, "user_agent", profile.user_agent);
set_option(ctx, child, "auto_hwid", "1");
set_option(ctx, child, "show_dashboard_metadata", "1");
set_option(ctx, child, "prefix_nodes", "0");
set_option(ctx, child, "include_urltest_groups", "1");
set_option(ctx, child, "hide_urltest_group_outbounds", "1");
set_option(ctx, child, "hide_detour_outbounds", "1");
+2
View File
@@ -845,6 +845,8 @@ function subscription_urls_signature(section) {
auto_hwid: connections.subscription_auto_hwid(section, entry) ? "1" : "0",
hwid: connections.subscription_hwid(section, entry),
show_dashboard_metadata: connections.subscription_dashboard_metadata_enabled(section, entry) ? "1" : "0",
prefix_nodes: connections.subscription_prefix_nodes(section, entry) ? "1" : "0",
node_prefix: connections.subscription_node_prefix(section, entry),
include_urltest_groups: connections.subscription_include_urltest_groups(section, entry) ? "1" : "0",
hide_urltest_group_outbounds: connections.subscription_hide_urltest_group_outbounds(section, entry) ? "1" : "0",
hide_detour_outbounds: connections.subscription_hide_detour_outbounds(section, entry) ? "1" : "0"
+8 -2
View File
@@ -570,7 +570,7 @@ function unique_tag(base, taken) {
return base + "-overflow";
}
function add_subscription_source_with_state(config, section, source_index, source_entry, taken, selector_tags, urltest_candidate_tags, state, show_metadata, include_urltest_groups, hide_urltest_group_outbounds, hide_detour_outbounds) {
function add_subscription_source_with_state(config, section, source_index, source_entry, taken, selector_tags, urltest_candidate_tags, state, show_metadata, include_urltest_groups, hide_urltest_group_outbounds, hide_detour_outbounds, node_prefix) {
let section_name = section[".name"];
let source_section = runtime_subscription.source_id(section_name, source_index);
if (!runtime_subscription.source_cache_is_current(
@@ -590,6 +590,7 @@ function add_subscription_source_with_state(config, section, source_index, sourc
let visibility_refs = subscription_visibility_refs(outbounds);
if (include_urltest_groups === false)
hide_urltest_group_outbounds = false;
node_prefix = trim(as_string(node_prefix));
let prepared = [];
let source_indices = [];
let display_names = [];
@@ -610,6 +611,10 @@ function add_subscription_source_with_state(config, section, source_index, sourc
continue;
let display_name = as_string(outbound.remark || outbound.tag || ("server-" + (i + 1)));
let base = as_string(outbound.tag || outbound.remark || ("server-" + (i + 1)));
if (node_prefix != "") {
display_name = node_prefix + " " + display_name;
base = display_name;
}
let new_tag = unique_tag(base, taken);
taken[new_tag] = true;
tag_map[base] = new_tag;
@@ -1780,7 +1785,8 @@ function add_connection_subscriptions(config, state, section, taken, selector_ta
connections.subscription_dashboard_metadata_enabled(section, subscription_urls[i]),
connections.subscription_include_urltest_groups(section, subscription_urls[i]),
connections.subscription_hide_urltest_group_outbounds(section, subscription_urls[i]),
connections.subscription_hide_detour_outbounds(section, subscription_urls[i])
connections.subscription_hide_detour_outbounds(section, subscription_urls[i]),
connections.subscription_node_prefix(section, subscription_urls[i])
);
}
@@ -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": {