191 lines
5.4 KiB
Bash
191 lines
5.4 KiB
Bash
#!/bin/sh
|
|
set -eu
|
|
|
|
project_root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
|
config_file="$project_root/.dingtalk/config.env"
|
|
|
|
usage() {
|
|
echo "Usage: ./scripts/dingtalk-progress <start|milestone|blocked|complete|failed> <summary>" >&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"
|