nodejs-insta-private-api-mqtt 1.3.39 → 1.3.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -30,7 +30,7 @@ By leveraging MQTT instead of Instagram's REST API, this library achieves sub-50
30
30
  - **FIX: IRIS subscription auto-fetch** - Messages now received automatically without manual irisData
31
31
  - **FIX: startRealTimeListener()** - Fixed to use correct inbox method for IRIS data retrieval
32
32
  - **NEW: sendPhoto() / sendVideo()** - Upload and send photos/videos directly via MQTT
33
- - **NEW: Clear message display** - Incoming messages shown with Username, ID, Text, Status (no emojis)
33
+ - **NEW: Clear message display** - Incoming messages shown with Username, ID, Text, Status
34
34
  - **NEW: All message types decoded** - Photos, videos, voice, reels, stories, links displayed clearly
35
35
  - Multi-file auth state - Session persistence like Baileys (WhatsApp library)
36
36
  - Real-time MQTT messaging - Receive and send DMs with <500ms latency
@@ -82,8 +82,6 @@ This feature allows you to **choose which phone model Instagram sees** when your
82
82
 
83
83
  ### Quick Start: Use a Preset Device
84
84
 
85
- The easiest way to set a custom device is using the built-in presets:
86
-
87
85
  ```javascript
88
86
  const { IgApiClient } = require('nodejs-insta-private-api');
89
87
 
@@ -99,7 +97,6 @@ await ig.login({
99
97
  });
100
98
 
101
99
  console.log('Logged in with device:', ig.state.deviceString);
102
- // Output: 35/15; 505dpi; 1440x3120; samsung; SM-S928B; e3q; qcom
103
100
  ```
104
101
 
105
102
  ### Available Preset Devices
@@ -119,20 +116,6 @@ console.log('Logged in with device:', ig.state.deviceString);
119
116
  | Xiaomi Redmi Note 13 Pro | Xiaomi | Android 14 |
120
117
  | OPPO Find X7 Ultra | OPPO | Android 14 |
121
118
 
122
- ### List All Available Presets
123
-
124
- ```javascript
125
- const ig = new IgApiClient();
126
-
127
- // Get all available preset devices
128
- const presets = ig.state.getPresetDevices();
129
-
130
- console.log('Available devices:');
131
- Object.keys(presets).forEach((name, i) => {
132
- console.log(`${i + 1}. ${name}`);
133
- });
134
- ```
135
-
136
119
  ### Set a Fully Custom Device
137
120
 
138
121
  For complete control, use `setCustomDevice()` with your own configuration:
@@ -140,1236 +123,548 @@ For complete control, use `setCustomDevice()` with your own configuration:
140
123
  ```javascript
141
124
  const ig = new IgApiClient();
142
125
 
143
- // Define your custom device
144
126
  ig.state.setCustomDevice({
145
- manufacturer: 'samsung', // Phone manufacturer
146
- model: 'SM-S928B', // Model code
147
- device: 'e3q', // Device codename
148
- androidVersion: '15', // Android version
149
- androidApiLevel: 35, // Android API level
150
- resolution: '1440x3120', // Screen resolution
151
- dpi: '505dpi', // Screen density
152
- chipset: 'qcom', // Chipset (qcom, kirin, google, etc.)
153
- build: 'UP1A.231005.007' // Build number (optional)
127
+ manufacturer: 'samsung',
128
+ model: 'SM-S928B',
129
+ device: 'e3q',
130
+ androidVersion: '15',
131
+ androidApiLevel: 35,
132
+ resolution: '1440x3120',
133
+ dpi: '505dpi',
134
+ chipset: 'qcom',
135
+ build: 'UP1A.231005.007'
154
136
  });
155
-
156
- console.log('Device string:', ig.state.deviceString);
157
- console.log('User agent:', ig.state.appUserAgent);
158
- ```
159
-
160
- ### Get Current Device Info
161
-
162
- ```javascript
163
- const ig = new IgApiClient();
164
- ig.state.usePresetDevice('Google Pixel 9 Pro');
165
-
166
- // Get full device information
167
- const info = ig.state.getCurrentDeviceInfo();
168
-
169
- console.log('Device String:', info.deviceString);
170
- console.log('Device ID:', info.deviceId);
171
- console.log('UUID:', info.uuid);
172
- console.log('Phone ID:', info.phoneId);
173
- console.log('Build:', info.build);
174
- console.log('User Agent:', info.userAgent);
175
137
  ```
176
138
 
177
- ### 178. Complete Bot Example with Instant MQTT Boot
139
+ ---
178
140
 
179
- This example demonstrates how to build a production-ready bot that restores its session and connects to MQTT instantly upon startup.
141
+ ## Quick Start: Instant MQTT Boot
180
142
 
181
143
  ```javascript
182
144
  const { IgApiClient, RealtimeClient, useMultiFileAuthState } = require('nodejs-insta-private-api-mqtt');
183
145
 
184
146
  async function startBot() {
185
147
  const ig = new IgApiClient();
186
-
187
- // 1. Initialize the multi-file auth state (Baileys style)
188
- // This folder stores your credentials, cookies, and MQTT session
189
148
  const auth = await useMultiFileAuthState('./auth_info_ig');
190
149
 
191
- // 2. Setup your device emulation
192
150
  ig.state.usePresetDevice('Samsung Galaxy S25 Ultra');
193
151
 
194
- // 3. Initialize the RealtimeClient
195
- // IMPORTANT: Because we modified the constructor, this will AUTOMATICALLY
196
- // detect the session in ./auth_info_ig and start MQTT in the background.
197
152
  const realtime = new RealtimeClient(ig);
198
153
 
199
- // 4. Set up your event listeners
200
154
  realtime.on('connected', () => {
201
- console.log('🚀 Bot is online and MQTT is connected!');
155
+ console.log('Bot is online and MQTT is connected!');
202
156
  });
203
157
 
204
158
  realtime.on('message_live', async (msg) => {
205
- console.log(`📩 [${msg.username}]: ${msg.text}`);
159
+ console.log(`[${msg.username}]: ${msg.text}`);
206
160
 
207
- // Simple auto-reply logic
208
161
  if (msg.text.toLowerCase() === 'ping') {
209
- await realtime.directCommands.sendTextViaRealtime(msg.thread_id, 'pong! 🏓');
162
+ await realtime.directCommands.sendText({
163
+ threadId: msg.thread_id,
164
+ text: 'pong!'
165
+ });
210
166
  }
211
167
  });
212
168
 
213
- // 5. Handle the initial "Pairing" (only happens once)
214
169
  if (!auth.hasSession()) {
215
- console.log('No session found. Please login to pair your account...');
216
-
217
170
  await ig.login({
218
171
  username: 'your_username',
219
172
  password: 'your_password'
220
173
  });
221
174
 
222
- // Save credentials for the next boot
223
175
  await auth.saveCreds(ig);
224
-
225
- // Start the realtime sync for the first time
226
176
  await realtime.startRealTimeListener();
227
-
228
- // Save the MQTT state so we can boot instantly next time
229
177
  await auth.saveMqttSession(realtime);
230
-
231
- console.log('✅ Pairing successful! Next time you run this, it will boot INSTANTLY.');
232
- } else {
233
- console.log('✨ Existing session detected. Instant MQTT boot sequence initiated...');
234
178
  }
235
179
  }
236
180
 
237
181
  startBot().catch(console.error);
238
182
  ```
239
183
 
240
- ### API Reference: Device Emulation Methods
241
-
242
- | Method | Description |
243
- |--------|-------------|
244
- | `state.usePresetDevice(name)` | Set device from preset list (e.g., 'Samsung Galaxy S25 Ultra') |
245
- | `state.setCustomDevice(config)` | Set fully custom device configuration |
246
- | `state.getPresetDevices()` | Get object with all available preset devices |
247
- | `state.getCurrentDeviceInfo()` | Get current device configuration |
248
- | `state.deviceString` | Current device string used in requests |
249
- | `state.appUserAgent` | Full User-Agent header sent to Instagram |
250
-
251
- ### setCustomDevice() Configuration Options
252
-
253
- | Property | Type | Description | Example |
254
- |----------|------|-------------|---------|
255
- | `manufacturer` | string | Phone manufacturer | 'samsung', 'HUAWEI', 'Google' |
256
- | `model` | string | Phone model code | 'SM-S928B', 'Pixel 9 Pro' |
257
- | `device` | string | Device codename | 'e3q', 'husky', 'caiman' |
258
- | `androidVersion` | string | Android version | '15', '14', '13' |
259
- | `androidApiLevel` | number | Android API level | 35, 34, 33 |
260
- | `resolution` | string | Screen resolution | '1440x3120', '1080x2340' |
261
- | `dpi` | string | Screen density | '505dpi', '480dpi', '420dpi' |
262
- | `chipset` | string | Chipset identifier | 'qcom', 'kirin', 'google' |
263
- | `build` | string | Build number (optional) | 'UP1A.231005.007' |
264
-
265
184
  ---
266
185
 
186
+ ## EnhancedDirectCommands - Complete MQTT Methods Reference
267
187
 
268
- ## 🖼️ IMPORTANT: How media actually works (photos & videos)
269
-
270
- ## 🖼️ IMPORTANT: How media actually works (photos & videos) — UPDATED (rupload + MQTT reference)
188
+ All MQTT direct messaging functionality is available through `realtime.directCommands`. These methods use proper payload formatting that matches the instagram_mqtt library format.
271
189
 
272
- > **Clarification (APK + client behaviour):**
273
- > 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".
274
- >
275
- > **Key points**
276
- > - **Media bytes are uploaded over HTTP (rupload).** MQTT never carries the raw image/video bytes.
277
- > - **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".
278
- > - 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.
190
+ ### Basic Messaging
279
191
 
280
- ---
192
+ #### Send Text Message
281
193
 
282
- ### Correct / supported patterns
194
+ ```javascript
195
+ await realtime.directCommands.sendText({
196
+ threadId: '340282366841710300949128114477782749726',
197
+ text: 'Hello from MQTT!'
198
+ });
199
+ ```
283
200
 
284
- There are two practical, compatible flows you can use depending on your client helpers and what the server expects:
201
+ #### Send Text (Alternative Signature)
285
202
 
286
- **A) Hybrid (rupload HTTP -> MQTT reference broadcast)** — matches what the APK does in many versions
287
- 1. Upload bytes to rupload_igphoto / rupload_igvideo (HTTP) -> receive `upload_id`.
288
- 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.
289
- - MQTT payload is **only the metadata** (JSON), typically compressed/deflated by the realtime client before publish.
290
- - This is the recommended **APK-compatible** approach.
203
+ ```javascript
204
+ await realtime.directCommands.sendTextViaRealtime(threadId, 'Hello!');
205
+ ```
291
206
 
292
- **B) Full HTTP (rupload HTTP -> configure/broadcast HTTP)** — always works, server-side creation via REST
293
- 1. Upload bytes to rupload endpoint (HTTP) -> receive `upload_id`.
294
- 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.
295
- - Use this if you prefer to avoid relying on the realtime command to create messages.
207
+ #### Reply to Message (Quote Reply)
296
208
 
297
- > 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.
209
+ ```javascript
210
+ await realtime.directCommands.replyToMessage(threadId, messageId, 'This is my reply');
211
+ ```
298
212
 
299
- ---
213
+ #### Edit Message
300
214
 
301
- ### Example: Hybrid flow — Photo (rupload HTTP then MQTT reference)
215
+ ```javascript
216
+ await realtime.directCommands.editMessage(threadId, itemId, 'Updated text here');
217
+ ```
302
218
 
303
- 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.
219
+ #### Delete Message
304
220
 
305
221
  ```javascript
306
- // 1) RUUPLOAD (HTTP) - upload bytes and get upload_id
307
- const uploadId = Date.now().toString(); // or use library helper that returns upload_id
308
- const objectName = `${uploadId}_${Math.floor(Math.random()*1e9)}.jpg`;
309
-
310
- const ruploadParams = {
311
- upload_id: uploadId,
312
- media_type: 1, // photo
313
- image_compression: '{"lib_name":"moz","lib_version":"3.1.m","quality":"80"}',
314
- xsharing_user_ids: JSON.stringify([]),
315
- is_clips_media: false,
316
- };
317
-
318
- const headers = {
319
- 'X-Instagram-Rupload-Params': JSON.stringify(ruploadParams),
320
- 'Content-Type': 'image/jpeg',
321
- 'X-Entity-Type': 'image/jpeg',
322
- 'X-Entity-Length': String(photoBuffer.length),
323
- 'Content-Length': String(photoBuffer.length),
324
- };
222
+ await realtime.directCommands.deleteMessage(threadId, itemId);
223
+ ```
325
224
 
326
- // send bytes to /rupload_igphoto/<objectName>
327
- await ig.request.send({
328
- url: `/rupload_igphoto/${objectName}`,
329
- method: 'POST',
330
- headers,
331
- body: photoBuffer,
332
- timeout: 120000,
333
- });
225
+ ---
334
226
 
335
- // server should now provide or accept the upload_id (uploadId)
336
-
337
- // 2) MQTT publish - tell realtime: "attach upload_id X to thread Y"
338
- const clientContext = require('uuid').v4();
339
- const command = {
340
- action: 'send_item',
341
- thread_id: String(threadId),
342
- item_type: 'media',
343
- upload_id: String(uploadId),
344
- text: caption || '',
345
- timestamp: Date.now(),
346
- client_context: clientContext
347
- };
227
+ ### Content Sharing
348
228
 
349
- // compress the JSON (match client's compress method)
350
- const json = JSON.stringify(command);
351
- const payload = await compressDeflate(json); // project helper
229
+ #### Send Hashtag
352
230
 
353
- // publish to MQTT send_message topic
354
- await mqtt.publish({
355
- topic: Topics.SEND_MESSAGE.id,
356
- qosLevel: 1,
357
- payload: payload
231
+ ```javascript
232
+ await realtime.directCommands.sendHashtag({
233
+ threadId: threadId,
234
+ hashtag: 'photography',
235
+ text: 'Check this out'
358
236
  });
359
237
  ```
360
238
 
361
- **Notes**
362
- - This sends only a *reference* to the server. The bytes were already sent via rupload.
363
- - The APK calls something equivalent to `RuploadClient.upload()` then `SendMessage.sendMedia(upload_id)` — the second call is an MQTT notification that references `upload_id`.
364
-
365
- ---
366
-
367
- ### Example: Hybrid flow — Video (rupload HTTP then MQTT reference)
239
+ #### Send Like (Heart)
368
240
 
369
241
  ```javascript
370
- // 1) RUUPLOAD (video)
371
- const uploadId = Date.now().toString();
372
- const objectName = `${uploadId}_${Math.floor(Math.random()*1e9)}.mp4`;
373
-
374
- const ruploadParams = {
375
- upload_id: uploadId,
376
- media_type: 2, // video
377
- xsharing_user_ids: JSON.stringify([]),
378
- upload_media_duration_ms: Math.round(duration * 1000),
379
- upload_media_width: width,
380
- upload_media_height: height
381
- };
382
-
383
- const headers = {
384
- 'X-Instagram-Rupload-Params': JSON.stringify(ruploadParams),
385
- 'Content-Type': 'video/mp4',
386
- 'X-Entity-Type': 'video/mp4',
387
- 'X-Entity-Length': String(videoBuffer.length),
388
- 'Content-Length': String(videoBuffer.length),
389
- 'Offset': '0'
390
- };
391
-
392
- await ig.request.send({
393
- url: `/rupload_igvideo/${objectName}`,
394
- method: 'POST',
395
- headers,
396
- body: videoBuffer,
397
- timeout: 300000
242
+ await realtime.directCommands.sendLike({
243
+ threadId: threadId
398
244
  });
245
+ ```
399
246
 
400
- // 2) MQTT publish - notify realtime to attach upload_id to thread
401
- const command = {
402
- action: 'send_item',
403
- thread_id: String(threadId),
404
- item_type: 'media',
405
- upload_id: String(uploadId),
406
- text: caption || '',
407
- timestamp: Date.now(),
408
- client_context: require('uuid').v4()
409
- };
247
+ #### Send Location
410
248
 
411
- const payload = await compressDeflate(JSON.stringify(command));
412
- await mqtt.publish({
413
- topic: Topics.SEND_MESSAGE.id,
414
- qosLevel: 1,
415
- payload: payload
249
+ ```javascript
250
+ await realtime.directCommands.sendLocation({
251
+ threadId: threadId,
252
+ locationId: '123456789',
253
+ text: 'Meet me here'
416
254
  });
417
255
  ```
418
256
 
419
- ---
420
-
421
- ### Short FAQ & Troubleshooting
422
-
423
- - **Q:** *Can I send the raw image through MQTT?*
424
- **A:** No — MQTT is not used for large raw bytes. The app uploads bytes via rupload HTTP.
425
-
426
- - **Q:** *If I publish the MQTT `send_item` with `upload_id`, will the message appear in the thread?*
427
- **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.
428
-
429
- - **Q:** *Do I need to call `/direct_v2/threads/broadcast/...` if I published MQTT?*
430
- **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.
431
-
432
- ---
433
-
434
- ### Security & Best Practices
435
-
436
- - Ensure `upload_id` you publish via MQTT is the one returned by rupload; otherwise servers will reject or ignore it.
437
- - Keep rupload headers accurate (`X-Entity-Length`, `X-Instagram-Rupload-Params`, etc.).
438
- - Use `client_context` (UUID) per message to track mutations.
439
- - Implement retries and exponential backoff for both rupload and MQTT publish.
440
- - Log both the rupload response and the MQTT publish result for debugging.
441
-
442
- ---
443
-
444
- 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.
445
-
446
- ### Why this is a game changer:
447
- - **Zero Configuration**: No need to pass IRIS data or subscription topics manually on every boot.
448
- - **Background Startup**: The MQTT connection starts the moment you create the `RealtimeClient`.
449
- - **Session Persistence**: Cookies, device info, and MQTT state are managed automatically.
450
-
451
- ### 🔌 How to use Instant Boot
452
-
453
- Once you've linked your account (see the setup section below), your main script can be as simple as this:
257
+ #### Send Media (Share Post)
454
258
 
455
259
  ```javascript
456
- const { IgApiClient, RealtimeClient } = require('nodejs-insta-private-api-mqtt');
457
-
458
- // 1. Initialize the client
459
- const ig = new IgApiClient();
460
-
461
- // 2. That's it!
462
- // If ./auth_info_ig exists, MQTT starts background connection immediately.
463
- const realtime = new RealtimeClient(ig);
464
-
465
- // 3. Listen for events
466
- realtime.on('connected', () => {
467
- console.log('✅ MQTT is live and kicking!');
260
+ await realtime.directCommands.sendMedia({
261
+ threadId: threadId,
262
+ mediaId: 'media_id_here',
263
+ text: 'Check this post'
468
264
  });
265
+ ```
469
266
 
470
- realtime.on('message_live', (msg) => {
471
- console.log(`📩 New message from ${msg.username}: ${msg.text}`);
472
- });
267
+ #### Send Profile
473
268
 
474
- realtime.on('error', (err) => {
475
- console.error('❌ MQTT Connection issue:', err.message);
269
+ ```javascript
270
+ await realtime.directCommands.sendProfile({
271
+ threadId: threadId,
272
+ userId: '12345678',
273
+ text: 'Follow this account'
476
274
  });
477
275
  ```
478
276
 
479
- ### 🔐 Initial Setup (Pairing)
480
-
481
- The first time you run your bot, you need to perform a "pairing" login to generate the session files.
277
+ #### Send User Story
482
278
 
483
279
  ```javascript
484
- const { IgApiClient, RealtimeClient, useMultiFileAuthState } = require('nodejs-insta-private-api-mqtt');
485
-
486
- async function pair() {
487
- const ig = new IgApiClient();
488
- const folder = './auth_info_ig'; // Default folder for instant boot
489
- const auth = await useMultiFileAuthState(folder);
490
- const realtime = new RealtimeClient(ig);
491
-
492
- if (!auth.hasSession()) {
493
- console.log('Linking account...');
494
- await ig.state.generateDevice('your_username');
495
- await ig.login({ username: 'your_username', password: 'your_password' });
496
-
497
- // Save credentials
498
- await auth.saveCreds(ig);
499
-
500
- // Fetch initial sync data and connect MQTT
501
- await realtime.startRealTimeListener();
502
-
503
- // Save the MQTT session for instant boot next time
504
- await auth.saveMqttSession(realtime);
505
-
506
- console.log('✅ Pairing complete! Restart your script to see Instant Boot in action.');
507
- }
508
- }
509
-
510
- pair().catch(console.error);
280
+ await realtime.directCommands.sendUserStory({
281
+ threadId: threadId,
282
+ storyId: 'story_id_here',
283
+ text: 'Did you see this?'
284
+ });
511
285
  ```
512
286
 
287
+ #### Send Link
513
288
 
514
- ### 🔄 Comparison: Old vs New MQTT Logic
515
-
516
- | Feature | **BEFORE (Manual)** | **NOW (Instant)** |
517
- |:--- |:--- |:--- |
518
- | **Setup** | Manual login + inbox fetch | Just initialize the client |
519
- | **MQTT Startup** | Manual `realtime.connect()` | Starts automatically in background |
520
- | **IRIS Sub** | Must pass `irisData` manually | Auto-fetches and connects |
521
- | **Session** | You manage cookie serialization | Handled by `useMultiFileAuthState` |
522
-
523
- #### The Old Way (Painful 😫)
524
289
  ```javascript
525
- const ig = new IgApiClient();
526
- await ig.login({ username: '...', password: '...' });
527
- const inbox = await ig.direct.getInbox(); // Manual step
528
-
529
- const realtime = new RealtimeClient(ig);
530
- await realtime.connect({
531
- graphQlSubs: ['ig_sub_direct'],
532
- irisData: inbox // Manual injection
290
+ await realtime.directCommands.sendLink({
291
+ threadId: threadId,
292
+ link: 'https://example.com',
293
+ text: 'Check this link'
533
294
  });
534
295
  ```
535
296
 
536
- #### The New Way (Instant 🚀)
537
- ```javascript
538
- const ig = new IgApiClient();
539
- // If ./auth_info_ig exists, this line starts the background connection!
540
- const realtime = new RealtimeClient(ig);
297
+ #### Send Animated Media (GIF/Sticker)
541
298
 
542
- realtime.on('connected', () => console.log('✅ Online!'));
299
+ ```javascript
300
+ await realtime.directCommands.sendAnimatedMedia({
301
+ threadId: threadId,
302
+ id: 'giphy_id_here',
303
+ isSticker: false
304
+ });
543
305
  ```
544
306
 
307
+ #### Send Voice Message (after upload)
545
308
 
546
309
  ```javascript
547
- const authState = await useMultiFileAuthState(folderPath);
310
+ await realtime.directCommands.sendVoice({
311
+ threadId: threadId,
312
+ uploadId: 'your_upload_id',
313
+ waveform: [0.1, 0.5, 0.8, 0.3],
314
+ waveformSamplingFrequencyHz: 10
315
+ });
548
316
  ```
549
317
 
550
- #### Methods
318
+ ---
551
319
 
552
- | Method | Description |
553
- |--------|-------------|
554
- | `hasSession()` | Returns `true` if saved credentials exist |
555
- | `hasMqttSession()` | Returns `true` if saved MQTT session exists |
556
- | `loadCreds(ig)` | Loads saved credentials into IgApiClient |
557
- | `saveCreds(ig)` | Saves current credentials to files |
558
- | `isSessionValid(ig)` | Validates session with Instagram API |
559
- | `loadMqttSession()` | Returns saved MQTT session data |
560
- | `saveMqttSession(realtime)` | Saves MQTT session from RealtimeClient |
561
- | `clearSession()` | Deletes all saved session files |
320
+ ### Reactions
562
321
 
563
- ### Complete Example: Bot with Auto-Reconnect
322
+ #### Send Reaction
564
323
 
565
324
  ```javascript
566
- const { IgApiClient, RealtimeClient, useMultiFileAuthState } = require('nodejs-insta-private-api-mqtt');
567
-
568
- const AUTH_FOLDER = './auth_info_instagram';
569
- const USERNAME = process.env.IG_USERNAME;
570
- const PASSWORD = process.env.IG_PASSWORD;
571
-
572
- async function startBot() {
573
- console.log('Starting Instagram Bot...');
574
-
575
- const authState = await useMultiFileAuthState(AUTH_FOLDER);
576
- const ig = new IgApiClient();
577
- let realtime;
325
+ await realtime.directCommands.sendReaction({
326
+ threadId: threadId,
327
+ itemId: messageId,
328
+ reactionType: 'like'
329
+ });
330
+ ```
578
331
 
579
- if (authState.hasSession()) {
580
- console.log('Found saved session, attempting to restore...');
581
-
582
- const loaded = await authState.loadCreds(ig);
583
- if (loaded) {
584
- const valid = await authState.isSessionValid(ig);
585
-
586
- if (valid) {
587
- console.log('Session is valid! Connecting to MQTT...');
588
- realtime = new RealtimeClient(ig);
589
-
590
- realtime.on('connected', () => console.log('MQTT Connected!'));
591
- realtime.on('error', (err) => console.error('MQTT Error:', err.message));
592
-
593
- await realtime.connectFromSavedSession(authState);
594
-
595
- setupMessageHandler(realtime);
596
- console.log('Bot is now listening for messages!');
597
- return;
598
- }
599
- }
600
-
601
- console.log('Session invalid or expired, clearing...');
602
- await authState.clearSession();
603
- }
332
+ #### Send Emoji Reaction
604
333
 
605
- // Fresh login required
606
- console.log('Performing fresh login...');
607
-
608
- await ig.login({ username: USERNAME, password: PASSWORD });
609
- console.log('Login successful!');
610
-
611
- await authState.saveCreds(ig);
612
-
613
- realtime = new RealtimeClient(ig);
614
- const inbox = await ig.direct.getInbox();
615
-
616
- await realtime.connect({
617
- graphQlSubs: ['ig_sub_direct', 'ig_sub_direct_v2_message_sync'],
618
- skywalkerSubs: ['presence_subscribe', 'typing_subscribe'],
619
- irisData: inbox
620
- });
621
-
622
- await authState.saveMqttSession(realtime);
623
-
624
- setupMessageHandler(realtime);
625
- console.log('Bot is now listening for messages!');
626
- }
334
+ ```javascript
335
+ await realtime.directCommands.sendReaction({
336
+ threadId: threadId,
337
+ itemId: messageId,
338
+ reactionType: 'emoji',
339
+ emoji: '🔥'
340
+ });
341
+ ```
627
342
 
628
- function setupMessageHandler(realtime) {
629
- realtime.on('message', async (data) => {
630
- const msg = data.message;
631
- if (!msg?.text) return;
632
-
633
- console.log(`New message from ${msg.from_user_id}: ${msg.text}`);
634
-
635
- // Auto-reply example
636
- if (msg.text.toLowerCase().includes('hello')) {
637
- await realtime.directCommands.sendTextViaRealtime(
638
- msg.thread_id,
639
- 'Hi there! Thanks for your message!'
640
- );
641
- }
642
- });
643
- }
343
+ #### Remove Reaction
644
344
 
645
- startBot().catch(console.error);
345
+ ```javascript
346
+ await realtime.directCommands.removeReaction({
347
+ threadId: threadId,
348
+ itemId: messageId
349
+ });
646
350
  ```
647
351
 
648
352
  ---
649
353
 
650
- ## NEW: iOS Device Emulation (v5.61.11)
651
-
652
- 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.
653
-
654
- ### Why Use iOS Emulation?
354
+ ### Read Receipts & Activity
655
355
 
656
- - **Avoid detection** - iOS devices have different fingerprints than Android
657
- - **More realistic** - Many real users use iPhones
658
- - **Testing** - Test how Instagram behaves with iOS vs Android clients
659
- - **Reduce bans** - Vary your device types to appear more natural
660
-
661
- ### Quick Start: Use an iPhone
356
+ #### Mark Message as Seen
662
357
 
663
358
  ```javascript
664
- const { IgApiClient } = require('nodejs-insta-private-api-mqtt');
665
-
666
- const ig = new IgApiClient();
667
-
668
- // Switch to iOS platform with iPhone 16 Pro Max
669
- ig.state.useIOSDevice('iPhone 16 Pro Max');
670
-
671
- console.log('Platform:', ig.state.platform); // 'ios'
672
- console.log('User-Agent:', ig.state.appUserAgent);
673
- // Output: Instagram 347.0.0.36.89 (iPhone17,1; iOS 18.1; en_US; en_US; scale=3.00; 1320x2868; 618023787) AppleWebKit/420+
359
+ await realtime.directCommands.markAsSeen({
360
+ threadId: threadId,
361
+ itemId: messageId
362
+ });
674
363
  ```
675
364
 
676
- ### Available iOS Devices (21 devices)
677
-
678
- | Device Name | Model ID | iOS Version | Resolution |
679
- |-------------|----------|-------------|------------|
680
- | **iPhone 16 Pro Max** | iPhone17,1 | iOS 18.1 | 1320x2868 |
681
- | **iPhone 16 Pro** | iPhone17,2 | iOS 18.1 | 1206x2622 |
682
- | **iPhone 16 Plus** | iPhone17,3 | iOS 18.1 | 1290x2796 |
683
- | **iPhone 16** | iPhone17,4 | iOS 18.1 | 1179x2556 |
684
- | **iPhone 15 Pro Max** | iPhone16,2 | iOS 18.1 | 1290x2796 |
685
- | **iPhone 15 Pro** | iPhone16,1 | iOS 18.1 | 1179x2556 |
686
- | **iPhone 15 Plus** | iPhone15,5 | iOS 18.1 | 1290x2796 |
687
- | **iPhone 15** | iPhone15,4 | iOS 18.1 | 1179x2556 |
688
- | **iPhone 14 Pro Max** | iPhone15,3 | iOS 18.1 | 1290x2796 |
689
- | **iPhone 14 Pro** | iPhone15,2 | iOS 18.1 | 1179x2556 |
690
- | **iPhone 14 Plus** | iPhone14,8 | iOS 18.1 | 1284x2778 |
691
- | **iPhone 14** | iPhone14,7 | iOS 18.1 | 1170x2532 |
692
- | **iPhone 13 Pro Max** | iPhone14,3 | iOS 17.6 | 1284x2778 |
693
- | **iPhone 13 Pro** | iPhone14,2 | iOS 17.6 | 1170x2532 |
694
- | **iPhone 13** | iPhone14,5 | iOS 17.6 | 1170x2532 |
695
- | **iPhone 12 Pro Max** | iPhone13,4 | iOS 17.6 | 1284x2778 |
696
- | **iPhone 12 Pro** | iPhone13,3 | iOS 17.6 | 1170x2532 |
697
- | **iPhone 12** | iPhone13,2 | iOS 17.6 | 1170x2532 |
698
- | **iPad Pro 12.9 (6th gen)** | iPad14,3 | iOS 18.1 | 2048x2732 |
699
- | **iPad Pro 11 (4th gen)** | iPad14,5 | iOS 18.1 | 1668x2388 |
700
- | **iPad Air (5th gen)** | iPad13,18 | iOS 18.1 | 2360x1640 |
701
-
702
- ### List All Available Devices
365
+ #### Indicate Typing
703
366
 
704
367
  ```javascript
705
- const ig = new IgApiClient();
368
+ // Start typing
369
+ await realtime.directCommands.indicateActivity({
370
+ threadId: threadId,
371
+ isActive: true
372
+ });
706
373
 
707
- // Get all devices grouped by platform
708
- const devices = ig.state.listAllDevices();
374
+ // Stop typing
375
+ await realtime.directCommands.indicateActivity({
376
+ threadId: threadId,
377
+ isActive: false
378
+ });
379
+ ```
709
380
 
710
- console.log('iOS Devices:', devices.ios);
711
- // ['iPhone 16 Pro Max', 'iPhone 16 Pro', 'iPhone 16 Plus', ...]
381
+ #### Mark Visual Message as Seen (disappearing media)
712
382
 
713
- console.log('Android Devices:', devices.android);
714
- // ['Samsung Galaxy S25 Ultra', 'Google Pixel 9 Pro', ...]
383
+ ```javascript
384
+ await realtime.directCommands.markVisualMessageSeen({
385
+ threadId: threadId,
386
+ itemId: messageId
387
+ });
715
388
  ```
716
389
 
717
- ### Switch Between iOS and Android
390
+ ---
718
391
 
719
- ```javascript
720
- const ig = new IgApiClient();
392
+ ### Thread Management
721
393
 
722
- // Start with Android
723
- ig.state.useAndroidDevice('Samsung Galaxy S25 Ultra');
724
- console.log('Platform:', ig.state.platform); // 'android'
394
+ #### Add Member to Thread
725
395
 
726
- // Switch to iOS
727
- ig.state.switchPlatform('ios', 'iPhone 16 Pro Max');
728
- console.log('Platform:', ig.state.platform); // 'ios'
396
+ ```javascript
397
+ await realtime.directCommands.addMemberToThread(threadId, userId);
729
398
 
730
- // Switch back to Android with different device
731
- ig.state.switchPlatform('android', 'Google Pixel 9 Pro');
732
- console.log('Platform:', ig.state.platform); // 'android'
399
+ // Add multiple members
400
+ await realtime.directCommands.addMemberToThread(threadId, [userId1, userId2]);
733
401
  ```
734
402
 
735
- ### Set Custom iOS Device
403
+ #### Remove Member from Thread
736
404
 
737
405
  ```javascript
738
- const ig = new IgApiClient();
739
-
740
- // Set a fully custom iOS device
741
- ig.state.setIOSDevice({
742
- iosDeviceModel: 'iPhone17,1',
743
- iosDeviceName: 'iPhone 16 Pro Max',
744
- iosVersion: '18.1',
745
- iosAppVersion: '347.0.0.36.89',
746
- iosAppVersionCode: '618023787'
747
- });
748
-
749
- console.log('User-Agent:', ig.state.appUserAgent);
406
+ await realtime.directCommands.removeMemberFromThread(threadId, userId);
750
407
  ```
751
408
 
752
- ### Get Current Platform Info
409
+ #### Leave Thread
753
410
 
754
411
  ```javascript
755
- const ig = new IgApiClient();
756
- ig.state.useIOSDevice('iPhone 15 Pro');
757
-
758
- const info = ig.state.getPlatformInfo();
759
-
760
- console.log(info);
761
- // {
762
- // platform: 'ios',
763
- // userAgent: 'Instagram 347.0.0.36.89 (iPhone16,1; iOS 18.1; ...) AppleWebKit/420+',
764
- // packageName: 'com.burbn.instagram',
765
- // deviceId: 'ios-A1B2C3D4E5F6...',
766
- // iosDeviceModel: 'iPhone16,1',
767
- // iosDeviceName: 'iPhone 15 Pro',
768
- // iosVersion: '18.1',
769
- // iosAppVersion: '347.0.0.36.89'
770
- // }
412
+ await realtime.directCommands.leaveThread(threadId);
771
413
  ```
772
414
 
773
- ### Complete Example: iOS Bot with useMultiFileAuthState
415
+ #### Update Thread Title
774
416
 
775
417
  ```javascript
776
- const { IgApiClient, RealtimeClient, useMultiFileAuthState } = require('nodejs-insta-private-api-mqtt');
418
+ await realtime.directCommands.updateThreadTitle(threadId, 'New Group Name');
419
+ ```
777
420
 
778
- async function startIOSBot() {
779
- const authState = await useMultiFileAuthState('./auth_info_instagram');
780
- const ig = new IgApiClient();
421
+ #### Mute Thread
781
422
 
782
- // Use iPhone 16 Pro Max for this bot
783
- ig.state.useIOSDevice('iPhone 16 Pro Max');
784
-
785
- console.log('Device:', ig.state.iosDeviceName);
786
- console.log('User-Agent:', ig.state.appUserAgent);
423
+ ```javascript
424
+ await realtime.directCommands.muteThread(threadId);
787
425
 
788
- if (authState.hasSession()) {
789
- console.log('Loading saved session...');
790
- await authState.loadCreds(ig);
791
-
792
- const valid = await authState.isSessionValid(ig);
793
- if (!valid) {
794
- console.log('Session expired, need fresh login');
795
- await authState.clearSession();
796
- }
797
- }
426
+ // Mute until specific time
427
+ await realtime.directCommands.muteThread(threadId, Date.now() + 3600000);
428
+ ```
798
429
 
799
- if (!authState.hasSession()) {
800
- console.log('Logging in...');
801
- await ig.login({
802
- username: process.env.IG_USERNAME,
803
- password: process.env.IG_PASSWORD
804
- });
805
- await authState.saveCreds(ig);
806
- }
430
+ #### Unmute Thread
807
431
 
808
- // Connect to real-time messaging
809
- const realtime = new RealtimeClient(ig);
810
- const inbox = await ig.direct.getInbox();
432
+ ```javascript
433
+ await realtime.directCommands.unmuteThread(threadId);
434
+ ```
811
435
 
812
- await realtime.connect({
813
- graphQlSubs: ['ig_sub_direct', 'ig_sub_direct_v2_message_sync'],
814
- skywalkerSubs: ['presence_subscribe', 'typing_subscribe'],
815
- irisData: inbox
816
- });
436
+ ---
817
437
 
818
- await authState.saveMqttSession(realtime);
819
-
820
- console.log('iOS Bot is online with', ig.state.iosDeviceName);
438
+ ### Message Requests
821
439
 
822
- realtime.on('message', (data) => {
823
- console.log('New DM:', data.message?.text);
824
- });
825
- }
440
+ #### Approve Pending Thread
826
441
 
827
- startIOSBot().catch(console.error);
442
+ ```javascript
443
+ await realtime.directCommands.approveThread(threadId);
828
444
  ```
829
445
 
830
- ### API Reference: iOS Methods
446
+ #### Decline Pending Thread
831
447
 
832
- | Method | Description |
833
- |--------|-------------|
834
- | `state.useIOSDevice(name)` | Set iOS device from preset (e.g., 'iPhone 16 Pro Max') |
835
- | `state.setIOSDevice(config)` | Set fully custom iOS device configuration |
836
- | `state.useAndroidDevice(name)` | Set Android device from preset |
837
- | `state.switchPlatform(platform, device)` | Switch between 'ios' and 'android' |
838
- | `state.getPlatformInfo()` | Get current platform and device details |
839
- | `state.listAllDevices()` | Get all devices grouped by platform |
840
- | `state.getIOSDevices()` | Get only iOS device presets |
841
- | `state.getAndroidDevices()` | Get only Android device presets |
842
- | `state.platform` | Current platform ('ios' or 'android') |
843
- | `state.iosUserAgent` | iOS-specific User-Agent string |
844
- | `state.packageName` | Package name ('com.burbn.instagram' for iOS) |
448
+ ```javascript
449
+ await realtime.directCommands.declineThread(threadId);
450
+ ```
845
451
 
846
452
  ---
847
453
 
848
- ## All 18 MQTT Methods - Complete Reference
454
+ ### Moderation
849
455
 
850
- ### Messaging Methods (Send, Edit, Delete, Reply)
851
-
852
- #### 1. Send Text Message
456
+ #### Block User in Thread
853
457
 
854
458
  ```javascript
855
- await realtime.directCommands.sendTextViaRealtime(threadId, 'Your message here');
459
+ await realtime.directCommands.blockUserInThread(threadId, userId);
856
460
  ```
857
461
 
858
- #### 2. Delete Message
462
+ #### Report Thread
859
463
 
860
464
  ```javascript
861
- await realtime.directCommands.deleteMessage(threadId, messageId);
465
+ await realtime.directCommands.reportThread(threadId, 'spam');
862
466
  ```
863
467
 
864
- #### 3. Edit Message
468
+ ---
865
469
 
866
- ```javascript
867
- await realtime.directCommands.editMessage(threadId, messageId, 'Updated text');
868
- ```
470
+ ### Disappearing Media (View-Once)
869
471
 
870
- #### 4. Reply to Message (Quote Reply)
472
+ #### Send Disappearing Photo
871
473
 
872
474
  ```javascript
873
- await realtime.directCommands.replyToMessage(threadId, messageId, 'My reply');
475
+ await realtime.directCommands.sendDisappearingPhoto({
476
+ threadId: threadId,
477
+ uploadId: 'your_upload_id',
478
+ viewMode: 'once' // 'once' or 'replayable'
479
+ });
874
480
  ```
875
481
 
876
- #### 5. Send Reaction (Emoji)
482
+ #### Send Disappearing Video
877
483
 
878
484
  ```javascript
879
- await realtime.directCommands.sendReaction({
485
+ await realtime.directCommands.sendDisappearingVideo({
880
486
  threadId: threadId,
881
- itemId: messageId,
882
- emoji: '❤️',
883
- reactionType: 'like',
884
- reactionStatus: 'created'
487
+ uploadId: 'your_upload_id',
488
+ viewMode: 'once'
885
489
  });
490
+ ```
886
491
 
887
- // Remove reaction
888
- await realtime.directCommands.sendReaction({
492
+ ---
493
+
494
+ ### Notifications
495
+
496
+ #### Send Screenshot Notification
497
+
498
+ ```javascript
499
+ await realtime.directCommands.sendScreenshotNotification({
889
500
  threadId: threadId,
890
- itemId: messageId,
891
- emoji: '❤️',
892
- reactionStatus: 'deleted'
501
+ itemId: messageId
893
502
  });
894
503
  ```
895
504
 
896
- ### Content Sending Methods (Media, Location, Profile, etc)
897
-
898
- #### 6. Send Media (Share existing Instagram post)
505
+ #### Send Replay Notification
899
506
 
900
507
  ```javascript
901
- await realtime.directCommands.sendMedia({
508
+ await realtime.directCommands.sendReplayNotification({
902
509
  threadId: threadId,
903
- mediaId: '12345678',
904
- text: 'Optional caption'
510
+ itemId: messageId
905
511
  });
906
512
  ```
907
513
 
908
- #### 6.1 Send Photo (NEW in v5.60.3 - Upload & Send)
514
+ ---
515
+
516
+ ### Media Upload (HTTP + Broadcast)
909
517
 
910
- Upload and send a photo directly from a Buffer. This method handles the complete upload process automatically.
518
+ These methods handle the full flow: upload via HTTP rupload, then broadcast to thread.
519
+
520
+ #### Send Photo
911
521
 
912
522
  ```javascript
913
523
  const fs = require('fs');
914
-
915
- // Read photo from file
916
524
  const photoBuffer = fs.readFileSync('./photo.jpg');
917
525
 
918
- // Send photo via realtime
919
526
  await realtime.directCommands.sendPhoto({
920
- photoBuffer: photoBuffer,
921
527
  threadId: threadId,
922
- caption: 'Check this out!', // Optional
923
- mimeType: 'image/jpeg' // Optional: 'image/jpeg' or 'image/png'
528
+ photoBuffer: photoBuffer,
529
+ caption: 'Check this out',
530
+ mimeType: 'image/jpeg'
924
531
  });
925
532
  ```
926
533
 
927
- **Download from URL and send:**
534
+ #### Send Video
928
535
 
929
536
  ```javascript
930
- const axios = require('axios');
931
-
932
- const response = await axios.get('https://example.com/image.jpg', {
933
- responseType: 'arraybuffer'
934
- });
935
- const photoBuffer = Buffer.from(response.data);
936
-
937
- await realtime.directCommands.sendPhoto({
938
- photoBuffer: photoBuffer,
939
- threadId: threadId
940
- });
941
- ```
942
-
943
- #### 6.2 Send Video (NEW in v5.60.3 - Upload & Send)
944
-
945
- Upload and send a video directly from a Buffer.
946
-
947
- ```javascript
948
- const fs = require('fs');
949
-
950
- const videoBuffer = fs.readFileSync('./video.mp4');
537
+ const fs = require('fs');
538
+ const videoBuffer = fs.readFileSync('./video.mp4');
951
539
 
952
540
  await realtime.directCommands.sendVideo({
953
- videoBuffer: videoBuffer,
954
- threadId: threadId,
955
- caption: 'Watch this!', // Optional
956
- duration: 15, // Optional: duration in seconds
957
- width: 720, // Optional
958
- height: 1280 // Optional
959
- });
960
- ```
961
-
962
- #### 7. Send Location
963
-
964
- ```javascript
965
- await realtime.directCommands.sendLocation({
966
- threadId: threadId,
967
- locationId: '213999449',
968
- text: 'Optional description'
969
- });
970
- ```
971
-
972
- #### 8. Send Profile
973
-
974
- ```javascript
975
- await realtime.directCommands.sendProfile({
976
- threadId: threadId,
977
- userId: '987654321',
978
- text: 'Optional text'
979
- });
980
- ```
981
-
982
- #### 9. Send Hashtag
983
-
984
- ```javascript
985
- await realtime.directCommands.sendHashtag({
986
- threadId: threadId,
987
- hashtag: 'instagram',
988
- text: 'Optional text'
989
- });
990
- ```
991
-
992
- #### 10. Send Like
993
-
994
- ```javascript
995
- await realtime.directCommands.sendLike({ threadId: threadId });
996
- ```
997
-
998
- #### 11. Send User Story
999
-
1000
- ```javascript
1001
- await realtime.directCommands.sendUserStory({
1002
- threadId: threadId,
1003
- storyId: 'story_12345',
1004
- text: 'Optional text'
1005
- });
1006
- ```
1007
-
1008
- ### Status Methods (Typing, Read Receipts)
1009
-
1010
- #### 12. Mark Message as Seen
1011
-
1012
- ```javascript
1013
- await realtime.directCommands.markAsSeen({
1014
- threadId: threadId,
1015
- itemId: messageId
1016
- });
1017
- ```
1018
-
1019
- #### 13. Indicate Activity (Typing Indicator)
1020
-
1021
- ```javascript
1022
- // Show typing indicator
1023
- await realtime.directCommands.indicateActivity({
1024
541
  threadId: threadId,
1025
- isActive: true
1026
- });
1027
-
1028
- // Stop typing
1029
- await realtime.directCommands.indicateActivity({
1030
- threadId: threadId,
1031
- isActive: false
1032
- });
1033
- ```
1034
-
1035
- ### Notification Subscription Methods
1036
-
1037
- #### 14. Subscribe to Follow Notifications
1038
-
1039
- ```javascript
1040
- await realtime.directCommands.subscribeToFollowNotifications();
1041
-
1042
- realtime.on('follow', (data) => {
1043
- console.log('New follower:', data.user_id);
542
+ videoBuffer: videoBuffer,
543
+ caption: 'Watch this',
544
+ duration: 15,
545
+ width: 720,
546
+ height: 1280
1044
547
  });
1045
548
  ```
1046
549
 
1047
- #### 15. Subscribe to Mention Notifications
1048
-
1049
- ```javascript
1050
- await realtime.directCommands.subscribeToMentionNotifications();
1051
-
1052
- realtime.on('mention', (data) => {
1053
- console.log('You were mentioned in:', data.content_type);
1054
- });
1055
- ```
550
+ ---
1056
551
 
1057
- #### 16. Subscribe to Call Notifications
552
+ ### Foreground State (Connection Keepalive)
1058
553
 
1059
554
  ```javascript
1060
- await realtime.directCommands.subscribeToCallNotifications();
1061
-
1062
- realtime.on('call', (data) => {
1063
- console.log('Incoming call from:', data.caller_id);
555
+ await realtime.directCommands.sendForegroundState({
556
+ inForegroundApp: true,
557
+ inForegroundDevice: true,
558
+ keepAliveTimeout: 60
1064
559
  });
1065
560
  ```
1066
561
 
1067
- ### Group Management Methods
1068
-
1069
- #### 17. Add Member to Thread
1070
-
1071
- ```javascript
1072
- await realtime.directCommands.addMemberToThread(threadId, userId);
1073
- ```
1074
-
1075
- #### 18. Remove Member from Thread
1076
-
1077
- ```javascript
1078
- await realtime.directCommands.removeMemberFromThread(threadId, userId);
1079
- ```
1080
-
1081
562
  ---
1082
563
 
1083
- ## NEW: Download Media from Messages (v5.60.8)
564
+ ## Complete Method Reference Table
1084
565
 
1085
- 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**.
566
+ | Method | Description |
567
+ |--------|-------------|
568
+ | `sendText({ threadId, text })` | Send text message |
569
+ | `sendTextViaRealtime(threadId, text)` | Send text (alternative) |
570
+ | `sendHashtag({ threadId, hashtag, text })` | Send hashtag |
571
+ | `sendLike({ threadId })` | Send heart/like |
572
+ | `sendLocation({ threadId, locationId, text })` | Send location |
573
+ | `sendMedia({ threadId, mediaId, text })` | Share a post |
574
+ | `sendProfile({ threadId, userId, text })` | Share a profile |
575
+ | `sendUserStory({ threadId, storyId, text })` | Share a story |
576
+ | `sendLink({ threadId, link, text })` | Send a link |
577
+ | `sendAnimatedMedia({ threadId, id, isSticker })` | Send GIF/sticker |
578
+ | `sendVoice({ threadId, uploadId, waveform })` | Send voice message |
579
+ | `sendReaction({ threadId, itemId, emoji })` | React to message |
580
+ | `removeReaction({ threadId, itemId })` | Remove reaction |
581
+ | `replyToMessage(threadId, messageId, text)` | Quote reply |
582
+ | `editMessage(threadId, itemId, newText)` | Edit message |
583
+ | `deleteMessage(threadId, itemId)` | Delete message |
584
+ | `markAsSeen({ threadId, itemId })` | Mark as read |
585
+ | `indicateActivity({ threadId, isActive })` | Typing indicator |
586
+ | `markVisualMessageSeen({ threadId, itemId })` | Mark disappearing media seen |
587
+ | `addMemberToThread(threadId, userId)` | Add group member |
588
+ | `removeMemberFromThread(threadId, userId)` | Remove group member |
589
+ | `leaveThread(threadId)` | Leave group |
590
+ | `updateThreadTitle(threadId, title)` | Change group name |
591
+ | `muteThread(threadId, muteUntil)` | Mute thread |
592
+ | `unmuteThread(threadId)` | Unmute thread |
593
+ | `approveThread(threadId)` | Accept message request |
594
+ | `declineThread(threadId)` | Decline message request |
595
+ | `blockUserInThread(threadId, userId)` | Block user |
596
+ | `reportThread(threadId, reason)` | Report thread |
597
+ | `sendDisappearingPhoto({ threadId, uploadId })` | Send view-once photo |
598
+ | `sendDisappearingVideo({ threadId, uploadId })` | Send view-once video |
599
+ | `sendScreenshotNotification({ threadId, itemId })` | Screenshot alert |
600
+ | `sendReplayNotification({ threadId, itemId })` | Replay alert |
601
+ | `sendPhoto({ threadId, photoBuffer, caption })` | Upload & send photo |
602
+ | `sendVideo({ threadId, videoBuffer, caption })` | Upload & send video |
603
+ | `sendForegroundState(state)` | Connection keepalive |
1086
604
 
1087
- ### Why This Matters
605
+ ---
1088
606
 
1089
- Instagram's view-once media (photos/videos that disappear after viewing) can now be **saved before marking as seen**. This works because:
607
+ ## Download Media from Messages
1090
608
 
1091
- 1. Media is received via MQTT with CDN URLs
1092
- 2. The "view-once" flag is client-side only
1093
- 3. Media remains on Instagram's CDN until expiry (~24 hours)
609
+ This feature provides Baileys-style media download for Instagram DM messages.
1094
610
 
1095
611
  ### Quick Start: Save View-Once Photo
1096
612
 
1097
613
  ```javascript
1098
614
  const {
1099
- IgApiClient,
1100
- RealtimeClient,
1101
615
  downloadContentFromMessage,
1102
616
  isViewOnceMedia
1103
617
  } = require('nodejs-insta-private-api-mqtt');
1104
618
  const fs = require('fs');
1105
619
 
1106
- const ig = new IgApiClient();
1107
- // ... login code ...
1108
-
1109
- const realtime = new RealtimeClient(ig);
1110
- // ... connect code ...
1111
-
1112
620
  realtime.on('message', async (data) => {
1113
621
  const msg = data.message;
1114
622
 
1115
- // Check if it's a view-once message
1116
623
  if (isViewOnceMedia(msg)) {
1117
- console.log('View-once media received! Saving before it expires...');
624
+ const stream = await downloadContentFromMessage(msg);
1118
625
 
1119
- try {
1120
- // Download the media as a stream
1121
- const stream = await downloadContentFromMessage(msg);
1122
-
1123
- // Collect chunks into buffer
1124
- let buffer = Buffer.from([]);
1125
- for await (const chunk of stream) {
1126
- buffer = Buffer.concat([buffer, chunk]);
1127
- }
1128
-
1129
- // Determine file extension
1130
- const ext = stream.mediaInfo.type.includes('video') ? 'mp4' : 'jpg';
1131
- const filename = `viewonce_${Date.now()}.${ext}`;
1132
-
1133
- // Save to file
1134
- fs.writeFileSync(filename, buffer);
1135
-
1136
- console.log(`Saved: ${filename} (${buffer.length} bytes)`);
1137
- console.log('Media info:', stream.mediaInfo);
1138
-
1139
- } catch (err) {
1140
- console.error('Failed to download:', err.message);
626
+ let buffer = Buffer.from([]);
627
+ for await (const chunk of stream) {
628
+ buffer = Buffer.concat([buffer, chunk]);
1141
629
  }
1142
630
 
1143
- // DO NOT mark as seen if you want to keep the media secret!
1144
- // await realtime.directCommands.markAsSeen({ threadId: msg.thread_id, itemId: msg.item_id });
631
+ const ext = stream.mediaInfo.type.includes('video') ? 'mp4' : 'jpg';
632
+ fs.writeFileSync(`viewonce_${Date.now()}.${ext}`, buffer);
1145
633
  }
1146
634
  });
1147
635
  ```
1148
636
 
1149
- ### Download Regular Media (Photos, Videos, Voice)
637
+ ### Download Regular Media
1150
638
 
1151
639
  ```javascript
1152
- const {
1153
- downloadMediaBuffer,
1154
- hasMedia,
1155
- getMediaType,
1156
- MEDIA_TYPES
1157
- } = require('nodejs-insta-private-api-mqtt');
640
+ const { downloadMediaBuffer, hasMedia } = require('nodejs-insta-private-api-mqtt');
1158
641
 
1159
642
  realtime.on('message', async (data) => {
1160
643
  const msg = data.message;
1161
644
 
1162
- // Check if message contains any media
1163
645
  if (hasMedia(msg)) {
1164
- const mediaType = getMediaType(msg);
1165
- console.log('Received media type:', mediaType);
1166
-
1167
- // Download as buffer directly
1168
646
  const { buffer, mediaInfo } = await downloadMediaBuffer(msg);
1169
-
1170
- // Save based on type
1171
- let filename;
1172
- switch (mediaInfo.type) {
1173
- case 'image':
1174
- case 'raven_image':
1175
- filename = `photo_${Date.now()}.jpg`;
1176
- break;
1177
- case 'video':
1178
- case 'raven_video':
1179
- filename = `video_${Date.now()}.mp4`;
1180
- break;
1181
- case 'voice':
1182
- filename = `voice_${Date.now()}.mp4`;
1183
- break;
1184
- default:
1185
- filename = `media_${Date.now()}`;
1186
- }
1187
-
1188
- fs.writeFileSync(filename, buffer);
1189
- console.log(`Saved ${mediaType}: ${filename}`);
647
+ fs.writeFileSync(`media_${Date.now()}.jpg`, buffer);
1190
648
  }
1191
649
  });
1192
650
  ```
1193
651
 
1194
- ### Extract Media URLs Without Downloading
1195
-
1196
- ```javascript
1197
- const { extractMediaUrls } = require('nodejs-insta-private-api-mqtt');
1198
-
1199
- realtime.on('message', async (data) => {
1200
- const msg = data.message;
1201
-
1202
- const mediaInfo = extractMediaUrls(msg);
1203
-
1204
- if (mediaInfo) {
1205
- console.log('Media type:', mediaInfo.type);
1206
- console.log('Is view-once:', mediaInfo.isViewOnce);
1207
- console.log('Dimensions:', mediaInfo.width, 'x', mediaInfo.height);
1208
- console.log('Duration (if video):', mediaInfo.duration);
1209
-
1210
- // Get all quality options
1211
- console.log('Available URLs:');
1212
- mediaInfo.urls.forEach((url, i) => {
1213
- console.log(` ${i}: ${url.width}x${url.height} - ${url.url.substring(0, 50)}...`);
1214
- });
1215
-
1216
- // Best quality URL is always first
1217
- const bestUrl = mediaInfo.urls[0].url;
1218
- }
1219
- });
1220
- ```
1221
-
1222
- ### Complete Bot Example: Auto-Save All Media
1223
-
1224
- ```javascript
1225
- const {
1226
- IgApiClient,
1227
- RealtimeClient,
1228
- useMultiFileAuthState,
1229
- downloadMediaBuffer,
1230
- hasMedia,
1231
- isViewOnceMedia,
1232
- MEDIA_TYPES
1233
- } = require('nodejs-insta-private-api-mqtt');
1234
- const fs = require('fs');
1235
- const path = require('path');
1236
-
1237
- const SAVE_DIR = './saved_media';
1238
-
1239
- async function startMediaBot() {
1240
- // Create save directory
1241
- if (!fs.existsSync(SAVE_DIR)) {
1242
- fs.mkdirSync(SAVE_DIR, { recursive: true });
1243
- }
1244
-
1245
- const authState = await useMultiFileAuthState('./auth_info_instagram');
1246
- const ig = new IgApiClient();
1247
-
1248
- // Login or restore session
1249
- if (authState.hasSession()) {
1250
- await authState.loadCreds(ig);
1251
- } else {
1252
- await ig.login({
1253
- username: process.env.IG_USERNAME,
1254
- password: process.env.IG_PASSWORD
1255
- });
1256
- await authState.saveCreds(ig);
1257
- }
1258
-
1259
- const realtime = new RealtimeClient(ig);
1260
- const inbox = await ig.direct.getInbox();
1261
-
1262
- await realtime.connect({
1263
- graphQlSubs: ['ig_sub_direct', 'ig_sub_direct_v2_message_sync'],
1264
- skywalkerSubs: ['presence_subscribe', 'typing_subscribe'],
1265
- irisData: inbox
1266
- });
1267
-
1268
- console.log('Media Bot Active - Saving all received media\n');
1269
-
1270
- realtime.on('message', async (data) => {
1271
- const msg = data.message;
1272
-
1273
- if (!hasMedia(msg)) return;
1274
-
1275
- try {
1276
- const { buffer, mediaInfo } = await downloadMediaBuffer(msg);
1277
-
1278
- // Generate filename with metadata
1279
- const prefix = mediaInfo.isViewOnce ? 'VIEWONCE_' : '';
1280
- const ext = getExtension(mediaInfo.type);
1281
- const filename = `${prefix}${mediaInfo.type}_${Date.now()}.${ext}`;
1282
- const filepath = path.join(SAVE_DIR, filename);
1283
-
1284
- fs.writeFileSync(filepath, buffer);
1285
-
1286
- console.log(`[SAVED] ${filename}`);
1287
- console.log(` Type: ${mediaInfo.type}`);
1288
- console.log(` Size: ${buffer.length} bytes`);
1289
- console.log(` Dimensions: ${mediaInfo.width}x${mediaInfo.height}`);
1290
- if (mediaInfo.isViewOnce) {
1291
- console.log(' WARNING: View-once media - do not mark as seen!');
1292
- }
1293
- console.log('');
1294
-
1295
- } catch (err) {
1296
- console.error('Download failed:', err.message);
1297
- }
1298
- });
1299
-
1300
- await new Promise(() => {});
1301
- }
1302
-
1303
- function getExtension(type) {
1304
- switch (type) {
1305
- case MEDIA_TYPES.IMAGE:
1306
- case MEDIA_TYPES.RAVEN_IMAGE:
1307
- case MEDIA_TYPES.MEDIA_SHARE:
1308
- case MEDIA_TYPES.STORY_SHARE:
1309
- return 'jpg';
1310
- case MEDIA_TYPES.VIDEO:
1311
- case MEDIA_TYPES.RAVEN_VIDEO:
1312
- case MEDIA_TYPES.CLIP:
1313
- case MEDIA_TYPES.VOICE:
1314
- return 'mp4';
1315
- default:
1316
- return 'bin';
1317
- }
1318
- }
1319
-
1320
- startMediaBot().catch(console.error);
1321
- ```
1322
-
1323
- ### API Reference: Media Download Functions
652
+ ### Media Functions Reference
1324
653
 
1325
654
  | Function | Description |
1326
655
  |----------|-------------|
1327
- | `downloadContentFromMessage(message, type?, options?)` | Download media as a readable stream (like Baileys) |
1328
- | `downloadMediaBuffer(message, type?, options?)` | Download media directly as Buffer |
1329
- | `extractMediaUrls(message)` | Extract media URLs and metadata without downloading |
1330
- | `hasMedia(message)` | Check if message contains downloadable media |
1331
- | `getMediaType(message)` | Get media type without downloading |
1332
- | `isViewOnceMedia(message)` | Check if message is view-once (disappearing) |
1333
-
1334
- ### downloadContentFromMessage() Options
1335
-
1336
- | Option | Type | Default | Description |
1337
- |--------|------|---------|-------------|
1338
- | `quality` | number | 0 | Quality index (0 = highest quality) |
1339
- | `timeout` | number | 30000 | Request timeout in milliseconds |
1340
- | `headers` | object | {} | Additional HTTP headers |
1341
-
1342
- ### MEDIA_TYPES Constants
1343
-
1344
- ```javascript
1345
- const { MEDIA_TYPES } = require('nodejs-insta-private-api-mqtt');
1346
-
1347
- MEDIA_TYPES.IMAGE // Regular photo
1348
- MEDIA_TYPES.VIDEO // Regular video
1349
- MEDIA_TYPES.VOICE // Voice message
1350
- MEDIA_TYPES.RAVEN_IMAGE // View-once photo
1351
- MEDIA_TYPES.RAVEN_VIDEO // View-once video
1352
- MEDIA_TYPES.MEDIA_SHARE // Shared post
1353
- MEDIA_TYPES.REEL_SHARE // Shared reel
1354
- MEDIA_TYPES.STORY_SHARE // Shared story
1355
- MEDIA_TYPES.CLIP // Clip/Reel
1356
- ```
1357
-
1358
- ### Important Notes
1359
-
1360
- 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.
1361
-
1362
- 2. **Media expiry** - View-once media URLs expire after ~24 hours. Regular media URLs may last longer but are not permanent.
1363
-
1364
- 3. **Rate limiting** - Downloading many files quickly may trigger Instagram's rate limits. Add delays between downloads if processing many messages.
1365
-
1366
- 4. **Legal considerations** - This feature is for personal backup purposes. Respect privacy and applicable laws regarding saved media.
656
+ | `downloadContentFromMessage(message)` | Download as stream |
657
+ | `downloadMediaBuffer(message)` | Download as Buffer |
658
+ | `extractMediaUrls(message)` | Get CDN URLs |
659
+ | `hasMedia(message)` | Check if has media |
660
+ | `getMediaType(message)` | Get media type |
661
+ | `isViewOnceMedia(message)` | Check if disappearing |
1367
662
 
1368
663
  ---
1369
664
 
1370
665
  ## Building Instagram Bots
1371
666
 
1372
- ### Example 1: Auto-Reply Bot (Keyword Triggered)
667
+ ### Auto-Reply Bot Example
1373
668
 
1374
669
  ```javascript
1375
670
  const { IgApiClient, RealtimeClient } = require('nodejs-insta-private-api-mqtt');
@@ -1385,11 +680,10 @@ const fs = require('fs');
1385
680
 
1386
681
  await realtime.connect({
1387
682
  graphQlSubs: ['ig_sub_direct', 'ig_sub_direct_v2_message_sync'],
1388
- skywalkerSubs: ['presence_subscribe', 'typing_subscribe'],
1389
683
  irisData: inbox
1390
684
  });
1391
685
 
1392
- console.log('Auto-Reply Bot Active\n');
686
+ console.log('Bot Active\n');
1393
687
 
1394
688
  realtime.on('message', async (data) => {
1395
689
  const msg = data.message;
@@ -1397,23 +691,11 @@ const fs = require('fs');
1397
691
 
1398
692
  console.log(`[${msg.from_user_id}]: ${msg.text}`);
1399
693
 
1400
- let reply = null;
1401
-
1402
694
  if (msg.text.toLowerCase().includes('hello')) {
1403
- reply = 'Hey! Thanks for reaching out!';
1404
- } else if (msg.text.toLowerCase().includes('help')) {
1405
- reply = 'How can I assist you?';
1406
- } else if (msg.text.toLowerCase().includes('thanks')) {
1407
- reply = 'You are welcome!';
1408
- }
1409
-
1410
- if (reply) {
1411
- try {
1412
- await realtime.directCommands.sendTextViaRealtime(msg.thread_id, reply);
1413
- console.log(`Replied: ${reply}\n`);
1414
- } catch (err) {
1415
- console.error(`Failed to send reply: ${err.message}\n`);
1416
- }
695
+ await realtime.directCommands.sendText({
696
+ threadId: msg.thread_id,
697
+ text: 'Hey! Thanks for reaching out!'
698
+ });
1417
699
  }
1418
700
  });
1419
701
 
@@ -1421,878 +703,331 @@ const fs = require('fs');
1421
703
  })();
1422
704
  ```
1423
705
 
1424
- ### Example 2: Smart Bot with Reactions and Typing
706
+ ### Smart Bot with Reactions and Typing
1425
707
 
1426
708
  ```javascript
1427
- const { IgApiClient, RealtimeClient } = require('nodejs-insta-private-api-mqtt');
1428
- const fs = require('fs');
1429
-
1430
- (async () => {
1431
- const ig = new IgApiClient();
1432
- const session = JSON.parse(fs.readFileSync('session.json'));
1433
- await ig.state.deserialize(session);
709
+ realtime.on('message', async (data) => {
710
+ const msg = data.message;
711
+ if (!msg?.text) return;
1434
712
 
1435
- const realtime = new RealtimeClient(ig);
1436
- const inbox = await ig.direct.getInbox();
1437
-
1438
- await realtime.connect({
1439
- graphQlSubs: ['ig_sub_direct', 'ig_sub_direct_v2_message_sync'],
1440
- skywalkerSubs: ['presence_subscribe', 'typing_subscribe'],
1441
- irisData: inbox
713
+ // Mark as seen
714
+ await realtime.directCommands.markAsSeen({
715
+ threadId: msg.thread_id,
716
+ itemId: msg.item_id
1442
717
  });
1443
718
 
1444
- console.log('Smart Bot Started\n');
1445
-
1446
- realtime.on('message', async (data) => {
1447
- const msg = data.message;
1448
- if (!msg?.text) return;
719
+ // Show typing
720
+ await realtime.directCommands.indicateActivity({
721
+ threadId: msg.thread_id,
722
+ isActive: true
723
+ });
1449
724
 
1450
- // Mark as seen immediately
1451
- await realtime.directCommands.markAsSeen({
1452
- threadId: msg.thread_id,
1453
- itemId: msg.item_id
1454
- });
725
+ await new Promise(r => setTimeout(r, 2000));
1455
726
 
1456
- // Show typing indicator
1457
- await realtime.directCommands.indicateActivity({
727
+ if (msg.text.toLowerCase().includes('hi')) {
728
+ await realtime.directCommands.sendText({
1458
729
  threadId: msg.thread_id,
1459
- isActive: true
730
+ text: 'Hello there!'
1460
731
  });
1461
732
 
1462
- // Simulate processing time
1463
- await new Promise(r => setTimeout(r, 2000));
1464
-
1465
- // Send reply
1466
- if (msg.text.toLowerCase().includes('hi')) {
1467
- await realtime.directCommands.sendTextViaRealtime(
1468
- msg.thread_id,
1469
- 'Hello there! How can I help?'
1470
- );
1471
-
1472
- // React with emoji
1473
- await realtime.directCommands.sendReaction({
1474
- threadId: msg.thread_id,
1475
- itemId: msg.item_id,
1476
- emoji: '👋'
1477
- });
1478
- }
1479
-
1480
- // Stop typing
1481
- await realtime.directCommands.indicateActivity({
733
+ // React with emoji
734
+ await realtime.directCommands.sendReaction({
1482
735
  threadId: msg.thread_id,
1483
- isActive: false
736
+ itemId: msg.item_id,
737
+ emoji: '👋'
1484
738
  });
1485
- });
1486
-
1487
- await new Promise(() => {});
1488
- })();
1489
- ```
1490
-
1491
- ---
1492
-
1493
-
1494
- ---
1495
-
1496
- ### Example 3: CLI Photo Sender (upload HTTP + broadcast HTTP, with MQTT sync)
1497
-
1498
- This example is a ready-to-run CLI script that demonstrates the correct flow to **send a photo** to one or more threads:
1499
- 1. upload the photo to Instagram's rupload endpoint (HTTP),
1500
- 2. broadcast (configure) the photo to the target thread(s) using the Direct API (HTTP),
1501
- 3. use MQTT (RealtimeClient) only for realtime sync and confirmation.
1502
-
1503
- Save this as `examples/send-photo-cli.js`.
1504
-
1505
- ```javascript
1506
- #!/usr/bin/env node
1507
- // examples/send-photo-cli.js
1508
- // Node 18+, install nodejs-insta-private-api-mqtt in the project
1509
-
1510
- const fs = require('fs');
1511
- const path = require('path');
1512
- const readline = require('readline/promises');
1513
- const { stdin: input, stdout: output } = require('process');
1514
- const { v4: uuidv4 } = require('uuid');
1515
-
1516
- (async () => {
1517
- const rl = readline.createInterface({ input, output });
1518
- try {
1519
- console.log('\\n--- Instagram Photo Sender (HTTP + broadcast) ---\\n');
1520
- const username = (await rl.question('Enter your Instagram username: ')).trim();
1521
- const password = (await rl.question('Enter your Instagram password: ')).trim();
1522
- if (!username || !password) {
1523
- console.error('username & password required'); process.exit(1);
1524
- }
1525
-
1526
- const { IgApiClient, RealtimeClient, useMultiFileAuthState } = require('nodejs-insta-private-api-mqtt');
1527
- const AUTH_FOLDER = path.resolve(process.cwd(), './auth_info_instagram');
1528
-
1529
- const authState = await useMultiFileAuthState(AUTH_FOLDER);
1530
- const ig = new IgApiClient();
1531
- ig.state.generateDevice?.(username);
1532
-
1533
- // load saved credentials if available
1534
- if (authState.hasSession && authState.hasSession()) {
1535
- try { await authState.loadCreds?.(ig); console.log('[*] Loaded saved creds'); } catch(e){}
1536
- }
1537
-
1538
- // login if needed
1539
- if (!authState.hasSession || (typeof authState.hasSession === 'function' && !authState.hasSession())) {
1540
- await ig.login({ username, password });
1541
- await authState.saveCreds?.(ig);
1542
- console.log('[+] Logged in and saved creds');
1543
- }
1544
-
1545
- // start realtime client (optional, helps sync)
1546
- const realtime = new RealtimeClient(ig);
1547
- realtime.on?.('connected', () => console.log('✅ MQTT connected (realtime)'));
1548
-
1549
- // fetch inbox and list threads
1550
- const inbox = await ig.direct.getInbox();
1551
- const threads = inbox.threads || inbox.items || inbox.threads_response?.threads || [];
1552
- if (!threads || threads.length === 0) {
1553
- console.error('No threads found in inbox. Exiting.'); process.exit(1);
1554
- }
1555
-
1556
- console.log('\\nAvailable threads:');
1557
- const indexToThread = [];
1558
- threads.slice(0, 50).forEach((t, i) => {
1559
- const id = String(t.thread_id || t.threadId || t.id || t.thread_id_str || (t.thread && t.thread.thread_id) || '');
1560
- 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';
1561
- indexToThread.push({ id, title });
1562
- console.log(`${i+1}. ${title} (thread_id: ${id})`);
1563
- });
1564
-
1565
- const pick = (await rl.question('\\nSelect thread numbers (e.g. "1 2 3"): ')).trim();
1566
- const picks = pick.split(/[\s,]+/).map(x => parseInt(x,10)).filter(n => Number.isFinite(n) && n >= 1 && n <= indexToThread.length);
1567
- const selected = picks.map(p => indexToThread[p-1].id);
1568
-
1569
- const photoPath = (await rl.question('\\nEnter your Photo path here (absolute or relative): ')).trim();
1570
- const resolved = path.isAbsolute(photoPath) ? photoPath : path.resolve(process.cwd(), photoPath);
1571
- if (!fs.existsSync(resolved)) { console.error('File not found', resolved); process.exit(1); }
1572
- const buffer = fs.readFileSync(resolved);
1573
- console.log('Photo size (MB):', (buffer.length/(1024*1024)).toFixed(2));
1574
-
1575
- // RUUPLOAD (HTTP) - try to use ig.publish.photo helper if available
1576
- let uploadId;
1577
- try {
1578
- if (typeof ig.publish?.photo === 'function') {
1579
- const up = await ig.publish.photo({ file: buffer });
1580
- uploadId = up.upload_id || up.uploadId || up;
1581
- } else {
1582
- // manual rupload via request
1583
- uploadId = Date.now().toString();
1584
- const objectName = `${uploadId}_0_${Math.floor(Math.random() * 1e10)}`;
1585
- const ruploadParams = {
1586
- retry_context: JSON.stringify({ num_step_auto_retry: 0, num_reupload: 0, num_step_manual_retry: 0 }),
1587
- media_type: '1',
1588
- upload_id: uploadId,
1589
- xsharing_user_ids: JSON.stringify([]),
1590
- image_compression: JSON.stringify({ lib_name: 'moz', lib_version: '3.1.m', quality: '80' })
1591
- };
1592
- const headers = {
1593
- 'X-Instagram-Rupload-Params': JSON.stringify(ruploadParams),
1594
- 'Content-Type': 'application/octet-stream',
1595
- 'X-Entity-Type': 'image/jpeg',
1596
- 'Offset': '0',
1597
- 'X-Entity-Name': objectName,
1598
- 'X-Entity-Length': String(buffer.length),
1599
- 'Content-Length': String(buffer.length),
1600
- };
1601
- await ig.request.send({
1602
- url: `/rupload_igphoto/${objectName}`,
1603
- method: 'POST',
1604
- headers,
1605
- body: buffer,
1606
- timeout: 120000,
1607
- });
1608
- }
1609
- console.log('[+] Upload completed. upload_id =', uploadId);
1610
- } catch (err) {
1611
- console.error('Upload failed:', err?.message || err); process.exit(1);
1612
- }
1613
-
1614
- // BROADCAST (HTTP) - create message on server
1615
- try {
1616
- const mutationToken = uuidv4();
1617
- const payload = {
1618
- action: 'configure_photo',
1619
- upload_id: String(uploadId),
1620
- thread_ids: JSON.stringify(selected),
1621
- client_context: mutationToken,
1622
- mutation_token: mutationToken,
1623
- _csrftoken: ig.state.cookieCsrfToken || '',
1624
- _uuid: ig.state.uuid || '',
1625
- device_id: ig.state.deviceId || ''
1626
- };
1627
- const resp = await ig.request.send({
1628
- url: '/api/v1/direct_v2/threads/broadcast/configure_photo/',
1629
- method: 'POST',
1630
- form: payload,
1631
- timeout: 60000,
1632
- });
1633
- console.log('✅ Broadcast response received:', resp && (resp.status || resp.body || resp.data) ? 'OK' : JSON.stringify(resp).slice(0,120));
1634
- } catch (err) {
1635
- console.error('Broadcast failed:', err?.message || err);
1636
- process.exit(1);
1637
- }
1638
-
1639
- console.log('\\nDone. MQTT will sync and you should see the message in the app.');
1640
- rl.close();
1641
- process.exit(0);
1642
- } catch (e) {
1643
- console.error('Fatal:', e);
1644
- process.exit(1);
1645
739
  }
1646
- })();
1647
- ```
1648
740
 
1649
- ---
1650
-
1651
- ### Example 4: Video Sender (upload + broadcast)
1652
-
1653
- 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/`.
1654
-
1655
- ```javascript
1656
- // pseudo-code outline
1657
- const videoBuffer = fs.readFileSync('./video.mp4');
1658
- const upload = await ig.publish.video({ video: videoBuffer, duration: 12000 });
1659
- // upload.upload_id -> then broadcast with configure_video payload similar to the photo flow
741
+ // Stop typing
742
+ await realtime.directCommands.indicateActivity({
743
+ threadId: msg.thread_id,
744
+ isActive: false
745
+ });
746
+ });
1660
747
  ```
1661
748
 
1662
- **Notes:** video uploads often require multipart/segmented upload and longer timeouts; prefer library helper `ig.publish.video()`.
1663
-
1664
749
  ---
1665
750
 
1666
- ### Example 5: Share a file / document (workaround)
1667
-
1668
- Instagram DM does not provide a simple "attach arbitrary file" endpoint. Common workarounds:
751
+ ## Session Management
1669
752
 
1670
- 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).
1671
- 2. If the file is an image or video, use the rupload + broadcast flow above.
1672
-
1673
- Example (send link message):
753
+ ### Multi-File Auth State (Baileys Style)
1674
754
 
1675
755
  ```javascript
1676
- // send link as text via realtime (MQTT)
1677
- await realtime.directCommands.sendTextViaRealtime(threadId, 'Download the file here: https://myhost.example.com/myfile.pdf');
756
+ const authState = await useMultiFileAuthState('./auth_folder');
1678
757
  ```
1679
758
 
1680
- ---
1681
-
1682
- ### Tips for bots sending media
1683
-
1684
- - Always **upload first**, then **broadcast**. Do not rely on MQTT-only calls for media.
1685
- - Use retries and exponential backoff for both rupload and broadcast steps (Instagram can return transient 503).
1686
- - Save `upload_id` and broadcast responses for debugging.
1687
- - If you need to send the same file to many threads, upload once, reuse the same `upload_id` in multiple broadcast calls.
759
+ | Method | Description |
760
+ |--------|-------------|
761
+ | `hasSession()` | Check if credentials exist |
762
+ | `hasMqttSession()` | Check if MQTT session exists |
763
+ | `loadCreds(ig)` | Load saved credentials |
764
+ | `saveCreds(ig)` | Save current credentials |
765
+ | `isSessionValid(ig)` | Validate with Instagram |
766
+ | `loadMqttSession()` | Get saved MQTT session |
767
+ | `saveMqttSession(realtime)` | Save MQTT session |
768
+ | `clearSession()` | Delete all session files |
1688
769
 
1689
770
  ---
1690
771
 
1691
-
1692
772
  ## API Reference
1693
773
 
1694
774
  ### IgApiClient
1695
775
 
1696
- #### Authentication
1697
-
1698
776
  ```javascript
1699
- // Login with credentials
777
+ // Login
1700
778
  await ig.login({
1701
779
  username: 'your_username',
1702
- password: 'your_password',
1703
- email: 'your_email@example.com'
780
+ password: 'your_password'
1704
781
  });
1705
782
 
1706
- // Load from saved session
1707
- const session = JSON.parse(fs.readFileSync('session.json'));
1708
- await ig.state.deserialize(session);
1709
-
1710
783
  // Save session
1711
784
  const serialized = ig.state.serialize();
1712
785
  fs.writeFileSync('session.json', JSON.stringify(serialized));
786
+
787
+ // Load session
788
+ const session = JSON.parse(fs.readFileSync('session.json'));
789
+ await ig.state.deserialize(session);
1713
790
  ```
1714
791
 
1715
- #### Direct Messages
792
+ ### Direct Messages (REST)
1716
793
 
1717
794
  ```javascript
1718
- // Get inbox with all conversations
795
+ // Get inbox
1719
796
  const inbox = await ig.direct.getInbox();
1720
797
 
1721
- // Get specific thread messages
798
+ // Get thread
1722
799
  const thread = await ig.direct.getThread(threadId);
1723
800
 
1724
- // Send text message (HTTP - slower than MQTT)
1725
- await ig.direct.send({
1726
- threadId: threadId,
1727
- item: {
1728
- type: 'text',
1729
- text: 'Hello there!'
1730
- }
1731
- });
1732
-
1733
- // Mark messages as seen
1734
- await ig.direct.markMessagesSeen(threadId, [messageId]);
801
+ // Send text (REST fallback)
802
+ await ig.direct.sendText({ thread_ids: [threadId], text: 'Hello' });
1735
803
  ```
1736
804
 
1737
- ### RealtimeClient
805
+ ### RealtimeClient Events
1738
806
 
1739
- #### Connection
807
+ | Event | Description |
808
+ |-------|-------------|
809
+ | `connected` | MQTT connected |
810
+ | `disconnected` | MQTT disconnected |
811
+ | `message` | New message received |
812
+ | `message_live` | Live message with parsed data |
813
+ | `typing` | Typing indicator |
814
+ | `presence` | User presence update |
815
+ | `error` | Connection error |
1740
816
 
1741
- ```javascript
1742
- const realtime = new RealtimeClient(ig);
1743
-
1744
- // Connect to MQTT
1745
- await realtime.connect({
1746
- graphQlSubs: ['ig_sub_direct', 'ig_sub_direct_v2_message_sync'],
1747
- skywalkerSubs: ['presence_subscribe', 'typing_subscribe'],
1748
- irisData: inbox // Required: inbox data from ig.direct.getInbox()
1749
- });
817
+ ---
1750
818
 
1751
- // Connect from saved session (NEW in v5.60.2)
1752
- await realtime.connectFromSavedSession(authState);
1753
- ```
819
+ ## Important Notes
1754
820
 
1755
- #### All 18 MQTT Methods via directCommands
821
+ ### Media Uploads
1756
822
 
1757
- ```javascript
1758
- // 1. Send Text
1759
- await realtime.directCommands.sendTextViaRealtime(threadId, 'Text');
823
+ Media files (photos, videos) are always uploaded via HTTP rupload first. MQTT only sends metadata/references, never the raw bytes. The `sendPhoto()` and `sendVideo()` methods handle this automatically.
1760
824
 
1761
- // 2. Delete Message
1762
- await realtime.directCommands.deleteMessage(threadId, messageId);
825
+ ### Rate Limiting
1763
826
 
1764
- // 3. Edit Message
1765
- await realtime.directCommands.editMessage(threadId, messageId, 'New text');
827
+ Instagram has strict rate limits. Add delays between rapid-fire messages to avoid temporary bans.
1766
828
 
1767
- // 4. Reply to Message
1768
- await realtime.directCommands.replyToMessage(threadId, messageId, 'Reply');
829
+ ### Session Persistence
1769
830
 
1770
- // 5. Send Reaction
1771
- await realtime.directCommands.sendReaction({
1772
- threadId, itemId, emoji: '❤️'
1773
- });
831
+ Always save your session after login to avoid repeated logins which can trigger verification.
1774
832
 
1775
- // 6. Send Media
1776
- await realtime.directCommands.sendMedia({ threadId, mediaId });
833
+ ---
1777
834
 
1778
- // 7. Send Location
1779
- await realtime.directCommands.sendLocation({ threadId, locationId });
835
+ ## Changelog
1780
836
 
1781
- // 8. Send Profile
1782
- await realtime.directCommands.sendProfile({ threadId, userId });
837
+ ### v5.61.11
838
+ - Full iOS support with 21 device presets
839
+ - Full Android support with 12 device presets
840
+ - switchPlatform() for easy platform switching
1783
841
 
1784
- // 9. Send Hashtag
1785
- await realtime.directCommands.sendHashtag({ threadId, hashtag });
842
+ ### v5.60.8
843
+ - downloadContentFromMessage() - Baileys-style media download
844
+ - View-once media extraction support
845
+ - downloadMediaBuffer() and extractMediaUrls()
1786
846
 
1787
- // 10. Send Like
1788
- await realtime.directCommands.sendLike({ threadId });
847
+ ### v5.60.7
848
+ - Custom Device Emulation with 12 preset devices
849
+ - setCustomDevice() and usePresetDevice() methods
1789
850
 
1790
- // 11. Send Story
1791
- await realtime.directCommands.sendUserStory({ threadId, storyId });
851
+ ### v5.60.3
852
+ - sendPhoto() and sendVideo() for media uploads
1792
853
 
1793
- // 12. Mark as Seen
1794
- await realtime.directCommands.markAsSeen({ threadId, itemId });
854
+ ### v5.60.2
855
+ - useMultiFileAuthState() - Baileys-style session persistence
856
+ - connectFromSavedSession() method
1795
857
 
1796
- // 13. Indicate Activity (Typing)
1797
- await realtime.directCommands.indicateActivity({ threadId, isActive: true });
858
+ ### v5.60.0
859
+ - Full MQTT integration with EnhancedDirectCommands
860
+ - Real-time messaging with <500ms latency
1798
861
 
1799
- // 14. Subscribe to Follow Notifications
1800
- await realtime.directCommands.subscribeToFollowNotifications();
862
+ ---
1801
863
 
1802
- // 15. Subscribe to Mention Notifications
1803
- await realtime.directCommands.subscribeToMentionNotifications();
864
+ ## License
1804
865
 
1805
- // 16. Subscribe to Call Notifications
1806
- await realtime.directCommands.subscribeToCallNotifications();
866
+ MIT
1807
867
 
1808
- // 17. Add Member to Thread
1809
- await realtime.directCommands.addMemberToThread(threadId, userId);
868
+ ## Support
1810
869
 
1811
- // 18. Remove Member from Thread
1812
- await realtime.directCommands.removeMemberFromThread(threadId, userId);
1813
- ```
870
+ For issues, bugs, or feature requests: https://github.com/Kunboruto20/nodejs-insta-private-api/issues
1814
871
 
1815
- #### Listening for Events
872
+ Documentation: https://github.com/Kunboruto20/nodejs-insta-private-api
1816
873
 
1817
- ```javascript
1818
- // Incoming messages
1819
- realtime.on('message', (data) => {
1820
- const msg = data.message;
1821
- console.log(msg.text); // Message text
1822
- console.log(msg.from_user_id); // Sender user ID
1823
- console.log(msg.thread_id); // Conversation thread ID
1824
- });
1825
874
 
1826
- // Connection status
1827
- realtime.on('connected', () => console.log('Connected'));
1828
- realtime.on('disconnected', () => console.log('Disconnected'));
875
+ ---
876
+ ## Enhanced Location Usage — Practical Examples (English)
1829
877
 
1830
- // Notifications
1831
- realtime.on('follow', (data) => console.log('New follower:', data.user_id));
1832
- realtime.on('mention', (data) => console.log('Mentioned:', data.content_type));
1833
- realtime.on('call', (data) => console.log('Call from:', data.caller_id));
878
+ The `EnhancedDirectCommands` class (see `enhanced.direct.commands.js`) implements robust location sending for Instagram Direct by trying **story-with-location-sticker → share story to thread → fallback to link**. Below are ready-to-copy examples showing how to use the location-related methods and payloads exposed by the class.
1834
879
 
1835
- // Errors
1836
- realtime.on('error', (err) => console.error('Error:', err.message));
1837
- ```
880
+ > These examples assume:
881
+ > - `realtime` is an instance of `RealtimeClient`.
882
+ > - `realtime.directCommands` is an instance of `EnhancedDirectCommands`.
883
+ > - You have a valid `threadId` (target DM thread).
884
+ > - `realtime.ig` exists when examples require publishing a story via the private IG client.
1838
885
 
1839
- ### User Information
886
+ ### 1) Send a location when you already have a `venue` object (recommended)
1840
887
 
1841
888
  ```javascript
1842
- // Get user info by username
1843
- const user = await ig.user.info('username');
1844
-
1845
- // Search users
1846
- const results = await ig.user.search({ username: 'query' });
1847
-
1848
- // Get followers
1849
- const followers = await ig.user.followers('user_id');
889
+ // venue shape expected by sendLocation:
890
+ // { id, name, address, lat, lng, facebook_places_id, external_source }
891
+ const venue = {
892
+ id: "213385402",
893
+ name: "McDonald's Unirii",
894
+ address: "Piața Unirii, Bucharest",
895
+ lat: 44.4268,
896
+ lng: 26.1025,
897
+ facebook_places_id: "213385402",
898
+ external_source: "facebook_places"
899
+ };
1850
900
 
1851
- // Get following
1852
- const following = await ig.user.following('user_id');
901
+ await realtime.directCommands.sendLocation({
902
+ threadId: "340282366841710300949128114477782749726",
903
+ venue,
904
+ text: "Meet me here at 18:00"
905
+ });
1853
906
  ```
1854
907
 
908
+ **What happens:**
909
+ 1. The method attempts to publish a Story with a location sticker using `realtime.ig.publish.story`.
910
+ 2. If the Story publish succeeds and a `storyId` is returned, `sendUserStory` (reel_share) is used to share the story to the thread.
911
+ 3. If either step fails, the method falls back to sending a link to `https://www.instagram.com/explore/locations/{placeId}/` via `itemType: 'link'`.
912
+
1855
913
  ---
1856
914
 
1857
- ## Message Structure
915
+ ### 2) Search for a place (instagram private search) and send it
1858
916
 
1859
- Messages arrive as event data with this structure:
917
+ Use `searchAndSendLocation()` when you only have a search query or coordinates:
1860
918
 
1861
919
  ```javascript
1862
- realtime.on('message', (data) => {
1863
- const msg = data.message;
1864
-
1865
- console.log({
1866
- text: msg.text, // Message content (string)
1867
- from_user_id: msg.from_user_id, // Sender's Instagram user ID
1868
- thread_id: msg.thread_id, // Conversation thread ID
1869
- timestamp: msg.timestamp, // Unix timestamp
1870
- item_id: msg.item_id // Unique message ID
1871
- });
920
+ await realtime.directCommands.searchAndSendLocation({
921
+ threadId: "340282366841710300949128114477782749726",
922
+ query: "Starbucks Piata Unirii",
923
+ lat: 44.4268,
924
+ lng: 26.1025
1872
925
  });
1873
926
  ```
1874
927
 
1875
- ---
1876
-
1877
- ## Performance & Latency
1878
-
1879
- | Operation | Latency | Method |
1880
- |-----------|---------|--------|
1881
- | Receive incoming DM | 100-500ms | MQTT (real-time) |
1882
- | Send DM via MQTT | 200-800ms | Direct MQTT publish |
1883
- | Send DM via HTTP | 1-3s | REST API fallback |
1884
- | Get inbox | 500ms-2s | REST API |
1885
-
1886
- MQTT is significantly faster for both receiving and sending messages.
928
+ This helper calls Instagram's `/fbsearch/places/` private endpoint (via `realtime.ig.request`) and then normalizes the first result into the `venue` shape before calling `sendLocation()`.
1887
929
 
1888
930
  ---
1889
931
 
1890
- ## Best Practices
932
+ ### 3) Build a location sticker manually & publish story (advanced)
1891
933
 
1892
- ### 1. Session Management (Recommended: useMultiFileAuthState)
934
+ If you want direct control over the sticker object or to publish your own image, you can use `createLocationStickerFromVenue()` and `realtime.ig.publish.story()` directly:
1893
935
 
1894
936
  ```javascript
1895
- // NEW: Use multi-file auth state for better session management
1896
- const authState = await useMultiFileAuthState('./auth_info_instagram');
1897
-
1898
- // Check and load existing session
1899
- if (authState.hasSession()) {
1900
- await authState.loadCreds(ig);
1901
- if (await authState.isSessionValid(ig)) {
1902
- // Session is valid, proceed
1903
- }
1904
- }
937
+ const venue = {
938
+ id: "213385402",
939
+ name: "McDonald's Unirii",
940
+ address: "Piața Unirii, Bucharest",
941
+ lat: 44.4268,
942
+ lng: 26.1025,
943
+ facebook_places_id: "213385402"
944
+ };
1905
945
 
1906
- // Save after login
1907
- await authState.saveCreds(ig);
1908
- await authState.saveMqttSession(realtime);
1909
- ```
946
+ // create sticker compatible with publish.story helpers
947
+ const sticker = realtime.directCommands.createLocationStickerFromVenue(venue);
1910
948
 
1911
- ### 2. Error Handling
949
+ // create a tiny placeholder image (1x1 PNG) or your real photo buffer
950
+ const SINGLE_PIXEL_PNG_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR4nGNgYAAAAAMAAWgmWQ0AAAAASUVORK5CYII=";
951
+ const photoBuffer = Buffer.from(SINGLE_PIXEL_PNG_BASE64, 'base64');
1912
952
 
1913
- ```javascript
1914
- realtime.on('message', async (data) => {
1915
- try {
1916
- const msg = data.message;
1917
- await realtime.directCommands.sendTextViaRealtime(msg.thread_id, 'Reply');
1918
- } catch (err) {
1919
- console.error('Error:', err.message);
1920
- }
953
+ // publish story with the sticker (if realtime.ig.publish.story exists)
954
+ const publishResult = await realtime.ig.publish.story({
955
+ file: photoBuffer,
956
+ stickers: [sticker]
1921
957
  });
1922
- ```
1923
-
1924
- ### 3. Rate Limiting
1925
958
 
1926
- ```javascript
1927
- const userLastSeen = new Map();
1928
-
1929
- if (userLastSeen.has(userId) && Date.now() - userLastSeen.get(userId) < 5000) {
1930
- return;
959
+ // try to resolve returned story id and then share it
960
+ const storyId = publishResult?.media?.pk || publishResult?.item_id || publishResult?.upload_id;
961
+ if (storyId) {
962
+ await realtime.directCommands.sendUserStory({
963
+ threadId: "340282366841710300949128114477782749726",
964
+ storyId,
965
+ text: "Location for tonight"
966
+ });
967
+ } else {
968
+ // fallback: send explore link manually
969
+ await realtime.directCommands.sendLink({
970
+ threadId: "340282366841710300949128114477782749726",
971
+ link: `https://www.instagram.com/explore/locations/${venue.facebook_places_id || venue.id}/`,
972
+ text: venue.name
973
+ });
1931
974
  }
1932
- userLastSeen.set(userId, Date.now());
1933
975
  ```
1934
976
 
1935
- ### 4. Connection Monitoring
1936
-
1937
- ```javascript
1938
- let isConnected = false;
1939
-
1940
- realtime.on('connected', () => {
1941
- isConnected = true;
1942
- console.log('Connected');
1943
- });
1944
-
1945
- realtime.on('disconnected', () => {
1946
- isConnected = false;
1947
- console.log('Disconnected - will auto-reconnect');
1948
- });
1949
- ```
1950
-
1951
- ---
1952
-
1953
- ## Limitations
1954
-
1955
- - Instagram account required - No API tokens needed, use your credentials
1956
- - Rate limiting - Instagram rate limits automated messaging, implement delays
1957
- - Mobile detection - Instagram may detect bot activity and require verification
1958
- - Session expiry - Sessions may expire after 60+ days, require re-login
1959
- - Message history - Only real-time messages available, no historical message sync
1960
-
1961
977
  ---
1962
978
 
1963
- ## Troubleshooting
979
+ ### 4) Force sending the explore-location link (explicit fallback)
1964
980
 
1965
- ### Login Fails
981
+ If you don't want to publish a story and only need the location link in DM:
1966
982
 
1967
983
  ```javascript
1968
- // Ensure credentials are correct
1969
- // Instagram may require 2FA verification
1970
- ```
1971
-
1972
- ### MQTT Connection Fails
1973
-
1974
- ```javascript
1975
- // Check that inbox data is loaded before connecting
1976
- const inbox = await ig.direct.getInbox();
1977
-
1978
- // Connection retries automatically with exponential backoff
1979
- ```
1980
-
1981
- ### Messages Not Sending
1982
-
1983
- ```javascript
1984
- // Ensure MQTT is connected
1985
- if (!isConnected) {
1986
- console.log('Waiting for MQTT connection...');
1987
- return;
1988
- }
1989
-
1990
- // Check rate limiting - Instagram blocks rapid messaging
984
+ const placeId = "213385402";
985
+ await realtime.directCommands.sendLink({
986
+ threadId: "340282366841710300949128114477782749726",
987
+ link: `https://www.instagram.com/explore/locations/${placeId}/`,
988
+ text: "Meet here"
989
+ });
1991
990
  ```
1992
991
 
1993
992
  ---
1994
993
 
1995
- ## Changelog
1996
-
1997
- ### v5.60.8
1998
- - **NEW:** `downloadContentFromMessage()` - Download media from DM messages (like Baileys for WhatsApp)
1999
- - **NEW:** `downloadMediaBuffer()` - Get media as Buffer directly
2000
- - **NEW:** `extractMediaUrls()` - Extract CDN URLs from any message type
2001
- - **NEW:** `hasMedia()` - Check if message contains downloadable media
2002
- - **NEW:** `getMediaType()` - Get media type without downloading
2003
- - **NEW:** `isViewOnceMedia()` - Check if message is view-once (disappearing)
2004
- - **NEW:** `MEDIA_TYPES` constant for all supported media types
2005
- - Full support for view-once (raven) media extraction
2006
- - Support for photos, videos, voice messages, reels, stories, clips
2007
-
2008
- ### v5.60.7
2009
- - Added Custom Device Emulation with 12 preset devices
2010
- - `setCustomDevice()` and `usePresetDevice()` methods
2011
- - Default device: Samsung Galaxy S25 Ultra (Android 15)
2012
-
2013
- ### v5.60.3
2014
- - Added `sendPhoto()` and `sendVideo()` for media uploads via MQTT
2015
-
2016
- ### v5.60.2
2017
- - Added `useMultiFileAuthState()` - Baileys-style multi-file session persistence
2018
- - Added `connectFromSavedSession()` method for RealtimeClient
2019
- - Session now persists both HTTP auth and MQTT real-time data
2020
- - 7 separate files for better organization and security
2021
-
2022
- ### v5.60.1
2023
- - Fixed MQTT message sync issues
2024
- - Improved connection stability
2025
-
2026
- ### v5.60.0
2027
- - Full MQTT integration with 18 methods
2028
- - Real-time messaging with <500ms latency
994
+ ### 5) Error handling & debug tips
2029
995
 
2030
- ---
2031
-
2032
- ## License
2033
-
2034
- MIT
2035
-
2036
- ## Support
2037
-
2038
- For issues, bugs, or feature requests: https://github.com/Kunboruto20/nodejs-insta-private-api/issues
2039
-
2040
- Documentation: https://github.com/Kunboruto20/nodejs-insta-private-api
2041
-
2042
- Examples: See repository examples/ directory for working implementations
2043
-
2044
-
2045
- ---
2046
-
2047
- ## Examples using `ig.request.send` wrapper (photo & video, MQTT *reference* + REST broadcast)
2048
-
2049
- Below are practical code snippets that use the `ig.request.send` wrapper (the same request helper used inside the Instagram client).
2050
- Each example shows **two flows** so you can pick the behavior you want:
2051
-
2052
- 1. **MQTT reference flow** — upload via `rupload` (HTTP) to obtain `upload_id` then **publish an MQTT `send_item` that contains only the `upload_id`** (Instagram attaches the already-uploaded file to the thread). This matches the APK behavior: MQTT only carries a reference, not the binary.
2053
- 2. **REST broadcast flow** — upload via `rupload` (HTTP) and then call the `direct_v2` broadcast REST endpoint to attach the uploaded media to a thread (alternate, server-side broadcast).
2054
-
2055
- > Note: these examples assume your runtime has access to:
2056
- > - `ig` (Instagram client wrapper) and `ig.request.send` method,
2057
- > - `mqtt` connected client exposing `.publish({ topic, qosLevel, payload })`,
2058
- > - `compressDeflate` helper (same as in project `shared`),
2059
- > - `uuid` (for `client_context` generation), and
2060
- > - `constants.Topics.SEND_MESSAGE.id` for the MQTT send topic.
2061
-
2062
- ### Photo — MQTT reference example (rupload via `ig.request.send` then MQTT reference)
2063
996
  ```javascript
2064
- // Photo upload -> get upload_id (rupload) -> publish MQTT 'send_item' with upload_id
2065
- const { v4: uuidv4 } = require('uuid');
2066
-
2067
- async function sendPhotoAsMqttReference({ ig, mqtt, photoBuffer, threadId, caption = '' }) {
2068
- if (!ig || !ig.request) throw new Error('ig.request not available');
2069
- if (!mqtt || typeof mqtt.publish !== 'function') throw new Error('mqtt client not available');
2070
- if (!Buffer.isBuffer(photoBuffer)) throw new Error('photoBuffer must be a Buffer');
2071
-
2072
- // 1) perform rupload via ig.request.send
2073
- const uploadId = Date.now().toString();
2074
- const objectName = `${uuidv4()}.jpg`;
2075
- const ruploadParams = {
2076
- upload_id: uploadId,
2077
- media_type: 1,
2078
- image_compression: '{"lib_name":"moz","lib_version":"3.1.m","quality":"80"}',
2079
- xsharing_user_ids: JSON.stringify([]),
2080
- };
2081
-
2082
- const uploadHeaders = {
2083
- 'X-Instagram-Rupload-Params': JSON.stringify(ruploadParams),
2084
- 'Content-Type': 'image/jpeg',
2085
- 'X-Entity-Type': 'image/jpeg',
2086
- 'X-Entity-Length': String(photoBuffer.length),
2087
- 'Content-Length': String(photoBuffer.length),
2088
- };
2089
-
2090
- const uploadUrl = `/rupload_igphoto/${objectName}`;
2091
-
2092
- const uploadResponse = await ig.request.send({
2093
- url: uploadUrl,
2094
- method: 'POST',
2095
- headers: uploadHeaders,
2096
- body: photoBuffer,
2097
- });
2098
-
2099
- const serverUploadId = (uploadResponse && uploadResponse.upload_id) ? uploadResponse.upload_id : uploadId;
2100
-
2101
- // 2) publish MQTT reference telling server "attach upload_id X to thread Y"
2102
- const clientContext = uuidv4();
2103
- const command = {
2104
- action: 'send_item',
2105
- thread_id: String(threadId),
2106
- item_type: 'media',
2107
- upload_id: serverUploadId,
2108
- text: caption || '',
2109
- client_context: clientContext,
2110
- timestamp: Date.now(),
2111
- };
2112
-
2113
- const json = JSON.stringify(command);
2114
- const payload = await compressDeflate(json); // reuse shared helper
2115
- await mqtt.publish({
2116
- topic: constants.Topics.SEND_MESSAGE.id,
2117
- qosLevel: 1,
2118
- payload,
997
+ try {
998
+ await realtime.directCommands.sendLocation({ threadId, venue, text: "See you" });
999
+ console.log("Location sent!");
1000
+ } catch (err) {
1001
+ console.error("Failed to send location:", err);
1002
+ // fallback to explicit link if needed
1003
+ await realtime.directCommands.sendLink({
1004
+ threadId,
1005
+ link: `https://www.instagram.com/explore/locations/${venue.facebook_places_id || venue.id}/`,
1006
+ text: venue.name || "Location"
2119
1007
  });
2120
-
2121
- return { upload_id: serverUploadId };
2122
1008
  }
2123
1009
  ```
2124
1010
 
2125
- ### Photo REST broadcast example (rupload via `ig.request.send` then direct_v2 broadcast)
2126
- ```javascript
2127
- // Photo upload -> rupload via ig.request.send -> REST broadcast to the thread
2128
- async function sendPhotoViaRestBroadcast({ ig, photoBuffer, threadId, caption = '' }) {
2129
- const uploadId = Date.now().toString();
2130
- const objectName = `${uuidv4()}.jpg`;
2131
-
2132
- const ruploadParams = {
2133
- upload_id: uploadId,
2134
- media_type: 1,
2135
- image_compression: '{"lib_name":"moz","lib_version":"3.1.m","quality":"80"}',
2136
- xsharing_user_ids: JSON.stringify([]),
2137
- };
2138
-
2139
- const uploadResponse = await ig.request.send({
2140
- url: `/rupload_igphoto/${objectName}`,
2141
- method: 'POST',
2142
- headers: {
2143
- 'X-Instagram-Rupload-Params': JSON.stringify(ruploadParams),
2144
- 'Content-Type': 'image/jpeg',
2145
- 'X-Entity-Type': 'image/jpeg',
2146
- 'X-Entity-Length': String(photoBuffer.length),
2147
- 'Content-Length': String(photoBuffer.length),
2148
- },
2149
- body: photoBuffer,
2150
- });
2151
-
2152
- const serverUploadId = (uploadResponse && uploadResponse.upload_id) ? uploadResponse.upload_id : uploadId;
2153
-
2154
- // Broadcast attach via REST endpoint
2155
- const broadcastResponse = await ig.request.send({
2156
- url: '/direct_v2/threads/broadcast/upload_photo/',
2157
- method: 'POST',
2158
- form: {
2159
- upload_id: serverUploadId,
2160
- action: 'send_item',
2161
- thread_ids: JSON.stringify([String(threadId)]),
2162
- caption: caption || '',
2163
- },
2164
- });
1011
+ If you need verbose logs, enable debug for the realtime/enhanced module:
2165
1012
 
2166
- return { upload_id: serverUploadId, broadcastResponse };
2167
- }
1013
+ ```bash
1014
+ # in your environment (example)
1015
+ DEBUG="realtime:enhanced-commands" node your_bot.js
1016
+ # or to see broader realtime logs
1017
+ DEBUG="realtime:*" node your_bot.js
2168
1018
  ```
2169
1019
 
2170
- ### Video — MQTT reference example (rupload video + MQTT reference)
2171
- ```javascript
2172
- // Video upload -> rupload video via ig.request.send -> MQTT reference
2173
- async function sendVideoAsMqttReference({ ig, mqtt, videoBuffer, threadId, caption = '', duration = 0, width = 720, height = 1280 }) {
2174
- const uploadId = Date.now().toString();
2175
- const objectName = `${uuidv4()}.mp4`;
2176
-
2177
- const ruploadParams = {
2178
- upload_id: uploadId,
2179
- media_type: 2, // video
2180
- xsharing_user_ids: JSON.stringify([]),
2181
- upload_media_duration_ms: Math.round(duration * 1000),
2182
- upload_media_width: width,
2183
- upload_media_height: height,
2184
- };
2185
-
2186
- const uploadResponse = await ig.request.send({
2187
- url: `/rupload_igvideo/${objectName}`,
2188
- method: 'POST',
2189
- headers: {
2190
- 'X-Instagram-Rupload-Params': JSON.stringify(ruploadParams),
2191
- 'Content-Type': 'video/mp4',
2192
- 'X-Entity-Type': 'video/mp4',
2193
- 'X-Entity-Length': String(videoBuffer.length),
2194
- 'Content-Length': String(videoBuffer.length),
2195
- 'Offset': '0',
2196
- },
2197
- body: videoBuffer,
2198
- });
2199
-
2200
- const serverUploadId = (uploadResponse && uploadResponse.upload_id) ? uploadResponse.upload_id : uploadId;
2201
-
2202
- // ATTACH VIA HTTP BROADCAST (preferred, reliable)
2203
- // Attempt REST broadcast first (most reliable). This will create the direct message server-side
2204
- // and let MQTT/clients receive the created message as normal sync.
2205
- try {
2206
- const broadcastResponse = await ig.request.send({
2207
- url: '/direct_v2/threads/broadcast/upload_photo/',
2208
- method: 'POST',
2209
- form: {
2210
- upload_id: serverUploadId,
2211
- action: 'send_item',
2212
- thread_ids: JSON.stringify([String(threadId)]),
2213
- caption: caption || '',
2214
- client_context: clientContext,
2215
- },
2216
- timeout: 60000,
2217
- });
2218
- return { upload_id: serverUploadId, broadcastResponse };
2219
- } catch (err) {
2220
- // If REST broadcast fails for transient reasons, fall back to attempting an MQTT reference publish.
2221
- // NOTE: MQTT 'reference' publish is not guaranteed to be accepted on all server versions or accounts.
2222
- // Keep it as a fallback only — the HTTP broadcast above is the recommended, reliable method.
2223
- try {
2224
- const json = JSON.stringify({
2225
- action: 'send_item',
2226
- thread_id: String(threadId),
2227
- item_type: 'media',
2228
- upload_id: serverUploadId,
2229
- text: caption || '',
2230
- client_context: clientContext,
2231
- timestamp: Date.now(),
2232
- });
2233
- const payload = await compressDeflate(json);
2234
- await mqtt.publish({
2235
- topic: constants.Topics.SEND_MESSAGE.id,
2236
- qosLevel: 1,
2237
- payload,
2238
- });
2239
- return { upload_id: serverUploadId, mqttFallback: true };
2240
- } catch (err2) {
2241
- // last-resort: return upload id and both errors for debugging
2242
- return { upload_id: serverUploadId, broadcastError: err?.message || String(err), mqttError: err2?.message || String(err2) };
2243
- }
2244
- }
2245
- }
2246
- ```
1020
+ ---
2247
1021
 
2248
- ### Video REST broadcast example (rupload video + direct_v2 broadcast)
2249
- ```javascript
2250
- async function sendVideoViaRestBroadcast({ ig, videoBuffer, threadId, caption = '', duration = 0, width = 720, height = 1280 }) {
2251
- const uploadId = Date.now().toString();
2252
- const objectName = `${uuidv4()}.mp4`;
2253
-
2254
- const ruploadParams = {
2255
- upload_id: uploadId,
2256
- media_type: 2,
2257
- xsharing_user_ids: JSON.stringify([]),
2258
- upload_media_duration_ms: Math.round(duration * 1000),
2259
- upload_media_width: width,
2260
- upload_media_height: height,
2261
- };
2262
-
2263
- const uploadResponse = await ig.request.send({
2264
- url: `/rupload_igvideo/${objectName}`,
2265
- method: 'POST',
2266
- headers: {
2267
- 'X-Instagram-Rupload-Params': JSON.stringify(ruploadParams),
2268
- 'Content-Type': 'video/mp4',
2269
- 'X-Entity-Type': 'video/mp4',
2270
- 'X-Entity-Length': String(videoBuffer.length),
2271
- 'Content-Length': String(videoBuffer.length),
2272
- 'Offset': '0',
2273
- },
2274
- body: videoBuffer,
2275
- });
1022
+ ### 6) Quick checklist (what the library needs to make story-with-sticker work)
2276
1023
 
2277
- const serverUploadId = (uploadResponse && uploadResponse.upload_id) ? uploadResponse.upload_id : uploadId;
2278
-
2279
- const broadcastResponse = await ig.request.send({
2280
- url: '/direct_v2/threads/broadcast/upload_video/',
2281
- method: 'POST',
2282
- form: {
2283
- upload_id: serverUploadId,
2284
- action: 'send_item',
2285
- thread_ids: JSON.stringify([String(threadId)]),
2286
- video_result: '',
2287
- caption: caption || '',
2288
- },
2289
- });
1024
+ - `realtime.ig` must exist and expose `publish.story(...)` (a private client publish helper).
1025
+ - The `venue` must include either `facebook_places_id` or `id`.
1026
+ - If `realtime.ig.publish.story` is unavailable or Instagram rejects the sticker, the library **automatically falls back** to sending a DM link to the Explore locations page.
2290
1027
 
2291
- return { upload_id: serverUploadId, broadcastResponse };
2292
- }
2293
- ```
1028
+ ---
2294
1029
 
1030
+ ## End of Location Examples
2295
1031
 
2296
- ---
1032
+ These examples are appended to the original README in order to keep the whole original file intact while adding clear, English examples for location flows.
2297
1033
 
2298
- (Section added programmatically: `Examples using ig.request.send` — appended to the existing README content.)