一个自动监控 GitHub 仓库更新的脚本

背景与需求

在快节奏的开发生态中,及时追踪项目更新是一大痛点。本文将介绍一个轻量但强大的 Bash 脚本,它能自动检查多个 GitHub 仓库的提交,并利用 ZeroClaw AI 生成中文更新摘要。

作为开发者或技术管理者,你可能同时关注多个开源项目。手动刷 GitHub 不仅耗时,还容易遗漏重要更新。理想的监控工具应具备:

  • 多仓库支持:一次配置,批量监控
  • 增量检查:避免重复处理旧提交
  • 智能总结:自动提炼提交信息的关键点
  • 轻量部署:无需复杂依赖,适合服务器定时任务

scripts/github_monitor.sh 正是针对这些需求设计的解决方案。

脚本核心功能

仓库列表配置

脚本使用数组定义要监控的仓库:

REPOS=(
    "xxx/yyy"
    "uuu/vvv"
)

你可以自由增删,脚本会按顺序检查每个仓库。

状态持久化

脚本在同级目录维护一个 JSON 状态文件 .github_repos.json,记录每个仓库的最后一次检查到的 commit SHA:

{
  "xxx/yyy": "...",
  "uuu/vvv": "..."
}

这种设计实现增量监控:每次运行只拉取自上次检查以来的新提交。

GitHub API 调用

脚本使用 curl 直接调用 GitHub API:

  • 无 Token 模式:适合公开仓库,但受限于每小时 60 次请求限制
  • Token 模式:通过环境变量 GITHUB_TOKEN 传递,大幅提升限额(公开仓库每小时 5000 次)

API 调用包含超时参数,避免网络阻塞:

curl -s --connect-timeout 10 --max-time 30 ...

时间戳增量策略

脚本的核心逻辑在 fetch_commits() 函数:

  1. 读取上次记录的 SHA
  2. 若为空(首次运行),只获取最新一条提交
  3. 若有上次 SHA,则获取该提交的时间戳,使用 since 参数拉取之后的新提交
  4. 过滤掉可能被重复包含的 last_sha 自身
  5. 更新状态文件为最新 SHA

这种策略在大多数情况下只需一次 API 调用,效率较高。

AI 智能总结

脚本最特别的功能是调用 ZeroClaw 生成中文更新摘要:

local prompt="
你是一个直接输出结果的工具。严禁输出任何思考、分析或解释过程。
严格限定于提交信息本身进行合理推断。严格遵循以下格式生成总结。
只输出最终总结,不输出任何冗余内容。输出语言使用中文。输出格式:

## 主要更新:
## 核心变更:
## 影响:

提交信息如下:
$commits_log
"
local summary_text=$(zeroclaw agent -m "$prompt" 2>/dev/null)

ZeroClaw 被限制在 60 秒内完成响应,并强制输出简洁的三段式总结。如果超时或失败,脚本会输出备用提示。

技术实现细节

依赖管理

脚本依赖两个命令行工具:

  • jq:JSON 解析,用于读取状态文件和 API 响应
  • curl:HTTP 请求,访问 GitHub API

启动时会检查是否已安装,缺失则报错退出。

错误处理

脚本在多个环节进行防御性编程:

  • API 返回非数组时提示错误并输出响应样例
  • 无法解析最新 SHA 时跳过该仓库
  • jq 更新状态文件失败时清理临时文件
  • AI 调用超时或失败时降级为提示信息

超时控制

  • curl:连接超时 10 秒,总时长不超过 30 秒
  • zeroclaw agent:60 秒硬超时(timeout 60s

这些参数可根据网络环境和 AI 响应速度调整。

部署与使用

安装依赖

# Ubuntu/Debian
apt-get install jq curl

# macOS
brew install jq curl

可选:配置 GitHub Token

为避免 API 限流,建议设置 GITHUB_TOKEN

export GITHUB_TOKEN="ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# 可写入 ~/.bashrc 或系统服务环境变量

Token 只需 public_repo 权限即可。

运行脚本

bash scripts/github_monitor.sh

首次运行会创建状态文件并获取最新提交。

定时任务

配合 cron 实现自动监控。例如每天 UTC 08:00 运行并发送到 QQ:

0 8 * * * bash /root/.zeroclaw/workspace/scripts/github_monitor.sh

在 ZeroClaw 环境中,可使用 cron_add 工具将输出直接投递到 QQ 频道。

优化与扩展建议

解决超时问题

当前脚本在无 Token 或 ZeroClaw 响应慢时可能超时。可考虑:

  • 配置 Token:最直接有效的方案
  • 减少仓库数量:串行处理累积时间,可拆分多个定时任务
  • 异步并行:使用 & 后台执行或 GNU Parallel
  • 缓存 AI 响应:对相同提交内容复用总结

增强总结质量

  • 调整 prompt 模板,指定更详细的输出结构
  • 传入更多上下文(如文件变更行数、语言统计)
  • 支持多语言输出(英文/中文)

输出多样化

  • 将结果写入 Markdown 文件,生成静态更新日志页
  • 集成到 Discord/Telegram/Slack Webhook
  • 发送邮件通知

总结

完整的脚本如下:

#!/bin/bash

# ======================== 配置区 ========================
REPOS=(
    "xxx/yyy"
    "uuu/vvv"
)
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)
STATE_FILE="$SCRIPT_DIR/.github_repos.json"
# 请通过环境变量设置 GITHUB_TOKEN,例如:export GITHUB_TOKEN="ghp_xxx"
# export GITHUB_TOKEN=""

# API 每页最大条数(增量模式下一般一次足够)
PER_PAGE=50

# ======================== 初始化 ========================
if [ ! -f "$STATE_FILE" ]; then
    echo "{}" > "$STATE_FILE"
fi

# ======================== 依赖检查 ========================
for cmd in jq curl; do
    if ! command -v $cmd &> /dev/null; then
        echo "错误:请先安装 $cmd"
        exit 1
    fi
done

# ======================== 辅助函数:获取提交时间 ========================
get_commit_date() {
    local repo=$1
    local sha=$2
    local auth_header=()
    if [ -n "$GITHUB_TOKEN" ]; then
        auth_header=(-H "Authorization: token $GITHUB_TOKEN")
    fi
    local url="https://api.github.com/repos/$repo/commits/$sha"
    curl -s --connect-timeout 10 --max-time 30 "${auth_header[@]}" "$url" | jq -r '.commit.committer.date // empty'
}

# ======================== 核心函数:增量获取新提交 ========================
fetch_commits() {
    local repo=$1
    # 安全读取上次记录的 SHA
    local last_sha=$(jq -r --arg repo "$repo" '.[$repo] // ""' "$STATE_FILE")

    echo "----"
    echo "# 正在检查仓库: $repo "

    local auth_header=()
    if [ -n "$GITHUB_TOKEN" ]; then
        auth_header=(-H "Authorization: token $GITHUB_TOKEN")
    fi

    local api_base="https://api.github.com/repos/$repo/commits"
    local since_param=""
    local commits_json=""

    if [ -z "$last_sha" ]; then
        # 首次运行:只取最新一条提交
        echo "首次运行,获取最新提交..."
        commits_json=$(curl -s --connect-timeout 10 --max-time 30 "${auth_header[@]}" "${api_base}?per_page=1")
    else
        # 获取上次提交的时间戳
        local last_date=$(get_commit_date "$repo" "$last_sha")
        if [ -z "$last_date" ]; then
            echo "警告:无法获取上次提交 ($last_sha) 的时间戳,将退回到拉取最近 $PER_PAGE 条提交"
            commits_json=$(curl -s --connect-timeout 10 --max-time 30 "${auth_header[@]}" "${api_base}?per_page=$PER_PAGE")
        else
            # URL 编码时间(ISO 8601 格式,直接将空格替换为 %20)
            local encoded_date=$(echo "$last_date" | sed 's/ /%20/g')
            since_param="?since=${encoded_date}&per_page=$PER_PAGE"
            local local_date=$(TZ='Asia/Shanghai' date -d "$last_date" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "$last_date")
            echo "最后提交时间:$local_date"
            commits_json=$(curl -s --connect-timeout 10 --max-time 30 "${auth_header[@]}" "${api_base}${since_param}")
        fi
    fi

    # 验证返回数据是否为有效数组
    if ! echo "$commits_json" | jq -e 'type == "array"' >/dev/null 2>&1; then
        echo "错误:无法获取 $repo 的提交信息(API 限制或网络问题)"
        echo "响应样例:$(echo "$commits_json" | head -c 200)"
        return
    fi

    # 获取最新 SHA(数组第一个)
    local latest_sha=$(echo "$commits_json" | jq -r '.[0].sha // empty')
    if [ -z "$latest_sha" ] || [ "$latest_sha" = "null" ]; then
        echo "错误:未能解析 $repo 的最新 SHA"
        return
    fi

    # 检查是否有新提交
    if [ "$latest_sha" = "$last_sha" ]; then
        echo "已是最新版本。"
        return
    fi

    # ------------------ 提取新提交日志 ------------------
    local commits_log=""
    if [ -z "$last_sha" ]; then
        # 首次运行:只展示最新一条
        commits_log=$(echo "$commits_json" | jq -r '.[0] | "\(.sha[0:7]) \(.commit.message)"')
    else
        # 过滤掉 last_sha 自身(since 参数可能包含该提交),并输出所有新提交
        commits_log=$(echo "$commits_json" | jq -r --arg last "$last_sha" '
            .[] | select(.sha != $last) | "\(.sha[0:7]) \(.commit.message)"
        ')
    fi

    if [ -z "$commits_log" ]; then
        echo "无有效新提交记录(可能时间戳精度问题或已处理过)。"
        # 仍然更新状态文件到最新 SHA,避免重复检查
    else
        # 可选:限制输出行数,防止 AI 过载(这里保留全部新提交,可根据需要取消注释)
        # commits_log=$(echo "$commits_log" | head -n 20)
        echo "发现新提交,正在生成总结:"
        #echo "$commits_log" | while IFS= read -r line; do echo "  $line"; done
    fi

    # ------------------ AI 总结 ------------------
    if [ -n "$commits_log" ]; then
        local prompt="
你是一个直接输出结果的工具。严禁输出任何思考、分析或解释过程。严格限定于提交信息本身进行合理推断。严格遵循以下格式生成总结。只输出最终总结,不输出任何冗余内容。输出语言使用中文。输出格式:

### 主要更新
### 核心变更
### 影响

提交信息如下:
$commits_log
"
        # 调用 AI 代理
        local summary_text=$(/root/.cargo/bin/zeroclaw agent -m "$prompt" 2>/dev/null)
        if [ $? -ne 0 ] || [ -z "$summary_text" ]; then
            summary_text="⚠️ AI 总结生成超时或失败"
        fi
        echo "## $repo 更新摘要"
        echo "$summary_text"
    fi

    # ------------------ 安全更新状态文件 ------------------
    local tmp_file=$(mktemp)
    if jq --arg repo "$repo" --arg sha "$latest_sha" '.[$repo] = $sha' "$STATE_FILE" > "$tmp_file" 2>/dev/null; then
        mv "$tmp_file" "$STATE_FILE"
        echo "已更新状态文件:$repo -> ${latest_sha:0:7}"
    else
        echo "错误:无法更新状态文件"
        rm -f "$tmp_file"
    fi
}

# ======================== 主循环 ========================
for repo in "${REPOS[@]}"; do
    fetch_commits "$repo"
done

github_monitor.sh 是一个简洁而实用的自动化工具,它结合了 GitHub API 的增量查询和 ZeroClaw AI 的智能总结,为开发者提供了“设置后忘记”的仓库监控体验。

核心优势

  • ✅ 纯 Bash 实现,易于理解和修改
  • ✅ 增量检查,避免重复处理
  • ✅ AI 自动生成中文摘要,降低阅读成本
  • ✅ 可轻松集成到定时任务和消息通知

适用场景

  • 个人开发者跟踪兴趣项目
  • 技术团队监控依赖库更新
  • 开源维护者收集社区动态

如果你也在寻找轻量级的 GitHub 监控方案,不妨试试这个脚本,并根据自己的需求进行定制。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注