From d6027e48eb4cb16a965af0ddf4b9ad5ddc7c64ac Mon Sep 17 00:00:00 2001 From: WrBug Date: Fri, 2 Jan 2026 04:13:10 +0800 Subject: [PATCH 1/7] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E8=AE=A2=E5=8D=95?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E6=9B=B4=E6=96=B0=E9=80=BB=E8=BE=91=EF=BC=8C?= =?UTF-8?q?=E9=81=BF=E5=85=8D=E8=AF=AF=E5=88=A0=E8=AE=A2=E5=8D=95=E5=92=8C?= =?UTF-8?q?=E5=8C=B9=E9=85=8D=E6=98=8E=E7=BB=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 先检查 HTTP 状态码,非 200 的都跳过,不删除订单 - 只有当 HTTP 200 且响应体为 null 时,才表示订单不存在 - 保护已部分卖出的订单,如果已部分卖出则保留用于统计 - 避免因临时网络问题或 API 错误导致订单和 sell_match_detail 被误删 --- .../statistics/OrderStatusUpdateService.kt | 49 +++++++++++++------ 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/statistics/OrderStatusUpdateService.kt b/backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/statistics/OrderStatusUpdateService.kt index 88d12ae..ad6fa12 100644 --- a/backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/statistics/OrderStatusUpdateService.kt +++ b/backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/statistics/OrderStatusUpdateService.kt @@ -199,22 +199,29 @@ class OrderStatusUpdateService( // 查询订单详情 val orderResponse = clobApi.getOrder(order.buyOrderId) - if (!orderResponse.isSuccessful) { - // HTTP 错误,可能是订单不存在,删除 - logger.info("订单查询失败(HTTP错误),删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, code=${orderResponse.code()}") - try { - copyOrderTrackingRepository.deleteById(order.id!!) - logger.info("已删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}") - } catch (e: Exception) { - logger.error("删除本地订单失败: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, error=${e.message}", e) - } + // 先检查 HTTP 状态码,非 200 的都跳过 + if (orderResponse.code() != 200) { + // HTTP 非 200,记录日志并跳过,等待下次轮询 + // 不删除订单,因为可能是临时网络问题或 API 错误 + val errorBody = orderResponse.errorBody()?.string()?.take(200) ?: "无错误详情" + logger.debug("订单查询失败(HTTP非200),等待下次轮询: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, code=${orderResponse.code()}, errorBody=$errorBody") continue } + // HTTP 200,检查响应体 + // 响应体也可能返回字符串 "null",Gson 解析时会返回 null val orderDetail = orderResponse.body() if (orderDetail == null) { - // HTTP 200 但响应体为空,表示订单不存在,删除 - logger.info("订单不存在(响应体为空),删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, code=${orderResponse.code()}") + // HTTP 200 且响应体为 null(或字符串 "null"),表示订单不存在 + // 检查订单是否已部分卖出,如果已部分卖出则保留订单用于统计 + val hasMatchedDetails = sellMatchDetailRepository.findByTrackingId(order.id!!).isNotEmpty() + if (hasMatchedDetails || order.matchedQuantity > BigDecimal.ZERO) { + logger.debug("订单不存在但已部分卖出,保留订单用于统计: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, matchedQuantity=${order.matchedQuantity}") + continue + } + + // 订单不存在且未部分卖出,删除本地订单 + logger.info("订单不存在(HTTP 200 但响应体为空),删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}") try { copyOrderTrackingRepository.deleteById(order.id!!) logger.info("已删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}") @@ -577,16 +584,28 @@ class OrderStatusUpdateService( // 查询订单详情 val orderResponse = clobApi.getOrder(order.buyOrderId) - if (!orderResponse.isSuccessful) { + + // 先检查 HTTP 状态码,非 200 的都跳过 + if (orderResponse.code() != 200) { val errorBody = orderResponse.errorBody()?.string()?.take(200) ?: "无错误详情" - logger.debug("查询订单详情失败,等待下次轮询: orderId=${order.buyOrderId}, code=${orderResponse.code()}, errorBody=$errorBody") + logger.debug("查询订单详情失败(HTTP非200),等待下次轮询: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, code=${orderResponse.code()}, errorBody=$errorBody") continue } + // HTTP 200,检查响应体 + // 响应体也可能返回字符串 "null",Gson 解析时会返回 null val orderDetail = orderResponse.body() if (orderDetail == null) { - // HTTP 200 但响应体为空,表示订单不存在(没有交易成功),删除本地订单 - logger.info("订单不存在(响应体为空),删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, code=${orderResponse.code()}") + // HTTP 200 且响应体为 null(或字符串 "null"),表示订单不存在 + // 检查订单是否已部分卖出,如果已部分卖出则保留订单用于统计 + val hasMatchedDetails = sellMatchDetailRepository.findByTrackingId(order.id!!).isNotEmpty() + if (hasMatchedDetails || order.matchedQuantity > BigDecimal.ZERO) { + logger.debug("订单不存在但已部分卖出,保留订单用于统计: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, matchedQuantity=${order.matchedQuantity}") + continue + } + + // 订单不存在且未部分卖出,删除本地订单 + logger.info("订单不存在(HTTP 200 但响应体为空),删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}") try { copyOrderTrackingRepository.deleteById(order.id!!) logger.info("已删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}") From ecdb8af14a4f93d9de0b1437a65b294554e8cbde Mon Sep 17 00:00:00 2001 From: WrBug Date: Fri, 2 Jan 2026 04:48:23 +0800 Subject: [PATCH 2/7] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20Telegram=20?= =?UTF-8?q?=E9=80=9A=E7=9F=A5=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 PR 合并到 main 分支时的 Telegram 通知 - 添加 Docker 镜像构建成功时的 Telegram 通知 - 支持 Markdown 转 HTML 格式 - 移除作者、仓库、变更统计、提交记录等冗余信息 --- .github/workflows/docker-build.yml | 57 ++++++++++ .github/workflows/telegram-notify.yml | 156 ++++++++++++++++++++++++++ 2 files changed, 213 insertions(+) create mode 100644 .github/workflows/telegram-notify.yml diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index ff9b24e..bc448a4 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -65,3 +65,60 @@ jobs: GITHUB_REPO_URL=https://github.com/WrBug/PolyHermes cache-from: type=registry,ref=wrbug/polyhermes:latest cache-to: type=inline + + - name: Send Telegram notification + env: + TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} + TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }} + run: | + # 检查必要的环境变量 + if [ -z "$TELEGRAM_BOT_TOKEN" ] || [ -z "$TELEGRAM_CHAT_ID" ]; then + echo "⚠️ Telegram Bot Token 或 Chat ID 未配置,跳过通知" + exit 0 + fi + + # 获取构建信息 + VERSION="${{ steps.extract_version.outputs.VERSION }}" + TAG="${{ steps.extract_version.outputs.TAG }}" + RELEASE_NAME="${{ github.event.release.name }}" + RELEASE_URL="${{ github.event.release.html_url }}" + REPO_NAME="${{ github.repository }}" + + # 构建消息内容(使用 HTML 格式) + MESSAGE="✅ Docker 镜像构建成功 + +📦 版本: ${VERSION} +🏷️ Tag: ${TAG} +🔗 Release: 查看 Release +🐳 Docker 镜像: wrbug/polyhermes:${TAG}" + + # 添加 Release 名称(如果有) + if [ -n "$RELEASE_NAME" ] && [ "$RELEASE_NAME" != "null" ] && [ "$RELEASE_NAME" != "" ]; then + RELEASE_NAME_ESCAPED=$(echo "$RELEASE_NAME" | sed 's/&/\&/g' | sed 's//\>/g') + MESSAGE="${MESSAGE} +📝 Release 名称: ${RELEASE_NAME_ESCAPED}" + fi + + # 发送 Telegram 消息(使用 jq 转义 JSON) + curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ + -H "Content-Type: application/json" \ + -d "$(jq -n \ + --arg chat_id "$TELEGRAM_CHAT_ID" \ + --arg text "$MESSAGE" \ + '{chat_id: $chat_id, text: $text, parse_mode: "HTML", disable_web_page_preview: false}')" > /tmp/telegram_response.json + + # 检查发送结果 + if [ $? -eq 0 ]; then + RESPONSE=$(cat /tmp/telegram_response.json) + if echo "$RESPONSE" | grep -q '"ok":true'; then + echo "✅ Telegram 通知发送成功" + else + echo "❌ Telegram 通知发送失败: $RESPONSE" + # 构建成功,通知失败不应该导致整个 job 失败 + exit 0 + fi + else + echo "❌ 发送 Telegram 消息时发生错误" + # 构建成功,通知失败不应该导致整个 job 失败 + exit 0 + fi \ No newline at end of file diff --git a/.github/workflows/telegram-notify.yml b/.github/workflows/telegram-notify.yml new file mode 100644 index 0000000..5af1259 --- /dev/null +++ b/.github/workflows/telegram-notify.yml @@ -0,0 +1,156 @@ +name: Telegram Notification on PR Merge + +on: + pull_request: + types: + - closed # 当 PR 被关闭(合并或关闭)时触发 + +jobs: + notify: + runs-on: ubuntu-latest + + # 只在 PR 被合并到 main 分支时执行 + if: github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'main' + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Get PR details + id: pr_details + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + PR_NUMBER="${{ github.event.pull_request.number }}" + REPO="${{ github.repository }}" + + # 获取 PR 详细信息 + PR_RESPONSE=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" \ + -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/${REPO}/pulls/${PR_NUMBER}") + + # 获取 PR 变更的文件列表 + FILES_RESPONSE=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" \ + -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/${REPO}/pulls/${PR_NUMBER}/files") + + # 提取 PR 描述(body),保留换行,限制长度 + PR_BODY=$(echo "$PR_RESPONSE" | jq -r '.body // ""') + if [ ${#PR_BODY} -gt 500 ]; then + PR_BODY="${PR_BODY:0:500}..." + fi + + # 保存到输出变量(使用 base64 编码避免特殊字符问题) + echo "pr_body<> $GITHUB_OUTPUT + echo "$PR_BODY" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Send Telegram notification + env: + TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} + TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }} + run: | + # 检查必要的环境变量 + # 注意:TELEGRAM_CHAT_ID 可以是个人聊天 ID(正数)或群组 ID(负数,如 -1001234567890) + if [ -z "$TELEGRAM_BOT_TOKEN" ] || [ -z "$TELEGRAM_CHAT_ID" ]; then + echo "⚠️ Telegram Bot Token 或 Chat ID 未配置,跳过通知" + exit 0 + fi + + # 获取 PR 基本信息 + PR_NUMBER="${{ github.event.pull_request.number }}" + PR_TITLE="${{ github.event.pull_request.title }}" + PR_URL="${{ github.event.pull_request.html_url }}" + PR_MERGE_COMMIT="${{ github.event.pull_request.merge_commit_sha }}" + + # 获取 PR 详细信息 + PR_BODY="${{ steps.pr_details.outputs.pr_body }}" + + # 转义 PR 标题和描述中的 HTML 特殊字符(但保留已有的 HTML 标签) + PR_TITLE_ESCAPED=$(echo "$PR_TITLE" | sed 's/&/\&/g' | sed 's//\>/g') + + # 构建消息内容(使用 HTML 格式) + MESSAGE="🚀 PR 已合并到 main 分支 + +📝 PR #${PR_NUMBER}: ${PR_TITLE_ESCAPED} +🔗 链接: 查看 PR +🔀 合并提交: ${PR_MERGE_COMMIT:0:7}" + + # 添加 PR 描述(如果有) + if [ -n "$PR_BODY" ] && [ "$PR_BODY" != "null" ] && [ "$PR_BODY" != "" ]; then + # 使用 Python 将 Markdown 转换为 HTML(更可靠) + PR_BODY_HTML=$(python3 << 'PYTHON_SCRIPT' +import sys +import re + +text = sys.stdin.read() + +# 转义 HTML 特殊字符(先转义,避免破坏后续的转换) +text = text.replace('&', '&') +text = text.replace('<', '<') +text = text.replace('>', '>') + +# 代码块:```code``` →
code
+text = re.sub(r'```([^`]+)```', r'
\1
', text, flags=re.DOTALL) + +# 行内代码:`code` → code(不在代码块中) +text = re.sub(r'`([^`]+)`', r'\1', text) + +# 粗体:**text** → text +text = re.sub(r'\*\*([^*]+)\*\*', r'\1', text) + +# 斜体:*text* → text(不在粗体或代码中) +text = re.sub(r'(?]*)\*([^*<>]+)\*(?![^<]*>)', r'\1', text) + +# 链接:[text](url) → text +text = re.sub(r'\[([^\]]+)\]\(([^)]+)\)', r'\1', text) + +# 标题:# Heading → Heading +text = re.sub(r'^### (.+)$', r'\1', text, flags=re.MULTILINE) +text = re.sub(r'^## (.+)$', r'\1', text, flags=re.MULTILINE) +text = re.sub(r'^# (.+)$', r'\1', text, flags=re.MULTILINE) + +# 列表项:- item → • item +text = re.sub(r'^- (.+)$', r'• \1', text, flags=re.MULTILINE) +text = re.sub(r'^ - (.+)$', r' • \1', text, flags=re.MULTILINE) +text = re.sub(r'^ - (.+)$', r' • \1', text, flags=re.MULTILINE) + +# 换行处理:Telegram HTML 模式不支持
标签,直接保留换行符 \n +# Telegram 会自动将 \n 渲染为换行 + +# 限制长度 +if len(text) > 1000: + text = text[:1000] + '...\n(内容已截断)' + +print(text, end='') +PYTHON_SCRIPT + <<< "$PR_BODY") + + MESSAGE="${MESSAGE} + +📄 PR 描述: +${PR_BODY_HTML}" + fi + + # 发送 Telegram 消息(使用 jq 转义 JSON) + curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ + -H "Content-Type: application/json" \ + -d "$(jq -n \ + --arg chat_id "$TELEGRAM_CHAT_ID" \ + --arg text "$MESSAGE" \ + '{chat_id: $chat_id, text: $text, parse_mode: "HTML", disable_web_page_preview: false}')" > /tmp/telegram_response.json + + # 检查发送结果 + if [ $? -eq 0 ]; then + RESPONSE=$(cat /tmp/telegram_response.json) + if echo "$RESPONSE" | grep -q '"ok":true'; then + echo "✅ Telegram 通知发送成功" + else + echo "❌ Telegram 通知发送失败: $RESPONSE" + exit 1 + fi + else + echo "❌ 发送 Telegram 消息时发生错误" + exit 1 + fi + From 46baa416f4d946fdc01f6b42ce6d0cb080165f56 Mon Sep 17 00:00:00 2001 From: WrBug Date: Fri, 2 Jan 2026 04:52:50 +0800 Subject: [PATCH 3/7] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20telegram-notify?= =?UTF-8?q?.yml=20YAML=20=E8=AF=AD=E6=B3=95=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 使用 heredoc 格式构建消息,避免 YAML 解析器误判 - 修复第 75 行的多行字符串格式问题 --- .github/workflows/telegram-notify.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/telegram-notify.yml b/.github/workflows/telegram-notify.yml index 5af1259..6c890e8 100644 --- a/.github/workflows/telegram-notify.yml +++ b/.github/workflows/telegram-notify.yml @@ -70,11 +70,14 @@ jobs: PR_TITLE_ESCAPED=$(echo "$PR_TITLE" | sed 's/&/\&/g' | sed 's//\>/g') # 构建消息内容(使用 HTML 格式) - MESSAGE="🚀 PR 已合并到 main 分支 + MESSAGE=$(cat <PR 已合并到 main 分支 📝 PR #${PR_NUMBER}: ${PR_TITLE_ESCAPED} -🔗 链接: 查看 PR -🔀 合并提交: ${PR_MERGE_COMMIT:0:7}" +🔗 链接: 查看 PR +🔀 合并提交: ${PR_MERGE_COMMIT:0:7} +EOF +) # 添加 PR 描述(如果有) if [ -n "$PR_BODY" ] && [ "$PR_BODY" != "null" ] && [ "$PR_BODY" != "" ]; then From 60e9f9235d04e608e5256b913f2f427d58c18e2f Mon Sep 17 00:00:00 2001 From: WrBug Date: Fri, 2 Jan 2026 04:57:00 +0800 Subject: [PATCH 4/7] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20workflow=20YAML?= =?UTF-8?q?=20=E8=AF=AD=E6=B3=95=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复 docker-build.yml 和 telegram-notify.yml 中的多行字符串格式问题 - 使用 heredoc 格式构建消息,避免 YAML 解析器误判 - 使用 $'\n' 格式追加内容,避免多行字符串导致 YAML 解析问题 --- .github/workflows/docker-build.yml | 12 +++++++----- .github/workflows/telegram-notify.yml | 5 +---- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index bc448a4..5e52dac 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -85,18 +85,20 @@ jobs: REPO_NAME="${{ github.repository }}" # 构建消息内容(使用 HTML 格式) - MESSAGE="✅ Docker 镜像构建成功 + MESSAGE=$(cat <Docker 镜像构建成功 📦 版本: ${VERSION} 🏷️ Tag: ${TAG} -🔗 Release: 查看 Release -🐳 Docker 镜像: wrbug/polyhermes:${TAG}" +🔗 Release: 查看 Release +🐳 Docker 镜像: wrbug/polyhermes:${TAG} +EOF +) # 添加 Release 名称(如果有) if [ -n "$RELEASE_NAME" ] && [ "$RELEASE_NAME" != "null" ] && [ "$RELEASE_NAME" != "" ]; then RELEASE_NAME_ESCAPED=$(echo "$RELEASE_NAME" | sed 's/&/\&/g' | sed 's//\>/g') - MESSAGE="${MESSAGE} -📝 Release 名称: ${RELEASE_NAME_ESCAPED}" + MESSAGE="${MESSAGE}"$'\n'$'\n'"📝 Release 名称: ${RELEASE_NAME_ESCAPED}" fi # 发送 Telegram 消息(使用 jq 转义 JSON) diff --git a/.github/workflows/telegram-notify.yml b/.github/workflows/telegram-notify.yml index 6c890e8..daa3525 100644 --- a/.github/workflows/telegram-notify.yml +++ b/.github/workflows/telegram-notify.yml @@ -129,10 +129,7 @@ print(text, end='') PYTHON_SCRIPT <<< "$PR_BODY") - MESSAGE="${MESSAGE} - -📄 PR 描述: -${PR_BODY_HTML}" + MESSAGE="${MESSAGE}"$'\n'$'\n'"📄 PR 描述:"$'\n'"${PR_BODY_HTML}" fi # 发送 Telegram 消息(使用 jq 转义 JSON) From c8e422a94b91c9d9d308bc1a19769bdbef034fe3 Mon Sep 17 00:00:00 2001 From: WrBug Date: Fri, 2 Jan 2026 04:58:14 +0800 Subject: [PATCH 5/7] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20workflow=20YAML?= =?UTF-8?q?=20=E8=AF=AD=E6=B3=95=E9=94=99=E8=AF=AF=EF=BC=8C=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=20heredoc=20=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复 docker-build.yml 和 telegram-notify.yml 中的 heredoc 格式问题 - 使用 $'\n' 格式构建多行字符串,避免 YAML 解析器误判 - 确保所有 workflow 文件符合 GitHub Actions YAML 语法规范 --- .github/workflows/docker-build.yml | 10 +--------- .github/workflows/telegram-notify.yml | 9 +-------- 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 5e52dac..5b77b2e 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -85,15 +85,7 @@ jobs: REPO_NAME="${{ github.repository }}" # 构建消息内容(使用 HTML 格式) - MESSAGE=$(cat <Docker 镜像构建成功 - -📦 版本: ${VERSION} -🏷️ Tag: ${TAG} -🔗 Release: 查看 Release -🐳 Docker 镜像: wrbug/polyhermes:${TAG} -EOF -) + MESSAGE="✅ Docker 镜像构建成功"$'\n'$'\n'"📦 版本: ${VERSION}"$'\n'"🏷️ Tag: ${TAG}"$'\n'"🔗 Release: 查看 Release"$'\n'"🐳 Docker 镜像: wrbug/polyhermes:${TAG}" # 添加 Release 名称(如果有) if [ -n "$RELEASE_NAME" ] && [ "$RELEASE_NAME" != "null" ] && [ "$RELEASE_NAME" != "" ]; then diff --git a/.github/workflows/telegram-notify.yml b/.github/workflows/telegram-notify.yml index daa3525..e32378b 100644 --- a/.github/workflows/telegram-notify.yml +++ b/.github/workflows/telegram-notify.yml @@ -70,14 +70,7 @@ jobs: PR_TITLE_ESCAPED=$(echo "$PR_TITLE" | sed 's/&/\&/g' | sed 's//\>/g') # 构建消息内容(使用 HTML 格式) - MESSAGE=$(cat <PR 已合并到 main 分支 - -📝 PR #${PR_NUMBER}: ${PR_TITLE_ESCAPED} -🔗 链接: 查看 PR -🔀 合并提交: ${PR_MERGE_COMMIT:0:7} -EOF -) + MESSAGE="🚀 PR 已合并到 main 分支"$'\n'$'\n'"📝 PR #${PR_NUMBER}: ${PR_TITLE_ESCAPED}"$'\n'"🔗 链接: 查看 PR"$'\n'"🔀 合并提交: ${PR_MERGE_COMMIT:0:7}" # 添加 PR 描述(如果有) if [ -n "$PR_BODY" ] && [ "$PR_BODY" != "null" ] && [ "$PR_BODY" != "" ]; then From 1de9b5e958ef0b1fa638c92c69967ff7c0670223 Mon Sep 17 00:00:00 2001 From: WrBug Date: Fri, 2 Jan 2026 05:02:10 +0800 Subject: [PATCH 6/7] =?UTF-8?q?fix:=20PR=20=E4=BB=85=E5=85=B3=E9=97=AD?= =?UTF-8?q?=E6=97=B6=E4=B8=8D=E5=8F=91=E9=80=81=20Telegram=20=E9=80=9A?= =?UTF-8?q?=E7=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在发送通知步骤中添加 PR 合并状态检查 - 如果 PR 仅关闭(未合并),直接退出,不发送通知 - 确保只有合并到 main 分支的 PR 才会发送通知 --- .github/workflows/telegram-notify.yml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/telegram-notify.yml b/.github/workflows/telegram-notify.yml index e32378b..21c4be1 100644 --- a/.github/workflows/telegram-notify.yml +++ b/.github/workflows/telegram-notify.yml @@ -50,6 +50,13 @@ jobs: TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }} run: | + # 检查 PR 是否被合并(而不是仅关闭) + PR_MERGED="${{ github.event.pull_request.merged }}" + if [ "$PR_MERGED" != "true" ]; then + echo "ℹ️ PR 仅关闭,未合并,跳过通知" + exit 0 + fi + # 检查必要的环境变量 # 注意:TELEGRAM_CHAT_ID 可以是个人聊天 ID(正数)或群组 ID(负数,如 -1001234567890) if [ -z "$TELEGRAM_BOT_TOKEN" ] || [ -z "$TELEGRAM_CHAT_ID" ]; then @@ -75,7 +82,8 @@ jobs: # 添加 PR 描述(如果有) if [ -n "$PR_BODY" ] && [ "$PR_BODY" != "null" ] && [ "$PR_BODY" != "" ]; then # 使用 Python 将 Markdown 转换为 HTML(更可靠) - PR_BODY_HTML=$(python3 << 'PYTHON_SCRIPT' + # 创建临时 Python 脚本 + cat > /tmp/markdown_to_html.py << 'PYEOF' import sys import re @@ -111,16 +119,13 @@ text = re.sub(r'^- (.+)$', r'• \1', text, flags=re.MULTILINE) text = re.sub(r'^ - (.+)$', r' • \1', text, flags=re.MULTILINE) text = re.sub(r'^ - (.+)$', r' • \1', text, flags=re.MULTILINE) -# 换行处理:Telegram HTML 模式不支持
标签,直接保留换行符 \n -# Telegram 会自动将 \n 渲染为换行 - # 限制长度 if len(text) > 1000: text = text[:1000] + '...\n(内容已截断)' print(text, end='') -PYTHON_SCRIPT - <<< "$PR_BODY") +PYEOF + PR_BODY_HTML=$(echo "$PR_BODY" | python3 /tmp/markdown_to_html.py) MESSAGE="${MESSAGE}"$'\n'$'\n'"📄 PR 描述:"$'\n'"${PR_BODY_HTML}" fi From 00f0898d985c2bcad8c2d92a285af5d9b7661a7b Mon Sep 17 00:00:00 2001 From: WrBug Date: Fri, 2 Jan 2026 05:04:02 +0800 Subject: [PATCH 7/7] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20workflow=20YAML?= =?UTF-8?q?=20=E8=AF=AD=E6=B3=95=E9=94=99=E8=AF=AF=EF=BC=8C=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=20heredoc=20=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 Python 脚本的 heredoc 格式改为使用多个 echo 命令 - 避免 GitHub Actions YAML 解析器将 heredoc 误判为 YAML 语法 - 确保 workflow 文件符合 GitHub Actions 语法规范 --- .github/workflows/telegram-notify.yml | 66 ++++++++++----------------- 1 file changed, 23 insertions(+), 43 deletions(-) diff --git a/.github/workflows/telegram-notify.yml b/.github/workflows/telegram-notify.yml index 21c4be1..7a66de7 100644 --- a/.github/workflows/telegram-notify.yml +++ b/.github/workflows/telegram-notify.yml @@ -82,49 +82,29 @@ jobs: # 添加 PR 描述(如果有) if [ -n "$PR_BODY" ] && [ "$PR_BODY" != "null" ] && [ "$PR_BODY" != "" ]; then # 使用 Python 将 Markdown 转换为 HTML(更可靠) - # 创建临时 Python 脚本 - cat > /tmp/markdown_to_html.py << 'PYEOF' -import sys -import re - -text = sys.stdin.read() - -# 转义 HTML 特殊字符(先转义,避免破坏后续的转换) -text = text.replace('&', '&') -text = text.replace('<', '<') -text = text.replace('>', '>') - -# 代码块:```code``` →
code
-text = re.sub(r'```([^`]+)```', r'
\1
', text, flags=re.DOTALL) - -# 行内代码:`code` → code(不在代码块中) -text = re.sub(r'`([^`]+)`', r'\1', text) - -# 粗体:**text** → text -text = re.sub(r'\*\*([^*]+)\*\*', r'\1', text) - -# 斜体:*text* → text(不在粗体或代码中) -text = re.sub(r'(?]*)\*([^*<>]+)\*(?![^<]*>)', r'\1', text) - -# 链接:[text](url) → text -text = re.sub(r'\[([^\]]+)\]\(([^)]+)\)', r'\1', text) - -# 标题:# Heading → Heading -text = re.sub(r'^### (.+)$', r'\1', text, flags=re.MULTILINE) -text = re.sub(r'^## (.+)$', r'\1', text, flags=re.MULTILINE) -text = re.sub(r'^# (.+)$', r'\1', text, flags=re.MULTILINE) - -# 列表项:- item → • item -text = re.sub(r'^- (.+)$', r'• \1', text, flags=re.MULTILINE) -text = re.sub(r'^ - (.+)$', r' • \1', text, flags=re.MULTILINE) -text = re.sub(r'^ - (.+)$', r' • \1', text, flags=re.MULTILINE) - -# 限制长度 -if len(text) > 1000: - text = text[:1000] + '...\n(内容已截断)' - -print(text, end='') -PYEOF + # 使用 echo 创建临时 Python 脚本,避免 heredoc 导致的 YAML 解析问题 + echo 'import sys' > /tmp/markdown_to_html.py + echo 'import re' >> /tmp/markdown_to_html.py + echo '' >> /tmp/markdown_to_html.py + echo 'text = sys.stdin.read()' >> /tmp/markdown_to_html.py + echo '' >> /tmp/markdown_to_html.py + echo 'text = text.replace("&", "&")' >> /tmp/markdown_to_html.py + echo 'text = text.replace("<", "<")' >> /tmp/markdown_to_html.py + echo 'text = text.replace(">", ">")' >> /tmp/markdown_to_html.py + echo 'text = re.sub(r"```([^`]+)```", r"
\\1
", text, flags=re.DOTALL)' >> /tmp/markdown_to_html.py + echo 'text = re.sub(r"`([^`]+)`", r"\\1", text)' >> /tmp/markdown_to_html.py + echo 'text = re.sub(r"\\*\\*([^*]+)\\*\\*", r"\\1", text)' >> /tmp/markdown_to_html.py + echo 'text = re.sub(r"(?]*)\\*([^*<>]+)\\*(?![^<]*>)", r"\\1", text)' >> /tmp/markdown_to_html.py + echo 'text = re.sub(r"\\[([^\\]]+)\\]\\(([^)]+)\\)", r"\\1", text)' >> /tmp/markdown_to_html.py + echo 'text = re.sub(r"^### (.+)$", r"\\1", text, flags=re.MULTILINE)' >> /tmp/markdown_to_html.py + echo 'text = re.sub(r"^## (.+)$", r"\\1", text, flags=re.MULTILINE)' >> /tmp/markdown_to_html.py + echo 'text = re.sub(r"^# (.+)$", r"\\1", text, flags=re.MULTILINE)' >> /tmp/markdown_to_html.py + echo 'text = re.sub(r"^- (.+)$", r"• \\1", text, flags=re.MULTILINE)' >> /tmp/markdown_to_html.py + echo 'text = re.sub(r"^ - (.+)$", r" • \\1", text, flags=re.MULTILINE)' >> /tmp/markdown_to_html.py + echo 'text = re.sub(r"^ - (.+)$", r" • \\1", text, flags=re.MULTILINE)' >> /tmp/markdown_to_html.py + echo 'if len(text) > 1000:' >> /tmp/markdown_to_html.py + echo ' text = text[:1000] + "...\\n(内容已截断)"' >> /tmp/markdown_to_html.py + echo 'print(text, end="")' >> /tmp/markdown_to_html.py PR_BODY_HTML=$(echo "$PR_BODY" | python3 /tmp/markdown_to_html.py) MESSAGE="${MESSAGE}"$'\n'$'\n'"📄 PR 描述:"$'\n'"${PR_BODY_HTML}"