← back to Beverlyhillsvideos
social/poster/ig-graph.mjs
70 lines
// ig-graph.mjs — Instagram Graph API Reel publishing (3-step async flow).
// Docs: developers.facebook.com/docs/instagram-platform/content-publishing
import { cfg } from './env.mjs';
const base = () => `https://graph.facebook.com/${cfg.graphVersion}`;
async function gpost(pathPart, params) {
const url = new URL(`${base()}/${pathPart}`);
const body = new URLSearchParams({ ...params, access_token: cfg.accessToken });
const res = await fetch(url, { method: 'POST', body });
const json = await res.json();
if (!res.ok || json.error) {
throw new Error(`Graph POST ${pathPart} failed: ${JSON.stringify(json.error || json)}`);
}
return json;
}
async function gget(pathPart, fields) {
const url = new URL(`${base()}/${pathPart}`);
url.searchParams.set('fields', fields);
url.searchParams.set('access_token', cfg.accessToken);
const res = await fetch(url);
const json = await res.json();
if (!res.ok || json.error) {
throw new Error(`Graph GET ${pathPart} failed: ${JSON.stringify(json.error || json)}`);
}
return json;
}
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
// Step 1: create a REELS container pointing at the public video URL.
export async function createReelContainer({ videoUrl, caption }) {
const json = await gpost(`${cfg.igUserId}/media`, {
media_type: 'REELS',
video_url: videoUrl,
caption,
share_to_feed: 'true',
});
return json.id; // container id
}
// Step 2: poll the container until Instagram finishes transcoding.
export async function waitForContainer(containerId, { timeoutMs = 300000, everyMs = 8000 } = {}) {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
const { status_code, status } = await gget(containerId, 'status_code,status');
if (status_code === 'FINISHED') return true;
if (status_code === 'ERROR' || status_code === 'EXPIRED') {
throw new Error(`Container ${containerId} ${status_code}: ${status}`);
}
await sleep(everyMs);
}
throw new Error(`Container ${containerId} not FINISHED within ${timeoutMs}ms`);
}
// Step 3: publish the finished container to the feed.
export async function publishContainer(containerId) {
const json = await gpost(`${cfg.igUserId}/media_publish`, { creation_id: containerId });
return json.id; // published media id
}
// Full flow.
export async function postReel({ videoUrl, caption }) {
const containerId = await createReelContainer({ videoUrl, caption });
await waitForContainer(containerId);
const mediaId = await publishContainer(containerId);
return { containerId, mediaId };
}