nodejs-insta-private-api-mqtt 1.3.14 → 1.3.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,93 +1,142 @@
1
1
  /**
2
- * sendPhoto.js - Fixed to support MQTT if realtime client provided
2
+ * uploadPhoto.fixed.js
3
+ *
4
+ * Fixed and hardened version of uploadPhoto for nodejs-insta-private-api(-mqtt).
5
+ * - sends Buffer in `data` (axios uses `data`, not `body`)
6
+ * - sets axios-friendly headers (X-Entity-Type uses provided mimeType)
7
+ * - removes explicit Accept-Encoding header (axios handles it)
8
+ * - sets maxContentLength / maxBodyLength via Request httpClient (should be patched there too)
9
+ * - returns the rupload/uploadId (server-provided when available, otherwise local uploadId)
10
+ *
11
+ * Usage:
12
+ * const uploadPhoto = require('./uploadPhoto.fixed');
13
+ * const uploadId = await uploadPhoto(session, photoBuffer, { mimeType: 'image/jpeg' });
14
+ *
15
+ * Note: session.request.send(...) is expected to accept axios-like options.
3
16
  */
4
17
 
5
- const uploadPhoto = require('./uploadPhoto');
18
+ const { v4: uuidv4 } = require('uuid');
19
+
20
+ function validateImageInput(photoBuffer, mimeType) {
21
+ if (!photoBuffer || !Buffer.isBuffer(photoBuffer) || photoBuffer.length === 0) {
22
+ throw new Error('uploadPhoto: photoBuffer must be a non-empty Buffer.');
23
+ }
24
+ const allowed = ['image/jpeg', 'image/jpg', 'image/png'];
25
+ if (!allowed.includes(mimeType)) {
26
+ throw new Error(`uploadPhoto: mimeType must be one of ${allowed.join(', ')}.`);
27
+ }
28
+ }
29
+
30
+ function buildRuploadParams(uploadId, mimeType) {
31
+ const isJpeg = mimeType === 'image/jpeg' || mimeType === 'image/jpg';
32
+ const compression = isJpeg
33
+ ? { lib_name: 'moz', lib_version: '3.1.m', quality: '80' }
34
+ : { lib_name: 'png', lib_version: '1.0', quality: '100' };
35
+
36
+ // Match instagram-private-api shape; include is_sidecar for compatibility
37
+ return {
38
+ retry_context: JSON.stringify({ num_step_auto_retry: 0, num_reupload: 0, num_step_manual_retry: 0 }),
39
+ media_type: '1',
40
+ upload_id: uploadId.toString(),
41
+ xsharing_user_ids: JSON.stringify([]),
42
+ image_compression: JSON.stringify(compression),
43
+ is_sidecar: '0'
44
+ };
45
+ }
6
46
 
7
47
  /**
8
- * Send a photo to Instagram Direct.
48
+ * Upload a photo buffer to Instagram rupload endpoint.
49
+ *
50
+ * @param {Object} session - client/session object that exposes session.request.send(options)
51
+ * @param {Buffer} photoBuffer - binary buffer of the image
52
+ * @param {Object} options - { mimeType = 'image/jpeg', fileName?, signal? }
53
+ * @returns {Promise<string>} uploadId (server upload_id when available, otherwise local uploadId)
9
54
  */
10
- async function sendPhoto(session, opts = {}) {
55
+ async function uploadPhoto(session, photoBuffer, options = {}) {
11
56
  const {
12
- photoBuffer,
13
57
  mimeType = 'image/jpeg',
14
58
  fileName,
15
- caption = '',
16
- userId,
17
- threadId,
18
- mentions = [],
19
- signal,
20
- realtimeClient, // NEW: Optional realtime client for MQTT sending
21
- } = opts;
22
-
23
- // Validate destination
24
- if (!userId && !threadId) {
25
- throw new Error('sendPhoto: You must provide either userId (DM) or threadId (existing thread).');
26
- }
27
- if (userId && threadId) {
28
- throw new Error('sendPhoto: Provide only one destination — userId OR threadId, not both.');
29
- }
30
- if (!photoBuffer || !Buffer.isBuffer(photoBuffer) || photoBuffer.length === 0) {
31
- throw new Error('sendPhoto: photoBuffer must be a non-empty Buffer.');
32
- }
59
+ signal
60
+ } = options;
33
61
 
34
- // 1) Upload photo to get upload_id
35
- const upload_id = await uploadPhoto(session, photoBuffer, { mimeType, fileName, signal });
36
-
37
- // 2) If RealtimeClient is provided and connected, use MQTT
38
- if (realtimeClient) {
39
- const commands = realtimeClient.directCommands || realtimeClient.direct;
40
- if (commands && typeof commands.sendMedia === 'function') {
41
- return await commands.sendMedia({
42
- text: caption,
43
- mediaId: upload_id,
44
- uploadId: upload_id, // Pass upload_id to MQTT
45
- threadId: threadId || userId,
46
- });
47
- }
48
- }
62
+ validateImageInput(photoBuffer, mimeType);
49
63
 
50
- // 3) Fallback to REST Broadcast
51
- const form = {
52
- upload_id,
53
- action: 'send_item',
54
- caption,
55
- };
64
+ // local upload id similar to insta clients
65
+ const uploadId = Date.now().toString();
66
+ // create an entity name similar to instagram-private-api naming
67
+ const randomSuffix = Math.floor(Math.random() * (9999999999 - 1000000000) + 1000000000);
68
+ const name = `${uploadId}_0_${randomSuffix}`;
69
+ const contentLength = photoBuffer.byteLength;
56
70
 
57
- if (Array.isArray(mentions) && mentions.length > 0) {
58
- form.entities = JSON.stringify(
59
- mentions.map((uid) => ({
60
- user_id: String(uid),
61
- type: 'mention',
62
- }))
63
- );
64
- }
71
+ const ruploadParams = buildRuploadParams(uploadId, mimeType);
65
72
 
66
- let url;
67
- if (userId) {
68
- url = '/direct_v2/threads/broadcast/upload_photo/';
69
- form.recipient_users = JSON.stringify([[String(userId)]]);
70
- } else {
71
- url = '/direct_v2/threads/broadcast/upload_photo/';
72
- form.thread_ids = JSON.stringify([String(threadId)]);
73
- }
73
+ const headers = {
74
+ 'X_FB_PHOTO_WATERFALL_ID': uuidv4(),
75
+ 'X-Entity-Type': mimeType, // use actual mimeType
76
+ 'Offset': '0',
77
+ 'X-Instagram-Rupload-Params': JSON.stringify(ruploadParams),
78
+ 'X-Entity-Name': name,
79
+ 'X-Entity-Length': String(contentLength),
80
+ 'Content-Type': 'application/octet-stream',
81
+ 'Content-Length': String(contentLength),
82
+ // remove explicit Accept-Encoding to avoid conflicts; axios handles compression
83
+ // 'Accept-Encoding': 'gzip',
84
+ };
85
+
86
+ const url = `/rupload_igphoto/${name}`;
74
87
 
75
88
  try {
89
+ // session.request.send is expected to forward options to axios (or similar)
90
+ // We MUST use `data` (not `body`) for axios-friendly request.
76
91
  const response = await session.request.send({
77
92
  url,
78
93
  method: 'POST',
79
- form,
94
+ headers,
95
+ data: photoBuffer,
80
96
  signal,
97
+ // ensure axios does not apply any transform that would corrupt binary
98
+ transformRequest: [(d) => d]
81
99
  });
82
100
 
83
101
  if (!response) {
84
- throw new Error('sendPhoto: Empty response from Instagram broadcast endpoint.');
102
+ throw new Error('uploadPhoto: Empty response from Instagram rupload endpoint.');
85
103
  }
86
- return response;
104
+
105
+ // The request wrapper in different forks may return { body, headers } OR axios response
106
+ // Normalize possible shapes:
107
+ let respBody = null;
108
+ if (response.body !== undefined) {
109
+ // wrapper returned { body, headers }
110
+ respBody = response.body;
111
+ } else if (response.data !== undefined) {
112
+ respBody = response.data;
113
+ } else {
114
+ respBody = response;
115
+ }
116
+
117
+ // If respBody is a string, try parse JSON
118
+ let parsed = null;
119
+ if (typeof respBody === 'string') {
120
+ try { parsed = JSON.parse(respBody); } catch (e) { parsed = null; }
121
+ } else if (typeof respBody === 'object') {
122
+ parsed = respBody;
123
+ }
124
+
125
+ // server might return upload_id inside parsed
126
+ const serverUploadId = parsed && (parsed.upload_id || parsed.uploadId || parsed.media && parsed.media.upload_id);
127
+ if (serverUploadId) {
128
+ return serverUploadId.toString();
129
+ }
130
+
131
+ // fallback: some endpoints respond with status only; return our local uploadId
132
+ return uploadId;
87
133
  } catch (err) {
88
- const msg = err.message || JSON.stringify(err);
89
- throw new Error(`sendPhoto: Broadcast failed — ${msg}`);
134
+ // try to unwrap axios error message
135
+ let msg = '';
136
+ if (err && err.message) msg = err.message;
137
+ else msg = String(err);
138
+ throw new Error(`uploadPhoto: Upload failed — ${msg}`);
90
139
  }
91
140
  }
92
141
 
93
- module.exports = sendPhoto;
142
+ module.exports = uploadPhoto;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodejs-insta-private-api-mqtt",
3
- "version": "1.3.14",
3
+ "version": "1.3.16",
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": {