Commit Graph

13 Commits

Author SHA1 Message Date
therealaleph 6c692441be ci(telegram): brief English bullets in announcement + cross-link, drop Persian-full
Telegram channel posts up through v1.9.9 inlined the full Persian half of `docs/changelog/v{version}.md` (often >2000 chars), with sub-bullets, contributor mentions, and architectural prose. In a chat-client viewport the result was an unreadable wall of mixed RTL Persian + LTR `<code>` / `<b>` spans + nested bullets that scrolled past most readers.

Switched to brief-extracted English instead:
- Added `brief_changelog(text)` — keeps only top-level `• ` bullets (drops sub-bullets), strips "by @user with full root cause + fix" / "from @user" prefatory phrases, replaces `[#nnn](url)` with `#nnn` for inline issue refs, cuts each bullet at the first natural sentence boundary (`:` after pos 30, `. `, ` — `), hard-caps at 200 chars per bullet, and trims any dangling unbalanced `(` or `[` left by the truncation.
- Both posts (files-channel announcement + main-channel cross-link) now use `english_brief = brief_changelog(english_notes)` instead of the full Persian.
- Title and footer chrome of both posts switched to English ("released" / "Files (Android, Windows, ...)" / "Channel:" / "or:").

The full Persian + full English text stays in `docs/changelog/v*.md` for archival; only the channel post becomes brief.

Verified locally on v1.9.7 / v1.9.8 / v1.9.9 — produces 246–458 char briefs with clean bullet structure, no dangling parens, no contributor noise.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 12:39:38 +03:00
therealaleph 50bb3d0eaf ci(telegram): include Persian changelog in announcement + main-channel cross-link
Up through v1.9.7 the Telegram posts said only "📦 mhrv-rs vX.Y.Z منتشر شد" + a hashtag and a link to the files channel — subscribers had to click through to GitHub to see what actually changed. Now the announcement post in the files channel and the cross-link post in the main channel both inline the Persian half of `docs/changelog/v{version}.md`.

How it works:
- New `load_changelog(repo_root, version)` reads `docs/changelog/v{version}.md`, strips the leading `<!-- ... -->` editor comment, splits on the lone `---` line that separates Persian from English. Returns (None, None) if the file doesn't exist (lets out-of-band re-publishes for old tags whose changelog file was never landed work without crashing).
- New `md_to_tg_html(md, max_len)` does a minimal markdown → Telegram-flavoured-HTML conversion: `**bold**`, `[text](url)`, `` `code` `` are translated; nested patterns (e.g. `[`code`](url)`, `**[`code`](url)**`) work via a placeholder/unwind pass that loops until stable. Truncates at the 4096-char sendMessage limit, snapping to a newline boundary so a span isn't cut in half, with a "see full notes on GitHub" tail.
- Falls back gracefully if the changelog file is missing — uses the old skeleton message.

Verified locally on docs/changelog/v1.9.7.md: 3039 chars after conversion, well under the 4096 limit, all bold / code / link spans render correctly including nested ones (`[`assets/apps_script/Code.gs`](url)` becomes `<a href="url"><code>assets/apps_script/Code.gs</code></a>`).

This change takes effect on the next release (v1.9.8+); v1.9.7 is already published with the old skeleton.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 20:23:01 +03:00
therealaleph 4aac9a793f 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.
2026-05-01 11:52:32 +03:00
therealaleph 0d54c5c6fb ci(telegram): use public mhrv_rs link in main-channel post + add invite
Two related changes to the main-channel cross-post (one message per
release that points at the files channel):

1. Post-link now uses the public-username form `t.me/mhrv_rs/<msg_id>`
   instead of the private `t.me/c/<chat_id>/<msg_id>`. The latter only
   resolves for channel members; the former works for everyone, so
   recipients seeing the main-channel announcement can click through
   to a specific release post even if they're not yet subscribed.

   Wired via the existing `FILES_CHANNEL_USERNAME` workflow env var,
   now defaulting to `mhrv_rs` (the channel's public username) if the
   `vars.FILES_CHANNEL_USERNAME` repo variable is unset. Override per
   repo if the channel is renamed.

2. Channel-join links rendered in the body of the main-channel post,
   below the post-link:

       لینک کانال:
       https://t.me/mhrv_rs
       و یا: https://t.me/+R1OyoHX2boA1ZDgx

   Two forms cover the cases where one or the other is filtered:
   - `t.me/mhrv_rs` — public username form, prettier, surfaces in
     Telegram search
   - `t.me/+<hash>` — invite link, the only join path that works for
     private/restricted channels and for users whose client doesn't
     resolve public usernames cleanly

   Wired via new `FILES_CHANNEL_INVITE` env var, defaulting to the
   current invite hash; override via `vars.FILES_CHANNEL_INVITE` if
   rotated.

Per user request — not running this against v1.8.0 retroactively,
just wiring it up for the next release.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 03:30:37 +03:00
therealaleph 0669b9310c ci(telegram): add SHA-256 to file captions + cross-link main channel to files channel
Two changes on top of last commit:

1. SHA-256 ("تایید اصالت") now in every file caption. Each artifact's
   caption gets a `<code>...</code>` line with the file's SHA-256 hex
   so recipients can `sha256sum <file>` after download and verify it
   matches what the channel posted. Defends against modified copies
   if the channel ever gets relayed through a third party.

   For chunked uploads (file > 45 MB), each part shows BOTH:
   - SHA-256 of that specific part (verifies the chunk downloaded
     intact before bothering to reassemble)
   - SHA-256 of the full reassembled file (verifies the final result
     after `cat <name>.part_* > <name>`)

2. Main channel post is now a cross-link, not files.

   Previously the legacy `telegram` job in release.yml posted the
   universal APK + full changelog as one sendDocument + sendMessage
   pair to the main announcement channel.

   New behaviour: telegram-publish-files.yml's last step posts a short
   message to the main channel saying "v1.8.0 released, click here
   for files" with a t.me link pointing at the files channel's
   announcement anchor post. Recipients land on the anchor, scroll
   to find the platform-specific artifact they need.

   Link format: `t.me/c/<chat_id>/<msg>` for private channels (works
   for members), or `t.me/<username>/<msg>` if `FILES_CHANNEL_USERNAME`
   repo variable is set (works for everyone — useful if the files
   channel is later made public).

   Legacy telegram job in release.yml stays in source, dormant,
   gated on `vars.TELEGRAM_NOTIFY_ENABLED == 'true'` (default false).
   Comment updated to note the new workflow is the canonical path.
   If both are turned on at once, the main channel gets two posts
   per release.

Tested manually for syntax + caption rendering — actual SHA-256 values
will appear on the next workflow_dispatch run.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 03:22:27 +03:00
therealaleph 7e5e2c7313 ci(telegram): publish each release file individually to channel
New workflow + script that posts every artifact (Android APKs, Windows
ZIP, macOS .app + CLI tarballs, Linux glibc + musl, OpenWRT, Raspbian)
to the Telegram channel as separate sendDocument calls, each with a
Persian caption naming the platform variant and a `#v<NNN>` hashtag
(e.g. `#v180`, `#v1810`, `#v200`) so users can find a specific release
later via the channel's hashtag search.

Files larger than 45 MB (the Bot API's effective ceiling after multipart
+ caption overhead) are split into byte chunks named `<name>.part_aa`,
`.part_ab`, ... and posted with reassembly instructions in the caption.
For the v1.8.0 file set everything is ≤41 MB so the split path is
defensive.

Decoupled from `release.yml` so it can be re-triggered for any past tag
via `workflow_dispatch` without rebuilding artifacts — downloads from
the GitHub Release page directly via `gh release download`. Also
auto-runs on each successful `release.yml` completion via
`workflow_run`.

Hard-codes the channel ID `-1003966234444` (one well-known channel,
auditable in source). Reuses `secrets.TELEGRAM_BOT_TOKEN` which already
has post permissions there.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 03:15:26 +03:00
therealaleph e81974c204 Revert "ci: post macOS/Linux/Windows/Android binaries as Telegram media group"
This reverts commit e9ce03e697.
2026-04-26 21:10:50 +03:00
therealaleph e9ce03e697 ci: post macOS/Linux/Windows/Android binaries as Telegram media group
The Telegram release notifier used to post just the universal APK with
a single-document caption. This change ships the per-platform binaries
for macOS (amd64+arm64 CLI), Linux (amd64+arm64 CLI), Windows
(amd64 UI), and Android (universal APK) as a single Telegram media
group with one caption listing every filename + SHA-256.

Workflow side (.github/workflows/release.yml):
- The telegram job now downloads ALL artifacts (was: APK only).
- New `Prepare files for Telegram media group` step extracts the raw
  binaries out of each per-platform .tar.gz / .zip (no archive
  wrappers in the channel) and renames them with version suffixes
  (mhrv-rs-linux-amd64-v1.7.2, mhrv-rs-windows-amd64-ui-v1.7.2.exe,
  etc.). Per-platform extraction is best-effort: a missing artifact
  emits a `::warning::` and skips that platform rather than failing
  the whole post.
- The post step builds a `--files <path>` arg list from tg-files/,
  sorted for deterministic order across runs, and invokes the
  notifier without --with-changelog (the script auto-replies with
  changelog whenever --files is used).

Script side (.github/scripts/telegram_release_notify.py):
- New --files arg (repeatable). 2..=10 files → sendMediaGroup; 1 file
  → sendDocument with the same caption shape; 0 → error. Telegram's
  sendMediaGroup rejects single-item groups, so the 1-file fallback
  isn't optional.
- New build_media_group_caption() composes title + per-file
  filename+SHA list + repo/release URLs. Fits ~860 chars for a 6-file
  release; fallback to filename-only-list if a future swell pushes
  past Telegram's 1024-char caption cap.
- send_media_group() handles the multipart/form-data shape with each
  file referenced as `attach://fileN` from the media JSON. Caption is
  attached to file 0 only (Telegram clients render per-item captions
  inconsistently for media groups; first-item-only is the safe
  pattern).
- Legacy --apk path kept for any caller that hasn't migrated; either
  --apk or --files must be present (validated at startup).
- _content_type_for() picks application/vnd.android.package-archive
  for .apk and application/octet-stream for everything else, so
  Telegram clients label the APK with the Android icon and label
  desktop binaries by filename without a misleading icon.

Behavioural change for users:
- The Telegram channel now sees one grouped post per release with all
  primary platform binaries inline, instead of just the APK. macOS
  users wanting the gatekeeper-friendly .app.zip still grab it from
  the GitHub Releases page; the Telegram drop is for the "give me
  the binary, I'll run it" path.
- The Persian/English changelog reply that used to be opt-in (via
  TELEGRAM_INCLUDE_CHANGELOG=true) is now automatic in the --files
  path because the per-file SHA list eats the caption budget that
  previously held the FA brief-note.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 20:12:00 +03:00
dazzling-no-more 4b728058bd ci: add release-drafter + prepare-release for faster releases (#260) 2026-04-26 18:23:23 +03:00
therealaleph fb552c227d v1.5.0: long-poll Full Tunnel + Docker tunnel-node + brief FA release notes
Ships PR #173 (event-driven drain) plus three operational improvements:

PR #173 — long-poll tunnel mode. The tunnel-node's batch drain
switched from a fixed 150 ms sleep to an event-driven Notify wait;
idle sessions long-poll up to 5 s and wake on the first byte from
upstream. Push notifications and chat messages now arrive in roughly
RTT instead of waiting for the next client poll tick. Backward compat
with pre-#173 tunnel-nodes is automatic via a sticky AtomicBool that
detects fast empty replies and reverts to the legacy cadence.
92 client tests + 17 tunnel-node tests pass, including end-to-end
TCP-pair verification of the notify wiring.

Docker image for tunnel-node. Adds a hardened Dockerfile (BuildKit
cache mounts, non-root runtime user, ca-certificates for HTTPS
upstreams) and a .dockerignore to keep build context small. New
`tunnel-docker` job in the release workflow builds + pushes
multi-arch (linux/amd64 + linux/arm64) to
ghcr.io/therealaleph/mhrv-tunnel-node with `:latest`, `:1.5`, and
`:1.5.0` tags on every release. Setting up Full Tunnel mode goes
from "rustup + cargo build on a 1 GB VPS" (which fails on memory
half the time) to a one-liner. tunnel-node/README.md updated with
prebuilt-image + docker-compose recipes.

Brief Persian release note in Telegram caption. The release-post
caption now leads with a `<blockquote>`-wrapped FA bullet headlines
extracted from `docs/changelog/v<ver>.md`, above the existing two
links (repo + release). Markdown links → Telegram HTML <a> for
clickability. Cap-budget-aware truncation at bullet boundaries
keeps total caption under Telegram's 1024-char limit. Headlines-only
rather than full bullets so multiple "what's new" items fit
comfortably (the full bullets remain on the GH release page and as
the optional --with-changelog reply-threaded message).

GitHub Releases page bodies now lead with the changelog content
(Persian section + `---` + English) instead of just a Full Changelog
comparison link. The auto comparison link is appended at the bottom
via `append_body: true` rather than removed.

Workflow changes:
- New `permissions: packages: write` at the workflow level (required
  for ghcr push via docker/login-action).
- New `tunnel-docker` job needs `build` (not the full matrix) to
  serialize the QEMU buildx layer with the matrix cache.
- Release job composes the body from `docs/changelog/v${VER}.md`
  in a pre-step that handles both tag-push and workflow_dispatch
  paths (uses inputs.version || github.ref_name like the rest of
  the workflow).

Tested locally:
- `cargo test` — 92 lib tests pass
- `cargo test -p mhrv-tunnel-node` — 17 tests pass
- `docker build` of tunnel-node Dockerfile — 32 MB image, runs as
  non-root, /health returns "ok", auth rejection works correctly,
  legitimate requests open sessions to remote hosts
- Telegram script `--dry-run` mode added; rendered captions for
  v1.4.0, v1.4.1, v1.5.0 all fit under 900 chars

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 11:56:41 +03:00
therealaleph 5bb26a4961 v1.3.0: per-deployment concurrency, ABI-split APKs, ONLY-mode fix
Rolls up the four post-v1.2.14 commits on main into a single tagged
release. Highlights:

- Per-deployment concurrency (#142): each deployment ID gets its own
  30-permit semaphore, so setups with deployments across multiple
  Google accounts get a genuine 30×N throughput ceiling. Single-account
  setups still cap at Google's per-account 30-simultaneous limit —
  docs (EN + FA) updated to call that out.
- Android app-splitting ONLY-mode bug fix (#143): the previous code
  called both addAllowedApplication and addDisallowedApplication,
  which Android documents as mutually exclusive. ONLY mode was
  silently failing establish(). Now fixed.
- Per-ABI Android APKs (#136): ships four split APKs (arm64-v8a ~21 MB,
  armeabi-v7a ~18 MB, x86_64 ~23 MB, x86 ~22 MB) alongside the ~53 MB
  universal. Huge distribution win for users on unreliable
  censorship-tunnel paths — the 21 MB arm64-v8a download succeeds
  where the universal doesn't.
- Honest IP-exposure note in Security Posture (#148): clarified that
  v1.2.9's forwarded-header stripping only covers the client-side leg;
  what Google's own infrastructure may add on the UrlFetchApp.fetch()
  second leg is outside this client's control. Full Tunnel mode is
  the recommendation for threat models where that matters.
- Telegram release-post format: added Persian preambles above both
  links (GitHub repo + full Persian guide; release page + desktop/
  router builds) so channel readers see the intent at a glance.

82 tests pass. Desktop + Android builds both verified clean locally
across the v1.2.15+ commit series.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 23:11:52 +03:00
Shin (Former Aleph) 5a108f73cb v1.1.5: merge upstream safety fixes + Telegram default = file + link only (#60)
Contains the three safety fixes from PRs #48/#49/#50 and the Persian
README RTL polishing from #58, all squashed into main. Merge details
already in their individual PR comments; summary:

  #48: reject truncated Content-Length relay responses (previously
       silently accepted whatever bytes arrived before EOF)
  #49: reject truncated or malformed (missing CRLF) chunked-encoding
       relay responses (same class of silent-acceptance bug)
  #50: restrict the SNI-rewrite tunnel dispatch to port 443. Plain
       HTTP (:80) targets that happened to match google.com / hosts
       override were being steered into the TLS tunnel and blocking
       waiting for a ClientHello that would never arrive.
  #58: trailing-whitespace line-breaks on Persian bullet lists in
       README so the RTL rendering doesn't collapse consecutive
       items into a single paragraph.

Test suite grew from 54 to 58 passing (three new negative tests for
the relay-reader correctness fixes + one SNI-rewrite port filter).

Telegram CI notify default switched to file-plus-link:
  - script gains a `--with-changelog` flag; default OFF
  - workflow only passes it when `vars.TELEGRAM_INCLUDE_CHANGELOG=true`
  - every routine release now posts just the APK + short caption
    (title + SHA-256 + repo URL + release URL) with no long body

To include bullets for a given release again:
  gh variable set TELEGRAM_INCLUDE_CHANGELOG --body true
The existing `vars.TELEGRAM_NOTIFY_ENABLED` job-level gate remains —
changelog toggle is orthogonal to enable/disable.

Also closes PR #55 without merging; ads/analytics domains were being
lumped under a YouTube-specific toggle, and the PR committed per-
machine \`.cargo/config.toml\` + zig-cc cross-compile helpers that
would have broken CI on actual Windows / macOS runners.
2026-04-23 14:40:15 +03:00
therealaleph cbfdab582a ci(telegram): move notify step to Python, fix curl < parse bug
The v1.1.0 CI telegram job failed with curl exit 26 "Failed to
open/read local data from file/application" because:

    -F "caption=<b>mhrv-rs Android v1.1.0</b>..."

curl's -F treats a value starting with `<` as "read from file
named ..." (the canonical way to put file CONTENTS into a text
form field). Our HTML captions start with `<b>`, so curl tried
to open a file named `b>mhrv-rs Android v1.1.0</b>...`, failed,
and the whole job went red.

Rewrote the step in Python (`.github/scripts/telegram_release_notify.py`).
stdlib urllib + http.client have no such value-interpretation
wart. Also:
  - uses `application/vnd.android.package-archive` content-type
    so Telegram shows the APK with an Android-package label, not
    generic octet-stream
  - proper sha256 hash (streaming, not shell-piped)
  - consolidated the two shell-script HEREDOCs that were parsing
    the changelog into one place
  - clean exit codes: "no changelog file" and "no secrets" both
    exit 0, a broken Telegram response exits non-zero

No behaviour change for callers — the workflow just calls the
script with the same four inputs.
2026-04-23 10:09:51 +03:00