nodejs-insta-private-api-mqtt 1.3.14 → 1.3.16
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 +596 -30
- package/dist/core/request.js +127 -53
- package/dist/realtime/commands/enhanced.direct.commands.js +175 -218
- package/dist/repositories/direct-thread.repository.js +108 -15
- package/dist/sendmedia/sendPhoto.js +115 -66
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -262,53 +262,182 @@ startBot().catch(console.error);
|
|
|
262
262
|
|
|
263
263
|
---
|
|
264
264
|
|
|
265
|
-
## 🖼️ FIXED: Send Media via MQTT (v5.70.0)
|
|
266
265
|
|
|
267
|
-
|
|
266
|
+
## 🖼️ IMPORTANT: How media actually works (photos & videos)
|
|
268
267
|
|
|
269
|
-
|
|
268
|
+
## 🖼️ IMPORTANT: How media actually works (photos & videos) — UPDATED (rupload + MQTT reference)
|
|
270
269
|
|
|
271
|
-
|
|
270
|
+
> **Clarification (APK + client behaviour):**
|
|
271
|
+
> 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".
|
|
272
|
+
>
|
|
273
|
+
> **Key points**
|
|
274
|
+
> - **Media bytes are uploaded over HTTP (rupload).** MQTT never carries the raw image/video bytes.
|
|
275
|
+
> - **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".
|
|
276
|
+
> - 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.
|
|
272
277
|
|
|
273
|
-
|
|
274
|
-
```javascript
|
|
275
|
-
const photoBuffer = fs.readFileSync('./image.jpg');
|
|
276
|
-
const upload = await ig.publish.photo({ file: photoBuffer });
|
|
278
|
+
---
|
|
277
279
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
280
|
+
### Correct / supported patterns
|
|
281
|
+
|
|
282
|
+
There are two practical, compatible flows you can use depending on your client helpers and what the server expects:
|
|
283
|
+
|
|
284
|
+
**A) Hybrid (rupload HTTP -> MQTT reference broadcast)** — matches what the APK does in many versions
|
|
285
|
+
1. Upload bytes to rupload_igphoto / rupload_igvideo (HTTP) -> receive `upload_id`.
|
|
286
|
+
2. Publish an MQTT `send_item` with `item_type: "media"` and `upload_id`: this tells the realtime server to attach the previously uploaded media to the thread.
|
|
287
|
+
- MQTT payload is **only the metadata** (JSON), typically compressed/deflated by the realtime client before publish.
|
|
288
|
+
- This is the recommended **APK-compatible** approach.
|
|
289
|
+
|
|
290
|
+
**B) Full HTTP (rupload HTTP -> configure/broadcast HTTP)** — always works, server-side creation via REST
|
|
291
|
+
1. Upload bytes to rupload endpoint (HTTP) -> receive `upload_id`.
|
|
292
|
+
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.
|
|
293
|
+
- Use this if you prefer to avoid relying on the realtime command to create messages.
|
|
294
|
+
|
|
295
|
+
> Summary: **Always rupload via HTTP.** Then either broadcast via HTTP (configure endpoints) or publish an MQTT `send_item` referencing `upload_id`. MQTT does not carry the media bytes, only the reference.
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
### Example: Hybrid flow — Photo (rupload HTTP then MQTT reference)
|
|
300
|
+
|
|
301
|
+
This example shows the two steps required. It assumes you have `ig.request.send` for HTTP and a `realtime.mqtt.publish()` helper for MQTT. It also shows the `compressDeflate` helper pattern used in this project.
|
|
284
302
|
|
|
285
|
-
#### 🎥 Send a Video
|
|
286
303
|
```javascript
|
|
287
|
-
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
304
|
+
// 1) RUUPLOAD (HTTP) - upload bytes and get upload_id
|
|
305
|
+
const uploadId = Date.now().toString(); // or use library helper that returns upload_id
|
|
306
|
+
const objectName = `${uploadId}_${Math.floor(Math.random()*1e9)}.jpg`;
|
|
307
|
+
|
|
308
|
+
const ruploadParams = {
|
|
309
|
+
upload_id: uploadId,
|
|
310
|
+
media_type: 1, // photo
|
|
311
|
+
image_compression: '{"lib_name":"moz","lib_version":"3.1.m","quality":"80"}',
|
|
312
|
+
xsharing_user_ids: JSON.stringify([]),
|
|
313
|
+
is_clips_media: false,
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const headers = {
|
|
317
|
+
'X-Instagram-Rupload-Params': JSON.stringify(ruploadParams),
|
|
318
|
+
'Content-Type': 'image/jpeg',
|
|
319
|
+
'X-Entity-Type': 'image/jpeg',
|
|
320
|
+
'X-Entity-Length': String(photoBuffer.length),
|
|
321
|
+
'Content-Length': String(photoBuffer.length),
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
// send bytes to /rupload_igphoto/<objectName>
|
|
325
|
+
await ig.request.send({
|
|
326
|
+
url: `/rupload_igphoto/${objectName}`,
|
|
327
|
+
method: 'POST',
|
|
328
|
+
headers,
|
|
329
|
+
body: photoBuffer,
|
|
330
|
+
timeout: 120000,
|
|
291
331
|
});
|
|
292
332
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
333
|
+
// server should now provide or accept the upload_id (uploadId)
|
|
334
|
+
|
|
335
|
+
// 2) MQTT publish - tell realtime: "attach upload_id X to thread Y"
|
|
336
|
+
const clientContext = require('uuid').v4();
|
|
337
|
+
const command = {
|
|
338
|
+
action: 'send_item',
|
|
339
|
+
thread_id: String(threadId),
|
|
340
|
+
item_type: 'media',
|
|
341
|
+
upload_id: String(uploadId),
|
|
342
|
+
text: caption || '',
|
|
343
|
+
timestamp: Date.now(),
|
|
344
|
+
client_context: clientContext
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
// compress the JSON (match client's compress method)
|
|
348
|
+
const json = JSON.stringify(command);
|
|
349
|
+
const payload = await compressDeflate(json); // project helper
|
|
350
|
+
|
|
351
|
+
// publish to MQTT send_message topic
|
|
352
|
+
await mqtt.publish({
|
|
353
|
+
topic: Topics.SEND_MESSAGE.id,
|
|
354
|
+
qosLevel: 1,
|
|
355
|
+
payload: payload
|
|
297
356
|
});
|
|
298
357
|
```
|
|
299
358
|
|
|
300
|
-
|
|
359
|
+
**Notes**
|
|
360
|
+
- This sends only a *reference* to the server. The bytes were already sent via rupload.
|
|
361
|
+
- The APK calls something equivalent to `RuploadClient.upload()` then `SendMessage.sendMedia(upload_id)` — the second call is an MQTT notification that references `upload_id`.
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
### Example: Hybrid flow — Video (rupload HTTP then MQTT reference)
|
|
366
|
+
|
|
301
367
|
```javascript
|
|
302
|
-
|
|
303
|
-
const
|
|
368
|
+
// 1) RUUPLOAD (video)
|
|
369
|
+
const uploadId = Date.now().toString();
|
|
370
|
+
const objectName = `${uploadId}_${Math.floor(Math.random()*1e9)}.mp4`;
|
|
371
|
+
|
|
372
|
+
const ruploadParams = {
|
|
373
|
+
upload_id: uploadId,
|
|
374
|
+
media_type: 2, // video
|
|
375
|
+
xsharing_user_ids: JSON.stringify([]),
|
|
376
|
+
upload_media_duration_ms: Math.round(duration * 1000),
|
|
377
|
+
upload_media_width: width,
|
|
378
|
+
upload_media_height: height
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
const headers = {
|
|
382
|
+
'X-Instagram-Rupload-Params': JSON.stringify(ruploadParams),
|
|
383
|
+
'Content-Type': 'video/mp4',
|
|
384
|
+
'X-Entity-Type': 'video/mp4',
|
|
385
|
+
'X-Entity-Length': String(videoBuffer.length),
|
|
386
|
+
'Content-Length': String(videoBuffer.length),
|
|
387
|
+
'Offset': '0'
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
await ig.request.send({
|
|
391
|
+
url: `/rupload_igvideo/${objectName}`,
|
|
392
|
+
method: 'POST',
|
|
393
|
+
headers,
|
|
394
|
+
body: videoBuffer,
|
|
395
|
+
timeout: 300000
|
|
396
|
+
});
|
|
304
397
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
398
|
+
// 2) MQTT publish - notify realtime to attach upload_id to thread
|
|
399
|
+
const command = {
|
|
400
|
+
action: 'send_item',
|
|
401
|
+
thread_id: String(threadId),
|
|
402
|
+
item_type: 'media',
|
|
403
|
+
upload_id: String(uploadId),
|
|
404
|
+
text: caption || '',
|
|
405
|
+
timestamp: Date.now(),
|
|
406
|
+
client_context: require('uuid').v4()
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
const payload = await compressDeflate(JSON.stringify(command));
|
|
410
|
+
await mqtt.publish({
|
|
411
|
+
topic: Topics.SEND_MESSAGE.id,
|
|
412
|
+
qosLevel: 1,
|
|
413
|
+
payload: payload
|
|
309
414
|
});
|
|
310
415
|
```
|
|
311
416
|
|
|
417
|
+
---
|
|
418
|
+
|
|
419
|
+
### Short FAQ & Troubleshooting
|
|
420
|
+
|
|
421
|
+
- **Q:** *Can I send the raw image through MQTT?*
|
|
422
|
+
**A:** No — MQTT is not used for large raw bytes. The app uploads bytes via rupload HTTP.
|
|
423
|
+
|
|
424
|
+
- **Q:** *If I publish the MQTT `send_item` with `upload_id`, will the message appear in the thread?*
|
|
425
|
+
**A:** Yes — the realtime server accepts an MQTT `send_item` referencing a valid `upload_id`. The server will attempt to create/attach the media message and then propagate the message via MESSAGE_SYNC to other clients. The exact behavior can differ between server versions, so keep fallback to HTTP broadcast if reliability is essential.
|
|
426
|
+
|
|
427
|
+
- **Q:** *Do I need to call `/direct_v2/threads/broadcast/...` if I published MQTT?*
|
|
428
|
+
**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.
|
|
429
|
+
|
|
430
|
+
---
|
|
431
|
+
|
|
432
|
+
### Security & Best Practices
|
|
433
|
+
|
|
434
|
+
- Ensure `upload_id` you publish via MQTT is the one returned by rupload; otherwise servers will reject or ignore it.
|
|
435
|
+
- Keep rupload headers accurate (`X-Entity-Length`, `X-Instagram-Rupload-Params`, etc.).
|
|
436
|
+
- Use `client_context` (UUID) per message to track mutations.
|
|
437
|
+
- Implement retries and exponential backoff for both rupload and MQTT publish.
|
|
438
|
+
- Log both the rupload response and the MQTT publish result for debugging.
|
|
439
|
+
|
|
440
|
+
---
|
|
312
441
|
|
|
313
442
|
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.
|
|
314
443
|
|
|
@@ -1359,6 +1488,205 @@ const fs = require('fs');
|
|
|
1359
1488
|
|
|
1360
1489
|
---
|
|
1361
1490
|
|
|
1491
|
+
|
|
1492
|
+
---
|
|
1493
|
+
|
|
1494
|
+
### Example 3: CLI Photo Sender (upload HTTP + broadcast HTTP, with MQTT sync)
|
|
1495
|
+
|
|
1496
|
+
This example is a ready-to-run CLI script that demonstrates the correct flow to **send a photo** to one or more threads:
|
|
1497
|
+
1. upload the photo to Instagram's rupload endpoint (HTTP),
|
|
1498
|
+
2. broadcast (configure) the photo to the target thread(s) using the Direct API (HTTP),
|
|
1499
|
+
3. use MQTT (RealtimeClient) only for realtime sync and confirmation.
|
|
1500
|
+
|
|
1501
|
+
Save this as `examples/send-photo-cli.js`.
|
|
1502
|
+
|
|
1503
|
+
```javascript
|
|
1504
|
+
#!/usr/bin/env node
|
|
1505
|
+
// examples/send-photo-cli.js
|
|
1506
|
+
// Node 18+, install nodejs-insta-private-api-mqtt in the project
|
|
1507
|
+
|
|
1508
|
+
const fs = require('fs');
|
|
1509
|
+
const path = require('path');
|
|
1510
|
+
const readline = require('readline/promises');
|
|
1511
|
+
const { stdin: input, stdout: output } = require('process');
|
|
1512
|
+
const { v4: uuidv4 } = require('uuid');
|
|
1513
|
+
|
|
1514
|
+
(async () => {
|
|
1515
|
+
const rl = readline.createInterface({ input, output });
|
|
1516
|
+
try {
|
|
1517
|
+
console.log('\\n--- Instagram Photo Sender (HTTP + broadcast) ---\\n');
|
|
1518
|
+
const username = (await rl.question('Enter your Instagram username: ')).trim();
|
|
1519
|
+
const password = (await rl.question('Enter your Instagram password: ')).trim();
|
|
1520
|
+
if (!username || !password) {
|
|
1521
|
+
console.error('username & password required'); process.exit(1);
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
const { IgApiClient, RealtimeClient, useMultiFileAuthState } = require('nodejs-insta-private-api-mqtt');
|
|
1525
|
+
const AUTH_FOLDER = path.resolve(process.cwd(), './auth_info_instagram');
|
|
1526
|
+
|
|
1527
|
+
const authState = await useMultiFileAuthState(AUTH_FOLDER);
|
|
1528
|
+
const ig = new IgApiClient();
|
|
1529
|
+
ig.state.generateDevice?.(username);
|
|
1530
|
+
|
|
1531
|
+
// load saved credentials if available
|
|
1532
|
+
if (authState.hasSession && authState.hasSession()) {
|
|
1533
|
+
try { await authState.loadCreds?.(ig); console.log('[*] Loaded saved creds'); } catch(e){}
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
// login if needed
|
|
1537
|
+
if (!authState.hasSession || (typeof authState.hasSession === 'function' && !authState.hasSession())) {
|
|
1538
|
+
await ig.login({ username, password });
|
|
1539
|
+
await authState.saveCreds?.(ig);
|
|
1540
|
+
console.log('[+] Logged in and saved creds');
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
// start realtime client (optional, helps sync)
|
|
1544
|
+
const realtime = new RealtimeClient(ig);
|
|
1545
|
+
realtime.on?.('connected', () => console.log('✅ MQTT connected (realtime)'));
|
|
1546
|
+
|
|
1547
|
+
// fetch inbox and list threads
|
|
1548
|
+
const inbox = await ig.direct.getInbox();
|
|
1549
|
+
const threads = inbox.threads || inbox.items || inbox.threads_response?.threads || [];
|
|
1550
|
+
if (!threads || threads.length === 0) {
|
|
1551
|
+
console.error('No threads found in inbox. Exiting.'); process.exit(1);
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
console.log('\\nAvailable threads:');
|
|
1555
|
+
const indexToThread = [];
|
|
1556
|
+
threads.slice(0, 50).forEach((t, i) => {
|
|
1557
|
+
const id = String(t.thread_id || t.threadId || t.id || t.thread_id_str || (t.thread && t.thread.thread_id) || '');
|
|
1558
|
+
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';
|
|
1559
|
+
indexToThread.push({ id, title });
|
|
1560
|
+
console.log(`${i+1}. ${title} (thread_id: ${id})`);
|
|
1561
|
+
});
|
|
1562
|
+
|
|
1563
|
+
const pick = (await rl.question('\\nSelect thread numbers (e.g. "1 2 3"): ')).trim();
|
|
1564
|
+
const picks = pick.split(/[\s,]+/).map(x => parseInt(x,10)).filter(n => Number.isFinite(n) && n >= 1 && n <= indexToThread.length);
|
|
1565
|
+
const selected = picks.map(p => indexToThread[p-1].id);
|
|
1566
|
+
|
|
1567
|
+
const photoPath = (await rl.question('\\nEnter your Photo path here (absolute or relative): ')).trim();
|
|
1568
|
+
const resolved = path.isAbsolute(photoPath) ? photoPath : path.resolve(process.cwd(), photoPath);
|
|
1569
|
+
if (!fs.existsSync(resolved)) { console.error('File not found', resolved); process.exit(1); }
|
|
1570
|
+
const buffer = fs.readFileSync(resolved);
|
|
1571
|
+
console.log('Photo size (MB):', (buffer.length/(1024*1024)).toFixed(2));
|
|
1572
|
+
|
|
1573
|
+
// RUUPLOAD (HTTP) - try to use ig.publish.photo helper if available
|
|
1574
|
+
let uploadId;
|
|
1575
|
+
try {
|
|
1576
|
+
if (typeof ig.publish?.photo === 'function') {
|
|
1577
|
+
const up = await ig.publish.photo({ file: buffer });
|
|
1578
|
+
uploadId = up.upload_id || up.uploadId || up;
|
|
1579
|
+
} else {
|
|
1580
|
+
// manual rupload via request
|
|
1581
|
+
uploadId = Date.now().toString();
|
|
1582
|
+
const objectName = `${uploadId}_0_${Math.floor(Math.random() * 1e10)}`;
|
|
1583
|
+
const ruploadParams = {
|
|
1584
|
+
retry_context: JSON.stringify({ num_step_auto_retry: 0, num_reupload: 0, num_step_manual_retry: 0 }),
|
|
1585
|
+
media_type: '1',
|
|
1586
|
+
upload_id: uploadId,
|
|
1587
|
+
xsharing_user_ids: JSON.stringify([]),
|
|
1588
|
+
image_compression: JSON.stringify({ lib_name: 'moz', lib_version: '3.1.m', quality: '80' })
|
|
1589
|
+
};
|
|
1590
|
+
const headers = {
|
|
1591
|
+
'X-Instagram-Rupload-Params': JSON.stringify(ruploadParams),
|
|
1592
|
+
'Content-Type': 'application/octet-stream',
|
|
1593
|
+
'X-Entity-Type': 'image/jpeg',
|
|
1594
|
+
'Offset': '0',
|
|
1595
|
+
'X-Entity-Name': objectName,
|
|
1596
|
+
'X-Entity-Length': String(buffer.length),
|
|
1597
|
+
'Content-Length': String(buffer.length),
|
|
1598
|
+
};
|
|
1599
|
+
await ig.request.send({
|
|
1600
|
+
url: `/rupload_igphoto/${objectName}`,
|
|
1601
|
+
method: 'POST',
|
|
1602
|
+
headers,
|
|
1603
|
+
body: buffer,
|
|
1604
|
+
timeout: 120000,
|
|
1605
|
+
});
|
|
1606
|
+
}
|
|
1607
|
+
console.log('[+] Upload completed. upload_id =', uploadId);
|
|
1608
|
+
} catch (err) {
|
|
1609
|
+
console.error('Upload failed:', err?.message || err); process.exit(1);
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
// BROADCAST (HTTP) - create message on server
|
|
1613
|
+
try {
|
|
1614
|
+
const mutationToken = uuidv4();
|
|
1615
|
+
const payload = {
|
|
1616
|
+
action: 'configure_photo',
|
|
1617
|
+
upload_id: String(uploadId),
|
|
1618
|
+
thread_ids: JSON.stringify(selected),
|
|
1619
|
+
client_context: mutationToken,
|
|
1620
|
+
mutation_token: mutationToken,
|
|
1621
|
+
_csrftoken: ig.state.cookieCsrfToken || '',
|
|
1622
|
+
_uuid: ig.state.uuid || '',
|
|
1623
|
+
device_id: ig.state.deviceId || ''
|
|
1624
|
+
};
|
|
1625
|
+
const resp = await ig.request.send({
|
|
1626
|
+
url: '/api/v1/direct_v2/threads/broadcast/configure_photo/',
|
|
1627
|
+
method: 'POST',
|
|
1628
|
+
form: payload,
|
|
1629
|
+
timeout: 60000,
|
|
1630
|
+
});
|
|
1631
|
+
console.log('✅ Broadcast response received:', resp && (resp.status || resp.body || resp.data) ? 'OK' : JSON.stringify(resp).slice(0,120));
|
|
1632
|
+
} catch (err) {
|
|
1633
|
+
console.error('Broadcast failed:', err?.message || err);
|
|
1634
|
+
process.exit(1);
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
console.log('\\nDone. MQTT will sync and you should see the message in the app.');
|
|
1638
|
+
rl.close();
|
|
1639
|
+
process.exit(0);
|
|
1640
|
+
} catch (e) {
|
|
1641
|
+
console.error('Fatal:', e);
|
|
1642
|
+
process.exit(1);
|
|
1643
|
+
}
|
|
1644
|
+
})();
|
|
1645
|
+
```
|
|
1646
|
+
|
|
1647
|
+
---
|
|
1648
|
+
|
|
1649
|
+
### Example 4: Video Sender (upload + broadcast)
|
|
1650
|
+
|
|
1651
|
+
This is similar to the photo sender but uses the video upload flow and longer timeouts. Use `ig.publish.video()` if available; otherwise rupload manually and call `/api/v1/direct_v2/threads/broadcast/configure_video/`.
|
|
1652
|
+
|
|
1653
|
+
```javascript
|
|
1654
|
+
// pseudo-code outline
|
|
1655
|
+
const videoBuffer = fs.readFileSync('./video.mp4');
|
|
1656
|
+
const upload = await ig.publish.video({ video: videoBuffer, duration: 12000 });
|
|
1657
|
+
// upload.upload_id -> then broadcast with configure_video payload similar to the photo flow
|
|
1658
|
+
```
|
|
1659
|
+
|
|
1660
|
+
**Notes:** video uploads often require multipart/segmented upload and longer timeouts; prefer library helper `ig.publish.video()`.
|
|
1661
|
+
|
|
1662
|
+
---
|
|
1663
|
+
|
|
1664
|
+
### Example 5: Share a file / document (workaround)
|
|
1665
|
+
|
|
1666
|
+
Instagram DM does not provide a simple "attach arbitrary file" endpoint. Common workarounds:
|
|
1667
|
+
|
|
1668
|
+
1. Upload the file to a hosting service (S3, Dropbox, your own server), then send the link as a text message via MQTT (or HTTP direct send).
|
|
1669
|
+
2. If the file is an image or video, use the rupload + broadcast flow above.
|
|
1670
|
+
|
|
1671
|
+
Example (send link message):
|
|
1672
|
+
|
|
1673
|
+
```javascript
|
|
1674
|
+
// send link as text via realtime (MQTT)
|
|
1675
|
+
await realtime.directCommands.sendTextViaRealtime(threadId, 'Download the file here: https://myhost.example.com/myfile.pdf');
|
|
1676
|
+
```
|
|
1677
|
+
|
|
1678
|
+
---
|
|
1679
|
+
|
|
1680
|
+
### Tips for bots sending media
|
|
1681
|
+
|
|
1682
|
+
- Always **upload first**, then **broadcast**. Do not rely on MQTT-only calls for media.
|
|
1683
|
+
- Use retries and exponential backoff for both rupload and broadcast steps (Instagram can return transient 503).
|
|
1684
|
+
- Save `upload_id` and broadcast responses for debugging.
|
|
1685
|
+
- If you need to send the same file to many threads, upload once, reuse the same `upload_id` in multiple broadcast calls.
|
|
1686
|
+
|
|
1687
|
+
---
|
|
1688
|
+
|
|
1689
|
+
|
|
1362
1690
|
## API Reference
|
|
1363
1691
|
|
|
1364
1692
|
### IgApiClient
|
|
@@ -1710,3 +2038,241 @@ For issues, bugs, or feature requests: https://github.com/Kunboruto20/nodejs-ins
|
|
|
1710
2038
|
Documentation: https://github.com/Kunboruto20/nodejs-insta-private-api
|
|
1711
2039
|
|
|
1712
2040
|
Examples: See repository examples/ directory for working implementations
|
|
2041
|
+
|
|
2042
|
+
|
|
2043
|
+
---
|
|
2044
|
+
|
|
2045
|
+
## Examples using `ig.request.send` wrapper (photo & video, MQTT *reference* + REST broadcast)
|
|
2046
|
+
|
|
2047
|
+
Below are practical code snippets that use the `ig.request.send` wrapper (the same request helper used inside the Instagram client).
|
|
2048
|
+
Each example shows **two flows** so you can pick the behavior you want:
|
|
2049
|
+
|
|
2050
|
+
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.
|
|
2051
|
+
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).
|
|
2052
|
+
|
|
2053
|
+
> Note: these examples assume your runtime has access to:
|
|
2054
|
+
> - `ig` (Instagram client wrapper) and `ig.request.send` method,
|
|
2055
|
+
> - `mqtt` connected client exposing `.publish({ topic, qosLevel, payload })`,
|
|
2056
|
+
> - `compressDeflate` helper (same as in project `shared`),
|
|
2057
|
+
> - `uuid` (for `client_context` generation), and
|
|
2058
|
+
> - `constants.Topics.SEND_MESSAGE.id` for the MQTT send topic.
|
|
2059
|
+
|
|
2060
|
+
### Photo — MQTT reference example (rupload via `ig.request.send` then MQTT reference)
|
|
2061
|
+
```javascript
|
|
2062
|
+
// Photo upload -> get upload_id (rupload) -> publish MQTT 'send_item' with upload_id
|
|
2063
|
+
const { v4: uuidv4 } = require('uuid');
|
|
2064
|
+
|
|
2065
|
+
async function sendPhotoAsMqttReference({ ig, mqtt, photoBuffer, threadId, caption = '' }) {
|
|
2066
|
+
if (!ig || !ig.request) throw new Error('ig.request not available');
|
|
2067
|
+
if (!mqtt || typeof mqtt.publish !== 'function') throw new Error('mqtt client not available');
|
|
2068
|
+
if (!Buffer.isBuffer(photoBuffer)) throw new Error('photoBuffer must be a Buffer');
|
|
2069
|
+
|
|
2070
|
+
// 1) perform rupload via ig.request.send
|
|
2071
|
+
const uploadId = Date.now().toString();
|
|
2072
|
+
const objectName = `${uuidv4()}.jpg`;
|
|
2073
|
+
const ruploadParams = {
|
|
2074
|
+
upload_id: uploadId,
|
|
2075
|
+
media_type: 1,
|
|
2076
|
+
image_compression: '{"lib_name":"moz","lib_version":"3.1.m","quality":"80"}',
|
|
2077
|
+
xsharing_user_ids: JSON.stringify([]),
|
|
2078
|
+
};
|
|
2079
|
+
|
|
2080
|
+
const uploadHeaders = {
|
|
2081
|
+
'X-Instagram-Rupload-Params': JSON.stringify(ruploadParams),
|
|
2082
|
+
'Content-Type': 'image/jpeg',
|
|
2083
|
+
'X-Entity-Type': 'image/jpeg',
|
|
2084
|
+
'X-Entity-Length': String(photoBuffer.length),
|
|
2085
|
+
'Content-Length': String(photoBuffer.length),
|
|
2086
|
+
};
|
|
2087
|
+
|
|
2088
|
+
const uploadUrl = `/rupload_igphoto/${objectName}`;
|
|
2089
|
+
|
|
2090
|
+
const uploadResponse = await ig.request.send({
|
|
2091
|
+
url: uploadUrl,
|
|
2092
|
+
method: 'POST',
|
|
2093
|
+
headers: uploadHeaders,
|
|
2094
|
+
body: photoBuffer,
|
|
2095
|
+
});
|
|
2096
|
+
|
|
2097
|
+
const serverUploadId = (uploadResponse && uploadResponse.upload_id) ? uploadResponse.upload_id : uploadId;
|
|
2098
|
+
|
|
2099
|
+
// 2) publish MQTT reference telling server "attach upload_id X to thread Y"
|
|
2100
|
+
const clientContext = uuidv4();
|
|
2101
|
+
const command = {
|
|
2102
|
+
action: 'send_item',
|
|
2103
|
+
thread_id: String(threadId),
|
|
2104
|
+
item_type: 'media',
|
|
2105
|
+
upload_id: serverUploadId,
|
|
2106
|
+
text: caption || '',
|
|
2107
|
+
client_context: clientContext,
|
|
2108
|
+
timestamp: Date.now(),
|
|
2109
|
+
};
|
|
2110
|
+
|
|
2111
|
+
const json = JSON.stringify(command);
|
|
2112
|
+
const payload = await compressDeflate(json); // reuse shared helper
|
|
2113
|
+
await mqtt.publish({
|
|
2114
|
+
topic: constants.Topics.SEND_MESSAGE.id,
|
|
2115
|
+
qosLevel: 1,
|
|
2116
|
+
payload,
|
|
2117
|
+
});
|
|
2118
|
+
|
|
2119
|
+
return { upload_id: serverUploadId };
|
|
2120
|
+
}
|
|
2121
|
+
```
|
|
2122
|
+
|
|
2123
|
+
### Photo — REST broadcast example (rupload via `ig.request.send` then direct_v2 broadcast)
|
|
2124
|
+
```javascript
|
|
2125
|
+
// Photo upload -> rupload via ig.request.send -> REST broadcast to the thread
|
|
2126
|
+
async function sendPhotoViaRestBroadcast({ ig, photoBuffer, threadId, caption = '' }) {
|
|
2127
|
+
const uploadId = Date.now().toString();
|
|
2128
|
+
const objectName = `${uuidv4()}.jpg`;
|
|
2129
|
+
|
|
2130
|
+
const ruploadParams = {
|
|
2131
|
+
upload_id: uploadId,
|
|
2132
|
+
media_type: 1,
|
|
2133
|
+
image_compression: '{"lib_name":"moz","lib_version":"3.1.m","quality":"80"}',
|
|
2134
|
+
xsharing_user_ids: JSON.stringify([]),
|
|
2135
|
+
};
|
|
2136
|
+
|
|
2137
|
+
const uploadResponse = await ig.request.send({
|
|
2138
|
+
url: `/rupload_igphoto/${objectName}`,
|
|
2139
|
+
method: 'POST',
|
|
2140
|
+
headers: {
|
|
2141
|
+
'X-Instagram-Rupload-Params': JSON.stringify(ruploadParams),
|
|
2142
|
+
'Content-Type': 'image/jpeg',
|
|
2143
|
+
'X-Entity-Type': 'image/jpeg',
|
|
2144
|
+
'X-Entity-Length': String(photoBuffer.length),
|
|
2145
|
+
'Content-Length': String(photoBuffer.length),
|
|
2146
|
+
},
|
|
2147
|
+
body: photoBuffer,
|
|
2148
|
+
});
|
|
2149
|
+
|
|
2150
|
+
const serverUploadId = (uploadResponse && uploadResponse.upload_id) ? uploadResponse.upload_id : uploadId;
|
|
2151
|
+
|
|
2152
|
+
// Broadcast attach via REST endpoint
|
|
2153
|
+
const broadcastResponse = await ig.request.send({
|
|
2154
|
+
url: '/direct_v2/threads/broadcast/upload_photo/',
|
|
2155
|
+
method: 'POST',
|
|
2156
|
+
form: {
|
|
2157
|
+
upload_id: serverUploadId,
|
|
2158
|
+
action: 'send_item',
|
|
2159
|
+
thread_ids: JSON.stringify([String(threadId)]),
|
|
2160
|
+
caption: caption || '',
|
|
2161
|
+
},
|
|
2162
|
+
});
|
|
2163
|
+
|
|
2164
|
+
return { upload_id: serverUploadId, broadcastResponse };
|
|
2165
|
+
}
|
|
2166
|
+
```
|
|
2167
|
+
|
|
2168
|
+
### Video — MQTT reference example (rupload video + MQTT reference)
|
|
2169
|
+
```javascript
|
|
2170
|
+
// Video upload -> rupload video via ig.request.send -> MQTT reference
|
|
2171
|
+
async function sendVideoAsMqttReference({ ig, mqtt, videoBuffer, threadId, caption = '', duration = 0, width = 720, height = 1280 }) {
|
|
2172
|
+
const uploadId = Date.now().toString();
|
|
2173
|
+
const objectName = `${uuidv4()}.mp4`;
|
|
2174
|
+
|
|
2175
|
+
const ruploadParams = {
|
|
2176
|
+
upload_id: uploadId,
|
|
2177
|
+
media_type: 2, // video
|
|
2178
|
+
xsharing_user_ids: JSON.stringify([]),
|
|
2179
|
+
upload_media_duration_ms: Math.round(duration * 1000),
|
|
2180
|
+
upload_media_width: width,
|
|
2181
|
+
upload_media_height: height,
|
|
2182
|
+
};
|
|
2183
|
+
|
|
2184
|
+
const uploadResponse = await ig.request.send({
|
|
2185
|
+
url: `/rupload_igvideo/${objectName}`,
|
|
2186
|
+
method: 'POST',
|
|
2187
|
+
headers: {
|
|
2188
|
+
'X-Instagram-Rupload-Params': JSON.stringify(ruploadParams),
|
|
2189
|
+
'Content-Type': 'video/mp4',
|
|
2190
|
+
'X-Entity-Type': 'video/mp4',
|
|
2191
|
+
'X-Entity-Length': String(videoBuffer.length),
|
|
2192
|
+
'Content-Length': String(videoBuffer.length),
|
|
2193
|
+
'Offset': '0',
|
|
2194
|
+
},
|
|
2195
|
+
body: videoBuffer,
|
|
2196
|
+
});
|
|
2197
|
+
|
|
2198
|
+
const serverUploadId = (uploadResponse && uploadResponse.upload_id) ? uploadResponse.upload_id : uploadId;
|
|
2199
|
+
|
|
2200
|
+
// Publish MQTT reference
|
|
2201
|
+
const clientContext = uuidv4();
|
|
2202
|
+
const command = {
|
|
2203
|
+
action: 'send_item',
|
|
2204
|
+
thread_id: String(threadId),
|
|
2205
|
+
item_type: 'media',
|
|
2206
|
+
upload_id: serverUploadId,
|
|
2207
|
+
text: caption || '',
|
|
2208
|
+
client_context: clientContext,
|
|
2209
|
+
timestamp: Date.now(),
|
|
2210
|
+
};
|
|
2211
|
+
|
|
2212
|
+
const json = JSON.stringify(command);
|
|
2213
|
+
const payload = await compressDeflate(json);
|
|
2214
|
+
await mqtt.publish({
|
|
2215
|
+
topic: constants.Topics.SEND_MESSAGE.id,
|
|
2216
|
+
qosLevel: 1,
|
|
2217
|
+
payload,
|
|
2218
|
+
});
|
|
2219
|
+
|
|
2220
|
+
return { upload_id: serverUploadId };
|
|
2221
|
+
}
|
|
2222
|
+
```
|
|
2223
|
+
|
|
2224
|
+
### Video — REST broadcast example (rupload video + direct_v2 broadcast)
|
|
2225
|
+
```javascript
|
|
2226
|
+
async function sendVideoViaRestBroadcast({ ig, videoBuffer, threadId, caption = '', duration = 0, width = 720, height = 1280 }) {
|
|
2227
|
+
const uploadId = Date.now().toString();
|
|
2228
|
+
const objectName = `${uuidv4()}.mp4`;
|
|
2229
|
+
|
|
2230
|
+
const ruploadParams = {
|
|
2231
|
+
upload_id: uploadId,
|
|
2232
|
+
media_type: 2,
|
|
2233
|
+
xsharing_user_ids: JSON.stringify([]),
|
|
2234
|
+
upload_media_duration_ms: Math.round(duration * 1000),
|
|
2235
|
+
upload_media_width: width,
|
|
2236
|
+
upload_media_height: height,
|
|
2237
|
+
};
|
|
2238
|
+
|
|
2239
|
+
const uploadResponse = await ig.request.send({
|
|
2240
|
+
url: `/rupload_igvideo/${objectName}`,
|
|
2241
|
+
method: 'POST',
|
|
2242
|
+
headers: {
|
|
2243
|
+
'X-Instagram-Rupload-Params': JSON.stringify(ruploadParams),
|
|
2244
|
+
'Content-Type': 'video/mp4',
|
|
2245
|
+
'X-Entity-Type': 'video/mp4',
|
|
2246
|
+
'X-Entity-Length': String(videoBuffer.length),
|
|
2247
|
+
'Content-Length': String(videoBuffer.length),
|
|
2248
|
+
'Offset': '0',
|
|
2249
|
+
},
|
|
2250
|
+
body: videoBuffer,
|
|
2251
|
+
});
|
|
2252
|
+
|
|
2253
|
+
const serverUploadId = (uploadResponse && uploadResponse.upload_id) ? uploadResponse.upload_id : uploadId;
|
|
2254
|
+
|
|
2255
|
+
const broadcastResponse = await ig.request.send({
|
|
2256
|
+
url: '/direct_v2/threads/broadcast/upload_video/',
|
|
2257
|
+
method: 'POST',
|
|
2258
|
+
form: {
|
|
2259
|
+
upload_id: serverUploadId,
|
|
2260
|
+
action: 'send_item',
|
|
2261
|
+
thread_ids: JSON.stringify([String(threadId)]),
|
|
2262
|
+
video_result: '',
|
|
2263
|
+
caption: caption || '',
|
|
2264
|
+
},
|
|
2265
|
+
});
|
|
2266
|
+
|
|
2267
|
+
return { upload_id: serverUploadId, broadcastResponse };
|
|
2268
|
+
}
|
|
2269
|
+
```
|
|
2270
|
+
|
|
2271
|
+
### FAQ notes
|
|
2272
|
+
- **Does MQTT send the file?** No — MQTT only transports the JSON reference (`upload_id`) telling the server to attach that already-uploaded file into the thread.
|
|
2273
|
+
- **Why use MQTT for reference instead of REST broadcast?** MQTT gives near-realtime messaging semantics; the server applies the `upload_id` to the thread (same result), but the heavy binary upload must be done via HTTP `rupload`.
|
|
2274
|
+
- **Can you skip rupload and only send MQTT?** No — server needs the binary available under the `upload_id`. If you send only MQTT without previously uploading, the server won't have the file to attach.
|
|
2275
|
+
|
|
2276
|
+
---
|
|
2277
|
+
|
|
2278
|
+
(Section added programmatically: `Examples using ig.request.send` — appended to the existing README content.)
|