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
|
|
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',
|
|
146
|
-
model: 'SM-S928B',
|
|
147
|
-
device: 'e3q',
|
|
148
|
-
androidVersion: '15',
|
|
149
|
-
androidApiLevel: 35,
|
|
150
|
-
resolution: '1440x3120',
|
|
151
|
-
dpi: '505dpi',
|
|
152
|
-
chipset: 'qcom',
|
|
153
|
-
build: 'UP1A.231005.007'
|
|
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
|
-
|
|
139
|
+
---
|
|
178
140
|
|
|
179
|
-
|
|
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('
|
|
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(
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
194
|
+
```javascript
|
|
195
|
+
await realtime.directCommands.sendText({
|
|
196
|
+
threadId: '340282366841710300949128114477782749726',
|
|
197
|
+
text: 'Hello from MQTT!'
|
|
198
|
+
});
|
|
199
|
+
```
|
|
283
200
|
|
|
284
|
-
|
|
201
|
+
#### Send Text (Alternative Signature)
|
|
285
202
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
-
|
|
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
|
-
|
|
209
|
+
```javascript
|
|
210
|
+
await realtime.directCommands.replyToMessage(threadId, messageId, 'This is my reply');
|
|
211
|
+
```
|
|
298
212
|
|
|
299
|
-
|
|
213
|
+
#### Edit Message
|
|
300
214
|
|
|
301
|
-
|
|
215
|
+
```javascript
|
|
216
|
+
await realtime.directCommands.editMessage(threadId, itemId, 'Updated text here');
|
|
217
|
+
```
|
|
302
218
|
|
|
303
|
-
|
|
219
|
+
#### Delete Message
|
|
304
220
|
|
|
305
221
|
```javascript
|
|
306
|
-
|
|
307
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
350
|
-
const json = JSON.stringify(command);
|
|
351
|
-
const payload = await compressDeflate(json); // project helper
|
|
229
|
+
#### Send Hashtag
|
|
352
230
|
|
|
353
|
-
|
|
354
|
-
await
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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
|
-
|
|
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
|
-
|
|
371
|
-
|
|
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
|
-
|
|
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
|
-
|
|
412
|
-
await
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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
|
-
|
|
471
|
-
console.log(`📩 New message from ${msg.username}: ${msg.text}`);
|
|
472
|
-
});
|
|
267
|
+
#### Send Profile
|
|
473
268
|
|
|
474
|
-
|
|
475
|
-
|
|
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
|
-
|
|
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
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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
|
-
####
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
318
|
+
---
|
|
551
319
|
|
|
552
|
-
|
|
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
|
-
|
|
322
|
+
#### Send Reaction
|
|
564
323
|
|
|
565
324
|
```javascript
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
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
|
-
|
|
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
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
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
|
-
|
|
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
|
-
|
|
345
|
+
```javascript
|
|
346
|
+
await realtime.directCommands.removeReaction({
|
|
347
|
+
threadId: threadId,
|
|
348
|
+
itemId: messageId
|
|
349
|
+
});
|
|
646
350
|
```
|
|
647
351
|
|
|
648
352
|
---
|
|
649
353
|
|
|
650
|
-
|
|
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
|
-
|
|
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
|
-
|
|
665
|
-
|
|
666
|
-
|
|
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
|
-
|
|
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
|
-
|
|
368
|
+
// Start typing
|
|
369
|
+
await realtime.directCommands.indicateActivity({
|
|
370
|
+
threadId: threadId,
|
|
371
|
+
isActive: true
|
|
372
|
+
});
|
|
706
373
|
|
|
707
|
-
//
|
|
708
|
-
|
|
374
|
+
// Stop typing
|
|
375
|
+
await realtime.directCommands.indicateActivity({
|
|
376
|
+
threadId: threadId,
|
|
377
|
+
isActive: false
|
|
378
|
+
});
|
|
379
|
+
```
|
|
709
380
|
|
|
710
|
-
|
|
711
|
-
// ['iPhone 16 Pro Max', 'iPhone 16 Pro', 'iPhone 16 Plus', ...]
|
|
381
|
+
#### Mark Visual Message as Seen (disappearing media)
|
|
712
382
|
|
|
713
|
-
|
|
714
|
-
|
|
383
|
+
```javascript
|
|
384
|
+
await realtime.directCommands.markVisualMessageSeen({
|
|
385
|
+
threadId: threadId,
|
|
386
|
+
itemId: messageId
|
|
387
|
+
});
|
|
715
388
|
```
|
|
716
389
|
|
|
717
|
-
|
|
390
|
+
---
|
|
718
391
|
|
|
719
|
-
|
|
720
|
-
const ig = new IgApiClient();
|
|
392
|
+
### Thread Management
|
|
721
393
|
|
|
722
|
-
|
|
723
|
-
ig.state.useAndroidDevice('Samsung Galaxy S25 Ultra');
|
|
724
|
-
console.log('Platform:', ig.state.platform); // 'android'
|
|
394
|
+
#### Add Member to Thread
|
|
725
395
|
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
console.log('Platform:', ig.state.platform); // 'ios'
|
|
396
|
+
```javascript
|
|
397
|
+
await realtime.directCommands.addMemberToThread(threadId, userId);
|
|
729
398
|
|
|
730
|
-
//
|
|
731
|
-
|
|
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
|
-
|
|
403
|
+
#### Remove Member from Thread
|
|
736
404
|
|
|
737
405
|
```javascript
|
|
738
|
-
|
|
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
|
-
|
|
409
|
+
#### Leave Thread
|
|
753
410
|
|
|
754
411
|
```javascript
|
|
755
|
-
|
|
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
|
-
|
|
415
|
+
#### Update Thread Title
|
|
774
416
|
|
|
775
417
|
```javascript
|
|
776
|
-
|
|
418
|
+
await realtime.directCommands.updateThreadTitle(threadId, 'New Group Name');
|
|
419
|
+
```
|
|
777
420
|
|
|
778
|
-
|
|
779
|
-
const authState = await useMultiFileAuthState('./auth_info_instagram');
|
|
780
|
-
const ig = new IgApiClient();
|
|
421
|
+
#### Mute Thread
|
|
781
422
|
|
|
782
|
-
|
|
783
|
-
|
|
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
|
-
|
|
789
|
-
|
|
790
|
-
|
|
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
|
-
|
|
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
|
-
|
|
809
|
-
|
|
810
|
-
|
|
432
|
+
```javascript
|
|
433
|
+
await realtime.directCommands.unmuteThread(threadId);
|
|
434
|
+
```
|
|
811
435
|
|
|
812
|
-
|
|
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
|
-
|
|
819
|
-
|
|
820
|
-
console.log('iOS Bot is online with', ig.state.iosDeviceName);
|
|
438
|
+
### Message Requests
|
|
821
439
|
|
|
822
|
-
|
|
823
|
-
console.log('New DM:', data.message?.text);
|
|
824
|
-
});
|
|
825
|
-
}
|
|
440
|
+
#### Approve Pending Thread
|
|
826
441
|
|
|
827
|
-
|
|
442
|
+
```javascript
|
|
443
|
+
await realtime.directCommands.approveThread(threadId);
|
|
828
444
|
```
|
|
829
445
|
|
|
830
|
-
|
|
446
|
+
#### Decline Pending Thread
|
|
831
447
|
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
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
|
-
|
|
454
|
+
### Moderation
|
|
849
455
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
#### 1. Send Text Message
|
|
456
|
+
#### Block User in Thread
|
|
853
457
|
|
|
854
458
|
```javascript
|
|
855
|
-
await realtime.directCommands.
|
|
459
|
+
await realtime.directCommands.blockUserInThread(threadId, userId);
|
|
856
460
|
```
|
|
857
461
|
|
|
858
|
-
####
|
|
462
|
+
#### Report Thread
|
|
859
463
|
|
|
860
464
|
```javascript
|
|
861
|
-
await realtime.directCommands.
|
|
465
|
+
await realtime.directCommands.reportThread(threadId, 'spam');
|
|
862
466
|
```
|
|
863
467
|
|
|
864
|
-
|
|
468
|
+
---
|
|
865
469
|
|
|
866
|
-
|
|
867
|
-
await realtime.directCommands.editMessage(threadId, messageId, 'Updated text');
|
|
868
|
-
```
|
|
470
|
+
### Disappearing Media (View-Once)
|
|
869
471
|
|
|
870
|
-
####
|
|
472
|
+
#### Send Disappearing Photo
|
|
871
473
|
|
|
872
474
|
```javascript
|
|
873
|
-
await realtime.directCommands.
|
|
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
|
-
####
|
|
482
|
+
#### Send Disappearing Video
|
|
877
483
|
|
|
878
484
|
```javascript
|
|
879
|
-
await realtime.directCommands.
|
|
485
|
+
await realtime.directCommands.sendDisappearingVideo({
|
|
880
486
|
threadId: threadId,
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
reactionType: 'like',
|
|
884
|
-
reactionStatus: 'created'
|
|
487
|
+
uploadId: 'your_upload_id',
|
|
488
|
+
viewMode: 'once'
|
|
885
489
|
});
|
|
490
|
+
```
|
|
886
491
|
|
|
887
|
-
|
|
888
|
-
|
|
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
|
-
|
|
897
|
-
|
|
898
|
-
#### 6. Send Media (Share existing Instagram post)
|
|
505
|
+
#### Send Replay Notification
|
|
899
506
|
|
|
900
507
|
```javascript
|
|
901
|
-
await realtime.directCommands.
|
|
508
|
+
await realtime.directCommands.sendReplayNotification({
|
|
902
509
|
threadId: threadId,
|
|
903
|
-
|
|
904
|
-
text: 'Optional caption'
|
|
510
|
+
itemId: messageId
|
|
905
511
|
});
|
|
906
512
|
```
|
|
907
513
|
|
|
908
|
-
|
|
514
|
+
---
|
|
515
|
+
|
|
516
|
+
### Media Upload (HTTP + Broadcast)
|
|
909
517
|
|
|
910
|
-
|
|
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
|
-
|
|
923
|
-
|
|
528
|
+
photoBuffer: photoBuffer,
|
|
529
|
+
caption: 'Check this out',
|
|
530
|
+
mimeType: 'image/jpeg'
|
|
924
531
|
});
|
|
925
532
|
```
|
|
926
533
|
|
|
927
|
-
|
|
534
|
+
#### Send Video
|
|
928
535
|
|
|
929
536
|
```javascript
|
|
930
|
-
const
|
|
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
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
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
|
-
|
|
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
|
-
|
|
552
|
+
### Foreground State (Connection Keepalive)
|
|
1058
553
|
|
|
1059
554
|
```javascript
|
|
1060
|
-
await realtime.directCommands.
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
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
|
-
##
|
|
564
|
+
## Complete Method Reference Table
|
|
1084
565
|
|
|
1085
|
-
|
|
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
|
-
|
|
605
|
+
---
|
|
1088
606
|
|
|
1089
|
-
|
|
607
|
+
## Download Media from Messages
|
|
1090
608
|
|
|
1091
|
-
|
|
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
|
-
|
|
624
|
+
const stream = await downloadContentFromMessage(msg);
|
|
1118
625
|
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
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
|
-
|
|
1144
|
-
|
|
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
|
|
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
|
-
###
|
|
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
|
|
1328
|
-
| `downloadMediaBuffer(message
|
|
1329
|
-
| `extractMediaUrls(message)` |
|
|
1330
|
-
| `hasMedia(message)` | Check if
|
|
1331
|
-
| `getMediaType(message)` | Get media type
|
|
1332
|
-
| `isViewOnceMedia(message)` | Check if
|
|
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
|
-
###
|
|
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('
|
|
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
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
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
|
-
###
|
|
706
|
+
### Smart Bot with Reactions and Typing
|
|
1425
707
|
|
|
1426
708
|
```javascript
|
|
1427
|
-
|
|
1428
|
-
const
|
|
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
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
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
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
719
|
+
// Show typing
|
|
720
|
+
await realtime.directCommands.indicateActivity({
|
|
721
|
+
threadId: msg.thread_id,
|
|
722
|
+
isActive: true
|
|
723
|
+
});
|
|
1449
724
|
|
|
1450
|
-
|
|
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
|
-
|
|
1457
|
-
await realtime.directCommands.
|
|
727
|
+
if (msg.text.toLowerCase().includes('hi')) {
|
|
728
|
+
await realtime.directCommands.sendText({
|
|
1458
729
|
threadId: msg.thread_id,
|
|
1459
|
-
|
|
730
|
+
text: 'Hello there!'
|
|
1460
731
|
});
|
|
1461
732
|
|
|
1462
|
-
//
|
|
1463
|
-
await
|
|
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
|
-
|
|
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
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
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
|
-
|
|
1667
|
-
|
|
1668
|
-
Instagram DM does not provide a simple "attach arbitrary file" endpoint. Common workarounds:
|
|
751
|
+
## Session Management
|
|
1669
752
|
|
|
1670
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
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
|
|
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
|
-
|
|
792
|
+
### Direct Messages (REST)
|
|
1716
793
|
|
|
1717
794
|
```javascript
|
|
1718
|
-
// Get inbox
|
|
795
|
+
// Get inbox
|
|
1719
796
|
const inbox = await ig.direct.getInbox();
|
|
1720
797
|
|
|
1721
|
-
// Get
|
|
798
|
+
// Get thread
|
|
1722
799
|
const thread = await ig.direct.getThread(threadId);
|
|
1723
800
|
|
|
1724
|
-
// Send text
|
|
1725
|
-
await ig.direct.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1752
|
-
await realtime.connectFromSavedSession(authState);
|
|
1753
|
-
```
|
|
819
|
+
## Important Notes
|
|
1754
820
|
|
|
1755
|
-
|
|
821
|
+
### Media Uploads
|
|
1756
822
|
|
|
1757
|
-
|
|
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
|
-
|
|
1762
|
-
await realtime.directCommands.deleteMessage(threadId, messageId);
|
|
825
|
+
### Rate Limiting
|
|
1763
826
|
|
|
1764
|
-
|
|
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
|
-
|
|
1768
|
-
await realtime.directCommands.replyToMessage(threadId, messageId, 'Reply');
|
|
829
|
+
### Session Persistence
|
|
1769
830
|
|
|
1770
|
-
|
|
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
|
-
|
|
1776
|
-
await realtime.directCommands.sendMedia({ threadId, mediaId });
|
|
833
|
+
---
|
|
1777
834
|
|
|
1778
|
-
|
|
1779
|
-
await realtime.directCommands.sendLocation({ threadId, locationId });
|
|
835
|
+
## Changelog
|
|
1780
836
|
|
|
1781
|
-
|
|
1782
|
-
|
|
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
|
-
|
|
1785
|
-
|
|
842
|
+
### v5.60.8
|
|
843
|
+
- downloadContentFromMessage() - Baileys-style media download
|
|
844
|
+
- View-once media extraction support
|
|
845
|
+
- downloadMediaBuffer() and extractMediaUrls()
|
|
1786
846
|
|
|
1787
|
-
|
|
1788
|
-
|
|
847
|
+
### v5.60.7
|
|
848
|
+
- Custom Device Emulation with 12 preset devices
|
|
849
|
+
- setCustomDevice() and usePresetDevice() methods
|
|
1789
850
|
|
|
1790
|
-
|
|
1791
|
-
|
|
851
|
+
### v5.60.3
|
|
852
|
+
- sendPhoto() and sendVideo() for media uploads
|
|
1792
853
|
|
|
1793
|
-
|
|
1794
|
-
|
|
854
|
+
### v5.60.2
|
|
855
|
+
- useMultiFileAuthState() - Baileys-style session persistence
|
|
856
|
+
- connectFromSavedSession() method
|
|
1795
857
|
|
|
1796
|
-
|
|
1797
|
-
|
|
858
|
+
### v5.60.0
|
|
859
|
+
- Full MQTT integration with EnhancedDirectCommands
|
|
860
|
+
- Real-time messaging with <500ms latency
|
|
1798
861
|
|
|
1799
|
-
|
|
1800
|
-
await realtime.directCommands.subscribeToFollowNotifications();
|
|
862
|
+
---
|
|
1801
863
|
|
|
1802
|
-
|
|
1803
|
-
await realtime.directCommands.subscribeToMentionNotifications();
|
|
864
|
+
## License
|
|
1804
865
|
|
|
1805
|
-
|
|
1806
|
-
await realtime.directCommands.subscribeToCallNotifications();
|
|
866
|
+
MIT
|
|
1807
867
|
|
|
1808
|
-
|
|
1809
|
-
await realtime.directCommands.addMemberToThread(threadId, userId);
|
|
868
|
+
## Support
|
|
1810
869
|
|
|
1811
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1827
|
-
|
|
1828
|
-
realtime.on('disconnected', () => console.log('Disconnected'));
|
|
875
|
+
---
|
|
876
|
+
## Enhanced Location Usage — Practical Examples (English)
|
|
1829
877
|
|
|
1830
|
-
|
|
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
|
-
|
|
1836
|
-
realtime
|
|
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
|
-
###
|
|
886
|
+
### 1) Send a location when you already have a `venue` object (recommended)
|
|
1840
887
|
|
|
1841
888
|
```javascript
|
|
1842
|
-
//
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
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
|
-
|
|
1852
|
-
|
|
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
|
-
|
|
915
|
+
### 2) Search for a place (instagram private search) and send it
|
|
1858
916
|
|
|
1859
|
-
|
|
917
|
+
Use `searchAndSendLocation()` when you only have a search query or coordinates:
|
|
1860
918
|
|
|
1861
919
|
```javascript
|
|
1862
|
-
realtime.
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
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
|
-
|
|
932
|
+
### 3) Build a location sticker manually & publish story (advanced)
|
|
1891
933
|
|
|
1892
|
-
|
|
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
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
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
|
-
//
|
|
1907
|
-
|
|
1908
|
-
await authState.saveMqttSession(realtime);
|
|
1909
|
-
```
|
|
946
|
+
// create sticker compatible with publish.story helpers
|
|
947
|
+
const sticker = realtime.directCommands.createLocationStickerFromVenue(venue);
|
|
1910
948
|
|
|
1911
|
-
|
|
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
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
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
|
-
|
|
1927
|
-
const
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
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
|
-
|
|
979
|
+
### 4) Force sending the explore-location link (explicit fallback)
|
|
1964
980
|
|
|
1965
|
-
|
|
981
|
+
If you don't want to publish a story and only need the location link in DM:
|
|
1966
982
|
|
|
1967
983
|
```javascript
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
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
|
-
|
|
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.)
|