fix: add feixiaohao #18
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: PR Merge Notifications | |
| on: | |
| pull_request_target: | |
| types: [closed] # PR 关闭时触发(下方判断 merged=true 才发送) | |
| push: | |
| branches: [main] # 本地合并或直接提交推到 main | |
| workflow_dispatch: {} # 手动测试 | |
| jobs: | |
| notify: | |
| runs-on: ubuntu-latest | |
| if: | | |
| github.event_name == 'workflow_dispatch' || | |
| (github.event_name == 'pull_request' && github.event.pull_request.merged == true) || | |
| (github.event_name == 'push' && github.ref == 'refs/heads/main') | |
| steps: | |
| - name: Install jq | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y jq | |
| # push 事件需要完整历史用于判别是否为“合并提交” | |
| - name: "Checkout (push only)" | |
| if: github.event_name == 'push' | |
| uses: actions/checkout@v5 | |
| with: | |
| fetch-depth: 0 | |
| - name: "Detect merge-like push (push only)" | |
| id: detect_merge | |
| if: github.event_name == 'push' | |
| env: | |
| BEFORE: ${{ github.event.before }} | |
| AFTER: ${{ github.event.after }} | |
| run: | | |
| set -e | |
| # 1) 多父提交 => 合并提交 | |
| PARENTS_COUNT=$(git rev-list --parents -n 1 "${GITHUB_SHA}" | wc -w) | |
| IS_MERGE=false | |
| if [ "$PARENTS_COUNT" -ge 3 ]; then IS_MERGE=true; fi | |
| # 2) 提交标题以 "Merge" 开头 => 视为合并 | |
| SUBJECT=$(git log -1 --pretty=%s) | |
| if printf '%s' "$SUBJECT" | grep -qE '^Merge'; then IS_MERGE=true; fi | |
| # 3) 本次 push 含多提交(FF 合并常见) => 视为合并 | |
| RANGE_COUNT=$(git rev-list --count "${BEFORE}..${AFTER}" || echo 1) | |
| if [ "$RANGE_COUNT" -ge 2 ]; then IS_MERGE=true; fi | |
| echo "is_merge=${IS_MERGE}" >> "$GITHUB_OUTPUT" | |
| - name: "Detect PR association (push only)" | |
| id: detect_pr | |
| if: github.event_name == 'push' | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN || github.token }} | |
| REPO: ${{ github.repository }} | |
| AFTER: ${{ github.event.after }} | |
| HEAD_MSG: ${{ github.event.head_commit.message }} | |
| run: | | |
| set -e | |
| owner="${REPO%%/*}" | |
| repo="${REPO##*/}" | |
| # 查询该提交是否与 PR 关联(官方接口) | |
| prs_json=$(curl -sS -H "Authorization: Bearer $GITHUB_TOKEN" \ | |
| -H "Accept: application/vnd.github+json" \ | |
| "https://api.github.com/repos/${owner}/${repo}/commits/${AFTER}/pulls") | |
| count=$(jq 'length' <<< "$prs_json") | |
| HAS_PR=false | |
| if [ "$count" -ge 1 ]; then HAS_PR=true; fi | |
| # 额外:根据常见提交信息模式判断(squash / merge) | |
| MSG="${HEAD_MSG:-}" | |
| if printf '%s' "$MSG" | grep -qE '^Merge pull request #[0-9]+'; then HAS_PR=true; fi | |
| if printf '%s' "$MSG" | grep -qE '\(#([0-9]+)\)$'; then HAS_PR=true; fi | |
| echo "has_pr=${HAS_PR}" >> "$GITHUB_OUTPUT" | |
| - name: "Check secrets" | |
| env: | |
| LARK_WEBHOOK_URL: ${{ secrets.LARK_WEBHOOK_URL }} | |
| LARK_SIGNING_SECRET: ${{ secrets.LARK_SIGNING_SECRET }} | |
| TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} | |
| TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }} | |
| run: | | |
| set -e | |
| for v in LARK_WEBHOOK_URL LARK_SIGNING_SECRET TELEGRAM_BOT_TOKEN TELEGRAM_CHAT_ID; do | |
| if [ -z "${!v}" ]; then | |
| echo "::error title=Missing secret::$v is empty or not visible to this repo" | |
| exit 1 | |
| fi | |
| done | |
| - name: "Compose message" | |
| id: compose | |
| env: | |
| EVENT: ${{ github.event_name }} | |
| REPO: ${{ github.repository }} | |
| # PR 事件字段 | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| PR_TITLE: ${{ github.event.pull_request.title }} | |
| PR_URL: ${{ github.event.pull_request.html_url }} | |
| AUTHOR: ${{ github.event.pull_request.user.login }} | |
| HEAD_REF: ${{ github.event.pull_request.head.ref }} | |
| BASE_REF: ${{ github.event.pull_request.base.ref }} | |
| # push 事件字段 | |
| REF_NAME: ${{ github.ref_name }} | |
| ACTOR: ${{ github.actor }} | |
| BEFORE: ${{ github.event.before }} | |
| AFTER: ${{ github.event.after }} | |
| IS_MERGE: ${{ steps.detect_merge.outputs.is_merge }} | |
| HAS_PR: ${{ steps.detect_pr.outputs.has_pr }} | |
| run: | | |
| set -e | |
| repo_short=$(printf "%s" "$REPO" | awk -F/ '{print $2}') | |
| should_send=false | |
| if [ "$EVENT" = "pull_request" ]; then | |
| # 只在已合并的 PR 场景 | |
| SAFE_TITLE=$(printf "%s" "$PR_TITLE" | tr '\n' ' ') | |
| TEXT=$(printf '✅ 已合并\n标题:%s\n仓库:%s\n提交者:%s\n分支:%s → %s\n记录:%s' \ | |
| "$SAFE_TITLE" "$repo_short" "$AUTHOR" "$HEAD_REF" "$BASE_REF" "$PR_URL") | |
| should_send=true | |
| elif [ "$EVENT" = "push" ]; then | |
| # 如果与 PR 关联,则认为 PR 事件会/已发送,避免重复 | |
| if [ "$HAS_PR" = "true" ]; then | |
| echo "Skip push: commit associated with a PR -> 去重" | |
| TEXT="" | |
| should_send=false | |
| else | |
| COMPARE_URL="https://github.com/${REPO}/compare/${BEFORE}...${AFTER}" | |
| if [ "$IS_MERGE" = "true" ]; then | |
| TEXT=$(printf '✅ 已合并到 %s\n仓库:%s\n提交者:%s\n范围:%s...%s\n记录:%s' \ | |
| "$REF_NAME" "$repo_short" "$ACTOR" "$BEFORE" "$AFTER" "$COMPARE_URL") | |
| else | |
| TEXT=$(printf '✅ 已更新 %s\n仓库:%s\n提交者:%s\n范围:%s...%s\n记录:%s' \ | |
| "$REF_NAME" "$repo_short" "$ACTOR" "$BEFORE" "$AFTER" "$COMPARE_URL") | |
| fi | |
| should_send=true | |
| fi | |
| else | |
| TEXT="✅ 连通性测试:这是一条手动触发的测试消息。" | |
| should_send=true | |
| fi | |
| echo "text<<EOF" >> "$GITHUB_OUTPUT" | |
| echo "$TEXT" >> "$GITHUB_OUTPUT" | |
| echo "EOF" >> "$GITHUB_OUTPUT" | |
| echo "should_send=$should_send" >> "$GITHUB_OUTPUT" | |
| - name: "Send to channel A" | |
| if: steps.compose.outputs.should_send == 'true' | |
| env: | |
| LARK_WEBHOOK_URL: ${{ secrets.LARK_WEBHOOK_URL }} | |
| LARK_SIGNING_SECRET: ${{ secrets.LARK_SIGNING_SECRET }} | |
| TEXT: ${{ steps.compose.outputs.text }} | |
| run: | | |
| set -e | |
| ts=$(date +%s) | |
| key=$(printf '%s\n%s' "$ts" "$LARK_SIGNING_SECRET") | |
| sign=$(printf '' | openssl dgst -sha256 -hmac "$key" -binary | base64 | tr -d '\n') | |
| payload=$(jq -n --arg ts "$ts" --arg sign "$sign" --arg text "$TEXT" \ | |
| '{timestamp:$ts, sign:$sign, msg_type:"text", content:{text:$text}}') | |
| resp=$(curl -sS -w '\n%{http_code}' -H 'Content-Type: application/json' -d "$payload" "$LARK_WEBHOOK_URL") | |
| http=$(tail -n1 <<< "$resp") | |
| body=$(head -n-1 <<< "$resp") | |
| code=$(jq -r '.code // .StatusCode // .status_code // -1' <<< "$body") | |
| msg=$(jq -r '.msg // .StatusMessage // .msg // empty' <<< "$body") | |
| echo "ChannelA HTTP: $http" | |
| echo "ChannelA Body: $body" | |
| if [ "$http" != "200" ] || [ "$code" != "0" ]; then | |
| echo "::error title=ChannelA push failed::HTTP=$http code=$code msg=$msg" | |
| exit 1 | |
| fi | |
| - name: "Send to channel B" | |
| if: steps.compose.outputs.should_send == 'true' | |
| env: | |
| TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} | |
| TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }} | |
| TEXT: ${{ steps.compose.outputs.text }} | |
| run: | | |
| set -e | |
| api="https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" | |
| resp=$(curl -sS -w '\n%{http_code}' -X POST \ | |
| --data-urlencode "chat_id=${TELEGRAM_CHAT_ID}" \ | |
| --data-urlencode "text=${TEXT}" \ | |
| --data-urlencode "disable_web_page_preview=true" \ | |
| "$api") | |
| http=$(tail -n1 <<< "$resp") | |
| body=$(head -n-1 <<< "$resp") | |
| ok=$(jq -r '.ok // false' <<< "$body") | |
| echo "ChannelB HTTP: $http" | |
| echo "ChannelB Body: $body" | |
| if [ "$http" != "200" ] || [ "$ok" != "true" ]; then | |
| echo "::error title=ChannelB push failed::HTTP=$http body=$body" | |
| exit 1 | |
| fi | |
| - name: "Done" | |
| if: steps.compose.outputs.should_send == 'true' | |
| run: echo "✅ Notifications sent" |