← back to Beverlyhillsvideos

social/poster/README.md

57 lines

# BHV Instagram — daily poster (Graph API)

Posts **1 restaurant film per day** to [@beverlyhillsvideos](https://instagram.com/beverlyhillsvideos)
via the official Instagram Graph API. Rotates least-recently-posted through the 44-film
dining library (`data/films.json`), so it cycles ~every 6 weeks with no repeats.

Stores are **not** wired yet — no store media exists (decision 2026-08-06). Restaurants only for now.

## Files
| file | role |
|---|---|
| `post-daily.mjs` | entry point — pick next film → build caption → post (or dry-run) → ledger |
| `ig-graph.mjs` | Graph API Reel flow: create container → poll status → publish |
| `caption.mjs` | on-brand caption + rotating hashtags (from `../LAUNCH-PACK.md`) |
| `env.mjs` | resolves config from env, falling back to the secrets master `.env` |
| `run.sh` | launchd/cron wrapper (logs to `post.log`) |
| `com.steve.bhv-ig-daily.plist` | **draft** launchd job, 11:30 AM PT — not installed |
| `ledger.jsonl` | append-only record of every pick (dry + live) |

## Safety model
Nothing posts for real unless **all three** are true:
`BHV_IG_LIVE=1` **and** `BHV_IG_USER_ID` set **and** `BHV_IG_ACCESS_TOKEN` set.
Otherwise it DRY-RUNs (prints exactly what it would post) and records a dry ledger entry.

```sh
node social/poster/post-daily.mjs            # dry-run, always safe
BHV_IG_LIVE=1 node social/poster/post-daily.mjs   # live (needs token)
```

## One-time Meta setup (required before first live post) — Steve-gated
The Graph API cannot use the account password. It needs a Business/Creator account + a
Facebook Page + a long-lived token. Password (`BHV_INSTAGRAM_PASSWORD`) is stored only for
account recovery / manual login — the poster never uses it.

1. In the IG app: **Settings → Account type → switch to Business (or Creator)**.
2. Create/link a **Facebook Page** for Beverly Hills Videos and connect it to the IG account.
3. Reuse the existing Meta app (`META_APP_ID` / `META_APP_SECRET` in secrets) or make one.
   Add the **Instagram Graph API** product; scopes: `instagram_basic`,
   `instagram_content_publish`, `pages_show_list`, `business_management`.
4. Generate a **long-lived** access token for the Page, and get the **IG Business user id**
   (`GET /me/accounts` → page → `instagram_business_account`).
5. Route both via the secrets manager (auto-fans to the master `.env`):
   ```sh
   node ~/Projects/secrets-manager/cli.js add BHV_IG_ACCESS_TOKEN <long-lived-token>
   node ~/Projects/secrets-manager/cli.js add BHV_IG_USER_ID <ig-business-user-id>
   ```
   Long-lived tokens last ~60 days — a refresh canary should be added before go-live.
6. Prove it live once: `BHV_IG_LIVE=1 node social/poster/post-daily.mjs`
7. Install the daily job:
   ```sh
   cp social/poster/com.steve.bhv-ig-daily.plist ~/Library/LaunchAgents/
   launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.steve.bhv-ig-daily.plist
   ```

Steps 1–2 (and possibly 3–4 in the Meta portal) need Steve's interactive login; the
`portal-driver` skill can drive the clicking once Steve is authenticated.