Skip to content

Instagram MCP Server

This document provides comprehensive documentation for the Instagram Model Context Protocol (MCP) server integration in Inttrest, enabling event discovery from Instagram posts and stories.

🎯 Overview

The Instagram MCP server leverages web scraping and automation to discover events from Instagram posts, stories, and profiles. It provides intelligent content analysis to extract event information from social media posts.

graph TB
    subgraph "Instagram MCP Architecture"
        A[MCP Client] --> B[Instagram MCP Server]
        B --> C[Browser Service]
        B --> D[Profile Scraper]
        B --> E[Content Analyzer]

        C --> F[Puppeteer Browser]
        C --> G[Session Management]
        C --> H[Anti-Detection]

        D --> I[Post Extraction]
        D --> J[Story Analysis]
        D --> K[Bio Parsing]

        E --> L[AI Content Analysis]
        E --> M[Event Detection]
        E --> N[Location Extraction]

        L --> O[OpenAI API]
        M --> P[Event Classification]
        N --> Q[Geocoding Service]
    end

    subgraph "Inttrest Integration"
        R[AI Chat Interface] --> A
        S[Event Discovery] --> A
        T[Social Feed] --> A

        P --> U[Event Database]
        Q --> V[Map Integration]
        N --> W[Real-time Updates]
    end

    style B fill:#e3f2fd
    style C fill:#e8f5e8
    style E fill:#fff3e0
    style O fill:#fce4ec

📦 Installation & Setup

Prerequisites

  • Node.js 18+ with npm/pnpm
  • Chrome/Chromium browser for Puppeteer
  • OpenAI API Key for content analysis
  • Instagram Account (optional, for authenticated scraping)

Installation

# Navigate to Instagram MCP server directory
cd mcp_servers/instagram-server-next-mcp

# Install dependencies
npm install
# or
pnpm install

# Install Puppeteer browser
npx puppeteer browsers install chrome

# Build the server
npm run build

Configuration

Create a .env file in the instagram-server-next-mcp directory:

# Instagram Configuration
INSTAGRAM_USERNAME=your_instagram_username
INSTAGRAM_PASSWORD=your_instagram_password
INSTAGRAM_SESSION_FILE=./sessions/instagram_session.json

# Browser Configuration
BROWSER_HEADLESS=true
BROWSER_TIMEOUT=30000
BROWSER_VIEWPORT_WIDTH=1920
BROWSER_VIEWPORT_HEIGHT=1080

# OpenAI Configuration for Content Analysis
OPENAI_API_KEY=your_openai_api_key_here
OPENAI_MODEL=gpt-4-turbo-preview

# Proxy Configuration (optional)
PROXY_HOST=
PROXY_PORT=
PROXY_USERNAME=
PROXY_PASSWORD=

# Rate Limiting
RATE_LIMIT_REQUESTS=10
RATE_LIMIT_WINDOW=60000  # 1 minute
CONCURRENT_REQUESTS=3

# Cache Configuration
CACHE_TTL=1800000  # 30 minutes
CACHE_MAX_SIZE=500

# Logging
LOG_LEVEL=info
LOG_FILE=instagram-mcp.log

# Anti-Detection
USER_AGENT="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"
STEALTH_MODE=true
RANDOM_DELAYS=true

Package Dependencies

{
  "name": "instagram-mcp-server",
  "version": "1.0.0",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.17.4",
    "puppeteer": "^22.0.0",
    "puppeteer-extra": "^3.3.6",
    "puppeteer-extra-plugin-stealth": "^2.11.2",
    "openai": "^4.20.1",
    "cheerio": "^1.0.0-rc.12",
    "node-cache": "^5.1.2",
    "winston": "^3.11.0",
    "zod": "^4.1.5",
    "dotenv": "^16.3.1",
    "axios": "^1.6.0",
    "uuid": "^9.0.1"
  },
  "devDependencies": {
    "@types/node": "^20.0.0",
    "@types/puppeteer": "^7.0.4",
    "typescript": "^5.0.0",
    "tsx": "^4.0.0",
    "vitest": "^1.0.0"
  }
}

🏗️ Server Implementation

Core Server Structure

// src/instagram-mcp-server.ts
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js'
import { BrowserService } from './services/browser-service.js'
import { ProfileScraper } from './services/profile-scraper.js'
import { ContentAnalyzer } from './services/content-analyzer.js'
import { Logger } from './utils/logger.js'
import { z } from 'zod'

const logger = new Logger('InstagramMCP')

class InstagramMCPServer {
  private server: Server
  private browserService: BrowserService
  private profileScraper: ProfileScraper
  private contentAnalyzer: ContentAnalyzer

  constructor() {
    this.server = new Server(
      {
        name: 'instagram-mcp-server',
        version: '1.0.0',
      },
      {
        capabilities: {
          tools: {},
        },
      }
    )

    this.browserService = new BrowserService()
    this.profileScraper = new ProfileScraper(this.browserService)
    this.contentAnalyzer = new ContentAnalyzer()

    this.setupToolHandlers()
  }

  private setupToolHandlers() {
    // List available tools
    this.server.setRequestHandler(ListToolsRequestSchema, async () => {
      return {
        tools: [
          {
            name: 'search_instagram_events',
            description: 'Search for events on Instagram using hashtags and locations',
            inputSchema: {
              type: 'object',
              properties: {
                hashtags: {
                  type: 'array',
                  items: { type: 'string' },
                  description: 'Hashtags to search for event-related content'
                },
                location: {
                  type: 'string',
                  description: 'Location to search for events'
                },
                date_range: {
                  type: 'object',
                  properties: {
                    start: { type: 'string' },
                    end: { type: 'string' }
                  },
                  description: 'Date range for event search'
                },
                max_posts: {
                  type: 'number',
                  minimum: 1,
                  maximum: 100,
                  description: 'Maximum number of posts to analyze'
                }
              },
              required: []
            }
          },
          {
            name: 'analyze_instagram_profile',
            description: 'Analyze an Instagram profile for event-related content',
            inputSchema: {
              type: 'object',
              properties: {
                username: {
                  type: 'string',
                  description: 'Instagram username to analyze'
                },
                include_stories: {
                  type: 'boolean',
                  description: 'Whether to include story analysis'
                },
                max_posts: {
                  type: 'number',
                  minimum: 1,
                  maximum: 50,
                  description: 'Maximum number of recent posts to analyze'
                }
              },
              required: ['username']
            }
          },
          {
            name: 'extract_event_from_post',
            description: 'Extract event information from a specific Instagram post',
            inputSchema: {
              type: 'object',
              properties: {
                post_url: {
                  type: 'string',
                  description: 'URL of the Instagram post'
                },
                deep_analysis: {
                  type: 'boolean',
                  description: 'Whether to perform deep AI analysis of the content'
                }
              },
              required: ['post_url']
            }
          },
          {
            name: 'monitor_instagram_accounts',
            description: 'Monitor specific Instagram accounts for new event posts',
            inputSchema: {
              type: 'object',
              properties: {
                usernames: {
                  type: 'array',
                  items: { type: 'string' },
                  description: 'List of usernames to monitor'
                },
                keywords: {
                  type: 'array',
                  items: { type: 'string' },
                  description: 'Keywords to look for in posts'
                },
                check_interval: {
                  type: 'number',
                  description: 'Interval in minutes to check for new posts'
                }
              },
              required: ['usernames']
            }
          }
        ]
      }
    })

    // Handle tool calls
    this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
      try {
        const { name, arguments: args } = request.params

        switch (name) {
          case 'search_instagram_events':
            return await this.handleSearchInstagramEvents(args)

          case 'analyze_instagram_profile':
            return await this.handleAnalyzeProfile(args)

          case 'extract_event_from_post':
            return await this.handleExtractEventFromPost(args)

          case 'monitor_instagram_accounts':
            return await this.handleMonitorAccounts(args)

          default:
            throw new Error(`Unknown tool: ${name}`)
        }
      } catch (error) {
        logger.error('Tool execution failed:', error)
        throw error
      }
    })
  }

  private async handleSearchInstagramEvents(args: any) {
    const searchParams = InstagramSearchSchema.parse(args)

    logger.info('Searching Instagram events:', searchParams)

    const posts = await this.profileScraper.searchHashtagPosts(
      searchParams.hashtags || ['event', 'party', 'concert'],
      searchParams.location,
      searchParams.max_posts || 20
    )

    const events = await this.contentAnalyzer.analyzePostsForEvents(posts)

    return {
      content: [
        {
          type: 'text',
          text: `Found ${events.length} potential events from ${posts.length} Instagram posts`
        }
      ],
      _meta: {
        source: 'instagram',
        search_params: searchParams,
        posts_analyzed: posts.length,
        events_found: events.length,
        events: events
      }
    }
  }

  private async handleAnalyzeProfile(args: any) {
    const profileParams = ProfileAnalysisSchema.parse(args)

    logger.info('Analyzing Instagram profile:', profileParams.username)

    const profileData = await this.profileScraper.scrapeProfile(
      profileParams.username,
      profileParams.max_posts || 20,
      profileParams.include_stories || false
    )

    const events = await this.contentAnalyzer.analyzeProfileForEvents(profileData)

    return {
      content: [
        {
          type: 'text',
          text: `Analyzed profile @${profileParams.username} and found ${events.length} potential events`
        }
      ],
      _meta: {
        source: 'instagram',
        profile: profileData.profile,
        posts_analyzed: profileData.posts.length,
        events_found: events.length,
        events: events
      }
    }
  }

  private async handleExtractEventFromPost(args: any) {
    const postParams = PostExtractionSchema.parse(args)

    logger.info('Extracting event from post:', postParams.post_url)

    const postData = await this.profileScraper.scrapePost(postParams.post_url)
    const event = await this.contentAnalyzer.extractEventFromPost(
      postData,
      postParams.deep_analysis || false
    )

    return {
      content: [
        {
          type: 'text',
          text: event ? `Extracted event: ${event.title}` : 'No event detected in this post'
        }
      ],
      _meta: {
        source: 'instagram',
        post_url: postParams.post_url,
        event_detected: !!event,
        event: event
      }
    }
  }

  private async handleMonitorAccounts(args: any) {
    const monitorParams = AccountMonitoringSchema.parse(args)

    logger.info('Setting up monitoring for accounts:', monitorParams.usernames)

    // This would typically set up a background job
    const results = await this.profileScraper.monitorAccounts(
      monitorParams.usernames,
      monitorParams.keywords || [],
      monitorParams.check_interval || 60
    )

    return {
      content: [
        {
          type: 'text',
          text: `Monitoring setup for ${monitorParams.usernames.length} accounts`
        }
      ],
      _meta: {
        source: 'instagram',
        monitoring_setup: results,
        accounts: monitorParams.usernames
      }
    }
  }

  async start() {
    await this.browserService.initialize()

    const transport = new StdioServerTransport()
    await this.server.connect(transport)

    logger.info('Instagram MCP Server started successfully')
  }

  async stop() {
    await this.browserService.close()
    logger.info('Instagram MCP Server stopped')
  }
}

// Schema definitions
const InstagramSearchSchema = z.object({
  hashtags: z.array(z.string()).optional(),
  location: z.string().optional(),
  date_range: z.object({
    start: z.string(),
    end: z.string()
  }).optional(),
  max_posts: z.number().min(1).max(100).optional()
})

const ProfileAnalysisSchema = z.object({
  username: z.string(),
  include_stories: z.boolean().optional(),
  max_posts: z.number().min(1).max(50).optional()
})

const PostExtractionSchema = z.object({
  post_url: z.string().url(),
  deep_analysis: z.boolean().optional()
})

const AccountMonitoringSchema = z.object({
  usernames: z.array(z.string()),
  keywords: z.array(z.string()).optional(),
  check_interval: z.number().optional()
})

// Start the server
if (import.meta.url === `file://${process.argv[1]}`) {
  const server = new InstagramMCPServer()

  process.on('SIGINT', async () => {
    await server.stop()
    process.exit(0)
  })

  server.start().catch(console.error)
}

export { InstagramMCPServer }

Browser Service

// src/services/browser-service.ts
import puppeteer, { Browser, Page } from 'puppeteer'
import puppeteerExtra from 'puppeteer-extra'
import StealthPlugin from 'puppeteer-extra-plugin-stealth'
import { Logger } from '../utils/logger.js'

puppeteerExtra.use(StealthPlugin())

interface BrowserConfig {
  headless: boolean
  timeout: number
  viewport: {
    width: number
    height: number
  }
  userAgent?: string
  proxy?: {
    host: string
    port: number
    username?: string
    password?: string
  }
}

export class BrowserService {
  private browser: Browser | null = null
  private config: BrowserConfig
  private logger: Logger

  constructor() {
    this.logger = new Logger('BrowserService')
    this.config = this.loadConfig()
  }

  private loadConfig(): BrowserConfig {
    return {
      headless: process.env.BROWSER_HEADLESS !== 'false',
      timeout: parseInt(process.env.BROWSER_TIMEOUT || '30000'),
      viewport: {
        width: parseInt(process.env.BROWSER_VIEWPORT_WIDTH || '1920'),
        height: parseInt(process.env.BROWSER_VIEWPORT_HEIGHT || '1080')
      },
      userAgent: process.env.USER_AGENT,
      proxy: process.env.PROXY_HOST ? {
        host: process.env.PROXY_HOST,
        port: parseInt(process.env.PROXY_PORT || '8080'),
        username: process.env.PROXY_USERNAME,
        password: process.env.PROXY_PASSWORD
      } : undefined
    }
  }

  async initialize(): Promise<void> {
    try {
      const launchOptions: any = {
        headless: this.config.headless,
        defaultViewport: this.config.viewport,
        args: [
          '--no-sandbox',
          '--disable-setuid-sandbox',
          '--disable-dev-shm-usage',
          '--disable-accelerated-2d-canvas',
          '--no-first-run',
          '--no-zygote',
          '--disable-gpu',
          '--disable-extensions'
        ]
      }

      if (this.config.proxy) {
        launchOptions.args.push(
          `--proxy-server=${this.config.proxy.host}:${this.config.proxy.port}`
        )
      }

      this.browser = await puppeteerExtra.launch(launchOptions)
      this.logger.info('Browser initialized successfully')

    } catch (error) {
      this.logger.error('Failed to initialize browser:', error)
      throw error
    }
  }

  async createPage(): Promise<Page> {
    if (!this.browser) {
      await this.initialize()
    }

    const page = await this.browser!.newPage()

    // Set user agent
    if (this.config.userAgent) {
      await page.setUserAgent(this.config.userAgent)
    }

    // Set timeout
    page.setDefaultTimeout(this.config.timeout)

    // Block unnecessary resources to speed up loading
    await page.setRequestInterception(true)
    page.on('request', (request) => {
      const resourceType = request.resourceType()
      if (['image', 'font', 'media'].includes(resourceType)) {
        request.abort()
      } else {
        request.continue()
      }
    })

    // Add stealth measures
    await this.addStealthMeasures(page)

    this.logger.debug('Created new page with stealth configuration')
    return page
  }

  private async addStealthMeasures(page: Page): Promise<void> {
    // Override navigator properties
    await page.evaluateOnNewDocument(() => {
      // Remove webdriver property
      delete (navigator as any).webdriver

      // Override plugins
      Object.defineProperty(navigator, 'plugins', {
        get: () => [1, 2, 3, 4, 5]
      })

      // Override languages
      Object.defineProperty(navigator, 'languages', {
        get: () => ['en-US', 'en']
      })

      // Override permissions
      const originalQuery = window.navigator.permissions.query
      window.navigator.permissions.query = (parameters) =>
        parameters.name === 'notifications'
          ? Promise.resolve({ state: Notification.permission } as any)
          : originalQuery(parameters)
    })

    // Random delays
    if (process.env.RANDOM_DELAYS === 'true') {
      await this.randomDelay(1000, 3000)
    }
  }

  async randomDelay(min: number = 1000, max: number = 3000): Promise<void> {
    const delay = Math.floor(Math.random() * (max - min + 1)) + min
    await new Promise(resolve => setTimeout(resolve, delay))
  }

  async navigateToInstagram(page: Page): Promise<void> {
    try {
      await page.goto('https://www.instagram.com/', {
        waitUntil: 'networkidle2',
        timeout: this.config.timeout
      })

      await this.randomDelay(2000, 4000)
      this.logger.debug('Navigated to Instagram')

    } catch (error) {
      this.logger.error('Failed to navigate to Instagram:', error)
      throw error
    }
  }

  async loginToInstagram(page: Page): Promise<void> {
    const username = process.env.INSTAGRAM_USERNAME
    const password = process.env.INSTAGRAM_PASSWORD

    if (!username || !password) {
      this.logger.info('No Instagram credentials provided, continuing without login')
      return
    }

    try {
      // Check if already logged in
      const isLoggedIn = await page.$('svg[aria-label="Home"]')
      if (isLoggedIn) {
        this.logger.info('Already logged in to Instagram')
        return
      }

      // Click login button
      await page.waitForSelector('a[href="/accounts/login/"]', { timeout: 10000 })
      await page.click('a[href="/accounts/login/"]')
      await this.randomDelay(2000, 3000)

      // Fill in credentials
      await page.waitForSelector('input[name="username"]')
      await page.type('input[name="username"]', username, { delay: 100 })
      await this.randomDelay(500, 1000)

      await page.type('input[name="password"]', password, { delay: 100 })
      await this.randomDelay(1000, 2000)

      // Submit login
      await page.click('button[type="submit"]')
      await page.waitForNavigation({ waitUntil: 'networkidle2' })

      // Handle potential security checks
      await this.handleSecurityChecks(page)

      this.logger.info('Successfully logged in to Instagram')

    } catch (error) {
      this.logger.error('Failed to login to Instagram:', error)
      throw error
    }
  }

  private async handleSecurityChecks(page: Page): Promise<void> {
    try {
      // Check for "Save Your Login Info" dialog
      const saveLoginButton = await page.$('button:has-text("Not Now")')
      if (saveLoginButton) {
        await saveLoginButton.click()
        await this.randomDelay(1000, 2000)
      }

      // Check for "Turn on Notifications" dialog
      const notificationButton = await page.$('button:has-text("Not Now")')
      if (notificationButton) {
        await notificationButton.click()
        await this.randomDelay(1000, 2000)
      }

      // Check for suspicious login activity
      const suspiciousActivity = await page.$('text="We Detected Unusual Activity"')
      if (suspiciousActivity) {
        this.logger.warn('Instagram detected unusual activity')
        // Handle this case based on your needs
      }

    } catch (error) {
      this.logger.debug('No security checks to handle')
    }
  }

  async closePage(page: Page): Promise<void> {
    try {
      await page.close()
      this.logger.debug('Page closed')
    } catch (error) {
      this.logger.error('Failed to close page:', error)
    }
  }

  async close(): Promise<void> {
    if (this.browser) {
      await this.browser.close()
      this.browser = null
      this.logger.info('Browser closed')
    }
  }

  async restartBrowser(): Promise<void> {
    await this.close()
    await this.initialize()
    this.logger.info('Browser restarted')
  }
}

Profile Scraper

// src/services/profile-scraper.ts
import { Page } from 'puppeteer'
import { BrowserService } from './browser-service.js'
import { Logger } from '../utils/logger.js'
import * as cheerio from 'cheerio'

interface InstagramPost {
  id: string
  url: string
  caption: string
  timestamp: string
  likes: number
  comments: number
  media_urls: string[]
  hashtags: string[]
  mentions: string[]
  location?: {
    name: string
    id?: string
  }
}

interface InstagramProfile {
  username: string
  display_name: string
  bio: string
  follower_count: number
  following_count: number
  post_count: number
  profile_pic_url: string
  is_verified: boolean
  is_business: boolean
  external_url?: string
}

interface ProfileData {
  profile: InstagramProfile
  posts: InstagramPost[]
  stories?: any[]
}

export class ProfileScraper {
  private browserService: BrowserService
  private logger: Logger

  constructor(browserService: BrowserService) {
    this.browserService = browserService
    this.logger = new Logger('ProfileScraper')
  }

  async scrapeProfile(
    username: string,
    maxPosts: number = 20,
    includeStories: boolean = false
  ): Promise<ProfileData> {
    const page = await this.browserService.createPage()

    try {
      await this.browserService.navigateToInstagram(page)
      await this.browserService.loginToInstagram(page)

      // Navigate to profile
      await page.goto(`https://www.instagram.com/${username}/`, {
        waitUntil: 'networkidle2'
      })

      // Extract profile information
      const profile = await this.extractProfileInfo(page, username)

      // Extract posts
      const posts = await this.extractPosts(page, maxPosts)

      // Extract stories if requested
      let stories: any[] = []
      if (includeStories) {
        stories = await this.extractStories(page, username)
      }

      this.logger.info(`Scraped profile @${username}: ${posts.length} posts`)

      return {
        profile,
        posts,
        stories: includeStories ? stories : undefined
      }

    } catch (error) {
      this.logger.error(`Failed to scrape profile @${username}:`, error)
      throw error
    } finally {
      await this.browserService.closePage(page)
    }
  }

  async scrapePost(postUrl: string): Promise<InstagramPost> {
    const page = await this.browserService.createPage()

    try {
      await this.browserService.navigateToInstagram(page)
      await this.browserService.loginToInstagram(page)

      await page.goto(postUrl, { waitUntil: 'networkidle2' })

      const post = await this.extractSinglePost(page, postUrl)

      this.logger.info(`Scraped post: ${postUrl}`)
      return post

    } catch (error) {
      this.logger.error(`Failed to scrape post ${postUrl}:`, error)
      throw error
    } finally {
      await this.browserService.closePage(page)
    }
  }

  async searchHashtagPosts(
    hashtags: string[],
    location?: string,
    maxPosts: number = 20
  ): Promise<InstagramPost[]> {
    const page = await this.browserService.createPage()
    const allPosts: InstagramPost[] = []

    try {
      await this.browserService.navigateToInstagram(page)
      await this.browserService.loginToInstagram(page)

      for (const hashtag of hashtags) {
        this.logger.info(`Searching hashtag: #${hashtag}`)

        const hashtagUrl = `https://www.instagram.com/explore/tags/${hashtag}/`
        await page.goto(hashtagUrl, { waitUntil: 'networkidle2' })

        const posts = await this.extractHashtagPosts(page, maxPosts / hashtags.length)
        allPosts.push(...posts)

        await this.browserService.randomDelay(2000, 4000)
      }

      // Filter by location if specified
      if (location) {
        return this.filterPostsByLocation(allPosts, location)
      }

      this.logger.info(`Found ${allPosts.length} posts from hashtag search`)
      return allPosts.slice(0, maxPosts)

    } catch (error) {
      this.logger.error('Failed to search hashtag posts:', error)
      throw error
    } finally {
      await this.browserService.closePage(page)
    }
  }

  private async extractProfileInfo(page: Page, username: string): Promise<InstagramProfile> {
    try {
      // Wait for profile data to load
      await page.waitForSelector('header section', { timeout: 10000 })

      const profileData = await page.evaluate(() => {
        // Extract from page script tags or DOM elements
        const scripts = Array.from(document.querySelectorAll('script'))

        for (const script of scripts) {
          if (script.textContent?.includes('window._sharedData')) {
            const sharedData = script.textContent.match(/window\._sharedData = ({.*?});/)
            if (sharedData) {
              try {
                const data = JSON.parse(sharedData[1])
                const user = data.entry_data?.ProfilePage?.[0]?.graphql?.user
                if (user) return user
              } catch (e) {
                // Continue to DOM extraction
              }
            }
          }
        }

        // Fallback to DOM extraction
        const header = document.querySelector('header section')
        if (!header) return null

        return {
          username: document.querySelector('h2')?.textContent || username,
          display_name: document.querySelector('h1')?.textContent || '',
          bio: document.querySelector('div[data-testid="user-bio"]')?.textContent || '',
          follower_count: this.extractCountFromElement('a[href*="/followers/"] span'),
          following_count: this.extractCountFromElement('a[href*="/following/"] span'),
          post_count: this.extractCountFromElement('div:first-child span'),
          profile_pic_url: document.querySelector('img[alt*="profile picture"]')?.src || '',
          is_verified: !!document.querySelector('[data-testid="verified-icon"]'),
          is_business: !!document.querySelector('[data-testid="business-icon"]')
        }
      })

      return {
        username,
        display_name: profileData?.full_name || profileData?.display_name || '',
        bio: profileData?.biography || '',
        follower_count: profileData?.edge_followed_by?.count || 0,
        following_count: profileData?.edge_follow?.count || 0,
        post_count: profileData?.edge_owner_to_timeline_media?.count || 0,
        profile_pic_url: profileData?.profile_pic_url || '',
        is_verified: profileData?.is_verified || false,
        is_business: profileData?.is_business_account || false,
        external_url: profileData?.external_url
      }

    } catch (error) {
      this.logger.error('Failed to extract profile info:', error)
      return {
        username,
        display_name: '',
        bio: '',
        follower_count: 0,
        following_count: 0,
        post_count: 0,
        profile_pic_url: '',
        is_verified: false,
        is_business: false
      }
    }
  }

  private async extractPosts(page: Page, maxPosts: number): Promise<InstagramPost[]> {
    const posts: InstagramPost[] = []

    try {
      // Scroll to load posts
      let loadedPosts = 0
      while (loadedPosts < maxPosts) {
        const postElements = await page.$$('article a[href*="/p/"]')

        if (postElements.length === 0) break

        for (let i = loadedPosts; i < Math.min(postElements.length, maxPosts); i++) {
          try {
            const postUrl = await postElements[i].evaluate(el => el.href)
            const post = await this.extractPostFromGrid(page, postUrl, i)
            if (post) posts.push(post)
            loadedPosts++
          } catch (error) {
            this.logger.debug(`Failed to extract post at index ${i}:`, error)
          }
        }

        if (loadedPosts < maxPosts) {
          await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight))
          await this.browserService.randomDelay(2000, 3000)
        }
      }

    } catch (error) {
      this.logger.error('Failed to extract posts:', error)
    }

    return posts
  }

  private async extractPostFromGrid(page: Page, postUrl: string, index: number): Promise<InstagramPost | null> {
    try {
      // Extract basic info from grid view
      const postData = await page.evaluate((url, idx) => {
        const postElement = document.querySelectorAll('article a[href*="/p/"]')[idx]
        if (!postElement) return null

        const img = postElement.querySelector('img')
        const timeElement = postElement.querySelector('time')

        return {
          url: url,
          media_urls: img ? [img.src] : [],
          timestamp: timeElement ? timeElement.getAttribute('datetime') : new Date().toISOString()
        }
      }, postUrl, index)

      if (!postData) return null

      // Extract post ID from URL
      const postIdMatch = postUrl.match(/\/p\/([^\/]+)\//)
      const postId = postIdMatch ? postIdMatch[1] : `post_${Date.now()}_${index}`

      return {
        id: postId,
        url: postUrl,
        caption: '', // Will be filled by detailed extraction if needed
        timestamp: postData.timestamp,
        likes: 0,
        comments: 0,
        media_urls: postData.media_urls,
        hashtags: [],
        mentions: [],
        location: undefined
      }

    } catch (error) {
      this.logger.debug(`Failed to extract post from grid:`, error)
      return null
    }
  }

  private async extractSinglePost(page: Page, postUrl: string): Promise<InstagramPost> {
    try {
      await page.waitForSelector('article', { timeout: 10000 })

      const postData = await page.evaluate(() => {
        const article = document.querySelector('article')
        if (!article) throw new Error('Post not found')

        // Extract caption
        const captionElement = article.querySelector('div[data-testid="post-caption"] span')
        const caption = captionElement?.textContent || ''

        // Extract hashtags and mentions from caption
        const hashtags = (caption.match(/#\w+/g) || []).map(tag => tag.slice(1))
        const mentions = (caption.match(/@\w+/g) || []).map(mention => mention.slice(1))

        // Extract media URLs
        const images = Array.from(article.querySelectorAll('img[src*="scontent"]'))
        const videos = Array.from(article.querySelectorAll('video source'))
        const media_urls = [
          ...images.map(img => img.src),
          ...videos.map(video => video.src)
        ]

        // Extract engagement metrics
        const likesElement = article.querySelector('button[data-testid="like-button"] + span')
        const likes = this.parseEngagementCount(likesElement?.textContent || '0')

        // Extract timestamp
        const timeElement = article.querySelector('time')
        const timestamp = timeElement?.getAttribute('datetime') || new Date().toISOString()

        // Extract location
        const locationElement = article.querySelector('a[href*="/locations/"]')
        const location = locationElement ? {
          name: locationElement.textContent || '',
          id: locationElement.href.match(/\/locations\/(\d+)\//)?.[1]
        } : undefined

        return {
          caption,
          hashtags,
          mentions,
          media_urls,
          likes,
          timestamp,
          location
        }
      })

      const postIdMatch = postUrl.match(/\/p\/([^\/]+)\//)
      const postId = postIdMatch ? postIdMatch[1] : `post_${Date.now()}`

      return {
        id: postId,
        url: postUrl,
        caption: postData.caption,
        timestamp: postData.timestamp,
        likes: postData.likes,
        comments: 0, // Comments require separate extraction
        media_urls: postData.media_urls,
        hashtags: postData.hashtags,
        mentions: postData.mentions,
        location: postData.location
      }

    } catch (error) {
      this.logger.error('Failed to extract single post:', error)
      throw error
    }
  }

  private async extractHashtagPosts(page: Page, maxPosts: number): Promise<InstagramPost[]> {
    const posts: InstagramPost[] = []

    try {
      // Wait for posts to load
      await page.waitForSelector('article a[href*="/p/"]', { timeout: 10000 })

      const postLinks = await page.$$eval('article a[href*="/p/"]', links => 
        links.slice(0, maxPosts).map(link => link.href)
      )

      for (const postUrl of postLinks) {
        try {
          // Extract basic info without navigating to each post
          const postIdMatch = postUrl.match(/\/p\/([^\/]+)\//)
          const postId = postIdMatch ? postIdMatch[1] : `hashtag_post_${Date.now()}`

          posts.push({
            id: postId,
            url: postUrl,
            caption: '',
            timestamp: new Date().toISOString(),
            likes: 0,
            comments: 0,
            media_urls: [],
            hashtags: [],
            mentions: [],
            location: undefined
          })

        } catch (error) {
          this.logger.debug(`Failed to process hashtag post ${postUrl}:`, error)
        }
      }

    } catch (error) {
      this.logger.error('Failed to extract hashtag posts:', error)
    }

    return posts
  }

  private async extractStories(page: Page, username: string): Promise<any[]> {
    try {
      // Stories require special handling and may not always be available
      // This is a simplified implementation

      const storiesButton = await page.$(`a[href="/${username}/"] img[alt*="story"]`)
      if (!storiesButton) {
        this.logger.debug('No stories available for this user')
        return []
      }

      // Stories extraction would require more complex implementation
      // due to Instagram's story viewing restrictions

      return []

    } catch (error) {
      this.logger.debug('Failed to extract stories:', error)
      return []
    }
  }

  private filterPostsByLocation(posts: InstagramPost[], location: string): InstagramPost[] {
    const locationLower = location.toLowerCase()

    return posts.filter(post => {
      if (post.location?.name?.toLowerCase().includes(locationLower)) {
        return true
      }

      if (post.caption?.toLowerCase().includes(locationLower)) {
        return true
      }

      return false
    })
  }

  async monitorAccounts(
    usernames: string[],
    keywords: string[],
    checkInterval: number
  ): Promise<any> {
    // This would implement a monitoring system
    // For now, return setup confirmation

    this.logger.info(`Setting up monitoring for ${usernames.length} accounts`)

    return {
      status: 'monitoring_setup',
      accounts: usernames,
      keywords: keywords,
      interval_minutes: checkInterval,
      next_check: new Date(Date.now() + checkInterval * 60000).toISOString()
    }
  }
}

Content Analyzer

// src/services/content-analyzer.ts
import OpenAI from 'openai'
import { Logger } from '../utils/logger.js'
import { InstagramPost, ProfileData } from './profile-scraper.js'

interface ExtractedEvent {
  id: string
  title: string
  description: string
  start_date?: string
  end_date?: string
  location?: {
    name: string
    address?: string
    coordinates?: {
      lat: number
      lng: number
    }
  }
  category: string
  organizer: {
    name: string
    instagram: string
  }
  ticket_info?: {
    is_free: boolean
    price?: string
    booking_url?: string
  }
  source_post: {
    url: string
    instagram_id: string
  }
  confidence_score: number
  extracted_at: string
}

export class ContentAnalyzer {
  private openai: OpenAI
  private logger: Logger

  constructor() {
    this.logger = new Logger('ContentAnalyzer')

    if (!process.env.OPENAI_API_KEY) {
      throw new Error('OPENAI_API_KEY is required for content analysis')
    }

    this.openai = new OpenAI({
      apiKey: process.env.OPENAI_API_KEY
    })
  }

  async analyzePostsForEvents(posts: InstagramPost[]): Promise<ExtractedEvent[]> {
    this.logger.info(`Analyzing ${posts.length} posts for events`)

    const events: ExtractedEvent[] = []

    for (const post of posts) {
      try {
        const event = await this.extractEventFromPost(post, true)
        if (event) {
          events.push(event)
        }
      } catch (error) {
        this.logger.debug(`Failed to analyze post ${post.id}:`, error)
      }
    }

    this.logger.info(`Extracted ${events.length} events from ${posts.length} posts`)
    return events
  }

  async analyzeProfileForEvents(profileData: ProfileData): Promise<ExtractedEvent[]> {
    this.logger.info(`Analyzing profile @${profileData.profile.username} for events`)

    // First, check if this is an event-related profile
    const isEventProfile = await this.isEventRelatedProfile(profileData.profile)

    if (!isEventProfile) {
      this.logger.debug(`Profile @${profileData.profile.username} doesn't appear to be event-related`)
      return []
    }

    return this.analyzePostsForEvents(profileData.posts)
  }

  async extractEventFromPost(
    post: InstagramPost,
    deepAnalysis: boolean = false
  ): Promise<ExtractedEvent | null> {
    try {
      // Quick keyword-based filtering first
      if (!this.containsEventKeywords(post.caption)) {
        return null
      }

      if (deepAnalysis) {
        return await this.performAIAnalysis(post)
      } else {
        return await this.performBasicAnalysis(post)
      }

    } catch (error) {
      this.logger.error(`Failed to extract event from post ${post.id}:`, error)
      return null
    }
  }

  private async isEventRelatedProfile(profile: any): Promise<boolean> {
    const eventKeywords = [
      'event', 'events', 'party', 'concert', 'festival', 'show', 'performance',
      'venue', 'club', 'bar', 'restaurant', 'theater', 'museum', 'gallery',
      'conference', 'workshop', 'seminar', 'meetup', 'gathering', 'celebration'
    ]

    const profileText = `${profile.bio} ${profile.display_name}`.toLowerCase()

    return eventKeywords.some(keyword => profileText.includes(keyword))
  }

  private containsEventKeywords(text: string): boolean {
    const eventKeywords = [
      'event', 'tonight', 'tomorrow', 'this weekend', 'next week',
      'join us', 'come to', 'happening', 'live music', 'performance',
      'show', 'concert', 'party', 'celebration', 'festival',
      'workshop', 'seminar', 'meetup', 'conference',
      'tickets', 'rsvp', 'register', 'book now',
      'when:', 'where:', 'time:', 'date:', 'location:'
    ]

    const textLower = text.toLowerCase()
    return eventKeywords.some(keyword => textLower.includes(keyword))
  }

  private async performBasicAnalysis(post: InstagramPost): Promise<ExtractedEvent | null> {
    // Basic pattern matching for event information
    const caption = post.caption

    // Extract potential dates
    const datePatterns = [
      /(\d{1,2}\/\d{1,2}\/\d{4})/g,
      /(\d{1,2}-\d{1,2}-\d{4})/g,
      /(monday|tuesday|wednesday|thursday|friday|saturday|sunday)/gi,
      /(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\w*\s+\d{1,2}/gi
    ]

    let extractedDate: string | undefined
    for (const pattern of datePatterns) {
      const match = caption.match(pattern)
      if (match) {
        extractedDate = match[0]
        break
      }
    }

    // Extract potential times
    const timePattern = /(\d{1,2}:\d{2}\s*(am|pm|AM|PM)?)/g
    const timeMatch = caption.match(timePattern)
    const extractedTime = timeMatch ? timeMatch[0] : undefined

    // Extract potential location
    const locationPatterns = [
      /at\s+([A-Z][A-Za-z\s]+)/g,
      /location:\s*([A-Za-z\s]+)/gi,
      /where:\s*([A-Za-z\s]+)/gi
    ]

    let extractedLocation: string | undefined
    for (const pattern of locationPatterns) {
      const match = caption.match(pattern)
      if (match && match[1]) {
        extractedLocation = match[1].trim()
        break
      }
    }

    // If we found event indicators, create a basic event
    if (extractedDate || extractedTime || extractedLocation) {
      return {
        id: `extracted_${post.id}`,
        title: this.extractTitle(caption),
        description: caption,
        start_date: this.constructDateTime(extractedDate, extractedTime),
        location: extractedLocation ? {
          name: extractedLocation
        } : post.location,
        category: this.inferCategory(caption),
        organizer: {
          name: 'Instagram User', // Would need profile info
          instagram: post.url.split('/')[3] || ''
        },
        source_post: {
          url: post.url,
          instagram_id: post.id
        },
        confidence_score: 0.6,
        extracted_at: new Date().toISOString()
      }
    }

    return null
  }

  private async performAIAnalysis(post: InstagramPost): Promise<ExtractedEvent | null> {
    try {
      const prompt = this.buildAnalysisPrompt(post)

      const response = await this.openai.chat.completions.create({
        model: process.env.OPENAI_MODEL || 'gpt-4-turbo-preview',
        messages: [
          {
            role: 'system',
            content: 'You are an expert at extracting event information from Instagram posts. Analyze the content and extract structured event data if an event is being promoted.'
          },
          {
            role: 'user',
            content: prompt
          }
        ],
        functions: [
          {
            name: 'extract_event',
            description: 'Extract event information from Instagram post',
            parameters: {
              type: 'object',
              properties: {
                is_event: {
                  type: 'boolean',
                  description: 'Whether this post is promoting an event'
                },
                event_title: {
                  type: 'string',
                  description: 'Title or name of the event'
                },
                event_description: {
                  type: 'string',
                  description: 'Description of the event'
                },
                start_date: {
                  type: 'string',
                  description: 'Event start date in ISO format'
                },
                end_date: {
                  type: 'string',
                  description: 'Event end date in ISO format (if different from start)'
                },
                location_name: {
                  type: 'string',
                  description: 'Name of the event location'
                },
                location_address: {
                  type: 'string',
                  description: 'Address of the event location'
                },
                category: {
                  type: 'string',
                  description: 'Event category (music, art, food, sports, etc.)'
                },
                is_free: {
                  type: 'boolean',
                  description: 'Whether the event is free'
                },
                ticket_price: {
                  type: 'string',
                  description: 'Ticket price if mentioned'
                },
                booking_info: {
                  type: 'string',
                  description: 'How to book or get tickets'
                },
                confidence_score: {
                  type: 'number',
                  description: 'Confidence score from 0 to 1'
                }
              },
              required: ['is_event', 'confidence_score']
            }
          }
        ],
        function_call: { name: 'extract_event' }
      })

      const functionCall = response.choices[0]?.message?.function_call
      if (!functionCall || functionCall.name !== 'extract_event') {
        return null
      }

      const extractedData = JSON.parse(functionCall.arguments)

      if (!extractedData.is_event || extractedData.confidence_score < 0.5) {
        return null
      }

      return {
        id: `ai_extracted_${post.id}`,
        title: extractedData.event_title || this.extractTitle(post.caption),
        description: extractedData.event_description || post.caption,
        start_date: extractedData.start_date,
        end_date: extractedData.end_date,
        location: {
          name: extractedData.location_name || post.location?.name || '',
          address: extractedData.location_address
        },
        category: extractedData.category || 'other',
        organizer: {
          name: 'Instagram User',
          instagram: post.url.split('/')[3] || ''
        },
        ticket_info: {
          is_free: extractedData.is_free || false,
          price: extractedData.ticket_price,
          booking_url: extractedData.booking_info
        },
        source_post: {
          url: post.url,
          instagram_id: post.id
        },
        confidence_score: extractedData.confidence_score,
        extracted_at: new Date().toISOString()
      }

    } catch (error) {
      this.logger.error('AI analysis failed:', error)
      return this.performBasicAnalysis(post)
    }
  }

  private buildAnalysisPrompt(post: InstagramPost): string {
    return `
Analyze this Instagram post for event information:

Caption: ${post.caption}

Hashtags: ${post.hashtags.join(', ')}

${post.location ? `Location: ${post.location.name}` : ''}

Posted: ${post.timestamp}

Media URLs: ${post.media_urls.slice(0, 3).join(', ')}

Please determine if this post is promoting an event and extract all relevant details including:
- Event title and description
- Date and time
- Location details
- Event category
- Ticket information
- How to attend/book

Provide a confidence score based on how clear the event information is.
`
  }

  private extractTitle(caption: string): string {
    // Extract potential title from the first line or sentence
    const lines = caption.split('\n').filter(line => line.trim())
    if (lines.length > 0) {
      const firstLine = lines[0].trim()
      // Remove hashtags and mentions from title
      return firstLine.replace(/#\w+/g, '').replace(/@\w+/g, '').trim().slice(0, 100)
    }
    return 'Instagram Event'
  }

  private constructDateTime(date?: string, time?: string): string | undefined {
    if (!date && !time) return undefined

    try {
      const now = new Date()
      let eventDate = now

      if (date) {
        eventDate = new Date(date)
        if (isNaN(eventDate.getTime())) {
          eventDate = now
        }
      }

      if (time) {
        const timeMatch = time.match(/(\d{1,2}):(\d{2})\s*(am|pm|AM|PM)?/)
        if (timeMatch) {
          let hours = parseInt(timeMatch[1])
          const minutes = parseInt(timeMatch[2])
          const ampm = timeMatch[3]?.toLowerCase()

          if (ampm === 'pm' && hours !== 12) {
            hours += 12
          } else if (ampm === 'am' && hours === 12) {
            hours = 0
          }

          eventDate.setHours(hours, minutes, 0, 0)
        }
      }

      return eventDate.toISOString()
    } catch (error) {
      return undefined
    }
  }

  private inferCategory(caption: string): string {
    const categoryKeywords = {
      music: ['concert', 'music', 'band', 'dj', 'live music', 'performance'],
      food: ['food', 'restaurant', 'dining', 'taste', 'menu', 'chef'],
      art: ['art', 'gallery', 'exhibition', 'artist', 'painting', 'sculpture'],
      sports: ['game', 'match', 'sports', 'team', 'tournament', 'athletic'],
      business: ['conference', 'workshop', 'seminar', 'networking', 'business'],
      entertainment: ['show', 'comedy', 'theater', 'movie', 'entertainment'],
      social: ['party', 'celebration', 'gathering', 'meetup', 'social']
    }

    const captionLower = caption.toLowerCase()

    for (const [category, keywords] of Object.entries(categoryKeywords)) {
      if (keywords.some(keyword => captionLower.includes(keyword))) {
        return category
      }
    }

    return 'other'
  }
}

This comprehensive Instagram MCP server documentation covers web scraping, content analysis, and AI-powered event extraction from Instagram! 📸