Skip to content

monashcoding/reels-notify

Repository files navigation

reels-notify — Instagram Reel → Discord bot

Polls the shared MAC Instagram account for new Reels and forwards each one to a MAC Discord channel. Detection is poll-based (Instagram has no "new post" webhook). One small Node worker, state in Postgres, output via a Discord channel webhook.

Part of the MAC Suite. Standalone worker — does not depend on mac-auth.

  • Uses the Instagram API with Instagram Login (graph.instagram.com), so it needs no Facebook Page / Business Portfolio — only the shared Instagram account.
  • The rotating Instagram token lives in Postgres (ig_token), never in env. It self-refreshes daily and survives downtime of up to ~60 days.
  • Fails loud: a lapsed/failed token refresh posts to a Discord ops channel.
  • Least privilege: requests instagram_business_basic (read-only) only.

Account-hygiene prerequisites (DO THESE FIRST)

The bot is worthless if the account behind it gets orphaned. Flag these to the current Marketing/Media Director before deploying:

  1. Move the Instagram 2FA / recovery phone off a personal number. Every API path authenticates against this account; if it locks, none survive. Set a committee-controlled number and marketing@monashcoding.com as recovery email.
  2. Register the Meta Developer app under marketing@monashcoding.com (or projects@monashcoding.com) — never a personal Meta login.
  3. Do NOT create a Facebook account for MAC. (Club policy; this flow doesn't need one.)

Meta app setup (one-time, by a marketing officer)

  1. Create a Meta Developer app, type Business, under the institutional inbox.
  2. Add Instagram product → Instagram API with Instagram Login (the graph.instagram.com path — not "Facebook Login for Business").
  3. Set the OAuth redirect URI to https://ig-bot.monashcoding.com/auth/callback.
  4. Add the MAC Instagram account as a connected/tester account so it can be authorized in dev mode.
  5. Requested scope: instagram_business_basic only.
  6. Checkpoint: confirm whether read-only access to your own connected account works in dev mode (account added as tester) or needs App Review / Advanced Access. Basic read on an owned account generally works without full review — verify against current Meta gating before assuming.

Record the App ID and App Secret → they go into env (IG_APP_ID, IG_APP_SECRET).


Architecture

Single Node 22 container. Express serves three routes; two node-cron jobs work.

Routes

  • GET /auth/start → redirects to Instagram's OAuth authorize URL. Used once during bootstrap.
  • GET /auth/callback → exchanges ?code for a long-lived token, persists it, shows a "connected ✓" page.
  • GET /healthz → 200 + token status (age, expiry) for uptime checks.

Cron jobs

  • Poll (POLL_CRON, default every 10 min): fetch recent media, filter to the configured product types, post any not-yet-posted reels to Discord.
  • Token refresh (REFRESH_CRON, daily): if the token is older than REFRESH_IF_OLDER_THAN_DAYS, refresh and persist. Self-healing after downtime (works as long as downtime < 60 days).

Schema is created idempotently at startup (ensureSchema in src/db/index.ts) — no manual migration step. Tables: ig_token, posted_reels, bot_state.


Environment variables

Copy .env.example.env and fill in. Note the IG token is not an env var — it is written to ig_token by the OAuth bootstrap and thereafter self-refreshed.

Var Purpose
IG_APP_ID, IG_APP_SECRET Meta app credentials (static).
IG_REDIRECT_URI https://ig-bot.monashcoding.com/auth/callback.
IG_SCOPE instagram_business_basic (default).
DISCORD_WEBHOOK_URL Channel for reel posts.
DISCORD_OPS_WEBHOOK_URL Ops/alerts channel (refresh failures).
POLL_CRON Reel poll frequency (default */10 * * * *).
REFRESH_CRON Daily token-refresh check (default 0 4 * * *).
REFRESH_IF_OLDER_THAN_DAYS Refresh when token age exceeds this (1–59, default 45).
POST_MEDIA_PRODUCT_TYPES Comma-sep; default REELS. Set REELS,FEED to post all posts.
DATABASE_URL Postgres connection string.
POSTGRES_USER/PASSWORD/DB Seed the bundled compose Postgres.

Deploy (Dokploy + Traefik on the Oracle VM)

cp .env.example .env    # fill in secrets
docker compose up -d

docker-compose.yml ships the worker plus a dedicated Postgres with a named volume (igbot_pgdata), so the app is fully self-contained. To reuse the existing suite Postgres instead, delete the db service + volume and point DATABASE_URL at that instance.

Traefik labels expose the worker on ig-bot.monashcoding.com for the OAuth callback + healthcheck, with TLS via the letsencrypt certresolver (adjust the resolver name to match your Traefik setup). Register the app's /auth/callback as the Meta app redirect URI.


Bootstrap runbook (for the next committee)

  1. Deploy; confirm https://ig-bot.monashcoding.com/healthz responds (reports "token":"missing").
  2. A marketing officer opens https://ig-bot.monashcoding.com/auth/start, logs in with the shared MAC Instagram credentials (2FA code from whoever holds it), and authorizes.
  3. The callback stores the long-lived token in ig_token. /healthz now shows a valid token + expiry.
  4. The first poll seeds posted_reels silently (no posts) so the historical back-catalogue is never dumped into the channel. Subsequent new reels post to Discord.
  5. The token self-refreshes daily. If it ever lapses (service down > 60 days), an ops alert fires and you repeat step 2 — a ~5-minute self-serve recovery, no Meta support needed.

Local development

npm install
cp .env.example .env     # set DATABASE_URL to a local Postgres
npm run dev              # tsx watch
# in another shell:
open http://localhost:3000/healthz

npm run typecheck / npm run build compile the TypeScript to dist/.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors