Create instagram-reel-sync-phase4.md via n8n
This commit is contained in:
parent
1ebd2b8bd9
commit
01dcce3ffb
223
PBS/Tech/Projects/instagram-reel-sync-phase4.md
Normal file
223
PBS/Tech/Projects/instagram-reel-sync-phase4.md
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
---
|
||||||
|
project: instagram-reel-sync-phase4
|
||||||
|
type: project-plan
|
||||||
|
status: active
|
||||||
|
tags:
|
||||||
|
- pbs
|
||||||
|
- n8n
|
||||||
|
- instagram
|
||||||
|
- mysql
|
||||||
|
- automation
|
||||||
|
created: 2026-03-17
|
||||||
|
updated: 2026-03-17
|
||||||
|
path: PBS/Tech/Projects/
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 4 Handoff — Instagram Reel Detection & Sync
|
||||||
|
|
||||||
|
## Context for New Chat Session
|
||||||
|
|
||||||
|
This is a handoff document from the previous chat session. Paste this into the new
|
||||||
|
chat along with the standard project context to get up to speed quickly.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Current State — What's Already Built
|
||||||
|
|
||||||
|
### MySQL Schema (pbs_automation database)
|
||||||
|
|
||||||
|
**`pbs_recipes`:**
|
||||||
|
```sql
|
||||||
|
CREATE TABLE IF NOT EXISTS pbs_recipes (
|
||||||
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
post_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
recipe_title VARCHAR(500) NOT NULL,
|
||||||
|
recipe_url VARCHAR(500) NOT NULL,
|
||||||
|
keyword VARCHAR(100) NULL,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
UNIQUE KEY (post_id)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**`instagram_posts`:**
|
||||||
|
```sql
|
||||||
|
CREATE TABLE IF NOT EXISTS instagram_posts (
|
||||||
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
reel_id VARCHAR(50) NOT NULL,
|
||||||
|
pbs_post_id BIGINT UNSIGNED NULL,
|
||||||
|
instragram_caption TEXT NULL,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
UNIQUE KEY (reel_id),
|
||||||
|
FOREIGN KEY (post_id) REFERENCES pbs_recipes(post_id)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key schema decisions:**
|
||||||
|
- `post_id` is nullable in `instagram_posts` — NULL means unmatched reel
|
||||||
|
- `reel_id` is VARCHAR not BIGINT (Instagram ID integer overflow issue)
|
||||||
|
- Normalized — keyword and URL live in `pbs_recipes` only, JOIN at query time
|
||||||
|
- Foreign key constraint means `pbs_recipes` record must exist before `instagram_posts` insert
|
||||||
|
|
||||||
|
### WordPress → pbs_recipes Sync (Complete ✅)
|
||||||
|
- WPCode Lite PHP snippet fires on every post save (admin only)
|
||||||
|
- Payload: post_id, title, url, tags, categories
|
||||||
|
- HMAC signature via X-PBS-Signature header (raw body via X-PBS-Body base64)
|
||||||
|
- n8n workflow verifies signature, upserts into pbs_recipes
|
||||||
|
- Keyword populated from first WordPress tag if present
|
||||||
|
- COALESCE logic: only sets keyword if currently NULL (preserves manual edits)
|
||||||
|
|
||||||
|
### Instagram Comment Reply Workflow (Complete ✅)
|
||||||
|
- Handles incoming Instagram comment webhooks
|
||||||
|
- Hash verification → subscribe check → PBS message filter
|
||||||
|
- Looks up reel in `instagram_posts` table
|
||||||
|
- Joins to `pbs_recipes` for keyword and URL
|
||||||
|
- Sends DM + public reply on keyword match
|
||||||
|
- All dead ends notify Travis via Google Chat
|
||||||
|
|
||||||
|
### Notify Travis Workflow (Complete ✅)
|
||||||
|
- Webhook trigger at: https://n8n.plantbasedsoutherner.com/webhook/notify-pbschat
|
||||||
|
- Routes all alerts to Google Chat
|
||||||
|
- Payload format:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"title": "Alert title",
|
||||||
|
"message": "Detail message",
|
||||||
|
"timestamp": "={{ $now.toFormat('MMM dd, HH:mm:ss') }}"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4 Goal — Automate Instagram Reel Detection
|
||||||
|
|
||||||
|
### The Problem
|
||||||
|
When Jenny publishes a new Instagram Reel, someone currently has to manually:
|
||||||
|
1. Get the WordPress post ID
|
||||||
|
2. Add it to the reel caption
|
||||||
|
3. Add the reel ID to the `instagram_posts` table
|
||||||
|
|
||||||
|
### The Solution — Three-Tier Detection System
|
||||||
|
|
||||||
|
**Tier 1 — Comment Triggered (real time)**
|
||||||
|
When a comment comes in and `instagram_posts` lookup returns empty:
|
||||||
|
- Instead of just notifying Travis, trigger the "Fetch Reel Data" workflow
|
||||||
|
- Pass enough context to reply to the original commenter if reel is found
|
||||||
|
- First commenter may experience a few seconds delay — acceptable
|
||||||
|
|
||||||
|
**Tier 2 — Scheduled Poll (every 12 hours)**
|
||||||
|
- n8n Schedule Trigger polls Instagram media API
|
||||||
|
- Catches reels that haven't received a comment yet
|
||||||
|
- Proactively keeps table up to date
|
||||||
|
|
||||||
|
**Tier 3 — Manual Trigger (Content Hub)**
|
||||||
|
- "Sync Reels" button in PBS Content Hub Flask app
|
||||||
|
- Jenny or assistant can trigger on demand after publishing
|
||||||
|
|
||||||
|
### Queue and Retry Pattern (Option B)
|
||||||
|
When Tier 1 triggers:
|
||||||
|
1. Comment comes in → no record found → store original comment data
|
||||||
|
2. Trigger "Fetch Reel Data" workflow with comment context
|
||||||
|
3. If reel found → insert into `instagram_posts` → retry reply to original commenter
|
||||||
|
4. If reel not found → Notify Travis with details
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4 Architecture
|
||||||
|
|
||||||
|
### New Workflow — "Fetch Reel Data"
|
||||||
|
|
||||||
|
**Triggers:**
|
||||||
|
- Called from comment reply workflow (Tier 1) with single reel ID
|
||||||
|
- Called from Schedule Trigger (Tier 2) with last 20 media items
|
||||||
|
- Called from Content Hub webhook (Tier 3) manually
|
||||||
|
|
||||||
|
**Core loop logic (Loop Over Items node):**
|
||||||
|
```
|
||||||
|
[Get reels from Instagram API]
|
||||||
|
→ [Code Node: extract post_id from caption]
|
||||||
|
→ [Loop Over Items]
|
||||||
|
→ [MySQL SELECT: pbs_recipes WHERE post_id = extracted_id]
|
||||||
|
→ [IF: record found?]
|
||||||
|
→ TRUE → [MySQL UPSERT: instagram_posts]
|
||||||
|
→ FALSE → [Notify Travis: Bad/missing post ID in caption]
|
||||||
|
→ [After loop: if Tier 1 triggered → retry comment reply]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Handoff payload from comment reply workflow (Tier 1):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"resolved_media_id": "the reel ID",
|
||||||
|
"comment_text": "original comment text",
|
||||||
|
"from_username": "commenter username",
|
||||||
|
"comment_id": "needed to post public reply"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Instagram Media API Details
|
||||||
|
|
||||||
|
**No webhook exists for new reel publications** — confirmed via Meta docs research.
|
||||||
|
Polling is the only option.
|
||||||
|
|
||||||
|
**API endpoint:**
|
||||||
|
```
|
||||||
|
GET https://graph.facebook.com/v25.0/{ig-user-id}/media
|
||||||
|
?fields=id,caption,media_type,media_product_type,permalink,timestamp
|
||||||
|
&limit=20
|
||||||
|
&access_token={token}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key field:** `media_product_type === "REELS"` — do NOT use `media_type`,
|
||||||
|
it returns "VIDEO" for both regular videos and Reels.
|
||||||
|
|
||||||
|
**Rate limits:** Generous — 4,800 × account impressions/24hrs. Polling every
|
||||||
|
12 hours is negligible.
|
||||||
|
|
||||||
|
**Token:** Long-lived user access token (60-day expiry). Build monitoring
|
||||||
|
for token expiry — biggest operational risk.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key n8n Patterns Established
|
||||||
|
|
||||||
|
- **Always Output Data** on MySQL nodes so IF nodes can evaluate empty results
|
||||||
|
- **Check field exists** (`id exists`) not `length > 0` when Always Output Data is on
|
||||||
|
- **Google Chat HTTP Request** must use "Using Fields Below" not raw JSON
|
||||||
|
- **Message strings** built as single `={{ }}` expression with `+` concatenation
|
||||||
|
- **`$now.toFormat()`** works in test and live contexts (not `$execution.startedAt`)
|
||||||
|
- **MySQL query parameters** — do NOT use `=` prefix, plain comma separated values
|
||||||
|
- **Loop Over Items** — use explicit `$('Node Name').first().json` not `$json`
|
||||||
|
inside loops since `$json` only references immediate previous node
|
||||||
|
- **Set node before loops** — consolidate upstream data needed inside loop
|
||||||
|
so it's always accessible as `$json.field`
|
||||||
|
- **`reel_id` always VARCHAR** — Instagram IDs exceed JS safe integer limit
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Remaining Work After Phase 4
|
||||||
|
|
||||||
|
- [ ] Refactor comment reply workflow to split at `Check if we sent`
|
||||||
|
into separate "Instagram Reply Handler" workflow (cleaner logs)
|
||||||
|
- [ ] PBS Content Hub — Phase 5 planning session required before building
|
||||||
|
- [ ] Authelia SSO for admin tools (Portainer, n8n, Uptime Kuma, phpMyAdmin)
|
||||||
|
- [ ] Deploy all staging changes to production
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Infrastructure Quick Reference
|
||||||
|
|
||||||
|
- **Staging:** staging.plantbasedsoutherner.com
|
||||||
|
- **n8n:** n8n.plantbasedsoutherner.com
|
||||||
|
- **phpMyAdmin:** phpmyadmin.staging.plantbasedsoutherner.com
|
||||||
|
- **PBS-API:** internal Docker service at http://pbs-api:5000
|
||||||
|
- **MySQL database:** pbs_automation
|
||||||
|
- **Google Chat alerts:** via Notify Travis workflow webhook
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last Updated: March 17, 2026*
|
||||||
|
*Maintained by: Travis*
|
||||||
|
*Project: Plant Based Southerner — Instagram Automation Phase 4*
|
||||||
Loading…
Reference in New Issue
Block a user