feat: add DoH blocking, subscription keyword filtering, bulk proxy text inputs, and insecure TLS flag

This commit is contained in:
Dushnilin
2026-09-09 22:36:12 +03:00
parent 3ee5cf2598
commit b51809c81d
26 changed files with 12547 additions and 9882 deletions
File diff suppressed because it is too large Load Diff
@@ -590,7 +590,9 @@ function createProfileContent(section) {
.map((l) => l.trim())
.filter(Boolean);
for (const line of lines) {
const clean = line.replace(/^(full:|keyword:|regex:)/, "").replace(/^\*?\./, "");
const clean = line
.replace(/^(full:|keyword:|regex:)/, "")
.replace(/^\*?\./, "");
if (/^(full:|keyword:|regex:)/.test(line) && !clean) {
return _("Invalid domain: empty prefix value");
}
@@ -673,7 +675,9 @@ function createProfileContent(section) {
style: "width:100%;min-height:100px;box-sizing:border-box;",
placeholder: "tiktok.com\nroblox.com",
});
const strVal = Array.isArray(cfgvalue) ? cfgvalue.join("\n") : (cfgvalue || "");
const strVal = Array.isArray(cfgvalue)
? cfgvalue.join("\n")
: cfgvalue || "";
if (strVal) textarea.value = strVal;
container.appendChild(textarea);
return container;
@@ -1229,7 +1233,9 @@ function createParentalContent(section) {
.map((l) => l.trim())
.filter(Boolean);
for (const line of lines) {
const clean = line.replace(/^(full:|keyword:|regex:)/, "").replace(/^\*?\./, "");
const clean = line
.replace(/^(full:|keyword:|regex:)/, "")
.replace(/^\*?\./, "");
if (/^(full:|keyword:|regex:)/.test(line) && !clean) {
return _("Invalid domain: empty prefix value");
}
@@ -1314,7 +1320,9 @@ function createParentalContent(section) {
style: "width:100%;min-height:110px;box-sizing:border-box;",
placeholder: "youtube.com\ngooglevideo.com",
});
const strVal = Array.isArray(cfgvalue) ? cfgvalue.join("\n") : (cfgvalue || "");
const strVal = Array.isArray(cfgvalue)
? cfgvalue.join("\n")
: cfgvalue || "";
if (strVal) textarea.value = strVal;
container.appendChild(textarea);
return container;
@@ -2447,6 +2447,7 @@ function subscriptionUrlSettingsKeys() {
"include_urltest_groups",
"hide_urltest_group_outbounds",
"hide_detour_outbounds",
"subscription_insecure",
];
}
@@ -2473,6 +2474,7 @@ function defaultSubscriptionUrlSettings() {
include_urltest_groups: "1",
hide_urltest_group_outbounds: "1",
hide_detour_outbounds: "1",
subscription_insecure: "0",
};
}
@@ -2872,6 +2874,17 @@ function addSubscriptionUrlItemOptions(itemSection, options = {}) {
);
o.default = "1";
o.rmempty = false;
o = itemSection.option(
form.Flag,
"subscription_insecure",
_("Allow insecure TLS"),
_(
"Download this subscription without TLS certificate verification (for panels with self-signed or mismatched certificates)",
),
);
o.default = "0";
o.rmempty = false;
}
function addInterfaceItemOptions(itemSection) {
@@ -5521,7 +5534,10 @@ function parseCommentAwareListTokens(value) {
let offset = 0;
lines.forEach((line, index) => {
const cleanLine = line.replace(/^(full|keyword|regex):[ \t]*(\/\/|#).*$/, "");
const cleanLine = line.replace(
/^(full|keyword|regex):[ \t]*(\/\/|#).*$/,
"",
);
const hashIndex = cleanLine.indexOf("#");
const slashIndex = cleanLine.indexOf("//");
let commentIndex = -1;
@@ -5534,7 +5550,8 @@ function parseCommentAwareListTokens(value) {
commentIndex = slashIndex;
}
const source = commentIndex >= 0 ? cleanLine.slice(0, commentIndex) : cleanLine;
const source =
commentIndex >= 0 ? cleanLine.slice(0, commentIndex) : cleanLine;
const matcher = /[^,\s]+/g;
let match;
@@ -9944,6 +9961,32 @@ function createSectionContent(section) {
};
outboundNameSourceOptions.set("selector_proxy_links", o);
o = section.taboption(
"settings",
form.TextValue,
"selector_proxy_links_text",
_("Connection links (text)"),
_("Paste proxy links one per line; they are added to the connection list"),
);
o.depends("action", "connection");
o.rmempty = true;
o.modalonly = true;
o.rows = 5;
o = section.taboption(
"settings",
form.TextValue,
"urltest_proxy_links_text",
_("URLTest links (text)"),
_(
"Paste proxy links one per line; they form a URLTest group with automatic fastest selection",
),
);
o.depends("action", "connection");
o.rmempty = true;
o.modalonly = true;
o.rows = 5;
o = section.taboption(
"settings",
SettingsDynamicList,
@@ -9992,6 +10035,34 @@ function createSectionContent(section) {
);
};
o = section.taboption(
"settings",
form.DynamicList,
"subscription_filter_include_keywords",
_("Subscription include keywords"),
_(
"Keep only subscription nodes whose name contains any of these keywords (empty keeps all). Case-insensitive, emoji supported.",
),
);
o.depends("action", "connection");
o.rmempty = true;
o.modalonly = true;
o.placeholder = _("Add include keyword");
o = section.taboption(
"settings",
form.DynamicList,
"subscription_filter_exclude_keywords",
_("Subscription exclude keywords"),
_(
"Drop subscription nodes whose name contains any of these keywords. Case-insensitive, emoji supported.",
),
);
o.depends("action", "connection");
o.rmempty = true;
o.modalonly = true;
o.placeholder = _("Add exclude keyword");
o = section.taboption(
"settings",
InterfaceSettingsDynamicList,
@@ -1468,8 +1468,10 @@ function buildAwgClientConfig(sectionId) {
const awgVer = uci.get(UCI_PACKAGE, sectionId, "awg_version") || "2.0";
if (awgVer === "3.0" || awgVer === "3.1") {
const hpk = uci.get(UCI_PACKAGE, sectionId, "awg_header_protection_key") || "";
const cpa = uci.get(UCI_PACKAGE, sectionId, "awg_content_padding_addition") || "";
const hpk =
uci.get(UCI_PACKAGE, sectionId, "awg_header_protection_key") || "";
const cpa =
uci.get(UCI_PACKAGE, sectionId, "awg_content_padding_addition") || "";
if (hpk) conf += `HeaderProtectionKey = ${hpk}\n`;
if (cpa) conf += `ContentPaddingAddition = ${cpa}\n`;
}
@@ -1478,7 +1480,8 @@ function buildAwgClientConfig(sectionId) {
const rkt = uci.get(UCI_PACKAGE, sectionId, "awg_rekey_timeout") || "";
const rja = uci.get(UCI_PACKAGE, sectionId, "awg_reject_after_time") || "";
const kpt = uci.get(UCI_PACKAGE, sectionId, "awg_keepalive_timeout") || "";
const mha = uci.get(UCI_PACKAGE, sectionId, "awg_max_handshake_attempts") || "";
const mha =
uci.get(UCI_PACKAGE, sectionId, "awg_max_handshake_attempts") || "";
if (rka) conf += `RekeyAfterTime = ${rka}\n`;
if (rkt) conf += `RekeyTimeout = ${rkt}\n`;
if (rja) conf += `RejectAfterTime = ${rja}\n`;
@@ -3470,7 +3473,9 @@ function createServerContent(section, options = {}) {
form.ListValue,
"awg_version",
_("AmneziaWG Version"),
_("Protocol version: 2.0 (standard integer headers) or 3.1 (header protection key, padding ranges, advanced timers)"),
_(
"Protocol version: 2.0 (standard integer headers) or 3.1 (header protection key, padding ranges, advanced timers)",
),
);
o.modalonly = true;
o.rmempty = false;
@@ -1440,9 +1440,13 @@ function createSettingsContent(section, capabilities) {
// path-style DoH (e.g. "cloudflare-dns.com/dns-query"). This mirrors
// the backend validator.uc check so the user gets immediate feedback.
if (normalized.includes("://"))
return _("Fallback DNS servers must be plain UDP (IP or hostname). DoH/DoT URLs are not allowed here.");
return _(
"Fallback DNS servers must be plain UDP (IP or hostname). DoH/DoT URLs are not allowed here.",
);
if (normalized.includes("/"))
return _("Fallback DNS servers must be plain UDP (IP or hostname). Path-style DoH (e.g. domain/path) is not allowed here.");
return _(
"Fallback DNS servers must be plain UDP (IP or hostname). Path-style DoH (e.g. domain/path) is not allowed here.",
);
const validation = main.validateDNS(normalized);
return validation.valid ? true : validation.message;
};
@@ -1848,6 +1852,18 @@ function createSettingsContent(section, capabilities) {
o.default = "0";
o.rmempty = false;
o = section.taboption(
"services",
form.Flag,
"block_doh",
_("Block DoH"),
_(
"Block well-known DNS-over-HTTPS resolver IPs so LAN clients cannot bypass the router DNS",
),
);
o.default = "0";
o.rmempty = false;
o = section.taboption(
"services",
form.Flag,