| #!/bin/bash |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| set -uo pipefail |
|
|
| CHANNEL="${CHANNEL:-}" |
| INTERVAL="${INTERVAL:-10}" |
| LOG_DIR="${LOG_DIR:-/var/log/openclaw}" |
| STATE_DIR="${STATE_DIR:-/var/lib/openclaw}" |
| LOG_FILE="$LOG_DIR/openclaw-auto-approve.log" |
| STATE_FILE="$STATE_DIR/auto-approve.approved" |
| PID_FILE="$STATE_DIR/auto-approve.pid" |
| DRY_RUN="${DRY_RUN:-0}" |
| PREFIX="${PREFIX:-}" |
| OPENCLAW_BIN="${OPENCLAW_BIN:-/usr/bin/openclaw}" |
|
|
| mkdir -p "$LOG_DIR" "$STATE_DIR" |
|
|
| |
| log() { |
| local level="$1"; shift |
| printf '%s [%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$level" "$*" | tee -a "$LOG_FILE" >&2 |
| } |
|
|
| run_oc() { |
| if [ -n "$PREFIX" ]; then |
| eval "$PREFIX $OPENCLAW_BIN $*" |
| else |
| "$OPENCLAW_BIN" "$@" |
| fi |
| } |
|
|
| |
| |
| parse_json_codes() { |
| local payload="$1" |
| if command -v jq >/dev/null 2>&1; then |
| printf '%s' "$payload" | jq -r '.requests[]?.code // empty' 2>/dev/null |
| elif command -v python3 >/dev/null 2>&1; then |
| printf '%s' "$payload" | python3 -c ' |
| import json, sys |
| try: |
| data = json.load(sys.stdin) |
| for r in data.get("requests", []): |
| if r.get("code"): |
| print(r["code"]) |
| except Exception: |
| pass |
| ' 2>/dev/null |
| else |
| log "ERROR" "需要 jq 或 python3 来解析 JSON,二者都不可用" |
| return 1 |
| fi |
| } |
|
|
| |
| |
| parse_json_channel_accounts() { |
| local payload="$1" |
| if command -v jq >/dev/null 2>&1; then |
| printf '%s' "$payload" | jq -r ' |
| .chat // {} | to_entries[]? as $ch |
| | select($ch.value.installed == true) |
| | select(($ch.value.accounts // []) | length > 0) |
| | $ch.value.accounts[] as $acc |
| | [$ch.key, $acc] | join(" ") |
| ' 2>/dev/null |
| elif command -v python3 >/dev/null 2>&1; then |
| printf '%s' "$payload" | python3 -c ' |
| import json, sys |
| try: |
| data = json.load(sys.stdin) |
| chat = data.get("chat", {}) |
| for ch_id, info in chat.items(): |
| if not info.get("installed"): |
| continue |
| accounts = info.get("accounts") or [] |
| for acc in accounts: |
| print(f"{ch_id} {acc}") |
| except Exception: |
| pass |
| ' 2>/dev/null |
| else |
| return 1 |
| fi |
| } |
|
|
| |
| acquire_lock() { |
| if [ -f "$PID_FILE" ]; then |
| local old_pid |
| old_pid=$(cat "$PID_FILE" 2>/dev/null || echo "") |
| if [ -n "$old_pid" ] && kill -0 "$old_pid" 2>/dev/null; then |
| log "ERROR" "已有实例在运行 (PID: $old_pid),退出" |
| exit 1 |
| fi |
| log "WARN" "残留 PID 文件 (PID: $old_pid 不存在),清理" |
| rm -f "$PID_FILE" |
| fi |
| echo $$ > "$PID_FILE" |
| } |
|
|
| release_lock() { |
| rm -f "$PID_FILE" |
| } |
|
|
| shutdown() { |
| log "INFO" "收到终止信号,关闭..." |
| release_lock |
| exit 0 |
| } |
| trap shutdown SIGTERM SIGINT SIGHUP |
|
|
| |
| |
| |
| state_key() { |
| local ch="$1" acc="$2" code="$3" |
| printf '%s|%s|%s' "$ch" "$acc" "$code" |
| } |
|
|
| is_approved() { |
| local key="$1" |
| |
| if grep -Fxq "$key" "$STATE_FILE" 2>/dev/null; then |
| return 0 |
| fi |
| |
| if [[ "$key" == "feishu|default|"* ]]; then |
| local bare_code="${key#feishu|default|}" |
| if grep -Fxq "$bare_code" "$STATE_FILE" 2>/dev/null; then |
| return 0 |
| fi |
| fi |
| return 1 |
| } |
|
|
| mark_approved() { |
| local key="$1" |
| printf '%s\n' "$key" >> "$STATE_FILE" |
| } |
|
|
| |
| main() { |
| acquire_lock |
|
|
| |
| touch "$STATE_FILE" |
| if [ -s "$STATE_FILE" ] && grep -q -v '|' "$STATE_FILE" 2>/dev/null; then |
| log "INFO" "检测到旧格式状态文件,迁移为 channel|account|code" |
| local migrated |
| migrated=$(awk -F'|' ' |
| NF==1 && $1!="" {print "feishu|default|"$1; next} |
| {print} |
| ' "$STATE_FILE") |
| printf '%s\n' "$migrated" > "$STATE_FILE" |
| fi |
|
|
| if [ -n "$CHANNEL" ]; then |
| log "INFO" "启动自动批准服务 (mode=锁定通道 channel=$CHANNEL interval=${INTERVAL}s dry_run=$DRY_RUN pid=$$)" |
| else |
| log "INFO" "启动自动批准服务 (mode=自动发现所有通道 interval=${INTERVAL}s dry_run=$DRY_RUN pid=$$)" |
| fi |
|
|
| while true; do |
| local tick_channels=() |
| local tick_accounts=() |
|
|
| |
| if [ -n "$CHANNEL" ]; then |
| |
| tick_channels=("$CHANNEL") |
| tick_accounts=("default") |
| else |
| |
| local channels_json |
| if ! channels_json=$(run_oc channels list --json 2>&1); then |
| log "ERROR" "channels list 失败: $channels_json" |
| sleep "$INTERVAL" |
| continue |
| fi |
| local pairs |
| pairs=$(parse_json_channel_accounts "$channels_json") |
| if [ -z "$pairs" ]; then |
| log "INFO" "当前没有已安装且有账户的通道" |
| sleep "$INTERVAL" |
| continue |
| fi |
| while IFS=' ' read -r ch acc; do |
| [ -z "$ch" ] && continue |
| tick_channels+=("$ch") |
| tick_accounts+=("$acc") |
| done <<< "$pairs" |
| fi |
|
|
| if [ ${#tick_channels[@]} -eq 0 ]; then |
| log "INFO" "无可扫描通道" |
| sleep "$INTERVAL" |
| continue |
| fi |
|
|
| local total_found=0 |
| local total_approved=0 |
| local total_skipped=0 |
| local total_failed=0 |
|
|
| |
| for idx in "${!tick_channels[@]}"; do |
| local ch="${tick_channels[$idx]}" |
| local acc="${tick_accounts[$idx]}" |
|
|
| |
| local list_json |
| if ! list_json=$(run_oc pairing list --channel "$ch" --account "$acc" --json 2>&1); then |
| |
| log "DEBUG" "跳过 $ch/$acc: $list_json" |
| continue |
| fi |
|
|
| local codes=() |
| while IFS= read -r c; do |
| [ -n "$c" ] && codes+=("$c") |
| done < <(parse_json_codes "$list_json") |
|
|
| if [ ${#codes[@]} -eq 0 ]; then |
| log "DEBUG" "$ch/$acc 无待批准" |
| continue |
| fi |
|
|
| log "INFO" "[$ch/$acc] 发现 ${#codes[@]} 个待批准请求" |
| total_found=$((total_found + ${#codes[@]})) |
|
|
| for code in "${codes[@]}"; do |
| local key |
| key=$(state_key "$ch" "$acc" "$code") |
|
|
| if is_approved "$key"; then |
| log "INFO" "[$ch/$acc] 跳过已批准过的 $code" |
| total_skipped=$((total_skipped + 1)) |
| continue |
| fi |
|
|
| log "INFO" "[$ch/$acc] 准备批准: $code" |
|
|
| if [ "$DRY_RUN" = "1" ]; then |
| log "INFO" "[$ch/$acc] [DRY-RUN] 不会真调用 approve" |
| continue |
| fi |
|
|
| local result |
| result=$(run_oc pairing approve "$ch" "$code" --account "$acc" --notify 2>&1) |
| local approve_rc=$? |
| echo "$result" >> "$LOG_FILE" |
|
|
| if [ $approve_rc -eq 0 ] || echo "$result" | grep -qi "approved"; then |
| mark_approved "$key" |
| total_approved=$((total_approved + 1)) |
| log "INFO" "✅ [$ch/$acc] 已批准并通知: $code" |
| else |
| total_failed=$((total_failed + 1)) |
| log "ERROR" "❌ [$ch/$acc] 批准失败 (rc=$approve_rc): $code" |
| fi |
| done |
| done |
|
|
| if [ $total_found -eq 0 ]; then |
| log "INFO" "本轮扫描 ${#tick_channels[@]} 个通道,无待批准请求" |
| else |
| log "INFO" "本轮汇总: 通道=${#tick_channels[@]} 发现=$total_found 新批准=$total_approved 跳过=$total_skipped 失败=$total_failed" |
| fi |
|
|
| sleep "$INTERVAL" |
| done |
| } |
|
|
| main |
|
|