small-packages/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe.lua
2026-07-16 21:39:02 +08:00

237 lines
7.1 KiB
Lua

local api = require "luci.passwall.api"
local uci = api.uci
local appname = "passwall"
local has_ss_rust = api.is_finded("sslocal")
local has_singbox = api.finded_com("sing-box")
local has_xray = api.finded_com("xray")
local has_hysteria2 = api.finded_com("hysteria")
local ss_type = {}
local trojan_type = {}
local vmess_type = {}
local vless_type = {}
local hysteria2_type = {}
local xray_version = api.get_app_version("xray")
if has_ss_rust then
local s = "shadowsocks-rust"
table.insert(ss_type, s)
end
if has_singbox then
local s = "sing-box"
table.insert(trojan_type, s)
table.insert(ss_type, s)
table.insert(vmess_type, s)
table.insert(vless_type, s)
table.insert(hysteria2_type, s)
end
if has_xray then
local s = "xray"
table.insert(trojan_type, s)
table.insert(ss_type, s)
table.insert(vmess_type, s)
table.insert(vless_type, s)
if api.compare_versions(xray_version, ">=", "26.1.13") then
table.insert(hysteria2_type, s)
end
end
if has_hysteria2 then
local s = "hysteria2"
table.insert(hysteria2_type, s)
end
api.set_default_cbi()
m = Map(appname)
api.set_apply_on_parse(m)
function m.on_before_save(self)
if self.no_commit then
return
end
self.uci:foreach(appname, "subscribe_list", function(e)
self:del(e[".name"], "md5")
end)
end
-- [[ Subscribe Settings ]]--
s = m:section(TypedSection, "global_subscribe", "")
s.anonymous = true
o = s:option(ListValue, "filter_keyword_mode", translate("Filter keyword Mode"))
o:value("0", translate("Close"))
o:value("1", translate("Discard List"))
o:value("2", translate("Keep List"))
o:value("3", translate("Discard List,But Keep List First"))
o:value("4", translate("Keep List,But Discard List First"))
o = s:option(DynamicList, "filter_discard_list", translate("Discard List"))
o = s:option(DynamicList, "filter_keep_list", translate("Keep List"))
if #ss_type > 0 then
o = s:option(ListValue, "ss_type", translatef("%s Node Use Type", "Shadowsocks"))
o:value("", translate("Auto"))
for key, value in pairs(ss_type) do
o:value(value)
end
end
if #trojan_type > 0 then
o = s:option(ListValue, "trojan_type", translatef("%s Node Use Type", "Trojan"))
o:value("", translate("Auto"))
for key, value in pairs(trojan_type) do
o:value(value)
end
end
if #vmess_type > 0 then
o = s:option(ListValue, "vmess_type", translatef("%s Node Use Type", "VMess"))
o:value("", translate("Auto"))
for key, value in pairs(vmess_type) do
o:value(value)
end
end
if #vless_type > 0 then
o = s:option(ListValue, "vless_type", translatef("%s Node Use Type", "VLESS"))
o:value("", translate("Auto"))
for key, value in pairs(vless_type) do
o:value(value)
end
end
if #hysteria2_type > 0 then
o = s:option(ListValue, "hysteria2_type", translatef("%s Node Use Type", "Hysteria2"))
o:value("", translate("Auto"))
for key, value in pairs(hysteria2_type) do
o:value(value)
end
end
if #ss_type > 0 or #trojan_type > 0 or #vmess_type > 0 or #vless_type > 0 or #hysteria2_type > 0 then
o.description = string.format("<font color='red'>%s</font>",
translate("The configured type also applies to the core specified when manually importing nodes."))
end
---- Subscribe Delete All
o = s:option(DummyValue, "_stop", translate("Delete All Subscribe Node"))
o.rawhtml = true
function o.cfgvalue(self, section)
return string.format(
[[<input type="button" class="btn cbi-button cbi-button-remove" onclick="return confirmDeleteAll()" value="%s" />]],
translate("Delete All Subscribe Node"))
end
o = s:option(DummyValue, "_update", translate("Manual subscription All"))
o.rawhtml = true
o.cfgvalue = function(self, section)
return string.format([[
<input type="button" class="btn cbi-button cbi-button-apply" onclick="ManualSubscribeAll()" value="%s" />]],
translate("Manual subscription All"))
end
local cfgname = "subscribe_list"
s = m:section(TypedSection, cfgname, "", "<font color='red'>" .. translate("When adding a new subscription, please save and apply before manually subscribing. If you only change the subscription URL, you can subscribe manually, and the system will save it automatically.") .. "</font>")
s.addremove = true
s.anonymous = true
s.sortable = true
s.template = "cbi/tblsection"
s.extedit = api.url("node_subscribe_config", "%s")
function s.create(e, t)
m.no_commit = true
local id = TypedSection.create(e, t)
luci.http.redirect(e.extedit:format(id))
end
o = s:option(Value, "remark", translate("Remarks"))
o.width = "auto"
o.rmempty = false
o.validate = function(self, value, section)
value = api.trim(value)
if value == "" then
return nil, translate("Remark cannot be empty.")
end
local duplicate = false
m.uci:foreach(appname, cfgname, function(e)
if e[".name"] ~= section and e["remark"] and e["remark"]:lower() == value:lower() then
duplicate = true
return false
end
end)
if duplicate or value:lower() == "default" then
return nil, translate("This remark already exists, please change a new remark.")
end
return value
end
o.write = function(self, section, value)
local old = m:get(section, self.option) or ""
if old ~= value then
m.uci:foreach(appname, "nodes", function(e)
if e["group"] and e["group"]:lower() == old:lower() then
m.uci:set(appname, e[".name"], "group", value)
end
if e["protocol"] and (e["protocol"] == "_balancing" or e["protocol"] == "_urltest") and e["node_group"] then
local gs = ""
for g in e["node_group"]:gmatch("%S+") do
if api.UrlEncode(old) == g then
gs = gs .. " " .. api.UrlEncode(value)
else
gs = gs .. " " .. g
end
end
gs = api.trim(gs)
m.uci:set(appname, e[".name"], "node_group", gs)
end
end)
end
return Value.write(self, section, value)
end
o = s:option(DummyValue, "_node_count", translate("Subscribe Info"))
o.rawhtml = true
o.cfgvalue = function(t, n)
local remark = m:get(n, "remark") or ""
local str = m:get(n, "rem_traffic") or ""
local expired_date = m:get(n, "expired_date") or ""
if expired_date ~= "" then
str = str .. (str ~= "" and "/" or "") .. expired_date
end
str = str ~= "" and "<br>" .. str or ""
local num = 0
m.uci:foreach(appname, "nodes", function(s)
if s["group"] and s["group"]:lower() == remark:lower() then
num = num + 1
end
end)
return string.format("%s%s", translate("Node num") .. ": " .. num, str)
end
o = s:option(Value, "url", translate("Subscribe URL"))
o.width = "auto"
o.rmempty = false
o = s:option(DummyValue, "_remove", translate("Delete the subscribed node"))
o.rawhtml = true
function o.cfgvalue(self, section)
local remark = m:get(section, "remark") or ""
return string.format(
[[<input type="button" class="btn cbi-button cbi-button-remove" onclick="return confirmDeleteNode('%s')" value="%s" />]],
remark, translate("Delete the subscribed node"))
end
o = s:option(DummyValue, "_update", translate("Manual subscription"))
o.rawhtml = true
o.cfgvalue = function(self, section)
return string.format([[
<input type="button" class="btn cbi-button cbi-button-apply" onclick="ManualSubscribe('%s')" value="%s" />]],
section, translate("Manual subscription"))
end
local sortable = Template(appname .. "/cbi/sortable")
sortable.api = api
sortable.target_cfgname = cfgname
m:append(sortable)
m:append(Template(appname .. "/node_subscribe/js"))
return api.return_map(m)