diff --git a/.github/workflows/release-go-daemons-reusable.yml b/.github/workflows/release-go-daemons-reusable.yml index 8f7405d..ff6d330 100644 --- a/.github/workflows/release-go-daemons-reusable.yml +++ b/.github/workflows/release-go-daemons-reusable.yml @@ -396,15 +396,68 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 steps: + # Release notes are arbitrary text (PR titles, usernames). Telegram's legacy + # Markdown has no escape mechanism, so any lone * or _ -- e.g. the "* " bullets + # GitHub generates, or a dependabot title like "Bump github.com/x/client_golang" + # -- either aborts the send with "can't parse entities" or silently mangles the + # message. HTML mode is the only format where the input can be fully escaped. + - name: Compose Telegram message + id: message + uses: actions/github-script@v9 + env: + # Passed via env, never interpolated into the script body, so that + # release notes cannot be executed as JavaScript. + RELEASE_BODY: ${{ needs.create_release.outputs.body }} + RELEASE_TAG: ${{ needs.create_release.outputs.tag_name }} + RELEASE_URL: ${{ needs.create_release.outputs.html_url }} + WORKFLOW_NAME: ${{ github.workflow }} + REPOSITORY: ${{ github.repository }} + STATUS: ${{ (contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')) && '❌' || '✅' }} + with: + script: | + const MAX_BODY = 3500; // Telegram rejects messages over 4096 chars + const esc = (s) => String(s || '') + .replace(/&/g, '&') + .replace(//g, '>'); + + let body = (process.env.RELEASE_BODY || '').replace(/\r\n/g, '\n').trim(); + let bodyHtml = 'no changes, build skipped'; + + if (body) { + // Trim on a line boundary before tagging, so a cut can never land + // inside a tag or an entity. + if (body.length > MAX_BODY) { + body = body.slice(0, MAX_BODY); + const nl = body.lastIndexOf('\n'); + if (nl > 0) body = body.slice(0, nl); + body += '\n…(truncated)'; + } + bodyHtml = body.split('\n').map((line) => esc(line) + .replace(/^\s{0,3}#{1,6}\s+(.*)$/, '$1') + .replace(/\*\*(.+?)\*\*/g, '$1') + .replace(/^\s{0,3}[*-]\s+/, '• ') + .replace(/\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g, '$1') + ).join('\n'); + } + + const tag = esc(process.env.RELEASE_TAG) || 'none'; + const url = process.env.RELEASE_URL; + const release = url ? `${tag}` : tag; + + core.setOutput('text', [ + `${process.env.STATUS} ${esc(process.env.WORKFLOW_NAME)} ${esc(process.env.REPOSITORY)}`, + '', + `Release: ${release}`, + 'Description:', + bodyHtml, + ].join('\n')); + - name: Notify Telegram (workflow result) uses: appleboy/telegram-action@v1.0.1 with: to: ${{ secrets.TELEGRAM_RELEASE_CHAT_ID }} token: ${{ secrets.TELEGRAM_BOT_TOKEN }} - format: markdown - message: | - ${{ (contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')) && '❌' || '✅' }} *${{ github.workflow }}* ${{ github.repository }} - - *Release:* [${{ needs.create_release.outputs.tag_name || 'none' }}](${{ needs.create_release.outputs.html_url }}) - *Description:* - ${{ needs.create_release.outputs.body || 'no changes, build skipped' }} + # Canonical Bot API parse_mode: the action forwards this value verbatim. + format: HTML + message: ${{ steps.message.outputs.text }}