mirror of
https://github.com/therealaleph/MasterHttpRelayVPN-RUST.git
synced 2026-05-17 21:24:48 +03:00
feat: v1.9.4 — exit node for ChatGPT/Claude/Grok + drop duplicate Telegram post
Two changes addressing user-reported issues today:
1. Exit-node feature ported from upstream masterking32@464a6e1d, with
hardening. Cloudflare-protected sites (chatgpt.com, claude.ai,
grok.com, x.com, openai.com) flag Google datacenter IPs as bots and
return Turnstile / CAPTCHA / 502 challenges. Apps Script's UrlFetchApp
exits from those IPs, so v1.9.3 surfaces these as "Relay error: json:
key must be a string..." with no apps_script-mode workaround.
Now a small TypeScript HTTP endpoint (assets/exit_node/valtown.ts)
deployed on val.town / Deno Deploy sits between Apps Script and the
destination. Chain: client → Apps Script (Google IP) → val.town
(non-Google IP) → destination. Destination sees val.town's IP, no
CF challenge.
Config:
"exit_node": {
"enabled": true,
"relay_url": "https://...web.val.run",
"psk": "<openssl rand -hex 32>",
"mode": "selective",
"hosts": ["chatgpt.com", "claude.ai", "x.com", "grok.com", "openai.com"]
}
Hardening over upstream: PSK fail-closed if still placeholder (fresh
deploy can't be open relay), loop guard (refuses fetch of own host),
explicit 503 on misconfigured. Fallback to direct Apps Script on exit
node failure (CF-affected sites fail, others keep working). Setup
docs in English + Persian at assets/exit_node/README*.md. Example
config at config.exit-node.example.json.
2. Removed the legacy `telegram` job from release.yml. With
TELEGRAM_NOTIFY_ENABLED repo var set to true, every release was
producing two duplicate APK posts on the main Telegram channel: the
old bundled-APK-on-main job AND the newer per-file files-channel
posts (telegram-publish-files.yml). Only the per-file flow is wanted.
Legacy job and its helper telegram_release_notify.py are gone.
Recoverable from git log if anyone needs the bundled pattern back.
169 mhrv-rs lib tests + 33 tunnel-node tests + UI build clean.
This commit is contained in:
@@ -1,392 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Post a CI-built Android APK to the project Telegram channel on each
|
||||
release tag, followed by a reply-threaded changelog message with
|
||||
Persian + English bullets in <blockquote> blocks.
|
||||
|
||||
Called from the `telegram:` job in `.github/workflows/release.yml`.
|
||||
Environment:
|
||||
BOT_TOKEN Telegram bot token (repo secret TELEGRAM_BOT_TOKEN)
|
||||
CHAT_ID Numeric chat id, e.g. -1002282061190 (repo secret
|
||||
TELEGRAM_CHAT_ID)
|
||||
Arguments:
|
||||
--apk path to the APK file to upload
|
||||
--version bare version string, e.g. "1.1.0"
|
||||
--repo "owner/repo"
|
||||
--changelog path to docs/changelog/vX.Y.Z.md; split on a line
|
||||
that is exactly "---" — anything before is Persian,
|
||||
anything after is English. Missing file = only the
|
||||
APK is posted (no reply).
|
||||
|
||||
Why Python over curl: curl's `-F name=value` multipart spec treats
|
||||
`<file` as "read from file" and `@file` as "upload file". Our HTML
|
||||
captions contain literal `<b>` tags, which triggers the file-read
|
||||
path and exits 26 "Failed to open/read local data". urllib has no
|
||||
such behavior.
|
||||
|
||||
Telegram quirks we deliberately handle:
|
||||
- Captions max out at 1024 chars, so the APK caption is short
|
||||
(title + sha256 + repo + release URL) and the real changelog
|
||||
goes in a reply-threaded message (sendMessage has no practical
|
||||
length limit).
|
||||
- sendDocument content-type defaults to application/octet-stream
|
||||
for unknown extensions — we pass .apk with
|
||||
application/vnd.android.package-archive so channel previews
|
||||
label it as an Android package, not a generic file.
|
||||
"""
|
||||
import argparse
|
||||
import hashlib
|
||||
import http.client
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import ssl
|
||||
import sys
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def _strip_leading_comments(body: str) -> str:
|
||||
"""Strip leading HTML comment blocks (single- or multi-line) from `body`.
|
||||
|
||||
The changelog template uses `<!-- ... -->` to document the format for
|
||||
editors; we don't want those echoed to Telegram or the GitHub Release
|
||||
page. The `(?:...)+` quantifier eats N consecutive comments separated
|
||||
only by whitespace, so a stub with both a format-docs comment and a
|
||||
TODO comment is cleaned in one pass. `re.S` makes `.` cross newlines
|
||||
for multi-line `<!--\\n...\\n-->` blocks.
|
||||
|
||||
The matching regex is also used inline by .github/workflows/release.yml
|
||||
to compose the GitHub Release body — keep them in sync if you change
|
||||
one. Run `python -m doctest telegram_release_notify.py -v` to check.
|
||||
|
||||
>>> _strip_leading_comments("<!-- header -->\\nbody")
|
||||
'body'
|
||||
>>> _strip_leading_comments("<!-- a -->\\n<!-- b -->\\nbody")
|
||||
'body'
|
||||
>>> _strip_leading_comments("<!--\\nmulti\\nline\\n-->\\nbody")
|
||||
'body'
|
||||
>>> _strip_leading_comments("<!-- a -->\\n\\n<!-- b -->\\n\\nbody")
|
||||
'body'
|
||||
>>> _strip_leading_comments("body without comments")
|
||||
'body without comments'
|
||||
>>> _strip_leading_comments("body\\n<!-- mid-file comment -->\\nmore")
|
||||
'body\\n<!-- mid-file comment -->\\nmore'
|
||||
"""
|
||||
return re.sub(r"^\s*(?:<!--.*?-->\s*)+", "", body, count=1, flags=re.S)
|
||||
|
||||
|
||||
def parse_changelog(path: str) -> tuple[str, str]:
|
||||
"""Return (persian_body, english_body). Blank strings if file missing."""
|
||||
p = Path(path)
|
||||
if not p.is_file():
|
||||
return "", ""
|
||||
body = _strip_leading_comments(p.read_text(encoding="utf-8"))
|
||||
fa, sep, en = body.partition("\n---\n")
|
||||
if not sep:
|
||||
# No separator — treat everything as Persian (content-language
|
||||
# is a project preference rather than a hard rule).
|
||||
return body.strip(), ""
|
||||
return fa.strip(), en.strip()
|
||||
|
||||
|
||||
# Telegram caption hard-cap is 1024 chars. The fixed parts of our caption
|
||||
# (title + SHA hash + two-link footer with their preambles) sum to roughly
|
||||
# 470 chars on a typical version string. That leaves ~550 chars for the
|
||||
# release-note section before we'd start losing the trailing release URL.
|
||||
# Keep the budget conservative so a long version string or a slightly
|
||||
# longer hash representation doesn't push us over.
|
||||
CAPTION_FA_NOTE_BUDGET = 500
|
||||
|
||||
|
||||
def _md_links_to_html(text: str) -> str:
|
||||
"""Convert `[label](url)` markdown links to `<a href="url">label</a>`.
|
||||
|
||||
Telegram's HTML parse mode renders `<a>` as clickable but treats
|
||||
markdown verbatim, so an unconverted `[#160](https://…)` appears as
|
||||
that literal string in the channel post — both ugly and wasteful of
|
||||
caption budget. The HTML form is shorter visually (`#160` vs the
|
||||
full URL), still clickable, and counts the same toward Telegram's
|
||||
1024-char limit. Inline `code` (`backtick-quoted`) is also
|
||||
translated to `<code>…</code>` since markdown backticks render
|
||||
literally too.
|
||||
"""
|
||||
text = re.sub(
|
||||
r"\[([^\]]+)\]\(([^)]+)\)",
|
||||
lambda m: f'<a href="{m.group(2)}">{m.group(1)}</a>',
|
||||
text,
|
||||
)
|
||||
text = re.sub(r"`([^`\n]+)`", r"<code>\1</code>", text)
|
||||
# Bold (**…**) is rare in our changelog but happens — convert to <b>.
|
||||
text = re.sub(r"\*\*([^*\n]+)\*\*", r"<b>\1</b>", text)
|
||||
return text
|
||||
|
||||
|
||||
def _extract_headlines(fa_section: str) -> str:
|
||||
"""For each `• …: …` bullet, keep the headline part and drop the
|
||||
elaboration.
|
||||
|
||||
Our changelog convention writes each bullet as one of:
|
||||
• headline: full explanation
|
||||
• headline ([#NN](url)): full explanation
|
||||
• headline (issue ref): full explanation
|
||||
|
||||
The headline is everything up to the `: ` (colon + space) that ends
|
||||
the leading clause. Naively searching for the first `:` lands inside
|
||||
`https:` URLs of the markdown link form — instead we search from the
|
||||
end of the parenthesized-issue-ref (if any) for the first `: `, or
|
||||
fall back to the first `: ` in the line.
|
||||
|
||||
Headlines stay on the FA caption; the explanation is preserved in
|
||||
the docs/changelog/ file and (optionally) the reply-threaded message
|
||||
posted via --with-changelog.
|
||||
|
||||
Returns a newline-joined string of `• <headline>` lines.
|
||||
"""
|
||||
headlines: list[str] = []
|
||||
for line in fa_section.splitlines():
|
||||
if not line.startswith("• "):
|
||||
continue
|
||||
body = line[2:] # drop "• "
|
||||
# Prefer cutting at "): " — the close of the parenthesized ref
|
||||
# followed by the convention colon + space. That's our actual
|
||||
# bullet structure and avoids the false-positive `https:` cut.
|
||||
cut_idx = body.find("): ")
|
||||
if cut_idx > 0:
|
||||
headline = body[: cut_idx + 1] # keep the close paren
|
||||
else:
|
||||
# Fall back to ": " (colon + space) anywhere in the body.
|
||||
# Adding the space requirement skips `https:` which is
|
||||
# always followed by `/`.
|
||||
cut_idx = body.find(": ")
|
||||
headline = body[:cut_idx] if cut_idx > 0 else body
|
||||
headlines.append(f"• {headline.rstrip()}")
|
||||
return "\n".join(headlines)
|
||||
|
||||
|
||||
def build_caption_release_note(changelog_path: str) -> str:
|
||||
"""Build the Persian "what's new" block for the Telegram caption.
|
||||
|
||||
Pulls the FA section of `docs/changelog/v<ver>.md`, extracts just
|
||||
the bullet headlines (before the first `:` of each bullet) so the
|
||||
note is compact, converts markdown links/code to Telegram HTML for
|
||||
clickability, and wraps in a `<blockquote>`. Falls back to the full
|
||||
FA section if the headlines extraction yields nothing (e.g. a
|
||||
changelog that doesn't follow our `• headline: details` convention).
|
||||
|
||||
If the result still exceeds CAPTION_FA_NOTE_BUDGET, truncate at a
|
||||
bullet boundary with a trailing `…`. In practice the headlines-only
|
||||
form fits comfortably for any reasonable release note.
|
||||
"""
|
||||
fa, _en = parse_changelog(changelog_path)
|
||||
if not fa:
|
||||
return ""
|
||||
headlines = _extract_headlines(fa)
|
||||
note = headlines if headlines else fa.strip()
|
||||
note = _md_links_to_html(note)
|
||||
if len(note) > CAPTION_FA_NOTE_BUDGET:
|
||||
truncated = note[:CAPTION_FA_NOTE_BUDGET]
|
||||
last_bullet = truncated.rfind("\n•")
|
||||
if last_bullet > 0:
|
||||
note = truncated[:last_bullet].rstrip() + "\n…"
|
||||
else:
|
||||
note = truncated.rstrip() + "…"
|
||||
return f"<blockquote>{note}</blockquote>"
|
||||
|
||||
|
||||
def sha256_of(path: str) -> str:
|
||||
h = hashlib.sha256()
|
||||
with open(path, "rb") as f:
|
||||
for chunk in iter(lambda: f.read(1024 * 1024), b""):
|
||||
h.update(chunk)
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
def tg_request(method: str, token: str, *, body: bytes, content_type: str) -> dict:
|
||||
"""POST `body` to https://api.telegram.org/bot<token>/<method>."""
|
||||
conn = http.client.HTTPSConnection(
|
||||
"api.telegram.org", context=ssl.create_default_context()
|
||||
)
|
||||
conn.request(
|
||||
"POST",
|
||||
f"/bot{token}/{method}",
|
||||
body=body,
|
||||
headers={"Content-Type": content_type, "Content-Length": str(len(body))},
|
||||
)
|
||||
resp = conn.getresponse()
|
||||
raw = resp.read()
|
||||
try:
|
||||
data = json.loads(raw)
|
||||
except json.JSONDecodeError:
|
||||
raise SystemExit(f"Telegram {method}: non-JSON response ({resp.status}): {raw!r}")
|
||||
if not data.get("ok"):
|
||||
raise SystemExit(f"Telegram {method} failed: {data}")
|
||||
return data["result"]
|
||||
|
||||
|
||||
def send_document(token: str, chat_id: str, apk_path: str, caption: str) -> int:
|
||||
"""Upload the APK file with a short HTML caption. Returns message_id."""
|
||||
boundary = "----" + uuid.uuid4().hex
|
||||
with open(apk_path, "rb") as f:
|
||||
file_bytes = f.read()
|
||||
|
||||
def text_field(name: str, value: str) -> bytes:
|
||||
return (
|
||||
f"--{boundary}\r\n"
|
||||
f'Content-Disposition: form-data; name="{name}"\r\n\r\n'
|
||||
f"{value}\r\n"
|
||||
).encode("utf-8")
|
||||
|
||||
def file_field(name: str, filename: str, content: bytes) -> bytes:
|
||||
head = (
|
||||
f"--{boundary}\r\n"
|
||||
f'Content-Disposition: form-data; name="{name}"; filename="{filename}"\r\n'
|
||||
# Proper MIME type — makes the Telegram client show the APK
|
||||
# with the Android package icon and honour its size/name.
|
||||
f"Content-Type: application/vnd.android.package-archive\r\n\r\n"
|
||||
).encode("utf-8")
|
||||
return head + content + b"\r\n"
|
||||
|
||||
body = (
|
||||
text_field("chat_id", chat_id)
|
||||
+ text_field("caption", caption)
|
||||
+ text_field("parse_mode", "HTML")
|
||||
+ file_field("document", os.path.basename(apk_path), file_bytes)
|
||||
+ f"--{boundary}--\r\n".encode("utf-8")
|
||||
)
|
||||
|
||||
result = tg_request(
|
||||
"sendDocument",
|
||||
token,
|
||||
body=body,
|
||||
content_type=f"multipart/form-data; boundary={boundary}",
|
||||
)
|
||||
return int(result["message_id"])
|
||||
|
||||
|
||||
def send_reply(token: str, chat_id: str, text: str, reply_to: int) -> None:
|
||||
"""Post a text message as a reply to the APK message."""
|
||||
from urllib.parse import urlencode
|
||||
|
||||
body = urlencode(
|
||||
{
|
||||
"chat_id": chat_id,
|
||||
"text": text,
|
||||
"parse_mode": "HTML",
|
||||
"reply_to_message_id": str(reply_to),
|
||||
}
|
||||
).encode()
|
||||
tg_request(
|
||||
"sendMessage",
|
||||
token,
|
||||
body=body,
|
||||
content_type="application/x-www-form-urlencoded",
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--apk", required=True)
|
||||
ap.add_argument("--version", required=True)
|
||||
ap.add_argument("--repo", required=True)
|
||||
ap.add_argument("--changelog", required=True,
|
||||
help="Path to docs/changelog/vX.Y.Z.md; only read when --with-changelog is passed.")
|
||||
# Default: just the APK + short caption (title + SHA-256 + repo URL +
|
||||
# release URL). The per-release Persian/English blockquote reply is
|
||||
# opt-in via `--with-changelog` so routine releases don't flood the
|
||||
# channel with bullet-point bodies. To re-enable for a specific tag:
|
||||
# set the repo variable TELEGRAM_INCLUDE_CHANGELOG=true before pushing
|
||||
# the tag (the workflow converts that into --with-changelog).
|
||||
ap.add_argument("--with-changelog", action="store_true",
|
||||
help="Include the Persian+English changelog as a reply-threaded message.")
|
||||
# Dry-run lets you verify the rendered caption locally without hitting
|
||||
# Telegram. Useful when changing the brief-release-note budget /
|
||||
# truncation logic — print, eyeball, push.
|
||||
ap.add_argument("--dry-run", action="store_true",
|
||||
help="Render the caption and print it instead of posting. "
|
||||
"Skips token/chat_id checks.")
|
||||
args = ap.parse_args()
|
||||
|
||||
if not args.dry_run:
|
||||
token = os.environ.get("BOT_TOKEN", "")
|
||||
chat_id = os.environ.get("CHAT_ID", "")
|
||||
if not token or not chat_id:
|
||||
print("TELEGRAM secrets not present, skipping post.")
|
||||
return 0
|
||||
else:
|
||||
token = ""
|
||||
chat_id = ""
|
||||
|
||||
ver = args.version
|
||||
sha = sha256_of(args.apk)
|
||||
# Brief Persian release-note above the links. Pulled from the FA
|
||||
# half of `docs/changelog/v<ver>.md` so each release auto-includes
|
||||
# what's new without manual edits to this script. Truncated to fit
|
||||
# Telegram's 1024-char caption budget alongside title + SHA + the
|
||||
# two-link footer.
|
||||
fa_note = build_caption_release_note(args.changelog)
|
||||
|
||||
# Caption structure requested by the repo owner:
|
||||
# 1. Title + SHA-256 (as before)
|
||||
# 2. Brief Persian "what's new" note (extracted from changelog)
|
||||
# 3. Persian preamble labelling the repo link as
|
||||
# "GitHub repo + full Persian guide"
|
||||
# 4. Repo URL
|
||||
# 5. Persian preamble labelling the release link as
|
||||
# "this version's release — desktop/router builds live here"
|
||||
# 6. Release URL
|
||||
# Keeps total well under Telegram's 1024-char caption limit.
|
||||
caption_parts = [
|
||||
f"<b>mhrv-rs Android v{ver}</b>",
|
||||
"",
|
||||
f"SHA-256: <code>{sha}</code>",
|
||||
]
|
||||
if fa_note:
|
||||
caption_parts.extend(["", fa_note])
|
||||
caption_parts.extend([
|
||||
"",
|
||||
"مخزن گیتهاب + مطالعه راهنمای کامل فارسی:",
|
||||
f"https://github.com/{args.repo}",
|
||||
"",
|
||||
"لینک به این نسخه جهت دریافت نسخه های مربوط به مودم و کامپیوتر:",
|
||||
f"https://github.com/{args.repo}/releases/tag/v{ver}",
|
||||
])
|
||||
caption = "\n".join(caption_parts)
|
||||
|
||||
if args.dry_run:
|
||||
print(f"--- DRY RUN: caption ({len(caption)} chars) ---")
|
||||
print(caption)
|
||||
print(f"--- END DRY RUN ---")
|
||||
if args.with_changelog:
|
||||
fa, en = parse_changelog(args.changelog)
|
||||
print(f"\nWould reply with changelog "
|
||||
f"(fa: {len(fa) if fa else 0} chars, "
|
||||
f"en: {len(en) if en else 0} chars)")
|
||||
return 0
|
||||
|
||||
doc_mid = send_document(token, chat_id, args.apk, caption)
|
||||
print(f"sendDocument OK, message_id={doc_mid}")
|
||||
|
||||
if not args.with_changelog:
|
||||
print("Changelog reply disabled (default). Pass --with-changelog to include.")
|
||||
return 0
|
||||
|
||||
fa, en = parse_changelog(args.changelog)
|
||||
if not fa and not en:
|
||||
print(f"No changelog at {args.changelog}, skipping reply.")
|
||||
return 0
|
||||
|
||||
parts = []
|
||||
if fa:
|
||||
parts.append(f"<blockquote>{fa}</blockquote>")
|
||||
if en:
|
||||
parts.append(f"<blockquote>{en}</blockquote>")
|
||||
reply = "\n\n".join(parts)
|
||||
|
||||
send_reply(token, chat_id, reply, doc_mid)
|
||||
print("Reply OK")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
+14
-100
@@ -652,11 +652,9 @@ jobs:
|
||||
{
|
||||
echo 'body<<__RELEASE_BODY_EOF__'
|
||||
# Strip leading HTML comment blocks (single-line OR multi-line)
|
||||
# using the SAME regex as
|
||||
# .github/scripts/telegram_release_notify.py:parse_changelog,
|
||||
# so the GitHub Release page and the Telegram post agree on
|
||||
# exactly what counts as "the leading comment block." Both
|
||||
# also strip any leading whitespace/blank lines that follow.
|
||||
# so the GitHub Release page sees only the body content, not
|
||||
# the file-format header comment that every changelog has.
|
||||
# Also strips any leading whitespace/blank lines that follow.
|
||||
#
|
||||
# Quoted heredoc (`<<'PY'`) so backticks/$ in the python
|
||||
# snippet aren't shell-interpolated; CHANGELOG is passed in
|
||||
@@ -828,99 +826,15 @@ jobs:
|
||||
# isn't gated by the same protection.
|
||||
git push origin HEAD:main
|
||||
|
||||
# ─────────── LEGACY — DORMANT BY DEFAULT ───────────
|
||||
# The legacy `telegram` job that posted a universal APK + changelog
|
||||
# bundle to the main Telegram channel was removed in v1.9.4. It was
|
||||
# superseded by `.github/workflows/telegram-publish-files.yml` (per-
|
||||
# platform per-file posts to the files channel + a single cross-link
|
||||
# to the main channel). With both running together, every release
|
||||
# produced a duplicate APK post on the main channel — the legacy
|
||||
# bundled post AND the new cross-link.
|
||||
#
|
||||
# Posts the universal APK + per-version changelog to the **main**
|
||||
# Telegram channel as one big sendDocument + sendMessage pair.
|
||||
#
|
||||
# Superseded as of v1.8.0+ by `.github/workflows/telegram-publish-files.yml`,
|
||||
# which posts each platform's artifact individually to the **files**
|
||||
# channel (with SHA-256 captions) and then a single cross-link
|
||||
# message to the main channel pointing at the files-channel anchor.
|
||||
#
|
||||
# This job stays in the source tree, dormant, in case we ever want
|
||||
# to revert to the bundled-changelog-on-main-channel pattern (or
|
||||
# use both at once during a transition). To turn it back on:
|
||||
#
|
||||
# gh variable set TELEGRAM_NOTIFY_ENABLED --body true
|
||||
#
|
||||
# Note: with the new workflow active too, that produces TWO posts
|
||||
# to the main channel per release (the legacy APK+changelog *and*
|
||||
# the new cross-link). Pick one.
|
||||
#
|
||||
# Default state is disabled.
|
||||
telegram:
|
||||
needs: [android, release]
|
||||
runs-on: ubuntu-latest
|
||||
# Gated on the repo variable `TELEGRAM_NOTIFY_ENABLED`. Default is
|
||||
# off — the job skips silently unless the variable is set to the
|
||||
# literal string "true".
|
||||
if: ${{ vars.TELEGRAM_NOTIFY_ENABLED == 'true' && needs.android.result == 'success' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# Same retry pattern as the `release` job above — `actions/download-artifact@v4`
|
||||
# has been flaking on this workflow with 5-retries-exhausted errors.
|
||||
- name: Download universal APK (with retries)
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
mkdir -p apk
|
||||
for attempt in 1 2 3; do
|
||||
if gh run download "${GITHUB_RUN_ID}" \
|
||||
--name mhrv-rs-android-universal \
|
||||
--dir apk \
|
||||
--repo "${GITHUB_REPOSITORY}"; then
|
||||
echo "downloaded universal APK on attempt $attempt"
|
||||
ls -la apk/
|
||||
exit 0
|
||||
fi
|
||||
echo "download attempt $attempt failed; retrying in 30s..."
|
||||
sleep 30
|
||||
done
|
||||
echo "::error::failed to download universal APK after 3 attempts"
|
||||
exit 1
|
||||
|
||||
- name: Post to Telegram
|
||||
env:
|
||||
BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||
CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
|
||||
INCLUDE_CHANGELOG: ${{ vars.TELEGRAM_INCLUDE_CHANGELOG }}
|
||||
# Python over curl/bash so we don't have to fight curl's -F
|
||||
# value-interpretation rules. curl treats `-F "caption=<..."`
|
||||
# as "read the caption from file named ..." when the value
|
||||
# starts with `<`, which matches our `<b>` HTML-bold tags and
|
||||
# silently turns the whole job into a "file not found" exit
|
||||
# 26. Python stdlib has no such wart.
|
||||
run: |
|
||||
set -euo pipefail
|
||||
VER="${{ inputs.version || github.ref_name }}"
|
||||
VER="${VER#v}"
|
||||
APK="apk/mhrv-rs-android-universal-v${VER}.apk"
|
||||
|
||||
if [ -z "${BOT_TOKEN:-}" ] || [ -z "${CHAT_ID:-}" ]; then
|
||||
echo "::notice::TELEGRAM_BOT_TOKEN / TELEGRAM_CHAT_ID not set, skipping Telegram post"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ ! -f "$APK" ]; then
|
||||
echo "::error::expected $APK to exist; got:"
|
||||
ls -la apk/
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --with-changelog is opt-in. Default post is just the APK
|
||||
# plus a short caption with the SHA-256, repo URL, and release
|
||||
# URL — no long body. To include the Persian/English bullets
|
||||
# for a specific tag, set the repo variable
|
||||
# TELEGRAM_INCLUDE_CHANGELOG=true before pushing that tag.
|
||||
INCLUDE_CHANGELOG_FLAG=""
|
||||
if [ "${INCLUDE_CHANGELOG:-}" = "true" ]; then
|
||||
INCLUDE_CHANGELOG_FLAG="--with-changelog"
|
||||
fi
|
||||
python3 .github/scripts/telegram_release_notify.py \
|
||||
--apk "$APK" \
|
||||
--version "$VER" \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--changelog "docs/changelog/v${VER}.md" \
|
||||
$INCLUDE_CHANGELOG_FLAG
|
||||
# If you ever need to bring back the bundled-APK-on-main pattern, the
|
||||
# commit history before v1.9.4 has the full job — `git log -- .github/workflows/release.yml`.
|
||||
# The `TELEGRAM_NOTIFY_ENABLED` repo variable + `telegram_release_notify.py`
|
||||
# script that the legacy job called are no longer referenced.
|
||||
|
||||
Reference in New Issue
Block a user