- TypeScript 99.5%
- Dockerfile 0.3%
- JavaScript 0.2%
|
|
||
|---|---|---|
| assets/fonts | ||
| docs | ||
| fixtures | ||
| scripts | ||
| src | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| docker-compose.yml | ||
| Dockerfile | ||
| ecosystem.config.cjs | ||
| LICENSE | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
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.

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 setFFMPEG_PATH - A GitHub token that can read the repo and write issues (classic token with
reposcope, 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
npm install- Copy
.env.exampleto.envand fill in the first block. See Configuration. - 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.
- Enable Topics in the group settings. The group id then starts with
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.
- Create an app at https://my.telegram.org → API development tools, and put
TG_API_IDandTG_API_HASHin.env. - Run
npm run tg:loginand 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
rto resend it another way.
- 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:
-
Commits. For a stable release, everything since the previous stable release.
-
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 listCards; one is tapped, lifts and gets a badge toggleSettings rows; a switch flips on and its hint expands editorCode or config typing itself out, then a status badge statusA big button that is tapped, spins, and turns green with a live timer menuA pointer right-clicks a card and a context menu opens dialogA sheet slides up with a message and two buttons fixesBugs turn into checkmarks metricBefore/after bars grow and count up, then a badge ("3× faster") pops; only for real numbers searchA query types into a search field; other rows filter away and matches get highlighted progressProgress bars fill at different speeds and turn into checks notifyA notification banner drops in, expands, and its action button is tapped tabsA tab is tapped in a segmented bar and its content slides in terminalA command types itself and its output prints line by line themeA sun button is tapped and the screen floods into dark mode dragA 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.
-
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.
-
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/).