← back to Norma
agents/instagram-agent/skills/discover.js
133 lines
/**
* Instagram Discover Skill
*
* Fetches recent media and performs hashtag search on Instagram.
* Runs in simulation mode when IG_ACCESS_TOKEN is not set.
*
* Real endpoints:
* Recent media:
* GET https://graph.facebook.com/v19.0/{ig_user_id}/media
* Params: fields=id,caption,media_type,media_url,permalink,timestamp,like_count,comments_count&limit=N&access_token=TOKEN
*
* Hashtag search:
* GET https://graph.facebook.com/v19.0/ig_hashtag_search?q={hashtag}&user_id={ig_user_id}&access_token=TOKEN
* GET https://graph.facebook.com/v19.0/{hashtag_id}/recent_media?user_id={ig_user_id}&fields=id,caption,media_type,permalink,timestamp&access_token=TOKEN
*/
const AGENT = 'instagram-agent';
const PLATFORM = 'instagram';
function hasCredentials() {
return !!(process.env.IG_USER_ID && process.env.IG_ACCESS_TOKEN);
}
/**
* @param {Object} params - Request body
* @param {number} [params.limit=10] - Number of recent media items to fetch
* @param {string} [params.hashtag] - Hashtag to search (without #)
* @param {boolean} [params.push_to_pulse=true] - Push results to Pulse
* @returns {Promise<Object>}
*/
module.exports = async function discover(params) {
const limit = Math.min(params.limit || 10, 50);
const hashtag = params.hashtag || '';
const simulated = !hasCredentials();
console.log(`[${AGENT}] Discover skill invoked — simulation=${simulated}, limit=${limit}`);
if (hashtag) console.log(`[${AGENT}] Hashtag search: #${hashtag}`);
if (simulated) {
// --- Simulation mode: return realistic-looking data ---
const media = Array.from({ length: Math.min(limit, 5) }, (_, i) => ({
id: `ig_media_${Date.now()}_${i}`,
caption: [
'Every signature counts. Join thousands who are making their voices heard. Link in bio. #CivicAction #Advocacy',
'Behind the scenes of our latest campaign. The team is working around the clock. #NonprofitLife',
'BREAKING: Our petition reached 25,000 signatures in just 48 hours! #PeoplesPower',
'Your story matters. Share why this cause is important to you in the comments below.',
'New campaign alert! Head to our bio link to learn more and take action today. #TakeAction',
][i],
media_type: ['IMAGE', 'IMAGE', 'CAROUSEL_ALBUM', 'IMAGE', 'VIDEO'][i],
media_url: `https://example.com/media/simulated_${i}.jpg`,
permalink: `https://www.instagram.com/p/sim${Date.now()}_${i}/`,
timestamp: new Date(Date.now() - i * 7200000).toISOString(),
like_count: Math.floor(Math.random() * 500) + 50,
comments_count: Math.floor(Math.random() * 60) + 5,
}));
const hashtagResults = hashtag
? Array.from({ length: 3 }, (_, i) => ({
id: `ig_ht_${Date.now()}_${i}`,
caption: `Community post about #${hashtag} — sharing because this matters.`,
media_type: 'IMAGE',
permalink: `https://www.instagram.com/p/ht_sim_${i}/`,
timestamp: new Date(Date.now() - i * 3600000).toISOString(),
}))
: [];
console.log(`[${AGENT}] SIMULATED discover: ${media.length} media, ${hashtagResults.length} hashtag results`);
return {
count: media.length,
media,
hashtag_search: hashtag
? { hashtag, count: hashtagResults.length, results: hashtagResults }
: null,
simulated: true,
platform: PLATFORM,
fetched_at: new Date().toISOString(),
};
}
// --- Real API call (commented out until credentials are configured) ---
const igUserId = process.env.IG_USER_ID;
const accessToken = process.env.IG_ACCESS_TOKEN;
// Fetch recent media
const fields = 'id,caption,media_type,media_url,permalink,timestamp,like_count,comments_count';
const mediaUrl = `https://graph.facebook.com/v19.0/${igUserId}/media?fields=${fields}&limit=${limit}&access_token=${accessToken}`;
const mediaResponse = await fetch(mediaUrl);
const mediaData = await mediaResponse.json();
if (mediaData.error) {
throw new Error(`Instagram API error (media): ${mediaData.error.message}`);
}
let hashtagResults = null;
if (hashtag) {
// Step 1: Get hashtag ID
const htSearchUrl = `https://graph.facebook.com/v19.0/ig_hashtag_search?q=${encodeURIComponent(hashtag)}&user_id=${igUserId}&access_token=${accessToken}`;
const htSearchResponse = await fetch(htSearchUrl);
const htSearchData = await htSearchResponse.json();
if (htSearchData.data && htSearchData.data.length > 0) {
const hashtagId = htSearchData.data[0].id;
// Step 2: Get recent media for hashtag
const htMediaUrl = `https://graph.facebook.com/v19.0/${hashtagId}/recent_media?user_id=${igUserId}&fields=id,caption,media_type,permalink,timestamp&access_token=${accessToken}`;
const htMediaResponse = await fetch(htMediaUrl);
const htMediaData = await htMediaResponse.json();
hashtagResults = {
hashtag,
hashtag_id: hashtagId,
count: htMediaData.data?.length || 0,
results: htMediaData.data || [],
};
}
}
return {
count: mediaData.data.length,
media: mediaData.data,
hashtag_search: hashtagResults,
paging: mediaData.paging || null,
simulated: false,
platform: PLATFORM,
fetched_at: new Date().toISOString(),
};
};