https://github.com/slgfire/ezgramwatch
Discord webhook bot that mirrors Instagram Business/Creator posts and Reels to Discord via the official Meta Graph API.
https://github.com/slgfire/ezgramwatch
Last synced: about 1 month ago
JSON representation
Discord webhook bot that mirrors Instagram Business/Creator posts and Reels to Discord via the official Meta Graph API.
- Host: GitHub
- URL: https://github.com/slgfire/ezgramwatch
- Owner: slgfire
- License: mit
- Created: 2026-05-17T20:51:06.000Z (about 2 months ago)
- Default Branch: main
- Last Pushed: 2026-05-17T23:06:39.000Z (about 2 months ago)
- Last Synced: 2026-05-17T23:15:46.829Z (about 2 months ago)
- Language: TypeScript
- Size: 209 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
[](https://github.com/slgfire/ezgramwatch/actions/workflows/build.yml)
[](https://github.com/slgfire/ezgramwatch/actions/workflows/docker.yml)
[](https://github.com/slgfire/ezgramwatch/releases)
[](LICENSE)
**๐ Automatically forwards your Instagram Business posts and Reels to Discord via the official Meta Graph API โ no scraping, fully ToS-compliant**
---
> **Heads up:** This bot only works with Instagram accounts **you own** and have connected to a Meta Developer App. Monitoring other people's profiles is not possible through the official API.
## Features
- ๐ธ Detects new **posts, videos, and Reels** on a configurable polling interval
- ๐ผ๏ธ **Carousel albums** are sent as a multi-image Discord gallery (up to 10 images per message)
- ๐ **Silent first run** โ existing posts are imported without flooding Discord
- ๐ **Automatic token refresh** 7 days before expiry (requires Meta App credentials)
- ๐๏ธ SQLite-backed deduplication โ no duplicate posts after restarts
- ๐ณ Single Docker Compose command to run
## Prerequisites
Before you start, you'll need:
- A **Meta Developer account** โ [developers.facebook.com](https://developers.facebook.com)
- An **Instagram Business or Creator account** linked to a Facebook Page
- A **Discord server** where you have permission to create Webhooks
- **Docker** and **Docker Compose** installed on your server
---
## Setup Guide
### Step 1 โ Create a Discord Webhook
1. Open your Discord server and go to the channel where posts should appear.
2. Click the **gear icon โ๏ธ** next to the channel name โ **Integrations** โ **Webhooks**.
3. Click **New Webhook**, give it a name (e.g. `ezgramwatch`), and optionally set an avatar.
4. Click **Copy Webhook URL**.
Save this URL โ you'll need it as `DISCORD_WEBHOOK_URL`.
---
### Step 2 โ Create a Meta Developer App
1. Go to [developers.facebook.com](https://developers.facebook.com) and sign in.
2. Click **My Apps** โ **Create App**.
3. Choose **Business** as the app type, give it a name, and confirm.
4. On the App Dashboard, click **Add Product** and add **Instagram Graph API**.
> **App Mode:** Your app starts in Development mode. This is fine for personal use โ you can connect your own account as a Tester without going through App Review.
---
### Step 3 โ Add yourself as Developer and connect your Instagram account
#### 3a. Add your Facebook account as Developer
1. In the App Dashboard, go to **App Roles** (in the left sidebar).
2. Click **Add People**, search for your Facebook account, and assign the **Developer** role.
> **Why?** The App starts with you as Admin, but connected Facebook accounts (e.g. a second account you use for Instagram) need to be explicitly added as Developer. Without this you'll see *"Entwickler-Rolle nicht ausreichend"* (Developer role insufficient) when connecting Instagram.
#### 3b. Connect your Instagram account as Tester
1. In the App Dashboard, go to **Instagram Graph API โ Settings**.
2. Click **Add Instagram Tester** and enter your Instagram username.
3. Open Instagram on your phone โ **Settings โ Apps and Websites โ Tester Invites** โ Accept the invitation.
Your Instagram account is now linked to the app.
---
### Step 4 โ Get a Long-Lived Access Token
#### 4a. Generate a short-lived token in Graph API Explorer
1. Open the [Graph API Explorer](https://developers.facebook.com/tools/explorer).
2. Select your app from the top-right dropdown.
3. Click **Generate Access Token**.
4. In the permission dialog, make sure at least one of these is checked:
- `instagram_business_basic` *(newer apps โ recommended)*
- `instagram_basic` + `pages_read_engagement` *(older app setup)*
5. Click **Generate Access Token** and confirm in the popup. Copy the token shown.
#### Option A: Dashboard token (newer apps)
Newer Meta apps offer a token generator directly in the App Dashboard under **Instagram Graph API โ API Setup โ Generate Token**. This produces a **long-lived token (~60 days) directly** โ no exchange needed.
**Get the exact expiry timestamp for `.env`:**
1. Open the [Token Debugger](https://developers.facebook.com/tools/debug/accesstoken)
2. Paste your token โ click **Debug**
3. Find the row **"Ablaufdatum"** โ copy the number before the parenthesis (e.g. `1784580703`)
4. Add it to `.env`: `INSTAGRAM_TOKEN_EXPIRES_AT=1784580703`
> **Tip:** The bot auto-refreshes the token automatically 7 days before expiry โ no extra credentials needed.
---
### Step 5 โ Find your Instagram User ID
You need the numeric ID of your Instagram account (not the username).
**Easiest way:** Call the API directly in your browser or with curl:
```
https://graph.instagram.com/me?fields=id,username&access_token=YOUR_TOKEN
```
The `id` in the response is your Instagram User ID.
Use it in `INSTAGRAM_ACCOUNTS` like this:
```
INSTAGRAM_ACCOUNTS=17841400000000001:mychannel
```
The `:mychannel` part is an optional display alias shown in the logs. If omitted, the bot fetches your actual username from the API.
---
### Step 6 โ Configure and run
```bash
git clone https://github.com/slgfire/ezgramwatch
cd ezgramwatch
cp .env.example .env
```
Open `.env` and fill in the required values:
```env
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/...
INSTAGRAM_ACCESS_TOKEN=EAABsb...
INSTAGRAM_ACCOUNTS=17841400000000001:myaccount
```
Then start the bot:
```bash
mkdir -p data
docker compose up -d
docker compose logs -f
```
On first start you'll see something like:
```
{"msg":"poll.account","fetched":12,"new":12,"posted":0}
```
All existing posts are silently imported. Only new posts from this point on will be forwarded to Discord.
---
## Configuration Reference
| Variable | Required | Default | Description |
|---|---|---|---|
| `DISCORD_WEBHOOK_URL` | โ
| โ | Full Discord Webhook URL |
| `INSTAGRAM_ACCESS_TOKEN` | โ
| โ | Long-lived User Access Token (`instagram_business_basic`) |
| `INSTAGRAM_ACCOUNTS` | โ
| โ | `[:],โฆ` comma-separated |
| `INSTAGRAM_TOKEN_EXPIRES_AT` | โ | โ | Unix timestamp of token expiry (from Token Debugger โ "Ablaufdatum") โ improves auto-refresh accuracy |
| `POLL_INTERVAL_SECONDS` | โ | `300` | How often to check for new posts (seconds) |
| `POST_EXISTING_ON_FIRST_RUN` | โ | `false` | Set `true` to post up to `FIRST_RUN_POST_LIMIT` existing posts on first start |
| `FIRST_RUN_POST_LIMIT` | โ | `10` | Max posts sent when `POST_EXISTING_ON_FIRST_RUN=true` |
| `CAPTION_PREVIEW_CHARS` | โ | `300` | Characters of caption shown in the embed |
| `MEDIA_FETCH_LIMIT` | โ | `25` | Items fetched per poll (max 100) |
| `LOG_LEVEL` | โ | `info` | Log level: `trace` `debug` `info` `warn` `error` `fatal` |
| `LOG_PRETTY` | โ | `true`ยน | Human-readable logs; set `false` for JSON (e.g. log aggregation) |
| `DATABASE_PATH` | โ | `/data/bot.sqlite` | SQLite path inside the container |
| `GRAPH_API_VERSION` | โ | `v21.0` | Meta Graph API version |
ยน Default `true` set in `compose.yaml`; override with `LOG_PRETTY=false` in `.env` for JSON output.
### Monitoring multiple accounts
Separate accounts with commas:
```env
INSTAGRAM_ACCOUNTS=17841400000000001:brand,17841400000000002:personal
```
---
## Volume & Permissions
The container runs as the `node` user (uid=1000). Make sure the `./data` directory is writable:
```bash
sudo chown -R 1000:1000 ./data
```
The SQLite database in `./data` persists across container restarts and image updates.
---
## Updating
```bash
docker compose pull
docker compose up -d
```
---
## Troubleshooting
**"Developer role insufficient" (Entwickler-Rolle nicht ausreichend)**
Your Facebook account is not listed as Developer in the App. Go to **App Roles** in the left sidebar of the App Dashboard and add it there (see Step 3a).
**"Tester Invites" not visible in Instagram settings**
Make sure your Instagram account is set to **Business** or **Creator** mode. Personal accounts cannot be connected to the Graph API. Change this in the Instagram app under **Settings โ Account โ Switch to Professional Account**.
**Bot logs a 401 or auth error**
Re-generate the token in the App Dashboard under **Instagram Graph API โ API Setup** and make sure `instagram_business_basic` is included. Paste the new token into `INSTAGRAM_ACCESS_TOKEN` in your `.env` and restart the container.
**`Fatal: unable to open database file`**
The container cannot write to the `./data` directory. Either fix ownership (`sudo chown -R 1000:1000 ./data`) or set `user: "root"` in `compose.yaml`.
---
## Known Limitations
- **Own accounts only** โ the Graph API does not provide access to third-party profiles.
- **Stories not supported** โ they use a separate endpoint with a 24-hour lifetime and are out of scope.
- **60-day token expiry** โ the bot renews automatically via `ig_refresh_token` 7 days before expiry.
- **Rate limits** โ 200 API calls/hour (Standard tier). With the default `POLL_INTERVAL_SECONDS=300` and a small number of accounts this is not an issue.
See [`.ai/API_LIMITATIONS.md`](.ai/API_LIMITATIONS.md) for the full list.
---
## License
MIT โ see [LICENSE](LICENSE).