nodejs-insta-private-api-mqtt 1.3.14 → 1.3.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +596 -30
- package/dist/core/request.js +127 -53
- package/dist/realtime/commands/enhanced.direct.commands.js +175 -218
- package/dist/repositories/direct-thread.repository.js +108 -15
- package/dist/sendmedia/sendPhoto.js +115 -66
- package/package.json +1 -1
|
@@ -1,93 +1,142 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
|
55
|
+
async function uploadPhoto(session, photoBuffer, options = {}) {
|
|
11
56
|
const {
|
|
12
|
-
photoBuffer,
|
|
13
57
|
mimeType = 'image/jpeg',
|
|
14
58
|
fileName,
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
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('
|
|
102
|
+
throw new Error('uploadPhoto: Empty response from Instagram rupload endpoint.');
|
|
85
103
|
}
|
|
86
|
-
|
|
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
|
-
|
|
89
|
-
|
|
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 =
|
|
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.
|
|
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": {
|