mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-05 09:54:06 +08:00
🍓 Sync 2026-09-29 07:32:36
This commit is contained in:
1 parent
08d3ec6fdd
commit
085188d2d1
13 files changed
+488
-192
No files matched your search
@@ -27,6 +27,11 @@ local TYPE_MAP = {
|
||||
http = "http",
|
||||
}
|
||||
|
||||
-- 该协议能否落到 sing-box outbound(SSR 无法表达,跳过)
|
||||
local function supported(proto)
|
||||
return (TYPE_MAP[proto] or proto) ~= "ssr"
|
||||
end
|
||||
|
||||
-- 构建 TLS 字段
|
||||
local function build_tls(n)
|
||||
local tls
|
||||
@@ -47,6 +52,9 @@ local function build_tls(n)
|
||||
elseif n.skip_cert_verify ~= nil then
|
||||
tls.insecure = bool(n.skip_cert_verify)
|
||||
end
|
||||
-- 空表会被 json_encode 编码成 [],而 tls 必须是对象;
|
||||
-- security=tls 但无 sni/alpn/insecure 时就会走到这里
|
||||
if next(tls) == nil then return util.JSON_EMPTY_OBJECT end
|
||||
return tls
|
||||
end
|
||||
return nil
|
||||
@@ -79,12 +87,13 @@ local function build_transport(n)
|
||||
end
|
||||
|
||||
-- 单节点 → sing-box outbound 表
|
||||
function M.to_outbound(n)
|
||||
-- tag 可选:完整配置里由 util.unique_tags 统一分配,避免节点重名导致 tag 冲突
|
||||
function M.to_outbound(n, tag)
|
||||
local stype = TYPE_MAP[n.proto] or n.proto
|
||||
if stype == "ssr" then return nil end -- sing-box 不支持 SSR,跳过
|
||||
local o = {
|
||||
type = stype,
|
||||
tag = n.name or ((n.server or "") .. ":" .. tostring(n.port or "")),
|
||||
tag = tag or n.name or ((n.server or "") .. ":" .. tostring(n.port or "")),
|
||||
server = n.server or "",
|
||||
server_port = tonumber(n.port) or 0,
|
||||
}
|
||||
@@ -145,15 +154,53 @@ function M.to_outbound(n)
|
||||
return o
|
||||
end
|
||||
|
||||
function M.generate(nodes)
|
||||
local outbounds = {}
|
||||
-- 保留 tag:节点名不得与之重名(util.unique_tags 负责消解)
|
||||
local RESERVED_TAGS = { select = true, auto = true, direct = true, block = true }
|
||||
|
||||
-- 生成完整 sing-box 配置(outbounds + route)。
|
||||
-- 不含 inbounds / dns:这两项会绑定本地监听端口、覆盖用户既有 DNS 设置,
|
||||
-- 由用户在自己的配置里维护,本输出只负责节点与分流。
|
||||
--
|
||||
-- 版本兼容说明:route 规则里的 action 字段自 1.11.0 起才存在,其默认值即 "route",
|
||||
-- 因此这里刻意省略 action —— 未知字段会被 sing-box 拒绝,而省略默认值在
|
||||
-- 1.10 与 1.11+ 上都能工作。
|
||||
function M.generate(nodes, options)
|
||||
local usable = {}
|
||||
for _, n in ipairs(nodes or {}) do
|
||||
if type(n) == "table" then
|
||||
local o = M.to_outbound(n)
|
||||
if o then outbounds[#outbounds + 1] = o end
|
||||
end
|
||||
if type(n) == "table" and supported(n.proto) then usable[#usable + 1] = n end
|
||||
end
|
||||
return util.json_encode({ outbounds = outbounds })
|
||||
local tags = util.unique_tags(usable, RESERVED_TAGS)
|
||||
|
||||
local outbounds, proxy_tags = {}, {}
|
||||
for i, n in ipairs(usable) do
|
||||
outbounds[#outbounds + 1] = M.to_outbound(n, tags[i])
|
||||
proxy_tags[#proxy_tags + 1] = tags[i]
|
||||
end
|
||||
|
||||
local final
|
||||
if #proxy_tags > 0 then
|
||||
-- select 供手工切换,auto 自动测速选优
|
||||
local sel = { type = "selector", tag = "select", outbounds = { "auto", "direct" } }
|
||||
for _, t in ipairs(proxy_tags) do sel.outbounds[#sel.outbounds + 1] = t end
|
||||
sel.default = "auto"
|
||||
outbounds[#outbounds + 1] = sel
|
||||
outbounds[#outbounds + 1] = { type = "urltest", tag = "auto", outbounds = proxy_tags }
|
||||
final = "select"
|
||||
else
|
||||
-- 无可用节点时不能生成 selector / urltest(outbounds 不允许为空)
|
||||
final = "direct"
|
||||
end
|
||||
outbounds[#outbounds + 1] = { type = "direct", tag = "direct" }
|
||||
outbounds[#outbounds + 1] = { type = "block", tag = "block" }
|
||||
|
||||
return util.json_encode({
|
||||
outbounds = outbounds,
|
||||
route = {
|
||||
rules = { { ip_is_private = true, outbound = "direct" } },
|
||||
final = final,
|
||||
auto_detect_interface = true,
|
||||
},
|
||||
})
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -47,13 +47,19 @@ local function build_stream_settings(n)
|
||||
return ss
|
||||
end
|
||||
|
||||
-- 该协议能否落到 V2Ray / Xray outbound(SSR 无法表达,跳过)
|
||||
local function supported(proto)
|
||||
return (proto or "vmess") ~= "ssr"
|
||||
end
|
||||
|
||||
-- 单节点 → V2Ray outbound 表
|
||||
function M.to_outbound(n)
|
||||
-- tag 可选:完整配置里由 util.unique_tags 统一分配,避免节点重名导致 tag 冲突
|
||||
function M.to_outbound(n, tag)
|
||||
local proto = n.proto or "vmess"
|
||||
if proto == "ssr" then return nil end -- V2Ray / Xray 不支持 SSR,跳过
|
||||
if not supported(proto) then return nil end
|
||||
local o = {
|
||||
protocol = (proto == "ss" and "shadowsocks" or proto),
|
||||
tag = n.name or ((n.server or "") .. ":" .. tostring(n.port or "")),
|
||||
tag = tag or n.name or ((n.server or "") .. ":" .. tostring(n.port or "")),
|
||||
settings = {},
|
||||
streamSettings = build_stream_settings(n),
|
||||
}
|
||||
@@ -94,15 +100,61 @@ function M.to_outbound(n)
|
||||
return o
|
||||
end
|
||||
|
||||
function M.generate(nodes)
|
||||
local outbounds = {}
|
||||
for _, n in ipairs(nodes or {}) do
|
||||
if type(n) == "table" then
|
||||
local o = M.to_outbound(n)
|
||||
if o then outbounds[#outbounds + 1] = o end
|
||||
end
|
||||
-- Xray 的 balancer / observatory selector 按「前缀」匹配 outbound tag。
|
||||
-- 若某节点 tag 恰好是 "direct" / "block" / "auto" 的前缀(例如节点名就叫 "d"、"auto"),
|
||||
-- selector 会把这些保留出站一并纳入负载均衡,导致流量被丢进 direct / block。
|
||||
-- 因此把保留 tag 的所有真前缀也登记为冲突,交给 util.unique_tags 加后缀消解。
|
||||
local function reserved_with_prefixes(names)
|
||||
local r = {}
|
||||
for _, s in ipairs(names) do
|
||||
r[s] = true
|
||||
for i = 1, #s - 1 do r[s:sub(1, i)] = true end
|
||||
end
|
||||
return util.json_encode({ outbounds = outbounds })
|
||||
return r
|
||||
end
|
||||
|
||||
-- 生成完整 V2Ray / Xray 配置(outbounds + observatory + routing)。
|
||||
-- 不含 inbounds / dns:这两项会绑定本地监听端口、覆盖用户既有 DNS 设置,
|
||||
-- 由用户在自己的配置里维护,本输出只负责节点与分流。
|
||||
function M.generate(nodes, options)
|
||||
local usable = {}
|
||||
for _, n in ipairs(nodes or {}) do
|
||||
if type(n) == "table" and supported(n.proto) then usable[#usable + 1] = n end
|
||||
end
|
||||
local tags = util.unique_tags(usable, reserved_with_prefixes({ "direct", "block", "auto" }))
|
||||
|
||||
local outbounds, proxy_tags = {}, {}
|
||||
for i, n in ipairs(usable) do
|
||||
outbounds[#outbounds + 1] = M.to_outbound(n, tags[i])
|
||||
proxy_tags[#proxy_tags + 1] = tags[i]
|
||||
end
|
||||
|
||||
-- direct / block 必须存在:路由规则要按 tag 引用它们
|
||||
outbounds[#outbounds + 1] = { protocol = "freedom", tag = "direct", settings = util.JSON_EMPTY_OBJECT }
|
||||
outbounds[#outbounds + 1] = { protocol = "blackhole", tag = "block", settings = { response = { type = "none" } } }
|
||||
|
||||
local routing = {
|
||||
domainStrategy = "IPIfNonMatch",
|
||||
rules = { { type = "field", ip = { "geoip:private" }, outboundTag = "direct" } },
|
||||
}
|
||||
local cfg = { log = { loglevel = "warning" }, outbounds = outbounds, routing = routing }
|
||||
|
||||
if #proxy_tags > 0 then
|
||||
-- leastPing 依赖 observatory 的探测结果;不配 observatory 时该策略不生效
|
||||
cfg.observatory = {
|
||||
subjectSelector = proxy_tags,
|
||||
probeUrl = "https://www.gstatic.com/generate_204",
|
||||
probeInterval = "10s",
|
||||
enableConcurrency = true,
|
||||
}
|
||||
routing.balancers = { { tag = "auto", selector = proxy_tags, strategy = { type = "leastPing" } } }
|
||||
routing.rules[#routing.rules + 1] = { type = "field", network = "tcp,udp", balancerTag = "auto" }
|
||||
else
|
||||
-- 无可用节点时没有 balancer 可引用;首元素 direct 即默认出站
|
||||
routing.rules[#routing.rules + 1] = { type = "field", network = "tcp,udp", outboundTag = "direct" }
|
||||
end
|
||||
|
||||
return util.json_encode(cfg)
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -147,6 +147,11 @@ end
|
||||
-- JSON null 占位符:仅在数组中用于保留位置(对象中的 null 仍解码为 nil)
|
||||
local JSON_NULL = {}
|
||||
|
||||
-- JSON 空对象占位符。Lua 的空表无法区分 {} 与 [](is_array 判定空表为数组),
|
||||
-- 而 sing-box / Xray 的部分字段必须是对象("tls": {}、"settings": {}),
|
||||
-- 编码成 [] 会被客户端拒绝。需要空对象时显式使用本常量。
|
||||
M.JSON_EMPTY_OBJECT = setmetatable({}, { __tostring = function() return "{}" end })
|
||||
|
||||
function M.json_encode(v)
|
||||
local function enc(v)
|
||||
local t = type(v)
|
||||
@@ -169,7 +174,9 @@ function M.json_encode(v)
|
||||
elseif t == "boolean" then
|
||||
return v and "true" or "false"
|
||||
elseif t == "table" then
|
||||
if is_array(v) then
|
||||
if v == M.JSON_EMPTY_OBJECT then
|
||||
return "{}"
|
||||
elseif is_array(v) then
|
||||
local a = {}
|
||||
for i = 1, #v do a[#a + 1] = enc(v[i]) end
|
||||
return "[" .. table.concat(a, ",") .. "]"
|
||||
@@ -378,4 +385,28 @@ function M.human_duration(secs)
|
||||
return "不足1分钟"
|
||||
end
|
||||
|
||||
-- 为一组节点生成唯一 tag(供 sing-box / Xray 完整配置使用)。
|
||||
-- 节点名可能重复,也可能与保留 tag(direct / block / select / auto …)同名;
|
||||
-- sing-box 与 Xray 都要求 outbound tag 唯一,重名时追加 " #2"、" #3"。
|
||||
-- 返回与 nodes 等长的 tag 数组。
|
||||
function M.unique_tags(nodes, reserved)
|
||||
local used = {}
|
||||
for k in pairs(reserved or {}) do used[k] = true end
|
||||
local tags = {}
|
||||
for i, n in ipairs(nodes or {}) do
|
||||
local base = n.name
|
||||
if base == nil or base == "" then
|
||||
base = (n.server or "") .. ":" .. tostring(n.port or "")
|
||||
end
|
||||
local tag, k = base, 2
|
||||
while used[tag] do
|
||||
tag = base .. " #" .. k
|
||||
k = k + 1
|
||||
end
|
||||
used[tag] = true
|
||||
tags[i] = tag
|
||||
end
|
||||
return tags
|
||||
end
|
||||
|
||||
return M
|
||||
Reference in new issue
Block a user