nodejs-insta-private-api-mqtt 1.3.15 → 1.3.17
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 +395 -62
- package/dist/realtime/commands/enhanced.direct.commands.js +202 -260
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -265,102 +265,179 @@ startBot().catch(console.error);
|
|
|
265
265
|
|
|
266
266
|
## 🖼️ IMPORTANT: How media actually works (photos & videos)
|
|
267
267
|
|
|
268
|
-
|
|
268
|
+
## 🖼️ IMPORTANT: How media actually works (photos & videos) — UPDATED (rupload + MQTT reference)
|
|
269
269
|
|
|
270
|
-
|
|
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
|
-
Use the Direct API `configure_photo` (or video equivalent) endpoint to create the DM message that references the `upload_id`. This action makes the message exist on Instagram's servers and associates it with the target thread(s).
|
|
278
|
+
---
|
|
275
279
|
|
|
276
|
-
|
|
277
|
-
Once the server-side message exists, MQTT will propagate a `MESSAGE_SYNC` (or similar) event so clients receive the message in realtime. MQTT does **not** perform the storage, processing or permanent creation of media on the server.
|
|
280
|
+
### Correct / supported patterns
|
|
278
281
|
|
|
279
|
-
|
|
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.
|
|
280
289
|
|
|
281
|
-
|
|
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.
|
|
282
294
|
|
|
283
|
-
|
|
284
|
-
- Media (images, videos, view-once content) require storage, processing and metadata which are handled by HTTP endpoints. Without the HTTP broadcast step, other clients will not see the uploaded bytes even if an MQTT "send" payload is transmitted.
|
|
295
|
+
> Summary: **Always rupload via HTTP.** Then **use the HTTP configure/broadcast endpoints as the primary, reliable method** to attach media to threads. Publishing an MQTT `send_item` referencing `upload_id` may work as a best-effort fallback on some servers, but it is not guaranteed. MQTT never carries the raw media bytes, only metadata/reference.
|
|
285
296
|
|
|
286
297
|
---
|
|
287
298
|
|
|
288
|
-
###
|
|
299
|
+
### Example: Hybrid flow — Photo (rupload HTTP then MQTT reference)
|
|
289
300
|
|
|
290
|
-
|
|
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.
|
|
291
302
|
|
|
292
303
|
```javascript
|
|
293
|
-
|
|
294
|
-
const
|
|
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`;
|
|
295
307
|
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
// 2) broadcast the uploaded photo via HTTP to the thread(s)
|
|
303
|
-
const mutationToken = uuidv4();
|
|
304
|
-
const payload = {
|
|
305
|
-
action: 'configure_photo',
|
|
306
|
-
upload_id: String(upload.upload_id || upload.uploadId || upload),
|
|
307
|
-
thread_ids: JSON.stringify([threadId]), // or JSON array of thread ids
|
|
308
|
-
client_context: mutationToken,
|
|
309
|
-
mutation_token: mutationToken,
|
|
310
|
-
_csrftoken: ig.state.cookieCsrfToken || '',
|
|
311
|
-
_uuid: ig.state.uuid || '',
|
|
312
|
-
device_id: ig.state.deviceId || ''
|
|
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,
|
|
313
314
|
};
|
|
314
315
|
|
|
315
|
-
const
|
|
316
|
-
|
|
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}`,
|
|
317
327
|
method: 'POST',
|
|
318
|
-
|
|
319
|
-
|
|
328
|
+
headers,
|
|
329
|
+
body: photoBuffer,
|
|
330
|
+
timeout: 120000,
|
|
320
331
|
});
|
|
321
332
|
|
|
322
|
-
//
|
|
323
|
-
|
|
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
|
|
356
|
+
});
|
|
324
357
|
```
|
|
325
358
|
|
|
326
|
-
|
|
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)
|
|
327
366
|
|
|
328
367
|
```javascript
|
|
329
|
-
|
|
368
|
+
// 1) RUUPLOAD (video)
|
|
369
|
+
const uploadId = Date.now().toString();
|
|
370
|
+
const objectName = `${uploadId}_${Math.floor(Math.random()*1e9)}.mp4`;
|
|
330
371
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
upload_id: String(upload.upload_id || upload.uploadId || upload),
|
|
339
|
-
thread_ids: JSON.stringify([threadId]),
|
|
340
|
-
client_context: mutationToken,
|
|
341
|
-
mutation_token: mutationToken,
|
|
342
|
-
_csrftoken: ig.state.cookieCsrfToken || '',
|
|
343
|
-
_uuid: ig.state.uuid || '',
|
|
344
|
-
device_id: ig.state.deviceId || ''
|
|
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
|
|
345
379
|
};
|
|
346
380
|
|
|
347
|
-
const
|
|
348
|
-
|
|
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}`,
|
|
349
392
|
method: 'POST',
|
|
350
|
-
|
|
351
|
-
|
|
393
|
+
headers,
|
|
394
|
+
body: videoBuffer,
|
|
395
|
+
timeout: 300000
|
|
396
|
+
});
|
|
397
|
+
|
|
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
|
|
352
414
|
});
|
|
353
415
|
```
|
|
354
416
|
|
|
355
417
|
---
|
|
356
418
|
|
|
357
|
-
###
|
|
358
|
-
|
|
359
|
-
-
|
|
360
|
-
|
|
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:** 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.
|
|
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.
|
|
361
429
|
|
|
362
430
|
---
|
|
363
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
|
+
---
|
|
364
441
|
|
|
365
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.
|
|
366
443
|
|
|
@@ -1961,3 +2038,259 @@ For issues, bugs, or feature requests: https://github.com/Kunboruto20/nodejs-ins
|
|
|
1961
2038
|
Documentation: https://github.com/Kunboruto20/nodejs-insta-private-api
|
|
1962
2039
|
|
|
1963
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
|
+
// ATTACH VIA HTTP BROADCAST (preferred, reliable)
|
|
2201
|
+
// Attempt REST broadcast first (most reliable). This will create the direct message server-side
|
|
2202
|
+
// and let MQTT/clients receive the created message as normal sync.
|
|
2203
|
+
try {
|
|
2204
|
+
const broadcastResponse = await ig.request.send({
|
|
2205
|
+
url: '/direct_v2/threads/broadcast/upload_photo/',
|
|
2206
|
+
method: 'POST',
|
|
2207
|
+
form: {
|
|
2208
|
+
upload_id: serverUploadId,
|
|
2209
|
+
action: 'send_item',
|
|
2210
|
+
thread_ids: JSON.stringify([String(threadId)]),
|
|
2211
|
+
caption: caption || '',
|
|
2212
|
+
client_context: clientContext,
|
|
2213
|
+
},
|
|
2214
|
+
timeout: 60000,
|
|
2215
|
+
});
|
|
2216
|
+
return { upload_id: serverUploadId, broadcastResponse };
|
|
2217
|
+
} catch (err) {
|
|
2218
|
+
// If REST broadcast fails for transient reasons, fall back to attempting an MQTT reference publish.
|
|
2219
|
+
// NOTE: MQTT 'reference' publish is not guaranteed to be accepted on all server versions or accounts.
|
|
2220
|
+
// Keep it as a fallback only — the HTTP broadcast above is the recommended, reliable method.
|
|
2221
|
+
try {
|
|
2222
|
+
const json = JSON.stringify({
|
|
2223
|
+
action: 'send_item',
|
|
2224
|
+
thread_id: String(threadId),
|
|
2225
|
+
item_type: 'media',
|
|
2226
|
+
upload_id: serverUploadId,
|
|
2227
|
+
text: caption || '',
|
|
2228
|
+
client_context: clientContext,
|
|
2229
|
+
timestamp: Date.now(),
|
|
2230
|
+
});
|
|
2231
|
+
const payload = await compressDeflate(json);
|
|
2232
|
+
await mqtt.publish({
|
|
2233
|
+
topic: constants.Topics.SEND_MESSAGE.id,
|
|
2234
|
+
qosLevel: 1,
|
|
2235
|
+
payload,
|
|
2236
|
+
});
|
|
2237
|
+
return { upload_id: serverUploadId, mqttFallback: true };
|
|
2238
|
+
} catch (err2) {
|
|
2239
|
+
// last-resort: return upload id and both errors for debugging
|
|
2240
|
+
return { upload_id: serverUploadId, broadcastError: err?.message || String(err), mqttError: err2?.message || String(err2) };
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
```
|
|
2245
|
+
|
|
2246
|
+
### Video — REST broadcast example (rupload video + direct_v2 broadcast)
|
|
2247
|
+
```javascript
|
|
2248
|
+
async function sendVideoViaRestBroadcast({ ig, videoBuffer, threadId, caption = '', duration = 0, width = 720, height = 1280 }) {
|
|
2249
|
+
const uploadId = Date.now().toString();
|
|
2250
|
+
const objectName = `${uuidv4()}.mp4`;
|
|
2251
|
+
|
|
2252
|
+
const ruploadParams = {
|
|
2253
|
+
upload_id: uploadId,
|
|
2254
|
+
media_type: 2,
|
|
2255
|
+
xsharing_user_ids: JSON.stringify([]),
|
|
2256
|
+
upload_media_duration_ms: Math.round(duration * 1000),
|
|
2257
|
+
upload_media_width: width,
|
|
2258
|
+
upload_media_height: height,
|
|
2259
|
+
};
|
|
2260
|
+
|
|
2261
|
+
const uploadResponse = await ig.request.send({
|
|
2262
|
+
url: `/rupload_igvideo/${objectName}`,
|
|
2263
|
+
method: 'POST',
|
|
2264
|
+
headers: {
|
|
2265
|
+
'X-Instagram-Rupload-Params': JSON.stringify(ruploadParams),
|
|
2266
|
+
'Content-Type': 'video/mp4',
|
|
2267
|
+
'X-Entity-Type': 'video/mp4',
|
|
2268
|
+
'X-Entity-Length': String(videoBuffer.length),
|
|
2269
|
+
'Content-Length': String(videoBuffer.length),
|
|
2270
|
+
'Offset': '0',
|
|
2271
|
+
},
|
|
2272
|
+
body: videoBuffer,
|
|
2273
|
+
});
|
|
2274
|
+
|
|
2275
|
+
const serverUploadId = (uploadResponse && uploadResponse.upload_id) ? uploadResponse.upload_id : uploadId;
|
|
2276
|
+
|
|
2277
|
+
const broadcastResponse = await ig.request.send({
|
|
2278
|
+
url: '/direct_v2/threads/broadcast/upload_video/',
|
|
2279
|
+
method: 'POST',
|
|
2280
|
+
form: {
|
|
2281
|
+
upload_id: serverUploadId,
|
|
2282
|
+
action: 'send_item',
|
|
2283
|
+
thread_ids: JSON.stringify([String(threadId)]),
|
|
2284
|
+
video_result: '',
|
|
2285
|
+
caption: caption || '',
|
|
2286
|
+
},
|
|
2287
|
+
});
|
|
2288
|
+
|
|
2289
|
+
return { upload_id: serverUploadId, broadcastResponse };
|
|
2290
|
+
}
|
|
2291
|
+
```
|
|
2292
|
+
|
|
2293
|
+
|
|
2294
|
+
---
|
|
2295
|
+
|
|
2296
|
+
(Section added programmatically: `Examples using ig.request.send` — appended to the existing README content.)
|
|
@@ -14,6 +14,57 @@ class EnhancedDirectCommands {
|
|
|
14
14
|
this.enhancedDebug = (0, shared_1.debugChannel)('realtime', 'enhanced-commands');
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
// ---------- HELPERS ----------
|
|
18
|
+
async _publishMqttCommand(commandObj) {
|
|
19
|
+
// helper: compress + publish to SEND_MESSAGE topic
|
|
20
|
+
const mqtt = this.realtimeClient.mqtt || this.realtimeClient._mqtt;
|
|
21
|
+
if (!mqtt || typeof mqtt.publish !== 'function') {
|
|
22
|
+
throw new Error('MQTT client not available');
|
|
23
|
+
}
|
|
24
|
+
const json = JSON.stringify(commandObj);
|
|
25
|
+
const { compressDeflate } = shared_1;
|
|
26
|
+
const payload = await compressDeflate(json);
|
|
27
|
+
this.enhancedDebug(`Publishing to MQTT topic ${constants_1.Topics.SEND_MESSAGE.id} payload=${json}`);
|
|
28
|
+
const result = await mqtt.publish({
|
|
29
|
+
topic: constants_1.Topics.SEND_MESSAGE.id,
|
|
30
|
+
qosLevel: 1,
|
|
31
|
+
payload: payload,
|
|
32
|
+
});
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Publish a media reference via MQTT (send_item with upload_id)
|
|
38
|
+
* This tells the server: "I have upload_id X, attach it to thread Y"
|
|
39
|
+
*/
|
|
40
|
+
async sendMediaViaMqtt({ threadId, uploadId, clientContext, text, mediaId }) {
|
|
41
|
+
this.enhancedDebug(`sendMediaViaMqtt: thread=${threadId} upload_id=${uploadId}`);
|
|
42
|
+
if (!threadId) throw new Error('threadId is required');
|
|
43
|
+
if (!uploadId) throw new Error('uploadId is required');
|
|
44
|
+
|
|
45
|
+
const ctx = clientContext || (0, uuid_1.v4)();
|
|
46
|
+
const command = {
|
|
47
|
+
action: 'send_item',
|
|
48
|
+
thread_id: threadId,
|
|
49
|
+
item_type: 'media',
|
|
50
|
+
upload_id: String(uploadId),
|
|
51
|
+
client_context: ctx,
|
|
52
|
+
timestamp: Date.now(),
|
|
53
|
+
};
|
|
54
|
+
// optional fields if present
|
|
55
|
+
if (typeof text === 'string' && text.length > 0) {
|
|
56
|
+
// Instagram sometimes keeps a caption in the media message; include as text
|
|
57
|
+
command.text = text;
|
|
58
|
+
}
|
|
59
|
+
if (mediaId) {
|
|
60
|
+
// sometimes media_id is sent along, include if present
|
|
61
|
+
command.media_id = mediaId;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return this._publishMqttCommand(command);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ---------- TEXT / BASIC COMMANDS (unchanged behavior) ----------
|
|
17
68
|
/**
|
|
18
69
|
* Send text via MQTT with proper payload format
|
|
19
70
|
*/
|
|
@@ -39,7 +90,8 @@ class EnhancedDirectCommands {
|
|
|
39
90
|
|
|
40
91
|
// Compress JSON payload
|
|
41
92
|
const json = JSON.stringify(command);
|
|
42
|
-
const
|
|
93
|
+
const { compressDeflate } = shared_1;
|
|
94
|
+
const payload = await compressDeflate(json);
|
|
43
95
|
|
|
44
96
|
// Send to MQTT
|
|
45
97
|
this.enhancedDebug(`Publishing to MQTT topic ${constants_1.Topics.SEND_MESSAGE.id}`);
|
|
@@ -79,7 +131,8 @@ class EnhancedDirectCommands {
|
|
|
79
131
|
};
|
|
80
132
|
|
|
81
133
|
const json = JSON.stringify(command);
|
|
82
|
-
const
|
|
134
|
+
const { compressDeflate } = shared_1;
|
|
135
|
+
const payload = await compressDeflate(json);
|
|
83
136
|
|
|
84
137
|
this.enhancedDebug(`Publishing delete command to MQTT topic ${constants_1.Topics.SEND_MESSAGE.id}`);
|
|
85
138
|
const result = await mqtt.publish({
|
|
@@ -119,7 +172,8 @@ class EnhancedDirectCommands {
|
|
|
119
172
|
};
|
|
120
173
|
|
|
121
174
|
const json = JSON.stringify(command);
|
|
122
|
-
const
|
|
175
|
+
const { compressDeflate } = shared_1;
|
|
176
|
+
const payload = await compressDeflate(json);
|
|
123
177
|
|
|
124
178
|
this.enhancedDebug(`Publishing edit command to MQTT topic ${constants_1.Topics.SEND_MESSAGE.id}`);
|
|
125
179
|
const result = await mqtt.publish({
|
|
@@ -160,7 +214,8 @@ class EnhancedDirectCommands {
|
|
|
160
214
|
};
|
|
161
215
|
|
|
162
216
|
const json = JSON.stringify(command);
|
|
163
|
-
const
|
|
217
|
+
const { compressDeflate } = shared_1;
|
|
218
|
+
const payload = await compressDeflate(json);
|
|
164
219
|
|
|
165
220
|
this.enhancedDebug(`Publishing reply command to MQTT topic ${constants_1.Topics.SEND_MESSAGE.id}`);
|
|
166
221
|
const result = await mqtt.publish({
|
|
@@ -198,7 +253,8 @@ class EnhancedDirectCommands {
|
|
|
198
253
|
};
|
|
199
254
|
|
|
200
255
|
const json = JSON.stringify(command);
|
|
201
|
-
const
|
|
256
|
+
const { compressDeflate } = shared_1;
|
|
257
|
+
const payload = await compressDeflate(json);
|
|
202
258
|
|
|
203
259
|
this.enhancedDebug(`Publishing follow subscription to MQTT`);
|
|
204
260
|
const result = await mqtt.publish({
|
|
@@ -236,7 +292,8 @@ class EnhancedDirectCommands {
|
|
|
236
292
|
};
|
|
237
293
|
|
|
238
294
|
const json = JSON.stringify(command);
|
|
239
|
-
const
|
|
295
|
+
const { compressDeflate } = shared_1;
|
|
296
|
+
const payload = await compressDeflate(json);
|
|
240
297
|
|
|
241
298
|
this.enhancedDebug(`Publishing mention subscription to MQTT`);
|
|
242
299
|
const result = await mqtt.publish({
|
|
@@ -274,7 +331,8 @@ class EnhancedDirectCommands {
|
|
|
274
331
|
};
|
|
275
332
|
|
|
276
333
|
const json = JSON.stringify(command);
|
|
277
|
-
const
|
|
334
|
+
const { compressDeflate } = shared_1;
|
|
335
|
+
const payload = await compressDeflate(json);
|
|
278
336
|
|
|
279
337
|
this.enhancedDebug(`Publishing call subscription to MQTT`);
|
|
280
338
|
const result = await mqtt.publish({
|
|
@@ -313,7 +371,8 @@ class EnhancedDirectCommands {
|
|
|
313
371
|
};
|
|
314
372
|
|
|
315
373
|
const json = JSON.stringify(command);
|
|
316
|
-
const
|
|
374
|
+
const { compressDeflate } = shared_1;
|
|
375
|
+
const payload = await compressDeflate(json);
|
|
317
376
|
|
|
318
377
|
this.enhancedDebug(`Publishing add member command to MQTT`);
|
|
319
378
|
const result = await mqtt.publish({
|
|
@@ -352,7 +411,8 @@ class EnhancedDirectCommands {
|
|
|
352
411
|
};
|
|
353
412
|
|
|
354
413
|
const json = JSON.stringify(command);
|
|
355
|
-
const
|
|
414
|
+
const { compressDeflate } = shared_1;
|
|
415
|
+
const payload = await compressDeflate(json);
|
|
356
416
|
|
|
357
417
|
this.enhancedDebug(`Publishing remove member command to MQTT`);
|
|
358
418
|
const result = await mqtt.publish({
|
|
@@ -394,7 +454,8 @@ class EnhancedDirectCommands {
|
|
|
394
454
|
};
|
|
395
455
|
|
|
396
456
|
const json = JSON.stringify(command);
|
|
397
|
-
const
|
|
457
|
+
const { compressDeflate } = shared_1;
|
|
458
|
+
const payload = await compressDeflate(json);
|
|
398
459
|
|
|
399
460
|
this.enhancedDebug(`Publishing reaction to MQTT`);
|
|
400
461
|
const result = await mqtt.publish({
|
|
@@ -433,7 +494,8 @@ class EnhancedDirectCommands {
|
|
|
433
494
|
};
|
|
434
495
|
|
|
435
496
|
const json = JSON.stringify(command);
|
|
436
|
-
const
|
|
497
|
+
const { compressDeflate } = shared_1;
|
|
498
|
+
const payload = await compressDeflate(json);
|
|
437
499
|
|
|
438
500
|
this.enhancedDebug(`Publishing mark as seen to MQTT`);
|
|
439
501
|
const result = await mqtt.publish({
|
|
@@ -472,7 +534,8 @@ class EnhancedDirectCommands {
|
|
|
472
534
|
};
|
|
473
535
|
|
|
474
536
|
const json = JSON.stringify(command);
|
|
475
|
-
const
|
|
537
|
+
const { compressDeflate } = shared_1;
|
|
538
|
+
const payload = await compressDeflate(json);
|
|
476
539
|
|
|
477
540
|
this.enhancedDebug(`Publishing activity indicator to MQTT`);
|
|
478
541
|
const result = await mqtt.publish({
|
|
@@ -490,95 +553,108 @@ class EnhancedDirectCommands {
|
|
|
490
553
|
}
|
|
491
554
|
|
|
492
555
|
/**
|
|
493
|
-
* Send media (image/video)
|
|
494
|
-
*
|
|
556
|
+
* Send media (image/video)
|
|
557
|
+
*
|
|
558
|
+
* Behavior:
|
|
559
|
+
* - if mediaBuffer provided -> perform rupload (REST) to get upload_id, then send MQTT send_item with upload_id (no REST broadcast)
|
|
560
|
+
* - if only uploadId provided -> send MQTT send_item with upload_id
|
|
495
561
|
*/
|
|
496
|
-
async sendMedia({ text, mediaId, threadId, clientContext, uploadId }) {
|
|
497
|
-
this.enhancedDebug(`
|
|
498
|
-
|
|
562
|
+
async sendMedia({ text, mediaId, threadId, clientContext, uploadId, mediaBuffer, mimeType = 'image/jpeg', duration = 0, width = 720, height = 1280 }) {
|
|
563
|
+
this.enhancedDebug(`sendMedia called for thread ${threadId} (will rupload if buffer provided)`);
|
|
499
564
|
try {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
return this.sendPhotoMqtt({ uploadId: String(uploadId), threadId: String(threadId), caption: text || '', clientContext });
|
|
565
|
+
if (!threadId) {
|
|
566
|
+
throw new Error('threadId is required');
|
|
503
567
|
}
|
|
504
568
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
throw new Error('MQTT client not available');
|
|
569
|
+
const ig = this.realtimeClient.ig;
|
|
570
|
+
if (!ig || !ig.request) {
|
|
571
|
+
throw new Error('Instagram client not available. Make sure you are logged in.');
|
|
509
572
|
}
|
|
510
|
-
|
|
511
|
-
const ctx = clientContext || (0, uuid_1.v4)();
|
|
512
|
-
const command = {
|
|
513
|
-
action: 'send_item',
|
|
514
|
-
thread_id: threadId,
|
|
515
|
-
item_type: 'media',
|
|
516
|
-
media_id: mediaId,
|
|
517
|
-
upload_id: uploadId || mediaId,
|
|
518
|
-
text: text || '',
|
|
519
|
-
timestamp: Date.now(),
|
|
520
|
-
client_context: ctx,
|
|
521
|
-
};
|
|
522
|
-
|
|
523
|
-
// Log payload for debugging
|
|
524
|
-
this.enhancedDebug(`Payload: ${JSON.stringify(command)}`);
|
|
525
|
-
|
|
526
|
-
const json = JSON.stringify(command);
|
|
527
|
-
const payload = await (0, shared_1.compressDeflate)(json);
|
|
528
|
-
|
|
529
|
-
this.enhancedDebug(`Publishing media to MQTT topic ${constants_1.Topics.SEND_MESSAGE.id}`);
|
|
530
|
-
const result = await mqtt.publish({
|
|
531
|
-
topic: constants_1.Topics.SEND_MESSAGE.id,
|
|
532
|
-
qosLevel: 1,
|
|
533
|
-
payload: payload,
|
|
534
|
-
});
|
|
535
|
-
|
|
536
|
-
this.enhancedDebug(`✅ Media sent via MQTT!`);
|
|
537
|
-
return result;
|
|
538
|
-
} catch (err) {
|
|
539
|
-
this.enhancedDebug(`Media send failed: ${err.message}`);
|
|
540
|
-
throw err;
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
573
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
* for Direct Message photos (upload_id + thread_id). Must be called after rupload.
|
|
547
|
-
*/
|
|
548
|
-
async sendPhotoMqtt({ uploadId, threadId, caption = '', clientContext }) {
|
|
549
|
-
this.enhancedDebug(`Sending photo via MQTT uploadId=${uploadId} to thread=${threadId}`);
|
|
550
|
-
try {
|
|
551
|
-
const mqtt = this.realtimeClient.mqtt || this.realtimeClient._mqtt;
|
|
552
|
-
if (!mqtt || typeof mqtt.publish !== 'function') {
|
|
553
|
-
throw new Error('MQTT client not available');
|
|
554
|
-
}
|
|
574
|
+
let serverUploadId = uploadId || null;
|
|
575
|
+
const isVideo = mimeType && mimeType.startsWith('video/');
|
|
555
576
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
item_type: 'photo',
|
|
561
|
-
upload_id: String(uploadId),
|
|
562
|
-
text: caption || '',
|
|
563
|
-
timestamp: Date.now(),
|
|
564
|
-
client_context: ctx,
|
|
565
|
-
};
|
|
577
|
+
if (mediaBuffer) {
|
|
578
|
+
if (!Buffer.isBuffer(mediaBuffer) || mediaBuffer.length === 0) {
|
|
579
|
+
throw new Error('mediaBuffer must be a non-empty Buffer when provided');
|
|
580
|
+
}
|
|
566
581
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
582
|
+
// generate upload id and object name
|
|
583
|
+
serverUploadId = serverUploadId || Date.now().toString();
|
|
584
|
+
const objectName = `${(0, uuid_1.v4)()}.${isVideo ? 'mp4' : (mimeType === 'image/png' ? 'png' : 'jpg')}`;
|
|
585
|
+
|
|
586
|
+
const ruploadParams = isVideo
|
|
587
|
+
? {
|
|
588
|
+
upload_id: serverUploadId,
|
|
589
|
+
media_type: 2,
|
|
590
|
+
xsharing_user_ids: JSON.stringify([]),
|
|
591
|
+
upload_media_duration_ms: Math.round(duration * 1000),
|
|
592
|
+
upload_media_width: width,
|
|
593
|
+
upload_media_height: height,
|
|
594
|
+
}
|
|
595
|
+
: {
|
|
596
|
+
upload_id: serverUploadId,
|
|
597
|
+
media_type: 1,
|
|
598
|
+
image_compression: '{"lib_name":"moz","lib_version":"3.1.m","quality":"80"}',
|
|
599
|
+
xsharing_user_ids: JSON.stringify([]),
|
|
600
|
+
is_clips_media: false,
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
const uploadHeaders = {
|
|
604
|
+
'X-Instagram-Rupload-Params': JSON.stringify(ruploadParams),
|
|
605
|
+
'Content-Type': mimeType,
|
|
606
|
+
'X-Entity-Type': mimeType,
|
|
607
|
+
'X-Entity-Length': String(mediaBuffer.length),
|
|
608
|
+
'Content-Length': String(mediaBuffer.length),
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
if (isVideo) {
|
|
612
|
+
uploadHeaders['X_FB_VIDEO_WATERFALL_ID'] = (0, uuid_1.v4)();
|
|
613
|
+
uploadHeaders['Offset'] = '0';
|
|
614
|
+
} else {
|
|
615
|
+
uploadHeaders['X_FB_PHOTO_WATERFALL_ID'] = (0, uuid_1.v4)();
|
|
616
|
+
}
|
|
570
617
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
618
|
+
const uploadUrl = isVideo ? `/rupload_igvideo/${objectName}` : `/rupload_igphoto/${objectName}`;
|
|
619
|
+
|
|
620
|
+
this.enhancedDebug(`Uploading media to ${uploadUrl} (${mediaBuffer.length} bytes)`);
|
|
621
|
+
try {
|
|
622
|
+
const uploadResponse = await ig.request.send({
|
|
623
|
+
url: uploadUrl,
|
|
624
|
+
method: 'POST',
|
|
625
|
+
headers: uploadHeaders,
|
|
626
|
+
body: mediaBuffer,
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
if (uploadResponse && typeof uploadResponse === 'object' && uploadResponse.upload_id) {
|
|
630
|
+
serverUploadId = uploadResponse.upload_id;
|
|
631
|
+
}
|
|
632
|
+
this.enhancedDebug(`✅ Media uploaded (serverUploadId=${serverUploadId})`);
|
|
633
|
+
} catch (uploadErr) {
|
|
634
|
+
this.enhancedDebug(`Upload error: ${uploadErr.message}`);
|
|
635
|
+
throw new Error(`Media upload failed: ${uploadErr.message}`);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// now we must have upload id
|
|
640
|
+
if (!serverUploadId) {
|
|
641
|
+
throw new Error('uploadId is required if mediaBuffer is not provided');
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// Publish MQTT reference (no REST broadcast)
|
|
645
|
+
const publishResult = await this.sendMediaViaMqtt({
|
|
646
|
+
threadId,
|
|
647
|
+
uploadId: serverUploadId,
|
|
648
|
+
clientContext,
|
|
649
|
+
text,
|
|
650
|
+
mediaId,
|
|
576
651
|
});
|
|
577
652
|
|
|
578
|
-
this.enhancedDebug(`✅
|
|
579
|
-
return
|
|
653
|
+
this.enhancedDebug(`✅ Media reference published via MQTT for upload_id=${serverUploadId}`);
|
|
654
|
+
return publishResult;
|
|
655
|
+
|
|
580
656
|
} catch (err) {
|
|
581
|
-
this.enhancedDebug(`
|
|
657
|
+
this.enhancedDebug(`sendMedia failed: ${err.message}`);
|
|
582
658
|
throw err;
|
|
583
659
|
}
|
|
584
660
|
}
|
|
@@ -607,7 +683,8 @@ class EnhancedDirectCommands {
|
|
|
607
683
|
};
|
|
608
684
|
|
|
609
685
|
const json = JSON.stringify(command);
|
|
610
|
-
const
|
|
686
|
+
const { compressDeflate } = shared_1;
|
|
687
|
+
const payload = await compressDeflate(json);
|
|
611
688
|
|
|
612
689
|
this.enhancedDebug(`Publishing location to MQTT`);
|
|
613
690
|
const result = await mqtt.publish({
|
|
@@ -648,7 +725,8 @@ class EnhancedDirectCommands {
|
|
|
648
725
|
};
|
|
649
726
|
|
|
650
727
|
const json = JSON.stringify(command);
|
|
651
|
-
const
|
|
728
|
+
const { compressDeflate } = shared_1;
|
|
729
|
+
const payload = await compressDeflate(json);
|
|
652
730
|
|
|
653
731
|
this.enhancedDebug(`Publishing profile to MQTT`);
|
|
654
732
|
const result = await mqtt.publish({
|
|
@@ -689,7 +767,8 @@ class EnhancedDirectCommands {
|
|
|
689
767
|
};
|
|
690
768
|
|
|
691
769
|
const json = JSON.stringify(command);
|
|
692
|
-
const
|
|
770
|
+
const { compressDeflate } = shared_1;
|
|
771
|
+
const payload = await compressDeflate(json);
|
|
693
772
|
|
|
694
773
|
this.enhancedDebug(`Publishing hashtag to MQTT`);
|
|
695
774
|
const result = await mqtt.publish({
|
|
@@ -728,7 +807,8 @@ class EnhancedDirectCommands {
|
|
|
728
807
|
};
|
|
729
808
|
|
|
730
809
|
const json = JSON.stringify(command);
|
|
731
|
-
const
|
|
810
|
+
const { compressDeflate } = shared_1;
|
|
811
|
+
const payload = await compressDeflate(json);
|
|
732
812
|
|
|
733
813
|
this.enhancedDebug(`Publishing like to MQTT`);
|
|
734
814
|
const result = await mqtt.publish({
|
|
@@ -769,7 +849,8 @@ class EnhancedDirectCommands {
|
|
|
769
849
|
};
|
|
770
850
|
|
|
771
851
|
const json = JSON.stringify(command);
|
|
772
|
-
const
|
|
852
|
+
const { compressDeflate } = shared_1;
|
|
853
|
+
const payload = await compressDeflate(json);
|
|
773
854
|
|
|
774
855
|
this.enhancedDebug(`Publishing story to MQTT`);
|
|
775
856
|
const result = await mqtt.publish({
|
|
@@ -787,19 +868,11 @@ class EnhancedDirectCommands {
|
|
|
787
868
|
}
|
|
788
869
|
|
|
789
870
|
/**
|
|
790
|
-
* Send photo via Realtime (Upload +
|
|
791
|
-
*
|
|
792
|
-
* Returns the MQTT publish result.
|
|
793
|
-
*
|
|
794
|
-
* @param {Object} options - Photo sending options
|
|
795
|
-
* @param {Buffer} options.photoBuffer - Image buffer (JPEG/PNG)
|
|
796
|
-
* @param {string} options.threadId - Thread ID to send to
|
|
797
|
-
* @param {string} [options.caption] - Optional caption
|
|
798
|
-
* @param {string} [options.mimeType='image/jpeg'] - MIME type
|
|
799
|
-
* @param {string} [options.clientContext] - Optional client context
|
|
871
|
+
* Send photo via Realtime (Upload + MQTT-ref)
|
|
872
|
+
* Uploads the photo via rupload, then sends MQTT send_item with upload_id.
|
|
800
873
|
*/
|
|
801
874
|
async sendPhotoViaRealtime({ photoBuffer, threadId, caption = '', mimeType = 'image/jpeg', clientContext }) {
|
|
802
|
-
this.enhancedDebug(`Sending photo to thread ${threadId} via Realtime (
|
|
875
|
+
this.enhancedDebug(`Sending photo to thread ${threadId} via Realtime (rupload + mqtt-ref)`);
|
|
803
876
|
|
|
804
877
|
try {
|
|
805
878
|
// Validate inputs
|
|
@@ -810,66 +883,15 @@ class EnhancedDirectCommands {
|
|
|
810
883
|
throw new Error('threadId is required');
|
|
811
884
|
}
|
|
812
885
|
|
|
813
|
-
//
|
|
814
|
-
const
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
const uploadId = Date.now().toString();
|
|
823
|
-
const objectName = `${(0, uuid_1.v4)()}.${mimeType === 'image/png' ? 'png' : 'jpg'}`;
|
|
824
|
-
|
|
825
|
-
const isJpeg = mimeType === 'image/jpeg' || mimeType === 'image/jpg';
|
|
826
|
-
const compression = isJpeg
|
|
827
|
-
? '{"lib_name":"moz","lib_version":"3.1.m","quality":"80"}'
|
|
828
|
-
: '{"lib_name":"png","lib_version":"1.0","quality":"100"}';
|
|
829
|
-
|
|
830
|
-
const ruploadParams = {
|
|
831
|
-
upload_id: uploadId,
|
|
832
|
-
media_type: 1,
|
|
833
|
-
image_compression: compression,
|
|
834
|
-
xsharing_user_ids: JSON.stringify([]),
|
|
835
|
-
is_clips_media: false,
|
|
836
|
-
};
|
|
837
|
-
|
|
838
|
-
const uploadHeaders = {
|
|
839
|
-
'X-Instagram-Rupload-Params': JSON.stringify(ruploadParams),
|
|
840
|
-
'Content-Type': mimeType,
|
|
841
|
-
'X_FB_PHOTO_WATERFALL_ID': (0, uuid_1.v4)(),
|
|
842
|
-
'X-Entity-Type': mimeType,
|
|
843
|
-
'X-Entity-Length': String(photoBuffer.length),
|
|
844
|
-
'Content-Length': String(photoBuffer.length),
|
|
845
|
-
};
|
|
846
|
-
|
|
847
|
-
const uploadUrl = `/rupload_igphoto/${objectName}`;
|
|
848
|
-
|
|
849
|
-
let serverUploadId = uploadId;
|
|
850
|
-
try {
|
|
851
|
-
const uploadResponse = await ig.request.send({
|
|
852
|
-
url: uploadUrl,
|
|
853
|
-
method: 'POST',
|
|
854
|
-
headers: uploadHeaders,
|
|
855
|
-
body: photoBuffer,
|
|
856
|
-
});
|
|
857
|
-
|
|
858
|
-
if (uploadResponse && typeof uploadResponse === 'object' && (uploadResponse.upload_id || uploadResponse.body?.upload_id || uploadResponse.data?.upload_id)) {
|
|
859
|
-
serverUploadId = uploadResponse.upload_id || (uploadResponse.body && uploadResponse.body.upload_id) || (uploadResponse.data && uploadResponse.data.upload_id) || serverUploadId;
|
|
860
|
-
}
|
|
861
|
-
this.enhancedDebug(`✅ Photo uploaded! upload_id: ${serverUploadId}`);
|
|
862
|
-
} catch (uploadErr) {
|
|
863
|
-
this.enhancedDebug(`Upload error: ${uploadErr.message}`);
|
|
864
|
-
throw new Error(`Photo upload failed: ${uploadErr.message}`);
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
// Step 2: Publish photo via MQTT (correct path)
|
|
868
|
-
this.enhancedDebug(`Step 2: Publishing photo via MQTT to thread ${threadId}...`);
|
|
869
|
-
const mqttResult = await this.sendPhotoMqtt({ uploadId: serverUploadId, threadId: String(threadId), caption, clientContext });
|
|
870
|
-
this.enhancedDebug(`✅ Photo MQTT publish result: ${JSON.stringify(mqttResult)}`);
|
|
871
|
-
return mqttResult;
|
|
872
|
-
|
|
886
|
+
// delegate to sendMedia which handles rupload + mqtt publish
|
|
887
|
+
const result = await this.sendMedia({
|
|
888
|
+
text: caption,
|
|
889
|
+
threadId,
|
|
890
|
+
clientContext,
|
|
891
|
+
mediaBuffer: photoBuffer,
|
|
892
|
+
mimeType,
|
|
893
|
+
});
|
|
894
|
+
return result;
|
|
873
895
|
} catch (err) {
|
|
874
896
|
this.enhancedDebug(`sendPhotoViaRealtime failed: ${err.message}`);
|
|
875
897
|
throw err;
|
|
@@ -884,22 +906,11 @@ class EnhancedDirectCommands {
|
|
|
884
906
|
}
|
|
885
907
|
|
|
886
908
|
/**
|
|
887
|
-
* Send video via Realtime (Upload +
|
|
888
|
-
*
|
|
889
|
-
* NOTE: Video via MQTT is more complex; for now this continues to use existing rupload + REST broadcast path.
|
|
890
|
-
* If you want MQTT video publish, we can add similar sendVideoMqtt that publishes item_type 'video' with upload_id.
|
|
891
|
-
*
|
|
892
|
-
* @param {Object} options - Video sending options
|
|
893
|
-
* @param {Buffer} options.videoBuffer - Video buffer (MP4)
|
|
894
|
-
* @param {string} options.threadId - Thread ID to send to
|
|
895
|
-
* @param {string} [options.caption] - Optional caption
|
|
896
|
-
* @param {number} [options.duration] - Video duration in seconds
|
|
897
|
-
* @param {number} [options.width] - Video width
|
|
898
|
-
* @param {number} [options.height] - Video height
|
|
899
|
-
* @param {string} [options.clientContext] - Optional client context
|
|
909
|
+
* Send video via Realtime (Upload + MQTT-ref)
|
|
910
|
+
* Uploads video via rupload, then sends MQTT send_item with upload_id.
|
|
900
911
|
*/
|
|
901
|
-
async sendVideoViaRealtime({ videoBuffer, threadId, caption = '', duration = 0, width = 720, height = 1280, clientContext }) {
|
|
902
|
-
this.enhancedDebug(`Sending video to thread ${threadId} via Realtime`);
|
|
912
|
+
async sendVideoViaRealtime({ videoBuffer, threadId, caption = '', duration = 0, width = 720, height = 1280, mimeType = 'video/mp4', clientContext }) {
|
|
913
|
+
this.enhancedDebug(`Sending video to thread ${threadId} via Realtime (rupload + mqtt-ref)`);
|
|
903
914
|
|
|
904
915
|
try {
|
|
905
916
|
// Validate inputs
|
|
@@ -910,85 +921,17 @@ class EnhancedDirectCommands {
|
|
|
910
921
|
throw new Error('threadId is required');
|
|
911
922
|
}
|
|
912
923
|
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
const ruploadParams = {
|
|
926
|
-
upload_id: uploadId,
|
|
927
|
-
media_type: 2, // 2 = video
|
|
928
|
-
xsharing_user_ids: JSON.stringify([]),
|
|
929
|
-
upload_media_duration_ms: Math.round(duration * 1000),
|
|
930
|
-
upload_media_width: width,
|
|
931
|
-
upload_media_height: height,
|
|
932
|
-
};
|
|
933
|
-
|
|
934
|
-
const uploadHeaders = {
|
|
935
|
-
'X-Instagram-Rupload-Params': JSON.stringify(ruploadParams),
|
|
936
|
-
'Content-Type': 'video/mp4',
|
|
937
|
-
'X_FB_VIDEO_WATERFALL_ID': (0, uuid_1.v4)(),
|
|
938
|
-
'X-Entity-Type': 'video/mp4',
|
|
939
|
-
'X-Entity-Length': String(videoBuffer.length),
|
|
940
|
-
'Content-Length': String(videoBuffer.length),
|
|
941
|
-
'Offset': '0',
|
|
942
|
-
};
|
|
943
|
-
|
|
944
|
-
const uploadUrl = `/rupload_igvideo/${objectName}`;
|
|
945
|
-
|
|
946
|
-
let serverUploadId = uploadId;
|
|
947
|
-
try {
|
|
948
|
-
const uploadResponse = await ig.request.send({
|
|
949
|
-
url: uploadUrl,
|
|
950
|
-
method: 'POST',
|
|
951
|
-
headers: uploadHeaders,
|
|
952
|
-
body: videoBuffer,
|
|
953
|
-
});
|
|
954
|
-
|
|
955
|
-
if (uploadResponse && typeof uploadResponse === 'object' && (uploadResponse.upload_id || uploadResponse.body?.upload_id || uploadResponse.data?.upload_id)) {
|
|
956
|
-
serverUploadId = uploadResponse.upload_id || (uploadResponse.body && uploadResponse.body.upload_id) || (uploadResponse.data && uploadResponse.data.upload_id) || serverUploadId;
|
|
957
|
-
}
|
|
958
|
-
this.enhancedDebug(`✅ Video uploaded! upload_id: ${serverUploadId}`);
|
|
959
|
-
} catch (uploadErr) {
|
|
960
|
-
this.enhancedDebug(`Video upload error: ${uploadErr.message}`);
|
|
961
|
-
throw new Error(`Video upload failed: ${uploadErr.message}`);
|
|
962
|
-
}
|
|
963
|
-
|
|
964
|
-
// Step 2: Broadcast the uploaded video to the thread via REST (legacy)
|
|
965
|
-
this.enhancedDebug(`Step 2: Broadcasting video to thread ${threadId} via REST upload_video endpoint...`);
|
|
966
|
-
|
|
967
|
-
const broadcastForm = {
|
|
968
|
-
upload_id: serverUploadId,
|
|
969
|
-
action: 'send_item',
|
|
970
|
-
thread_ids: JSON.stringify([String(threadId)]),
|
|
971
|
-
video_result: '',
|
|
972
|
-
};
|
|
973
|
-
|
|
974
|
-
if (caption) {
|
|
975
|
-
broadcastForm.caption = caption;
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
try {
|
|
979
|
-
const broadcastResponse = await ig.request.send({
|
|
980
|
-
url: '/direct_v2/threads/broadcast/upload_video/',
|
|
981
|
-
method: 'POST',
|
|
982
|
-
form: broadcastForm,
|
|
983
|
-
});
|
|
984
|
-
|
|
985
|
-
this.enhancedDebug(`✅ Video sent successfully to thread ${threadId}!`);
|
|
986
|
-
return broadcastResponse;
|
|
987
|
-
} catch (broadcastErr) {
|
|
988
|
-
this.enhancedDebug(`Video broadcast error: ${broadcastErr.message}`);
|
|
989
|
-
throw new Error(`Video broadcast failed: ${broadcastErr.message}`);
|
|
990
|
-
}
|
|
991
|
-
|
|
924
|
+
const result = await this.sendMedia({
|
|
925
|
+
text: caption,
|
|
926
|
+
threadId,
|
|
927
|
+
clientContext,
|
|
928
|
+
mediaBuffer: videoBuffer,
|
|
929
|
+
mimeType,
|
|
930
|
+
duration,
|
|
931
|
+
width,
|
|
932
|
+
height,
|
|
933
|
+
});
|
|
934
|
+
return result;
|
|
992
935
|
} catch (err) {
|
|
993
936
|
this.enhancedDebug(`sendVideoViaRealtime failed: ${err.message}`);
|
|
994
937
|
throw err;
|
|
@@ -1003,4 +946,3 @@ class EnhancedDirectCommands {
|
|
|
1003
946
|
}
|
|
1004
947
|
}
|
|
1005
948
|
exports.EnhancedDirectCommands = EnhancedDirectCommands;
|
|
1006
|
-
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodejs-insta-private-api-mqtt",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.17",
|
|
4
4
|
"description": "Complete Instagram MQTT protocol with FULL iOS + Android support. 33 device presets (21 iOS + 12 Android). iPhone 16/15/14/13/12, iPad Pro, Samsung, Pixel, Huawei. Real-time DM messaging, view-once media extraction, sub-500ms latency.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|