Project Summary Notification (Issue & PR) #431
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: Project Summary Notification (Issue & PR) | |
| on: | |
| schedule: | |
| # UTC 时间对应北京时间 +8 | |
| - cron: '0 1 * * *' # 北京时间 09:00 | |
| - cron: '0 6 * * *' # 北京时间 14:00 | |
| - cron: '0 10 * * *' # 北京时间 18:00 | |
| workflow_dispatch: | |
| inputs: | |
| test_mode: | |
| description: '测试模式:汇总当天0点至今的活动' | |
| required: false | |
| type: boolean | |
| default: false | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| jobs: | |
| summary-report: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check Out Repository | |
| uses: actions/checkout@v4 | |
| - name: Calculate Time Windows | |
| id: time_calc | |
| run: | | |
| # 获取当前时间 | |
| CURRENT_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") | |
| echo "current_time=$CURRENT_TIME" >> $GITHUB_OUTPUT | |
| # 如果是手动触发的测试模式 | |
| if [ "${{ github.event_name }}" == "workflow_dispatch" ] && [ "${{ inputs.test_mode }}" == "true" ]; then | |
| # 当天0点 UTC | |
| START_TIME=$(date -u -d "00:00:00" +"%Y-%m-%dT%H:%M:%SZ") | |
| TIME_LABEL="今天汇总" | |
| else | |
| # 根据定时任务确定时间窗口 | |
| CURRENT_HOUR=$(date -u +"%H") | |
| case $CURRENT_HOUR in | |
| "01") # 对应北京时间 09:00 | |
| START_TIME=$(date -u -d "18:00:00 yesterday" +"%Y-%m-%dT%H:%M:%SZ") | |
| TIME_LABEL="昨夜汇总" | |
| ;; | |
| "06") # 对应北京时间 14:00 | |
| START_TIME=$(date -u -d "01:00:00" +"%Y-%m-%dT%H:%M:%SZ") | |
| TIME_LABEL="上午汇总" | |
| ;; | |
| "10") # 对应北京时间 18:00 | |
| START_TIME=$(date -u -d "06:00:00" +"%Y-%m-%dT%H:%M:%SZ") | |
| TIME_LABEL="下午汇总" | |
| ;; | |
| *) | |
| # 默认情况,用于手动触发非测试模式 | |
| START_TIME=$(date -u -d "6 hours ago" +"%Y-%m-%dT%H:%M:%SZ") | |
| TIME_LABEL="近期汇总" | |
| ;; | |
| esac | |
| fi | |
| echo "start_time=$START_TIME" >> $GITHUB_OUTPUT | |
| echo "time_label=$TIME_LABEL" >> $GITHUB_OUTPUT | |
| echo "Time window: $START_TIME to $CURRENT_TIME" | |
| - name: Fetch Activities (Issues & PRs) | |
| id: fetch_activities | |
| env: | |
| START_TIME: ${{ steps.time_calc.outputs.start_time }} | |
| END_TIME: ${{ steps.time_calc.outputs.current_time }} | |
| REPO: ${{ github.repository }} | |
| run: | | |
| sudo apt-get update && sudo apt-get install -y jq | |
| echo "开始获取活动数据..." | |
| # --------------------------------------------------- | |
| # 1. 获取 PR 数据 | |
| # --------------------------------------------------- | |
| echo "Fetching PRs..." | |
| PRS_JSON=$(curl -s -H "Authorization: token $GH_TOKEN" \ | |
| -H "Accept: application/vnd.github.v3+json" \ | |
| "https://api.github.com/repos/$REPO/pulls?state=all&sort=updated&direction=desc&per_page=30") | |
| PR_COUNT=0 | |
| PRS_ARRAY="[" | |
| # 检查 JSON 是否有效且非空 | |
| if [ -n "$PRS_JSON" ] && echo "$PRS_JSON" | jq -e 'length > 0' >/dev/null; then | |
| LEN=$(echo "$PRS_JSON" | jq 'length') | |
| # 使用 seq 生成序列时,确保 LEN > 0 | |
| if [ "$LEN" -gt 0 ]; then | |
| for i in $(seq 0 $(($LEN - 1))); do | |
| ITEM=$(echo "$PRS_JSON" | jq ".[$i]") | |
| CREATED_AT=$(echo "$ITEM" | jq -r '.created_at') | |
| CLOSED_AT=$(echo "$ITEM" | jq -r '.closed_at') | |
| MERGED_AT=$(echo "$ITEM" | jq -r '.merged_at') | |
| # 状态判定 | |
| STATUS="" | |
| # 使用 [[ ]] 进行字符串/时间比较更安全 | |
| if [[ "$MERGED_AT" != "null" && "$MERGED_AT" > "$START_TIME" ]]; then | |
| STATUS="🟣 合并" | |
| elif [[ "$CLOSED_AT" != "null" && "$CLOSED_AT" > "$START_TIME" ]]; then | |
| STATUS="🚫 关闭" | |
| elif [[ "$CREATED_AT" > "$START_TIME" ]]; then | |
| STATUS="🆕 新建" | |
| fi | |
| # 如果有状态变化,加入列表 | |
| if [ -n "$STATUS" ]; then | |
| NUMBER=$(echo "$ITEM" | jq -r '.number') | |
| TITLE=$(echo "$ITEM" | jq -r '.title' | sed 's/"/\\"/g') | |
| USER=$(echo "$ITEM" | jq -r '.user.login') | |
| if [ $PR_COUNT -gt 0 ]; then PRS_ARRAY="$PRS_ARRAY,"; fi | |
| PRS_ARRAY="$PRS_ARRAY{\"number\": $NUMBER, \"title\": \"$TITLE\", \"user\": \"$USER\", \"status\": \"$STATUS\"}" | |
| PR_COUNT=$((PR_COUNT + 1)) | |
| fi | |
| done | |
| fi | |
| fi | |
| PRS_ARRAY="$PRS_ARRAY]" | |
| # --------------------------------------------------- | |
| # 2. 获取 Issue 评论 (修正了 split 错误) | |
| # --------------------------------------------------- | |
| echo "Fetching Comments..." | |
| COMMENTS_JSON=$(curl -s -H "Authorization: token $GH_TOKEN" \ | |
| -H "Accept: application/vnd.github.v3+json" \ | |
| "https://api.github.com/repos/$REPO/issues/comments?since=$START_TIME&sort=created&direction=desc" | \ | |
| jq -c '.[] | select(.created_at >= "'$START_TIME'" and .created_at <= "'$END_TIME'")') | |
| COMMENT_COUNT=0 | |
| COMMENTS_ARRAY="[" | |
| if [ -n "$COMMENTS_JSON" ]; then | |
| while IFS= read -r comment; do | |
| if [ -n "$comment" ]; then | |
| ISSUE_URL=$(echo "$comment" | jq -r '.issue_url') | |
| # --- 修复点:使用 basename 提取编号 --- | |
| ISSUE_NUMBER=$(basename "$ISSUE_URL") | |
| ISSUE_TITLE=$(curl -s -H "Authorization: token $GH_TOKEN" "$ISSUE_URL" | jq -r '.title' | sed 's/"/\\"/g') | |
| AUTHOR=$(echo "$comment" | jq -r '.user.login') | |
| BODY=$(echo "$comment" | jq -r '.body' | sed 's/"/\\"/g' | tr '\n' ' ') | |
| PREVIEW=$(echo "$BODY" | cut -c 1-30) | |
| if [ $COMMENT_COUNT -gt 0 ]; then COMMENTS_ARRAY="$COMMENTS_ARRAY,"; fi | |
| COMMENTS_ARRAY="$COMMENTS_ARRAY{\"number\": $ISSUE_NUMBER, \"title\": \"$ISSUE_TITLE\", \"author\": \"$AUTHOR\", \"preview\": \"$PREVIEW\"}" | |
| COMMENT_COUNT=$((COMMENT_COUNT + 1)) | |
| fi | |
| done <<< "$COMMENTS_JSON" | |
| fi | |
| COMMENTS_ARRAY="$COMMENTS_ARRAY]" | |
| # --------------------------------------------------- | |
| # 3. 获取 Issue 事件 (标签/分配) | |
| # --------------------------------------------------- | |
| echo "Fetching Events..." | |
| EVENTS_JSON=$(curl -s -H "Authorization: token $GH_TOKEN" \ | |
| -H "Accept: application/vnd.github.v3+json" \ | |
| "https://api.github.com/repos/$REPO/issues/events?since=$START_TIME" | \ | |
| jq -c '.[] | select(.created_at >= "'$START_TIME'" and .created_at <= "'$END_TIME'")') | |
| LABEL_COUNT=0 | |
| LABELS_ARRAY="[" | |
| ASSIGN_COUNT=0 | |
| ASSIGNMENTS_ARRAY="[" | |
| if [ -n "$EVENTS_JSON" ]; then | |
| while IFS= read -r event; do | |
| if [ -n "$event" ]; then | |
| EVENT_TYPE=$(echo "$event" | jq -r '.event') | |
| ISSUE_NUMBER=$(echo "$event" | jq -r '.issue.number') | |
| ISSUE_TITLE=$(echo "$event" | jq -r '.issue.title' | sed 's/"/\\"/g') | |
| case "$EVENT_TYPE" in | |
| "labeled") | |
| LABEL_NAME=$(echo "$event" | jq -r '.label.name') | |
| if [ $LABEL_COUNT -gt 0 ]; then LABELS_ARRAY="$LABELS_ARRAY,"; fi | |
| LABELS_ARRAY="$LABELS_ARRAY{\"number\": $ISSUE_NUMBER, \"title\": \"$ISSUE_TITLE\", \"change\": \"添加了$LABEL_NAME\"}" | |
| LABEL_COUNT=$((LABEL_COUNT + 1)) | |
| ;; | |
| "unlabeled") | |
| LABEL_NAME=$(echo "$event" | jq -r '.label.name') | |
| if [ $LABEL_COUNT -gt 0 ]; then LABELS_ARRAY="$LABELS_ARRAY,"; fi | |
| LABELS_ARRAY="$LABELS_ARRAY{\"number\": $ISSUE_NUMBER, \"title\": \"$ISSUE_TITLE\", \"change\": \"移除了$LABEL_NAME\"}" | |
| LABEL_COUNT=$((LABEL_COUNT + 1)) | |
| ;; | |
| "assigned") | |
| ASSIGNEE=$(echo "$event" | jq -r '.assignee.login') | |
| if [ $ASSIGN_COUNT -gt 0 ]; then ASSIGNMENTS_ARRAY="$ASSIGNMENTS_ARRAY,"; fi | |
| ASSIGNMENTS_ARRAY="$ASSIGNMENTS_ARRAY{\"number\": $ISSUE_NUMBER, \"title\": \"$ISSUE_TITLE\", \"assignee\": \"$ASSIGNEE\"}" | |
| ASSIGN_COUNT=$((ASSIGN_COUNT + 1)) | |
| ;; | |
| esac | |
| fi | |
| done <<< "$EVENTS_JSON" | |
| fi | |
| LABELS_ARRAY="$LABELS_ARRAY]" | |
| ASSIGNMENTS_ARRAY="$ASSIGNMENTS_ARRAY]" | |
| # --------------------------------------------------- | |
| # 4. 汇总输出 | |
| # --------------------------------------------------- | |
| cat > summary_data.json << EOF | |
| { | |
| "prs": $PRS_ARRAY, | |
| "comments": $COMMENTS_ARRAY, | |
| "labels": $LABELS_ARRAY, | |
| "assignments": $ASSIGNMENTS_ARRAY | |
| } | |
| EOF | |
| TOTAL_COUNT=$((PR_COUNT + COMMENT_COUNT + LABEL_COUNT + ASSIGN_COUNT)) | |
| echo "pr_count=$PR_COUNT" >> $GITHUB_OUTPUT | |
| echo "comment_count=$COMMENT_COUNT" >> $GITHUB_OUTPUT | |
| echo "label_count=$LABEL_COUNT" >> $GITHUB_OUTPUT | |
| echo "assign_count=$ASSIGN_COUNT" >> $GITHUB_OUTPUT | |
| echo "total_count=$TOTAL_COUNT" >> $GITHUB_OUTPUT | |
| echo "数据统计: PR=$PR_COUNT, Comment=$COMMENT_COUNT, Label=$LABEL_COUNT" | |
| - name: Generate Summary Message | |
| id: generate_message | |
| env: | |
| TIME_LABEL: ${{ steps.time_calc.outputs.time_label }} | |
| PR_COUNT: ${{ steps.fetch_activities.outputs.pr_count }} | |
| COMMENT_COUNT: ${{ steps.fetch_activities.outputs.comment_count }} | |
| LABEL_COUNT: ${{ steps.fetch_activities.outputs.label_count }} | |
| ASSIGN_COUNT: ${{ steps.fetch_activities.outputs.assign_count }} | |
| TOTAL_COUNT: ${{ steps.fetch_activities.outputs.total_count }} | |
| run: | | |
| # 清理数据 | |
| CLEANED_JSON=$(cat summary_data.json | tr -d '\000-\031') | |
| SUMMARY_DATA=$(echo "$CLEANED_JSON") | |
| MESSAGE="📊 [${{ github.repository }}] 项目动态 ($TIME_LABEL)" | |
| # --- 1. 处理 PR (新增) --- | |
| if [ "$PR_COUNT" -gt 0 ]; then | |
| MESSAGE="$MESSAGE"$'\n'"🔀 PR 变更 ($PR_COUNT)" | |
| # 循环读取 | |
| LEN=$(echo "$SUMMARY_DATA" | jq '.prs | length') | |
| LIMIT=5 # PR 比较重要,多显示几条 | |
| for i in $(seq 0 $((LEN-1))); do | |
| if [ "$i" -ge "$LIMIT" ]; then | |
| MESSAGE="$MESSAGE"$'\n'" ...等 $((PR_COUNT-LIMIT)) 个 PR" | |
| break | |
| fi | |
| ITEM=$(echo "$SUMMARY_DATA" | jq -r ".prs[$i]") | |
| NUM=$(echo "$ITEM" | jq -r '.number') | |
| TITLE=$(echo "$ITEM" | jq -r '.title') | |
| STATUS=$(echo "$ITEM" | jq -r '.status') | |
| USER=$(echo "$ITEM" | jq -r '.user') | |
| MESSAGE="$MESSAGE"$'\n'" - $STATUS [#$NUM] $TITLE ($USER)" | |
| done | |
| fi | |
| # --- 2. 处理评论 --- | |
| if [ "$COMMENT_COUNT" -gt 0 ]; then | |
| MESSAGE="$MESSAGE"$'\n'"💬 新评论 ($COMMENT_COUNT)" | |
| LEN=$(echo "$SUMMARY_DATA" | jq '.comments | length') | |
| LIMIT=5 | |
| for i in $(seq 0 $((LEN-1))); do | |
| if [ "$i" -ge "$LIMIT" ]; then | |
| MESSAGE="$MESSAGE"$'\n'" ...等 $((COMMENT_COUNT-LIMIT)) 条评论" | |
| break | |
| fi | |
| ITEM=$(echo "$SUMMARY_DATA" | jq -r ".comments[$i]") | |
| NUM=$(echo "$ITEM" | jq -r '.number') | |
| TITLE=$(echo "$ITEM" | jq -r '.title') | |
| AUTHOR=$(echo "$ITEM" | jq -r '.author') | |
| PREVIEW=$(echo "$ITEM" | jq -r '.preview') | |
| MESSAGE="$MESSAGE"$'\n'" - [#$NUM] $TITLE:$AUTHOR:$PREVIEW..." | |
| done | |
| fi | |
| # --- 3. 处理标签 --- | |
| # 标签区块已禁用 | |
| # if [ "$LABEL_COUNT" -gt 0 ]; then | |
| # MESSAGE="$MESSAGE"$'\n'"🏷️ 标签 ($LABEL_COUNT)" | |
| # LEN=$(echo "$SUMMARY_DATA" | jq '.labels | length') | |
| # LIMIT=3 | |
| # for i in $(seq 0 $((LEN-1))); do | |
| # if [ "$i" -ge "$LIMIT" ]; then break; fi | |
| # ITEM=$(echo "$SUMMARY_DATA" | jq -r ".labels[$i]") | |
| # NUM=$(echo "$ITEM" | jq -r '.number') | |
| # CHANGE=$(echo "$ITEM" | jq -r '.change') | |
| # MESSAGE="$MESSAGE"$'\n'" - [#$NUM] $CHANGE" | |
| # done | |
| # fi | |
| # --- 4. 处理分配 --- | |
| if [ "$ASSIGN_COUNT" -gt 0 ]; then | |
| MESSAGE="$MESSAGE"$'\n'"👤 分配 ($ASSIGN_COUNT)" | |
| LEN=$(echo "$SUMMARY_DATA" | jq '.assignments | length') | |
| LIMIT=3 | |
| for i in $(seq 0 $((LEN-1))); do | |
| if [ "$i" -ge "$LIMIT" ]; then break; fi | |
| ITEM=$(echo "$SUMMARY_DATA" | jq -r ".assignments[$i]") | |
| NUM=$(echo "$ITEM" | jq -r '.number') | |
| ASSIGNEE=$(echo "$ITEM" | jq -r '.assignee') | |
| MESSAGE="$MESSAGE"$'\n'" - [#$NUM] -> $ASSIGNEE" | |
| done | |
| fi | |
| if [ "$TOTAL_COUNT" -eq 0 ]; then | |
| MESSAGE="$MESSAGE"$'\n'"💤 本时段无动态" | |
| fi | |
| # 输出 | |
| { | |
| echo "message<<EOF" | |
| echo -e "$MESSAGE" | |
| echo "EOF" | |
| } >> $GITHUB_OUTPUT | |
| - name: Send QQ Notification | |
| if: steps.fetch_activities.outputs.total_count != '0' | |
| uses: Y2Nk4/qmsg-action@master | |
| with: | |
| # qq: ${{ needs.meta.outputs.version_type == 'ci' && secrets.QMSG_QQ_DEV || secrets.QMSG_QQ_PUB }} | |
| # groups: ${{ needs.meta.outputs.version_type == 'ci' && secrets.QMSG_GROUPS_DEV || secrets.QMSG_GROUPS_PUB }} | |
| groups: ${{ secrets.QMSG_GROUPS_PUB }} | |
| key: ${{ secrets.QMSG_KEY }} | |
| message: ${{ steps.generate_message.outputs.message }} |