npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

nodejs-insta-private-api-mqtt

v1.3.17

Published

Complete Instagram MQTT protocol with FULL iOS + Android support. 33 device presets (21 iOS + 12 Android). iPhone 16/15/14/13/12, iPad Pro, Samsung, Pixel, Huawei. Real-time DM messaging, view-once media extraction, sub-500ms latency.

Readme

Dear users, I post many versions of the project because Instagram changes the protocol almost daily, if you like this project leave a star on github https://github.com/Kunboruto20/nodejs-insta-private-api.git

nodejs-insta-private-api-mqtt

This project implements a complete and production-ready MQTT protocol client for Instagram's real-time messaging infrastructure. Instagram uses MQTT natively for direct messages, notifications, and real-time presence updates. This library replicates that exact implementation, allowing developers to build high-performance bots and automation tools that communicate with Instagram's backend using the same protocol the official app uses.

By leveraging MQTT instead of Instagram's REST API, this library achieves sub-500ms message latency, bidirectional real-time communication, and native support for notifications, presence tracking, and thread management. The implementation is reverse-engineered from Instagram's mobile app protocol and tested extensively for reliability and compatibility.

Features (v5.61.11 - iOS + Android Full Support)

  • NEW: FULL iOS SUPPORT - iPhone 16/15/14/13/12 + iPad Pro/Air device emulation
  • NEW: FULL ANDROID SUPPORT - Samsung, Huawei, Google Pixel, OnePlus, Xiaomi, OPPO
  • NEW: 33 Preset Devices - 21 iOS + 12 Android devices ready to use
  • NEW: switchPlatform() - Switch between iOS and Android with one line
  • NEW: useIOSDevice() - Emulate any iPhone or iPad
  • NEW: useAndroidDevice() - Emulate any Android phone
  • NEW: getPlatformInfo() - Get current platform and device details
  • NEW: downloadContentFromMessage() - Download media from DM messages (like Baileys for WhatsApp)
  • NEW: View-Once Media Extraction - Save disappearing photos/videos before they expire
  • NEW: downloadMediaBuffer() - Get media as Buffer directly
  • NEW: extractMediaUrls() - Extract CDN URLs from any message type
  • NEW: Custom Device Emulation - Choose which phone model Instagram sees
  • NEW: setCustomDevice() - Set any custom Android device with full control
  • NEW: setIOSDevice() - Set any custom iOS device with full control
  • FIX: IRIS subscription auto-fetch - Messages now received automatically without manual irisData
  • FIX: startRealTimeListener() - Fixed to use correct inbox method for IRIS data retrieval
  • NEW: sendPhoto() / sendVideo() - Upload and send photos/videos directly via MQTT
  • NEW: Clear message display - Incoming messages shown with Username, ID, Text, Status (no emojis)
  • NEW: All message types decoded - Photos, videos, voice, reels, stories, links displayed clearly
  • Multi-file auth state - Session persistence like Baileys (WhatsApp library)
  • Real-time MQTT messaging - Receive and send DMs with <500ms latency
  • Bidirectional communication - Send messages back through the same MQTT connection
  • Message management - Send, delete, edit, and reply to messages via MQTT
  • Notification subscriptions - Follow, mention, and call notifications via MQTT
  • Thread management - Add/remove members from groups via MQTT
  • Auto-reply bots - Build keyword-triggered or scheduled response bots
  • Session persistence - Avoid repeated logins with saved sessions
  • Full Instagram API - Stories, media uploads, search, comments, user info
  • Group chat support - Automatic detection with thread-based messaging
  • IRIS subscription protocol - Reliable message delivery with compression
  • Automatic reconnection - Exponential backoff with connection pooling
  • Pure JavaScript - No compilation required, works in Node.js 18+

Scope: DM-Focused Implementation

This library is optimized for Direct Messages and implements the core MQTT protocols used by Instagram for:

  • Real-time message delivery and reception
  • Presence status tracking
  • Typing indicators
  • Notifications for follows, mentions, and calls
  • Group thread management

For full MQTT coverage analysis, see MQTT_COVERAGE_ANALYSIS.md

Installation

npm install nodejs-insta-private-api-mqtt

Requires Node.js 18 or higher.


NEW: Custom Device Emulation (v5.60.7)

Default Device: Samsung Galaxy S25 Ultra (Android 15) - used automatically if you don't set a custom device.

This feature allows you to choose which phone model Instagram sees when your bot connects. Instead of using a default device, you can emulate any Android phone like Samsung Galaxy S25 Ultra, Huawei P60 Pro, Google Pixel 9, and more.

Why Use Custom Device Emulation?

  • Avoid detection - Use realistic, modern device fingerprints
  • Match your target audience - Emulate devices popular in specific regions
  • Testing - Test how Instagram behaves with different devices
  • Reduce bans - Modern devices are less likely to trigger security checks

Quick Start: Use a Preset Device

The easiest way to set a custom device is using the built-in presets:

const { IgApiClient } = require('nodejs-insta-private-api');

const ig = new IgApiClient();

// Set device BEFORE login
ig.state.usePresetDevice('Samsung Galaxy S25 Ultra');

// Now login - Instagram will see a Samsung S25 Ultra
await ig.login({
  username: 'your_username',
  password: 'your_password'
});

console.log('Logged in with device:', ig.state.deviceString);
// Output: 35/15; 505dpi; 1440x3120; samsung; SM-S928B; e3q; qcom

Available Preset Devices

| Device Name | Manufacturer | Android Version | |-------------|--------------|-----------------| | Samsung Galaxy S25 Ultra | Samsung | Android 15 | | Samsung Galaxy S24 Ultra | Samsung | Android 14 | | Samsung Galaxy S23 Ultra | Samsung | Android 14 | | Samsung Galaxy Z Fold 5 | Samsung | Android 14 | | Huawei P60 Pro | Huawei | Android 12 | | Huawei Mate 60 Pro | Huawei | Android 12 | | Google Pixel 8 Pro | Google | Android 14 | | Google Pixel 9 Pro | Google | Android 15 | | OnePlus 12 | OnePlus | Android 14 | | Xiaomi 14 Ultra | Xiaomi | Android 14 | | Xiaomi Redmi Note 13 Pro | Xiaomi | Android 14 | | OPPO Find X7 Ultra | OPPO | Android 14 |

List All Available Presets

const ig = new IgApiClient();

// Get all available preset devices
const presets = ig.state.getPresetDevices();

console.log('Available devices:');
Object.keys(presets).forEach((name, i) => {
  console.log(`${i + 1}. ${name}`);
});

Set a Fully Custom Device

For complete control, use setCustomDevice() with your own configuration:

const ig = new IgApiClient();

// Define your custom device
ig.state.setCustomDevice({
  manufacturer: 'samsung',           // Phone manufacturer
  model: 'SM-S928B',                 // Model code
  device: 'e3q',                     // Device codename
  androidVersion: '15',              // Android version
  androidApiLevel: 35,               // Android API level
  resolution: '1440x3120',           // Screen resolution
  dpi: '505dpi',                     // Screen density
  chipset: 'qcom',                   // Chipset (qcom, kirin, google, etc.)
  build: 'UP1A.231005.007'           // Build number (optional)
});

console.log('Device string:', ig.state.deviceString);
console.log('User agent:', ig.state.appUserAgent);

Get Current Device Info

const ig = new IgApiClient();
ig.state.usePresetDevice('Google Pixel 9 Pro');

// Get full device information
const info = ig.state.getCurrentDeviceInfo();

console.log('Device String:', info.deviceString);
console.log('Device ID:', info.deviceId);
console.log('UUID:', info.uuid);
console.log('Phone ID:', info.phoneId);
console.log('Build:', info.build);
console.log('User Agent:', info.userAgent);

178. Complete Bot Example with Instant MQTT Boot

This example demonstrates how to build a production-ready bot that restores its session and connects to MQTT instantly upon startup.

const { IgApiClient, RealtimeClient, useMultiFileAuthState } = require('nodejs-insta-private-api-mqtt');

async function startBot() {
  const ig = new IgApiClient();
  
  // 1. Initialize the multi-file auth state (Baileys style)
  // This folder stores your credentials, cookies, and MQTT session
  const auth = await useMultiFileAuthState('./auth_info_ig');
  
  // 2. Setup your device emulation
  ig.state.usePresetDevice('Samsung Galaxy S25 Ultra');
  
  // 3. Initialize the RealtimeClient
  // IMPORTANT: Because we modified the constructor, this will AUTOMATICALLY 
  // detect the session in ./auth_info_ig and start MQTT in the background.
  const realtime = new RealtimeClient(ig);

  // 4. Set up your event listeners
  realtime.on('connected', () => {
    console.log('🚀 Bot is online and MQTT is connected!');
  });

  realtime.on('message_live', async (msg) => {
    console.log(`📩 [${msg.username}]: ${msg.text}`);
    
    // Simple auto-reply logic
    if (msg.text.toLowerCase() === 'ping') {
      await realtime.directCommands.sendTextViaRealtime(msg.thread_id, 'pong! 🏓');
    }
  });

  // 5. Handle the initial "Pairing" (only happens once)
  if (!auth.hasSession()) {
    console.log('No session found. Please login to pair your account...');
    
    await ig.login({
      username: 'your_username',
      password: 'your_password'
    });
    
    // Save credentials for the next boot
    await auth.saveCreds(ig);
    
    // Start the realtime sync for the first time
    await realtime.startRealTimeListener();
    
    // Save the MQTT state so we can boot instantly next time
    await auth.saveMqttSession(realtime);
    
    console.log('✅ Pairing successful! Next time you run this, it will boot INSTANTLY.');
  } else {
    console.log('✨ Existing session detected. Instant MQTT boot sequence initiated...');
  }
}

startBot().catch(console.error);

API Reference: Device Emulation Methods

| Method | Description | |--------|-------------| | state.usePresetDevice(name) | Set device from preset list (e.g., 'Samsung Galaxy S25 Ultra') | | state.setCustomDevice(config) | Set fully custom device configuration | | state.getPresetDevices() | Get object with all available preset devices | | state.getCurrentDeviceInfo() | Get current device configuration | | state.deviceString | Current device string used in requests | | state.appUserAgent | Full User-Agent header sent to Instagram |

setCustomDevice() Configuration Options

| Property | Type | Description | Example | |----------|------|-------------|---------| | manufacturer | string | Phone manufacturer | 'samsung', 'HUAWEI', 'Google' | | model | string | Phone model code | 'SM-S928B', 'Pixel 9 Pro' | | device | string | Device codename | 'e3q', 'husky', 'caiman' | | androidVersion | string | Android version | '15', '14', '13' | | androidApiLevel | number | Android API level | 35, 34, 33 | | resolution | string | Screen resolution | '1440x3120', '1080x2340' | | dpi | string | Screen density | '505dpi', '480dpi', '420dpi' | | chipset | string | Chipset identifier | 'qcom', 'kirin', 'google' | | build | string | Build number (optional) | 'UP1A.231005.007' |


🖼️ IMPORTANT: How media actually works (photos & videos)

🖼️ IMPORTANT: How media actually works (photos & videos) — UPDATED (rupload + MQTT reference)

Clarification (APK + client behaviour):
The mobile app uploads raw media bytes via HTTP rupload (rupload_igphoto / rupload_igvideo). After the rupload completes and the app receives an upload_id, the app notifies Instagram's realtime layer (MQTT) that "there is media with upload_id — attach it to thread X".

Key points

  • Media bytes are uploaded over HTTP (rupload). MQTT never carries the raw image/video bytes.
  • MQTT is used as a realtime reference/notification channel: the client publishes a send_item command containing item_type: "media" and upload_id: "<id>". That tells the server: "the media exists; attach it to this thread".
  • In some cases the client also calls HTTP broadcast/configure endpoints. The exact flow can vary by platform/version; the APK shows the pattern RuploadClient.upload() -> upload_id then SendMessage.sendMedia(upload_id) (MQTT). The hybrid pattern below matches that behavior.

Correct / supported patterns

There are two practical, compatible flows you can use depending on your client helpers and what the server expects:

A) Hybrid (rupload HTTP -> MQTT reference broadcast) — matches what the APK does in many versions

  1. Upload bytes to rupload_igphoto / rupload_igvideo (HTTP) -> receive upload_id.
  2. Publish an MQTT send_item with item_type: "media" and upload_id: this tells the realtime server to attach the previously uploaded media to the thread.
    • MQTT payload is only the metadata (JSON), typically compressed/deflated by the realtime client before publish.
    • This is the recommended APK-compatible approach.

B) Full HTTP (rupload HTTP -> configure/broadcast HTTP) — always works, server-side creation via REST

  1. Upload bytes to rupload endpoint (HTTP) -> receive upload_id.
  2. Call the direct broadcast / configure_photo or configure_video HTTP endpoint to create the message server-side. MQTT will then deliver the server-created message to connected clients.
    • Use this if you prefer to avoid relying on the realtime command to create messages.

Summary: Always rupload via HTTP. Then use the HTTP configure/broadcast endpoints as the primary, reliable method to attach media to threads. Publishing an MQTT send_item referencing upload_id may work as a best-effort fallback on some servers, but it is not guaranteed. MQTT never carries the raw media bytes, only metadata/reference.


Example: Hybrid flow — Photo (rupload HTTP then MQTT reference)

This example shows the two steps required. It assumes you have ig.request.send for HTTP and a realtime.mqtt.publish() helper for MQTT. It also shows the compressDeflate helper pattern used in this project.

// 1) RUUPLOAD (HTTP) - upload bytes and get upload_id
const uploadId = Date.now().toString(); // or use library helper that returns upload_id
const objectName = `${uploadId}_${Math.floor(Math.random()*1e9)}.jpg`;

const ruploadParams = {
  upload_id: uploadId,
  media_type: 1, // photo
  image_compression: '{"lib_name":"moz","lib_version":"3.1.m","quality":"80"}',
  xsharing_user_ids: JSON.stringify([]),
  is_clips_media: false,
};

const headers = {
  'X-Instagram-Rupload-Params': JSON.stringify(ruploadParams),
  'Content-Type': 'image/jpeg',
  'X-Entity-Type': 'image/jpeg',
  'X-Entity-Length': String(photoBuffer.length),
  'Content-Length': String(photoBuffer.length),
};

// send bytes to /rupload_igphoto/<objectName>
await ig.request.send({
  url: `/rupload_igphoto/${objectName}`,
  method: 'POST',
  headers,
  body: photoBuffer,
  timeout: 120000,
});

// server should now provide or accept the upload_id (uploadId)

// 2) MQTT publish - tell realtime: "attach upload_id X to thread Y"
const clientContext = require('uuid').v4();
const command = {
  action: 'send_item',
  thread_id: String(threadId),
  item_type: 'media',
  upload_id: String(uploadId),
  text: caption || '',
  timestamp: Date.now(),
  client_context: clientContext
};

// compress the JSON (match client's compress method)
const json = JSON.stringify(command);
const payload = await compressDeflate(json); // project helper

// publish to MQTT send_message topic
await mqtt.publish({
  topic: Topics.SEND_MESSAGE.id,
  qosLevel: 1,
  payload: payload
});

Notes

  • This sends only a reference to the server. The bytes were already sent via rupload.
  • The APK calls something equivalent to RuploadClient.upload() then SendMessage.sendMedia(upload_id) — the second call is an MQTT notification that references upload_id.

Example: Hybrid flow — Video (rupload HTTP then MQTT reference)

// 1) RUUPLOAD (video)
const uploadId = Date.now().toString();
const objectName = `${uploadId}_${Math.floor(Math.random()*1e9)}.mp4`;

const ruploadParams = {
  upload_id: uploadId,
  media_type: 2, // video
  xsharing_user_ids: JSON.stringify([]),
  upload_media_duration_ms: Math.round(duration * 1000),
  upload_media_width: width,
  upload_media_height: height
};

const headers = {
  'X-Instagram-Rupload-Params': JSON.stringify(ruploadParams),
  'Content-Type': 'video/mp4',
  'X-Entity-Type': 'video/mp4',
  'X-Entity-Length': String(videoBuffer.length),
  'Content-Length': String(videoBuffer.length),
  'Offset': '0'
};

await ig.request.send({
  url: `/rupload_igvideo/${objectName}`,
  method: 'POST',
  headers,
  body: videoBuffer,
  timeout: 300000
});

// 2) MQTT publish - notify realtime to attach upload_id to thread
const command = {
  action: 'send_item',
  thread_id: String(threadId),
  item_type: 'media',
  upload_id: String(uploadId),
  text: caption || '',
  timestamp: Date.now(),
  client_context: require('uuid').v4()
};

const payload = await compressDeflate(JSON.stringify(command));
await mqtt.publish({
  topic: Topics.SEND_MESSAGE.id,
  qosLevel: 1,
  payload: payload
});

Short FAQ & Troubleshooting

  • Q: Can I send the raw image through MQTT?
    A: No — MQTT is not used for large raw bytes. The app uploads bytes via rupload HTTP.

  • Q: If I publish the MQTT send_item with upload_id, will the message appear in the thread?
    A: Sometimes the realtime server accepts an MQTT send_item referencing a valid upload_id, but this behavior is not universally reliable across server versions, accounts, or configurations. For production reliability prefer the HTTP broadcast/configure endpoints; keep MQTT reference as a best-effort fallback.

  • Q: Do I need to call /direct_v2/threads/broadcast/... if I published MQTT?
    A: Often not — the APK pattern uses MQTT for the attach request. However, for absolute compatibility or for older server behavior, the HTTP configure/broadcast endpoints are a safe fallback.


Security & Best Practices

  • Ensure upload_id you publish via MQTT is the one returned by rupload; otherwise servers will reject or ignore it.
  • Keep rupload headers accurate (X-Entity-Length, X-Instagram-Rupload-Params, etc.).
  • Use client_context (UUID) per message to track mutations.
  • Implement retries and exponential backoff for both rupload and MQTT publish.
  • Log both the rupload response and the MQTT publish result for debugging.

We've completely overhauled how the MQTT connection works. You no longer need to manually manage connection states every time your bot restarts. Just like Baileys, if a session is present in the default folder, the library takes care of everything for you.

Why this is a game changer:

  • Zero Configuration: No need to pass IRIS data or subscription topics manually on every boot.
  • Background Startup: The MQTT connection starts the moment you create the RealtimeClient.
  • Session Persistence: Cookies, device info, and MQTT state are managed automatically.

🔌 How to use Instant Boot

Once you've linked your account (see the setup section below), your main script can be as simple as this:

const { IgApiClient, RealtimeClient } = require('nodejs-insta-private-api-mqtt');

// 1. Initialize the client
const ig = new IgApiClient();

// 2. That's it! 
// If ./auth_info_ig exists, MQTT starts background connection immediately.
const realtime = new RealtimeClient(ig);

// 3. Listen for events
realtime.on('connected', () => {
    console.log('✅ MQTT is live and kicking!');
});

realtime.on('message_live', (msg) => {
    console.log(`📩 New message from ${msg.username}: ${msg.text}`);
});

realtime.on('error', (err) => {
    console.error('❌ MQTT Connection issue:', err.message);
});

🔐 Initial Setup (Pairing)

The first time you run your bot, you need to perform a "pairing" login to generate the session files.

const { IgApiClient, RealtimeClient, useMultiFileAuthState } = require('nodejs-insta-private-api-mqtt');

async function pair() {
    const ig = new IgApiClient();
    const folder = './auth_info_ig'; // Default folder for instant boot
    const auth = await useMultiFileAuthState(folder);
    const realtime = new RealtimeClient(ig);

    if (!auth.hasSession()) {
        console.log('Linking account...');
        await ig.state.generateDevice('your_username');
        await ig.login({ username: 'your_username', password: 'your_password' });
        
        // Save credentials
        await auth.saveCreds(ig);
        
        // Fetch initial sync data and connect MQTT
        await realtime.startRealTimeListener();
        
        // Save the MQTT session for instant boot next time
        await auth.saveMqttSession(realtime);
        
        console.log('✅ Pairing complete! Restart your script to see Instant Boot in action.');
    }
}

pair().catch(console.error);

🔄 Comparison: Old vs New MQTT Logic

| Feature | BEFORE (Manual) | NOW (Instant) | |:--- |:--- |:--- | | Setup | Manual login + inbox fetch | Just initialize the client | | MQTT Startup | Manual realtime.connect() | Starts automatically in background | | IRIS Sub | Must pass irisData manually | Auto-fetches and connects | | Session | You manage cookie serialization | Handled by useMultiFileAuthState |

The Old Way (Painful 😫)

const ig = new IgApiClient();
await ig.login({ username: '...', password: '...' });
const inbox = await ig.direct.getInbox(); // Manual step

const realtime = new RealtimeClient(ig);
await realtime.connect({
    graphQlSubs: ['ig_sub_direct'],
    irisData: inbox // Manual injection
});

The New Way (Instant 🚀)

const ig = new IgApiClient();
// If ./auth_info_ig exists, this line starts the background connection!
const realtime = new RealtimeClient(ig); 

realtime.on('connected', () => console.log('✅ Online!'));
const authState = await useMultiFileAuthState(folderPath);

Methods

| Method | Description | |--------|-------------| | hasSession() | Returns true if saved credentials exist | | hasMqttSession() | Returns true if saved MQTT session exists | | loadCreds(ig) | Loads saved credentials into IgApiClient | | saveCreds(ig) | Saves current credentials to files | | isSessionValid(ig) | Validates session with Instagram API | | loadMqttSession() | Returns saved MQTT session data | | saveMqttSession(realtime) | Saves MQTT session from RealtimeClient | | clearSession() | Deletes all saved session files |

Complete Example: Bot with Auto-Reconnect

const { IgApiClient, RealtimeClient, useMultiFileAuthState } = require('nodejs-insta-private-api-mqtt');

const AUTH_FOLDER = './auth_info_instagram';
const USERNAME = process.env.IG_USERNAME;
const PASSWORD = process.env.IG_PASSWORD;

async function startBot() {
  console.log('Starting Instagram Bot...');
  
  const authState = await useMultiFileAuthState(AUTH_FOLDER);
  const ig = new IgApiClient();
  let realtime;

  if (authState.hasSession()) {
    console.log('Found saved session, attempting to restore...');
    
    const loaded = await authState.loadCreds(ig);
    if (loaded) {
      const valid = await authState.isSessionValid(ig);
      
      if (valid) {
        console.log('Session is valid! Connecting to MQTT...');
        realtime = new RealtimeClient(ig);
        
        realtime.on('connected', () => console.log('MQTT Connected!'));
        realtime.on('error', (err) => console.error('MQTT Error:', err.message));
        
        await realtime.connectFromSavedSession(authState);
        
        setupMessageHandler(realtime);
        console.log('Bot is now listening for messages!');
        return;
      }
    }
    
    console.log('Session invalid or expired, clearing...');
    await authState.clearSession();
  }

  // Fresh login required
  console.log('Performing fresh login...');
  
  await ig.login({ username: USERNAME, password: PASSWORD });
  console.log('Login successful!');
  
  await authState.saveCreds(ig);
  
  realtime = new RealtimeClient(ig);
  const inbox = await ig.direct.getInbox();
  
  await realtime.connect({
    graphQlSubs: ['ig_sub_direct', 'ig_sub_direct_v2_message_sync'],
    skywalkerSubs: ['presence_subscribe', 'typing_subscribe'],
    irisData: inbox
  });
  
  await authState.saveMqttSession(realtime);
  
  setupMessageHandler(realtime);
  console.log('Bot is now listening for messages!');
}

function setupMessageHandler(realtime) {
  realtime.on('message', async (data) => {
    const msg = data.message;
    if (!msg?.text) return;
    
    console.log(`New message from ${msg.from_user_id}: ${msg.text}`);
    
    // Auto-reply example
    if (msg.text.toLowerCase().includes('hello')) {
      await realtime.directCommands.sendTextViaRealtime(
        msg.thread_id,
        'Hi there! Thanks for your message!'
      );
    }
  });
}

startBot().catch(console.error);

NEW: iOS Device Emulation (v5.61.11)

This library now supports full iOS device emulation alongside Android. You can connect to Instagram as any iPhone or iPad model, giving you more flexibility for testing and avoiding detection.

Why Use iOS Emulation?

  • Avoid detection - iOS devices have different fingerprints than Android
  • More realistic - Many real users use iPhones
  • Testing - Test how Instagram behaves with iOS vs Android clients
  • Reduce bans - Vary your device types to appear more natural

Quick Start: Use an iPhone

const { IgApiClient } = require('nodejs-insta-private-api-mqtt');

const ig = new IgApiClient();

// Switch to iOS platform with iPhone 16 Pro Max
ig.state.useIOSDevice('iPhone 16 Pro Max');

console.log('Platform:', ig.state.platform);           // 'ios'
console.log('User-Agent:', ig.state.appUserAgent);
// Output: Instagram 347.0.0.36.89 (iPhone17,1; iOS 18.1; en_US; en_US; scale=3.00; 1320x2868; 618023787) AppleWebKit/420+

Available iOS Devices (21 devices)

| Device Name | Model ID | iOS Version | Resolution | |-------------|----------|-------------|------------| | iPhone 16 Pro Max | iPhone17,1 | iOS 18.1 | 1320x2868 | | iPhone 16 Pro | iPhone17,2 | iOS 18.1 | 1206x2622 | | iPhone 16 Plus | iPhone17,3 | iOS 18.1 | 1290x2796 | | iPhone 16 | iPhone17,4 | iOS 18.1 | 1179x2556 | | iPhone 15 Pro Max | iPhone16,2 | iOS 18.1 | 1290x2796 | | iPhone 15 Pro | iPhone16,1 | iOS 18.1 | 1179x2556 | | iPhone 15 Plus | iPhone15,5 | iOS 18.1 | 1290x2796 | | iPhone 15 | iPhone15,4 | iOS 18.1 | 1179x2556 | | iPhone 14 Pro Max | iPhone15,3 | iOS 18.1 | 1290x2796 | | iPhone 14 Pro | iPhone15,2 | iOS 18.1 | 1179x2556 | | iPhone 14 Plus | iPhone14,8 | iOS 18.1 | 1284x2778 | | iPhone 14 | iPhone14,7 | iOS 18.1 | 1170x2532 | | iPhone 13 Pro Max | iPhone14,3 | iOS 17.6 | 1284x2778 | | iPhone 13 Pro | iPhone14,2 | iOS 17.6 | 1170x2532 | | iPhone 13 | iPhone14,5 | iOS 17.6 | 1170x2532 | | iPhone 12 Pro Max | iPhone13,4 | iOS 17.6 | 1284x2778 | | iPhone 12 Pro | iPhone13,3 | iOS 17.6 | 1170x2532 | | iPhone 12 | iPhone13,2 | iOS 17.6 | 1170x2532 | | iPad Pro 12.9 (6th gen) | iPad14,3 | iOS 18.1 | 2048x2732 | | iPad Pro 11 (4th gen) | iPad14,5 | iOS 18.1 | 1668x2388 | | iPad Air (5th gen) | iPad13,18 | iOS 18.1 | 2360x1640 |

List All Available Devices

const ig = new IgApiClient();

// Get all devices grouped by platform
const devices = ig.state.listAllDevices();

console.log('iOS Devices:', devices.ios);
// ['iPhone 16 Pro Max', 'iPhone 16 Pro', 'iPhone 16 Plus', ...]

console.log('Android Devices:', devices.android);
// ['Samsung Galaxy S25 Ultra', 'Google Pixel 9 Pro', ...]

Switch Between iOS and Android

const ig = new IgApiClient();

// Start with Android
ig.state.useAndroidDevice('Samsung Galaxy S25 Ultra');
console.log('Platform:', ig.state.platform);  // 'android'

// Switch to iOS
ig.state.switchPlatform('ios', 'iPhone 16 Pro Max');
console.log('Platform:', ig.state.platform);  // 'ios'

// Switch back to Android with different device
ig.state.switchPlatform('android', 'Google Pixel 9 Pro');
console.log('Platform:', ig.state.platform);  // 'android'

Set Custom iOS Device

const ig = new IgApiClient();

// Set a fully custom iOS device
ig.state.setIOSDevice({
  iosDeviceModel: 'iPhone17,1',
  iosDeviceName: 'iPhone 16 Pro Max',
  iosVersion: '18.1',
  iosAppVersion: '347.0.0.36.89',
  iosAppVersionCode: '618023787'
});

console.log('User-Agent:', ig.state.appUserAgent);

Get Current Platform Info

const ig = new IgApiClient();
ig.state.useIOSDevice('iPhone 15 Pro');

const info = ig.state.getPlatformInfo();

console.log(info);
// {
//   platform: 'ios',
//   userAgent: 'Instagram 347.0.0.36.89 (iPhone16,1; iOS 18.1; ...) AppleWebKit/420+',
//   packageName: 'com.burbn.instagram',
//   deviceId: 'ios-A1B2C3D4E5F6...',
//   iosDeviceModel: 'iPhone16,1',
//   iosDeviceName: 'iPhone 15 Pro',
//   iosVersion: '18.1',
//   iosAppVersion: '347.0.0.36.89'
// }

Complete Example: iOS Bot with useMultiFileAuthState

const { IgApiClient, RealtimeClient, useMultiFileAuthState } = require('nodejs-insta-private-api-mqtt');

async function startIOSBot() {
  const authState = await useMultiFileAuthState('./auth_info_instagram');
  const ig = new IgApiClient();

  // Use iPhone 16 Pro Max for this bot
  ig.state.useIOSDevice('iPhone 16 Pro Max');
  
  console.log('Device:', ig.state.iosDeviceName);
  console.log('User-Agent:', ig.state.appUserAgent);

  if (authState.hasSession()) {
    console.log('Loading saved session...');
    await authState.loadCreds(ig);
    
    const valid = await authState.isSessionValid(ig);
    if (!valid) {
      console.log('Session expired, need fresh login');
      await authState.clearSession();
    }
  }

  if (!authState.hasSession()) {
    console.log('Logging in...');
    await ig.login({
      username: process.env.IG_USERNAME,
      password: process.env.IG_PASSWORD
    });
    await authState.saveCreds(ig);
  }

  // Connect to real-time messaging
  const realtime = new RealtimeClient(ig);
  const inbox = await ig.direct.getInbox();

  await realtime.connect({
    graphQlSubs: ['ig_sub_direct', 'ig_sub_direct_v2_message_sync'],
    skywalkerSubs: ['presence_subscribe', 'typing_subscribe'],
    irisData: inbox
  });

  await authState.saveMqttSession(realtime);
  
  console.log('iOS Bot is online with', ig.state.iosDeviceName);

  realtime.on('message', (data) => {
    console.log('New DM:', data.message?.text);
  });
}

startIOSBot().catch(console.error);

API Reference: iOS Methods

| Method | Description | |--------|-------------| | state.useIOSDevice(name) | Set iOS device from preset (e.g., 'iPhone 16 Pro Max') | | state.setIOSDevice(config) | Set fully custom iOS device configuration | | state.useAndroidDevice(name) | Set Android device from preset | | state.switchPlatform(platform, device) | Switch between 'ios' and 'android' | | state.getPlatformInfo() | Get current platform and device details | | state.listAllDevices() | Get all devices grouped by platform | | state.getIOSDevices() | Get only iOS device presets | | state.getAndroidDevices() | Get only Android device presets | | state.platform | Current platform ('ios' or 'android') | | state.iosUserAgent | iOS-specific User-Agent string | | state.packageName | Package name ('com.burbn.instagram' for iOS) |


All 18 MQTT Methods - Complete Reference

Messaging Methods (Send, Edit, Delete, Reply)

1. Send Text Message

await realtime.directCommands.sendTextViaRealtime(threadId, 'Your message here');

2. Delete Message

await realtime.directCommands.deleteMessage(threadId, messageId);

3. Edit Message

await realtime.directCommands.editMessage(threadId, messageId, 'Updated text');

4. Reply to Message (Quote Reply)

await realtime.directCommands.replyToMessage(threadId, messageId, 'My reply');

5. Send Reaction (Emoji)

await realtime.directCommands.sendReaction({
  threadId: threadId,
  itemId: messageId,
  emoji: '❤️',
  reactionType: 'like',
  reactionStatus: 'created'
});

// Remove reaction
await realtime.directCommands.sendReaction({
  threadId: threadId,
  itemId: messageId,
  emoji: '❤️',
  reactionStatus: 'deleted'
});

Content Sending Methods (Media, Location, Profile, etc)

6. Send Media (Share existing Instagram post)

await realtime.directCommands.sendMedia({
  threadId: threadId,
  mediaId: '12345678',
  text: 'Optional caption'
});

6.1 Send Photo (NEW in v5.60.3 - Upload & Send)

Upload and send a photo directly from a Buffer. This method handles the complete upload process automatically.

const fs = require('fs');

// Read photo from file
const photoBuffer = fs.readFileSync('./photo.jpg');

// Send photo via realtime
await realtime.directCommands.sendPhoto({
  photoBuffer: photoBuffer,
  threadId: threadId,
  caption: 'Check this out!',  // Optional
  mimeType: 'image/jpeg'       // Optional: 'image/jpeg' or 'image/png'
});

Download from URL and send:

const axios = require('axios');

const response = await axios.get('https://example.com/image.jpg', { 
  responseType: 'arraybuffer' 
});
const photoBuffer = Buffer.from(response.data);

await realtime.directCommands.sendPhoto({
  photoBuffer: photoBuffer,
  threadId: threadId
});

6.2 Send Video (NEW in v5.60.3 - Upload & Send)

Upload and send a video directly from a Buffer.

const fs = require('fs');

const videoBuffer = fs.readFileSync('./video.mp4');

await realtime.directCommands.sendVideo({
  videoBuffer: videoBuffer,
  threadId: threadId,
  caption: 'Watch this!',  // Optional
  duration: 15,            // Optional: duration in seconds
  width: 720,              // Optional
  height: 1280             // Optional
});

7. Send Location

await realtime.directCommands.sendLocation({
  threadId: threadId,
  locationId: '213999449',
  text: 'Optional description'
});

8. Send Profile

await realtime.directCommands.sendProfile({
  threadId: threadId,
  userId: '987654321',
  text: 'Optional text'
});

9. Send Hashtag

await realtime.directCommands.sendHashtag({
  threadId: threadId,
  hashtag: 'instagram',
  text: 'Optional text'
});

10. Send Like

await realtime.directCommands.sendLike({ threadId: threadId });

11. Send User Story

await realtime.directCommands.sendUserStory({
  threadId: threadId,
  storyId: 'story_12345',
  text: 'Optional text'
});

Status Methods (Typing, Read Receipts)

12. Mark Message as Seen

await realtime.directCommands.markAsSeen({
  threadId: threadId,
  itemId: messageId
});

13. Indicate Activity (Typing Indicator)

// Show typing indicator
await realtime.directCommands.indicateActivity({
  threadId: threadId,
  isActive: true
});

// Stop typing
await realtime.directCommands.indicateActivity({
  threadId: threadId,
  isActive: false
});

Notification Subscription Methods

14. Subscribe to Follow Notifications

await realtime.directCommands.subscribeToFollowNotifications();

realtime.on('follow', (data) => {
  console.log('New follower:', data.user_id);
});

15. Subscribe to Mention Notifications

await realtime.directCommands.subscribeToMentionNotifications();

realtime.on('mention', (data) => {
  console.log('You were mentioned in:', data.content_type);
});

16. Subscribe to Call Notifications

await realtime.directCommands.subscribeToCallNotifications();

realtime.on('call', (data) => {
  console.log('Incoming call from:', data.caller_id);
});

Group Management Methods

17. Add Member to Thread

await realtime.directCommands.addMemberToThread(threadId, userId);

18. Remove Member from Thread

await realtime.directCommands.removeMemberFromThread(threadId, userId);

NEW: Download Media from Messages (v5.60.8)

This feature provides Baileys-style media download for Instagram DM messages. Just like Baileys' downloadContentFromMessage() for WhatsApp, you can now extract and save photos, videos, and voice messages from Instagram DMs - including view-once (disappearing) media.

Why This Matters

Instagram's view-once media (photos/videos that disappear after viewing) can now be saved before marking as seen. This works because:

  1. Media is received via MQTT with CDN URLs
  2. The "view-once" flag is client-side only
  3. Media remains on Instagram's CDN until expiry (~24 hours)

Quick Start: Save View-Once Photo

const { 
  IgApiClient, 
  RealtimeClient, 
  downloadContentFromMessage,
  isViewOnceMedia 
} = require('nodejs-insta-private-api-mqtt');
const fs = require('fs');

const ig = new IgApiClient();
// ... login code ...

const realtime = new RealtimeClient(ig);
// ... connect code ...

realtime.on('message', async (data) => {
  const msg = data.message;
  
  // Check if it's a view-once message
  if (isViewOnceMedia(msg)) {
    console.log('View-once media received! Saving before it expires...');
    
    try {
      // Download the media as a stream
      const stream = await downloadContentFromMessage(msg);
      
      // Collect chunks into buffer
      let buffer = Buffer.from([]);
      for await (const chunk of stream) {
        buffer = Buffer.concat([buffer, chunk]);
      }
      
      // Determine file extension
      const ext = stream.mediaInfo.type.includes('video') ? 'mp4' : 'jpg';
      const filename = `viewonce_${Date.now()}.${ext}`;
      
      // Save to file
      fs.writeFileSync(filename, buffer);
      
      console.log(`Saved: ${filename} (${buffer.length} bytes)`);
      console.log('Media info:', stream.mediaInfo);
      
    } catch (err) {
      console.error('Failed to download:', err.message);
    }
    
    // DO NOT mark as seen if you want to keep the media secret!
    // await realtime.directCommands.markAsSeen({ threadId: msg.thread_id, itemId: msg.item_id });
  }
});

Download Regular Media (Photos, Videos, Voice)

const { 
  downloadMediaBuffer,
  hasMedia,
  getMediaType,
  MEDIA_TYPES
} = require('nodejs-insta-private-api-mqtt');

realtime.on('message', async (data) => {
  const msg = data.message;
  
  // Check if message contains any media
  if (hasMedia(msg)) {
    const mediaType = getMediaType(msg);
    console.log('Received media type:', mediaType);
    
    // Download as buffer directly
    const { buffer, mediaInfo } = await downloadMediaBuffer(msg);
    
    // Save based on type
    let filename;
    switch (mediaInfo.type) {
      case 'image':
      case 'raven_image':
        filename = `photo_${Date.now()}.jpg`;
        break;
      case 'video':
      case 'raven_video':
        filename = `video_${Date.now()}.mp4`;
        break;
      case 'voice':
        filename = `voice_${Date.now()}.mp4`;
        break;
      default:
        filename = `media_${Date.now()}`;
    }
    
    fs.writeFileSync(filename, buffer);
    console.log(`Saved ${mediaType}: ${filename}`);
  }
});

Extract Media URLs Without Downloading

const { extractMediaUrls } = require('nodejs-insta-private-api-mqtt');

realtime.on('message', async (data) => {
  const msg = data.message;
  
  const mediaInfo = extractMediaUrls(msg);
  
  if (mediaInfo) {
    console.log('Media type:', mediaInfo.type);
    console.log('Is view-once:', mediaInfo.isViewOnce);
    console.log('Dimensions:', mediaInfo.width, 'x', mediaInfo.height);
    console.log('Duration (if video):', mediaInfo.duration);
    
    // Get all quality options
    console.log('Available URLs:');
    mediaInfo.urls.forEach((url, i) => {
      console.log(`  ${i}: ${url.width}x${url.height} - ${url.url.substring(0, 50)}...`);
    });
    
    // Best quality URL is always first
    const bestUrl = mediaInfo.urls[0].url;
  }
});

Complete Bot Example: Auto-Save All Media

const { 
  IgApiClient, 
  RealtimeClient, 
  useMultiFileAuthState,
  downloadMediaBuffer,
  hasMedia,
  isViewOnceMedia,
  MEDIA_TYPES
} = require('nodejs-insta-private-api-mqtt');
const fs = require('fs');
const path = require('path');

const SAVE_DIR = './saved_media';

async function startMediaBot() {
  // Create save directory
  if (!fs.existsSync(SAVE_DIR)) {
    fs.mkdirSync(SAVE_DIR, { recursive: true });
  }

  const authState = await useMultiFileAuthState('./auth_info_instagram');
  const ig = new IgApiClient();

  // Login or restore session
  if (authState.hasSession()) {
    await authState.loadCreds(ig);
  } else {
    await ig.login({
      username: process.env.IG_USERNAME,
      password: process.env.IG_PASSWORD
    });
    await authState.saveCreds(ig);
  }

  const realtime = new RealtimeClient(ig);
  const inbox = await ig.direct.getInbox();

  await realtime.connect({
    graphQlSubs: ['ig_sub_direct', 'ig_sub_direct_v2_message_sync'],
    skywalkerSubs: ['presence_subscribe', 'typing_subscribe'],
    irisData: inbox
  });

  console.log('Media Bot Active - Saving all received media\n');

  realtime.on('message', async (data) => {
    const msg = data.message;
    
    if (!hasMedia(msg)) return;
    
    try {
      const { buffer, mediaInfo } = await downloadMediaBuffer(msg);
      
      // Generate filename with metadata
      const prefix = mediaInfo.isViewOnce ? 'VIEWONCE_' : '';
      const ext = getExtension(mediaInfo.type);
      const filename = `${prefix}${mediaInfo.type}_${Date.now()}.${ext}`;
      const filepath = path.join(SAVE_DIR, filename);
      
      fs.writeFileSync(filepath, buffer);
      
      console.log(`[SAVED] ${filename}`);
      console.log(`  Type: ${mediaInfo.type}`);
      console.log(`  Size: ${buffer.length} bytes`);
      console.log(`  Dimensions: ${mediaInfo.width}x${mediaInfo.height}`);
      if (mediaInfo.isViewOnce) {
        console.log('  WARNING: View-once media - do not mark as seen!');
      }
      console.log('');
      
    } catch (err) {
      console.error('Download failed:', err.message);
    }
  });

  await new Promise(() => {});
}

function getExtension(type) {
  switch (type) {
    case MEDIA_TYPES.IMAGE:
    case MEDIA_TYPES.RAVEN_IMAGE:
    case MEDIA_TYPES.MEDIA_SHARE:
    case MEDIA_TYPES.STORY_SHARE:
      return 'jpg';
    case MEDIA_TYPES.VIDEO:
    case MEDIA_TYPES.RAVEN_VIDEO:
    case MEDIA_TYPES.CLIP:
    case MEDIA_TYPES.VOICE:
      return 'mp4';
    default:
      return 'bin';
  }
}

startMediaBot().catch(console.error);

API Reference: Media Download Functions

| Function | Description | |----------|-------------| | downloadContentFromMessage(message, type?, options?) | Download media as a readable stream (like Baileys) | | downloadMediaBuffer(message, type?, options?) | Download media directly as Buffer | | extractMediaUrls(message) | Extract media URLs and metadata without downloading | | hasMedia(message) | Check if message contains downloadable media | | getMediaType(message) | Get media type without downloading | | isViewOnceMedia(message) | Check if message is view-once (disappearing) |

downloadContentFromMessage() Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | quality | number | 0 | Quality index (0 = highest quality) | | timeout | number | 30000 | Request timeout in milliseconds | | headers | object | {} | Additional HTTP headers |

MEDIA_TYPES Constants

const { MEDIA_TYPES } = require('nodejs-insta-private-api-mqtt');

MEDIA_TYPES.IMAGE         // Regular photo
MEDIA_TYPES.VIDEO         // Regular video
MEDIA_TYPES.VOICE         // Voice message
MEDIA_TYPES.RAVEN_IMAGE   // View-once photo
MEDIA_TYPES.RAVEN_VIDEO   // View-once video
MEDIA_TYPES.MEDIA_SHARE   // Shared post
MEDIA_TYPES.REEL_SHARE    // Shared reel
MEDIA_TYPES.STORY_SHARE   // Shared story
MEDIA_TYPES.CLIP          // Clip/Reel

Important Notes

  1. Download BEFORE marking as seen - For view-once media, download immediately when message is received. Once you call markAsSeen(), Instagram knows you viewed it.

  2. Media expiry - View-once media URLs expire after ~24 hours. Regular media URLs may last longer but are not permanent.

  3. Rate limiting - Downloading many files quickly may trigger Instagram's rate limits. Add delays between downloads if processing many messages.

  4. Legal considerations - This feature is for personal backup purposes. Respect privacy and applicable laws regarding saved media.


Building Instagram Bots

Example 1: Auto-Reply Bot (Keyword Triggered)

const { IgApiClient, RealtimeClient } = require('nodejs-insta-private-api-mqtt');
const fs = require('fs');

(async () => {
  const ig = new IgApiClient();
  const session = JSON.parse(fs.readFileSync('session.json'));
  await ig.state.deserialize(session);

  const realtime = new RealtimeClient(ig);
  const inbox = await ig.direct.getInbox();
  
  await realtime.connect({
    graphQlSubs: ['ig_sub_direct', 'ig_sub_direct_v2_message_sync'],
    skywalkerSubs: ['presence_subscribe', 'typing_subscribe'],
    irisData: inbox
  });

  console.log('Auto-Reply Bot Active\n');

  realtime.on('message', async (data) => {
    const msg = data.message;
    if (!msg?.text) return;

    console.log(`[${msg.from_user_id}]: ${msg.text}`);

    let reply = null;

    if (msg.text.toLowerCase().includes('hello')) {
      reply = 'Hey! Thanks for reaching out!';
    } else if (msg.text.toLowerCase().includes('help')) {
      reply = 'How can I assist you?';
    } else if (msg.text.toLowerCase().includes('thanks')) {
      reply = 'You are welcome!';
    }

    if (reply) {
      try {
        await realtime.directCommands.sendTextViaRealtime(msg.thread_id, reply);
        console.log(`Replied: ${reply}\n`);
      } catch (err) {
        console.error(`Failed to send reply: ${err.message}\n`);
      }
    }
  });

  await new Promise(() => {});
})();

Example 2: Smart Bot with Reactions and Typing

const { IgApiClient, RealtimeClient } = require('nodejs-insta-private-api-mqtt');
const fs = require('fs');

(async () => {
  const ig = new IgApiClient();
  const session = JSON.parse(fs.readFileSync('session.json'));
  await ig.state.deserialize(session);

  const realtime = new RealtimeClient(ig);
  const inbox = await ig.direct.getInbox();
  
  await realtime.connect({
    graphQlSubs: ['ig_sub_direct', 'ig_sub_direct_v2_message_sync'],
    skywalkerSubs: ['presence_subscribe', 'typing_subscribe'],
    irisData: inbox
  });

  console.log('Smart Bot Started\n');

  realtime.on('message', async (data) => {
    const msg = data.message;
    if (!msg?.text) return;

    // Mark as seen immediately
    await realtime.directCommands.markAsSeen({
      threadId: msg.thread_id,
      itemId: msg.item_id
    });

    // Show typing indicator
    await realtime.directCommands.indicateActivity({
      threadId: msg.thread_id,
      isActive: true
    });

    // Simulate processing time
    await new Promise(r => setTimeout(r, 2000));

    // Send reply
    if (msg.text.toLowerCase().includes('hi')) {
      await realtime.directCommands.sendTextViaRealtime(
        msg.thread_id,
        'Hello there! How can I help?'
      );

      // React with emoji
      await realtime.directCommands.sendReaction({
        threadId: msg.thread_id,
        itemId: msg.item_id,
        emoji: '👋'
      });
    }

    // Stop typing
    await realtime.directCommands.indicateActivity({
      threadId: msg.thread_id,
      isActive: false
    });
  });

  await new Promise(() => {});
})();


Example 3: CLI Photo Sender (upload HTTP + broadcast HTTP, with MQTT sync)

This example is a ready-to-run CLI script that demonstrates the correct flow to send a photo to one or more threads:

  1. upload the photo to Instagram's rupload endpoint (HTTP),
  2. broadcast (configure) the photo to the target thread(s) using the Direct API (HTTP),
  3. use MQTT (RealtimeClient) only for realtime sync and confirmation.

Save this as examples/send-photo-cli.js.

#!/usr/bin/env node
// examples/send-photo-cli.js
// Node 18+, install nodejs-insta-private-api-mqtt in the project

const fs = require('fs');
const path = require('path');
const readline = require('readline/promises');
const { stdin: input, stdout: output } = require('process');
const { v4: uuidv4 } = require('uuid');

(async () => {
  const rl = readline.createInterface({ input, output });
  try {
    console.log('\\n--- Instagram Photo Sender (HTTP + broadcast) ---\\n');
    const username = (await rl.question('Enter your Instagram username: ')).trim();
    const password = (await rl.question('Enter your Instagram password: ')).trim();
    if (!username || !password) {
      console.error('username & password required'); process.exit(1);
    }

    const { IgApiClient, RealtimeClient, useMultiFileAuthState } = require('nodejs-insta-private-api-mqtt');
    const AUTH_FOLDER = path.resolve(process.cwd(), './auth_info_instagram');

    const authState = await useMultiFileAuthState(AUTH_FOLDER);
    const ig = new IgApiClient();
    ig.state.generateDevice?.(username);

    // load saved credentials if available
    if (authState.hasSession && authState.hasSession()) {
      try { await authState.loadCreds?.(ig); console.log('[*] Loaded saved creds'); } catch(e){}
    }

    // login if needed
    if (!authState.hasSession || (typeof authState.hasSession === 'function' && !authState.hasSession())) {
      await ig.login({ username, password });
      await authState.saveCreds?.(ig);
      console.log('[+] Logged in and saved creds');
    }

    // start realtime client (optional, helps sync)
    const realtime = new RealtimeClient(ig);
    realtime.on?.('connected', () => console.log('✅ MQTT connected (realtime)'));

    // fetch inbox and list threads
    const inbox = await ig.direct.getInbox();
    const threads = inbox.threads || inbox.items || inbox.threads_response?.threads || [];
    if (!threads || threads.length === 0) {
      console.error('No threads found in inbox. Exiting.'); process.exit(1);
    }

    console.log('\\nAvailable threads:');
    const indexToThread = [];
    threads.slice(0, 50).forEach((t, i) => {
      const id = String(t.thread_id || t.threadId || t.id || t.thread_id_str || (t.thread && t.thread.thread_id) || '');
      const title = t.thread_title || t.title || t.item_title || (t.users || t.participants || []).map(u => u.username || u.full_name).slice(0,3).join(', ') || 'Unnamed';
      indexToThread.push({ id, title });
      console.log(`${i+1}. ${title} (thread_id: ${id})`);
    });

    const pick = (await rl.question('\\nSelect thread numbers (e.g. "1 2 3"): ')).trim();
    const picks = pick.split(/[\s,]+/).map(x => parseInt(x,10)).filter(n => Number.isFinite(n) && n >= 1 && n <= indexToThread.length);
    const selected = picks.map(p => indexToThread[p-1].id);

    const photoPath = (await rl.question('\\nEnter your Photo path here (absolute or relative): ')).trim();
    const resolved = path.isAbsolute(photoPath) ? photoPath : path.resolve(process.cwd(), photoPath);
    if (!fs.existsSync(resolved)) { console.error('File not found', resolved); process.exit(1); }
    const buffer = fs.readFileSync(resolved);
    console.log('Photo size (MB):', (buffer.length/(1024*1024)).toFixed(2));

    // RUUPLOAD (HTTP) - try to use ig.publish.photo helper if available
    let uploadId;
    try {
      if (typeof ig.publish?.photo === 'function') {
        const up = await ig.publish.photo({ file: buffer });
        uploadId = up.upload_id || up.uploadId || up;
      } else {
        // manual rupload via request
        uploadId = Date.now().toString();
        const objectName = `${uploadId}_0_${Math.floor(Math.random() * 1e10)}`;
        const ruploadParams = {
          retry_context: JSON.stringify({ num_step_auto_retry: 0, num_reupload: 0, num_step_manual_retry: 0 }),
          media_type: '1',
          upload_id: uploadId,
          xsharing_user_ids: JSON.stringify([]),
          image_compression: JSON.stringify({ lib_name: 'moz', lib_version: '3.1.m', quality: '80' })
        };
        const headers = {
          'X-Instagram-Rupload-Params': JSON.stringify(ruploadParams),
          'Content-Type': 'application/octet-stream',
          'X-Entity-Type': 'image/jpeg',
          'Offset': '0',
          'X-Entity-Name': objectName,
          'X-Entity-Length': String(buffer.length),
          'Content-Length': String(buffer.length),
        };
        await ig.request.send({
          url: `/rupload_igphoto/${objectName}`,
          method: 'POST',
          headers,
          body: buffer,
          timeout: 120000,
        });
      }
      console.log('[+] Upload completed. upload_id =', uploadId);
    } catch (err) {
      console.error('Upload failed:', err?.message || err); process.exit(1);
    }

    // BROADCAST (HTTP) - create message on server
    try {
      const mutationToken = uuidv4();
      const payload = {
        action: 'configure_photo',
        upload_id: String(uploadId),
        thread_ids: JSON.stringify(selected),
        client_context: mutationToken,
        mutation_token: mutationToken,
        _csrftoken: ig.state.cookieCsrfToken || '',
        _uuid: ig.state.uuid || '',
        device_id: ig.state.deviceId || ''
      };
      const resp = await ig.request.send({
        url: '/api/v1/direct_v2/threads/broadcast/configure_photo/',
        method: 'POST',
        form: payload,
        timeout: 60000,
      });
      console.log('✅ Broadcast response received:', resp && (resp.status || resp.body || resp.data) ? 'OK' : JSON.stringify(resp).slice(0,120));
    } catch (err) {
      console.error('Broadcast failed:', err?.message || err);
      process.exit(1);
    }

    console.log('\\nDone. MQTT will sync and you should see the message in the app.');
    rl.close();
    process.exit(0);
  } catch (e) {
    console.error('Fatal:', e);
    process.exit(1);
  }
})();

Example 4: Video Sender (upload + broadcast)

This is similar to the photo sender but uses the video upload flow and longer timeouts. Use ig.publish.video() if available; otherwise rupload manually and call /api/v1/direct_v2/threads/broadcast/configure_video/.

// pseudo-code outline
const videoBuffer = fs.readFileSync('./video.mp4');
const upload = await ig.publish.video({ video: videoBuffer, duration: 12000 });
// upload.upload_id -> then broadcast with configure_video payload similar to the photo flow

Notes: video uploads often require multipart/segmented upload and longer timeouts; prefer library helper ig.publish.video().


Example 5: Share a file / document (workaround)

Instagram DM does not provide a simple "attach arbitrary file" endpoint. Common workarounds:

  1. Upload the file to a hosting service (S3, Dropbox, your own server), then send the link as a text message via MQTT (or HTTP direct send).
  2. If the file is an image or video, use the rupload + broadcast flow above.

Example (send link message):

// send link as text via realtime (MQTT)
await realtime.directCommands.sendTextViaRealtime(threadId, 'Download the file here: https://myhost.example.com/myfile.pdf');

Tips for bots sending media

  • Always upload first, then broadcast. Do not rely on MQTT-only calls for media.
  • Use retries and exponential backoff for both rupload and broadcast steps (Instagram can return transient 503).
  • Save upload_id and broadcast responses for debugging.
  • If you need to send the same file to many threads, upload once, reuse the same upload_id in multiple broadcast calls.

API Reference

IgApiClient

Authentication

// Login with credentials
await ig.login({
  username: 'your_username',
  password: 'your_password',
  email: '[email protected]'
});

// Load from saved session
const session = JSON.parse(fs.readFileSync('session.json'));
await ig.state.deserialize(session);

// Save session
const serialized = ig.state.serialize();
fs.writeFileSync('session.json', JSON.stringify(serialized));

Direct Messages

// Get inbox with all conversations
const inbox = await ig.direct.getInbox();

// Get specific thread messages
const thread = await ig.direct.getThread(threadId);

// Send text message (HTTP - slower than MQTT)
await ig.direct.send({
  threadId: threadId,
  item: {
    type: 'text',
    text: 'Hello there!'
  }
});

// Mark messages as seen
await ig.direct.markMessagesSeen(threadId, [messageId]);

RealtimeClient

Connection

const realtime = new RealtimeClient(ig);

// Connect to MQTT
await realtime.connect({
  graphQlSubs: ['ig_sub_direct', 'ig_sub_direct_v2_message_sync'],
  skywalkerSubs: ['presence_subscribe', 'typing_subscribe'],
  irisData: inbox  // Required: inbox data from ig.direct.getInbox()
});

// Connect from saved session (NEW in v5.60.2)
await realtime.connectFromSavedSession(authState);

All 18 MQTT Methods via directCommands

// 1. Send Text
await realtime.directCommands.sendTextViaRealtime(threadId, 'Text');

// 2. Delete Message
await realtime.directCommands.deleteMessage(threadId, messageId);

// 3. Edit Message
await realtime.directCommands.editMessage(threadId, messageId, 'New text');

// 4. Reply to Message
await realtime.directCommands.replyToMessage(threadId, messageId, 'Reply');

// 5. Send Reaction
await realtime.directCommands.sendReaction({
  threadId, itemId, emoji: '❤️'
});

// 6. Send Media
await realtime.directCommands.sendMedia({ threadId, mediaId });

// 7. Send Location
await realtime.directCommands.sendLocation({ threadId, locationId });

// 8. Send Profile
await realtime.directCommands.sendProfile({ threadId, userId });

// 9. Send Hashtag
await realtime.directCommands.sendHashtag({ threadId, hashtag });

// 10. Send Like
await realtime.directCommands.sendLike({ threadId });

// 11. Send Story
await realtime.directCommands.sendUserStory({ threadId, storyId });

// 12. Mark as Seen
await realtime.directCommands.markAsSeen({ threadId, itemId });

// 13. Indicate Activity (Typing)
await realtime.directCommands.indicateActivity({ threadId, isActive: true });

// 14. Subscribe to Follow Notifications
await realtime.directCommands.subscribeToFollowNotifications();

// 15. Subscribe to Mention Notifications
await realtime.directCommands.subscribeToMentionNotifications();

// 16. Subscribe to Call Notifications
await realtime.directCommands.subscribeToCallNotifications();

// 17. Add Member to Thread
await realtime.directCommands.addMemberToThread(threadId, userId);

// 18. Remove Member from Thread
await realtime.directCommands.removeMemberFromThread(threadId, userId);

Listening for Events

// Incoming messages
realtime.on('message', (data) => {
  const msg = data.message;
  console.log(msg.text);           // Message text
  console.log(msg.from_user_id);   // Sender user ID
  console.log(msg.thread_id);      // Conversation thread ID
});

// Connection status
realtime.on('connected', () => console.log('Connected'));
realtime.on('disconnected', () => console.log('Disconnected'));

// Notifications
realtime.on('follow', (data) => console.log('New follower:', data.user_id));
realtime.on('mention', (data) => console.log('Mentioned:', data.content_type));
realtime.on('call', (data) => console.log('Call from:', data.caller_id));

// Errors
realtime.on('error', (err) => console.error('Error:', err.message));

User Information

// Get user info by username
const user = await ig.user.info('username');

// Search users
const results = await ig.user.search({ username: 'query' });

// Get followers
const followers = await ig.user.followers('user_id');

// Get following
const following = await ig.user.following('user_id');

Message Structure

Messages arrive as event data with this structure:

realtime.on('message', (data) => {
  const msg = data.message;
  
  console.log({
    text: msg.text,                // Message content (string)
    from_user_id: msg.from_user_id,  // Sender's Instagram user ID
    thread_id: msg.thread_id,      // Conversation thread ID
    timestamp: msg.timestamp,      // Unix timestamp
    item_id: msg.item_id           // Unique message ID
  });
});

Performance & Latency

| Operation | Latency | Method | |-----------|---------|--------| | Receive incoming DM | 100-500ms | MQTT (real-time) | | Send DM via MQTT | 200-800ms | Direct MQTT publish | | Send DM via HTTP | 1-3s | REST API fallback | | Get inbox | 500ms-2s | REST API |

MQTT is significantly faster for both receiving and sending messages.


Best Practices

1. Session Management (Recommended: useMultiFileAuthState)

// NEW: Use multi-file auth state for better session management
const authState = await useMultiFileAuthState('./auth_info_instagram');

// Check and load existing session
if (authState.hasSession()) {
  await authState.loadCreds(ig);
  if (await authState.isSessionValid(ig)) {
    // Session is valid, proceed
  }
}

// Save after login
await authState.saveCreds(ig);
await authState.saveMqttSession(realtime);

2. Error Handling

realtime.on('message', async (data) => {
  try {
    const msg = data.message;
    await realtime.directCommands.sendTextViaRealtime(msg.thread_id, 'Reply');
  } catch (err) {
    console.error('Error:', err.message);
  }
});

3. Rate Limiting

const userLastSeen = new Map();

if (userLastSeen.has(userId) && Date.now() - userLastSeen.get(userId) < 5000) {
  return;
}
userLastSeen.set(userId, Date.now());

4. Connection Monitoring

let isConnected = false;

realtime.on('connected', () => {
  isConnected = true;
  console.log('Connected');
});

realtime.on('disconnected', () => {
  isConnected = false;
  console.log('Disconnected - will auto-reconnect');
});

Limitations

  • Instagram account required - No API tokens needed, use your credentials
  • Rate limiting - Instagram rate limits automated messaging, implement delays
  • Mobile detection - Instagram may detect bot activity and require verification
  • Session expiry - Sessions may expire after 60+ days, require re-login
  • Message history - Only real-time messages available, no historical message sync

Troubleshooting

Login Fails

// Ensure credentials are correct
// Instagram may require 2FA verification

MQTT Connection Fails

// Check that inbox data is loaded before connecting
const inbox = await ig.direct.getInbox();

// Connection retries automatically with exponential backoff

Messages Not Sending

// Ensure MQTT is connected
if (!isConnected) {
  console.log('Waiting for MQTT connection...');
  return;
}

// Check rate limiting - Instagram blocks rapid messaging

Changelog

v5.60.8

  • NEW: downloadContentFromMessage() - Download media from DM messages (like Baileys for WhatsApp)
  • NEW: downloadMediaBuffer() - Get media as Buffer directly
  • NEW: extractMediaUrls() - Extract CDN URLs from any message type
  • NEW: hasMedia() - Check if message contains downloadable media
  • NEW: getMediaType() - Get media type without downloading
  • NEW: isViewOnceMedia() - Check if message is view-once (disappearing)
  • NEW: MEDIA_TYPES constant for all supported media types
  • Full support for view-once (raven) media extraction
  • Support for photos, videos, voice messages, reels, stories, clips

v5.60.7

  • Added Custom Device Emulation with 12 preset devices
  • setCustomDevice() and usePresetDevice() methods
  • Default device: Samsung Galaxy S25 Ultra (Android 15)

v5.60.3

  • Added sendPhoto() and sendVideo() for media uploads via MQTT

v5.60.2

  • Added useMultiFileAuthState() - Baileys-style multi-file session persistence
  • Added connectFromSavedSession() method for RealtimeClient
  • Session now persists both HTTP auth and MQTT real-time data
  • 7 separate files for better organization and security

v5.60.1

  • Fixed MQTT message sync issues
  • Improved connection stability

v5.60.0

  • Full MQTT integration with 18 methods
  • Real-time messaging with <500ms latency

License

MIT

Support

For issues, bugs, or feature requests: https://github.com/Kunboruto20/nodejs-insta-private-api/issues

Documentation: https://github.com/Kunboruto20/nodejs-insta-private-api

Examples: See repository examples/ directory for working implementations


Examples using ig.request.send wrapper (photo & video, MQTT reference + REST broadcast)

Below are practical code snippets that use the ig.request.send wrapper (the same request helper used inside the Instagram client).
Each example shows two flows so you can