2023-09-15 09:42:16 +08:00
local api = require " luci.passwall.api "
2024-03-10 04:14:35 +08:00
local appname = " passwall "
2023-09-15 09:42:16 +08:00
local datatypes = api.datatypes
2024-03-07 04:16:26 +08:00
m = Map ( appname , " Sing-Box/Xray " .. translate ( " Shunt Rule " ) )
2023-09-15 09:42:16 +08:00
m.redirect = api.url ( )
2025-01-15 00:23:56 +08:00
api.set_apply_on_parse ( m )
2023-09-15 09:42:16 +08:00
s = m : section ( NamedSection , arg [ 1 ] , " shunt_rules " , " " )
s.addremove = false
s.dynamic = false
remarks = s : option ( Value , " remarks " , translate ( " Remarks " ) )
remarks.default = arg [ 1 ]
remarks.rmempty = false
protocol = s : option ( MultiValue , " protocol " , translate ( " Protocol " ) )
protocol : value ( " http " )
protocol : value ( " tls " )
protocol : value ( " bittorrent " )
2024-01-09 06:44:45 +08:00
o = s : option ( MultiValue , " inbound " , translate ( " Inbound Tag " ) )
o : value ( " tproxy " , translate ( " Transparent proxy " ) )
o : value ( " socks " , " Socks " )
network = s : option ( ListValue , " network " , translate ( " Network " ) )
network : value ( " tcp,udp " , " TCP UDP " )
network : value ( " tcp " , " TCP " )
network : value ( " udp " , " UDP " )
source = s : option ( DynamicList , " source " , translate ( " Source " ) )
source.description = " <ul><li> " .. translate ( " Example: " )
.. " </li><li> " .. translate ( " IP " ) .. " : 192.168.1.100 "
.. " </li><li> " .. translate ( " IP CIDR " ) .. " : 192.168.1.0/24 "
.. " </li><li> " .. translate ( " GeoIP " ) .. " : geoip:private "
.. " </li></ul> "
source.cast = " string "
source.cfgvalue = function ( self , section )
local value
if self.tag_error [ section ] then
value = self : formvalue ( section )
else
value = self.map : get ( section , self.option )
if type ( value ) == " string " then
local value2 = { }
string.gsub ( value , ' [^ ' .. " " .. ' ]+ ' , function ( w ) table.insert ( value2 , w ) end )
value = value2
end
end
return value
end
source.validate = function ( self , value , t )
local err = { }
for _ , v in ipairs ( value ) do
local flag = false
if datatypes.ip4addr ( v ) then
flag = true
end
if flag == false and v : find ( " geoip: " ) and v : find ( " geoip: " ) == 1 then
flag = true
end
if flag == false then
err [ # err + 1 ] = v
end
end
if # err > 0 then
self : add_error ( t , " invalid " , translate ( " Not true format, please re-enter! " ) )
for _ , v in ipairs ( err ) do
self : add_error ( t , " invalid " , v )
end
end
return value
end
local dynamicList_write = function ( self , section , value )
local t = { }
local t2 = { }
if type ( value ) == " table " then
local x
for _ , x in ipairs ( value ) do
if x and # x > 0 then
if not t2 [ x ] then
t2 [ x ] = x
t [ # t + 1 ] = x
end
end
end
else
t = { value }
end
t = table.concat ( t , " " )
return DynamicList.write ( self , section , t )
end
source.write = dynamicList_write
2025-02-11 10:44:37 +08:00
sourcePort = s : option ( Value , " sourcePort " , translate ( " Source port " ) )
2024-01-09 06:44:45 +08:00
port = s : option ( Value , " port " , translate ( " port " ) )
2023-09-15 09:42:16 +08:00
domain_list = s : option ( TextValue , " domain_list " , translate ( " Domain " ) )
domain_list.rows = 10
domain_list.wrap = " off "
domain_list.validate = function ( self , value )
local hosts = { }
2024-05-31 16:22:34 +08:00
value = value : gsub ( " ^%s+ " , " " ) : gsub ( " %s+$ " , " \n " ) : gsub ( " \r \n " , " \n " ) : gsub ( " [ \t ]* \n [ \t ]* " , " \n " )
string.gsub ( value , " [^ \r \n ]+ " , function ( w ) table.insert ( hosts , w ) end )
2023-09-15 09:42:16 +08:00
for index , host in ipairs ( hosts ) do
local flag = 1
local tmp_host = host
2024-05-31 16:22:34 +08:00
if not host : find ( " # " ) and host : find ( " %s " ) then
elseif host : find ( " regexp: " ) and host : find ( " regexp: " ) == 1 then
2023-09-15 09:42:16 +08:00
flag = 0
elseif host : find ( " domain:. " ) and host : find ( " domain:. " ) == 1 then
tmp_host = host : gsub ( " domain: " , " " )
elseif host : find ( " full:. " ) and host : find ( " full:. " ) == 1 then
tmp_host = host : gsub ( " full: " , " " )
elseif host : find ( " geosite: " ) and host : find ( " geosite: " ) == 1 then
flag = 0
elseif host : find ( " ext: " ) and host : find ( " ext: " ) == 1 then
flag = 0
2024-04-19 04:17:04 +08:00
elseif host : find ( " # " ) and host : find ( " # " ) == 1 then
flag = 0
2023-09-15 09:42:16 +08:00
end
if flag == 1 then
if not datatypes.hostname ( tmp_host ) then
return nil , tmp_host .. " " .. translate ( " Not valid domain name, please re-enter! " )
end
end
end
return value
end
domain_list.description = " <br /><ul><li> " .. translate ( " Plaintext: If this string matches any part of the targeting domain, this rule takes effet. Example: rule 'sina.com' matches targeting domain 'sina.com', 'sina.com.cn' and 'www.sina.com', but not 'sina.cn'. " )
.. " </li><li> " .. translate ( " Regular expression: Begining with 'regexp:', the rest is a regular expression. When the regexp matches targeting domain, this rule takes effect. Example: rule 'regexp: \\ .goo.* \\ .com$' matches 'www.google.com' and 'fonts.googleapis.com', but not 'google.com'. " )
.. " </li><li> " .. translate ( " Subdomain (recommended): Begining with 'domain:' and the rest is a domain. When the targeting domain is exactly the value, or is a subdomain of the value, this rule takes effect. Example: rule 'domain:v2ray.com' matches 'www.v2ray.com', 'v2ray.com', but not 'xv2ray.com'. " )
.. " </li><li> " .. translate ( " Full domain: Begining with 'full:' and the rest is a domain. When the targeting domain is exactly the value, the rule takes effect. Example: rule 'domain:v2ray.com' matches 'v2ray.com', but not 'www.v2ray.com'. " )
.. " </li><li> " .. translate ( " Pre-defined domain list: Begining with 'geosite:' and the rest is a name, such as geosite:google or geosite:cn. " )
2024-04-19 04:17:04 +08:00
.. " </li><li> " .. translate ( " Annotation: Begining with # " )
2023-09-15 09:42:16 +08:00
.. " </li></ul> "
ip_list = s : option ( TextValue , " ip_list " , " IP " )
ip_list.rows = 10
ip_list.wrap = " off "
ip_list.validate = function ( self , value )
local ipmasks = { }
2024-05-31 16:22:34 +08:00
value = value : gsub ( " ^%s+ " , " " ) : gsub ( " %s+$ " , " \n " ) : gsub ( " \r \n " , " \n " ) : gsub ( " [ \t ]* \n [ \t ]* " , " \n " )
string.gsub ( value , " [^ \r \n ]+ " , function ( w ) table.insert ( ipmasks , w ) end )
2023-09-15 09:42:16 +08:00
for index , ipmask in ipairs ( ipmasks ) do
2024-05-31 16:22:34 +08:00
if ipmask : find ( " geoip: " ) and ipmask : find ( " geoip: " ) == 1 and not ipmask : find ( " %s " ) then
elseif ipmask : find ( " ext: " ) and ipmask : find ( " ext: " ) == 1 and not ipmask : find ( " %s " ) then
2024-04-19 04:17:04 +08:00
elseif ipmask : find ( " # " ) and ipmask : find ( " # " ) == 1 then
2023-09-15 09:42:16 +08:00
else
if not ( datatypes.ipmask4 ( ipmask ) or datatypes.ipmask6 ( ipmask ) ) then
return nil , ipmask .. " " .. translate ( " Not valid IP format, please re-enter! " )
end
end
end
return value
end
ip_list.description = " <br /><ul><li> " .. translate ( " IP: such as '127.0.0.1'. " )
.. " </li><li> " .. translate ( " CIDR: such as '127.0.0.0/8'. " )
.. " </li><li> " .. translate ( " GeoIP: such as 'geoip:cn'. It begins with geoip: (lower case) and followed by two letter of country code. " )
2024-04-19 04:17:04 +08:00
.. " </li><li> " .. translate ( " Annotation: Begining with # " )
2023-09-15 09:42:16 +08:00
.. " </li></ul> "
return m