Add proxy parameter filters for URLTest and Priority

This commit is contained in:
ushan0v
2026-07-13 03:28:59 +03:00
parent ec936a675a
commit 5b06f51f61
12 changed files with 2367 additions and 1356 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+50 -2
View File
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: FORKOP\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-12 00:20+0300\n"
"PO-Revision-Date: 2026-07-12 00:20+0300\n"
"POT-Creation-Date: 2026-07-13 03:22+0300\n"
"PO-Revision-Date: 2026-07-13 03:22+0300\n"
"Last-Translator: ushan0v\n"
"Language-Team: none\n"
"Language: ru\n"
@@ -80,6 +80,12 @@ msgstr "Добавьте URL или локальные пути к списка
msgid "Additional marking rules found"
msgstr "Найдены дополнительные правила маркировки"
msgid "Additionally exclude servers by protocol, transport, and security. Exclude only servers matching the specified parameters."
msgstr "Дополнительно исключать серверы по протоколу, транспорту и защите. Исключать только серверы, совпавшие по указанным параметрам."
msgid "Additionally filter servers by protocol, transport, and security. Add only servers matching the specified parameters."
msgstr "Дополнительно отбирать серверы по протоколу, транспорту и защите. Добавлять только серверы, совпавшие по указанным параметрам."
msgid "Address"
msgstr "Адрес"
@@ -482,6 +488,15 @@ msgstr "Не тестировать серверы из этих стран."
msgid "Do not test servers whose names match the expression."
msgstr "Не тестировать серверы, имена которых совпадают с выражением."
msgid "Do not test servers with one of the selected protocols."
msgstr "Не тестировать серверы с одним из выбранных протоколов."
msgid "Do not test servers with one of the selected security types."
msgstr "Не тестировать серверы с одним из выбранных типов защиты."
msgid "Do not test servers with one of the selected transports."
msgstr "Не тестировать серверы с одним из выбранных транспортов."
msgid "Do not test specified servers."
msgstr "Не тестировать указанные серверы."
@@ -605,6 +620,9 @@ msgstr "HWID, который будет отправляться при запр
msgid "Enter the subscription URL"
msgstr "Введите URL подписки"
msgid "Exclude by proxy parameters"
msgstr "Исключить по параметрам прокси"
msgid "Exclude by regular expression"
msgstr "Исключить по регулярному выражению"
@@ -620,6 +638,15 @@ msgstr "Исключите трафик протокола NTP из туннел
msgid "Exclude servers"
msgstr "Исключить серверы"
msgid "Exclude servers with one of the selected protocols from this level."
msgstr "Исключить из этого уровня серверы с одним из выбранных протоколов."
msgid "Exclude servers with one of the selected security types from this level."
msgstr "Исключить из этого уровня серверы с одним из выбранных типов защиты."
msgid "Exclude servers with one of the selected transports from this level."
msgstr "Исключить из этого уровня серверы с одним из выбранных транспортов."
msgid "Expecting: %s"
msgstr "Ожидается: %s"
@@ -839,6 +866,9 @@ msgstr "Inbound JSON не может быть пустым"
msgid "Inbound JSON must be an object with a type field"
msgstr "Inbound JSON должен быть объектом с полем type"
msgid "Include by proxy parameters"
msgstr "Включить по параметрам прокси"
msgid "Include by regular expression"
msgstr "Включить по регулярному выражению"
@@ -1316,6 +1346,15 @@ msgstr "Только выбранные, кроме исключений"
msgid "Only servers whose names match the expression."
msgstr "Только серверы, имена которых соответствуют выражению."
msgid "Only servers with one of the selected protocols."
msgstr "Только серверы с одним из выбранных протоколов."
msgid "Only servers with one of the selected security types."
msgstr "Только серверы с одним из выбранных типов защиты."
msgid "Only servers with one of the selected transports."
msgstr "Только серверы с одним из выбранных транспортов."
msgid "Only the specified servers."
msgstr "Только указанные серверы."
@@ -1841,6 +1880,15 @@ msgstr "Тестирование задержки"
msgid "Test only selected servers."
msgstr "Тестировать только выбранные серверы."
msgid "Test only servers with one of the selected protocols."
msgstr "Тестировать только серверы с одним из выбранных протоколов."
msgid "Test only servers with one of the selected security types."
msgstr "Тестировать только серверы с одним из выбранных типов защиты."
msgid "Test only servers with one of the selected transports."
msgstr "Тестировать только серверы с одним из выбранных транспортов."
msgid "Test servers only from the specified countries."
msgstr "Тестировать серверы только из указанных стран."
@@ -715,6 +715,26 @@ function urltest_include_regex(section, value) {
list_value(section, "urltest_include_regex"));
}
function urltest_include_proxy_parameters(section, value) {
let child = urltest_child(section, value);
return child != null ? child_bool(child, "include_proxy_parameters", false) : false;
}
function urltest_include_protocols(section, value) {
let child = urltest_child(section, value);
return child != null ? child_list(child, "include_protocols", []) : [];
}
function urltest_include_transports(section, value) {
let child = urltest_child(section, value);
return child != null ? child_list(child, "include_transports", []) : [];
}
function urltest_include_securities(section, value) {
let child = urltest_child(section, value);
return child != null ? child_list(child, "include_securities", []) : [];
}
function urltest_exclude_countries(section, value) {
let child = urltest_child(section, value);
if (child != null)
@@ -739,6 +759,26 @@ function urltest_exclude_regex(section, value) {
list_value(section, "urltest_exclude_regex"));
}
function urltest_exclude_proxy_parameters(section, value) {
let child = urltest_child(section, value);
return child != null ? child_bool(child, "exclude_proxy_parameters", false) : false;
}
function urltest_exclude_protocols(section, value) {
let child = urltest_child(section, value);
return child != null ? child_list(child, "exclude_protocols", []) : [];
}
function urltest_exclude_transports(section, value) {
let child = urltest_child(section, value);
return child != null ? child_list(child, "exclude_transports", []) : [];
}
function urltest_exclude_securities(section, value) {
let child = urltest_child(section, value);
return child != null ? child_list(child, "exclude_securities", []) : [];
}
function priority_group_display_name(section, value) {
let child = priority_group_child(section, value);
return child != null ? child_option(child, "name", as_string(value)) : as_string(value);
@@ -829,6 +869,26 @@ function priority_level_include_regex(group_id, value) {
return child != null ? child_list_alias(child, [ "include_regex", "regex" ], []) : [];
}
function priority_level_include_proxy_parameters(group_id, value) {
let child = priority_level_child(group_id, value);
return child != null ? child_bool(child, "include_proxy_parameters", false) : false;
}
function priority_level_include_protocols(group_id, value) {
let child = priority_level_child(group_id, value);
return child != null ? child_list(child, "include_protocols", []) : [];
}
function priority_level_include_transports(group_id, value) {
let child = priority_level_child(group_id, value);
return child != null ? child_list(child, "include_transports", []) : [];
}
function priority_level_include_securities(group_id, value) {
let child = priority_level_child(group_id, value);
return child != null ? child_list(child, "include_securities", []) : [];
}
function priority_level_exclude_countries(group_id, value) {
let child = priority_level_child(group_id, value);
return child != null ? child_list(child, "exclude_countries", []) : [];
@@ -844,6 +904,26 @@ function priority_level_exclude_regex(group_id, value) {
return child != null ? child_list(child, "exclude_regex", []) : [];
}
function priority_level_exclude_proxy_parameters(group_id, value) {
let child = priority_level_child(group_id, value);
return child != null ? child_bool(child, "exclude_proxy_parameters", false) : false;
}
function priority_level_exclude_protocols(group_id, value) {
let child = priority_level_child(group_id, value);
return child != null ? child_list(child, "exclude_protocols", []) : [];
}
function priority_level_exclude_transports(group_id, value) {
let child = priority_level_child(group_id, value);
return child != null ? child_list(child, "exclude_transports", []) : [];
}
function priority_level_exclude_securities(group_id, value) {
let child = priority_level_child(group_id, value);
return child != null ? child_list(child, "exclude_securities", []) : [];
}
function priority_level_countries(group_id, value) {
return priority_level_include_countries(group_id, value);
}
@@ -958,9 +1038,17 @@ return {
urltest_include_countries,
urltest_include_outbounds,
urltest_include_regex,
urltest_include_proxy_parameters,
urltest_include_protocols,
urltest_include_transports,
urltest_include_securities,
urltest_exclude_countries,
urltest_exclude_outbounds,
urltest_exclude_regex,
urltest_exclude_proxy_parameters,
urltest_exclude_protocols,
urltest_exclude_transports,
urltest_exclude_securities,
priority_groups,
priority_group_settings,
priority_levels,
@@ -982,9 +1070,17 @@ return {
priority_level_include_countries,
priority_level_include_outbounds,
priority_level_include_regex,
priority_level_include_proxy_parameters,
priority_level_include_protocols,
priority_level_include_transports,
priority_level_include_securities,
priority_level_exclude_countries,
priority_level_exclude_outbounds,
priority_level_exclude_regex,
priority_level_exclude_proxy_parameters,
priority_level_exclude_protocols,
priority_level_exclude_transports,
priority_level_exclude_securities,
priority_level_countries,
priority_level_outbounds,
priority_level_regex,
+49
View File
@@ -1047,6 +1047,27 @@ function validate_detect_server_country_value(value, section) {
fail_validation("Invalid server country detection mode '" + value + "' in rule '" + section + "'. Aborted.");
}
function validate_proxy_parameter_value(value, allowed, parameter, section) {
value = lc(as_string(value));
if (value == "" || contains(allowed, value))
return;
fail_validation("Invalid proxy " + parameter + " '" + value + "' in rule '" + section + "'. Aborted.");
}
function validate_proxy_parameter_filters(protocols, transports, securities, section) {
for (let value in protocols)
validate_proxy_parameter_value(value,
[ "direct", "http", "hysteria2", "shadowsocks", "socks", "trojan", "vless", "vmess" ],
"protocol", section);
for (let value in transports)
validate_proxy_parameter_value(value,
[ "tcp", "ws", "grpc", "http", "httpupgrade", "xhttp" ],
"transport", section);
for (let value in securities)
validate_proxy_parameter_value(value, [ "none", "tls", "reality" ], "security", section);
}
function validate_urltest_regex_value(value, section) {
if (as_string(value) == "" || regex_valid(value))
return;
@@ -1121,12 +1142,26 @@ function validate_priority_group(section, group_id) {
validate_country_code_value(value, name);
for (let value in connections.priority_level_include_regex(group_id, level_id))
validate_urltest_regex_value(value, name);
if (connections.priority_level_include_proxy_parameters(group_id, level_id))
validate_proxy_parameter_filters(
connections.priority_level_include_protocols(group_id, level_id),
connections.priority_level_include_transports(group_id, level_id),
connections.priority_level_include_securities(group_id, level_id),
name
);
}
if (filter_mode == "exclude" || filter_mode == "mixed") {
for (let value in connections.priority_level_exclude_countries(group_id, level_id))
validate_country_code_value(value, name);
for (let value in connections.priority_level_exclude_regex(group_id, level_id))
validate_urltest_regex_value(value, name);
if (connections.priority_level_exclude_proxy_parameters(group_id, level_id))
validate_proxy_parameter_filters(
connections.priority_level_exclude_protocols(group_id, level_id),
connections.priority_level_exclude_transports(group_id, level_id),
connections.priority_level_exclude_securities(group_id, level_id),
name
);
}
}
}
@@ -1382,12 +1417,26 @@ function validate_rule(section, sections, context) {
validate_urltest_regex_value(value, name);
for (let value in connections.urltest_include_countries(section, urltest_id))
validate_country_code_value(value, name);
if (connections.urltest_include_proxy_parameters(section, urltest_id))
validate_proxy_parameter_filters(
connections.urltest_include_protocols(section, urltest_id),
connections.urltest_include_transports(section, urltest_id),
connections.urltest_include_securities(section, urltest_id),
name
);
}
if (contains([ "exclude", "mixed" ], urltest_filter_mode)) {
for (let value in connections.urltest_exclude_countries(section, urltest_id))
validate_country_code_value(value, name);
for (let value in connections.urltest_exclude_regex(section, urltest_id))
validate_urltest_regex_value(value, name);
if (connections.urltest_exclude_proxy_parameters(section, urltest_id))
validate_proxy_parameter_filters(
connections.urltest_exclude_protocols(section, urltest_id),
connections.urltest_exclude_transports(section, urltest_id),
connections.urltest_exclude_securities(section, urltest_id),
name
);
}
}
+97 -12
View File
@@ -986,21 +986,75 @@ function tag_country_filter_matches(tag, countries, country_filter) {
return country != "" && array_contains(country_filter, country);
}
function tag_attribute_filter_matches(tag, metadata, selected_values) {
selected_values = array_or_empty(selected_values);
if (length(selected_values) == 0)
return true;
let value = lc(as_string(object_or_empty(metadata)[tag] || ""));
if (value == "")
return false;
for (let selected in selected_values)
if (lc(as_string(selected)) == value)
return true;
return false;
}
function proxy_parameter_filter_matches_all(tag, metadata, protocols, transports, securities) {
metadata = object_or_empty(metadata);
return tag_attribute_filter_matches(tag, metadata.protocols, protocols) &&
tag_attribute_filter_matches(tag, metadata.transports, transports) &&
tag_attribute_filter_matches(tag, metadata.securities, securities);
}
function proxy_parameter_filter_matches_any(tag, metadata, protocols, transports, securities) {
metadata = object_or_empty(metadata);
return (length(array_or_empty(protocols)) > 0 &&
tag_attribute_filter_matches(tag, metadata.protocols, protocols)) ||
(length(array_or_empty(transports)) > 0 &&
tag_attribute_filter_matches(tag, metadata.transports, transports)) ||
(length(array_or_empty(securities)) > 0 &&
tag_attribute_filter_matches(tag, metadata.securities, securities));
}
function name_or_country_filter_configured(name_filter, regexes, country_filter) {
return length(array_or_empty(name_filter)) > 0 ||
length(array_or_empty(regexes)) > 0 ||
length(array_or_empty(country_filter)) > 0;
}
function urltest_all_candidate_outbounds(urltest_candidate_tags) {
return unique_string_array(urltest_candidate_tags);
}
function urltest_matching_candidate_outbounds(urltest_candidate_tags, names, countries, name_filter, regexes, country_filter) {
function urltest_matching_candidate_outbounds(urltest_candidate_tags, names, countries, name_filter, regexes, country_filter,
metadata, proxy_parameters_enabled, proxy_parameters_operator, protocols, transports, securities) {
names = object_or_empty(names);
countries = object_or_empty(countries);
country_filter = runtime_urltest.normalized_country_list(country_filter);
let regex_set = regex_match_set(urltest_candidate_tags, names, regexes);
let base_filter_configured = name_or_country_filter_configured(name_filter, regexes, country_filter);
let result = [];
for (let tag in array_or_empty(urltest_candidate_tags)) {
if (tag_name_filter_matches(tag, names, name_filter, regex_set) ||
tag_country_filter_matches(tag, countries, country_filter))
let base_matches = tag_name_filter_matches(tag, names, name_filter, regex_set) ||
tag_country_filter_matches(tag, countries, country_filter);
let matches = base_matches;
if (proxy_parameters_enabled && proxy_parameters_operator == "or") {
matches = base_matches || proxy_parameter_filter_matches_any(
tag, metadata, protocols, transports, securities
);
}
else if (proxy_parameters_enabled) {
if (!base_filter_configured)
base_matches = true;
matches = base_matches && proxy_parameter_filter_matches_all(
tag, metadata, protocols, transports, securities
);
}
if (matches)
push(result, tag);
}
@@ -1017,8 +1071,11 @@ function urltest_exclude_outbounds(all_outbounds, excluded_outbounds) {
return result;
}
function filter_candidate_outbounds(filter_mode, urltest_candidate_tags, names, countries,
include_names, include_regex, include_countries, exclude_names, exclude_regex, exclude_countries) {
function filter_candidate_outbounds(filter_mode, urltest_candidate_tags, names, countries, metadata,
include_names, include_regex, include_countries,
include_proxy_parameters, include_protocols, include_transports, include_securities,
exclude_names, exclude_regex, exclude_countries,
exclude_proxy_parameters, exclude_protocols, exclude_transports, exclude_securities) {
let all_outbounds = urltest_all_candidate_outbounds(urltest_candidate_tags);
if (filter_mode == "" || filter_mode == "disabled")
return all_outbounds;
@@ -1031,7 +1088,13 @@ function filter_candidate_outbounds(filter_mode, urltest_candidate_tags, names,
countries,
include_names,
include_regex,
include_countries
include_countries,
metadata,
include_proxy_parameters,
"and",
include_protocols,
include_transports,
include_securities
);
let exclude_outbounds = urltest_matching_candidate_outbounds(
urltest_candidate_tags,
@@ -1039,7 +1102,13 @@ function filter_candidate_outbounds(filter_mode, urltest_candidate_tags, names,
countries,
exclude_names,
exclude_regex,
exclude_countries
exclude_countries,
metadata,
exclude_proxy_parameters,
"or",
exclude_protocols,
exclude_transports,
exclude_securities
);
if (filter_mode == "include")
@@ -1057,12 +1126,21 @@ function urltest_filtered_outbounds(section, urltest_id, urltest_candidate_tags,
urltest_candidate_tags,
object_or_empty(object_or_empty(state.outboundMetadata).names),
urltest_country_metadata(section, urltest_id, state),
object_or_empty(state.outboundMetadata),
connections.urltest_include_outbounds(section, urltest_id),
connections.urltest_include_regex(section, urltest_id),
connections.urltest_include_countries(section, urltest_id),
connections.urltest_include_proxy_parameters(section, urltest_id),
connections.urltest_include_protocols(section, urltest_id),
connections.urltest_include_transports(section, urltest_id),
connections.urltest_include_securities(section, urltest_id),
connections.urltest_exclude_outbounds(section, urltest_id),
connections.urltest_exclude_regex(section, urltest_id),
connections.urltest_exclude_countries(section, urltest_id)
connections.urltest_exclude_countries(section, urltest_id),
connections.urltest_exclude_proxy_parameters(section, urltest_id),
connections.urltest_exclude_protocols(section, urltest_id),
connections.urltest_exclude_transports(section, urltest_id),
connections.urltest_exclude_securities(section, urltest_id)
);
}
@@ -1083,12 +1161,21 @@ function priority_level_filtered_outbounds(group_id, level_id, urltest_candidate
urltest_candidate_tags,
object_or_empty(object_or_empty(state.outboundMetadata).names),
priority_level_country_metadata(group_id, level_id, state),
object_or_empty(state.outboundMetadata),
connections.priority_level_include_outbounds(group_id, level_id),
connections.priority_level_include_regex(group_id, level_id),
connections.priority_level_include_countries(group_id, level_id),
connections.priority_level_include_proxy_parameters(group_id, level_id),
connections.priority_level_include_protocols(group_id, level_id),
connections.priority_level_include_transports(group_id, level_id),
connections.priority_level_include_securities(group_id, level_id),
connections.priority_level_exclude_outbounds(group_id, level_id),
connections.priority_level_exclude_regex(group_id, level_id),
connections.priority_level_exclude_countries(group_id, level_id)
connections.priority_level_exclude_countries(group_id, level_id),
connections.priority_level_exclude_proxy_parameters(group_id, level_id),
connections.priority_level_exclude_protocols(group_id, level_id),
connections.priority_level_exclude_transports(group_id, level_id),
connections.priority_level_exclude_securities(group_id, level_id)
);
}
@@ -1911,9 +1998,7 @@ function add_manual_proxy_link(config, state, section_name, manual_index, link,
push(urltest_candidate_tags, tag_name);
state.links[tag_name] = as_string(link);
state.outboundMetadata.names[tag_name] = display_name;
if (as_string(outbound.server || "") != "")
state.servers[tag_name] = as_string(outbound.server);
runtime_subscription.remember_outbound_metadata(state, tag_name, display_name, outbound);
return tag_name;
}
+23 -1
View File
@@ -199,6 +199,25 @@ function remember_outbound_metadata(state, tag_name, display_name, outbound) {
if (type(state) != "object")
return;
state.outboundMetadata.names[tag_name] = display_name;
let protocol = lc(as_string(outbound.type || ""));
if (protocol != "")
state.outboundMetadata.protocols[tag_name] = protocol;
let transport = lc(as_string(object_or_empty(outbound.transport).type || ""));
if (transport == "" || transport == "raw")
transport = "tcp";
else if (transport == "h2")
transport = "http";
state.outboundMetadata.transports[tag_name] = transport;
let tls = type(outbound.tls) == "object" ? outbound.tls : null;
let security = "none";
if (tls != null && tls.enabled !== false) {
let reality = type(tls.reality) == "object" ? tls.reality : null;
security = reality != null && reality.enabled !== false ? "reality" : "tls";
}
state.outboundMetadata.securities[tag_name] = security;
let server = as_string(outbound.server || "");
if (server != "")
state.servers[tag_name] = server;
@@ -337,7 +356,10 @@ function new_section_state(section_name) {
linkRefs: {},
outboundMetadata: {
names: {},
countries: {}
countries: {},
protocols: {},
transports: {},
securities: {}
},
servers: {},
urltestCandidateTags: [],
@@ -1708,6 +1708,91 @@ function serverCountryDetectionChoices() {
];
}
function proxyProtocolChoices() {
return [
["vless", "VLESS"],
["vmess", "VMess"],
["trojan", "Trojan"],
["shadowsocks", "Shadowsocks"],
["socks", "SOCKS"],
["http", "HTTP"],
["hysteria2", "Hysteria2"],
["direct", "Direct"],
];
}
function proxyTransportChoices() {
return [
["tcp", "TCP"],
["ws", "WebSocket"],
["grpc", "gRPC"],
["http", "HTTP"],
["httpupgrade", "HTTPUpgrade"],
["xhttp", "XHTTP"],
];
}
function proxySecurityChoices() {
return [
["none", "None"],
["tls", "TLS"],
["reality", "Reality"],
];
}
function addProxyParameterFilterOptions(itemSection, options) {
const prefix = options.prefix;
const dependencies = options.dependencies;
let o = itemSection.option(
form.Flag,
`${prefix}_proxy_parameters`,
options.toggleLabel,
options.toggleDescription,
);
dependencies.forEach((dependency) => o.depends(dependency));
o.default = "0";
o.rmempty = false;
[
[
"protocols",
_("Protocol"),
options.protocolDescription,
proxyProtocolChoices(),
],
[
"transports",
_("Transport"),
options.transportDescription,
proxyTransportChoices(),
],
[
"securities",
_("Security"),
options.securityDescription,
proxySecurityChoices(),
],
].forEach(([suffix, label, description, choices]) => {
const list = itemSection.option(
form.DynamicList,
`${prefix}_${suffix}`,
label,
description,
);
dependencies.forEach((dependency) =>
list.depends(
Object.assign({}, dependency, {
[`${prefix}_proxy_parameters`]: "1",
}),
),
);
list.rmempty = true;
choices.forEach(([value, choiceLabel]) => list.value(value, choiceLabel));
list.placeholder = _("-- Select --");
});
}
function urlTestUrlChoices() {
return Array.isArray(main.LATENCY_TEST_URL_OPTIONS)
? main.LATENCY_TEST_URL_OPTIONS
@@ -1837,9 +1922,17 @@ function urlTestSettingsKeys() {
"include_countries",
"include_outbounds",
"include_regex",
"include_proxy_parameters",
"include_protocols",
"include_transports",
"include_securities",
"exclude_countries",
"exclude_outbounds",
"exclude_regex",
"exclude_proxy_parameters",
"exclude_protocols",
"exclude_transports",
"exclude_securities",
];
}
@@ -1929,9 +2022,17 @@ function priorityLevelSettingsKeys() {
"country",
"server_name",
"regex",
"include_proxy_parameters",
"include_protocols",
"include_transports",
"include_securities",
"exclude_countries",
"exclude_outbounds",
"exclude_regex",
"exclude_proxy_parameters",
"exclude_protocols",
"exclude_transports",
"exclude_securities",
];
}
@@ -2300,6 +2401,41 @@ function addUrlTestItemOptions(itemSection, options = {}) {
);
o.default = "flag_emoji";
const includeProxyParameterOptions = {
prefix: "include",
toggleLabel: _("Include by proxy parameters"),
toggleDescription: _(
"Additionally filter servers by protocol, transport, and security. Add only servers matching the specified parameters.",
),
dependencies: [{ filter_mode: "include" }, { filter_mode: "mixed" }],
protocolDescription: _(
"Test only servers with one of the selected protocols.",
),
transportDescription: _(
"Test only servers with one of the selected transports.",
),
securityDescription: _(
"Test only servers with one of the selected security types.",
),
};
const excludeProxyParameterOptions = {
prefix: "exclude",
toggleLabel: _("Exclude by proxy parameters"),
toggleDescription: _(
"Additionally exclude servers by protocol, transport, and security. Exclude only servers matching the specified parameters.",
),
dependencies: [{ filter_mode: "exclude" }, { filter_mode: "mixed" }],
protocolDescription: _(
"Do not test servers with one of the selected protocols.",
),
transportDescription: _(
"Do not test servers with one of the selected transports.",
),
securityDescription: _(
"Do not test servers with one of the selected security types.",
),
};
[
[
"include_countries",
@@ -2377,6 +2513,11 @@ function addUrlTestItemOptions(itemSection, options = {}) {
return validator(null, value);
};
}
if (key === "include_regex") {
addProxyParameterFilterOptions(itemSection, includeProxyParameterOptions);
} else if (key === "exclude_regex") {
addProxyParameterFilterOptions(itemSection, excludeProxyParameterOptions);
}
});
}
@@ -2466,6 +2607,45 @@ function addPriorityLevelItemOptions(itemSection, options = {}) {
);
o.default = "flag_emoji";
const includeProxyParameterOptions = {
prefix: "include",
toggleLabel: _("Include by proxy parameters"),
toggleDescription: _(
"Additionally filter servers by protocol, transport, and security. Add only servers matching the specified parameters.",
),
dependencies: [
{ direct: "0", filter_mode: "include" },
{ direct: "0", filter_mode: "mixed" },
],
protocolDescription: _("Only servers with one of the selected protocols."),
transportDescription: _(
"Only servers with one of the selected transports.",
),
securityDescription: _(
"Only servers with one of the selected security types.",
),
};
const excludeProxyParameterOptions = {
prefix: "exclude",
toggleLabel: _("Exclude by proxy parameters"),
toggleDescription: _(
"Additionally exclude servers by protocol, transport, and security. Exclude only servers matching the specified parameters.",
),
dependencies: [
{ direct: "0", filter_mode: "exclude" },
{ direct: "0", filter_mode: "mixed" },
],
protocolDescription: _(
"Exclude servers with one of the selected protocols from this level.",
),
transportDescription: _(
"Exclude servers with one of the selected transports from this level.",
),
securityDescription: _(
"Exclude servers with one of the selected security types from this level.",
),
};
[
[
"country",
@@ -2543,6 +2723,11 @@ function addPriorityLevelItemOptions(itemSection, options = {}) {
return validator(null, value);
};
}
if (key === "regex") {
addProxyParameterFilterOptions(itemSection, includeProxyParameterOptions);
} else if (key === "exclude_regex") {
addProxyParameterFilterOptions(itemSection, excludeProxyParameterOptions);
}
});
}
+50 -2
View File
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: FORKOP\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-12 00:20+0300\n"
"PO-Revision-Date: 2026-07-12 00:20+0300\n"
"POT-Creation-Date: 2026-07-13 03:22+0300\n"
"PO-Revision-Date: 2026-07-13 03:22+0300\n"
"Last-Translator: ushan0v\n"
"Language-Team: none\n"
"Language: ru\n"
@@ -80,6 +80,12 @@ msgstr "Добавьте URL или локальные пути к списка
msgid "Additional marking rules found"
msgstr "Найдены дополнительные правила маркировки"
msgid "Additionally exclude servers by protocol, transport, and security. Exclude only servers matching the specified parameters."
msgstr "Дополнительно исключать серверы по протоколу, транспорту и защите. Исключать только серверы, совпавшие по указанным параметрам."
msgid "Additionally filter servers by protocol, transport, and security. Add only servers matching the specified parameters."
msgstr "Дополнительно отбирать серверы по протоколу, транспорту и защите. Добавлять только серверы, совпавшие по указанным параметрам."
msgid "Address"
msgstr "Адрес"
@@ -482,6 +488,15 @@ msgstr "Не тестировать серверы из этих стран."
msgid "Do not test servers whose names match the expression."
msgstr "Не тестировать серверы, имена которых совпадают с выражением."
msgid "Do not test servers with one of the selected protocols."
msgstr "Не тестировать серверы с одним из выбранных протоколов."
msgid "Do not test servers with one of the selected security types."
msgstr "Не тестировать серверы с одним из выбранных типов защиты."
msgid "Do not test servers with one of the selected transports."
msgstr "Не тестировать серверы с одним из выбранных транспортов."
msgid "Do not test specified servers."
msgstr "Не тестировать указанные серверы."
@@ -605,6 +620,9 @@ msgstr "HWID, который будет отправляться при запр
msgid "Enter the subscription URL"
msgstr "Введите URL подписки"
msgid "Exclude by proxy parameters"
msgstr "Исключить по параметрам прокси"
msgid "Exclude by regular expression"
msgstr "Исключить по регулярному выражению"
@@ -620,6 +638,15 @@ msgstr "Исключите трафик протокола NTP из туннел
msgid "Exclude servers"
msgstr "Исключить серверы"
msgid "Exclude servers with one of the selected protocols from this level."
msgstr "Исключить из этого уровня серверы с одним из выбранных протоколов."
msgid "Exclude servers with one of the selected security types from this level."
msgstr "Исключить из этого уровня серверы с одним из выбранных типов защиты."
msgid "Exclude servers with one of the selected transports from this level."
msgstr "Исключить из этого уровня серверы с одним из выбранных транспортов."
msgid "Expecting: %s"
msgstr "Ожидается: %s"
@@ -839,6 +866,9 @@ msgstr "Inbound JSON не может быть пустым"
msgid "Inbound JSON must be an object with a type field"
msgstr "Inbound JSON должен быть объектом с полем type"
msgid "Include by proxy parameters"
msgstr "Включить по параметрам прокси"
msgid "Include by regular expression"
msgstr "Включить по регулярному выражению"
@@ -1316,6 +1346,15 @@ msgstr "Только выбранные, кроме исключений"
msgid "Only servers whose names match the expression."
msgstr "Только серверы, имена которых соответствуют выражению."
msgid "Only servers with one of the selected protocols."
msgstr "Только серверы с одним из выбранных протоколов."
msgid "Only servers with one of the selected security types."
msgstr "Только серверы с одним из выбранных типов защиты."
msgid "Only servers with one of the selected transports."
msgstr "Только серверы с одним из выбранных транспортов."
msgid "Only the specified servers."
msgstr "Только указанные серверы."
@@ -1841,6 +1880,15 @@ msgstr "Тестирование задержки"
msgid "Test only selected servers."
msgstr "Тестировать только выбранные серверы."
msgid "Test only servers with one of the selected protocols."
msgstr "Тестировать только серверы с одним из выбранных протоколов."
msgid "Test only servers with one of the selected security types."
msgstr "Тестировать только серверы с одним из выбранных типов защиты."
msgid "Test only servers with one of the selected transports."
msgstr "Тестировать только серверы с одним из выбранных транспортов."
msgid "Test servers only from the specified countries."
msgstr "Тестировать серверы только из указанных стран."
File diff suppressed because it is too large Load Diff
+20
View File
@@ -11,6 +11,8 @@ fail() {
wrapper_styles="$(sed -n '/^\.fkp-button-add-dynlist > \.add-item {$/,/^}$/p' "$SECTION_JS")"
button_styles="$(sed -n '/^\.fkp-button-add-dynlist > \.add-item > \.cbi-button-add {$/,/^}$/p' "$SECTION_JS")"
urltest_options="$(sed -n '/^function addUrlTestItemOptions(/,/^function priorityLevelSettingsForValidation(/p' "$SECTION_JS")"
priority_options="$(sed -n '/^function addPriorityLevelItemOptions(/,/^function addPriorityGroupItemOptions(/p' "$SECTION_JS")"
grep -Fq 'display: flex;' <<<"$wrapper_styles" ||
fail "button-only DynamicList add rows must use a content-sized flex wrapper"
@@ -30,4 +32,22 @@ grep -Fq 'max-width: 100% !important;' <<<"$button_styles" ||
grep -Fq 'text-overflow: ellipsis !important;' <<<"$button_styles" ||
fail "button-only DynamicList labels must truncate instead of overflowing"
grep -Fq 'if (key === "include_regex") {' <<<"$urltest_options" ||
fail "URLTest include proxy parameters must follow the include regex option"
grep -Fq 'else if (key === "exclude_regex") {' <<<"$urltest_options" ||
fail "URLTest exclude proxy parameters must follow the exclude regex option"
grep -Fq 'if (key === "regex") {' <<<"$priority_options" ||
fail "Priority include proxy parameters must follow the include regex option"
grep -Fq 'else if (key === "exclude_regex") {' <<<"$priority_options" ||
fail "Priority exclude proxy parameters must follow the exclude regex option"
grep -Fq '["direct", "Direct"]' "$SECTION_JS" ||
fail "proxy protocol choices must keep Direct untranslated"
grep -Fq '["none", "None"]' "$SECTION_JS" ||
fail "proxy security choices must keep None untranslated"
grep -Fq '_("Security")' "$SECTION_JS" ||
fail "proxy parameter filters must use the short Security label"
if grep -Fq 'Connection security' "$SECTION_JS"; then
fail "proxy parameter filter copy must not mention connection security"
fi
printf 'LuCI DynamicList layout checks passed\n'
+194
View File
@@ -0,0 +1,194 @@
#!/usr/bin/env bash
set -eo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
FORKOP_LIB="$ROOT_DIR/forkop/files/usr/lib"
GENERATOR_UC="$FORKOP_LIB/singbox/generator.uc"
VALIDATOR_UC="$FORKOP_LIB/config/validator.uc"
WORK_DIR="$(mktemp -d)"
cleanup() {
rm -rf "$WORK_DIR"
}
trap cleanup EXIT
fail() {
printf 'FAIL: %s\n' "$1" >&2
exit 1
}
generate_config() {
local fixture="$1"
local output="$2"
mkdir -p "${output}.section-cache"
ucode -L "$FORKOP_LIB" "$GENERATOR_UC" generate-config-fixture \
"$fixture" "$output" "127.0.0.1"
}
validate_fixture() {
local fixture="$1"
FORKOP_LIB="$FORKOP_LIB" ucode -L "$FORKOP_LIB" "$VALIDATOR_UC" \
validate-runtime-fixture "$fixture" "{}"
}
cat >"$WORK_DIR/fixture.json" <<'JSON'
{
"settings": {
".name": "settings",
".type": "settings",
"dns_server": [ "77.88.8.8" ],
"bootstrap_dns_server": [ "77.88.8.8" ]
},
"section": [
{
".name": "proxy",
".type": "section",
"enabled": "1",
"action": "proxy",
"outbound_jsons": [
"{\"type\":\"vless\",\"tag\":\"Alpha\",\"server\":\"alpha.example\",\"server_port\":443,\"uuid\":\"00000000-0000-4000-8000-000000000001\",\"tls\":{\"enabled\":true}}",
"{\"type\":\"vless\",\"tag\":\"Beta\",\"server\":\"beta.example\",\"server_port\":443,\"uuid\":\"00000000-0000-4000-8000-000000000002\",\"transport\":{\"type\":\"ws\"},\"tls\":{\"enabled\":true,\"reality\":{\"enabled\":true}}}",
"{\"type\":\"vmess\",\"tag\":\"Gamma\",\"server\":\"gamma.example\",\"server_port\":443,\"uuid\":\"00000000-0000-4000-8000-000000000003\",\"transport\":{\"type\":\"grpc\"}}",
"{\"type\":\"trojan\",\"tag\":\"Delta\",\"server\":\"delta.example\",\"server_port\":443,\"password\":\"secret\",\"transport\":{\"type\":\"xhttp\"},\"tls\":{\"enabled\":true}}",
"{\"type\":\"http\",\"tag\":\"Echo\",\"server\":\"echo.example\",\"server_port\":8080}"
]
}
],
"urltest": [
{
".name": "and_filter",
".type": "urltest",
"section": "proxy",
"name": "AND filter",
"filter_mode": "mixed",
"include_regex": [ "Alpha|Beta|Gamma|Delta" ],
"include_proxy_parameters": "1",
"include_protocols": [ "vless", "vmess" ],
"include_transports": [ "tcp", "ws", "grpc" ],
"include_securities": [ "tls", "reality", "none" ],
"exclude_regex": [ "Alpha" ],
"exclude_proxy_parameters": "1",
"exclude_securities": [ "reality" ]
},
{
".name": "parameters_only",
".type": "urltest",
"section": "proxy",
"name": "Parameters only",
"filter_mode": "include",
"include_proxy_parameters": "1",
"include_protocols": [ "http" ],
"include_transports": [ "tcp" ],
"include_securities": [ "none" ]
}
],
"priority_group": [
{
".name": "pg_parameters",
".type": "priority_group",
"section": "proxy",
"name": "Parameter priority"
}
],
"priority_level": [
{
".name": "pl_grpc",
".type": "priority_level",
"group": "pg_parameters",
"name": "gRPC VMess",
"order": "0",
"filter_mode": "include",
"include_proxy_parameters": "1",
"include_protocols": [ "vmess" ],
"include_transports": [ "grpc" ],
"include_securities": [ "none" ]
},
{
".name": "pl_mixed",
".type": "priority_level",
"group": "pg_parameters",
"name": "Mixed VLESS",
"order": "1",
"filter_mode": "mixed",
"regex": [ "Alpha|Beta" ],
"include_proxy_parameters": "1",
"include_protocols": [ "vless" ],
"exclude_regex": [ "Alpha" ],
"exclude_proxy_parameters": "1",
"exclude_securities": [ "reality" ]
},
{
".name": "pl_xhttp",
".type": "priority_level",
"group": "pg_parameters",
"name": "XHTTP Trojan",
"order": "2",
"filter_mode": "include",
"include_proxy_parameters": "1",
"include_protocols": [ "trojan" ],
"include_transports": [ "xhttp" ],
"include_securities": [ "tls" ]
},
{
".name": "pl_remaining",
".type": "priority_level",
"group": "pg_parameters",
"name": "Remaining",
"order": "3",
"filter_mode": "disabled"
}
]
}
JSON
validate_fixture "$WORK_DIR/fixture.json" >/dev/null || fail "valid proxy parameter filters"
generate_config "$WORK_DIR/fixture.json" "$WORK_DIR/config.json"
ucode -e '
let fs = require("fs");
function fail(message) { die(message + "\n"); }
function outbound_by_tag(config, tag) {
for (let outbound in config.outbounds || [])
if (outbound && outbound.tag == tag)
return outbound;
return null;
}
function assert_array(value, expected, label) {
value = value || [];
if (length(value) != length(expected))
fail(label + " length mismatch: " + sprintf("%J", value));
for (let i = 0; i < length(expected); i++)
if (value[i] != expected[i])
fail(label + " mismatch: " + sprintf("%J", value));
}
let config = json(fs.readfile(ARGV[0]));
let cache = json(fs.readfile(ARGV[1]));
assert_array(outbound_by_tag(config, "proxy-urltest-and_filter-out").outbounds, [ "Gamma" ], "URLTest AND/OR filter");
assert_array(outbound_by_tag(config, "proxy-urltest-parameters_only-out").outbounds, [ "Echo" ], "URLTest parameter-only filter");
assert_array(outbound_by_tag(config, "proxy-priority-pg_parameters-out").outbounds,
[ "Gamma", "Delta", "Alpha", "Beta", "Echo" ], "Priority parameter filters");
let priority_levels = ((cache.priorityGroups || {})["proxy-priority-pg_parameters-out"] || {}).levels || [];
assert_array(priority_levels[1].outbounds, [], "Priority exclusion OR filter");
let metadata = cache.outboundMetadata || {};
if (metadata.protocols.Alpha != "vless" || metadata.protocols.Echo != "http")
fail("protocol metadata was not normalized");
if (metadata.transports.Alpha != "tcp" || metadata.transports.Beta != "ws" || metadata.transports.Delta != "xhttp")
fail("transport metadata was not normalized");
if (metadata.securities.Alpha != "tls" || metadata.securities.Beta != "reality" || metadata.securities.Gamma != "none")
fail("security metadata was not normalized");
' "$WORK_DIR/config.json" "$WORK_DIR/config.json.section-cache/proxy.json" ||
fail "URLTest and Priority proxy parameter filtering"
node - "$WORK_DIR/fixture.json" "$WORK_DIR/invalid.json" <<'JS'
const fs = require('fs');
const fixture = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
fixture.urltest[0].include_protocols = ['invalid'];
fs.writeFileSync(process.argv[3], JSON.stringify(fixture));
JS
if validate_fixture "$WORK_DIR/invalid.json" >/dev/null 2>&1; then
fail "invalid proxy protocol should be rejected"
fi
printf 'proxy parameter filter regression tests passed\n'