← back to Norma
agents/instagram-agent/skills/post.js
102 lines
/**
* Instagram Post Skill
*
* Creates an Instagram feed post via Meta Graph API (Content Publishing API).
* Two-step flow: create media container, then publish it.
* Runs in simulation mode when IG_ACCESS_TOKEN is not set.
*
* Real endpoints:
* Step 1 — Create container:
* POST https://graph.facebook.com/v19.0/{ig_user_id}/media
* Params: image_url, caption, access_token
*
* Step 2 — Publish:
* POST https://graph.facebook.com/v19.0/{ig_user_id}/media_publish
* Params: creation_id, access_token
*/
const { loadCredentials } = require('../../shared/credentials');
const ig = require('./_ig-api');
const AGENT = 'instagram-agent';
const PLATFORM = 'instagram';
async function getCredentials() {
const creds = await loadCredentials('instagram');
return {
has: !!(creds?.IG_USER_ID && creds?.IG_ACCESS_TOKEN),
userId: creds?.IG_USER_ID || '',
accessToken: creds?.IG_ACCESS_TOKEN || '',
};
}
/**
* @param {Object} params - Request body
* @param {string} params.image_url - Public URL of the image to post
* @param {string} [params.caption] - Post caption text
* @param {string} [params.petition_url] - Petition URL to include in caption
* @param {string} [params.pipeline_id] - Pipeline entry ID to link
* @returns {Promise<Object>}
*/
module.exports = async function post(params) {
const imageUrl = params.image_url || '';
const caption = params.caption || params.text || '';
const { has: hasCreds, userId, accessToken } = await getCredentials();
const simulated = !hasCreds;
console.log(`[${AGENT}] Post skill invoked — simulation=${simulated}`);
console.log(`[${AGENT}] Caption: ${caption.substring(0, 100)}${caption.length > 100 ? '...' : ''}`);
if (imageUrl) console.log(`[${AGENT}] Image URL: ${imageUrl}`);
if (simulated) {
// --- Simulation mode ---
const simulatedContainerId = `ig_container_${Date.now()}`;
const simulatedMediaId = `ig_media_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`;
console.log(`[${AGENT}] SIMULATED post created: container=${simulatedContainerId}, media=${simulatedMediaId}`);
return {
media_id: simulatedMediaId,
container_id: simulatedContainerId,
caption,
image_url: imageUrl || null,
permalink: `https://www.instagram.com/p/${simulatedMediaId.substring(0, 11)}/`,
media_type: 'IMAGE',
simulated: true,
posted: false,
platform: PLATFORM,
pipeline_id: params.pipeline_id || null,
created_at: new Date().toISOString(),
};
}
// --- Real API call (Instagram API with Instagram Login) ---
if (!imageUrl) throw new Error('image_url is required to post to the feed');
// Step 1: create the image container
const containerId = await ig.createContainer(userId, accessToken, {
image_url: imageUrl,
caption,
});
console.log(`[${AGENT}] Post container created: ${containerId}`);
// Step 2: publish (image containers are ready immediately)
const mediaId = await ig.publishContainer(userId, accessToken, containerId);
const permalink = await ig.permalinkOf(mediaId, accessToken);
console.log(`[${AGENT}] Post published: media=${mediaId} ${permalink || ''}`);
return {
media_id: mediaId,
container_id: containerId,
caption,
image_url: imageUrl,
permalink,
media_type: 'IMAGE',
simulated: false,
posted: true,
platform: PLATFORM,
pipeline_id: params.pipeline_id || null,
created_at: new Date().toISOString(),
};
};