Sign inSign up

pepebits/share-stories

By pepebits

•Updated 5 days ago

Reposts Telegram stories to Instagram via the official Content Publishing API

Image
0

1.7K

pepebits/share-stories repository overview

⁠share-stories

Reposts stories from monitored Telegram peers to Instagram, using Instagram's official Content Publishing API.

Source and full documentation: https://github.com/Pepebits/share-stories⁠

docker pull pepebits/share-stories

Multi-architecture (amd64, arm64). No native modules — SQLite comes from Node itself.


⁠Story audiences do not survive the crossing

Telegram stories have an audience — public, contacts, selected contacts, close friends. Instagram's publishing API has none: everything it publishes goes to all your followers, and there is no close-friends equivalent to publish into.

A close-friends story therefore arrives on Instagram public. The bridge cannot narrow it, only decline to carry it. It carries everything by default:

TELEGRAM_STORY_SCOPES=public   # only what was already open to everyone

Set that whenever TELEGRAM_MONITORED_PEERS names anyone but yourself.

⁠What it needs to work

Meta does not accept file uploads: it downloads each story from a URL you serve. So this container must sit behind a public HTTPS origin — a Cloudflare tunnel, Caddy, nginx, anything with a real certificate.

Port 8080 serves that media at single-use, unguessable URLs that are revoked as soon as Meta fetches them. Never publish it straight to the internet.

⁠Environment variables

⁠Required
VariableWhat it is
PUBLIC_BASE_URLThe public HTTPS address Meta resolves, e.g. https://stories.example.com. Not the container's port. Startup refuses a loopback or private address.
TELEGRAM_API_IDFrom my.telegram.org/apps⁠
TELEGRAM_API_HASHFrom the same place
TELEGRAM_PHONE_NUMBEROf the account that reads the stories, E.164 (+34600000000)
TELEGRAM_MONITORED_PEERSComma-separated. @username or username matches usernames only, digits match the peer id only, title:Some Name matches the display name (unsafe for peers you don't control)
INSTAGRAM_ACCOUNT_IDNumeric id of an Instagram Business or Creator account
INSTAGRAM_ACCESS_TOKENLong-lived token, starts with IGAA
⁠Optional
VariableDefaultWhat it does
TELEGRAM_SESSION_FILE./data/telegram-session.txtWhere the session written by the one-off login lives — see below. Also where the bridge saves it when Telegram rotates it.
TELEGRAM_SESSION_STRING—Seed for a first start without the session file. Read only while the file does not exist; the bridge writes the file on the first connection and the file wins from then on, since it follows Telegram's session rotation.
TELEGRAM_STORY_SCOPESallWhich story audiences to carry: public, contacts, selectedContacts, closeFriends, comma-separated, or all. A value that cannot be parsed falls back to public.
ALERT_AFTER_FAILURES3After this many consecutive publish failures, a warning is sent to the Telegram account's Saved Messages. 0 disables it.
MEDIA_SERVER_HOST127.0.0.1Set to 0.0.0.0 in Docker — loopback is unreachable from another container
MEDIA_SERVER_PORT8080Port the media server binds
MEDIA_URL_TTL_SECONDS600How long a media URL stays valid if Meta never fetches it
POLL_INTERVAL_SECONDS120How often Telegram is checked
INSTAGRAM_QUOTA_RESERVE0Publishes held back from the 100/24h quota, so you can still post by hand
INSTAGRAM_QUOTA_REFRESH_SECONDS600How long a quota reading is trusted
INSTAGRAM_TOKEN_FILE./data/instagram-token.jsonWhere the rotating token is stored
DATABASE_PATH./data/state.dbDeduplication database
LOG_LEVELinfoerror, warn, info, http, verbose, debug or silly (winston's own levels) — anything else fails at startup

⁠The volume matters

Mount /app/data. It holds the Telegram session, the rotating Instagram token and the deduplication database. Losing it means re-authenticating by hand and republishing every story still active.

⁠First run

The session cannot be obtained inside a detached container: Telegram sends a login code that has to be typed in. Run the one-off login on any machine with Node 24.19+:

git clone https://github.com/Pepebits/share-stories && cd share-stories
pnpm install && pnpm run login

Copy the resulting data/telegram-session.txt into the data/ directory this container mounts (mode 0600, owned by uid 1000 — the container's non-root user).

That file is an unscoped credential for the whole Telegram account — it can read every private message and send as you. Keep it out of images, out of version control, and out of anywhere it might be logged. It can be revoked instantly from Telegram → Settings → Devices.

⁠Example

services:
  bridge:
    image: pepebits/share-stories:latest
    restart: unless-stopped
    init: true
    env_file: .env
    environment:
      MEDIA_SERVER_HOST: 0.0.0.0
      PUBLIC_BASE_URL: https://stories.example.com
    volumes:
      - ./data:/app/data
    ports:
      # Loopback only: your reverse proxy reaches it, the internet does not.
      - '127.0.0.1:8080:8080'

GET /health returns 200 ok while Telegram is connected and 503 once the session drops, so the container goes unhealthy instead of idling. It discloses nothing about what is hosted.

⁠Instagram setup

Getting INSTAGRAM_ACCOUNT_ID and INSTAGRAM_ACCESS_TOKEN out of Meta's console is the fiddliest part, and its documentation contradicts its own dashboard in places. Step by step: docs/INSTAGRAM_SETUP.md⁠

⁠Tags

TagMeaning
1.0.0Exact version, never moves
1.0Newest patch of that minor series
latestNewest release

Pin 1.0.0 if you want reproducibility; use 1.0 to pick up patches.

⁠Licence

MIT

Tag summary

Content type

Image

Digest

sha256:09c058efc…

Size

53.9 MB

Last updated

5 days ago

docker pull pepebits/share-stories