← 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(),
  };

};