GitHub → Telegram forum sync: issues ⇄ topics, releases with files, and auto-rendered release videos + stories.
  • TypeScript 99.5%
  • Dockerfile 0.3%
  • JavaScript 0.2%
Find a file
TFBosoN d3eb623df2
Add pm2 ecosystem config
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 14:46:46 +03:00
assets/fonts Initial commit: GitHub-to-Telegram sync bot with release story videos 2026-09-26 13:59:39 +03:00
docs Add proprietary license, author, and README demo 2026-09-26 14:15:32 +03:00
fixtures Initial commit: GitHub-to-Telegram sync bot with release story videos 2026-09-26 13:59:39 +03:00
scripts Initial commit: GitHub-to-Telegram sync bot with release story videos 2026-09-26 13:59:39 +03:00
src Mirror issue edits and relabels, copy Telegram attachments to GitHub 2026-09-26 14:17:27 +03:00
.dockerignore Initial commit: GitHub-to-Telegram sync bot with release story videos 2026-09-26 13:59:39 +03:00
.env.example Mirror issue edits and relabels, copy Telegram attachments to GitHub 2026-09-26 14:17:27 +03:00
.gitignore Initial commit: GitHub-to-Telegram sync bot with release story videos 2026-09-26 13:59:39 +03:00
docker-compose.yml Initial commit: GitHub-to-Telegram sync bot with release story videos 2026-09-26 13:59:39 +03:00
Dockerfile Initial commit: GitHub-to-Telegram sync bot with release story videos 2026-09-26 13:59:39 +03:00
ecosystem.config.cjs Add pm2 ecosystem config 2026-09-26 14:46:46 +03:00
LICENSE Add proprietary license, author, and README demo 2026-09-26 14:15:32 +03:00
package-lock.json Add proprietary license, author, and README demo 2026-09-26 14:15:32 +03:00
package.json Add proprietary license, author, and README demo 2026-09-26 14:15:32 +03:00
README.md Mirror issue edits and relabels, copy Telegram attachments to GitHub 2026-09-26 14:17:27 +03:00
tsconfig.json Initial commit: GitHub-to-Telegram sync bot with release story videos 2026-09-26 13:59:39 +03:00

GH2TG

Mirrors a GitHub repo into a Telegram forum group, and turns each stable release into an animated "What's new" video that's posted to the group and as a group story.

Release video rendered by GH2TG

GitHub → Telegram

On GitHub In Telegram
New issue Card in Bug reports or Feature requests, with an "Open on GitHub" button. Label bug → Bug reports, enhancement → Feature requests; the AI sorts unlabeled issues.
Issue comment, close, reopen Reply under the issue's card
Issue title or text edited, comment edited The card or comment message is updated in place. For an issue that started as someone's Telegram message, a rename is posted as a reply.
bug ⇄ enhancement label changed A new card in the other topic, and a note linking to it under the old one
Release, including the rolling nightly Grouped changelog in Releases, with the build files attached
Stable release (not a pre-release) Animated video in Releases, plus a group story

Telegram → GitHub

In Telegram On GitHub
A new message in Bug reports / Feature requests New issue, labeled. The AI writes the title, ignores chit-chat, and relabels a bug posted under features (or the reverse). The bot replies with the issue link.
A reply to any message that belongs to an issue Issue comment
Photos, files, videos and albums in either of the above Copied into the repo and embedded in the issue or comment. See Attachments.

It's a single Node service. It polls GitHub (no webhook, public URL or repo admin rights needed) and long-polls Telegram, so it runs anywhere with outbound internet.

Requirements

  • Node 24+ (runs the TypeScript directly; there's no build step)
  • ffmpeg on PATH, or set FFMPEG_PATH
  • A GitHub token that can read the repo and write issues (classic token with repo scope, or fine-grained with Issues read/write and Contents read/write; the write access is only for attachments)
  • A Telegram bot from @BotFather
  • Optional: an OpenAI-compatible API (OpenRouter, OpenAI, a local gateway) for triage and video scripts
  • Optional: a Telegram user account that's a group admin, for posting stories

Setup

  1. npm install
  2. Copy .env.example to .env and fill in the first block. See Configuration.
  3. Telegram group:
    • Enable Topics in the group settings. The group id then starts with -100.
    • Add the bot as an admin with Manage topics.
    • Don't know the id? Start the bot and write anything in the group; it logs the id.
  4. npm start

On first start, the bot creates the Bug reports, Feature requests and Releases topics. To use topics that already exist, send /bind bugs, /bind features or /bind releases inside each one, or set TOPIC_* in .env.

Only issues, comments and releases created after the first run are mirrored. Earlier releases only set the starting point for the first changelog.

Bot commands

Command Where What it does
/bind bugs|features|releases Inside a topic (admins only) Sends that kind of update to this topic
/topics Anywhere in the group Shows the repo and the topic ids in use

Group stories

Bots can't post stories to groups, so a user account posts them. That account must be an admin of the group with the Post stories right.

  1. Create an app at https://my.telegram.org → API development tools, and put TG_API_ID and TG_API_HASH in .env.
  2. Run npm run tg:login and choose one of:
    • QR code (recommended): scan it in Telegram on your phone, under Settings → Devices → Link Desktop Device.
    • Phone number: the code usually arrives in the Telegram service chat on a device where you're already logged in, not by SMS. Type r to resend it another way.
  3. Paste the printed TG_SESSION= line into .env. It gives full access to that account, so keep it as secret as a password.

The group needs enough boosts to have stories, and its boost level also caps how many stories it can post per day. When it can't post, the video still goes to the Releases topic and the log says why the story was skipped.

Attachments

GitHub's API has no way to attach a file to an issue, so the bot commits files from Telegram to a separate branch, gh2tg-attachments, and links them from the issue or comment. Images show inline. The bot creates the branch on first use. It shares no history with your code and has no workflows, so it never triggers CI.

The Bot API only downloads files up to 20 MB (90 MB with a local Bot API server). Larger files, and any upload that fails, are listed in the issue with a link to the Telegram message instead.

Release video

Nothing is screen-recorded or generated as pixels by an AI. The video is drawn frame by frame in the style of Telegram's own promo clips: navy-to-blue gradient, bold captions whose words blur in, a mock app window with tap ripples and a pointer, spring animations, and motion blur on fast moves.

How one gets made:

  1. Commits. For a stable release, everything since the previous stable release.

  2. Storyboard. The AI picks the 3–5 most user-visible changes. Each gets a caption and one animated UI template. Besides the commits, the AI sees the changed files and the UI strings those diffs add (button labels, setting names, messages; logs, comments, identifiers and tests are filtered out), so it can pick the template that fits the kind of change and fill it with the app's real labels:

    Template Shows
    list Cards; one is tapped, lifts and gets a badge
    toggle Settings rows; a switch flips on and its hint expands
    editor Code or config typing itself out, then a status badge
    status A big button that is tapped, spins, and turns green with a live timer
    menu A pointer right-clicks a card and a context menu opens
    dialog A sheet slides up with a message and two buttons
    fixes Bugs turn into checkmarks
    metric Before/after bars grow and count up, then a badge ("3× faster") pops; only for real numbers
    search A query types into a search field; other rows filter away and matches get highlighted
    progress Progress bars fill at different speeds and turn into checks
    notify A notification banner drops in, expands, and its action button is tapped
    tabs A tab is tapped in a segmented bar and its content slides in
    terminal A command types itself and its output prints line by line
    theme A sun button is tapped and the screen floods into dark mode
    drag A card is held and dragged to a new position

    The feature/fix/improvement counts come from the commits' conventional-commit prefixes, not the AI. Without an AI, a simpler storyboard is built from those prefixes.

  3. Render. Drawn with skia-canvas, on the GPU (Vulkan/Metal) when there is one, and encoded to 1080×1920 60 fps H.264 for the Releases topic, using NVENC/AMF/QSV when available.

  4. Story copy. A separate 720×1280 30 fps HEVC file with a keyframe every second, at most 30 MB and 60 s: the only format Telegram accepts for stories.

Everything is written to data/videos/: <version>.mp4, <version>.story.mp4, and <version>.storyboard.json (the storyboard, handy for tweaking and re-rendering).

Speed: on an RTX 4090, a 26 s video renders in about 23 s. On a CPU-only VPS, expect a few minutes.

Previewing and posting by hand

# Render a storyboard file, no GitHub or Telegram involved
npm run preview                                            # fixtures/sample-storyboard.json → out/preview.mp4
npm run preview -- data/videos/v0.1.0.storyboard.json --icon out/appicon.png
npm run preview -- --stills 3,7.5                          # single frames as PNG, to check layout
npm run preview -- --range 5,9 --encoder x264 --cpu        # part of the video, forcing the CPU paths

# Build from real commits (writes to data/videos/)
npm run story -- --tag v0.2.0                              # previous stable release..v0.2.0
npm run story -- --from 07d09dc --to main --version v0.1.0

# Post
npm run story -- --tag v0.2.0 --post                       # Releases topic + group story
npm run story -- --tag v0.2.0 --video data/videos/v0.2.0.mp4 --post --story-only   # story only, reuse the video

Configuration

All settings live in .env; .env.example lists every key.

Key Default Meaning
GITHUB_REPO — https://github.com/owner/repo
GITHUB_API — GitHub token
TELEGRAM_GROUP — Forum group id, starts with -100
TELEGRAM_BOT_API — Bot token
AI_GATE, AI_TOKEN, AI_MODEL off OpenAI-compatible endpoint, e.g. https://openrouter.ai/api/v1. Without it, heuristics and the simple storyboard are used.
POLL_INTERVAL 60 Seconds between GitHub checks (minimum 15)
TOPIC_BUGS, TOPIC_FEATURES, TOPIC_RELEASES auto Fixed topic ids, overriding /bind and auto-creation
TOPIC_BUGS_NAME, TOPIC_FEATURES_NAME, TOPIC_RELEASES_NAME Bug reports, … Names for topics the bot creates
TELEGRAM_API_ROOT — A local Bot API server, which raises the upload limit from 50 MB to 2000 MB
STORY_FOR stable Which releases get a video: stable, all or none
STORY_APP_NAME repo name Name shown in the video
STORY_ICON build/appicon.png App icon: a local file, or a path inside the repo
FFMPEG_PATH ffmpeg ffmpeg binary; ffprobe is expected next to it
STORY_ENCODER auto auto, nvenc, amf, qsv or x264. auto test-encodes to find a hardware encoder that works.
STORY_GPU true Draw on the GPU when one is available
STORY_PERIOD_HOURS 24 Story lifetime: 6, 12, 24 or 48
TG_API_ID, TG_API_HASH, TG_SESSION — User account for stories; see Group stories
DATA_DIR data State and videos

Docker

docker compose up -d --build

./data holds the state and videos. Without a GPU, the container draws and encodes on the CPU. With an NVIDIA GPU and the NVIDIA Container Toolkit on the host, uncomment the deploy block in docker-compose.yml. Run npm run tg:login on your own machine and copy TG_SESSION into the server's .env.

Troubleshooting

Symptom Cause and fix
Bot cannot see TELEGRAM_GROUP / chat not found The bot isn't in the group, or the id is from before Topics were enabled. Add the bot, then write in the group to log the current id.
… has no topics Enable Topics in the group settings.
could not create topic The bot lacks the Manage topics admin right. Grant it, or /bind existing topics.
Files listed as "too large for Telegram" Bot uploads are capped at 50 MB. Set up a local Bot API server and TELEGRAM_API_ROOT.
boost level allows no more stories right now Stories aren't unlocked for the group yet, or today's story limit is used up. More boosts raise the limit.
Attachments show as "couldn't be copied (… 403 …)" The token can't write repo contents. Give a fine-grained token Contents read/write, or use a classic token with repo scope.
MEDIA_FILE_INVALID when posting a story The upload wasn't the *.story.mp4 copy. Stories must be 720×1280 HEVC with a keyframe every second; npm run story handles this.
TG_SESSION is not logged in The session was revoked (for example under Settings → Devices). Run npm run tg:login again.

State lives in data/state.json. Deleting it starts over: a new starting point, and topics are re-created unless they're bound or set in .env.

Project layout

src/
  index.ts             entry point: setup, poll loop, bot
  config.ts            .env parsing
  state.ts             data/state.json: what is mirrored where
  github.ts            GitHub REST client
  telegram.ts          topics, posting, Telegram → GitHub (with attachments)
  ai.ts                OpenAI-compatible client, issue classification and triage
  format.ts            Markdown → Telegram HTML, conventional commits
  sync/issues.ts       GitHub issues and comments → Telegram, edits, relabel moves
  sync/releases.ts     releases → changelog, files, video
  story/storyboard.ts  commits → storyboard (AI + fallback)
  story/diff.ts        changed files and added UI strings for the AI
  story/types.ts       storyboard and scene types
  story/pipeline.ts    render → topic video → story
  story/publish.ts     story upload through the user account
  story/render/
    engine.ts          drawing, easing, text, icons, background
    scenes.ts          the fifteen animated UI templates
    compose.ts         timeline, motion blur, ffmpeg encoding, story copy
scripts/               preview, story, tg-login
assets/fonts/          Inter and JetBrains Mono (OFL)
fixtures/              sample storyboards (templates-storyboard.json shows every new template)

npm run typecheck checks everything with TypeScript.

License

Copyright © 2026 TFBosoN. All rights reserved. The source is published for viewing only; using, modifying or redistributing it requires written permission. See LICENSE. The bundled fonts are under the SIL Open Font License (assets/fonts/).