88 types : [opened, reopened, synchronize, closed]
99 release :
1010 types : [published]
11- # uncomment if you also want issues events
1211 # issues:
1312 # types: [opened, reopened, closed, edited, labeled, unlabeled]
1413 workflow_dispatch :
1716 description : " Message to send to Telegram"
1817 required : true
1918 chat_id :
20- description : " Optional: override TG_CHAT_ID (e.g. @YourChannel or numeric id)"
19+ description : " Optional: override TG_CHAT_ID (e.g. @Channel or numeric id)"
2120 required : false
22- parse_mode :
23- description : " Telegram parse mode (plain|MarkdownV2|HTML)"
24- required : false
25- default : " HTML"
2621
2722concurrency :
2823 group : telegram-${{ github.ref }}-${{ github.event_name }}
@@ -34,15 +29,14 @@ permissions:
3429jobs :
3530 notify :
3631 runs-on : ubuntu-latest
37- environment : SANDBOX # uses TG_BOT_TOKEN and TG_CHAT_ID secrets
38-
32+ environment : SANDBOX # TG_BOT_TOKEN + TG_CHAT_ID
3933 steps :
40- - name : Check out (for commit list)
34+ - name : Checkout (for commit list)
4135 uses : actions/checkout@v4
4236 with :
4337 fetch-depth : 50
4438
45- - name : Build message (HTML )
39+ - name : Build message (MarkdownV2 )
4640 id : msg
4741 shell : bash
4842 env :
@@ -75,58 +69,71 @@ jobs:
7569 run : |
7670 set -euo pipefail
7771
78- # minimal HTML escaper for Telegram
79- esc() { sed -e 's/&/\&/g' -e 's/</\</g' -e 's/>/\>/g'; }
72+ # MarkdownV2 escaper for Telegram
73+ mde() {
74+ sed -e 's/\\/\\\\/g' \
75+ -e 's/_/\\_/g' -e 's/*/\\*/g' -e 's/\[/\\[/g' -e 's/]/\\]/g' \
76+ -e 's/(/\\(/g' -e 's/)/\\)/g' -e 's/~/\\~/g' -e 's/`/\\`/g' \
77+ -e 's/>/\\>/g' -e 's/#/\\#/g' -e 's/\\+/\\\\+/g' \
78+ -e 's/-/\\-/g' -e 's/=/\\=/g' -e 's/|/\\|/g' -e 's/{/\\{/g' -e 's/}/\\}/g' \
79+ -e 's/\./\\./g' -e 's/!/\\!/g'
80+ }
8081
81- header() {
82- printf '<b>📣 %s</b>\nRepo: <a href="%s">%s</a>\nBy: %s\nRef: <code>%s</code>\nRun: <a href="%s">view run</a>\n\n' \
83- "$(printf '%s' "$1" | esc)" "$REPO_URL" "$REPO" \
84- "$(printf '%s' "$ACTOR" | esc)" "$(printf '%s' "$REF_NAME" | esc)" "$RUN_URL"
82+ header_plain() { # shared header for non-manual events
83+ printf '📣 %s\nRepo: %s\nBy: %s\nRef: %s\nRun: %s\n' \
84+ "$(printf '%s' "$1")" "$REPO" "$ACTOR" "$REF_NAME" "$RUN_URL"
8585 }
8686
8787 MESSAGE=""
8888
8989 if [[ "$EVENT" == "workflow_dispatch" ]]; then
90- MESSAGE="$(header "Manual notification")${DISPATCH_TEXT:+$'\n'${DISPATCH_TEXT}}"
90+ # --- Minimal manual notification (what you asked) ---
91+ # 📣 Manual
92+ # By: <actor>
93+ # Run: <url>
94+ # <your message>
95+ MSG_ESC="$(printf '%s' "${DISPATCH_TEXT:-}" | mde)"
96+ HEADER="📣 Manual\nBy: $(printf '%s' "$ACTOR" | mde)\nRun: $(printf '%s' "$RUN_URL" | mde)\n\n"
97+ MESSAGE="${HEADER}${MSG_ESC}"
9198
9299 elif [[ "$EVENT" == "push" ]]; then
93100 BEFORE="${GIT_BEFORE:-}"
94101 AFTER="$GIT_SHA"
95- mapfile -t LINES < <(
96- if [[ -z "$BEFORE" || "$BEFORE" =~ ^0+$ ]]; then
97- git log -n 10 --pretty=format:'%s%x1f%h%x1f%an%x1f%H' "$AFTER" || true
98- else
99- git log --pretty=format:'%s%x1f%h%x1f%an%x1f%H' "$BEFORE..$AFTER" | head -n 10 || true
100- fi
101- )
102- COMMITS_HTML=""
102+ if [[ -z "$BEFORE" || "$BEFORE" =~ ^0+$ ]]; then
103+ mapfile -t LINES < <(git log -n 10 --pretty=format:'%s%x1f%h%x1f%H' "$AFTER" || true)
104+ else
105+ mapfile -t LINES < <(git log --pretty=format:'%s%x1f%h%x1f%H' "$BEFORE..$AFTER" | head -n 10 || true)
106+ fi
107+
108+ COMMITS_TXT=""
103109 if [[ ${#LINES[@]} -eq 0 ]]; then
104- COMMITS_HTML ="- (no commit messages found)"
110+ COMMITS_TXT ="- (no commit messages found)"
105111 else
106- COMMIT_BASE="${REPO_URL}/commit "
112+ ACTOR_URL="https://github.com/${ACTOR} "
107113 for row in "${LINES[@]}"; do
108- IFS=$'\x1f' read -r subj short author full <<<"$row"
109- subj_esc="$(printf '%s' "$subj" | esc)"
110- author_esc="$(printf '%s' "$author" | esc)"
111- COMMITS_HTML+="- <a href=\"${COMMIT_BASE}/${full}\">${subj_esc}</a> (<code>${short}</code>) by ${author_esc}\n"
114+ IFS=$'\x1f' read -r subj short full <<<"$row"
115+ subj_esc="$(printf '%s' "$subj" | mde)"
116+ COMMITS_TXT+="- ${subj_esc} (${short}) by $(printf '%s' "$ACTOR_URL" | mde)\n"
112117 done
113118 fi
114- MESSAGE="$(header "New push detected")Last changes:\n${COMMITS_HTML}\n\nRepo: <a href=\"${REPO_URL}\">${REPO_URL}</a>"
119+
120+ MESSAGE="$(header_plain 'Last 10 commits')"
121+ MESSAGE="${MESSAGE}\n${COMMITS_TXT}\nRepo: ${REPO_URL}"
115122
116123 elif [[ "$EVENT" == "pull_request" ]]; then
117124 if [[ "$PR_ACTION" == "closed" ]]; then
118125 [[ "${PR_MERGED}" == "true" ]] && STATUS="PR merged ✅" || STATUS="PR closed ❌"
119126 else
120127 STATUS="PR updated ✏️"
121128 fi
122- MESSAGE="$(header "$STATUS")#${PR_NUMBER}: $(printf '%s' "$PR_TITLE" | esc)\n${PR_URL}"
129+ TITLE_ESC="$(printf '%s' "$PR_TITLE" | mde)"
130+ MESSAGE="$(header_plain "$STATUS")\n#${PR_NUMBER}: ${TITLE_ESC}\n${PR_URL}"
123131
124132 elif [[ "$EVENT" == "release" ]]; then
125133 NAME="${REL_NAME:-$REL_TAG}"
126- MESSAGE="$(header " Release published 🏷️")Tag : $(printf '%s' "$REL_TAG" | esc )\nName: $(printf '%s' "$NAME" | esc )\n${REL_URL}"
134+ MESSAGE="$(header_plain ' Release published 🏷️')\nTag : $(printf '%s' "$REL_TAG" | mde )\nName: $(printf '%s' "$NAME" | mde )\n${REL_URL}"
127135
128136 elif [[ "$EVENT" == "issues" ]]; then
129- TITLE_ESC="$(printf '%s' "$ISSUE_TITLE" | esc)"
130137 case "$ISSUE_ACTION" in
131138 opened) STATUS="Issue opened 🐞" ;;
132139 reopened) STATUS="Issue reopened ♻️" ;;
@@ -136,16 +143,18 @@ jobs:
136143 unlabeled)STATUS="Issue unlabeled 🏷️" ;;
137144 *) STATUS="Issue update 📌" ;;
138145 esac
139- MESSAGE="$(header "$STATUS")#${ISSUE_NUM}: ${TITLE_ESC}\n${ISSUE_URL}"
146+ TITLE_ESC="$(printf '%s' "$ISSUE_TITLE" | mde)"
147+ MESSAGE="$(header_plain "$STATUS")\n#${ISSUE_NUM}: ${TITLE_ESC}\n${ISSUE_URL}"
140148
141149 else
142- MESSAGE="$(header "Event: $EVENT")"
150+ MESSAGE="$(header_plain "Event: $EVENT")"
143151 fi
144152
153+ # write multi-line output safely
145154 {
146- echo " text<<EOF"
147- echo "$MESSAGE"
148- echo "EOF"
155+ printf ' text<<MSGEOF\n'
156+ printf '%s\n' "$MESSAGE"
157+ printf 'MSGEOF\n'
149158 } >> "$GITHUB_OUTPUT"
150159
151160 - name : Send to Telegram
@@ -155,31 +164,18 @@ jobs:
155164 TG_CHAT_ID_DEFAULT : ${{ secrets.TG_CHAT_ID }}
156165 TEXT : ${{ steps.msg.outputs.text }}
157166 OVERRIDE_CHAT_ID : ${{ github.event.inputs.chat_id }}
158- PARSE_MODE : ${{ github.event.inputs.parse_mode }}
159167 run : |
160168 set -euo pipefail
161-
162169 CHAT_ID="${OVERRIDE_CHAT_ID:-$TG_CHAT_ID_DEFAULT}"
163- if [[ -z "${TG_BOT_TOKEN:-}" || -z "${CHAT_ID:-}" ]]; then
164- echo "Telegram secrets missing (TG_BOT_TOKEN or TG_CHAT_ID)." >&2
165- exit 1
166- fi
167-
168- case "${PARSE_MODE:-HTML}" in
169- MarkdownV2|HTML) PMODE="$PARSE_MODE" ;;
170- *) PMODE="" ;;
171- esac
172-
173- if [[ -n "$PMODE" ]]; then
174- PAYLOAD="$(jq -n --arg chat_id "$CHAT_ID" --arg text "$TEXT" --arg pm "$PMODE" \
175- '{chat_id:$chat_id, text:$text, parse_mode:$pm, disable_web_page_preview:true}')"
176- else
177- PAYLOAD="$(jq -n --arg chat_id "$CHAT_ID" --arg text "$TEXT" \
178- '{chat_id:$chat_id, text:$text, disable_web_page_preview:true}')"
179- fi
170+ [[ -n "${TG_BOT_TOKEN:-}" && -n "${CHAT_ID:-}" ]] || { echo "Missing TG secrets"; exit 1; }
180171
172+ # Send as MarkdownV2 (we already escaped)
181173 curl -sS -X POST "https://api.telegram.org/bot${TG_BOT_TOKEN}/sendMessage" \
182174 -H 'Content-Type: application/json' \
183- -d "$PAYLOAD" | jq -e '.ok == true' >/dev/null
175+ -d "$(jq -n \
176+ --arg chat_id "$CHAT_ID" \
177+ --arg text "$TEXT" \
178+ '{chat_id:$chat_id, text:$text, parse_mode:"MarkdownV2", disable_web_page_preview:true}')" \
179+ | jq -e '.ok == true' >/dev/null
184180
185181 echo "✅ Message sent to Telegram."
0 commit comments