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 CHANGED
@@ -265,102 +265,179 @@ startBot().catch(console.error);
265
265
 
266
266
  ## 🖼️ IMPORTANT: How media actually works (photos & videos)
267
267
 
268
- > **Critical clarification:** The previous README wording that claimed *"Send Media via MQTT"* was misleading. Instagram's real-time (MQTT) layer is a *sync/notification* channel **it does not replace the HTTP upload + broadcast flow required to create new photo/video DM messages server-side**. The correct and reliable sequence to send media is:
268
+ ## 🖼️ IMPORTANT: How media actually works (photos & videos) — UPDATED (rupload + MQTT reference)
269
269
 
270
- 1. **Upload the media (rupload) — HTTP**
271
- Upload the raw bytes to Instagram's rupload endpoint (this returns an `upload_id`). This step stores the media on Instagram's storage/CDN.
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
- 2. **Create / broadcast the media message — HTTP**
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
- 3. **MQTT = realtime sync / notification**
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
- ### Why this change matters
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
- - MQTT is excellent for **text**, **reactions**, **typing**, **presence** and **read receipts** these can be (and are) sent directly over realtime topics.
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
- ### Correct examples (upload + broadcast)
299
+ ### Example: Hybrid flow — Photo (rupload HTTP then MQTT reference)
289
300
 
290
- #### Upload a photo and broadcast (recommended pattern)
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
- const fs = require('fs');
294
- const { v4: uuidv4 } = require('uuid');
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 photoBuffer = fs.readFileSync('./image.jpg');
297
-
298
- // 1) rupload (helper may exist as ig.publish.photo)
299
- const upload = await ig.publish.photo({ file: photoBuffer });
300
- // upload.upload_id should be available (or upload.uploadId)
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 response = await ig.request.send({
316
- url: '/api/v1/direct_v2/threads/broadcast/configure_photo/',
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
- form: payload,
319
- timeout: 60000
328
+ headers,
329
+ body: photoBuffer,
330
+ timeout: 120000,
320
331
  });
321
332
 
322
- // If response indicates success (status: 'ok' or similar), the message is now created server-side.
323
- // MQTT will usually emit a MESSAGE_SYNC event shortly after with the CDN URL for the image.
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
- #### Upload and broadcast a video
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
- const videoBuffer = fs.readFileSync('./video.mp4');
368
+ // 1) RUUPLOAD (video)
369
+ const uploadId = Date.now().toString();
370
+ const objectName = `${uploadId}_${Math.floor(Math.random()*1e9)}.mp4`;
330
371
 
331
- // 1) rupload video (may be ig.publish.video)
332
- const upload = await ig.publish.video({ video: videoBuffer, duration: 15000 });
333
-
334
- // 2) broadcast via HTTP similar to photo but to the upload_video endpoint
335
- const mutationToken = uuidv4();
336
- const payload = {
337
- action: 'configure_video',
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 response = await ig.request.send({
348
- url: '/api/v1/direct_v2/threads/broadcast/configure_video/',
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
- form: payload,
351
- timeout: 90000
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
- ### What was removed/changed
358
- - Any claim that photos/videos can be **created** solely through MQTT was corrected.
359
- - Examples and helper text were updated to show the required HTTP upload + broadcast flow.
360
- - MQTT examples for text and realtime events remain unchanged; MQTT should still be used for fast realtime behavior.
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 payload = await (0, shared_1.compressDeflate)(json);
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 payload = await (0, shared_1.compressDeflate)(json);
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 payload = await (0, shared_1.compressDeflate)(json);
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 payload = await (0, shared_1.compressDeflate)(json);
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 payload = await (0, shared_1.compressDeflate)(json);
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 payload = await (0, shared_1.compressDeflate)(json);
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 payload = await (0, shared_1.compressDeflate)(json);
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 payload = await (0, shared_1.compressDeflate)(json);
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 payload = await (0, shared_1.compressDeflate)(json);
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 payload = await (0, shared_1.compressDeflate)(json);
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 payload = await (0, shared_1.compressDeflate)(json);
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 payload = await (0, shared_1.compressDeflate)(json);
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) via MQTT
494
- * If uploadId present or item_type indicates photo, delegate to MQTT photo path.
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(`Sending media ${mediaId} (uploadId: ${uploadId}) to ${threadId} via MQTT`);
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
- // If an uploadId exists, this is a DM upload flow — use photo MQTT path
501
- if (uploadId) {
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
- // Otherwise continue with existing generic media publish (share/post style)
506
- const mqtt = this.realtimeClient.mqtt || this.realtimeClient._mqtt;
507
- if (!mqtt || typeof mqtt.publish !== 'function') {
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
- * Proper MQTT photo send. This publishes the minimal payload Instagram expects
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
- const ctx = clientContext || (0, uuid_1.v4)();
557
- const command = {
558
- action: 'send_item',
559
- thread_id: String(threadId),
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
- this.enhancedDebug(`Photo payload: ${JSON.stringify(command)}`);
568
- const json = JSON.stringify(command);
569
- const payload = await (0, shared_1.compressDeflate)(json);
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
- this.enhancedDebug(`Publishing photo to MQTT topic ${constants_1.Topics.SEND_MESSAGE.id}`);
572
- const result = await mqtt.publish({
573
- topic: constants_1.Topics.SEND_MESSAGE.id,
574
- qosLevel: 1,
575
- payload: payload,
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(`✅ Photo published via MQTT!`);
579
- return result;
653
+ this.enhancedDebug(`✅ Media reference published via MQTT for upload_id=${serverUploadId}`);
654
+ return publishResult;
655
+
580
656
  } catch (err) {
581
- this.enhancedDebug(`Photo MQTT publish failed: ${err.message}`);
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 payload = await (0, shared_1.compressDeflate)(json);
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 payload = await (0, shared_1.compressDeflate)(json);
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 payload = await (0, shared_1.compressDeflate)(json);
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 payload = await (0, shared_1.compressDeflate)(json);
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 payload = await (0, shared_1.compressDeflate)(json);
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 + Broadcast via MQTT)
791
- * This method uploads the photo first (rupload) then publishes the MQTT photo command (send_item item_type=photo).
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 (upload -> MQTT)`);
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
- // Get the ig client from realtime client
814
- const ig = this.realtimeClient.ig;
815
- if (!ig || !ig.request) {
816
- throw new Error('Instagram client not available. Make sure you are logged in.');
817
- }
818
-
819
- // Step 1: Upload photo using rupload endpoint
820
- this.enhancedDebug(`Step 1: Uploading photo (${photoBuffer.length} bytes)...`);
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 + Broadcast)
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
- // Get the ig client from realtime client
914
- const ig = this.realtimeClient.ig;
915
- if (!ig || !ig.request) {
916
- throw new Error('Instagram client not available. Make sure you are logged in.');
917
- }
918
-
919
- // Step 1: Upload video using rupload endpoint
920
- this.enhancedDebug(`Step 1: Uploading video (${videoBuffer.length} bytes)...`);
921
-
922
- const uploadId = Date.now().toString();
923
- const objectName = `${(0, uuid_1.v4)()}.mp4`;
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.15",
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": {