From 37e1475e87f2a788bf48d43017ecc43830417521 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 24 Oct 2026 23:19:23 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A5=20Sync=202026-10-24=2023:19:23?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- clashoo/Makefile | 8 +- glorytun-udp/Makefile | 2 +- glorytun-udp/init | 19 +- gowebdav/Makefile | 4 +- headscale/Makefile | 4 +- .../usr/share/rpcd/acl.d/luci-app-dsvpn.json | 3 + .../rpcd/acl.d/luci-app-glorytun-tcp.json | 3 + .../resources/view/services/glorytun-udp.js | 7 - .../rpcd/acl.d/luci-app-glorytun-udp.json | 3 + luci-app-mail/root/etc/init.d/msmtp | 27 +- .../usr/share/rpcd/acl.d/luci-app-mlvpn.json | 3 + .../luci-static/resources/view/mptcp/mptcp.js | 18 + .../view/mptcp/mptcp_dscp_routing.js | 13 + .../root/usr/libexec/rpcd/luci.mptcp | 55 +- luci-app-open-box/Makefile | 4 +- luci-app-passwall/Makefile | 2 +- .../resources/view/passwall/node_list_sort.js | 773 +++++++++++++++++ .../luasrc/model/cbi/passwall/client/rule.lua | 2 +- .../luasrc/passwall/util_sing-box.lua | 17 +- .../luasrc/passwall/util_xray.lua | 34 +- .../view/passwall/node_list/node_list.htm | 136 ++- luci-app-passwall/po/zh-cn/passwall.po | 58 +- .../root/usr/share/passwall/app.sh | 19 +- .../root/usr/share/passwall/iptables.sh | 1 + .../root/usr/share/passwall/nftables.sh | 1 + luci-app-passwall2/Makefile | 2 +- .../view/passwall2/node_list_sort.js | 775 +++++++++++++++++ .../luasrc/passwall2/util_xray.lua | 10 +- .../view/passwall2/node_list/node_list.htm | 138 ++- luci-app-passwall2/po/fa/passwall2.po | 51 ++ luci-app-passwall2/po/ru/passwall2.po | 51 ++ luci-app-passwall2/po/zh-cn/passwall2.po | 50 ++ luci-app-passwall2/po/zh-tw/passwall2.po | 51 ++ luci-app-ssr-plus/Makefile | 2 +- .../model/cbi/shadowsocksr/client-config.lua | 4 + luci-app-ssr-plus/po/zh_Hans/ssr-plus.po | 6 + .../root/etc/init.d/shadowsocksr | 8 +- .../usr/share/shadowsocksr/gen_config.lua | 47 +- luci-app-substore/CHANGELOG.md | 38 + luci-app-substore/Makefile | 2 +- luci-app-substore/README.en.md | 110 +-- luci-app-substore/README.md | 101 ++- luci-app-substore/docs/INSTALL.md | 8 +- .../luci-static/resources/view/tailscale.js | 216 ++++- .../po/es/community.po | 816 +++++++++++------- .../po/pl/community.po | 657 ++++++++------ .../po/templates/community.pot | 493 +++++++---- .../po/zh_Hans/community.po | 577 ++++++++----- .../po/zh_Hant/community.po | 555 +++++++----- .../root/etc/init.d/tailscale-settings | 244 +++++- .../root/usr/libexec/tailscale-setup-firewall | 87 ++ .../acl.d/luci-app-tailscale-community.json | 6 +- .../root/usr/share/rpcd/ucode/tailscale.uc | 120 ++- mptcp/Makefile | 2 +- mptcp/files/etc/init.d/mptcp | 93 +- mptcp/files/etc/uci-defaults/mptcp-defaults | 73 +- openlist2/Makefile | 4 +- sing-box/Makefile | 4 +- wwand/Makefile | 33 +- 59 files changed, 5167 insertions(+), 1483 deletions(-) create mode 100644 luci-app-passwall/htdocs/luci-static/resources/view/passwall/node_list_sort.js create mode 100644 luci-app-passwall2/htdocs/luci-static/resources/view/passwall2/node_list_sort.js create mode 100755 luci-app-tailscale-community/root/usr/libexec/tailscale-setup-firewall diff --git a/clashoo/Makefile b/clashoo/Makefile index 6395889da..f350f5acb 100644 --- a/clashoo/Makefile +++ b/clashoo/Makefile @@ -1,17 +1,17 @@ include $(TOPDIR)/rules.mk PKG_NAME:=clashoo -PKG_SHORT_SHA:=3f27ef3 -PKG_COMMIT_DATE:=2026.10.21 +PKG_SHORT_SHA:=91e85ee +PKG_COMMIT_DATE:=2026.10.24 PKG_VERSION:=$(PKG_COMMIT_DATE)~$(PKG_SHORT_SHA) -PKG_RELEASE:=16 +PKG_RELEASE:=17 PKG_SOURCE:=$(PKG_NAME)-alpha-$(PKG_SHORT_SHA).tar.gz PKG_SOURCE_SUBDIR:=$(PKG_NAME)-alpha-$(PKG_SHORT_SHA) PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-alpha-$(PKG_SHORT_SHA) PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-clashoo/releases/download/mihomo-src -PKG_SOURCE_VERSION:=c8979247b7baf46202891be29e174e3f54173cee +PKG_SOURCE_VERSION:=413a02e9b55b94dc3892616f43822e191516e493 PKG_HASH:=skip PKG_BUILD_VERSION:=alpha-$(PKG_SHORT_SHA) diff --git a/glorytun-udp/Makefile b/glorytun-udp/Makefile index ba3b98055..321f16ebc 100644 --- a/glorytun-udp/Makefile +++ b/glorytun-udp/Makefile @@ -13,7 +13,7 @@ PKG_SOURCE_URL:=https://github.com/ysurac/glorytun.git PKG_SOURCE_VERSION:=203c5f2223d7d5146d659b51159b2f02c2480d15 PKG_NAME:=glorytun-udp PKG_VERSION:=0.3.4 -PKG_RELEASE:=5 +PKG_RELEASE:=6 PKG_LICENSE:=BSD-2-Clause PKG_LICENSE_FILES:=LICENSE diff --git a/glorytun-udp/init b/glorytun-udp/init index c6567cffa..cb43b0496 100755 --- a/glorytun-udp/init +++ b/glorytun-udp/init @@ -31,12 +31,14 @@ validate_section() { 'kxtimeout:string:7d' \ 'timetolerance:string:10m' \ 'keepalive:string:25s' \ + 'localip:string' \ + 'remoteip:string' \ 'dev:string' } start_instance() { local enable key host port listener proto mtuauto chacha dev mode - local persist kxtimeout timetolerance keepalive + local persist kxtimeout timetolerance keepalive localip remoteip local key_dir="/var/run/${PROG_NAME}" local key_file="${key_dir}/${1}.key" @@ -106,10 +108,11 @@ start_instance() { procd_close_instance - # kxtimeout, timetolerance and keepalive are not bind arguments: only - # the ifup hotplug set them, and a restart that keeps the device raises - # no ifup, nor does a change of them alone (procd keeps the instance). - # Set them once the tunnel answers; the delay leaves procd the time to + # kxtimeout, timetolerance, keepalive and the tunnel addresses are not + # bind arguments: only the ifup hotplug set them, and a restart that + # keeps the device raises no ifup, nor does a change of them alone + # (procd keeps the instance). Set them once the tunnel answers (ifconfig + # leaves unchanged addresses alone); the delay leaves procd the time to # replace an instance it restarts. fd 1000 is procd.sh's service lock: # kept open, it would block every other glorytun-udp init call meanwhile ( @@ -120,6 +123,8 @@ start_instance() { [ "$count" -gt 10 ] && exit 0 sleep 1 done + [ -n "$localip" ] && [ -n "$remoteip" ] && + ifconfig "$dev" "$localip" pointopoint "$remoteip" up ${PROG} set dev "$dev" kxtimeout "$kxtimeout" timetolerance "$timetolerance" keepalive "$keepalive" ) /dev/null 2>&1 1000>&- & @@ -153,8 +158,8 @@ start_service() { # No reload_service: stop;start there restarted the tunnel on every network # config change. The default (start) lets procd restart only an instance whose -# command or key file changed; kxtimeout, timetolerance and keepalive are -# applied by start_instance either way. +# command or key file changed; kxtimeout, timetolerance, keepalive and the +# tunnel addresses are applied by start_instance either way. service_triggers() { procd_add_reload_trigger glorytun-udp network } diff --git a/gowebdav/Makefile b/gowebdav/Makefile index 34ef87a02..67866eda0 100644 --- a/gowebdav/Makefile +++ b/gowebdav/Makefile @@ -6,8 +6,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=gowebdav -PKG_VERSION:=5.17.1 -PKG_RELEASE:=26 +PKG_VERSION:=5.17.2 +PKG_RELEASE:=27 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/hacdias/webdav/tar.gz/v$(PKG_VERSION)? diff --git a/headscale/Makefile b/headscale/Makefile index 8e9b7ad3d..3bc8bf107 100644 --- a/headscale/Makefile +++ b/headscale/Makefile @@ -6,8 +6,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=headscale -PKG_VERSION:=0.29.5 -PKG_RELEASE:=10 +PKG_VERSION:=0.29.6 +PKG_RELEASE:=11 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/juanfont/headscale/tar.gz/v$(PKG_VERSION)? diff --git a/luci-app-dsvpn/root/usr/share/rpcd/acl.d/luci-app-dsvpn.json b/luci-app-dsvpn/root/usr/share/rpcd/acl.d/luci-app-dsvpn.json index e11c9127c..ee6ecc528 100644 --- a/luci-app-dsvpn/root/usr/share/rpcd/acl.d/luci-app-dsvpn.json +++ b/luci-app-dsvpn/root/usr/share/rpcd/acl.d/luci-app-dsvpn.json @@ -2,6 +2,9 @@ "luci-app-dsvpn": { "description": "Grant access to DSVPN", "read": { + "ubus": { + "luci-rpc": [ "getHostHints" ] + }, "uci": [ "dsvpn" ] }, "write": { diff --git a/luci-app-glorytun-tcp/root/usr/share/rpcd/acl.d/luci-app-glorytun-tcp.json b/luci-app-glorytun-tcp/root/usr/share/rpcd/acl.d/luci-app-glorytun-tcp.json index f4f288835..94cb379bd 100644 --- a/luci-app-glorytun-tcp/root/usr/share/rpcd/acl.d/luci-app-glorytun-tcp.json +++ b/luci-app-glorytun-tcp/root/usr/share/rpcd/acl.d/luci-app-glorytun-tcp.json @@ -2,6 +2,9 @@ "luci-app-glorytun-tcp": { "description": "Grant access to glorytun TCP", "read": { + "ubus": { + "luci-rpc": [ "getHostHints" ] + }, "uci": [ "glorytun" ] }, "write": { diff --git a/luci-app-glorytun-udp/htdocs/luci-static/resources/view/services/glorytun-udp.js b/luci-app-glorytun-udp/htdocs/luci-static/resources/view/services/glorytun-udp.js index b6b1a32e2..c0f3843c9 100644 --- a/luci-app-glorytun-udp/htdocs/luci-static/resources/view/services/glorytun-udp.js +++ b/luci-app-glorytun-udp/htdocs/luci-static/resources/view/services/glorytun-udp.js @@ -8,13 +8,6 @@ var callHostHints; -var callServiceList = rpc.declare({ - object: 'service', - method: 'list', - params: [ 'name' ], - expect: { '': {} } -}); - return L.view.extend({ callHostHints: rpc.declare({ object: 'luci-rpc', diff --git a/luci-app-glorytun-udp/root/usr/share/rpcd/acl.d/luci-app-glorytun-udp.json b/luci-app-glorytun-udp/root/usr/share/rpcd/acl.d/luci-app-glorytun-udp.json index 3e24adb8e..9018caa09 100644 --- a/luci-app-glorytun-udp/root/usr/share/rpcd/acl.d/luci-app-glorytun-udp.json +++ b/luci-app-glorytun-udp/root/usr/share/rpcd/acl.d/luci-app-glorytun-udp.json @@ -2,6 +2,9 @@ "luci-app-glorytun-udp": { "description": "Grant access to glorytun UDP", "read": { + "ubus": { + "luci-rpc": [ "getHostHints" ] + }, "uci": [ "glorytun-udp" ] }, "write": { diff --git a/luci-app-mail/root/etc/init.d/msmtp b/luci-app-mail/root/etc/init.d/msmtp index b112c3bce..ad0b5d76c 100755 --- a/luci-app-mail/root/etc/init.d/msmtp +++ b/luci-app-mail/root/etc/init.d/msmtp @@ -63,17 +63,28 @@ config_account() { } start_service() { - local msmtp_tmp msmtp_written=0 - # The file holds the SMTP password: created 0600 next to the target so - # the mv is a rename, never readable by others even for a moment - msmtp_tmp="$(umask 077; mktemp /etc/msmtprc.XXXXXX)" || return 1 + local msmtp_tmp msmtp_new msmtp_written=0 + # The file holds the SMTP password: always created 0600. Built in tmpfs + # and only written to flash when it changed, every start and reload + # rewrote /etc/msmtprc + msmtp_tmp="$(umask 077; mktemp /tmp/msmtprc.XXXXXX)" || return 1 config_load mail config_foreach config_account smtp - if [ "$msmtp_written" = "1" ]; then - mv "$msmtp_tmp" /etc/msmtprc - else - rm -f "$msmtp_tmp" + if [ "$msmtp_written" = "1" ] && ! cmp -s "$msmtp_tmp" /etc/msmtprc; then + # next to the target so the mv is a rename, never readable by + # others even for a moment; keeps the owner and mode of the old + # file, but never its bits for others + msmtp_new="$(umask 077; mktemp /etc/msmtprc.XXXXXX)" && + cat "$msmtp_tmp" > "$msmtp_new" && { + [ -f /etc/msmtprc ] && { + chown "$(stat -c %u:%g /etc/msmtprc)" "$msmtp_new" + chmod "$(stat -c %a /etc/msmtprc)" "$msmtp_new" + chmod o-rwx "$msmtp_new" + } + mv "$msmtp_new" /etc/msmtprc + } || rm -f "$msmtp_new" fi + rm -f "$msmtp_tmp" } service_triggers() { diff --git a/luci-app-mlvpn/root/usr/share/rpcd/acl.d/luci-app-mlvpn.json b/luci-app-mlvpn/root/usr/share/rpcd/acl.d/luci-app-mlvpn.json index e1727a866..0cb2291bc 100644 --- a/luci-app-mlvpn/root/usr/share/rpcd/acl.d/luci-app-mlvpn.json +++ b/luci-app-mlvpn/root/usr/share/rpcd/acl.d/luci-app-mlvpn.json @@ -2,6 +2,9 @@ "luci-app-mlvpn": { "description": "Grant UCI access for luci-app-mlvpn", "read": { + "ubus": { + "luci-rpc": [ "getHostHints" ] + }, "uci": [ "mlvpn" ] }, "write": { diff --git a/luci-app-mptcp/htdocs/luci-static/resources/view/mptcp/mptcp.js b/luci-app-mptcp/htdocs/luci-static/resources/view/mptcp/mptcp.js index abe9d982a..a1e3d3a5d 100644 --- a/luci-app-mptcp/htdocs/luci-static/resources/view/mptcp/mptcp.js +++ b/luci-app-mptcp/htdocs/luci-static/resources/view/mptcp/mptcp.js @@ -17,6 +17,17 @@ var callSystemBoard = rpc.declare({ }); +// 040-multipath-weight's reading of network..multipath_weight: +// not a number is unset (100), 0 is 1, over 256 is 256 +function normalizeWeightValue(value) { + if (value == null || value === '') + return value; + if (!/^[0-9]+$/.test(value)) + return null; + value = +value; + return String(value < 1 ? 1 : (value > 256 ? 256 : value)); +} + return L.view.extend({ load: function() { return Promise.all([ @@ -400,6 +411,13 @@ return L.view.extend({ // none. Unset means 100 for every reader (omr-weight-sync, mqvpn). o.placeholder = "100"; o.rmempty = true; + // A weight stored out of range (0, 300) failed the datatype and + // blocked every save of this page: shown as 040-multipath-weight reads + // it. A save that doesn't change the field leaves a number unwritten + // and drops what is not one + o.cfgvalue = function(section_id) { + return normalizeWeightValue(uci.get('network', section_id, 'multipath_weight')); + }; return m.render(); } diff --git a/luci-app-mptcp/htdocs/luci-static/resources/view/mptcp/mptcp_dscp_routing.js b/luci-app-mptcp/htdocs/luci-static/resources/view/mptcp/mptcp_dscp_routing.js index 327b6d17a..ec0e36c19 100644 --- a/luci-app-mptcp/htdocs/luci-static/resources/view/mptcp/mptcp_dscp_routing.js +++ b/luci-app-mptcp/htdocs/luci-static/resources/view/mptcp/mptcp_dscp_routing.js @@ -185,6 +185,19 @@ return L.view.extend({ // multipath_weight='100' on each WAN that had none (unset is 100) o.placeholder = '100'; o.rmempty = true; + // A weight stored out of range (0, 300) failed the datatype + // and blocked every save: shown as 040-multipath-weight reads + // it (not a number: unset, 0: 1, over 256: 256). A save that + // doesn't change the field leaves a number unwritten + o.cfgvalue = function(section_id) { + var value = uci.get('network', section_id, 'multipath_weight'); + if (value == null || value === '') + return value; + if (!/^[0-9]+$/.test(value)) + return null; + value = +value; + return String(value < 1 ? 1 : (value > 256 ? 256 : value)); + }; } return m.render(); diff --git a/luci-app-mptcp/root/usr/libexec/rpcd/luci.mptcp b/luci-app-mptcp/root/usr/libexec/rpcd/luci.mptcp index c2e3c55fe..6b9a944c1 100755 --- a/luci-app-mptcp/root/usr/libexec/rpcd/luci.mptcp +++ b/luci-app-mptcp/root/usr/libexec/rpcd/luci.mptcp @@ -612,6 +612,52 @@ local function valid_name(v) return v:match("^[%w_]+$") ~= nil end +-- set_settings / set_interface_settings committed without applying: nothing +-- changed until the next reload, and a second master was dropped by it (the +-- init script keeps the first one in config order). As setmptcpconfig of +-- the openmptcprouter object does: the other masters are demoted here, and +-- mptcp reloads in the background. +local function mptcp_reload(device) + local cmd = "/etc/init.d/mptcp reload" + -- embedded bare inside the sh -c single quotes + if device and device:match("^[%w_%.%-]+$") then cmd = cmd .. " " .. device end + os.execute("sh -c '(sleep 1; " .. cmd .. ") >/dev/null 2>/dev/null &'") +end + +local function set_multipath(iface, mp) + local previous = uci:get("network", iface, "multipath") or "" + if previous == "" then + -- /etc/init.d/mptcp falls back to the openmptcprouter copy + previous = uci:get("openmptcprouter", iface, "multipath") or "" + end + local demoted = 0 + if mp == "master" then + uci:foreach("network", "interface", function(s) + if s[".name"] ~= iface and s["multipath"] == "master" then + uci:set("network", s[".name"], "multipath", "on") + if uci:get("openmptcprouter", s[".name"]) ~= nil then + uci:set("openmptcprouter", s[".name"], "multipath", "on") + end + demoted = demoted + 1 + end + end) + end + uci:set("network", iface, "multipath", mp) + -- what omr-tracker puts back on a WAN it brings up again + if uci:get("openmptcprouter", iface) == nil then + uci:set("openmptcprouter", iface, "interface") + end + uci:set("openmptcprouter", iface, "multipath", mp) + uci:save("openmptcprouter") + uci:commit("openmptcprouter") + if previous == mp and demoted == 0 then return nil end + -- master changes need the unscoped pass, which alone enforces one master + if demoted > 0 or (previous == "master") ~= (mp == "master") then + return "" + end + return get_device(iface) +end + local setting_checks = { mptcp_checksum = function(v) return v == "0" or v == "1" end, mptcp_debug = function(v) return v == "0" or v == "1" end, @@ -751,18 +797,22 @@ local methods = { end end end + local changed = false for _, field in ipairs(string_fields) do if args[field] ~= nil and args[field] ~= "" then + changed = changed or uci:get("network", "globals", field) ~= args[field] uci:set("network", "globals", field, args[field]) end end for _, field in ipairs(list_fields) do if args[field] ~= nil and type(args[field]) == "table" then + changed = true uci:set_list("network", "globals", field, args[field]) end end uci:save("network") uci:commit("network") + if changed then mptcp_reload() end return { result = "ok" } end }, @@ -822,14 +872,17 @@ local methods = { not valid_weight(args.multipath_weight) then return { error = "Invalid value for multipath_weight" } end + -- the weight needs no reload: the post-tracking hooks apply it + local reload if mp ~= "" then - uci:set("network", iface, "multipath", mp) + reload = set_multipath(iface, mp) end if args.multipath_weight ~= nil and args.multipath_weight ~= "" then uci:set("network", iface, "multipath_weight", args.multipath_weight) end uci:save("network") uci:commit("network") + if reload then mptcp_reload(reload) end return { result = "ok" } end }, diff --git a/luci-app-open-box/Makefile b/luci-app-open-box/Makefile index 21fa268ed..e94a09d70 100644 --- a/luci-app-open-box/Makefile +++ b/luci-app-open-box/Makefile @@ -1,8 +1,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-open-box -PKG_VERSION:=0.1.356 -PKG_RELEASE:=44 +PKG_VERSION:=0.1.359 +PKG_RELEASE:=45 PKG_MAINTAINER:=liandu2024 PKG_LICENSE:=GPL-3.0-or-later diff --git a/luci-app-passwall/Makefile b/luci-app-passwall/Makefile index 06e2ffab8..7cf4099a2 100644 --- a/luci-app-passwall/Makefile +++ b/luci-app-passwall/Makefile @@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-passwall PKG_VERSION:=26.10.22 -PKG_RELEASE:=347 +PKG_RELEASE:=348 PKG_PO_VERSION:=$(PKG_VERSION) PKG_CONFIG_DEPENDS:= \ diff --git a/luci-app-passwall/htdocs/luci-static/resources/view/passwall/node_list_sort.js b/luci-app-passwall/htdocs/luci-static/resources/view/passwall/node_list_sort.js new file mode 100644 index 000000000..20a1e885b --- /dev/null +++ b/luci-app-passwall/htdocs/luci-static/resources/view/passwall/node_list_sort.js @@ -0,0 +1,773 @@ +/* + * Click-to-sort headers and a per-group filter bar for the node list. + * + * Sort reorders rows inside one group table and calls set_order(), the same + * path as drag-and-drop and To Top. Nothing is written until Save & Apply. + * + * A filter only toggles the pw-row-hidden class. Rows stay in the form, so + * Save & Apply still posts every node and the full group.order value. + * + * Translated strings are read from #pw-nodesort-i18n (the node list template). + * The template calls init() once, then enhanceAll() after loadNodeList() + * has built the group tables. + * + * auto_detection_time is a top-level let in the template, not a window + * property. It is read by name so rows are not moved while automatic Ping + * or TCPing is still writing results by row position. + */ +(function () { + 'use strict'; + + var COLUMNS = { + 'pw-remark': 'name', + 'pw-ping': 'ping', + 'pw-tcping': 'tcping', + 'pw-urltest': 'urltest' + }; + /* sub controls drawn inside the Remarks header: [sort key, label key or symbol, title key] */ + var NAME_EXTRA = [ + ['type', 'type', 'typeHint'], + ['addr', 'addr', 'addrHint'], + ['orig', '\u21BA', 'origHint'] + ]; + + var state = { + app: 'passwall', + root: null, + text: {}, + bound: false + }; + var searchTimer = null; + + function T(key) { + var value = state.text[key]; + return (value == null || value === '') ? key : value; + } + + function readText(box) { + var text = {}; + var spans = box.querySelectorAll('[data-k]'); + for (var i = 0; i < spans.length; i++) + text[spans[i].getAttribute('data-k')] = spans[i].textContent; + return text; + } + + var collator = null; + function prepareCollator() { + var lang = document.documentElement.lang || (navigator.language || ''); + try { + collator = (window.Intl && Intl.Collator) ? new Intl.Collator(lang || undefined, { numeric: true, sensitivity: 'base' }) : null; + } catch (e) { + collator = null; + } + } + + function cmpText(a, b) { + if (collator) + return collator.compare(a, b); + return a < b ? -1 : (a > b ? 1 : 0); + } + + /* "123 ms", "12.5 ms", "1.2 s" -> milliseconds. Timeout, Error, ---, Test stay null. */ + function parseLatency(text) { + var t = String(text || '').replace(/\s+/g, ' ').trim(); + var m = /^(\d+(?:[.,]\d+)?)\s*(ms|s)?$/i.exec(t); + if (!m) + return null; + var v = parseFloat(m[1].replace(',', '.')); + if (isNaN(v)) + return null; + if (m[2] && /^s$/i.test(m[2])) + v *= 1000; + return v; + } + + function hiddenVal(id, opt) { + var el = document.getElementById('cbid.' + state.app + '.' + id + '.' + opt); + return el && el.value != null ? String(el.value).trim() : ''; + } + + function cellsOf(tr) { + var out = []; + for (var i = 0; i < tr.children.length; i++) { + var tag = tr.children[i].tagName; + if (tag === 'TD' || tag === 'TH') + out.push(tr.children[i]); + } + return out; + } + + var wordSplit; + try { + wordSplit = new RegExp('[^\\p{L}\\p{N}]+', 'u'); + } catch (e) { + wordSplit = /[\s.,;:!?|\/\\()\[\]{}<>"'`~@#$%^&*=+_-]+/; + } + + function wordsOf(text) { + var seen = {}; + var out = []; + var parts = String(text || '').split(wordSplit); + for (var i = 0; i < parts.length; i++) { + var w = parts[i]; + if (w.length < 2 || /^\d+$/.test(w)) + continue; + var k = w.toLowerCase(); + if (!seen[k]) { + seen[k] = 1; + out.push(w); + } + } + return out; + } + + function infoOf(table) { + var prefix = 'cbi-' + state.app + '-nodes-'; + var id = table && table.id ? table.id : ''; + if (id.indexOf(prefix) !== 0 || id.slice(-6) !== '-table') + return null; + return { group: id.slice(prefix.length, -6) }; + } + + function rowId(tr) { + var prefix = 'cbi-' + state.app + '-'; + return tr.id.indexOf(prefix) === 0 ? tr.id.substring(prefix.length) : tr.id; + } + + function bodyOf(table) { + return table.querySelector('tbody') || table; + } + + function dataRows(table) { + var body = bodyOf(table); + var out = []; + for (var i = 0; i < body.children.length; i++) { + var tr = body.children[i]; + if (tr.tagName === 'TR' && tr.classList.contains('cbi-section-table-row') && !tr.classList.contains('placeholder')) + out.push(tr); + } + return out; + } + + function colIndexMap(table) { + var rows = dataRows(table); + var map = {}; + if (!rows.length) + return map; + var cells = cellsOf(rows[0]); + for (var i = 0; i < cells.length; i++) { + for (var cls in COLUMNS) { + if (cells[i].classList.contains(cls)) + map[COLUMNS[cls]] = i; + } + } + return map; + } + + function typeOf(tr, cols) { + var cell = cellsOf(tr)[cols.name]; + var text = cell ? cell.textContent.replace(/\s+/g, ' ').trim() : ''; + var i = text.indexOf('\uFF1A'); + if (i < 0) + i = text.indexOf(':'); + return (i < 0 ? '' : text.slice(0, i)).replace(/\s+Realm$/, '').trim(); + } + + function sortKey(tr, key, cols) { + var id = rowId(tr); + var cell; + if (key === 'name') + return hiddenVal(id, 'remarks') || ((cellsOf(tr)[cols.name] || {}).textContent || '').trim(); + if (key === 'type') + return typeOf(tr, cols) + '\u0000' + hiddenVal(id, 'remarks'); + if (key === 'addr') { + var address = hiddenVal(id, 'address'); + if (!address) + return null; + return address + '\u0000' + ('00000' + hiddenVal(id, 'port')).slice(-5); + } + cell = cellsOf(tr)[cols[key]]; + return cell ? parseLatency(cell.textContent) : null; + } + + function detectionMode() { + try { + if (typeof auto_detection_time === 'undefined') + return null; + return auto_detection_time; + } catch (e) { + return null; + } + } + + /* { inflight: requests still running, left: cells still waiting for a result } */ + function pingBusy() { + var mode = detectionMode(); + var inflight = 0; + var left = 0; + if (mode !== 'icmp' && mode !== 'tcping') + return { inflight: 0, left: 0 }; + var list = window.ajax && window.ajax.list; + if (list && list.length) { + for (var i = 0; i < list.length; i++) { + if (list[i].readyState > 0 && list[i].readyState < 4) + inflight++; + } + } + if (!state.root) + return { inflight: inflight, left: 0 }; + var spans = state.root.getElementsByClassName(mode === 'tcping' ? 'tcping_value' : 'ping_value'); + for (var s = 0; s < spans.length; s++) { + var id = spans[s].getAttribute('cbiid'); + if (!id || spans[s].textContent.trim() !== '---') + continue; + var full = null; + if (typeof window.get_address_full === 'function') + full = window.get_address_full(id); + if (!full) + full = { address: hiddenVal(id, 'address'), port: hiddenVal(id, 'port') }; + if (full.address && (mode === 'icmp' || full.port)) + left++; + } + return { inflight: inflight, left: left }; + } + + function note(table, text) { + var id = 'pw-note-' + table.id; + var el = document.getElementById(id); + if (!text) { + if (el && el.parentNode) + el.parentNode.removeChild(el); + return; + } + if (!el) { + el = document.createElement('div'); + el.id = id; + el.className = 'pw-sort-note'; + table.parentNode.insertBefore(el, table); + } + el.textContent = text; + } + + function setIndicator(table, th, sub, key, dir) { + var arrows = table.querySelectorAll('.pw-sort-arrow'); + var subs = table.querySelectorAll('.pw-sort-sub'); + var headers = table.querySelectorAll('th[data-pw-sort]'); + var i; + for (i = 0; i < arrows.length; i++) + arrows[i].textContent = ''; + for (i = 0; i < subs.length; i++) + subs[i].classList.remove('pw-sort-active'); + for (i = 0; i < headers.length; i++) { + if (headers[i] !== th) { + delete headers[i].dataset.pwDir; + delete headers[i].dataset.pwKey; + } + } + var arrow = th.querySelector('.pw-sort-arrow'); + if (arrow && key !== 'orig' && !sub) + arrow.textContent = dir === 'desc' ? '\u25BC' : '\u25B2'; + if (sub && key !== 'orig') + sub.classList.add('pw-sort-active'); + } + + function applyOrder(table, info, rows) { + var body = bodyOf(table); + var frag = document.createDocumentFragment(); + for (var i = 0; i < rows.length; i++) { + rows[i].classList.remove('cbi-rowstyle-1', 'cbi-rowstyle-2'); + rows[i].classList.add('cbi-rowstyle-' + (i % 2 + 1)); + frag.appendChild(rows[i]); + } + body.appendChild(frag); + if (typeof window.set_order === 'function') + window.set_order(info.group); + } + + function doSort(table, th, sub, key) { + var info = infoOf(table); + var rows = dataRows(table); + var dir = 'asc'; + if (!info || rows.length < 2) + return; + if (key === 'orig') { + var pos = {}; + var orig = table.__pwOrig || []; + for (var n = 0; n < orig.length; n++) + pos[orig[n]] = n; + rows.sort(function (a, b) { + var pa = pos[rowId(a)]; + var pb = pos[rowId(b)]; + return (pa == null ? 1e9 : pa) - (pb == null ? 1e9 : pb); + }); + delete th.dataset.pwDir; + delete th.dataset.pwKey; + } else { + dir = (th.dataset.pwKey === key && th.dataset.pwDir === 'asc') ? 'desc' : 'asc'; + th.dataset.pwKey = key; + th.dataset.pwDir = dir; + var cols = colIndexMap(table); + var sign = dir === 'desc' ? -1 : 1; + var list = []; + for (var i = 0; i < rows.length; i++) + list.push({ tr: rows[i], i: i, k: sortKey(rows[i], key, cols) }); + list.sort(function (a, b) { + var emptyA = (a.k === null || a.k === ''); + var emptyB = (b.k === null || b.k === ''); + if (emptyA || emptyB) + return emptyA === emptyB ? a.i - b.i : (emptyA ? 1 : -1); + var cmp = (typeof a.k === 'number' && typeof b.k === 'number') ? a.k - b.k : cmpText(String(a.k), String(b.k)); + return cmp !== 0 ? sign * cmp : a.i - b.i; + }); + rows = []; + for (var j = 0; j < list.length; j++) + rows.push(list[j].tr); + } + applyOrder(table, info, rows); + refreshFilter(table); + setIndicator(table, th, sub, key, dir); + note(table, key === 'orig' ? T('restored') : T('changed')); + } + + function requestSort(table, th, sub, key) { + if (table.__pwWait) { + clearInterval(table.__pwWait); + table.__pwWait = null; + } + var st = pingBusy(); + if (!st.inflight && !st.left) { + doSort(table, th, sub, key); + return; + } + var started = Date.now(); + var idle = 0; + note(table, T('wait').replace('%d', st.left || st.inflight)); + table.__pwWait = setInterval(function () { + st = pingBusy(); + idle = st.inflight ? 0 : idle + 1; + if ((!st.inflight && !st.left) || idle >= 6) { + clearInterval(table.__pwWait); + table.__pwWait = null; + if (document.body.contains(table)) + doSort(table, th, sub, key); + return; + } + if (Date.now() - started > 120000) { + clearInterval(table.__pwWait); + table.__pwWait = null; + note(table, T('waitFail')); + return; + } + note(table, T('wait').replace('%d', st.left || st.inflight)); + }, 500); + } + + function enhance(table) { + var info = infoOf(table); + if (!info || table.__pwReady) + return; + var titles = table.querySelector('tr.cbi-section-table-titles'); + var rows = dataRows(table); + if (!titles || !rows.length) + return; + var cols = colIndexMap(table); + var headers = cellsOf(titles); + table.__pwReady = true; + table.__pwOrig = []; + for (var r = 0; r < rows.length; r++) + table.__pwOrig.push(rowId(rows[r])); + for (var key in cols) { + if (!Object.prototype.hasOwnProperty.call(cols, key)) + continue; + var th = headers[cols[key]]; + if (!th || th.classList.contains('cbi-section-actions')) + continue; + th.setAttribute('data-pw-sort', key); + th.title = T('hint'); + var arrow = document.createElement('span'); + arrow.className = 'pw-sort-arrow'; + th.appendChild(arrow); + if (key !== 'name') + continue; + for (var s = 0; s < NAME_EXTRA.length; s++) { + var spec = NAME_EXTRA[s]; + var sp = document.createElement('span'); + sp.className = 'pw-sort-sub'; + sp.setAttribute('data-pw-sort', spec[0]); + sp.textContent = T(spec[1]); + sp.title = T(spec[2]); + th.appendChild(sp); + } + } + } + + function rowVisible(el) { + var row = el && el.closest ? el.closest('tr') : null; + return !row || !row.classList.contains('pw-row-hidden'); + } + + function filterDefs(rows, cols) { + return [ + { + key: 'type', + label: T('type'), + multi: false, + get: function (tr) { return typeOf(tr, cols); } + }, + { + key: 'word', + label: T('fWord'), + multi: true, + minCount: 2, + max: 40, + get: function (tr) { return wordsOf(hiddenVal(rowId(tr), 'remarks')); } + } + ]; + } + + function searchText(tr, cols) { + var id = rowId(tr); + return [ + typeOf(tr, cols), + hiddenVal(id, 'remarks'), + hiddenVal(id, 'address') + ':' + hiddenVal(id, 'port') + ].join(' '); + } + + function collect(table) { + var rows = dataRows(table); + var cols = colIndexMap(table); + var defs = rows.length >= 2 ? filterDefs(rows, cols) : []; + var shown = []; + for (var d = 0; d < defs.length; d++) { + var def = defs[d]; + var counts = {}; + var labels = {}; + var vals = []; + for (var r = 0; r < rows.length; r++) { + var raw = def.get(rows[r]); + var list = def.multi ? (raw || []) : [raw == null ? '' : String(raw)]; + var keys = []; + for (var n = 0; n < list.length; n++) { + var item = list[n]; + var k = def.multi ? String(item).toLowerCase() : String(item); + counts[k] = (counts[k] || 0) + 1; + if (!labels[k]) + labels[k] = {}; + labels[k][item] = (labels[k][item] || 0) + 1; + keys.push(k); + } + vals.push(keys); + } + var opts = []; + for (var k in counts) { + if (!Object.prototype.hasOwnProperty.call(counts, k)) + continue; + if (def.multi && (counts[k] < (def.minCount || 1) || counts[k] >= rows.length)) + continue; + var best = ''; + var bestN = -1; + for (var label in labels[k]) { + if (labels[k][label] > bestN) { + bestN = labels[k][label]; + best = label; + } + } + opts.push({ v: k, label: best === '' ? T('empty') : best, n: counts[k] }); + } + opts.sort(function (a, b) { + if (def.multi && a.n !== b.n) + return b.n - a.n; + return cmpText(a.label, b.label); + }); + if (def.max && opts.length > def.max) + opts = opts.slice(0, def.max); + var enough = def.multi ? opts.length >= 1 : opts.length >= 2; + if (!enough) + continue; + def.vals = vals; + def.opts = opts; + shown.push(def); + } + var search = rows.length >= 2; + var sig = String(rows.length) + (search ? 's' : ''); + for (var i = 0; i < shown.length; i++) { + sig += '|' + shown[i].key + '='; + for (var o = 0; o < shown[i].opts.length; o++) + sig += shown[i].opts[o].v + ':' + shown[i].opts[o].n + ','; + } + return { rows: rows, cols: cols, defs: shown, search: search, sig: sig }; + } + + function barId(table) { + return 'pw-filter-' + table.id; + } + + function buildBar(table, collected) { + var st = table.__pwF || (table.__pwF = { sel: {}, q: '' }); + var id = barId(table); + var bar = document.getElementById(id); + if (!bar) { + bar = document.createElement('div'); + bar.id = id; + bar.className = 'pw-filter'; + bar.title = T('filterHint'); + table.parentNode.insertBefore(bar, table); + bar.__pwTable = table; + } + while (bar.firstChild) + bar.removeChild(bar.firstChild); + var head = document.createElement('span'); + head.textContent = T('filter'); + bar.appendChild(head); + var keep = {}; + for (var d = 0; d < collected.defs.length; d++) { + var def = collected.defs[d]; + var lab = document.createElement('label'); + var sel = document.createElement('select'); + lab.appendChild(document.createTextNode(def.label)); + sel.className = 'cbi-input-select'; + sel.setAttribute('data-pw-filter', def.key); + var all = document.createElement('option'); + all.value = ''; + all.textContent = T('all') + ' (' + collected.rows.length + ')'; + sel.appendChild(all); + for (var o = 0; o < def.opts.length; o++) { + var op = document.createElement('option'); + op.value = 'v:' + def.opts[o].v; + op.textContent = def.opts[o].label + ' (' + def.opts[o].n + ')'; + sel.appendChild(op); + } + var cur = st.sel[def.key] || ''; + var known = false; + if (cur) { + for (var c = 0; c < def.opts.length; c++) { + if (('v:' + def.opts[c].v) === cur) { + known = true; + break; + } + } + } + if (!known) + cur = ''; + sel.value = cur; + if (cur) + keep[def.key] = cur; + lab.appendChild(sel); + bar.appendChild(lab); + } + st.sel = keep; + if (collected.search) { + var input = document.createElement('input'); + input.type = 'search'; + input.className = 'cbi-input-text'; + input.placeholder = T('search'); + input.title = T('searchHint'); + input.autocomplete = 'off'; + input.setAttribute('data-pw-search', '1'); + input.value = st.q; + bar.appendChild(input); + } else { + st.q = ''; + } + var count = document.createElement('span'); + count.className = 'pw-filter-count'; + bar.appendChild(count); + var reset = document.createElement('button'); + reset.type = 'button'; + reset.className = 'btn cbi-button'; + reset.setAttribute('data-pw-reset', '1'); + reset.textContent = T('reset'); + reset.title = T('resetHint'); + bar.appendChild(reset); + return bar; + } + + function listHas(list, value) { + for (var i = 0; i < list.length; i++) { + if (list[i] === value) + return true; + } + return false; + } + + function applyFilter(table, collected, bar) { + var st = table.__pwF || { sel: {}, q: '' }; + var q = String(st.q || '').toLowerCase().trim(); + var active = !!q; + var shown = 0; + var d; + for (d = 0; d < collected.defs.length; d++) { + if (st.sel[collected.defs[d].key]) + active = true; + } + for (var i = 0; i < collected.rows.length; i++) { + var tr = collected.rows[i]; + var ok = true; + for (d = 0; d < collected.defs.length; d++) { + var sel = st.sel[collected.defs[d].key]; + if (ok && sel && !listHas(collected.defs[d].vals[i], sel.slice(2))) + ok = false; + } + if (ok && q && searchText(tr, collected.cols).toLowerCase().indexOf(q) < 0) + ok = false; + if (ok !== !tr.classList.contains('pw-row-hidden')) + tr.classList.toggle('pw-row-hidden', !ok); + if (ok) { + shown++; + var want = 'cbi-rowstyle-' + ((shown - 1) % 2 + 1); + var other = want === 'cbi-rowstyle-1' ? 'cbi-rowstyle-2' : 'cbi-rowstyle-1'; + if (!tr.classList.contains(want)) + tr.classList.add(want); + if (tr.classList.contains(other)) + tr.classList.remove(other); + } else { + var cb = tr.querySelector('input.nodes_select'); + if (cb && cb.checked) { + cb.checked = false; + } + } + } + if (typeof window.update_select_state === 'function') + window.update_select_state(); + var count = bar.querySelector('.pw-filter-count'); + var txt = T('shown').replace('%s', shown).replace('%t', collected.rows.length); + if (count && count.textContent !== txt) + count.textContent = txt; + if (bar.classList.contains('pw-filter-on') !== active) + bar.classList.toggle('pw-filter-on', active); + } + + function removeBar(table) { + var bar = document.getElementById(barId(table)); + if (bar && bar.parentNode) + bar.parentNode.removeChild(bar); + var rows = dataRows(table); + for (var i = 0; i < rows.length; i++) { + if (rows[i].classList.contains('pw-row-hidden')) + rows[i].classList.remove('pw-row-hidden'); + } + table.__pwSig = null; + } + + function refreshFilter(table) { + if (!infoOf(table)) + return; + var collected = collect(table); + if (!collected.defs.length && !collected.search) { + if (table.__pwSig) + removeBar(table); + return; + } + var bar = document.getElementById(barId(table)); + if (!bar || collected.sig !== table.__pwSig) { + bar = buildBar(table, collected); + table.__pwSig = collected.sig; + } + applyFilter(table, collected, bar); + } + + function barTable(el) { + var bar = el && el.closest ? el.closest('.pw-filter') : null; + return bar && bar.__pwTable ? bar.__pwTable : null; + } + + function eventTarget(ev) { + var t = ev.target; + if (t && t.nodeType !== 1) + t = t.parentNode; + return t; + } + + function bind(root) { + root.addEventListener('click', function (ev) { + var t = eventTarget(ev); + if (t && t.getAttribute && t.getAttribute('data-pw-reset')) { + var resetTable = barTable(t); + if (!resetTable) + return; + ev.preventDefault(); + resetTable.__pwF = { sel: {}, q: '' }; + resetTable.__pwSig = null; + refreshFilter(resetTable); + return; + } + var el = t && t.closest ? t.closest('[data-pw-sort]') : null; + if (!el || !root.contains(el)) + return; + if (t.closest && t.closest('input, a, button, select')) + return; + var th = el.closest('th'); + var table = el.closest('table'); + if (!th || !table || !infoOf(table)) + return; + ev.preventDefault(); + ev.stopPropagation(); + requestSort(table, th, el === th ? null : el, el.getAttribute('data-pw-sort')); + }); + root.addEventListener('change', function (ev) { + var t = eventTarget(ev); + if (!t || !t.getAttribute || !t.getAttribute('data-pw-filter')) + return; + var table = barTable(t); + if (!table) + return; + (table.__pwF = table.__pwF || { sel: {}, q: '' }).sel[t.getAttribute('data-pw-filter')] = t.value; + refreshFilter(table); + }); + root.addEventListener('input', function (ev) { + var t = eventTarget(ev); + if (!t || !t.getAttribute || !t.getAttribute('data-pw-search')) + return; + var table = barTable(t); + if (!table) + return; + clearTimeout(searchTimer); + searchTimer = setTimeout(function () { + (table.__pwF = table.__pwF || { sel: {}, q: '' }).q = t.value; + refreshFilter(table); + }, 150); + }); + root.addEventListener('keydown', function (ev) { + var t = eventTarget(ev); + if (ev.key === 'Enter' && t && t.getAttribute && t.getAttribute('data-pw-search')) + ev.preventDefault(); + }); + } + + function init() { + var box = document.getElementById('pw-nodesort-i18n'); + var root = document.getElementById('node_list'); + if (!box || !root) + return; + state.app = box.getAttribute('data-app') || 'passwall'; + state.text = readText(box); + state.root = root; + prepareCollator(); + if (!state.bound) { + bind(root); + state.bound = true; + } + } + + function enhanceAll() { + if (!state.root) + return; + var tables = state.root.querySelectorAll('table.cbi-section-table'); + for (var i = 0; i < tables.length; i++) { + try { + enhance(tables[i]); + refreshFilter(tables[i]); + } catch (e) { + if (window.console) + console.warn('passwall node list sort:', e); + } + } + } + + window.pwNodeSort = { + init: init, + enhanceAll: enhanceAll, + rowVisible: rowVisible + }; +})(); diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/rule.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/rule.lua index c19b53ab5..4913ce442 100644 --- a/luci-app-passwall/luasrc/model/cbi/passwall/client/rule.lua +++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/rule.lua @@ -78,7 +78,7 @@ if has_xray or has_singbox then o.default = 0 o.rmempty = false o.description = translate("Generate rule lists such as GFW, China domains, and China IP ranges based on Geo files.") .. "
" .. - translate("When manually updating with this option enabled, rules will be regenerated from existing Geo files even if no new version is available.") .. "" + translate("GeoSite files without GFW rules cannot generate a GFW list (e.g. the original v2fly/Xray files).") .. "" o = s:option(Flag, "enable_geoview", translate("Enable Geo Data Parsing")) o.default = 0 diff --git a/luci-app-passwall/luasrc/passwall/util_sing-box.lua b/luci-app-passwall/luasrc/passwall/util_sing-box.lua index 6ed462bb3..f5421b71e 100644 --- a/luci-app-passwall/luasrc/passwall/util_sing-box.lua +++ b/luci-app-passwall/luasrc/passwall/util_sing-box.lua @@ -1692,8 +1692,21 @@ function gen_config(var) local bin = api.finded_com("geoview") if bin then local geo_file = (api.uci_get_c("@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"):match("^(.*)/") .. "/geosite.dat" - if luci.sys.call('"' .. bin .. '" -type geosite -input "' .. geo_file .. '" | grep -q "^GFW$"') == 0 then - domain_list = (domain_list == "") and "geosite:gfw" or domain_list .. "\ngeosite:gfw" + local gfw_rule + if sys.call('"' .. bin .. '" -type geosite -input "' .. geo_file .. '" 2>/dev/null | grep -q "^GFW$"') == 0 then + gfw_rule = "geosite:gfw" + elseif api.compare_versions(api.get_app_version("geoview"), ">=", "0.2.8") then + local output = GEO_VAR.TO_SRS_PATH .. "geosite-gfw.srs" + if not fs.access(output) then + fs.mkdirr(GEO_VAR.TO_SRS_PATH) + local cmd = string.format("%s -type geosite -action convert -input /usr/share/passwall/rules/gfwlist -list '*' -output %s -format srs", + api.util.shellquote(bin), api.util.shellquote(output)) + if api.exec_call(cmd) ~= 0 then fs.unlink(output) end + end + gfw_rule = fs.access(output) and ("rule-set:local:" .. output) or nil + end + if gfw_rule then + domain_list = (domain_list == "") and gfw_rule or domain_list .. "\n" .. gfw_rule end end diff --git a/luci-app-passwall/luasrc/passwall/util_xray.lua b/luci-app-passwall/luasrc/passwall/util_xray.lua index f463116c7..f7e9619ce 100644 --- a/luci-app-passwall/luasrc/passwall/util_xray.lua +++ b/luci-app-passwall/luasrc/passwall/util_xray.lua @@ -579,11 +579,15 @@ function gen_config_server(node) end end - if node.protocol == "vmess" or node.protocol == "vless" then + if node.protocol == "vless" then + settings = { + users = users, + decryption = (node.decryption and node.decryption ~= "") and node.decryption or "none" + } + elseif node.protocol == "vmess" then if users then settings = { - users = users, - decryption = (node.protocol == "vless") and ((node.decryption and node.decryption ~= "") and node.decryption or "none") or nil + users = users } end elseif node.protocol == "socks" then @@ -1479,8 +1483,21 @@ function gen_config(var) local bin = api.finded_com("geoview") if bin then local geo_file = (api.uci_get_c("@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"):match("^(.*)/") .. "/geosite.dat" - if luci.sys.call('"' .. bin .. '" -type geosite -input "' .. geo_file .. '" | grep -q "^GFW$"') == 0 then - domain_list = (domain_list == "") and "geosite:gfw" or domain_list .. "\ngeosite:gfw" + local gfw_rule + if sys.call('"' .. bin .. '" -type geosite -input "' .. geo_file .. '" 2>/dev/null | grep -q "^GFW$"') == 0 then + gfw_rule = "geosite:gfw" + elseif api.compare_versions(api.get_app_version("geoview"), ">=", "0.2.8") then + local output = api.CACHE_PATH .. "/geodata/gfwlist.dat" + if not fs.access(output) then + fs.mkdirr(api.CACHE_PATH .. "/geodata") + local cmd = string.format("%s -type geosite -action convert -input /usr/share/passwall/rules/gfwlist -list '*' -output %s -format geosite", + api.util.shellquote(bin), api.util.shellquote(output)) + if api.exec_call(cmd) ~= 0 then fs.unlink(output) end + end + gfw_rule = fs.access(output) and "ext:gfwlist.dat:all" or nil + end + if gfw_rule then + domain_list = (domain_list == "") and gfw_rule or domain_list .. "\n" .. gfw_rule end end @@ -1539,7 +1556,7 @@ function gen_config(var) w = api.trim(w) if w == "" or w:find("#") == 1 then return end if w:find("rule-set:", 1, true) == 1 or w:find("rs:") == 1 then return end - if w:find("ext:", 1, true) == 1 then return end -- Rule currently not support ext: + if w:find("ext:", 1, true) == 1 and not (e[".name"] == "GFW_Mode_List" and w == "ext:gfwlist.dat:all") then return end table.insert(domains, w) table.insert(domain_table.domain, w) end) @@ -2051,10 +2068,7 @@ function gen_config(var) if inbounds or outbounds then local config = { - env = (function() - local asset_location = api.uci_get_c("@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/" - return { XRAY_LOCATION_ASSET = asset_location } - end)(), + env = { XRAY_LOCATION_ASSET = api.CACHE_PATH .. "/geodata" }, log = { -- error = string.format("/tmp/etc/passwall/%s.log", node[".name"]), loglevel = get_log_level(loglevel) diff --git a/luci-app-passwall/luasrc/view/passwall/node_list/node_list.htm b/luci-app-passwall/luasrc/view/passwall/node_list/node_list.htm index c69a075b5..731a35c8d 100644 --- a/luci-app-passwall/luasrc/view/passwall/node_list/node_list.htm +++ b/luci-app-passwall/luasrc/view/passwall/node_list/node_list.htm @@ -24,6 +24,7 @@ if node then end -%> + + + + + +