#!/bin/sh set -eu project_root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) config_file="$project_root/.dingtalk/config.env" usage() { echo "Usage: ./scripts/dingtalk-progress " >&2 } if [ "$#" -lt 2 ]; then usage exit 2 fi event_name=$1 shift summary=$* case "$event_name" in start|milestone|blocked|complete|failed) ;; *) usage exit 2 ;; esac if [ ! -f "$config_file" ]; then echo "DingTalk progress reporting is not configured; skipping." >&2 exit 0 fi file_mode=$(stat -f '%Lp' "$config_file" 2>/dev/null || stat -c '%a' "$config_file" 2>/dev/null || true) case "$file_mode" in 400|600) ;; *) echo "Refusing to read $config_file because its mode is ${file_mode:-unknown}; run chmod 600 on it." >&2 exit 2 ;; esac while IFS= read -r line || [ -n "$line" ]; do case "$line" in ''|'#'*) continue ;; esac key=${line%%=*} value=${line#*=} case "$key" in DINGTALK_PROGRESS_ENABLED|DINGTALK_PROGRESS_DRY_RUN|DINGTALK_PROGRESS_MODE|DINGTALK_PROGRESS_CLIENT_ID|DINGTALK_PROGRESS_TARGET|DINGTALK_PROGRESS_ROBOT_CODE|DINGTALK_PROGRESS_KEYCHAIN_SERVICE|DINGTALK_PROGRESS_PROJECT|DINGTALK_PROGRESS_TITLE_PREFIX) export "$key=$value" ;; *) echo "Unknown key in $config_file: $key" >&2 exit 2 ;; esac done < "$config_file" if [ "${DINGTALK_PROGRESS_ENABLED:-0}" != "1" ]; then echo "DingTalk progress reporting is disabled; skipping." >&2 exit 0 fi if [ "${DINGTALK_PROGRESS_MODE:-app-bot}" != "app-bot" ]; then echo "DINGTALK_PROGRESS_MODE must be app-bot." >&2 exit 2 fi if [ -z "${DINGTALK_PROGRESS_CLIENT_ID:-}" ] || [ -z "${DINGTALK_PROGRESS_TARGET:-}" ] || [ -z "${DINGTALK_PROGRESS_ROBOT_CODE:-}" ]; then echo "App-bot mode requires DINGTALK_PROGRESS_CLIENT_ID, DINGTALK_PROGRESS_TARGET, and DINGTALK_PROGRESS_ROBOT_CODE." >&2 exit 2 fi if ! command -v jq >/dev/null 2>&1; then echo "jq is required to construct and validate the DingTalk OpenAPI request." >&2 exit 127 fi if ! command -v dws >/dev/null 2>&1; then echo "Global dws command was not found in PATH." >&2 exit 127 fi dws=$(command -v dws) if [ -n "${DING_SEC:-}" ]; then client_secret=$DING_SEC else if [ ! -x /usr/bin/security ]; then echo "DING_SEC is unset and macOS Keychain command /usr/bin/security is unavailable." >&2 exit 127 fi keychain_service=${DINGTALK_PROGRESS_KEYCHAIN_SERVICE:-com.eapil.dingtalk.$(basename -- "$project_root")} if ! client_secret=$(/usr/bin/security find-generic-password \ -a "$DINGTALK_PROGRESS_CLIENT_ID" \ -s "$keychain_service" \ -w 2>/dev/null); then echo "DingTalk Client Secret was not found in DING_SEC or this project's macOS Keychain item." >&2 echo "See .dingtalk/README.md for credential setup." >&2 exit 2 fi fi if [ -z "$client_secret" ]; then echo "The DingTalk Client Secret is empty." >&2 exit 2 fi case "$event_name" in start) event_label="开始" ;; milestone) event_label="里程碑" ;; blocked) event_label="阻塞" ;; complete) event_label="完成" ;; failed) event_label="失败" ;; esac project_name=${DINGTALK_PROGRESS_PROJECT:-$(basename -- "$project_root")} title_prefix=${DINGTALK_PROGRESS_TITLE_PREFIX:-[Agent进度]} title="$title_prefix $event_label" timestamp=$(date '+%Y-%m-%d %H:%M:%S %Z') body=$(printf '## %s\n\n- 项目:%s\n- 状态:%s\n- 时间:%s\n\n%s' \ "$title" "$project_name" "$event_label" "$timestamp" "$summary") msg_param=$(jq -cn --arg title "$title" --arg text "$body" '{title:$title,text:$text}') request_body=$(jq -cn \ --arg msgParam "$msg_param" \ --arg openConversationId "$DINGTALK_PROGRESS_TARGET" \ --arg robotCode "$DINGTALK_PROGRESS_ROBOT_CODE" \ '{msgParam:$msgParam,msgKey:"sampleMarkdown",openConversationId:$openConversationId,robotCode:$robotCode}') if [ "${DINGTALK_PROGRESS_DRY_RUN:-0}" = "1" ]; then if DWS_CLIENT_ID="$DINGTALK_PROGRESS_CLIENT_ID" \ DWS_CLIENT_SECRET="$client_secret" \ "$dws" api POST /v1.0/robot/groupMessages/send \ --data "$request_body" --dry-run --yes --format json; then send_status=0 else send_status=$? fi unset client_secret DWS_CLIENT_SECRET exit "$send_status" fi max_send_attempts=3 retry_delay_seconds=10 attempt=1 final_status=1 while [ "$attempt" -le "$max_send_attempts" ]; do response= if response=$(DWS_CLIENT_ID="$DINGTALK_PROGRESS_CLIENT_ID" \ DWS_CLIENT_SECRET="$client_secret" \ "$dws" api POST /v1.0/robot/groupMessages/send \ --data "$request_body" --yes --format json); then send_status=0 else send_status=$? fi if [ -n "$response" ]; then printf '%s\n' "$response" fi if [ "$send_status" -eq 0 ] && printf '%s\n' "$response" | jq -e '.processQueryKey | type == "string" and length > 0' >/dev/null 2>&1; then unset client_secret DWS_CLIENT_SECRET exit 0 fi if [ "$send_status" -eq 0 ]; then echo "DingTalk attempt $attempt returned no valid processQueryKey; service acceptance is unverified." >&2 final_status=1 else echo "DingTalk attempt $attempt failed with status $send_status." >&2 final_status=$send_status fi if [ "$attempt" -lt "$max_send_attempts" ]; then echo "Retrying DingTalk send in $retry_delay_seconds seconds." >&2 sleep "$retry_delay_seconds" fi attempt=$((attempt + 1)) done unset client_secret DWS_CLIENT_SECRET echo "DingTalk progress reporting failed after $max_send_attempts attempts; Etunel work should continue." >&2 exit "$final_status"