nodejs-insta-private-api-mqtt 1.3.13 → 1.3.15
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 +290 -31
- package/dist/core/request.js +127 -53
- package/dist/realtime/commands/enhanced.direct.commands.js +93 -74
- package/dist/repositories/direct-thread.repository.js +108 -15
- package/dist/repositories/upload.repository.js +5 -9
- package/dist/sendmedia/sendPhoto.js +115 -74
- package/dist/sendmedia/uploadPhoto.js +2 -2
- package/package.json +1 -1
|
@@ -1,101 +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
|
-
// We need to use EnhancedDirectCommands or similar
|
|
40
|
-
// Assuming realtimeClient has direct/enhanced/graph interface or we can just publish
|
|
41
|
-
// EnhancedDirectCommands is usually at realtimeClient.direct (if using standard structure)
|
|
42
|
-
// or we can instantiate it if not present.
|
|
43
|
-
|
|
44
|
-
// Check if .direct exists and has sendMedia
|
|
45
|
-
if (realtimeClient.direct && typeof realtimeClient.direct.sendMedia === 'function') {
|
|
46
|
-
return await realtimeClient.direct.sendMedia({
|
|
47
|
-
text: caption,
|
|
48
|
-
mediaId: upload_id,
|
|
49
|
-
threadId: threadId || userId, // Note: MQTT usually needs threadId. If userId provided, might need to resolve threadId first or use specific user send.
|
|
50
|
-
// Actually, sendMedia usually takes threadId. If userId is provided, we might be stuck if we don't have a thread.
|
|
51
|
-
// MQTT is best for existing threads.
|
|
52
|
-
// If userId is provided, we might need to create thread via REST first?
|
|
53
|
-
// For now, assume threadId is preferred for MQTT.
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
}
|
|
62
|
+
validateImageInput(photoBuffer, mimeType);
|
|
57
63
|
|
|
58
|
-
//
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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;
|
|
64
70
|
|
|
65
|
-
|
|
66
|
-
form.entities = JSON.stringify(
|
|
67
|
-
mentions.map((uid) => ({
|
|
68
|
-
user_id: String(uid),
|
|
69
|
-
type: 'mention',
|
|
70
|
-
}))
|
|
71
|
-
);
|
|
72
|
-
}
|
|
71
|
+
const ruploadParams = buildRuploadParams(uploadId, mimeType);
|
|
73
72
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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}`;
|
|
82
87
|
|
|
83
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.
|
|
84
91
|
const response = await session.request.send({
|
|
85
92
|
url,
|
|
86
93
|
method: 'POST',
|
|
87
|
-
|
|
94
|
+
headers,
|
|
95
|
+
data: photoBuffer,
|
|
88
96
|
signal,
|
|
97
|
+
// ensure axios does not apply any transform that would corrupt binary
|
|
98
|
+
transformRequest: [(d) => d]
|
|
89
99
|
});
|
|
90
100
|
|
|
91
101
|
if (!response) {
|
|
92
|
-
throw new Error('
|
|
102
|
+
throw new Error('uploadPhoto: Empty response from Instagram rupload endpoint.');
|
|
93
103
|
}
|
|
94
|
-
|
|
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;
|
|
95
133
|
} catch (err) {
|
|
96
|
-
|
|
97
|
-
|
|
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}`);
|
|
98
139
|
}
|
|
99
140
|
}
|
|
100
141
|
|
|
101
|
-
module.exports =
|
|
142
|
+
module.exports = uploadPhoto;
|
|
@@ -55,10 +55,10 @@ async function uploadPhoto(session, photoBuffer, options = {}) {
|
|
|
55
55
|
|
|
56
56
|
// Headers expected by Instagram rupload (matched with instagram-private-api)
|
|
57
57
|
const headers = {
|
|
58
|
-
'X-Instagram-Rupload-Params': JSON.stringify(ruploadParams),
|
|
59
58
|
'X_FB_PHOTO_WATERFALL_ID': uuidv4(),
|
|
60
|
-
'X-Entity-Type': 'image/jpeg',
|
|
59
|
+
'X-Entity-Type': 'image/jpeg',
|
|
61
60
|
'Offset': '0',
|
|
61
|
+
'X-Instagram-Rupload-Params': JSON.stringify(ruploadParams),
|
|
62
62
|
'X-Entity-Name': name,
|
|
63
63
|
'X-Entity-Length': String(contentLength),
|
|
64
64
|
'Content-Type': 'application/octet-stream',
|
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.15",
|
|
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": {
|