Merge pull request #227 from wnlen/dev

fix(select): block manual node picking for auto strategy groups #214
This commit is contained in:
Arvin
2026-04-24 01:13:43 +08:00
committed by GitHub
2 changed files with 288 additions and 21 deletions
+137 -4
View File
@@ -6191,7 +6191,7 @@ cmd_select() {
if [ -z "${1:-}" ]; then
print_select_context || return $?
proxy_select_interactive
proxy_select_interactive_guarded
return 0
fi
@@ -6217,7 +6217,7 @@ cmd_proxy_groups() {
[ -n "${group:-}" ] || continue
found="true"
type="$(proxy_group_type "$group" 2>/dev/null || echo "unknown")"
current="$(proxy_group_current "$group" 2>/dev/null || echo "-")"
current="$(proxy_group_current "$group" 2>/dev/null || true)"
printf ' 📦 %-20s %-12s %s\n' "$group" "$type" "$current"
done < <(proxy_group_list)
@@ -6333,14 +6333,14 @@ proxy_pick_index() {
}
proxy_pick_group_interactive() {
local idx count group current
local idx count group current type
local -a groups=()
local -a ordered_groups=()
while IFS= read -r group; do
[ -n "${group:-}" ] || continue
groups+=("$group")
done < <(proxy_group_manual_list)
done < <(proxy_group_display_list)
for group in "节点选择" "自动选择"; do
if printf '%s\n' "${groups[@]}" | grep -Fxq "$group"; then
@@ -6543,6 +6543,139 @@ proxy_select_interactive() {
print_select_feedback "$group"
}
proxy_pick_group_for_select() {
local idx count group current type
local -a groups=()
local -a ordered_groups=()
while IFS= read -r group; do
[ -n "${group:-}" ] || continue
groups+=("$group")
done < <(proxy_group_display_list)
for group in "节点选择" "自动选择"; do
if printf '%s\n' "${groups[@]}" | grep -Fxq "$group"; then
ordered_groups+=("$group")
fi
done
for group in "${groups[@]}"; do
case "$group" in
节点选择|自动选择)
continue
;;
esac
ordered_groups+=("$group")
done
groups=("${ordered_groups[@]}")
count="${#groups[@]}"
[ "$count" -gt 0 ] || die "📭 暂无可切换策略组"
echo "📦 请选择策略组:" >&2
echo "💡 通常优先选择:节点选择" >&2
idx=1
for group in "${groups[@]}"; do
current="$(proxy_group_current "$group" 2>/dev/null || true)"
type="$(proxy_group_type_label "$group" 2>/dev/null || echo "unknown")"
[ -n "${current:-}" ] || current="<unknown>"
printf ' %s) 📦 %s [%s] -> 🚀 %s\n' "$idx" "$group" "$type" "$current" >&2
idx=$((idx + 1))
done
echo " q) 退出" >&2
echo >&2
idx="$(proxy_pick_index "$count")" || return 1
echo "${groups[$((idx - 1))]}"
}
print_proxy_group_manual_pick_blocked() {
local group="$1"
local current type message
message="$(proxy_group_manual_pick_error_message "$group" 2>/dev/null || echo "该策略组不支持手动切换:$group")"
current="$(proxy_group_current "$group" 2>/dev/null || true)"
type="$(proxy_group_type_label "$group" 2>/dev/null || echo "unknown")"
ui_warn "$message"
ui_kv "📦" "策略组" "$group"
ui_kv "🧭" "策略组类型" "$type"
if [ -n "${current:-}" ]; then
ui_kv "🚀" "当前节点" "$current"
else
ui_kv "🚀" "当前节点" "<unknown>"
fi
ui_blank
}
proxy_select_interactive_guarded() {
local group="${1:-}"
local current idx count total_count node selected_node
local -a nodes=()
prepare
if ! status_is_running; then
die_state "代理内核未运行" "clashon"
fi
if ! proxy_controller_reachable 2>/dev/null; then
die_state "控制器不可访问" "clashctl doctor"
fi
if [ -z "${group:-}" ]; then
ui_title "🚀 节点切换"
group="$(proxy_pick_group_for_select)" || return 0
fi
proxy_group_exists "$group" || die "策略组不存在:$group"
if ! proxy_group_supports_manual_pick "$group"; then
print_proxy_group_manual_pick_blocked "$group"
return 0
fi
current="$(proxy_group_current "$group" 2>/dev/null || true)"
while IFS= read -r node; do
[ -n "${node:-}" ] || continue
nodes+=("$node")
done < <(proxy_group_selectable_nodes "$group")
count="${#nodes[@]}"
[ "$count" -gt 0 ] || die "📭 当前策略组没有候选节点:$group"
total_count="$(select_total_node_count 2>/dev/null || true)"
echo
echo "📦 当前策略组:$group"
[ -n "${current:-}" ] && echo "🚀 当前节点:$current"
echo "📦 当前策略组候选数:$count"
[ -n "${total_count:-}" ] && echo "🔢 全部节点总数:$total_count"
echo "ℹ️ 以下仅显示当前策略组可切换节点"
echo
echo "🚀 请选择节点:"
idx=1
for node in "${nodes[@]}"; do
if [ "$node" = "$current" ]; then
printf ' %s) 🚀 %s [当前]\n' "$idx" "$node"
else
printf ' %s) 🚀 %s\n' "$idx" "$node"
fi
idx=$((idx + 1))
done
echo " q) 退出"
echo
idx="$(proxy_pick_index "$count")" || return 0
selected_node="${nodes[$((idx - 1))]}"
proxy_group_select "$group" "$selected_node"
print_select_feedback "$group"
}
cmd_proxy() {
echo "⚠ 当前版本不提供 proxy 子命令"
echo "👉 使用 clashon / clashoff 控制代理"
+151 -17
View File
@@ -278,13 +278,50 @@ proxy_group_type() {
proxy_groups_json | "$(yq_bin)" -p=json eval ".proxies.\"$group\".type // \"\"" - 2>/dev/null
}
proxy_group_is_selector() {
proxy_group_type_key() {
local type
type="$(proxy_group_type "$1")"
type="$(proxy_group_type "$1" 2>/dev/null || true)"
printf '%s' "${type:-}" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]_-'
}
case "$type" in
Selector|URLTest|Fallback|LoadBalance)
proxy_group_type_label() {
local group="$1"
local type_key raw_type
type_key="$(proxy_group_type_key "$group")"
case "$type_key" in
selector)
echo "select"
;;
urltest)
echo "自动选择 / url-test"
;;
fallback)
echo "故障转移 / fallback"
;;
loadbalance)
echo "负载均衡 / load-balance"
;;
*)
raw_type="$(proxy_group_type "$group" 2>/dev/null || true)"
if [ -n "${raw_type:-}" ] && [ "$raw_type" != "null" ]; then
echo "$raw_type"
else
echo "unknown"
fi
;;
esac
}
proxy_group_can_show_candidates() {
local type_key
type_key="$(proxy_group_type_key "$1")"
case "$type_key" in
selector|urltest|fallback|loadbalance)
return 0
;;
*)
@@ -293,22 +330,25 @@ proxy_group_is_selector() {
esac
}
proxy_group_is_selector() {
proxy_group_can_show_candidates "$1"
}
proxy_group_is_manual_selector() {
proxy_group_supports_manual_pick "$1"
}
proxy_group_is_auto_managed() {
local group="$1"
local type normalized_type
local type_key
[ -n "${group:-}" ] || return 0
proxy_group_exists "$group" || return 0
type="$(proxy_group_type "$group" 2>/dev/null || true)"
normalized_type="$(printf '%s' "${type:-}" | tr '[:upper:]' '[:lower:]')"
type_key="$(proxy_group_type_key "$group")"
case "$normalized_type" in
urltest|url-test|fallback|loadbalance|load-balance)
case "$type_key" in
urltest|fallback|loadbalance)
return 0
;;
esac
@@ -389,14 +429,54 @@ proxy_group_selectable_nodes() {
done < <(proxy_group_nodes "$group")
}
proxy_group_supports_manual_pick() {
proxy_group_has_selectable_candidates() {
local group="$1"
local node
local has_now=""
[ -n "${group:-}" ] || return 1
proxy_group_exists "$group" || return 1
proxy_group_is_auto_managed "$group" && return 1
proxy_group_can_show_candidates "$group" || return 1
while IFS= read -r node; do
[ -n "${node:-}" ] || continue
return 0
done < <(proxy_group_selectable_nodes "$group")
return 1
}
proxy_group_manual_pick_error_message() {
local group="$1"
local type_key type_label
[ -n "${group:-}" ] || {
echo "该策略组不支持手动切换"
return 0
}
type_key="$(proxy_group_type_key "$group")"
type_label="$(proxy_group_type_label "$group")"
case "$type_key" in
urltest|fallback|loadbalance)
echo "该策略组为${type_label}类型,不支持手动切换:$group"
;;
*)
echo "该策略组不支持手动切换:$group"
;;
esac
}
proxy_group_supports_manual_pick() {
local group="$1"
local has_now=""
local type_key
[ -n "${group:-}" ] || return 1
proxy_group_exists "$group" || return 1
type_key="$(proxy_group_type_key "$group")"
[ "$type_key" = "selector" ] || return 1
has_now="$(
proxy_groups_json \
@@ -405,12 +485,17 @@ proxy_group_supports_manual_pick() {
)"
[ "${has_now:-false}" = "true" ] || return 1
while IFS= read -r node; do
[ -n "${node:-}" ] || continue
return 0
done < <(proxy_group_selectable_nodes "$group")
proxy_group_has_selectable_candidates "$group"
}
return 1
proxy_group_display_list() {
local group
while IFS= read -r group; do
[ -n "${group:-}" ] || continue
proxy_group_has_selectable_candidates "$group" || continue
echo "$group"
done < <(proxy_group_list)
}
proxy_group_manual_list() {
@@ -472,6 +557,55 @@ proxy_group_select() {
rm -f "$response_file" 2>/dev/null || true
}
proxy_group_select() {
local group="$1"
local node="$2"
local base secret
local code response_file response_body
local available_node found
[ -n "${group:-}" ] || die "策略组名称不能为空"
[ -n "${node:-}" ] || die "节点名称不能为空"
proxy_group_exists "$group" || die "策略组不存在:$group"
proxy_group_supports_manual_pick "$group" || die "$(proxy_group_manual_pick_error_message "$group")"
found=false
while IFS= read -r available_node; do
[ -n "${available_node:-}" ] || continue
if [ "$available_node" = "$node" ]; then
found=true
break
fi
done < <(proxy_group_selectable_nodes "$group")
if [ "$found" != "true" ]; then
die "节点不存在于策略组中:$group -> $node"
fi
base="$(controller_api_base)"
secret="$(controller_secret)"
response_file="$(mktemp)"
code="$(
curl -sS -o "$response_file" -w "%{http_code}" -X PUT \
-H "Content-Type: application/json" \
${secret:+-H "Authorization: Bearer $secret"} \
--data "{\"name\":\"$node\"}" \
"$base/proxies/$group"
)"
if [ "${code:-000}" -lt 200 ] || [ "${code:-000}" -ge 300 ]; then
response_body="$(cat "$response_file" 2>/dev/null || true)"
rm -f "$response_file" 2>/dev/null || true
if [ -n "${response_body:-}" ]; then
die "节点切换失败:$response_body"
fi
die "节点切换失败:controller 返回 HTTP $code"
fi
rm -f "$response_file" 2>/dev/null || true
}
proxy_group_count() {
proxy_group_list 2>/dev/null | awk 'NF{c++} END{print c+0}'
}