mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-05-25 17:47:48 +02:00
201 lines
8.4 KiB
YAML
201 lines
8.4 KiB
YAML
name: Notify Telegram
|
|
|
|
# tauri-action creates the release with the default GITHUB_TOKEN, and GitHub
|
|
# Actions deliberately suppresses `release: published` events for releases
|
|
# made by GITHUB_TOKEN (to prevent recursive workflow chains). So we can't
|
|
# listen for `release: published` — it will never fire on stable releases.
|
|
#
|
|
# Instead, chain off the Release workflow via `workflow_run`, the same way
|
|
# `publish-repos.yml` does. `workflow_dispatch` is kept so a missed
|
|
# announcement can be replayed by hand.
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
tag:
|
|
description: "Release tag to announce (e.g. v0.23.0). Leave empty for latest stable."
|
|
required: false
|
|
type: string
|
|
workflow_run:
|
|
workflows: ["Release"]
|
|
types:
|
|
- completed
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
notify:
|
|
if: >
|
|
github.repository == 'zhom/donutbrowser' &&
|
|
(github.event_name == 'workflow_dispatch' ||
|
|
github.event.workflow_run.conclusion == 'success')
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
ref: main
|
|
fetch-depth: 0
|
|
|
|
- name: Resolve release tag
|
|
id: tag
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
INPUT_TAG: ${{ inputs.tag }}
|
|
# `head_branch` of a workflow_run trigger is attacker-influenceable
|
|
# (anyone with push to a tag can choose its name), so we pass it via
|
|
# env and validate before use rather than splicing it into the
|
|
# shell script literally. See CodeQL actions/code-injection.
|
|
EVENT_NAME: ${{ github.event_name }}
|
|
WORKFLOW_RUN_HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
|
|
REPO: ${{ github.repository }}
|
|
run: |
|
|
if [[ -n "${INPUT_TAG:-}" ]]; then
|
|
TAG="${INPUT_TAG}"
|
|
elif [[ "${EVENT_NAME}" == "workflow_run" ]]; then
|
|
# The Release workflow runs on `push: tags: v*` so head_branch
|
|
# of the triggering run is the tag name. Reject anything that
|
|
# isn't a plain tag-shaped string to keep this resistant to
|
|
# shell metacharacters injected via a crafted ref name.
|
|
if [[ ! "${WORKFLOW_RUN_HEAD_BRANCH}" =~ ^[A-Za-z0-9._/-]+$ ]]; then
|
|
echo "::error::Refusing tag with unexpected characters: ${WORKFLOW_RUN_HEAD_BRANCH}"
|
|
exit 1
|
|
fi
|
|
TAG="${WORKFLOW_RUN_HEAD_BRANCH}"
|
|
else
|
|
TAG=$(gh release view --repo "${REPO}" --json tagName -q .tagName)
|
|
fi
|
|
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
|
echo "Resolved tag: ${TAG}"
|
|
|
|
- name: Skip pre-releases / missing releases
|
|
id: gate
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
TAG: ${{ steps.tag.outputs.tag }}
|
|
run: |
|
|
# Tag like `nightly-…` or `nightly` is never an announceable
|
|
# stable release. Short-circuit before hitting the API.
|
|
if [[ "${TAG}" == nightly* ]]; then
|
|
echo "Tag '${TAG}' is a rolling/nightly build, skipping Telegram post."
|
|
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
|
|
# Only stable semver tags vX.Y.Z are eligible. Reject anything
|
|
# with a pre-release suffix (`-rc1`, `-beta`, etc.).
|
|
if [[ ! "${TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
echo "Tag '${TAG}' is not a stable semver tag, skipping."
|
|
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
|
|
# Confirm the release exists and isn't marked prerelease in the
|
|
# GitHub UI — guards against someone manually flipping the flag.
|
|
RELEASE_JSON=$(gh release view "${TAG}" --repo "${{ github.repository }}" --json isPrerelease,tagName 2>/dev/null || echo "")
|
|
if [[ -z "${RELEASE_JSON}" ]]; then
|
|
echo "Release ${TAG} not found via gh — skipping."
|
|
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
IS_PRE=$(jq -r .isPrerelease <<< "${RELEASE_JSON}")
|
|
if [[ "${IS_PRE}" == "true" ]]; then
|
|
echo "Release ${TAG} is marked prerelease, skipping."
|
|
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
echo "skip=false" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Post release announcement to Telegram
|
|
if: steps.gate.outputs.skip != 'true'
|
|
env:
|
|
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
|
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
|
|
TAG: ${{ steps.tag.outputs.tag }}
|
|
REPO: ${{ github.repository }}
|
|
run: |
|
|
if [ -z "$TELEGRAM_BOT_TOKEN" ] || [ -z "$TELEGRAM_CHAT_ID" ]; then
|
|
echo "::warning::TELEGRAM_BOT_TOKEN or TELEGRAM_CHAT_ID is not set — skipping Telegram notification."
|
|
exit 0
|
|
fi
|
|
|
|
# Find the previous stable tag (skip the current one) so the
|
|
# changelog range is well-defined.
|
|
PREV_TAG=$(git tag --sort=-version:refname \
|
|
| grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' \
|
|
| grep -v "^${TAG}$" \
|
|
| head -n 1)
|
|
if [ -z "$PREV_TAG" ]; then
|
|
PREV_TAG=$(git rev-list --max-parents=0 HEAD)
|
|
fi
|
|
|
|
strip_prefix() { echo "$1" | sed -E 's/^[a-z]+(\([^)]*\))?: //'; }
|
|
|
|
# Build a plain bullet list from feat / fix / refactor commits.
|
|
# Other commit types (chore, docs, ci, test, deps) are intentionally
|
|
# filtered out to keep the channel focused on user-visible changes.
|
|
CHANGES=""
|
|
while IFS= read -r msg; do
|
|
[ -z "$msg" ] && continue
|
|
case "$msg" in
|
|
feat\(*\):*|feat:*|fix\(*\):*|fix:*|refactor\(*\):*|refactor:*)
|
|
CHANGES="${CHANGES}• $(strip_prefix "$msg")"$'\n'
|
|
;;
|
|
esac
|
|
done < <(git log --pretty=format:%s "${PREV_TAG}..${TAG}")
|
|
|
|
if [ -z "$CHANGES" ]; then
|
|
CHANGES="• See release notes."$'\n'
|
|
fi
|
|
|
|
# HTML-escape the changelog before injecting into Telegram HTML
|
|
# mode — commit messages can legitimately contain `<`, `>`, `&`.
|
|
ESCAPED_CHANGES=$(printf '%s' "$CHANGES" \
|
|
| python3 -c "import html, sys; sys.stdout.write(html.escape(sys.stdin.read()))")
|
|
|
|
VERSION="${TAG}"
|
|
VERSION_NUM="${TAG#v}"
|
|
RELEASE_URL="https://github.com/${REPO}/releases/tag/${VERSION}"
|
|
DL="https://github.com/${REPO}/releases/download/${VERSION}"
|
|
|
|
# Build the API payload in one jq pass — keeps every literal
|
|
# newline, every angle bracket, and every quote correctly escaped
|
|
# for both shell and JSON.
|
|
PAYLOAD=$(jq -n \
|
|
--arg chat_id "$TELEGRAM_CHAT_ID" \
|
|
--arg version "$VERSION" \
|
|
--arg changes "$ESCAPED_CHANGES" \
|
|
--arg dl "$DL" \
|
|
--arg vnum "$VERSION_NUM" \
|
|
--arg release_url "$RELEASE_URL" \
|
|
'{
|
|
chat_id: $chat_id,
|
|
parse_mode: "HTML",
|
|
disable_web_page_preview: true,
|
|
text: (
|
|
"<b>Donut Browser " + $version + " released</b>\n\n" +
|
|
$changes + "\n" +
|
|
"<b>Download</b>\n" +
|
|
"<a href=\"" + $dl + "/Donut_" + $vnum + "_aarch64.dmg\">macOS (Apple Silicon)</a> · " +
|
|
"<a href=\"" + $dl + "/Donut_" + $vnum + "_x64.dmg\">macOS (Intel)</a>\n" +
|
|
"<a href=\"" + $dl + "/Donut_" + $vnum + "_x64-setup.exe\">Windows x64</a> · " +
|
|
"<a href=\"" + $dl + "/Donut_" + $vnum + "_amd64.AppImage\">Linux x64</a>\n\n" +
|
|
"<a href=\"" + $release_url + "\">Full release notes</a>"
|
|
)
|
|
}')
|
|
|
|
# Use --fail-with-body so we surface Telegram's error JSON on 4xx/5xx
|
|
# instead of just a curl exit code.
|
|
RESPONSE=$(curl -sSL --fail-with-body \
|
|
-H "Content-Type: application/json" \
|
|
-d "$PAYLOAD" \
|
|
"https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage") \
|
|
|| { echo "::error::Telegram API call failed"; echo "$RESPONSE"; exit 1; }
|
|
|
|
if [ "$(jq -r .ok <<< "$RESPONSE")" != "true" ]; then
|
|
echo "::error::Telegram API rejected the message:"
|
|
jq . <<< "$RESPONSE"
|
|
exit 1
|
|
fi
|
|
|
|
echo "Posted to Telegram (message_id $(jq -r .result.message_id <<< "$RESPONSE"))"
|