mirror of
https://github.com/caiwx86/small-packages.git
synced 2026-07-27 08:31:44 +08:00
update 2026-06-21 16:24:44
This commit is contained in:
parent
c6a479c0b9
commit
1a2f3e74c1
@ -3,8 +3,8 @@ include $(TOPDIR)/rules.mk
|
||||
LUCI_TITLE:=luci-app-ssr-plus
|
||||
LUCI_PKGARCH:=all
|
||||
PKG_NAME:=luci-app-ssr-plus
|
||||
PKG_VERSION:=193
|
||||
PKG_RELEASE:=23
|
||||
PKG_VERSION:=195
|
||||
PKG_RELEASE:=5
|
||||
|
||||
PKG_CONFIG_DEPENDS:= \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy \
|
||||
@ -99,7 +99,6 @@ endchoice
|
||||
|
||||
choice
|
||||
prompt "V2ray-core Selection"
|
||||
default PACKAGE_$(PKG_NAME)_INCLUDE_Xray
|
||||
default PACKAGE_$(PKG_NAME)_INCLUDE_NONE_V2RAY
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_NONE_V2RAY
|
||||
|
||||
11
luci-app-ssr-plus/Makefile.rej
Normal file
11
luci-app-ssr-plus/Makefile.rej
Normal file
@ -0,0 +1,11 @@
|
||||
--- luci-app-ssr-plus/Makefile
|
||||
+++ luci-app-ssr-plus/Makefile
|
||||
@@ -118,7 +118,7 @@ endchoice
|
||||
|
||||
choice
|
||||
prompt "V2ray-core Selection"
|
||||
- default PACKAGE_$(PKG_NAME)_INCLUDE_Xray if aarch64||arm||i386||x86_64
|
||||
+ default PACKAGE_$(PKG_NAME)_INCLUDE_Xray
|
||||
default PACKAGE_$(PKG_NAME)_INCLUDE_NONE_V2RAY
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_NONE_V2RAY
|
||||
@ -817,6 +817,13 @@ function add_subscribe_item()
|
||||
return
|
||||
end
|
||||
|
||||
local commit_subscribe = luci.sys.call("uci -q commit shadowsocksr >/dev/null 2>&1")
|
||||
if commit_subscribe ~= 0 then
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json({ ret = 0, error = "commit failed" })
|
||||
return
|
||||
end
|
||||
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json({ ret = 1, sid = sid, alias = alias, enabled = "1" })
|
||||
end
|
||||
|
||||
@ -417,6 +417,9 @@ end
|
||||
if is_finded("xray") then
|
||||
o:value("hysteria2", translate("Hysteria2"))
|
||||
end
|
||||
if is_finded("mihomo") then
|
||||
o:value("snell", translate("Snell"))
|
||||
end
|
||||
o:value("socks", translate("Socks"))
|
||||
o:value("http", translate("HTTP"))
|
||||
o:depends("type", "v2ray")
|
||||
@ -500,6 +503,11 @@ o:depends({type = "v2ray", v2ray_protocol = "socks", socks_ver = "5", auth_enabl
|
||||
o:depends({type = "v2ray", v2ray_protocol = "shadowsocks"})
|
||||
o:depends({type = "v2ray", v2ray_protocol = "trojan"})
|
||||
|
||||
o = s:option(Value, "snell_psk", translate("Snell PSK"))
|
||||
o.password = true
|
||||
o.rmempty = true
|
||||
o:depends({type = "v2ray", v2ray_protocol = "snell"})
|
||||
|
||||
o = s:option(ListValue, "encrypt_method", translate("Encrypt Method"))
|
||||
for _, v in ipairs(encrypt_methods) do
|
||||
o:value(v)
|
||||
@ -946,6 +954,28 @@ o.rmempty = true
|
||||
o.default = "5"
|
||||
o:depends({type = "v2ray", v2ray_protocol = "socks"})
|
||||
|
||||
o = s:option(ListValue, "snell_version", translate("Snell Version"))
|
||||
o:value("1", "v1")
|
||||
o:value("2", "v2")
|
||||
o:value("3", "v3")
|
||||
o:value("4", "v4")
|
||||
o:value("5", "v5")
|
||||
o.default = "4"
|
||||
o.rmempty = true
|
||||
o:depends({type = "v2ray", v2ray_protocol = "snell"})
|
||||
|
||||
o = s:option(ListValue, "snell_obfs", translate("Snell Obfs"))
|
||||
o:value("", translate("Disable"))
|
||||
o:value("http", "HTTP")
|
||||
o:value("tls", "TLS")
|
||||
o.default = ""
|
||||
o.rmempty = true
|
||||
o:depends({type = "v2ray", v2ray_protocol = "snell"})
|
||||
|
||||
o = s:option(Value, "snell_obfs_host", translate("Snell Obfs Host"))
|
||||
o.rmempty = true
|
||||
o:depends({type = "v2ray", v2ray_protocol = "snell"})
|
||||
|
||||
-- 传输协议
|
||||
o = s:option(ListValue, "transport", translate("Transport"))
|
||||
o:value("raw", "RAW (TCP)")
|
||||
|
||||
@ -7,7 +7,7 @@ require "nixio.fs"
|
||||
local m, s, o
|
||||
local sid = arg[1]
|
||||
|
||||
-- 加密方式(SS/SSR用)
|
||||
-- 加密方式(SS/SSR用)
|
||||
local encrypt_methods = {
|
||||
"rc4-md5", "rc4-md5-6", "rc4", "table",
|
||||
"aes-128-cfb", "aes-192-cfb", "aes-256-cfb",
|
||||
@ -23,13 +23,13 @@ local encrypt_methods_ss = {
|
||||
"2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
|
||||
}
|
||||
|
||||
-- Shadowsocks 加密方法(Xray 版)
|
||||
-- Shadowsocks 加密方法(Xray 版)
|
||||
local v_ss_encrypt_method_list = {
|
||||
"aes-128-cfb", "aes-256-cfb", "aes-128-gcm", "aes-256-gcm",
|
||||
"chacha20", "chacha20-ietf", "chacha20-poly1305", "chacha20-ietf-poly1305"
|
||||
}
|
||||
|
||||
-- 伪装类型(用于 mKCP/QUIC)
|
||||
-- 伪装类型(用于 mKCP/QUIC)
|
||||
local header_type_list = {
|
||||
"none", "srtp", "utp", "wechat-video", "dtls", "wireguard"
|
||||
}
|
||||
@ -50,7 +50,7 @@ s = m:section(NamedSection, sid, "server_config")
|
||||
s.anonymous = true
|
||||
s.addremove = false
|
||||
|
||||
-- ========== 基本设置 ==========
|
||||
-- ========== 基本设置 ==========
|
||||
o = s:option(Flag, "enable", translate("Enable"))
|
||||
o.default = 1
|
||||
o.rmempty = false
|
||||
@ -63,12 +63,14 @@ end
|
||||
if nixio.fs.access("/usr/bin/ssr-server") then
|
||||
o:value("ssr", translate("ShadowsocksR"))
|
||||
end
|
||||
-- Xray 协议支持
|
||||
if nixio.fs.access("/usr/bin/xray") or nixio.fs.access("/usr/libexec/xray") then
|
||||
o:value("vmess", "VMess (Xray)")
|
||||
o:value("vless", "VLESS (Xray)")
|
||||
o:value("trojan", "Trojan (Xray)")
|
||||
o:value("shadowsocks", "Shadowsocks (Xray)")
|
||||
-- Xray 协议支持
|
||||
if nixio.fs.access("/usr/bin/mihomo") or nixio.fs.access("/usr/libexec/mihomo")
|
||||
or nixio.fs.access("/usr/bin/xray") or nixio.fs.access("/usr/libexec/xray")
|
||||
then
|
||||
o:value("vmess", "VMess (Mihomo/Xray)")
|
||||
o:value("vless", "VLESS (Mihomo/Xray)")
|
||||
o:value("trojan", "Trojan (Mihomo/Xray)")
|
||||
o:value("shadowsocks", "Shadowsocks (Mihomo/Xray)")
|
||||
end
|
||||
o.default = "socks5"
|
||||
|
||||
@ -99,7 +101,7 @@ o:depends("type", "ssr")
|
||||
o:depends("type", "trojan")
|
||||
o:depends("type", "shadowsocks")
|
||||
|
||||
-- ========== SS/SSR 协议特有 ==========
|
||||
-- ========== SS/SSR 协议特有 ==========
|
||||
o = s:option(ListValue, "encrypt_method", translate("Encrypt Method"))
|
||||
for _, v in ipairs(encrypt_methods) do o:value(v) end
|
||||
o.rmempty = false
|
||||
@ -128,8 +130,8 @@ o.rmempty = false
|
||||
o:depends("type", "ss")
|
||||
o:depends("type", "ssr")
|
||||
|
||||
-- ========== Xray 协议通用字段 ==========
|
||||
-- 用户备注
|
||||
-- ========== Xray 协议通用字段 ==========
|
||||
-- 用户备注
|
||||
o = s:option(Value, "remarks", translate("Remarks"))
|
||||
o.default = translate("Remarks")
|
||||
o.rmempty = true
|
||||
@ -138,7 +140,7 @@ o:depends("type", "vless")
|
||||
o:depends("type", "trojan")
|
||||
o:depends("type", "shadowsocks")
|
||||
|
||||
-- 用户等级
|
||||
-- 用户等级
|
||||
o = s:option(Value, "level", translate("User Level"))
|
||||
o.datatype = "uinteger"
|
||||
o.default = 1
|
||||
@ -148,21 +150,21 @@ o:depends("type", "vless")
|
||||
o:depends("type", "shadowsocks")
|
||||
o:depends("type", "trojan")
|
||||
|
||||
-- ========== Xray 协议认证字段 ==========
|
||||
-- UUID (用于 vmess/vless)
|
||||
-- ========== Xray 协议认证字段 ==========
|
||||
-- UUID (用于 vmess/vless)
|
||||
o = s:option(Value, "uuid", translate("UUID"))
|
||||
o.description = translate("Required for VMess/VLESS. Generate with: uuidgen")
|
||||
o.rmempty = true
|
||||
o:depends("type", "vmess")
|
||||
o:depends("type", "vless")
|
||||
|
||||
-- Trojan 密码
|
||||
-- Trojan 密码
|
||||
o = s:option(Value, "trojan_password", translate("Trojan Password"))
|
||||
o.password = true
|
||||
o.rmempty = true
|
||||
o:depends("type", "trojan")
|
||||
|
||||
-- Shadowsocks 密码和加密(Xray 内置)
|
||||
-- Shadowsocks 密码和加密(Xray 内置)
|
||||
o = s:option(Value, "ss_password", translate("Shadowsocks Password"))
|
||||
o.password = true
|
||||
o.rmempty = true
|
||||
@ -195,7 +197,7 @@ o.default = "none"
|
||||
o.rmempty = true
|
||||
o:depends("type", "vless")
|
||||
|
||||
-- ========== TLS / XTLS 设置 ==========
|
||||
-- ========== TLS / XTLS 设置 ==========
|
||||
o = s:option(Flag, "tls", translate("TLS"))
|
||||
o.default = 0
|
||||
o.rmempty = true
|
||||
@ -234,7 +236,7 @@ o.description = translate("e.g.: /etc/ssl/private.key")
|
||||
o.rmempty = true
|
||||
o:depends("tls", "1")
|
||||
|
||||
-- ========== 传输层设置 (Transport) ==========
|
||||
-- ========== 传输层设置 (Transport) ==========
|
||||
o = s:option(ListValue, "transport", translate("Transport Protocol"))
|
||||
o:value("tcp", "TCP")
|
||||
o:value("mkcp", "mKCP")
|
||||
@ -248,7 +250,7 @@ o:depends("type", "vmess")
|
||||
o:depends("type", "vless")
|
||||
o:depends("type", "trojan")
|
||||
|
||||
-- ----- WebSocket 设置 -----
|
||||
-- ----- WebSocket 设置 -----
|
||||
o = s:option(Value, "ws_host", translate("WebSocket Host"))
|
||||
o.rmempty = true
|
||||
o:depends("transport", "ws")
|
||||
@ -264,7 +266,7 @@ o:depends("type", "vmess")
|
||||
o:depends("type", "vless")
|
||||
o:depends("type", "trojan")
|
||||
|
||||
-- ----- HTTP/2 设置 -----
|
||||
-- ----- HTTP/2 设置 -----
|
||||
o = s:option(Value, "h2_host", translate("HTTP/2 Host"))
|
||||
o.rmempty = true
|
||||
o:depends("transport", "h2")
|
||||
@ -278,7 +280,7 @@ o:depends("transport", "h2")
|
||||
o:depends("type", "vmess")
|
||||
o:depends("type", "vless")
|
||||
|
||||
-- ----- TCP 伪装设置 -----
|
||||
-- ----- TCP 伪装设置 -----
|
||||
o = s:option(ListValue, "tcp_guise", translate("TCP Camouflage Type"))
|
||||
o:value("none", "none")
|
||||
o:value("http", "http")
|
||||
@ -296,7 +298,7 @@ o = s:option(DynamicList, "tcp_guise_http_path", translate("HTTP Path"))
|
||||
o.rmempty = true
|
||||
o:depends("tcp_guise", "http")
|
||||
|
||||
-- ----- mKCP 设置 -----
|
||||
-- ----- mKCP 设置 -----
|
||||
o = s:option(ListValue, "mkcp_guise", translate("mKCP Camouflage Type"))
|
||||
for _, v in ipairs(header_type_list) do o:value(v) end
|
||||
o.default = "none"
|
||||
@ -349,13 +351,13 @@ o.datatype = "uinteger"
|
||||
o.rmempty = true
|
||||
o:depends("transport", "mkcp")
|
||||
|
||||
-- ----- DomainSocket 设置 -----
|
||||
-- ----- DomainSocket 设置 -----
|
||||
o = s:option(Value, "ds_path", translate("DomainSocket Path"))
|
||||
o.description = translate("A legal file path. This file must not exist before running.")
|
||||
o.rmempty = true
|
||||
o:depends("transport", "ds")
|
||||
|
||||
-- ----- QUIC 设置 -----
|
||||
-- ----- QUIC 设置 -----
|
||||
o = s:option(ListValue, "quic_security", translate("QUIC Security"))
|
||||
o:value("none", "none")
|
||||
o:value("aes-128-gcm", "aes-128-gcm")
|
||||
@ -374,7 +376,7 @@ o.default = "none"
|
||||
o.rmempty = true
|
||||
o:depends("transport", "quic")
|
||||
|
||||
-- ========== 访问控制 ==========
|
||||
-- ========== 访问控制 ==========
|
||||
o = s:option(Flag, "bind_local", translate("Bind Local Only"))
|
||||
o.description = translate("When selected, it can only be accessed locally. Recommended when using reverse proxies.")
|
||||
o.default = 0
|
||||
|
||||
@ -458,6 +458,7 @@ o.remove = function() end
|
||||
|
||||
o = s:option(Value, "filter_words", translate("Subscribe Filter Words"))
|
||||
o.rmempty = true
|
||||
o.default = "过期/重置/套餐/剩余/网址/QQ群/官网/防失联/回国"
|
||||
o.description = translate("Filter Words splited by /")
|
||||
o:depends("_subscribe_advanced_toggle", "1")
|
||||
preserve_when_hidden(o, "_subscribe_advanced_toggle", "1")
|
||||
@ -544,6 +545,26 @@ if has_mihomo then
|
||||
o = s:option(DummyValue, "_upload_clash_yaml", translate("Upload Custom YAML File"))
|
||||
o.template = "shadowsocksr/clash_yaml_upload"
|
||||
o.description = translate("Upload a custom Clash/Mihomo YAML file. The file will be preprocessed and saved as a local Clash node.")
|
||||
|
||||
o = s:option(Flag, "sub_convert", translate("Subscribe Convert Online"))
|
||||
o.description = translate("Convert subscriptions to Clash/Mihomo YAML with a subscription template URL.")
|
||||
o.default = "0"
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(Value, "convert_address", translate("Convert Address"))
|
||||
o.default = "https://api.asailor.org/sub"
|
||||
o.placeholder = "https://api.asailor.org/sub"
|
||||
o:value("https://api.asailor.org/sub", "api.asailor.org")
|
||||
o:value("https://api.wcc.best/sub", "api.wcc.best")
|
||||
o.rmempty = true
|
||||
o:depends("sub_convert", "1")
|
||||
|
||||
o = s:option(Value, "template_url", translate("Subscribe Template URL"))
|
||||
o.default = "https://raw.githubusercontent.com/Fzlwhyc/OpenClash-Templates/main//fzlwhyc-openclash.ini"
|
||||
o.placeholder = "https://raw.githubusercontent.com/Fzlwhyc/OpenClash-Templates/main//fzlwhyc-openclash.ini"
|
||||
o:value("https://raw.githubusercontent.com/Fzlwhyc/OpenClash-Templates/main//fzlwhyc-openclash.ini", "Fzlwhyc")
|
||||
o.rmempty = true
|
||||
o:depends("sub_convert", "1")
|
||||
end
|
||||
|
||||
s:append(cbi.Template("shadowsocksr/subscribe_schedule_compact"))
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
local map = self.map
|
||||
|
||||
local has_ss_rust = luci.sys.exec('type -t -p sslocal 2>/dev/null || type -t -p ssserver 2>/dev/null') ~= ""
|
||||
local has_mihomo = luci.sys.exec('type -t -p mihomo -p /usr/libexec/mihomo 2>/dev/null') ~= ""
|
||||
local has_mihomo = luci.sys.exec('type -t -p mihomo 2>/dev/null || { test -x /usr/libexec/mihomo && echo /usr/libexec/mihomo; }') ~= ""
|
||||
local has_xray = luci.sys.exec('type -t -p xray 2>/dev/null') ~= ""
|
||||
-%>
|
||||
<script type="text/javascript">
|
||||
@ -11,6 +11,9 @@ local has_xray = luci.sys.exec('type -t -p xray 2>/dev/null') ~= ""
|
||||
let has_ss_rust = "<%=has_ss_rust%>"
|
||||
let has_mihomo = "<%=has_mihomo%>"
|
||||
let has_xray = "<%=has_xray%>"
|
||||
function hasMihomoOrXray() {
|
||||
return has_mihomo === "true" || has_xray === "true";
|
||||
}
|
||||
|
||||
function padright(str, cnt, pad) {
|
||||
return str + Array(cnt + 1).join(pad);
|
||||
@ -112,6 +115,31 @@ function import_ssr_url(btn, urlname, sid) {
|
||||
var event = document.createEvent("HTMLEvents");
|
||||
event.initEvent("change", true, true);
|
||||
switch (ssu[0]) {
|
||||
case "snell":
|
||||
if (has_mihomo !== "true") {
|
||||
s.innerHTML = "<font style=\'color:red\'><%:No available Mihomo core to import this Snell node.%></font>";
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
var url = new URL("http://" + ssu[1]);
|
||||
var params = url.searchParams;
|
||||
} catch(e) {
|
||||
alert(e);
|
||||
return false;
|
||||
}
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].value = "v2ray";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].dispatchEvent(event);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.v2ray_protocol')[0].value = "snell";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.v2ray_protocol')[0].dispatchEvent(event);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server')[0].value = normalizeHost(url.hostname);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server_port')[0].value = url.port;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.snell_psk')[0].value = decodeURIComponent(url.username || params.get("psk") || params.get("password") || "");
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.snell_version')[0].value = params.get("version") || params.get("v") || "4";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.snell_obfs')[0].value = params.get("obfs") || params.get("obfs-mode") || params.get("obfs_mode") || "";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.snell_obfs_host')[0].value = params.get("obfs-host") || params.get("obfs_host") || params.get("host") || "";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.alias')[0].value = url.hash ? decodeURIComponent(url.hash.slice(1)) : "";
|
||||
s.innerHTML = "<font style=\'color:green\'><%:Import configuration information successfully.%></font>";
|
||||
return false;
|
||||
case "hysteria2":
|
||||
case "hy2":
|
||||
try {
|
||||
@ -122,8 +150,8 @@ function import_ssr_url(btn, urlname, sid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Xray Hysteria2 导入函数
|
||||
function importAsXrayHy2() {
|
||||
// Mihomo/Xray Hysteria2 导入函数
|
||||
function importAsV2RayHy2() {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].value = "v2ray"
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].dispatchEvent(event);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.v2ray_protocol')[0].value = (ssu[0] === "hy2") ? "hysteria2" : ssu[0];
|
||||
@ -152,11 +180,11 @@ function import_ssr_url(btn, urlname, sid) {
|
||||
}
|
||||
}
|
||||
|
||||
if (has_xray !== "true") {
|
||||
s.innerHTML = "<font style=\'color:red\'><%:No available Xray core to import this Hysteria2 node.%></font>";
|
||||
if (!hasMihomoOrXray()) {
|
||||
s.innerHTML = "<font style=\'color:red\'><%:No available Mihomo or Xray core to import this Hysteria2 node.%></font>";
|
||||
return false;
|
||||
}
|
||||
importAsXrayHy2();
|
||||
importAsV2RayHy2();
|
||||
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.alias')[0].value = url.hash ? decodeURIComponent(url.hash.slice(1)) : "";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server')[0].value = normalizeHost(url.hostname);
|
||||
@ -535,19 +563,19 @@ function import_ssr_url(btn, urlname, sid) {
|
||||
}
|
||||
|
||||
let finalSSBackend;
|
||||
const finalXray = (params.security && params.security !== "none") || // TLS/XTLS/Reality
|
||||
const finalV2Ray = (params.security && params.security !== "none") || // TLS/XTLS/Reality
|
||||
(params.type && ["ws", "httpupgrade", "xhttp", "splithttp", "grpc", "quic", "kcp", "http", "h2"].includes(params.type));
|
||||
if (finalXray && has_xray === "true") {
|
||||
if (finalV2Ray && hasMihomoOrXray()) {
|
||||
finalSSBackend = "v2ray";
|
||||
} else {
|
||||
finalSSBackend = preferredSSBackend();
|
||||
}
|
||||
if (finalSSBackend === null) {
|
||||
s.innerHTML = "<font style=\'color:red\'><%:No available core (Shadowsocks or Xray) to import this node.%></font>";
|
||||
s.innerHTML = "<font style=\'color:red\'><%:No available core (Shadowsocks, Mihomo or Xray) to import this node.%></font>";
|
||||
return false;
|
||||
}
|
||||
if (finalSSBackend === "v2ray" && ssNode.plugin && ssNode.plugin !== "none") {
|
||||
s.innerHTML = "<font style='color:red'>Xray fallback does not support importing Shadowsocks links with SIP003 plugins.</font>";
|
||||
s.innerHTML = "<font style='color:red'>V2Ray fallback does not support importing Shadowsocks links with SIP003 plugins.</font>";
|
||||
return false;
|
||||
}
|
||||
if (finalSSBackend === "v2ray") importAsXraySS(ssNode);
|
||||
@ -625,8 +653,8 @@ function import_ssr_url(btn, urlname, sid) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Xray Trojan 导入逻辑
|
||||
function importAsXrayTrojan() {
|
||||
// Mihomo/Xray Trojan 导入逻辑
|
||||
function importAsV2RayTrojan() {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.alias')[0].value = url.hash ? decodeURIComponent(url.hash.slice(1)) : "";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].value = "v2ray";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].dispatchEvent(event);
|
||||
@ -732,13 +760,12 @@ function import_ssr_url(btn, urlname, sid) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 主逻辑:Trojan 统一使用 Xray
|
||||
function hasXrayCore() { return has_xray === "true"; }
|
||||
if (!hasXrayCore()) {
|
||||
s.innerHTML = "<font style=\'color:red\'><%:No available core (Xray) to import this node.%></font>";
|
||||
// 主逻辑:Trojan 使用 Mihomo/Xray 兼容的 v2ray 节点格式
|
||||
if (!hasMihomoOrXray()) {
|
||||
s.innerHTML = "<font style=\'color:red\'><%:No available core (Mihomo or Xray) to import this node.%></font>";
|
||||
return false;
|
||||
}
|
||||
importAsXrayTrojan();
|
||||
importAsV2RayTrojan();
|
||||
return false;
|
||||
case "vmess":
|
||||
var sstr = b64DecodeUnicode((ssu[1] || "").replace(/#.*/, "").trim());
|
||||
|
||||
@ -133,23 +133,56 @@ end
|
||||
);
|
||||
}
|
||||
|
||||
function createSubscribeCell(sid, name, title, widget, html) {
|
||||
var td = document.createElement('td');
|
||||
td.className = 'td cbi-value-field';
|
||||
td.setAttribute('data-name', name);
|
||||
if (widget) td.setAttribute('data-widget', widget);
|
||||
if (title) td.setAttribute('data-title', title);
|
||||
|
||||
var wrap = document.createElement('div');
|
||||
wrap.id = 'cbi-shadowsocksr-' + sid + '-' + name;
|
||||
wrap.innerHTML = html;
|
||||
td.appendChild(wrap);
|
||||
return td;
|
||||
}
|
||||
|
||||
function createFallbackRow(sid) {
|
||||
var newRow = document.createElement('tr');
|
||||
newRow.className = 'tr cbi-section-table-row cbi-rowstyle-1';
|
||||
|
||||
newRow.appendChild(createSubscribeCell(sid, 'enabled', '<%:Enable%>', 'Flag',
|
||||
'<input type="hidden" value="0" name="cbi.cbe.shadowsocksr.' + sid + '.enabled" />' +
|
||||
'<input class="cbi-input-checkbox" type="checkbox" value="1" name="cbid.shadowsocksr.' + sid + '.enabled" id="cbid.shadowsocksr.' + sid + '.enabled" />'));
|
||||
newRow.appendChild(createSubscribeCell(sid, 'alias', '<%:Alias%>', 'Value',
|
||||
'<input class="cbi-input-text" type="text" name="cbid.shadowsocksr.' + sid + '.alias" id="cbid.shadowsocksr.' + sid + '.alias" value="" />'));
|
||||
newRow.appendChild(createSubscribeCell(sid, 'url', '<%:Subscribe URL%>', 'Value',
|
||||
'<input class="cbi-input-text" type="text" name="cbid.shadowsocksr.' + sid + '.url" id="cbid.shadowsocksr.' + sid + '.url" value="" />'));
|
||||
|
||||
var actions = document.createElement('td');
|
||||
actions.className = 'td cbi-section-table-cell nowrap cbi-section-actions';
|
||||
actions.innerHTML = '<div class="ssr-subscribe-actions">' +
|
||||
'<input class="btn cbi-button cbi-button-save" type="button" value="<%:Save%>" title="<%:Save%>" onclick="saveAllSubscribeItems(this)" />' +
|
||||
'<input class="btn cbi-button cbi-button-remove" type="button" value="<%:Delete%>" data-sid="' + sid + '" onclick="deleteSubscribeItem(this); return false;" />' +
|
||||
'<input type="button" class="btn cbi-button cbi-button-apply" value="<%:Update%>" data-original-value="<%:Update%>" data-sid="' + sid + '" data-title="Subscribe" onclick="return window.ssrUpdateSubscribeItem(this)" />' +
|
||||
'</div>';
|
||||
newRow.appendChild(actions);
|
||||
return newRow;
|
||||
}
|
||||
|
||||
function createEmptyRow(sid) {
|
||||
var templateRow = document.querySelector('#cbi-shadowsocksr-server_subscribe_item .cbi-section-table-row:not(.cbi-section-table-titles):not(.placeholder)');
|
||||
if (!templateRow) {
|
||||
console.error('No template row found');
|
||||
return null;
|
||||
}
|
||||
|
||||
var newRow = templateRow.cloneNode(true);
|
||||
newRow.id = 'cbi-shadowsocksr-server_subscribe_item-' + sid;
|
||||
var newRow = templateRow ? templateRow.cloneNode(true) : createFallbackRow(sid);
|
||||
newRow.id = 'cbi-shadowsocksr-' + sid;
|
||||
|
||||
var inputs = newRow.querySelectorAll('input');
|
||||
inputs.forEach(function(input) {
|
||||
if (input.name) {
|
||||
input.name = input.name.replace(/cbid\.shadowsocksr\.[^.]+\./, 'cbid.shadowsocksr.' + sid + '.');
|
||||
input.name = input.name.replace(/(cbid|cbi\.cbe)\.shadowsocksr\.[^.]+\./, '$1.shadowsocksr.' + sid + '.');
|
||||
}
|
||||
if (input.id) {
|
||||
input.id = input.id.replace(/cfg[^.]+/, sid);
|
||||
input.id = input.id.replace(/(cbid|cbi)-shadowsocksr-[^-]+-/, '$1-shadowsocksr-' + sid + '-');
|
||||
input.id = input.id.replace(/(cbid|cbi\.cbe)\.shadowsocksr\.[^.]+\./, '$1.shadowsocksr.' + sid + '.');
|
||||
}
|
||||
if (input.type === 'checkbox') {
|
||||
input.checked = false;
|
||||
@ -161,6 +194,10 @@ end
|
||||
input.disabled = false;
|
||||
});
|
||||
|
||||
newRow.querySelectorAll('[id^="cbi-shadowsocksr-"]').forEach(function(node) {
|
||||
node.id = node.id.replace(/cbi-shadowsocksr-[^-]+-/, 'cbi-shadowsocksr-' + sid + '-');
|
||||
});
|
||||
|
||||
var delBtn = newRow.querySelector('.cbi-button-remove');
|
||||
if (delBtn) {
|
||||
delBtn.setAttribute('data-sid', sid);
|
||||
|
||||
@ -500,6 +500,14 @@ msgstr ""
|
||||
msgid "Configure XHTTP Extra Settings (JSON format), see:"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:553
|
||||
msgid "Convert Address"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:549
|
||||
msgid "Convert subscriptions to Clash/Mihomo YAML with a subscription template URL."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:839
|
||||
msgid "Congestion control algorithm"
|
||||
msgstr ""
|
||||
@ -2353,6 +2361,10 @@ msgstr ""
|
||||
msgid "Subscribe Default Auto-Switch"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:548
|
||||
msgid "Subscribe Convert Online"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:459
|
||||
msgid "Subscribe Filter Words"
|
||||
msgstr ""
|
||||
@ -2365,6 +2377,10 @@ msgstr ""
|
||||
msgid "Subscribe Save Words"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:561
|
||||
msgid "Subscribe Template URL"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:551
|
||||
#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:575
|
||||
msgid "Subscribe URL"
|
||||
|
||||
@ -504,6 +504,14 @@ msgstr "组件升级"
|
||||
msgid "Configure XHTTP Extra Settings (JSON format), see:"
|
||||
msgstr "配置 XHTTP 额外设置(JSON 格式),具体请参见:"
|
||||
|
||||
#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:553
|
||||
msgid "Convert Address"
|
||||
msgstr "转换订阅模板API服务器"
|
||||
|
||||
#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:549
|
||||
msgid "Convert subscriptions to Clash/Mihomo YAML with a subscription template URL."
|
||||
msgstr "使用订阅转换模板 URL 将订阅转换为 Clash/Mihomo YAML。"
|
||||
|
||||
#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:839
|
||||
msgid "Congestion control algorithm"
|
||||
msgstr "拥塞控制算法"
|
||||
@ -2369,6 +2377,10 @@ msgstr "订阅高级设置"
|
||||
msgid "Subscribe Default Auto-Switch"
|
||||
msgstr "订阅新节点自动切换设置"
|
||||
|
||||
#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:548
|
||||
msgid "Subscribe Convert Online"
|
||||
msgstr "在线订阅转换"
|
||||
|
||||
#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:459
|
||||
msgid "Subscribe Filter Words"
|
||||
msgstr "订阅节点关键字过滤"
|
||||
@ -2381,6 +2393,10 @@ msgstr "订阅日志"
|
||||
msgid "Subscribe Save Words"
|
||||
msgstr "订阅节点关键字保留检查"
|
||||
|
||||
#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:561
|
||||
msgid "Subscribe Template URL"
|
||||
msgstr "订阅转换模板 URL"
|
||||
|
||||
#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:551
|
||||
#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:575
|
||||
msgid "Subscribe URL"
|
||||
|
||||
@ -214,7 +214,7 @@ is_builtin_dns_active() {
|
||||
ps_list | grep -v "grep" | grep -q "ssr-retcp"
|
||||
;;
|
||||
v2ray)
|
||||
ps_list | grep -v "grep" | grep -q "$TMP_PATH/.*ssr-retcp\\.json"
|
||||
ps_list | grep -v "grep" | grep -Eq "$TMP_PATH/.*ssr-retcp\\.(json|yaml)"
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
@ -284,6 +284,22 @@ get_ss_server_mihomo_runtime_file() {
|
||||
echo "$(get_ss_server_mihomo_workdir "$1")/config.yaml"
|
||||
}
|
||||
|
||||
get_v2ray_mihomo_workdir() {
|
||||
echo "$TMP_PATH/v2ray-$1"
|
||||
}
|
||||
|
||||
get_v2ray_mihomo_runtime_file() {
|
||||
echo "$(get_v2ray_mihomo_workdir "$1")/config.yaml"
|
||||
}
|
||||
|
||||
get_v2ray_server_mihomo_workdir() {
|
||||
echo "$TMP_PATH/v2ray-server-$1"
|
||||
}
|
||||
|
||||
get_v2ray_server_mihomo_runtime_file() {
|
||||
echo "$(get_v2ray_server_mihomo_workdir "$1")/config.yaml"
|
||||
}
|
||||
|
||||
link_mihomo_geodata() {
|
||||
local workdir="$1"
|
||||
|
||||
@ -366,6 +382,41 @@ prepare_ss_server_mihomo_runtime_config() {
|
||||
fi
|
||||
}
|
||||
|
||||
prepare_v2ray_mihomo_runtime_config() {
|
||||
local sid="$1"
|
||||
local local_port="$2"
|
||||
local socks_port="$3"
|
||||
local instance_key="${4:-$sid}"
|
||||
local run_mode="${5:-redir}"
|
||||
local runtime_file="$(get_v2ray_mihomo_runtime_file "$instance_key")"
|
||||
local workdir="$(get_v2ray_mihomo_workdir "$instance_key")"
|
||||
|
||||
[ -n "$sid" ] || return 1
|
||||
[ -n "$local_port" ] || return 1
|
||||
|
||||
mkdir -p "$workdir"
|
||||
if ! /usr/bin/lua "$CLASH_YAML_HELPER" v2ray "$sid" "$runtime_file" "$local_port" "$socks_port" "$run_mode" >/dev/null 2>&1; then
|
||||
echolog "V2Ray/Xray 单节点 Mihomo 运行配置生成失败:$sid"
|
||||
return 1
|
||||
fi
|
||||
|
||||
link_mihomo_geodata "$workdir"
|
||||
}
|
||||
|
||||
prepare_v2ray_server_mihomo_runtime_config() {
|
||||
local sid="$1"
|
||||
local runtime_file="$(get_v2ray_server_mihomo_runtime_file "$sid")"
|
||||
local workdir="$(get_v2ray_server_mihomo_workdir "$sid")"
|
||||
|
||||
[ -n "$sid" ] || return 1
|
||||
|
||||
mkdir -p "$workdir"
|
||||
if ! /usr/bin/lua "$CLASH_YAML_HELPER" v2ray_server "$sid" "$runtime_file" >/dev/null 2>&1; then
|
||||
echolog "V2Ray/Xray 服务端 Mihomo 配置生成失败:$sid"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
download_clash_config() {
|
||||
local sid="$1"
|
||||
local clash_url="$(uci_get_by_name "$sid" clash_url)"
|
||||
@ -419,7 +470,7 @@ clash_cache() {
|
||||
|
||||
filter_clash_yaml_proxies() {
|
||||
local yaml_file="$1"
|
||||
local filter_words="$(uci_get_by_type server_subscribe filter_words '过期时间/剩余流量')"
|
||||
local filter_words="$(uci_get_by_type server_subscribe filter_words '过期/重置/套餐/剩余/网址/QQ群/官网/防失联/回国')"
|
||||
local names_file="$TMP_PATH/clash-proxy-names.$$"
|
||||
local removed_file="$TMP_PATH/clash-proxy-removed.$$"
|
||||
local removed_count=0
|
||||
@ -697,6 +748,70 @@ use_mihomo_for_ss_rust_server() {
|
||||
has_mihomo_binary
|
||||
}
|
||||
|
||||
start_v2ray_with_mihomo() {
|
||||
local sid="$1"
|
||||
local local_port="$2"
|
||||
local socks_port="$3"
|
||||
local instance_key="$4"
|
||||
local run_mode="$5"
|
||||
local process_name="$6"
|
||||
local log_prefix="$7"
|
||||
local mihomo_bin="$(first_type mihomo)"
|
||||
|
||||
[ -x "$mihomo_bin" ] || return 1
|
||||
prepare_v2ray_mihomo_runtime_config "$sid" "$local_port" "$socks_port" "$instance_key" "$run_mode" || return 1
|
||||
|
||||
local workdir="$(get_v2ray_mihomo_workdir "$instance_key")"
|
||||
local protocol_name="$(uci_get_by_name "$sid" v2ray_protocol)"
|
||||
[ -n "$protocol_name" ] || protocol_name="$(uci_get_by_name "$sid" type)"
|
||||
ln_start_bin "$mihomo_bin" "$process_name" -d "$workdir" -f "$workdir/config.yaml"
|
||||
echolog "$log_prefix:Mihomo ($protocol_name) Started!"
|
||||
return 0
|
||||
}
|
||||
|
||||
start_socks5_udp_with_ipt2socks() {
|
||||
local sid="$1"
|
||||
local local_port="$2"
|
||||
local threads="$3"
|
||||
local ipt2socks_bin="$(first_type ipt2socks)"
|
||||
local auth_opts=""
|
||||
|
||||
[ -x "$ipt2socks_bin" ] || return 1
|
||||
if [ "$(uci_get_by_name "$sid" auth_enable 0)" = "1" ]; then
|
||||
auth_opts="-a $(uci_get_by_name "$sid" username) -k $(uci_get_by_name "$sid" password)"
|
||||
fi
|
||||
for i in $(seq 1 $threads); do
|
||||
ln_start_bin "$ipt2socks_bin" ipt2socks -U -r -b 0.0.0.0 -4 \
|
||||
-s "$(uci_get_by_name "$sid" server)" \
|
||||
-p "$(uci_get_by_name "$sid" server_port)" \
|
||||
-l "$local_port" $auth_opts
|
||||
done
|
||||
echolog "UDP TPROXY Relay:Socks5 via IPT2Socks $threads Threads Started!"
|
||||
return 0
|
||||
}
|
||||
|
||||
start_socks5_redir_with_ipt2socks() {
|
||||
local sid="$1"
|
||||
local local_port="$2"
|
||||
local threads="$3"
|
||||
local ipt2socks_bin="$(first_type ipt2socks)"
|
||||
local auth_opts=""
|
||||
|
||||
[ -x "$ipt2socks_bin" ] || return 1
|
||||
if [ "$(uci_get_by_name "$sid" auth_enable 0)" = "1" ]; then
|
||||
auth_opts="-a $(uci_get_by_name "$sid" username) -k $(uci_get_by_name "$sid" password)"
|
||||
fi
|
||||
for i in $(seq 1 $threads); do
|
||||
ln_start_bin "$ipt2socks_bin" ipt2socks \
|
||||
-T -R -r -b 0.0.0.0 -4 \
|
||||
-s "$(uci_get_by_name "$sid" server)" \
|
||||
-p "$(uci_get_by_name "$sid" server_port)" \
|
||||
-l "$local_port" $auth_opts
|
||||
done
|
||||
echolog "Main node:Socks5 via IPT2Socks $threads Threads Started!"
|
||||
return 0
|
||||
}
|
||||
|
||||
ln_start_bin() {
|
||||
local file_func=${1}
|
||||
local ln_name=${2}
|
||||
@ -1289,9 +1404,15 @@ start_udp() {
|
||||
fi
|
||||
;;
|
||||
v2ray)
|
||||
gen_config_file $UDP_RELAY_SERVER $type 2 $tmp_udp_port
|
||||
ln_start_bin $(first_type xray) v2ray run -c $udp_config_file
|
||||
echolog "UDP TPROXY Relay:$($(first_type xray) version | head -1) Started!"
|
||||
if has_mihomo_binary; then
|
||||
redir_udp=0
|
||||
ARG_UDP=""
|
||||
echolog "UDP TPROXY Relay:V2Ray/Xray 单节点由主 Mihomo 实例处理。"
|
||||
else
|
||||
gen_config_file $UDP_RELAY_SERVER $type 2 $tmp_udp_port
|
||||
ln_start_bin $(first_type xray) v2ray run -c $udp_config_file
|
||||
echolog "UDP TPROXY Relay:$($(first_type xray) version | head -1) Started!"
|
||||
fi
|
||||
;;
|
||||
trojan) #client
|
||||
gen_config_file $UDP_RELAY_SERVER $type 2 $tmp_udp_local_port
|
||||
@ -1317,8 +1438,12 @@ start_udp() {
|
||||
local chain_type=$(uci_get_by_name $UDP_RELAY_SERVER chain_type)
|
||||
case ${chain_type} in
|
||||
vmess)
|
||||
ln_start_bin $(first_type xray) v2ray run -c $udp_config_file
|
||||
echolog "UDP TPROXY Relay:shadow-tls chain-to $($(first_type xray) --version) Started!"
|
||||
if start_v2ray_with_mihomo "$UDP_RELAY_SERVER" "$tmp_udp_port" "0" "${UDP_RELAY_SERVER}-shadowtls-udp" "redir" "shadowtls-vmess-udp" "UDP TPROXY Relay:shadow-tls chain-to"; then
|
||||
:
|
||||
else
|
||||
ln_start_bin $(first_type xray) v2ray run -c $udp_config_file
|
||||
echolog "UDP TPROXY Relay:shadow-tls chain-to $($(first_type xray) --version) Started!"
|
||||
fi
|
||||
;;
|
||||
sslocal)
|
||||
ln_start_bin $(first_type sslocal) sslocal -c $udp_config_file
|
||||
@ -1327,16 +1452,16 @@ start_udp() {
|
||||
esac
|
||||
;;
|
||||
socks5)
|
||||
if [ "$(uci_get_by_name $UDP_RELAY_SERVER auth_enable 0)" == "1" ]; then
|
||||
local auth="-a $(uci_get_by_name $UDP_RELAY_SERVER username) -k $(uci_get_by_name $UDP_RELAY_SERVER password)"
|
||||
if start_socks5_udp_with_ipt2socks "$UDP_RELAY_SERVER" "$tmp_udp_port" "$threads"; then
|
||||
:
|
||||
elif has_mihomo_binary; then
|
||||
redir_udp=0
|
||||
ARG_UDP=""
|
||||
echolog "UDP TPROXY Relay:Socks5 缺少 ipt2socks,已回退到主 Mihomo 实例处理。"
|
||||
else
|
||||
echolog "UDP TPROXY Relay:Socks5 缺少 ipt2socks,且 Mihomo 不可用。"
|
||||
return 1
|
||||
fi
|
||||
for i in $(seq 1 $threads); do
|
||||
ln_start_bin $(first_type ipt2socks) ipt2socks -U -r -b 0.0.0.0 -4 \
|
||||
-s $(uci_get_by_name $UDP_RELAY_SERVER server) \
|
||||
-p $(uci_get_by_name $UDP_RELAY_SERVER server_port) \
|
||||
-l $tmp_udp_port $auth
|
||||
done
|
||||
echolog "UDP TPROXY Relay:Socks5 via IPT2Socks $threads Threads Started!"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
@ -1395,11 +1520,20 @@ start_local() {
|
||||
fi
|
||||
;;
|
||||
v2ray)
|
||||
if [ "$_local" == "2" ]; then
|
||||
local instance_key="${LOCAL_SERVER}-local"
|
||||
[ -n "$probe_instance_key" ] && instance_key="$probe_instance_key"
|
||||
if start_v2ray_with_mihomo "$LOCAL_SERVER" "$local_port" "$local_port" "$instance_key" "socks" "v2ray-local" "Global_Socks5"; then
|
||||
:
|
||||
elif [ "$(uci_get_by_name "$LOCAL_SERVER" v2ray_protocol)" = "snell" ]; then
|
||||
echolog "Global_Socks5:Snell 需要 Mihomo 内核,且当前 Mihomo 启动失败。"
|
||||
return 1
|
||||
elif [ "$_local" == "2" ]; then
|
||||
gen_config_file $LOCAL_SERVER $type 4 0 $local_port
|
||||
ln_start_bin $(first_type xray) v2ray run -c $local_config_file
|
||||
echolog "Global_Socks5:$($(first_type xray) version | head -1) Started!"
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
echolog "Global_Socks5:$($(first_type xray) version | head -1) Started!"
|
||||
;;
|
||||
trojan) #client
|
||||
gen_config_file $LOCAL_SERVER $type 4 $local_port
|
||||
@ -1433,8 +1567,12 @@ start_local() {
|
||||
local chain_type=$(uci_get_by_name $LOCAL_SERVER chain_type)
|
||||
case ${chain_type} in
|
||||
vmess)
|
||||
ln_start_bin $(first_type xray) v2ray run -c $local_config_file
|
||||
echolog "Global Socks5 Proxy:shadow-tls chain-to$($(first_type xray) --version) Started!"
|
||||
if start_v2ray_with_mihomo "$LOCAL_SERVER" "$local_port" "$local_port" "${LOCAL_SERVER}-shadowtls-local" "socks" "shadowtls-vmess-local" "Global Socks5 Proxy:shadow-tls chain-to"; then
|
||||
:
|
||||
else
|
||||
ln_start_bin $(first_type xray) v2ray run -c $local_config_file
|
||||
echolog "Global Socks5 Proxy:shadow-tls chain-to$($(first_type xray) --version) Started!"
|
||||
fi
|
||||
;;
|
||||
sslocal)
|
||||
ln_start_bin $(first_type sslocal) sslocal -c $local_config_file
|
||||
@ -1443,16 +1581,22 @@ start_local() {
|
||||
esac
|
||||
fi
|
||||
;;
|
||||
clash)
|
||||
echolog "Global_Socks5:Clash total node is only supported when using Same as Global Server."
|
||||
clash)
|
||||
echolog "Global_Socks5:Clash total node is only supported when using Same as Global Server."
|
||||
return 1
|
||||
;;
|
||||
socks5)
|
||||
local instance_key="${LOCAL_SERVER}-local"
|
||||
[ -n "$probe_instance_key" ] && instance_key="$probe_instance_key"
|
||||
if start_v2ray_with_mihomo "$LOCAL_SERVER" "$local_port" "$local_port" "$instance_key" "socks" "socks5-local" "Global_Socks5"; then
|
||||
:
|
||||
else
|
||||
echolog "Global_Socks5:Socks5 单节点需要 Mihomo 才能作为本地 SOCKS 出口。"
|
||||
return 1
|
||||
;;
|
||||
socks5)
|
||||
echolog "Global_Socks5:Socks5 transparent-only node cannot be used as generic Socks outbound."
|
||||
return 1
|
||||
;;
|
||||
*)
|
||||
local listenip='-i 0.0.0.0'
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
local listenip='-i 0.0.0.0'
|
||||
[ -e /proc/sys/net/ipv6 ] && listenip='-i ::'
|
||||
ln_start_bin $(first_type microsocks) microsocks $listenip -p $local_port tcp-udp-ssr-local
|
||||
echolog "Global_Socks5:$type Started!"
|
||||
@ -1725,9 +1869,16 @@ Start_Run() {
|
||||
fi
|
||||
;;
|
||||
v2ray)
|
||||
gen_config_file $GLOBAL_SERVER $type 1 $tcp_port $socks_port
|
||||
ln_start_bin $(first_type xray) v2ray run -c $tcp_config_file
|
||||
echolog "Main node:$($(first_type xray) version | head -1) Started!"
|
||||
if start_v2ray_with_mihomo "$GLOBAL_SERVER" "$tcp_port" "$socks_port" "$GLOBAL_SERVER" "redir" "ssr-retcp" "Main node"; then
|
||||
:
|
||||
elif [ "$(uci_get_by_name "$GLOBAL_SERVER" v2ray_protocol)" = "snell" ]; then
|
||||
echolog "Main node:Snell 需要 Mihomo 内核,且当前 Mihomo 启动失败。"
|
||||
return 1
|
||||
else
|
||||
gen_config_file $GLOBAL_SERVER $type 1 $tcp_port $socks_port
|
||||
ln_start_bin $(first_type xray) v2ray run -c $tcp_config_file
|
||||
echolog "Main node:$($(first_type xray) version | head -1) Started!"
|
||||
fi
|
||||
;;
|
||||
trojan)
|
||||
gen_config_file $GLOBAL_SERVER $type 1 $tcp_port
|
||||
@ -1759,24 +1910,28 @@ Start_Run() {
|
||||
shadowtls)
|
||||
if [ -z "$socks_port" ]; then
|
||||
gen_config_file $GLOBAL_SERVER $type 1 "10${tmp_tcp_local_port}"
|
||||
gen_config_file $GLOBAL_SERVER $type 1 "10${tmp_tcp_local_port}" 0 chain
|
||||
else
|
||||
gen_config_file $GLOBAL_SERVER $type 1 "10${tmp_tcp_local_port}"
|
||||
gen_config_file $GLOBAL_SERVER $type 1 "10${tmp_tcp_local_port}" $socks_port chain
|
||||
fi
|
||||
local chain_type=$(uci_get_by_name $GLOBAL_SERVER chain_type)
|
||||
case ${chain_type} in
|
||||
vmess)
|
||||
ln_start_bin $(first_type shadow-tls) shadow-tls config --config $chain_config_file
|
||||
ln_start_bin $(first_type xray) v2ray run -c $tcp_config_file
|
||||
echolog "Mian node:shadow-tls chain-to $($(first_type xray) --version) Started!"
|
||||
;;
|
||||
sslocal)
|
||||
ln_start_bin $(first_type shadow-tls) shadow-tls config --config $chain_config_file
|
||||
ln_start_bin $(first_type sslocal) sslocal -c $tcp_config_file
|
||||
echolog "Main node:shadow-tls chain-to $($(first_type sslocal) --version) Started!"
|
||||
;;
|
||||
esac
|
||||
gen_config_file $GLOBAL_SERVER $type 1 "10${tmp_tcp_local_port}" 0 chain
|
||||
else
|
||||
gen_config_file $GLOBAL_SERVER $type 1 "10${tmp_tcp_local_port}"
|
||||
gen_config_file $GLOBAL_SERVER $type 1 "10${tmp_tcp_local_port}" $socks_port chain
|
||||
fi
|
||||
local chain_type=$(uci_get_by_name $GLOBAL_SERVER chain_type)
|
||||
case ${chain_type} in
|
||||
vmess)
|
||||
ln_start_bin $(first_type shadow-tls) shadow-tls config --config $chain_config_file
|
||||
if start_v2ray_with_mihomo "$GLOBAL_SERVER" "$tcp_port" "$socks_port" "${GLOBAL_SERVER}-shadowtls" "redir" "shadowtls-vmess" "Main node:shadow-tls chain-to"; then
|
||||
:
|
||||
else
|
||||
ln_start_bin $(first_type xray) v2ray run -c $tcp_config_file
|
||||
echolog "Mian node:shadow-tls chain-to $($(first_type xray) --version) Started!"
|
||||
fi
|
||||
;;
|
||||
sslocal)
|
||||
ln_start_bin $(first_type shadow-tls) shadow-tls config --config $chain_config_file
|
||||
ln_start_bin $(first_type sslocal) sslocal -c $tcp_config_file
|
||||
echolog "Main node:shadow-tls chain-to $($(first_type sslocal) --version) Started!"
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
clash)
|
||||
local clash_socks_port=0
|
||||
@ -1802,23 +1957,14 @@ Start_Run() {
|
||||
echolog "Main node:Mihomo (Clash) Started!"
|
||||
;;
|
||||
socks5)
|
||||
local ipt2socks_bin="$(first_type ipt2socks)"
|
||||
local auth_opts=""
|
||||
[ -x "$ipt2socks_bin" ] || {
|
||||
echolog "Main node:Socks5 缺少 ipt2socks,无法启动透明代理。"
|
||||
if start_socks5_redir_with_ipt2socks "$GLOBAL_SERVER" "$tcp_port" "$threads"; then
|
||||
:
|
||||
elif start_v2ray_with_mihomo "$GLOBAL_SERVER" "$tcp_port" "$socks_port" "$GLOBAL_SERVER" "redir" "ssr-retcp" "Main node"; then
|
||||
echolog "Main node:Socks5 缺少 ipt2socks,已回退到 Mihomo。"
|
||||
else
|
||||
echolog "Main node:Socks5 缺少 ipt2socks,且 Mihomo 不可用。"
|
||||
return 1
|
||||
}
|
||||
if [ "$(uci_get_by_name $GLOBAL_SERVER auth_enable 0)" = "1" ]; then
|
||||
auth_opts="-a $(uci_get_by_name $GLOBAL_SERVER username) -k $(uci_get_by_name $GLOBAL_SERVER password)"
|
||||
fi
|
||||
for i in $(seq 1 $threads); do
|
||||
ln_start_bin "$ipt2socks_bin" ipt2socks \
|
||||
-T -R -r -b 0.0.0.0 -4 \
|
||||
-s "$(uci_get_by_name $GLOBAL_SERVER server)" \
|
||||
-p "$(uci_get_by_name $GLOBAL_SERVER server_port)" \
|
||||
-l "$tcp_port" $auth_opts
|
||||
done
|
||||
echolog "Main node:Socks5 via IPT2Socks $threads Threads Started!"
|
||||
;;
|
||||
esac
|
||||
redir_tcp=1
|
||||
@ -2018,6 +2164,7 @@ start_server() {
|
||||
local tcp_guise=$(uci_get_by_name "$1" tcp_guise "none")
|
||||
local tcp_guise_http_host=$(uci_get_by_name "$1" tcp_guise_http_host "")
|
||||
local tcp_guise_http_path=$(uci_get_by_name "$1" tcp_guise_http_path "")
|
||||
local mihomo_server_started=0
|
||||
|
||||
# 确定密码/UUID
|
||||
local password=""
|
||||
@ -2036,26 +2183,37 @@ start_server() {
|
||||
echolog "Server: $type 服务端缺少密码/UUID,跳过启动"
|
||||
return 1
|
||||
}
|
||||
|
||||
local config_file="$TMP_PATH/xray-server-$server_count.json"
|
||||
|
||||
# 生成 Xray 配置
|
||||
generate_xray_config "$type" "$port" "$password" "$security" "$method" "$network" "$ws_path" "$ws_host" "$grpc_service" "$alter_id" "$tcp_guise" "$tcp_guise_http_host" "$tcp_guise_http_path" "$config_file"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echolog "Server: Xray $type 配置生成失败"
|
||||
return 1
|
||||
|
||||
local mihomo_bin="$(first_type mihomo)"
|
||||
if [ -x "$mihomo_bin" ] && prepare_v2ray_server_mihomo_runtime_config "$1"; then
|
||||
local server_workdir="$(get_v2ray_server_mihomo_workdir "$1")"
|
||||
ln_start_bin "$mihomo_bin" mihomo-server-$server_count -d "$server_workdir" -f "$server_workdir/config.yaml"
|
||||
echolog "Server: Mihomo $type 服务端 Server$server_count 已启动 (端口: $port)"
|
||||
mihomo_server_started=1
|
||||
fi
|
||||
|
||||
local xray_bin=$(first_type xray)
|
||||
if [ -z "$xray_bin" ]; then
|
||||
echolog "Server: xray-core 未安装,无法启动 $type 服务端"
|
||||
return 1
|
||||
|
||||
if [ "$mihomo_server_started" != "1" ]; then
|
||||
|
||||
local config_file="$TMP_PATH/xray-server-$server_count.json"
|
||||
|
||||
# 生成 Xray 配置
|
||||
generate_xray_config "$type" "$port" "$password" "$security" "$method" "$network" "$ws_path" "$ws_host" "$grpc_service" "$alter_id" "$tcp_guise" "$tcp_guise_http_host" "$tcp_guise_http_path" "$config_file"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echolog "Server: Xray $type 配置生成失败"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local xray_bin=$(first_type xray)
|
||||
if [ -z "$xray_bin" ]; then
|
||||
echolog "Server: xray-core 未安装,无法启动 $type 服务端"
|
||||
return 1
|
||||
fi
|
||||
|
||||
ln_start_bin "$xray_bin" xray-server-$server_count run -c "$config_file"
|
||||
echolog "Server: Xray $type 服务端 Server$server_count 已启动 (端口: $port)"
|
||||
fi
|
||||
|
||||
ln_start_bin "$xray_bin" xray-server-$server_count run -c "$config_file"
|
||||
echolog "Server: Xray $type 服务端 Server$server_count 已启动 (端口: $port)"
|
||||
;;
|
||||
;;
|
||||
esac
|
||||
server_port=$(uci_get_by_name $1 server_port)
|
||||
if [ "$USE_TABLES" = "nftables" ]; then
|
||||
|
||||
@ -55,6 +55,13 @@ if [ -s "/etc/config/shadowsocksr" ]; then
|
||||
uci -q set shadowsocksr.@server_subscribe[0].proxy='1'
|
||||
fi
|
||||
|
||||
current_filter_words="$(uci -q get shadowsocksr.@server_subscribe[0].filter_words)"
|
||||
if [ -z "$current_filter_words" ]; then
|
||||
uci -q set shadowsocksr.@server_subscribe[0].filter_words='过期/重置/套餐/剩余/网址/QQ群/官网/防失联/回国'
|
||||
elif ! printf '%s\n' "$current_filter_words" | grep -q '重置'; then
|
||||
uci -q set shadowsocksr.@server_subscribe[0].filter_words="${current_filter_words}/重置"
|
||||
fi
|
||||
|
||||
if ! uci -q get shadowsocksr.@global_xray_fragment[0] > /dev/null; then
|
||||
uci -q add shadowsocksr global_xray_fragment
|
||||
uci -q set shadowsocksr.@global_xray_fragment[0].fragment='0'
|
||||
|
||||
@ -29,11 +29,11 @@ CURRENT_SERVER="$DEFAULT_SERVER"
|
||||
ENABLE_SERVER="nil"
|
||||
|
||||
cleanup_probe_socks() {
|
||||
ps_list | grep -v "grep" | grep -E "tcp-http-ssr-switch-local\\.json|ss-${PROBE_INSTANCE_KEY}/config\\.yaml|tuic-${PROBE_INSTANCE_KEY}/config\\.yaml" | awk '{print $1}' | xargs kill >/dev/null 2>&1
|
||||
ps_list | grep -v "grep" | grep -E "tcp-http-ssr-switch-local\\.json|ss-${PROBE_INSTANCE_KEY}/config\\.yaml|tuic-${PROBE_INSTANCE_KEY}/config\\.yaml|v2ray-${PROBE_INSTANCE_KEY}/config\\.yaml" | awk '{print $1}' | xargs kill >/dev/null 2>&1
|
||||
sleep 1
|
||||
ps_list | grep -v "grep" | grep -E "tcp-http-ssr-switch-local\\.json|ss-${PROBE_INSTANCE_KEY}/config\\.yaml|tuic-${PROBE_INSTANCE_KEY}/config\\.yaml" | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1
|
||||
ps_list | grep -v "grep" | grep -E "tcp-http-ssr-switch-local\\.json|ss-${PROBE_INSTANCE_KEY}/config\\.yaml|tuic-${PROBE_INSTANCE_KEY}/config\\.yaml|v2ray-${PROBE_INSTANCE_KEY}/config\\.yaml" | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1
|
||||
rm -f "$TMP_PATH/tcp-http-ssr-switch-local.json" 2>/dev/null
|
||||
rm -rf "$TMP_PATH/ss-${PROBE_INSTANCE_KEY}" "$TMP_PATH/tuic-${PROBE_INSTANCE_KEY}" 2>/dev/null
|
||||
rm -rf "$TMP_PATH/ss-${PROBE_INSTANCE_KEY}" "$TMP_PATH/tuic-${PROBE_INSTANCE_KEY}" "$TMP_PATH/v2ray-${PROBE_INSTANCE_KEY}" 2>/dev/null
|
||||
rm -f /tmp/ssr-switch-probe.log 2>/dev/null
|
||||
}
|
||||
|
||||
@ -54,21 +54,13 @@ start_probe_socks() {
|
||||
tmp_local_port=
|
||||
mode="tcp,udp"
|
||||
|
||||
if [ "$server_type" = "v2ray" ]; then
|
||||
export SSR_SWITCH_PROBE=1
|
||||
gen_config_file "$LOCAL_SERVER" "$server_type" 4 0 "$probe_socks_port" >/tmp/ssr-switch-probe.log 2>&1 || return 1
|
||||
unset SSR_SWITCH_PROBE
|
||||
local xray_bin
|
||||
xray_bin="$(first_type xray)"
|
||||
[ -x "$xray_bin" ] || return 1
|
||||
"$xray_bin" run -c "$local_config_file" >>/tmp/ssr-switch-probe.log 2>&1 &
|
||||
else
|
||||
export SSR_SWITCH_PROBE=1
|
||||
export SSR_SWITCH_PROBE_INSTANCE_KEY="$PROBE_INSTANCE_KEY"
|
||||
start_local_with_port "$probe_socks_port" >/tmp/ssr-switch-probe.log 2>&1 || return 1
|
||||
unset SSR_SWITCH_PROBE_INSTANCE_KEY
|
||||
unset SSR_SWITCH_PROBE
|
||||
fi
|
||||
export SSR_SWITCH_PROBE=1
|
||||
export SSR_SWITCH_PROBE_INSTANCE_KEY="$PROBE_INSTANCE_KEY"
|
||||
start_local_with_port "$probe_socks_port" >/tmp/ssr-switch-probe.log 2>&1
|
||||
local rc=$?
|
||||
unset SSR_SWITCH_PROBE_INSTANCE_KEY
|
||||
unset SSR_SWITCH_PROBE
|
||||
[ "$rc" = "0" ] || return 1
|
||||
|
||||
sleep 2
|
||||
return 0
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#!/usr/bin/lua
|
||||
|
||||
require "nixio"
|
||||
require "nixio.fs"
|
||||
require "luci.model.uci"
|
||||
|
||||
@ -582,6 +583,376 @@ local function string_or_nil(value)
|
||||
return tostring(value)
|
||||
end
|
||||
|
||||
local build_shadowsocks_plugin
|
||||
|
||||
local function first_nonempty(...)
|
||||
for i = 1, select("#", ...) do
|
||||
local value = select(i, ...)
|
||||
if value ~= nil and value ~= "" then
|
||||
return value
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function split_alpn(value)
|
||||
local items = {}
|
||||
for part in tostring(value or ""):gmatch("[^,;|%s]+") do
|
||||
items[#items + 1] = part
|
||||
end
|
||||
return items
|
||||
end
|
||||
|
||||
local function parse_wireguard_reserved(sid)
|
||||
local raw = get_server_field(sid, "reserved", nil)
|
||||
local values = {}
|
||||
local bytes = {}
|
||||
|
||||
if raw == nil or raw == "" then
|
||||
return nil
|
||||
end
|
||||
if type(raw) == "table" then
|
||||
values = raw
|
||||
else
|
||||
values = { raw }
|
||||
end
|
||||
for _, item in ipairs(values) do
|
||||
local text = tostring(item or "")
|
||||
if text ~= "" then
|
||||
if not text:match("[^%d,]+") then
|
||||
for byte in text:gmatch("%d+") do
|
||||
bytes[#bytes + 1] = tonumber(byte)
|
||||
end
|
||||
else
|
||||
local decoded = nixio.bin.b64decode(text)
|
||||
if decoded then
|
||||
for i = 1, #decoded do
|
||||
bytes[#bytes + 1] = decoded:byte(i)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return #bytes > 0 and bytes or nil
|
||||
end
|
||||
|
||||
local function split_local_addresses(value)
|
||||
local items = {}
|
||||
if type(value) == "table" then
|
||||
for _, item in ipairs(value) do
|
||||
if item and item ~= "" then
|
||||
items[#items + 1] = tostring(item)
|
||||
end
|
||||
end
|
||||
elseif value and value ~= "" then
|
||||
for item in tostring(value):gmatch("[^,%s]+") do
|
||||
items[#items + 1] = item
|
||||
end
|
||||
end
|
||||
return items
|
||||
end
|
||||
|
||||
local function split_wireguard_addresses(value)
|
||||
local ip, ipv6
|
||||
for _, item in ipairs(split_local_addresses(value)) do
|
||||
if item:find(":", 1, true) then
|
||||
ipv6 = ipv6 or item
|
||||
else
|
||||
ip = ip or item
|
||||
end
|
||||
end
|
||||
return ip, ipv6
|
||||
end
|
||||
|
||||
local function apply_v2ray_tls_options(proxy, sid)
|
||||
local tls = get_server_field(sid, "tls", "0")
|
||||
local reality = get_server_field(sid, "reality", "0")
|
||||
if tls ~= "1" and reality ~= "1" then
|
||||
return
|
||||
end
|
||||
|
||||
proxy.tls = true
|
||||
proxy.servername = string_or_nil(get_server_field(sid, "tls_host", ""))
|
||||
proxy["client-fingerprint"] = string_or_nil(get_server_field(sid, "fingerprint", ""))
|
||||
proxy.fingerprint = string_or_nil(get_server_field(sid, "tls_CertSha", ""))
|
||||
proxy["skip-cert-verify"] = bool_enabled(get_server_field(sid, "insecure", "0"))
|
||||
|
||||
local alpn = split_alpn(get_server_field(sid, "tls_alpn", ""))
|
||||
if #alpn > 0 then
|
||||
proxy.alpn = alpn
|
||||
end
|
||||
|
||||
if reality == "1" then
|
||||
proxy["reality-opts"] = {
|
||||
["public-key"] = string_or_nil(get_server_field(sid, "reality_publickey", "")),
|
||||
["short-id"] = string_or_nil(get_server_field(sid, "reality_shortid", "")),
|
||||
["support-x25519mlkem768"] = bool_enabled(get_server_field(sid, "enable_mldsa65verify", "0"))
|
||||
}
|
||||
end
|
||||
|
||||
if get_server_field(sid, "enable_ech", "0") == "1" then
|
||||
proxy["ech-opts"] = {
|
||||
enable = true,
|
||||
config = string_or_nil(get_server_field(sid, "ech_config", ""))
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
local function apply_trojan_tls_options(proxy, sid)
|
||||
proxy.sni = string_or_nil(get_server_field(sid, "tls_host", ""))
|
||||
proxy["client-fingerprint"] = string_or_nil(get_server_field(sid, "fingerprint", ""))
|
||||
proxy.fingerprint = string_or_nil(get_server_field(sid, "tls_CertSha", ""))
|
||||
proxy["skip-cert-verify"] = bool_enabled(get_server_field(sid, "insecure", "0"))
|
||||
|
||||
local alpn = split_alpn(get_server_field(sid, "tls_alpn", ""))
|
||||
if #alpn > 0 then
|
||||
proxy.alpn = alpn
|
||||
end
|
||||
|
||||
if get_server_field(sid, "reality", "0") == "1" then
|
||||
proxy["reality-opts"] = {
|
||||
["public-key"] = string_or_nil(get_server_field(sid, "reality_publickey", "")),
|
||||
["short-id"] = string_or_nil(get_server_field(sid, "reality_shortid", ""))
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
local function apply_v2ray_transport_options(proxy, sid)
|
||||
local transport = get_server_field(sid, "transport", "raw")
|
||||
if transport == "raw" or transport == "tcp" or transport == "" then
|
||||
return
|
||||
end
|
||||
|
||||
if transport == "ws" then
|
||||
proxy.network = "ws"
|
||||
proxy["ws-opts"] = {
|
||||
path = string_or_nil(get_server_field(sid, "ws_path", "")),
|
||||
headers = first_nonempty(get_server_field(sid, "ws_host", ""), get_server_field(sid, "tls_host", "")) and {
|
||||
Host = first_nonempty(get_server_field(sid, "ws_host", ""), get_server_field(sid, "tls_host", ""))
|
||||
} or nil
|
||||
}
|
||||
elseif transport == "httpupgrade" then
|
||||
proxy.network = "ws"
|
||||
proxy["ws-opts"] = {
|
||||
path = string_or_nil(get_server_field(sid, "httpupgrade_path", "")),
|
||||
headers = first_nonempty(get_server_field(sid, "httpupgrade_host", ""), get_server_field(sid, "tls_host", "")) and {
|
||||
Host = first_nonempty(get_server_field(sid, "httpupgrade_host", ""), get_server_field(sid, "tls_host", ""))
|
||||
} or nil,
|
||||
["v2ray-http-upgrade"] = true
|
||||
}
|
||||
elseif transport == "h2" then
|
||||
local host = first_nonempty(get_server_field(sid, "h2_host", ""), get_server_field(sid, "tls_host", ""))
|
||||
proxy.network = "h2"
|
||||
proxy["h2-opts"] = {
|
||||
host = host and split_alpn(host) or nil,
|
||||
path = string_or_nil(get_server_field(sid, "h2_path", ""))
|
||||
}
|
||||
elseif transport == "grpc" then
|
||||
proxy.network = "grpc"
|
||||
proxy["grpc-opts"] = {
|
||||
["grpc-service-name"] = string_or_nil(get_server_field(sid, "serviceName", ""))
|
||||
}
|
||||
elseif transport == "xhttp" and proxy.type == "vless" then
|
||||
proxy.network = "xhttp"
|
||||
proxy["xhttp-opts"] = {
|
||||
path = string_or_nil(get_server_field(sid, "xhttp_path", "")),
|
||||
host = string_or_nil(get_server_field(sid, "xhttp_host", "")),
|
||||
mode = string_or_nil(get_server_field(sid, "xhttp_mode", ""))
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
local function can_mihomo_handle_v2ray_transport(protocol, sid)
|
||||
local transport = get_server_field(sid, "transport", "raw")
|
||||
if transport == "" or transport == "raw" or transport == "tcp" then
|
||||
if get_server_field(sid, "tcp_guise", "none") == "http" then
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
if protocol == "socks" or protocol == "http" then
|
||||
return false
|
||||
end
|
||||
if transport == "ws" or transport == "httpupgrade" or transport == "h2" or transport == "grpc" then
|
||||
return true
|
||||
end
|
||||
return protocol == "vless" and transport == "xhttp"
|
||||
end
|
||||
|
||||
local function build_v2ray_mihomo_proxy(sid)
|
||||
local node_type = get_server_field(sid, "type", "")
|
||||
local protocol = get_server_field(sid, "v2ray_protocol", "vmess")
|
||||
local proxy = {
|
||||
name = sid,
|
||||
server = get_server_field(sid, "server", ""),
|
||||
port = tonumber(get_server_field(sid, "server_port", "0")) or 0,
|
||||
udp = true,
|
||||
tfo = bool_enabled(get_server_field(sid, "fast_open", "0"))
|
||||
}
|
||||
|
||||
if node_type == "socks5" then
|
||||
proxy.type = "socks5"
|
||||
if get_server_field(sid, "auth_enable", "0") == "1" then
|
||||
proxy.username = string_or_nil(get_server_field(sid, "username", ""))
|
||||
proxy.password = string_or_nil(get_server_field(sid, "password", ""))
|
||||
end
|
||||
elseif protocol == "vmess" then
|
||||
if not can_mihomo_handle_v2ray_transport(protocol, sid) then
|
||||
return nil
|
||||
end
|
||||
proxy.type = "vmess"
|
||||
proxy.uuid = first_nonempty(get_server_field(sid, "vmess_id", ""), get_server_field(sid, "vmess_uuid", "")) or ""
|
||||
proxy.alterId = tonumber(get_server_field(sid, "alter_id", "0")) or 0
|
||||
proxy.cipher = first_nonempty(get_server_field(sid, "security", ""), get_server_field(sid, "vmess_method", ""), "auto")
|
||||
apply_v2ray_tls_options(proxy, sid)
|
||||
apply_v2ray_transport_options(proxy, sid)
|
||||
elseif protocol == "vless" then
|
||||
if not can_mihomo_handle_v2ray_transport(protocol, sid) then
|
||||
return nil
|
||||
end
|
||||
proxy.type = "vless"
|
||||
proxy.uuid = get_server_field(sid, "vmess_id", "")
|
||||
proxy.flow = string_or_nil(get_server_field(sid, "tls_flow", ""))
|
||||
proxy.encryption = get_server_field(sid, "vless_encryption", "")
|
||||
apply_v2ray_tls_options(proxy, sid)
|
||||
apply_v2ray_transport_options(proxy, sid)
|
||||
elseif protocol == "trojan" then
|
||||
if not can_mihomo_handle_v2ray_transport(protocol, sid) then
|
||||
return nil
|
||||
end
|
||||
proxy.type = "trojan"
|
||||
proxy.password = get_server_field(sid, "password", "")
|
||||
apply_trojan_tls_options(proxy, sid)
|
||||
apply_v2ray_transport_options(proxy, sid)
|
||||
elseif protocol == "shadowsocks" then
|
||||
if not can_mihomo_handle_v2ray_transport(protocol, sid) then
|
||||
return nil
|
||||
end
|
||||
proxy.type = "ss"
|
||||
proxy.cipher = get_server_field(sid, "encrypt_method_ss", "none")
|
||||
proxy.password = get_server_field(sid, "password", "")
|
||||
build_shadowsocks_plugin(proxy, sid)
|
||||
elseif protocol == "hysteria2" then
|
||||
proxy.type = "hysteria2"
|
||||
proxy.password = get_server_field(sid, "hy2_auth", "")
|
||||
proxy.ports = string_or_nil(get_server_field(sid, "port_range", ""))
|
||||
proxy.up = string_or_nil(get_server_field(sid, "uplink_capacity", "")) and (get_server_field(sid, "uplink_capacity", "") .. " Mbps") or nil
|
||||
proxy.down = string_or_nil(get_server_field(sid, "downlink_capacity", "")) and (get_server_field(sid, "downlink_capacity", "") .. " Mbps") or nil
|
||||
proxy.sni = string_or_nil(get_server_field(sid, "tls_host", ""))
|
||||
proxy.fingerprint = string_or_nil(get_server_field(sid, "tls_CertSha", ""))
|
||||
proxy["skip-cert-verify"] = bool_enabled(get_server_field(sid, "insecure", "0"))
|
||||
local alpn = split_alpn(get_server_field(sid, "tls_alpn", ""))
|
||||
if #alpn > 0 then
|
||||
proxy.alpn = alpn
|
||||
end
|
||||
if get_server_field(sid, "flag_obfs", "0") == "1" then
|
||||
proxy.obfs = string_or_nil(get_server_field(sid, "obfs_type", ""))
|
||||
proxy["obfs-password"] = string_or_nil(get_server_field(sid, "salamander", ""))
|
||||
end
|
||||
elseif protocol == "socks" then
|
||||
if not can_mihomo_handle_v2ray_transport(protocol, sid) then
|
||||
return nil
|
||||
end
|
||||
proxy.type = "socks5"
|
||||
if get_server_field(sid, "socks_ver", "5") ~= "5" then
|
||||
return nil
|
||||
end
|
||||
if get_server_field(sid, "auth_enable", "0") == "1" then
|
||||
proxy.username = string_or_nil(get_server_field(sid, "username", ""))
|
||||
proxy.password = string_or_nil(get_server_field(sid, "password", ""))
|
||||
end
|
||||
apply_v2ray_tls_options(proxy, sid)
|
||||
elseif protocol == "http" then
|
||||
if not can_mihomo_handle_v2ray_transport(protocol, sid) then
|
||||
return nil
|
||||
end
|
||||
proxy.type = "http"
|
||||
if get_server_field(sid, "auth_enable", "0") == "1" then
|
||||
proxy.username = string_or_nil(get_server_field(sid, "username", ""))
|
||||
proxy.password = string_or_nil(get_server_field(sid, "password", ""))
|
||||
end
|
||||
apply_v2ray_tls_options(proxy, sid)
|
||||
elseif protocol == "wireguard" then
|
||||
local ip, ipv6 = split_wireguard_addresses(get_server_field(sid, "local_addresses", ""))
|
||||
proxy.type = "wireguard"
|
||||
proxy["private-key"] = get_server_field(sid, "private_key", "")
|
||||
proxy["public-key"] = get_server_field(sid, "peer_pubkey", "")
|
||||
proxy["pre-shared-key"] = string_or_nil(get_server_field(sid, "preshared_key", ""))
|
||||
proxy.ip = ip
|
||||
proxy.ipv6 = ipv6
|
||||
proxy["allowed-ips"] = split_local_addresses(get_server_field(sid, "allowedips", "0.0.0.0/0"))
|
||||
proxy.reserved = parse_wireguard_reserved(sid)
|
||||
proxy["persistent-keepalive"] = number_or_nil(get_server_field(sid, "keepaliveperiod", ""))
|
||||
proxy.mtu = number_or_nil(get_server_field(sid, "mtu", ""))
|
||||
elseif protocol == "snell" then
|
||||
proxy.type = "snell"
|
||||
proxy.psk = get_server_field(sid, "snell_psk", "")
|
||||
proxy.version = number_or_nil(get_server_field(sid, "snell_version", ""))
|
||||
local obfs_mode = string_or_nil(get_server_field(sid, "snell_obfs", ""))
|
||||
local obfs_host = string_or_nil(get_server_field(sid, "snell_obfs_host", ""))
|
||||
if obfs_mode or obfs_host then
|
||||
proxy["obfs-opts"] = {
|
||||
mode = obfs_mode,
|
||||
host = obfs_host
|
||||
}
|
||||
end
|
||||
else
|
||||
return nil
|
||||
end
|
||||
|
||||
if not proxy.server or proxy.server == "" or not proxy.port or proxy.port == 0 then
|
||||
return nil
|
||||
end
|
||||
if proxy.type == "snell" and (not proxy.psk or proxy.psk == "") then
|
||||
return nil
|
||||
end
|
||||
return proxy
|
||||
end
|
||||
|
||||
local function build_single_proxy_runtime_doc(proxy, local_port, socks_port, mode)
|
||||
local dns_mode = uci:get_first("shadowsocksr", "global", "pdnsd_enable", "0")
|
||||
local listen_port = tonumber(local_port)
|
||||
local socks_listen = tonumber(socks_port)
|
||||
local doc = {
|
||||
["allow-lan"] = true,
|
||||
["bind-address"] = "0.0.0.0",
|
||||
mode = "rule",
|
||||
["log-level"] = "silent",
|
||||
["find-process-mode"] = "off",
|
||||
["unified-delay"] = true,
|
||||
["tcp-concurrent"] = true,
|
||||
["routing-mark"] = 255,
|
||||
proxies = { proxy },
|
||||
["proxy-groups"] = {
|
||||
{
|
||||
name = "PROXY",
|
||||
type = "select",
|
||||
proxies = { proxy.name }
|
||||
}
|
||||
},
|
||||
rules = { "MATCH,PROXY" },
|
||||
tun = { enable = false },
|
||||
profile = { ["store-selected"] = true },
|
||||
dns = {
|
||||
enable = dns_mode == "7",
|
||||
["enhanced-mode"] = "redir-host",
|
||||
listen = "127.0.0.1:5335",
|
||||
ipv6 = get_filter_aaaa() ~= "1"
|
||||
}
|
||||
}
|
||||
|
||||
if mode == "socks" then
|
||||
doc["socks-port"] = listen_port
|
||||
else
|
||||
doc["redir-port"] = listen_port
|
||||
doc["tproxy-port"] = listen_port
|
||||
if socks_listen and socks_listen > 0 then
|
||||
doc["socks-port"] = socks_listen
|
||||
end
|
||||
end
|
||||
return doc
|
||||
end
|
||||
|
||||
local function pick_plugin_opt(plugin_opts, ...)
|
||||
for i = 1, select("#", ...) do
|
||||
local key = select(i, ...)
|
||||
@ -661,7 +1032,7 @@ local function normalize_plugin_name(plugin)
|
||||
return value
|
||||
end
|
||||
|
||||
local function build_shadowsocks_plugin(proxy, sid)
|
||||
function build_shadowsocks_plugin(proxy, sid)
|
||||
local plugin = normalize_plugin_name(get_server_field(sid, "plugin", ""))
|
||||
local plugin_opts = parse_plugin_opts(get_server_field(sid, "plugin_opts", ""))
|
||||
|
||||
@ -871,6 +1242,22 @@ local function generate_shadowsocks_runtime(sid, output_path, local_port, socks_
|
||||
return true
|
||||
end
|
||||
|
||||
local function generate_v2ray_runtime(sid, output_path, local_port, socks_port, mode)
|
||||
local proxy = build_v2ray_mihomo_proxy(sid)
|
||||
if not proxy then
|
||||
io.stderr:write("unsupported_or_invalid_v2ray_node\n")
|
||||
return false
|
||||
end
|
||||
local doc = build_single_proxy_runtime_doc(proxy, local_port, socks_port, mode)
|
||||
local ok, rendered = pcall(lyaml.dump, { doc })
|
||||
if not ok or not rendered then
|
||||
io.stderr:write("dump_failed\n")
|
||||
return false
|
||||
end
|
||||
write_file(output_path, rendered)
|
||||
return true
|
||||
end
|
||||
|
||||
local function build_shadowsocks_server_doc(sid)
|
||||
local server_port = tonumber(get_server_field(sid, "server_port", "0")) or 0
|
||||
local method = get_server_field(sid, "encrypt_method_ss", "aes-128-gcm")
|
||||
@ -916,6 +1303,86 @@ local function generate_shadowsocks_server(sid, output_path)
|
||||
return true
|
||||
end
|
||||
|
||||
local function build_mihomo_listener_doc(sid)
|
||||
local ltype = get_server_field(sid, "type", "")
|
||||
local server_port = tonumber(get_server_field(sid, "server_port", "0")) or 0
|
||||
local listener = {
|
||||
name = sid,
|
||||
type = ltype,
|
||||
listen = "::",
|
||||
port = server_port
|
||||
}
|
||||
|
||||
if ltype == "vmess" then
|
||||
listener.users = {
|
||||
{
|
||||
username = "1",
|
||||
uuid = get_server_field(sid, "uuid", ""),
|
||||
alterId = tonumber(get_server_field(sid, "alter_id", "0")) or 0
|
||||
}
|
||||
}
|
||||
elseif ltype == "vless" then
|
||||
listener.users = {
|
||||
{
|
||||
username = "1",
|
||||
uuid = get_server_field(sid, "uuid", ""),
|
||||
flow = string_or_nil(get_server_field(sid, "flow", ""))
|
||||
}
|
||||
}
|
||||
elseif ltype == "trojan" then
|
||||
listener.users = {
|
||||
{
|
||||
username = "1",
|
||||
password = get_server_field(sid, "trojan_password", "")
|
||||
}
|
||||
}
|
||||
elseif ltype == "shadowsocks" then
|
||||
listener.type = "shadowsocks"
|
||||
listener.cipher = get_server_field(sid, "security", "chacha20-ietf-poly1305")
|
||||
listener.password = get_server_field(sid, "ss_password", "")
|
||||
listener.udp = true
|
||||
else
|
||||
return nil
|
||||
end
|
||||
|
||||
local network = get_server_field(sid, "network", "tcp")
|
||||
if network == "ws" then
|
||||
listener["ws-path"] = string_or_nil(get_server_field(sid, "ws_path", "/"))
|
||||
elseif network == "grpc" then
|
||||
listener["grpc-service-name"] = string_or_nil(get_server_field(sid, "grpc_service", ""))
|
||||
end
|
||||
|
||||
local cert = get_server_field(sid, "certpath", "")
|
||||
local key = get_server_field(sid, "keypath", "")
|
||||
if cert ~= "" and key ~= "" then
|
||||
listener.certificate = cert
|
||||
listener["private-key"] = key
|
||||
end
|
||||
|
||||
return {
|
||||
["allow-lan"] = true,
|
||||
["bind-address"] = "*",
|
||||
["log-level"] = "silent",
|
||||
["find-process-mode"] = "off",
|
||||
listeners = { listener }
|
||||
}
|
||||
end
|
||||
|
||||
local function generate_mihomo_listener(sid, output_path)
|
||||
local doc = build_mihomo_listener_doc(sid)
|
||||
if not doc then
|
||||
io.stderr:write("unsupported_listener\n")
|
||||
return false
|
||||
end
|
||||
local ok, rendered = pcall(lyaml.dump, { doc })
|
||||
if not ok or not rendered then
|
||||
io.stderr:write("dump_failed\n")
|
||||
return false
|
||||
end
|
||||
write_file(output_path, rendered)
|
||||
return true
|
||||
end
|
||||
|
||||
local action = arg[1]
|
||||
if action == "validate" then
|
||||
os.exit(validate(arg[2]) and 0 or 1)
|
||||
@ -931,9 +1398,13 @@ elseif action == "tuic" then
|
||||
os.exit(generate_tuic_runtime(arg[2], arg[3], arg[4], arg[5], arg[6]) and 0 or 1)
|
||||
elseif action == "ss" then
|
||||
os.exit(generate_shadowsocks_runtime(arg[2], arg[3], arg[4], arg[5], arg[6]) and 0 or 1)
|
||||
elseif action == "v2ray" then
|
||||
os.exit(generate_v2ray_runtime(arg[2], arg[3], arg[4], arg[5], arg[6]) and 0 or 1)
|
||||
elseif action == "ss_server" then
|
||||
os.exit(generate_shadowsocks_server(arg[2], arg[3]) and 0 or 1)
|
||||
elseif action == "v2ray_server" then
|
||||
os.exit(generate_mihomo_listener(arg[2], arg[3]) and 0 or 1)
|
||||
else
|
||||
io.stderr:write("usage: clash_yaml.lua validate <yaml> | filter <yaml> <words> | prepare <input> <output> | merge <raw> <overlay> <output> | append_client_policy_rules <runtime_yaml> <sid> | tuic <sid> <output> <local_port> [socks_port] [mode] | ss <sid> <output> <local_port> [socks_port] [mode] | ss_server <sid> <output>\n")
|
||||
io.stderr:write("usage: clash_yaml.lua validate <yaml> | filter <yaml> <words> | prepare <input> <output> | merge <raw> <overlay> <output> | append_client_policy_rules <runtime_yaml> <sid> | tuic <sid> <output> <local_port> [socks_port] [mode] | ss <sid> <output> <local_port> [socks_port] [mode] | v2ray <sid> <output> <local_port> [socks_port] [mode] | ss_server <sid> <output> | v2ray_server <sid> <output>\n")
|
||||
os.exit(1)
|
||||
end
|
||||
|
||||
@ -29,7 +29,7 @@ config server_subscribe
|
||||
option auto_update_min_time '0'
|
||||
option url_test_url 'https://www.google.com/generate_204'
|
||||
option user_agent 'v2rayN/9.99'
|
||||
option filter_words '过期/套餐/剩余/网址/QQ群/官网/防失联/回国'
|
||||
option filter_words '过期/重置/套餐/剩余/网址/QQ群/官网/防失联/回国'
|
||||
|
||||
config access_control
|
||||
option lan_ac_mode '0'
|
||||
|
||||
@ -26,9 +26,12 @@ local ucic = require "luci.model.uci".cursor()
|
||||
local proxy = ucic:get_first(name, 'server_subscribe', 'proxy', '0')
|
||||
local switch = ucic:get_first(name, 'server_subscribe', 'switch', '1')
|
||||
local allow_insecure = ucic:get_first(name, 'server_subscribe', 'allow_insecure', '0')
|
||||
local filter_words = ucic:get_first(name, 'server_subscribe', 'filter_words', '过期/套餐/剩余/网址/QQ群/官网/防失联/回国')
|
||||
local filter_words = ucic:get_first(name, 'server_subscribe', 'filter_words', '过期/重置/套餐/剩余/网址/QQ群/官网/防失联/回国')
|
||||
local save_words = ucic:get_first(name, 'server_subscribe', 'save_words', '')
|
||||
local user_agent = ucic:get_first(name, 'server_subscribe', 'user_agent', 'v2rayN/9.99')
|
||||
local sub_convert = ucic:get_first(name, 'server_subscribe', 'sub_convert', '0')
|
||||
local convert_address = ucic:get_first(name, 'server_subscribe', 'convert_address', 'https://api.asailor.org/sub')
|
||||
local template_url = ucic:get_first(name, 'server_subscribe', 'template_url', '')
|
||||
local local_clash_dir = "/etc/ssrplus/clash"
|
||||
local target_subscribe_sid = tostring(arg and arg[1] or ""):gsub("^%s*(.-)%s*$", "%1")
|
||||
|
||||
@ -125,6 +128,11 @@ local function urlEncode(szText)
|
||||
return str
|
||||
end
|
||||
|
||||
local function urlEncodeRFC3986(szText)
|
||||
local str = tostring(szText or ""):gsub("([^0-9a-zA-Z%-_%.~])", get_urlencode)
|
||||
return str
|
||||
end
|
||||
|
||||
local function get_urldecode(h)
|
||||
return schar(tonumber(h, 16))
|
||||
end
|
||||
@ -397,6 +405,7 @@ local function parseAnytlsShare(content)
|
||||
if not userinfo or not hostinfo then
|
||||
return nil
|
||||
end
|
||||
hostinfo = hostinfo:gsub("/+$", "")
|
||||
|
||||
local password = UrlDecode(userinfo)
|
||||
local server, port = hostinfo:match("^(.+):(%d+)$")
|
||||
@ -468,6 +477,389 @@ local function buildAnytlsClashYaml(entries, group_name)
|
||||
return table.concat(lines, "\n") .. "\n"
|
||||
end
|
||||
|
||||
local function anytls_to_mihomo_node(entry)
|
||||
if not entry then
|
||||
return nil
|
||||
end
|
||||
|
||||
return {
|
||||
type = "anytls",
|
||||
alias = entry.name,
|
||||
raw_alias = entry.name,
|
||||
server = entry.server,
|
||||
server_port = entry.port,
|
||||
password = entry.password,
|
||||
tls_host = entry.sni,
|
||||
insecure = entry.allow_insecure and "1" or "0",
|
||||
fingerprint = entry.client_fingerprint
|
||||
}
|
||||
end
|
||||
|
||||
local function split_csv_values(value)
|
||||
local items = {}
|
||||
for v in tostring(value or ""):gmatch("[^,;|%s]+") do
|
||||
items[#items + 1] = v
|
||||
end
|
||||
return items
|
||||
end
|
||||
|
||||
local function bool_from_flag(value)
|
||||
return is_true_value(value) and true or nil
|
||||
end
|
||||
|
||||
local function number_from_value(value)
|
||||
if value == nil or value == "" then
|
||||
return nil
|
||||
end
|
||||
return tonumber(value)
|
||||
end
|
||||
|
||||
local function string_from_value(value)
|
||||
if value == nil or value == "" then
|
||||
return nil
|
||||
end
|
||||
return tostring(value)
|
||||
end
|
||||
|
||||
local function split_plugin_opts(value)
|
||||
local opts = {}
|
||||
for part in tostring(value or ""):gmatch("[^;]+") do
|
||||
local key, val = part:match("^%s*([^=]+)=?(.*)%s*$")
|
||||
if key and key ~= "" then
|
||||
opts[key] = val or ""
|
||||
end
|
||||
end
|
||||
return opts
|
||||
end
|
||||
|
||||
local function pick_plugin_opt(opts, ...)
|
||||
for i = 1, select("#", ...) do
|
||||
local key = select(i, ...)
|
||||
if opts[key] ~= nil and opts[key] ~= "" then
|
||||
return opts[key]
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function normalize_mihomo_plugin_name(plugin)
|
||||
local value = tostring(plugin or ""):lower()
|
||||
if value == "" or value == "none" then
|
||||
return ""
|
||||
end
|
||||
if value == "simple-obfs" or value == "obfs" or value == "obfs-local" then
|
||||
return "obfs"
|
||||
end
|
||||
if value == "xray-plugin" then
|
||||
return "v2ray-plugin"
|
||||
end
|
||||
if value == "shadowtls" then
|
||||
return "shadow-tls"
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
local function apply_mihomo_ss_plugin(proxy, node)
|
||||
local plugin = normalize_mihomo_plugin_name(node.plugin)
|
||||
if plugin == "" then
|
||||
return
|
||||
end
|
||||
|
||||
local opts = split_plugin_opts(node.plugin_opts)
|
||||
proxy.plugin = plugin
|
||||
if plugin == "obfs" then
|
||||
proxy["plugin-opts"] = {
|
||||
mode = pick_plugin_opt(opts, "obfs", "mode") or "http",
|
||||
host = string_from_value(pick_plugin_opt(opts, "obfs-host", "obfs_host", "host"))
|
||||
}
|
||||
elseif plugin == "shadow-tls" then
|
||||
proxy["client-fingerprint"] = string_from_value(node.fingerprint)
|
||||
proxy["plugin-opts"] = {
|
||||
host = string_from_value(pick_plugin_opt(opts, "host")),
|
||||
password = string_from_value(pick_plugin_opt(opts, "passwd", "password")),
|
||||
version = number_from_value(pick_plugin_opt(opts, "version")) or (opts.v3 == "1" and 3) or (opts.v2 == "1" and 2) or (opts.v1 == "1" and 1) or nil
|
||||
}
|
||||
elseif plugin == "v2ray-plugin" then
|
||||
proxy["plugin-opts"] = {
|
||||
mode = pick_plugin_opt(opts, "mode") or "websocket",
|
||||
tls = bool_from_flag(pick_plugin_opt(opts, "tls")),
|
||||
host = string_from_value(pick_plugin_opt(opts, "host")),
|
||||
path = string_from_value(pick_plugin_opt(opts, "path")),
|
||||
mux = bool_from_flag(pick_plugin_opt(opts, "mux")),
|
||||
["skip-cert-verify"] = bool_from_flag(pick_plugin_opt(opts, "skip-cert-verify", "skip_cert_verify", "insecure")),
|
||||
["v2ray-http-upgrade"] = bool_from_flag(pick_plugin_opt(opts, "v2ray-http-upgrade", "v2ray_http_upgrade"))
|
||||
}
|
||||
else
|
||||
proxy["plugin-opts"] = next(opts) and opts or nil
|
||||
end
|
||||
end
|
||||
|
||||
local function apply_mihomo_tls_options(proxy, node)
|
||||
local tls_enabled = node.tls == "1" or node.reality == "1"
|
||||
if not tls_enabled then
|
||||
return
|
||||
end
|
||||
|
||||
proxy.tls = true
|
||||
proxy.servername = string_from_value(node.tls_host)
|
||||
proxy.fingerprint = string_from_value(node.tls_CertSha)
|
||||
proxy["client-fingerprint"] = string_from_value(node.fingerprint)
|
||||
proxy["skip-cert-verify"] = bool_from_flag(node.insecure)
|
||||
|
||||
local alpn = split_csv_values(node.tls_alpn)
|
||||
if #alpn > 0 then
|
||||
proxy.alpn = alpn
|
||||
end
|
||||
|
||||
if node.reality == "1" then
|
||||
proxy["reality-opts"] = {
|
||||
["public-key"] = string_from_value(node.reality_publickey),
|
||||
["short-id"] = string_from_value(node.reality_shortid),
|
||||
["support-x25519mlkem768"] = bool_from_flag(node.enable_mldsa65verify)
|
||||
}
|
||||
end
|
||||
|
||||
if node.enable_ech == "1" and node.ech_config and node.ech_config ~= "" then
|
||||
proxy["ech-opts"] = {
|
||||
enable = true,
|
||||
config = node.ech_config
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
local function apply_mihomo_trojan_tls_options(proxy, node)
|
||||
proxy.sni = string_from_value(node.tls_host)
|
||||
proxy.fingerprint = string_from_value(node.tls_CertSha)
|
||||
proxy["client-fingerprint"] = string_from_value(node.fingerprint)
|
||||
proxy["skip-cert-verify"] = bool_from_flag(node.insecure)
|
||||
|
||||
local alpn = split_csv_values(node.tls_alpn)
|
||||
if #alpn > 0 then
|
||||
proxy.alpn = alpn
|
||||
end
|
||||
|
||||
if node.reality == "1" then
|
||||
proxy["reality-opts"] = {
|
||||
["public-key"] = string_from_value(node.reality_publickey),
|
||||
["short-id"] = string_from_value(node.reality_shortid)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
local function apply_mihomo_transport_options(proxy, node)
|
||||
local transport = node.transport or "raw"
|
||||
if transport == "raw" or transport == "tcp" or transport == "" then
|
||||
return
|
||||
end
|
||||
if transport == "httpupgrade" then
|
||||
proxy.network = "ws"
|
||||
proxy["ws-opts"] = {
|
||||
path = string_from_value(node.httpupgrade_path),
|
||||
headers = node.httpupgrade_host and node.httpupgrade_host ~= "" and { Host = node.httpupgrade_host } or nil,
|
||||
["v2ray-http-upgrade"] = true
|
||||
}
|
||||
elseif transport == "ws" then
|
||||
proxy.network = "ws"
|
||||
proxy["ws-opts"] = {
|
||||
path = string_from_value(node.ws_path),
|
||||
headers = node.ws_host and node.ws_host ~= "" and { Host = node.ws_host } or nil
|
||||
}
|
||||
elseif transport == "h2" then
|
||||
proxy.network = "h2"
|
||||
proxy["h2-opts"] = {
|
||||
host = node.h2_host and node.h2_host ~= "" and split_csv_values(node.h2_host) or nil,
|
||||
path = string_from_value(node.h2_path)
|
||||
}
|
||||
elseif transport == "grpc" then
|
||||
proxy.network = "grpc"
|
||||
proxy["grpc-opts"] = {
|
||||
["grpc-service-name"] = string_from_value(node.serviceName)
|
||||
}
|
||||
elseif transport == "xhttp" and proxy.type == "vless" then
|
||||
proxy.network = "xhttp"
|
||||
proxy["xhttp-opts"] = {
|
||||
path = string_from_value(node.xhttp_path),
|
||||
host = string_from_value(node.xhttp_host),
|
||||
mode = string_from_value(node.xhttp_mode)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
local function to_mihomo_proxy(node)
|
||||
local proxy = {
|
||||
name = node.alias,
|
||||
server = node.server,
|
||||
port = tonumber(node.server_port) or node.server_port,
|
||||
udp = true
|
||||
}
|
||||
|
||||
if node.type == "v2ray" and node.v2ray_protocol == "vmess" then
|
||||
proxy.type = "vmess"
|
||||
proxy.uuid = node.vmess_id
|
||||
proxy.alterId = tonumber(node.alter_id) or 0
|
||||
proxy.cipher = node.security or "auto"
|
||||
proxy.tfo = bool_from_flag(node.fast_open)
|
||||
apply_mihomo_tls_options(proxy, node)
|
||||
apply_mihomo_transport_options(proxy, node)
|
||||
elseif node.type == "v2ray" and node.v2ray_protocol == "vless" then
|
||||
proxy.type = "vless"
|
||||
proxy.uuid = node.vmess_id
|
||||
proxy.flow = string_from_value(node.tls_flow)
|
||||
proxy.encryption = node.vless_encryption or ""
|
||||
proxy.tfo = bool_from_flag(node.fast_open)
|
||||
apply_mihomo_tls_options(proxy, node)
|
||||
apply_mihomo_transport_options(proxy, node)
|
||||
elseif node.type == "ss" or node.type == "ss-rust" or (node.type == "v2ray" and node.v2ray_protocol == "shadowsocks") then
|
||||
proxy.type = "ss"
|
||||
proxy.cipher = node.encrypt_method_ss or node.encrypt_method
|
||||
proxy.password = node.password
|
||||
proxy.tfo = bool_from_flag(node.fast_open)
|
||||
apply_mihomo_ss_plugin(proxy, node)
|
||||
elseif node.type == "ssr" then
|
||||
proxy.type = "ssr"
|
||||
proxy.cipher = node.encrypt_method
|
||||
proxy.password = node.password
|
||||
proxy.obfs = node.obfs
|
||||
proxy.protocol = node.protocol
|
||||
proxy["obfs-param"] = string_from_value(node.obfs_param)
|
||||
proxy["protocol-param"] = string_from_value(node.protocol_param)
|
||||
proxy.tfo = bool_from_flag(node.fast_open)
|
||||
elseif node.type == "v2ray" and node.v2ray_protocol == "trojan" then
|
||||
proxy.type = "trojan"
|
||||
proxy.password = node.password
|
||||
proxy.tfo = bool_from_flag(node.fast_open)
|
||||
apply_mihomo_trojan_tls_options(proxy, node)
|
||||
apply_mihomo_transport_options(proxy, node)
|
||||
elseif node.type == "tuic" then
|
||||
local alpn = split_csv_values(node.tls_alpn)
|
||||
local heartbeat = number_from_value(node.heartbeat)
|
||||
local timeout = number_from_value(node.timeout)
|
||||
proxy.type = "tuic"
|
||||
proxy.uuid = node.tuic_uuid
|
||||
proxy.password = node.tuic_passwd
|
||||
proxy.ip = string_from_value(node.tuic_ip)
|
||||
proxy.sni = string_from_value(node.tls_host)
|
||||
proxy.alpn = (#alpn > 0) and alpn or nil
|
||||
proxy["udp-relay-mode"] = string_from_value(node.udp_relay_mode)
|
||||
proxy["congestion-controller"] = string_from_value(node.congestion_control)
|
||||
proxy["heartbeat-interval"] = heartbeat and (heartbeat * 1000) or nil
|
||||
proxy["request-timeout"] = timeout and (timeout * 1000) or nil
|
||||
proxy["max-udp-relay-packet-size"] = number_from_value(node.tuic_max_package_size)
|
||||
proxy["disable-sni"] = bool_from_flag(node.disable_sni)
|
||||
proxy["reduce-rtt"] = bool_from_flag(node.zero_rtt_handshake)
|
||||
proxy["skip-cert-verify"] = bool_from_flag(node.insecure)
|
||||
elseif node.type == "v2ray" and (node.v2ray_protocol == "hysteria2" or node.v2ray_protocol == "hy2") then
|
||||
local alpn = split_csv_values(node.tls_alpn)
|
||||
proxy.type = "hysteria2"
|
||||
proxy.password = node.hy2_auth
|
||||
proxy.ports = string_from_value(node.port_range)
|
||||
proxy.up = node.uplink_capacity and (tostring(node.uplink_capacity) .. " Mbps") or nil
|
||||
proxy.down = node.downlink_capacity and (tostring(node.downlink_capacity) .. " Mbps") or nil
|
||||
proxy.sni = string_from_value(node.tls_host)
|
||||
proxy.fingerprint = string_from_value(node.tls_CertSha)
|
||||
proxy["skip-cert-verify"] = bool_from_flag(node.insecure)
|
||||
proxy.alpn = (#alpn > 0) and alpn or nil
|
||||
if node.flag_obfs == "1" then
|
||||
proxy.obfs = string_from_value(node.obfs_type)
|
||||
proxy["obfs-password"] = string_from_value(node.salamander)
|
||||
end
|
||||
elseif node.type == "anytls" then
|
||||
proxy.type = "anytls"
|
||||
proxy.password = node.password
|
||||
proxy.sni = string_from_value(node.tls_host)
|
||||
proxy["client-fingerprint"] = string_from_value(node.fingerprint)
|
||||
proxy["skip-cert-verify"] = bool_from_flag(node.insecure)
|
||||
elseif node.type == "v2ray" and node.v2ray_protocol == "snell" then
|
||||
proxy.type = "snell"
|
||||
proxy.psk = node.snell_psk
|
||||
proxy.version = tonumber(node.snell_version) or nil
|
||||
local obfs_mode = string_from_value(node.snell_obfs)
|
||||
local obfs_host = string_from_value(node.snell_obfs_host)
|
||||
if obfs_mode or obfs_host then
|
||||
proxy["obfs-opts"] = {
|
||||
mode = obfs_mode,
|
||||
host = obfs_host
|
||||
}
|
||||
end
|
||||
else
|
||||
return nil
|
||||
end
|
||||
|
||||
if not proxy.name or proxy.name == "" or not proxy.server or not proxy.port then
|
||||
return nil
|
||||
end
|
||||
|
||||
return proxy
|
||||
end
|
||||
|
||||
local function can_group_into_mihomo(node)
|
||||
if not node then
|
||||
return false
|
||||
end
|
||||
if node.type == "ssr" or node.type == "ss" or node.type == "ss-rust" then
|
||||
return true
|
||||
end
|
||||
if node.type == "v2ray" and (
|
||||
node.v2ray_protocol == "vmess"
|
||||
or node.v2ray_protocol == "vless"
|
||||
or node.v2ray_protocol == "shadowsocks"
|
||||
or node.v2ray_protocol == "trojan"
|
||||
or node.v2ray_protocol == "hysteria2"
|
||||
or node.v2ray_protocol == "hy2"
|
||||
or node.v2ray_protocol == "snell"
|
||||
) then
|
||||
return true
|
||||
end
|
||||
if node.type == "tuic" then
|
||||
return true
|
||||
end
|
||||
if node.type == "anytls" then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function buildMihomoSubscribeYaml(nodes, group_name)
|
||||
local proxies = {}
|
||||
local names = {}
|
||||
|
||||
for _, node in ipairs(nodes) do
|
||||
local proxy = to_mihomo_proxy(node)
|
||||
if proxy then
|
||||
proxies[#proxies + 1] = proxy
|
||||
names[#names + 1] = proxy.name
|
||||
end
|
||||
end
|
||||
|
||||
if #proxies == 0 then
|
||||
return nil, 0
|
||||
end
|
||||
|
||||
local doc = {
|
||||
mode = "rule",
|
||||
["log-level"] = "silent",
|
||||
proxies = proxies,
|
||||
["proxy-groups"] = {
|
||||
{
|
||||
name = group_name,
|
||||
type = "select",
|
||||
proxies = names
|
||||
}
|
||||
},
|
||||
rules = { "MATCH," .. group_name }
|
||||
}
|
||||
|
||||
local ok_lyaml, lyaml = pcall(require, "lyaml")
|
||||
if ok_lyaml then
|
||||
local ok_dump, dumped = pcall(lyaml.dump, { doc })
|
||||
if ok_dump and dumped then
|
||||
return dumped, #proxies
|
||||
end
|
||||
end
|
||||
|
||||
return nil, 0
|
||||
end
|
||||
|
||||
local function processLocalClashSubscription(path, alias)
|
||||
local result = {
|
||||
type = "clash",
|
||||
@ -499,7 +891,7 @@ local function processData(szType, content, cfgid)
|
||||
local url = URL.parse("http://" .. content)
|
||||
local params = url.query
|
||||
|
||||
if not has_xray then
|
||||
if not has_xray and not has_mihomo then
|
||||
return nil
|
||||
end
|
||||
|
||||
@ -562,6 +954,30 @@ local function processData(szType, content, cfgid)
|
||||
-- 处理 fast open 参数
|
||||
result.fast_open = params.tfo
|
||||
end
|
||||
elseif szType == "snell" then
|
||||
if not has_mihomo then
|
||||
log("跳过 Snell 节点:本地未安装 mihomo。")
|
||||
return nil
|
||||
end
|
||||
|
||||
local url = URL.parse("http://" .. content)
|
||||
local params = url.query or {}
|
||||
local raw_alias = url.fragment and UrlDecode(url.fragment) or nil
|
||||
local psk = url.user and UrlDecode(url.user) or first_nonempty(params, {"psk", "password"})
|
||||
|
||||
result.type = "v2ray"
|
||||
result.v2ray_protocol = "snell"
|
||||
result.raw_alias = raw_alias
|
||||
result.alias = raw_alias
|
||||
result.server = normalize_host(url.host)
|
||||
result.server_port = url.port
|
||||
result.snell_psk = psk
|
||||
result.snell_version = first_nonempty(params, {"version", "v"}) or "4"
|
||||
result.snell_obfs = first_nonempty(params, {"obfs", "obfs-mode", "obfs_mode"})
|
||||
result.snell_obfs_host = first_nonempty(params, {"obfs-host", "obfs_host", "host"})
|
||||
if not result.snell_psk or result.snell_psk == "" then
|
||||
result.server = nil
|
||||
end
|
||||
elseif szType == 'ssr' then
|
||||
-- 去掉前后空白和#注释
|
||||
local link = trim(content:gsub("#.*$", ""))
|
||||
@ -1168,7 +1584,7 @@ local function processData(szType, content, cfgid)
|
||||
end
|
||||
|
||||
-- 自动决定模式(true=Xray, false=普通 Trojan)
|
||||
if not has_xray then
|
||||
if not has_xray and not has_mihomo then
|
||||
return nil
|
||||
end
|
||||
|
||||
@ -1575,6 +1991,56 @@ local function curl(url, user_agent)
|
||||
return stdout, md5
|
||||
end
|
||||
|
||||
local function build_convert_url(url)
|
||||
if sub_convert ~= "1" then
|
||||
return url, false
|
||||
end
|
||||
|
||||
local tpl = trim(template_url or "")
|
||||
local endpoint = trim(convert_address or "")
|
||||
local exclude = trim(filter_words or "")
|
||||
if tpl == "" or endpoint == "" then
|
||||
return url, false
|
||||
end
|
||||
|
||||
endpoint = endpoint:gsub("%s+$", ""):gsub("^%s+", ""):gsub("%z", ""):gsub("[\r\n]", "")
|
||||
local sep = endpoint:find("?", 1, true) and "&" or "?"
|
||||
local query = "target=clash"
|
||||
.. "&new_name=true"
|
||||
.. "&url=" .. urlEncodeRFC3986(url)
|
||||
.. "&config=" .. urlEncodeRFC3986(tpl)
|
||||
.. "&exclude=" .. urlEncodeRFC3986(exclude)
|
||||
.. "&emoji=false"
|
||||
.. "&list=false"
|
||||
.. "&sort=false"
|
||||
.. "&udp=true"
|
||||
.. "&scv=" .. (allow_insecure == "1" and "true" or "false")
|
||||
.. "&fdn=true"
|
||||
|
||||
return endpoint .. sep .. query, true
|
||||
end
|
||||
|
||||
local function filterClashYamlRaw(raw)
|
||||
if not raw or raw == "" then
|
||||
return raw, 0
|
||||
end
|
||||
|
||||
local tmp = string.format("/tmp/ssrplus-subscribe-filter-%d-%d.yaml", nixio.getpid(), os.time())
|
||||
local ok = nixio.fs.writefile(tmp, raw)
|
||||
if not ok then
|
||||
return raw, 0
|
||||
end
|
||||
|
||||
local removed = tonumber(trim(luci.sys.exec(string.format(
|
||||
"/usr/bin/lua /usr/share/shadowsocksr/clash_yaml.lua filter %s %s 2>/dev/null",
|
||||
shell_quote(tmp),
|
||||
shell_quote(filter_words)
|
||||
)))) or 0
|
||||
local filtered = nixio.fs.readfile(tmp) or raw
|
||||
nixio.fs.remove(tmp)
|
||||
return filtered, removed
|
||||
end
|
||||
|
||||
local function collect_wan_interfaces()
|
||||
local ifaces = {}
|
||||
local seen = {}
|
||||
@ -1845,6 +2311,7 @@ end
|
||||
-- 加载订阅未变化的节点用于防止被误删
|
||||
local function loadOldNodes(groupHash)
|
||||
local nodes = {}
|
||||
local count = 0
|
||||
cache[groupHash] = {}
|
||||
nodeResult[#nodeResult + 1] = nodes
|
||||
local index = #nodeResult
|
||||
@ -1854,8 +2321,22 @@ local function loadOldNodes(groupHash)
|
||||
local section = setmetatable({}, {__index = s})
|
||||
nodes[s.hashkey] = section
|
||||
cache[groupHash][s.hashkey] = section
|
||||
count = count + 1
|
||||
end
|
||||
end)
|
||||
|
||||
return count
|
||||
end
|
||||
|
||||
local function group_has_saved_nodes(groupHash)
|
||||
local found = false
|
||||
ucic:foreach(name, uciType, function(s)
|
||||
if s.grouphashkey == groupHash and s.hashkey then
|
||||
found = true
|
||||
return false
|
||||
end
|
||||
end)
|
||||
return found
|
||||
end
|
||||
|
||||
local function get_section_ss_backend(section)
|
||||
@ -1901,6 +2382,58 @@ local function group_needs_ss_backend_refresh(groupHash)
|
||||
return has_ss_node and needs_refresh
|
||||
end
|
||||
|
||||
local function is_mihomo_subscribe_node(section)
|
||||
if not section then
|
||||
return false
|
||||
end
|
||||
if section.type == "clash" and section.clash_path and tostring(section.clash_path):match("%.mihomo%.yaml$") then
|
||||
return true
|
||||
end
|
||||
if section.type == "ssr" then
|
||||
return true
|
||||
end
|
||||
if section.type == "ss" or section.type == "ss-rust" then
|
||||
return true
|
||||
end
|
||||
if section.type == "v2ray" and (
|
||||
section.v2ray_protocol == "vmess"
|
||||
or section.v2ray_protocol == "vless"
|
||||
or section.v2ray_protocol == "shadowsocks"
|
||||
or section.v2ray_protocol == "trojan"
|
||||
or section.v2ray_protocol == "hysteria2"
|
||||
or section.v2ray_protocol == "hy2"
|
||||
or section.v2ray_protocol == "snell"
|
||||
) then
|
||||
return true
|
||||
end
|
||||
if section.type == "tuic" then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function group_needs_mihomo_subscribe_refresh(groupHash)
|
||||
if not has_mihomo then
|
||||
return false
|
||||
end
|
||||
|
||||
local needs_refresh = false
|
||||
ucic:foreach(name, uciType, function(s)
|
||||
if s.grouphashkey == groupHash then
|
||||
if is_mihomo_subscribe_node(s) and s.type ~= "clash" then
|
||||
needs_refresh = true
|
||||
return false
|
||||
end
|
||||
if s.type == "clash" and s.clash_path and tostring(s.clash_path):match("%.anytls%.yaml$") then
|
||||
needs_refresh = true
|
||||
return false
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
return needs_refresh
|
||||
end
|
||||
|
||||
local function preserve_unselected_groups(selected_hashes)
|
||||
local preserved = {}
|
||||
|
||||
@ -1918,7 +2451,8 @@ local execute = function()
|
||||
local selected_hashes = {}
|
||||
|
||||
for _, item in ipairs(subscribe_items) do
|
||||
selected_hashes[md5(item.url)] = true
|
||||
local fetch_url = build_convert_url(item.url)
|
||||
selected_hashes[md5(fetch_url)] = true
|
||||
end
|
||||
|
||||
if target_subscribe_sid ~= "" then
|
||||
@ -1927,21 +2461,38 @@ local execute = function()
|
||||
|
||||
for _, item in ipairs(subscribe_items) do
|
||||
local url = item.url
|
||||
local raw, new_md5 = curl(url, user_agent)
|
||||
local fetch_url, converted = build_convert_url(url)
|
||||
local raw, new_md5 = curl(fetch_url, user_agent)
|
||||
local yaml_removed_count = 0
|
||||
if isClashYAML(raw) then
|
||||
raw, yaml_removed_count = filterClashYamlRaw(raw)
|
||||
if yaml_removed_count and yaml_removed_count > 0 then
|
||||
new_md5 = md5_string(raw)
|
||||
end
|
||||
end
|
||||
log("raw 长度: "..#raw)
|
||||
local groupHash = md5(url)
|
||||
local groupHash = md5(fetch_url)
|
||||
local old_md5 = read_old_md5(groupHash)
|
||||
|
||||
log("处理订阅: " .. url)
|
||||
if converted then
|
||||
log("使用订阅转换模板: " .. template_url)
|
||||
log("转换服务: " .. convert_address)
|
||||
end
|
||||
if yaml_removed_count and yaml_removed_count > 0 then
|
||||
log("Clash/Mihomo YAML 已按订阅过滤关键词移除节点数量: " .. tostring(yaml_removed_count))
|
||||
end
|
||||
log("groupHash: " .. groupHash)
|
||||
log("old_md5: " .. tostring(old_md5))
|
||||
log("new_md5: " .. tostring(new_md5))
|
||||
|
||||
local backend_refresh = group_needs_ss_backend_refresh(groupHash)
|
||||
local mihomo_subscribe_refresh = group_needs_mihomo_subscribe_refresh(groupHash)
|
||||
local missing_saved_nodes = old_md5 and new_md5 == old_md5 and not group_has_saved_nodes(groupHash)
|
||||
if #raw == 0 then
|
||||
log(url .. ': 获取内容为空')
|
||||
loadOldNodes(groupHash)
|
||||
elseif old_md5 and new_md5 == old_md5 and not backend_refresh then
|
||||
elseif old_md5 and new_md5 == old_md5 and not backend_refresh and not mihomo_subscribe_refresh and not missing_saved_nodes then
|
||||
log("订阅未变化, 跳过无需更新的订阅: " .. url)
|
||||
-- 防止 diff 阶段误删未更新订阅节点
|
||||
loadOldNodes(groupHash)
|
||||
@ -1955,6 +2506,12 @@ local execute = function()
|
||||
if backend_refresh and old_md5 and new_md5 == old_md5 then
|
||||
log("检测到 SS 后端偏好变化,强制重建订阅节点: " .. url)
|
||||
end
|
||||
if mihomo_subscribe_refresh and old_md5 and new_md5 == old_md5 then
|
||||
log("检测到可迁移订阅节点,强制重建为 Mihomo 总节点: " .. url)
|
||||
end
|
||||
if missing_saved_nodes then
|
||||
log("订阅内容未变化但本地节点不存在,强制重建订阅节点: " .. url)
|
||||
end
|
||||
updated = true
|
||||
-- 保存更新后的 MD5 值到以 groupHash 为标识的临时文件中,用于下次订阅更新时进行对比
|
||||
write_new_md5(groupHash, new_md5)
|
||||
@ -1967,7 +2524,7 @@ local execute = function()
|
||||
|
||||
if isClashYAML(raw) then
|
||||
is_clash_subscription = true
|
||||
local result = processClashSubscription(url)
|
||||
local result = processClashSubscription(fetch_url)
|
||||
if result and not check_filer(result) and not cache[groupHash][result.hashkey] then
|
||||
result.grouphashkey = groupHash
|
||||
table.insert(nodeResult[index], result)
|
||||
@ -2014,7 +2571,7 @@ local execute = function()
|
||||
if line:match("^anytls://") then
|
||||
local parsed = parseAnytlsShare(line:gsub("^anytls://", ""))
|
||||
if parsed then
|
||||
table.insert(anytls_nodes, parsed)
|
||||
table.insert(anytls_nodes, anytls_to_mihomo_node(parsed))
|
||||
end
|
||||
elseif line ~= "" then
|
||||
table.insert(normal_nodes, node)
|
||||
@ -2022,19 +2579,8 @@ local execute = function()
|
||||
end
|
||||
|
||||
if #anytls_nodes > 0 then
|
||||
local parsed_url = URL.parse(url)
|
||||
local alias = "Clash_" .. (parsed_url.host or groupHash)
|
||||
local local_path = string.format("%s/%s.anytls.yaml", local_clash_dir, groupHash)
|
||||
local yaml = buildAnytlsClashYaml(anytls_nodes, "Proxy")
|
||||
nixio.fs.mkdirr(local_clash_dir)
|
||||
nixio.fs.writefile(local_path, yaml)
|
||||
|
||||
local result = processLocalClashSubscription(local_path, alias)
|
||||
if result and not cache[groupHash][result.hashkey] then
|
||||
result.grouphashkey = groupHash
|
||||
table.insert(nodeResult[index], result)
|
||||
cache[groupHash][result.hashkey] = result
|
||||
log('成功导入 AnyTLS 转 Clash 总节点: ' .. result.alias)
|
||||
for _, parsed in ipairs(anytls_nodes) do
|
||||
table.insert(normal_nodes, parsed)
|
||||
end
|
||||
end
|
||||
|
||||
@ -2046,10 +2592,12 @@ local execute = function()
|
||||
|
||||
if not is_clash_subscription then
|
||||
for _, v in ipairs(nodes) do
|
||||
if v and not string.match(v, "^%s*$") then
|
||||
if v and (type(v) == "table" or not string.match(v, "^%s*$")) then
|
||||
xpcall(function()
|
||||
local result
|
||||
if szType then
|
||||
if type(v) == "table" then
|
||||
result = v
|
||||
elseif szType then
|
||||
result = processData(szType, v)
|
||||
elseif not szType then
|
||||
local node = trim(v)
|
||||
@ -2061,7 +2609,7 @@ local execute = function()
|
||||
if dat[3] then
|
||||
dat3 = "://" .. dat[3]
|
||||
end
|
||||
if dat[1] == 'ss' or dat[1] == 'trojan' or dat[1] == 'tuic' then
|
||||
if dat[1] == 'ss' or dat[1] == 'trojan' or dat[1] == 'tuic' or dat[1] == 'snell' then
|
||||
result = processData(dat[1], dat[2] .. dat3)
|
||||
else
|
||||
result = processData(dat[1], base64Decode(dat[2]))
|
||||
@ -2110,7 +2658,44 @@ local execute = function()
|
||||
end
|
||||
-- 清理临时字段
|
||||
node.raw_alias = nil
|
||||
-- 存入 nodeResult
|
||||
end
|
||||
|
||||
local mihomo_nodes = {}
|
||||
local legacy_nodes = {}
|
||||
for _, node in ipairs(groupRawNodes) do
|
||||
if has_mihomo and can_group_into_mihomo(node) then
|
||||
mihomo_nodes[#mihomo_nodes + 1] = node
|
||||
else
|
||||
legacy_nodes[#legacy_nodes + 1] = node
|
||||
end
|
||||
end
|
||||
|
||||
if #mihomo_nodes > 0 then
|
||||
local parsed_url = URL.parse(url)
|
||||
local alias = item.alias ~= "" and item.alias or ("Mihomo_" .. ((parsed_url and parsed_url.host) or groupHash))
|
||||
local local_path = string.format("%s/%s.mihomo.yaml", local_clash_dir, groupHash)
|
||||
local yaml, proxy_count = buildMihomoSubscribeYaml(mihomo_nodes, "Proxy")
|
||||
|
||||
if yaml and proxy_count > 0 then
|
||||
nixio.fs.mkdirr(local_clash_dir)
|
||||
nixio.fs.writefile(local_path, yaml)
|
||||
|
||||
local result = processLocalClashSubscription(local_path, alias)
|
||||
if result and not cache[groupHash][result.hashkey] then
|
||||
result.grouphashkey = groupHash
|
||||
table.insert(nodeResult[index], result)
|
||||
cache[groupHash][result.hashkey] = result
|
||||
log('成功导入 Mihomo 总节点: ' .. result.alias .. ',包含节点数量: ' .. proxy_count)
|
||||
end
|
||||
else
|
||||
log('Mihomo 总节点生成失败,回退为普通订阅节点。')
|
||||
for _, node in ipairs(mihomo_nodes) do
|
||||
legacy_nodes[#legacy_nodes + 1] = node
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for _, node in ipairs(legacy_nodes) do
|
||||
node.grouphashkey = groupHash
|
||||
table.insert(nodeResult[index], node)
|
||||
cache[groupHash][node.hashkey] = node
|
||||
|
||||
@ -21,13 +21,13 @@ define Download/geoip
|
||||
HASH:=a322dfb6bfd8987c83453c39582a07771c9deddf9f8f7d2d19c7927ebd5e76c8
|
||||
endef
|
||||
|
||||
GEOSITE_VER:=20260620045413
|
||||
GEOSITE_VER:=20260621035818
|
||||
GEOSITE_FILE:=dlc.dat.$(GEOSITE_VER)
|
||||
define Download/geosite
|
||||
URL:=https://github.com/v2fly/domain-list-community/releases/download/$(GEOSITE_VER)/
|
||||
URL_FILE:=dlc.dat
|
||||
FILE:=$(GEOSITE_FILE)
|
||||
HASH:=5ea0d1f019066eff222b0fa5e27d2875000cf0edad3f226663be3c72040a762e
|
||||
HASH:=3aa6a23a6f4bd89cf5b22348fd6527b5b53881cdeece3147ee3ac3381fe61ec2
|
||||
endef
|
||||
|
||||
GEOSITE_IRAN_VER:=202606150209
|
||||
|
||||
Loading…
Reference in New Issue
Block a user