op-packages/luci-app-xjay/files/utils/gen_config.lua
github-actions[bot] 32600acc60 🌴 Sync 2026-03-05 23:51:42
2026-03-05 23:51:42 +08:00

973 lines
32 KiB
Lua

#!/usr/bin/lua
local ucursor = require "luci.model.uci"
local json = require "luci.jsonc"
local nixiofs = require "nixio.fs"
local general_section = ucursor:get_first("xjay", "general")
local general = ucursor:get_all("xjay", general_section)
local inbound_section = ucursor:get_first("xjay", "inbound")
local inbound = ucursor:get_all("xjay", inbound_section)
local outbound_section = ucursor:get_first("xjay", "outbound")
local outbound = ucursor:get_all("xjay", outbound_section)
local dns_section = ucursor:get_first("xjay", "dns")
local dns = ucursor:get_all("xjay", dns_section)
local routing_section = ucursor:get_first("xjay", "routing")
local routing = ucursor:get_all("xjay", routing_section)
local misc_section = ucursor:get_first("xjay", "misc")
local misc = ucursor:get_all("xjay", misc_section)
local function tcp_settings(data, direction)
if data.stream_network == "tcp" then
local acceptproxyprotocal = data.tcp_acceptproxyprotocol == "true" and true or false
return {
acceptProxyProtocol = direction == "inbound" and acceptproxyprotocal or nil,
header = {
type = data.tcp_type,
request = data.tcp_type == "http" and {
version = "1.1",
method = "GET",
path = data.http_path,
headers = {
Host = data.http_host,
User_Agent = {
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36",
"Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/53.0.2785.109 Mobile/14A456 Safari/601.1.46"
},
Accept_Encoding = {"gzip, deflate"},
Connection = {"keep-alive"},
Pragma = "no-cache"
}
} or nil,
response = data.tcp_type == "http" and {
version = "1.1",
status = "200",
reason = "OK",
headers = {
Content_Type = {"application/octet-stream", "video/mpeg"},
Transfer_Encoding = {"chunked"},
Connection = {"keep-alive"},
Pragma = "no-cache"
}
} or nil
} or nil
}
else
return nil
end
end
local function kcp_settings(data, direction)
if data.stream_network == "kcp" then
return {
mtu = data.kcp_mtu ~= nil and tonumber(data.kcp_mtu) or nil,
tti = data.kcp_tti ~= nil and tonumber(data.kcp_tti) or nil,
uplinkCapacity = data.kcp_uplinkcapacity ~= nil and tonumber(data.kcp_uplinkcapacity) or nil,
downlinkCapacity = data.kcp_downlinkcapacity ~= nil and tonumber(data.kcp_downlinkcapacity) or nil,
congestion = data.kcp_congestion == "true" and true or false,
readBufferSize = data.kcp_readbuffersize ~= nil and tonumber(data.kcp_readbuffersize) or nil,
writeBufferSize = data.kcp_writebuffersize ~= nil and tonumber(data.kcp_writebuffersize) or nil,
seed = data.kcp_seed,
header = {
type = data.kcp_type
}
}
else
return nil
end
end
local function ws_settings(data, direction)
if data.stream_network == "ws" then
local acceptproxyprotocal = nil
if direction == "inbound" then
acceptproxyprotocal = data.ws_acceptproxyprotocol == "true" and true or false
end
return {
acceptProxyProtocol = acceptproxyprotocal,
path = data.ws_path,
headers = data.ws_host ~= nil and {
Host = data.ws_host
} or nil
}
else
return nil
end
end
local function http_settings(data, direction)
if data.stream_network == "h2" then
local readidletimeout = nil
local healthchecktimeout = nil
local http_settings = {}
http_settings.host = data.http_host
http_settings.path = data.http_path
if direction == "outbound" then
http_settings.read_idle_timeout = data.http_readidletimeout ~= nil and tonumber(data.http_readidletimeout) or nil
http_settings.health_check_timeout = data.http_healthchecktimeout ~= nil and tonumber(data.http_healthchecktimeout) or nil
end
http_settings.method = data.http_method
-- headers not implemented yet
return next(http_settings) ~= nil and http_settings or nil
else
return nil
end
end
local function quic_settings(data, direction)
if data.stream_network == "quic" then
return {
security = data.quic_security,
key = data.quic_key,
header = {
type = data.quic_type
}
}
else
return nil
end
end
local function ds_settings(data, direction)
if data.stream_network == "ds" and direction == "inbound" then
return {
path = data.ds_path,
abstract = data.ds_abstract == "true" and true or false,
padding = data.ds_padding == "true" and true or false
}
else
return nil
end
end
local function grpc_settings(data, direction)
if (data.stream_network == "grpc") then
local multimode = nil
local idletimeout = nil
local healthchecktimeout = nil
local initialwindowssize = nil
local useragent = nil
if direction == "outbound" then
multimode = data.grpc_multimode == "true" and true or false
idletimeout = data.grpc_idletimeout ~= nil and tonumber(data.grpc_idletimeout) or nil
healthchecktimeout = data.grpc_healthchecktimeout ~= nil and tonumber(data.grpc_healthchecktimeout) or nil
permitwithoutstream = data.grpc_permitwithoutstream == "true" and true or false
initialwindowssize = data.grpc_initialwindownsize ~= nil and tonumber(data.grpc_initialwindownsize) or nil
useragent = data.grpc_useragent
end
return {
serviceName = data.grpc_servicename,
multiMode = multimode,
idle_timeout = idletimeout,
health_check_timeout = healthchecktimeout,
permit_without_stream = permitwithoutstream,
initial_windows_size = initialwindowssize,
user_agent = useragent
}
else
return nil
end
end
local function tls_settings(data, direction)
local result = {}
if direction == "outbound" then
local pinnedpeercertificatechainsha256 = {}
for _, x in ipairs(data.pinnedpeercertificatechainsha256 ~= nil and data.pinnedpeercertificatechainsha256 or {}) do
table.insert(pinnedpeercertificatechainsha256, x)
end
result.serverName = data.tls_servername
result.allowInsecure = data.tls_allowinsecure == "true" and true or false
result.disableSystemRoot = data.tls_disablesystemroot == "true" and true or false
result.enableSessionResumption = data.tls_enablesessionresumption == "true" and true or false
result.fingerprint = data.tls_fingerprint or ""
result.pinnedPeerCertificateChainSha256 = next(pinnedpeercertificatechainsha256) ~= nil and pinnedpeercertificatechainsha256 or nil
end
if direction == "inbound" then
local ciphersuites = {}
for i, v in ipairs(data.tls_ciphersuites ~= nil and data.tls_ciphersuites or {}) do
if i > 1 then v = ":" .. v end
ciphersuites[#ciphersuites+1] = tostring(v)
end
result.rejectUnknownSni = data.tls_rejectnuknownsni == "true" and true or false
result.minVersion = data.tls_minversion
result.maxVersion = data.tls_maxversion
result.cipherSuites = next(ciphersuites) and table.concat(ciphersuites) or nil
result.certificates = {
{
ocspStapling = data.cert_ocspStapling ~= nil and tonumber(data.cert_ocspStapling) or nil,
oneTimeLoading = data.cert_onetimeloading == "true" and true or false,
usage = data.cert_usage,
certificateFile = data.cert_certificatefile,
keyFile = data.cert_certificatekeyfile
}
}
end
if data.tls_alpn ~= nil then
local alpn = {}
for _, x in ipairs(data.tls_alpn) do
table.insert(alpn, x)
end
result.alpn = alpn
end
return result
end
local function reality_settings(data, direction)
local result = {}
result.show = data.reality_show == "true" and true or false
if direction == "outbound" then
result.serverName = data.reality_servername
result.fingerprint = data.reality_fingerprint
result.shortId = data.reality_shortid
result.publicKey = data.reality_publickey
result.spiderX = data.reality_spiderx or ""
end
if direction == "inbound" then
local servernames = {}
local shortids = {}
for _, x in ipairs(data.reality_servername ~= nil and data.reality_servername or {}) do
table.insert(servernames, x)
end
for _, x in ipairs(data.reality_shortid ~= nil and data.reality_shortid or {}) do
table.insert(shortids, x)
end
result.dest = data.reality_dest
result.xver = data.reality_xver ~= nil and tonumber(data.reality_xver) or nil
result.serverNames = next(servernames) ~= nil and servernames or nil
result.privateKey = data.reality_privatekey
result.minClientVer = data.reality_minclientver
result.maxClientVer = data.reality_maxclientver
result.maxTimeDiff = data.reality_maxtimediff ~= nil and tonumber(data.reality_maxtimediff) or nil
result.shortIds = next(shortids) ~= nil and shortids or nil
end
return result
end
local function sockopt_settings(data, direction)
local tproxy = nil
local acceptproxyprotocal = nil
local tcpcongestion = nil
local dialerproxy = nil
local mark = nil
local tcpkeepaliveinterval = nil
local interface = nil
if direction == "inbound" then
tproxy = data.sockopt_tproxy
acceptproxyprotocal = data.sockopt_acceptproxyprotocol == "true" and true or false
tcpcongestion = data.sockopt_tcpcongestion
elseif direction == "outbound" then
dialerproxy = data.sockopt_dialerproxy
mark = outbound.sockopt_mark ~= nil and tonumber(outbound.sockopt_mark) or nil
tcpkeepaliveinterval = data.sockopt_tcpkeepaliveinterval ~= nil and tonumber(data.sockopt_tcpkeepaliveinterval) or nil
interface = data.sockopt_interface
end
return {
mark = mark,
tcpFastOpen = data.sockopt_tcpfastopen == "true" and true or false,
tproxy = tproxy,
domainStrategy = data.sockopt_domainstrategy,
dialerProxy = dialerproxy,
acceptProxyProtocol = acceptproxyprotocal,
tcpKeepAliveInterval =tcpkeepaliveinterval,
tcpcongestion = tcpcongestion,
interface = interface
}
end
local function mux_settings(data, direction)
if data.mux_enabled == "true" and direction == 'outbound' then
return {
enabled = data.mux_enabled == "true" and true or false,
concurrency = data.mux_concurrency ~= nil and tonumber(data.mux_concurrency) or nil,
xudpConcurrency = data.mux_xudpconcurrency ~= nil and tonumber(data.mux_xudpconcurrency) or nil,
xudpProxyUDP443 = data.mux_xudpproxyudp443
}
else
return nil
end
end
local function stream_settings(data, direction)
return {
network = data.stream_network,
security = data.stream_security,
tlsSettings = data.stream_security == "tls" and tls_settings(data, direction) or nil,
xtlsSettings = data.stream_security == "xtls" and tls_settings(data, direction) or nil,
realitySettings = data.stream_security == "reality" and reality_settings(data, direction) or nil,
tcpSettings = tcp_settings(data, direction),
kcpSettings = kcp_settings(data, direction),
wsSettings = ws_settings(data, direction),
httpSettings = http_settings(data, direction),
quicSettings = quic_settings(data, direction),
dsSettings = ds_settings(data, direction),
grpcSettings = grpc_settings(data, direction),
sockopt = sockopt_settings(data, direction)
}
end
local function sniffing(data)
local destoverride = {}
local domainsexcluded = {}
for i, v in ipairs(data.sniffing_destoverride ~= nil and data.sniffing_destoverride or {}) do
table.insert(destoverride, v)
end
for i, v in ipairs(data.sniffing_domainsexcluded ~= nil and data.sniffing_domainsexcluded or {}) do
table.insert(domainsexcluded, v)
end
return data.sniffing_enabled == "true" and {
enabled = true,
destOverride = next(destoverride) and destoverride or nil,
metadataOnly = data.sniffing_metadataonly == "true" and true or false,
domainsExcluded = next(domainsexcluded) and domainsexcluded or nil,
routeOnly = data.sniffing_routeonly == "true" and true or false,
} or nil
end
local function fallback(data)
local fallbacks = {}
for i, v in ipairs(data.fallback ~= nil and data.fallback or {}) do
ucursor:foreach("xjay", "fallback", function(fb)
if fb.tag == v and fb.dest ~= nil then
local fallback = {
name = fb.name,
alpn = fb.alpn,
path = fb.path,
xver = fb.xver ~= nil and tonumber(fb.xver) or nil
}
-- if the dest is only a port number, then convert it to number
if string.match(fb.dest, "%d+") == fb.dest then
fallback.dest = tonumber(fb.dest)
else
fallback.dest = fb.dest
end
table.insert(fallbacks, fallback)
end
end)
end
return next(fallbacks) ~= nil and fallbacks or nil
end
local function dokodemo_inbound(data)
local network = {}
for i, v in ipairs(data.dokodemo_network ~= nil and data.dokodemo_network or {}) do
if i > 1 then v = "," .. v end
network[#network+1] = tostring(v)
end
return {
address = data.dokodemo_address or nil,
port = data.dokodemo_port ~= nil and tonumber(data.dokodemo_port) or nil,
network = next(network) and table.concat(network) or nil,
timeout = data.dokodemo_timeout ~= nil and tonumber(v.dokodemo_timeout) or nil,
followRedirect = data.dokodemo_followredirect == "true" and true or false,
userLevel = data.dokodemo_level ~= nil and tonumber(data.dokodemo_level) or nil,
fallbacks = fallback(data)
}
end
local function http_inbound(data)
local accounts = {}
for i, v in ipairs(data.http_user ~= nil and data.http_user or {}) do
ucursor:foreach("xjay", "user", function(u)
if u.name == v then
local user = {
user = u.name,
pass = u.password
}
table.insert(accounts, user)
end
end)
end
return {
timeout = data.http_timeout ~= nil and tonumber(data.http_timeout) or nil,
allowTransparent = data.http_allocatransparent == "true" and true or false,
accounts = data.http_auth == 'password' and accounts or nil,
userLevel = data.http_level ~= nil and tonumber(data.http_level) or nil,
fallbacks = fallback(data)
}
end
local function shadowsocks_inbound(data)
local network = {}
local clients = {}
for i, v in ipairs(data.ss_user ~= nil and data.ss_user or {}) do
ucursor:foreach("xjay", "user", function(u)
if u.name == v then
local user = {
password = u.password,
method = data.ss_method,
email = u.email,
level = u.level ~= nil and tonumber(u.level) or nil
}
table.insert(clients, user)
end
end)
end
for i, v in ipairs(data.ss_network == nil and {} or data.ss_network) do
if i > 1 then v = "," .. v end
network[#network+1] = tostring(v)
end
return {
clients = next(clients) and clients or nil,
network = next(network) and table.concat(network) or nil,
fallbacks = fallback(data)
}
end
local function socks_inbound(data)
local accounts = {}
for i, v in ipairs(data.socks_user ~= nil and data.socks_user or {}) do
ucursor:foreach("xjay", "user", function(u)
if u.name == v then
local user = {
user = u.name,
pass = u.password
}
table.insert(accounts, user)
end
end)
end
return {
auth = data.socks_auth,
accounts = data.socks_auth == 'password' and accounts or nil,
udp = data.socks_udp == "true" and true or false,
ip = data.socks_ip,
userLevel = data.socks_level ~= nil and tonumber(data.socks_level) or nil,
fallbacks = fallback(data)
}
end
local function trojan_inbound(data)
local clients = {}
for i, v in ipairs(data.trojan_user ~= nil and data.trojan_user or {}) do
ucursor:foreach("xjay", "user", function(u)
if u.name == v then
local user = {
password = u.password,
email = u.email,
level = u.level ~= nil and tonumber(u.level) or nil
}
table.insert(clients, user)
end
end)
end
return {
clients = next(clients) and clients or nil,
fallbacks = fallback(data)
}
end
local function vless_inbound(data)
local clients = {}
for i, v in ipairs(data.vless_user ~= nil and data.vless_user or {}) do
ucursor:foreach("xjay", "user", function(u)
if u.name == v then
local user = {
id = u.password,
flow = data.vless_flow ~= "none" and data.vless_flow or "",
email = u.email,
level = u.level ~= nil and tonumber(u.level) or nil
}
table.insert(clients, user)
end
end)
end
return {
clients = next(clients) and clients or nil,
decryption = "none",
fallbacks = fallback(data)
}
end
local function vmess_inbound(data)
local clients = {}
for i, v in ipairs(data.vmess_user ~= nil and data.vmess_user or {}) do
ucursor:foreach("xjay", "user", function(u)
if u.name == v then
local user = {
id = u.password,
alterId = data.vmess_alterid ~= nil and tonumber(data.vmess_alterid) or nil,
email = u.email,
level = u.level ~= nil and tonumber(u.level) or nil
}
table.insert(clients, user)
end
end)
end
return {
clients = next(clients) and clients or nil,
default = {
alterId = 0,
level = 0
},
detour = data.vmess_detour ~= nil and {
to = data.vmess_detour
} or nil,
fallbacks = fallback(data)
}
end
local function inbound_service(data)
local inbound_settings = {}
if data.protocol == "dokodemo-door" then
inbound_settings = dokodemo_inbound(data)
elseif data.protocol == "http" then
inbound_settings = http_inbound(data)
elseif data.protocol == "shadowsocks" then
inbound_settings = shadowsocks_inbound(data)
elseif data.protocol == "socks" then
inbound_settings = socks_inbound(data)
elseif data.protocol == "trojan" then
inbound_settings = trojan_inbound(data)
elseif data.protocol == "vless" then
inbound_settings = vless_inbound(data)
elseif data.protocol == "vmess" then
inbound_settings = vmess_inbound(data)
end
return {
tag = data.tag,
listen = data.listen,
port = tonumber(data.port),
protocol = data.protocol,
settings = inbound_settings,
streamSettings = stream_settings(data, "inbound"),
sniffing = sniffing(data)
}
end
local function blackhole_outbound()
return {
tag = "blackhole",
protocol = "blackhole"
}
end
local function dns_outbound()
return {
protocol = "dns",
tag = "dns",
streamSettings = {
sockopt = {
mark = outbound.sockopt_mark ~= nil and tonumber(outbound.sockopt_mark) or nil
}
}
}
end
local function direct_outbound()
return {
protocol = "freedom",
tag = "direct",
streamSettings = {
sockopt = {
mark = outbound.sockopt_mark ~= nil and tonumber(outbound.sockopt_mark) or nil
}
}
}
end
local function http_outbound(data)
return {
servers = {
{
address = data.address,
port = tonumber(data.port),
users = {
{
user = data.http_user,
pass = data.http_pass
}
}
}
}
}
end
local function shadowsocks_outbound(data)
return {
servers = {
{
address = data.address,
port = tonumber(data.port),
method = data.ss_method, -- encryption algorithim
password = data.ss_password,
uot = data.ss_uot == "true" and true or false, -- udp over tcp
email = data.ss_email or nil, -- optional, for identifying a user
level = data.ss_level ~= nil and tonumber(data.ss_level) or nil
}
}
}
end
local function socks_outbound(data)
return {
servers = {
{
address = data.address,
port = tonumber(data.port),
users = {
{
user = data.socks_user,
pass = data.socks_pass,
level = data.socks_level ~= nil and tonumber(data.socks_level) or nil
}
}
}
}
}
end
local function trojan_outbound(data)
return {
servers = {
{
address = data.address,
port = tonumber(data.port),
password = data.trojan_password,
email = data.trojan_email or nil, -- optional, for identifying a user
level = data.trojan_level ~= nil and tonumber(data.trojan_level) or nil
}
}
}
end
local function vless_outbound(data)
return {
vnext = {
{
address = data.address,
port = tonumber(data.port),
users = {
{
id = data.vless_id,
flow = data.vless_flow ~= "none" and data.vless_flow or "",
encryption = data.vless_encryption, -- encryption algorithim, for vless, currently only none
level = data.vless_level ~= nil and tonumber(data.vless_level) or nil
}
}
}
}
}
end
local function vmess_outbound(data)
return {
vnext = {
{
address = data.address,
port = tonumber(data.port),
users = {
{
id = data.vmess_id,
alterId = data.vmess_alterid ~= nil and tonumber(data.vmess_alterid) or nil,
security = data.vmess_security, -- encryption algorithim
level = data.vmess_level ~= nil and tonumber(data.vmess_level) or nil
}
}
}
}
}
end
local function outbound_server(data)
local outbound_settings = {}
if data.protocol == "http" then
outbound_settings = http_outbound(data)
elseif data.protocol == "shadowsocks" then
outbound_settings = shadowsocks_outbound(data)
elseif data.protocol == "socks" then
outbound_settings = socks_outbound(data)
elseif data.protocol == "trojan" then
outbound_settings = trojan_outbound(data)
elseif data.protocol == "vless" then
outbound_settings = vless_outbound(data)
elseif data.protocol == "vmess" then
outbound_settings = vmess_outbound(data)
end
return {
tag = data.tag,
sendThrough = data.sendthrough,
protocol = data.protocol,
settings = outbound_settings,
streamSettings = stream_settings(data, "outbound"),
mux = mux_settings(data, 'outbound')
}
end
local function logging()
return {
access = misc.log_access == "true" and "" or "none",
error = misc.log_error == "true" and "" or "none",
loglevel = misc.log_level or "warning",
dnsLog = misc.log_dnslog == "true" and true or false
}
end
local function inbounds()
local inbounds = {}
ucursor:foreach("xjay", "inbound_service", function(data)
table.insert(inbounds, inbound_service(data))
end)
return inbounds
end
local function outbounds()
local outbounds = {}
local function is_outbound_created(tag)
for i, v in ipairs(outbounds) do
if tag == v.tag then
return true
end
end
return false
end
-- generating outbounds based on ruouting outbound tags
ucursor:foreach("xjay", "outbound_server", function(data)
-- insert default oubound in the first
if data.tag == outbound.default_outbound then table.insert(outbounds, 1, outbound_server(data)) end
-- append other outbounds from routing rules in the last
ucursor:foreach("xjay", "routing_rule", function(dd)
if data.tag == dd.rule_outboundtag and not is_outbound_created(dd.rule_outboundtag) then
table.insert(outbounds, outbound_server(data))
end
end)
end)
-- generating some pre-defined outbounds
if outbound.default_outbound == "direct" then
table.insert(outbounds, 1, direct_outbound())
else
table.insert(outbounds, direct_outbound())
end
if outbound.default_outbound == "blackhole" then
table.insert(outbounds, 1, blackhole_outbound())
else
table.insert(outbounds, blackhole_outbound())
end
table.insert(outbounds, dns_outbound())
return outbounds
end
local function dnss()
local hosts = {}
local servers = {}
for i, v in ipairs(dns.host == nil and {} or dns.host) do
-- break string "/dns.google/8.8.8.8" into table tab = { "dns.google", "8.8.8.8" }
local tab = {}
for w in v:gmatch("([^/]+)") do table.insert(tab, w) end
-- then add them as key value pair into hosts table
-- will be hosts = { dns.google = "8.8.8.8" }
-- not elegant but working
hosts[ tab[1] ] = tab[2]
end
ucursor:foreach("xjay", "dns_server", function(data)
local domains = {}
local expectedips = {}
local function is_domain_inserted(domain)
for i, v in ipairs(domains) do
if domain == v then
return true
end
end
return false
end
for i, v in ipairs(data.server_domain == nil and {} or data.server_domain) do
table.insert(domains, v)
end
-- get domains from routing rules domain list
ucursor:foreach("xjay", "routings", function(dd)
if data.alias == dd.dns_server then
for i, v in ipairs(dd.rule_domain == nil and {} or dd.rule_domain) do
if not is_domain_inserted(v) then table.insert(domains, v) end
end
end
end)
for i, v in ipairs(data.server_expectedip == nil and {} or data.server_expectedip) do
table.insert(expectedips, v)
end
local server = {
queryStrategy = data.querystrategy,
address = data.server_address ~= nil and data.server_address or nil,
port = data.server_port ~= nil and tonumber(data.server_port) or nil,
skipFallback = data.server_skipfallback == "true" and true or false,
domains = next(domains) and domains or nil,
expectIPs = next(expectedips) and expectedips or nil,
clientIP = data.server_clientip
}
table.insert(servers, server)
end)
for i, v in ipairs(dns.alt_dns == nil and {} or dns.alt_dns) do
table.insert(servers, v)
end
return {
tag = dns.tag,
clientIP = dns.clientip,
queryStrategy = dns.querystrategy,
disableCache = dns.disablecache == "true" and true or false,
disableFallback = dns.disablefallback == "true" and true or false,
disableFallbackIfMatch = dns.disablefallbackifmatch == "true" and true or false,
hosts = next(hosts) and hosts or nil,
servers = next(servers) and servers or nil
}
end
local function routings()
local rules = {}
ucursor:foreach("xjay", "routing_rule", function(data)
local domain = {}
local ip = {}
local port = {}
local source = {}
local sourceport = {}
local network = {}
local protocol = {}
local inboundtag = {}
for i, v in ipairs(data.rule_domain == nil and {} or data.rule_domain) do
table.insert(domain, v)
end
for i, v in ipairs(data.rule_ip == nil and {} or data.rule_ip) do
table.insert(ip, v)
end
if data.rule_port == nil then
port = nil
elseif #data.rule_port == 1 then
port = tonumber(data.rule_port[1])
elseif #data.rule_port > 1 then
for i, v in ipairs(data.rule_port) do
port[#port+1] = tostring(i == 1 and v or "," .. v)
end
port = table.concat(port)
end
for i, v in ipairs(data.rule_source == nil and {} or data.rule_source) do
table.insert(source, v)
end
if data.rule_sourceport == nil then
sourceport = nil
elseif #data.rule_sourceport == 1 then
sourceport = tonumber(data.rule_sourceport[1])
elseif #data.rule_sourceport > 1 then
for i, v in ipairs(data.rule_sourceport) do
sourceport[#sourceport+1] = tostring(i == 1 and v or "," .. v)
end
sourceport = table.concat(sourceport)
end
for i, v in ipairs(data.rule_network == nil and {} or data.rule_network) do
if i > 1 then v = "," .. v end
network[#network+1] = tostring(v)
end
for i, v in ipairs(data.rule_protocol == nil and {} or data.rule_protocol) do
table.insert(protocol, v)
end
for i, v in ipairs(data.rule_inboundtag == nil and {} or data.rule_inboundtag) do
table.insert(inboundtag, v)
end
local rule = {
domainMatcher = data.domain_matcher == nil and nil or data.domain_matcher,
type = "field",
domain = next(domain) and domain or nil,
ip = next(ip) and ip or nil,
port = port,
source = next(source) and source or nil,
sourcePort = sourceport,
network = next(network) and table.concat(network) or nil,
protocol = next(protocol) and protocol or nil,
attrs = data.rule_attrs,
inboundTag = next(inboundtag) and inboundtag or nil,
outboundTag = data.rule_outboundtag
}
table.insert(rules, rule)
end)
return {
domainStrategy = routing.domainstrategy,
domainMatcher = routing.domainmatcher,
rules = rules
}
end
local xray = {
log = logging(),
inbounds = inbounds(),
outbounds = outbounds(),
dns = dnss(),
routing = routings()
}
print(json.stringify(xray, true))