nodejs-insta-private-api-mqtt 1.0.0
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 +1650 -0
- package/dist/constants/constants.js +280 -0
- package/dist/constants/index.js +41 -0
- package/dist/core/client.js +243 -0
- package/dist/core/repository.js +7 -0
- package/dist/core/request.js +212 -0
- package/dist/core/state.js +1456 -0
- package/dist/core/utils.js +786 -0
- package/dist/downloadMedia.js +381 -0
- package/dist/errors/index.d.ts +16 -0
- package/dist/errors/index.js +30 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/fbns/fbns.client.d.ts +32 -0
- package/dist/fbns/fbns.client.events.d.ts +41 -0
- package/dist/fbns/fbns.client.events.js +3 -0
- package/dist/fbns/fbns.client.events.js.map +1 -0
- package/dist/fbns/fbns.client.js +179 -0
- package/dist/fbns/fbns.client.js.map +1 -0
- package/dist/fbns/fbns.device-auth.d.ts +17 -0
- package/dist/fbns/fbns.device-auth.js +54 -0
- package/dist/fbns/fbns.device-auth.js.map +1 -0
- package/dist/fbns/fbns.types.d.ts +83 -0
- package/dist/fbns/fbns.types.js +3 -0
- package/dist/fbns/fbns.types.js.map +1 -0
- package/dist/fbns/fbns.utilities.d.ts +2 -0
- package/dist/fbns/fbns.utilities.js +79 -0
- package/dist/fbns/fbns.utilities.js.map +1 -0
- package/dist/fbns/index.d.ts +4 -0
- package/dist/fbns/index.js +21 -0
- package/dist/fbns/index.js.map +1 -0
- package/dist/index.js +39 -0
- package/dist/mqttot/index.d.ts +4 -0
- package/dist/mqttot/index.js +21 -0
- package/dist/mqttot/index.js.map +1 -0
- package/dist/mqttot/mqttot.client.d.ts +39 -0
- package/dist/mqttot/mqttot.client.js +120 -0
- package/dist/mqttot/mqttot.client.js.map +1 -0
- package/dist/mqttot/mqttot.connect.request.packet.d.ts +7 -0
- package/dist/mqttot/mqttot.connect.request.packet.js +9 -0
- package/dist/mqttot/mqttot.connect.request.packet.js.map +1 -0
- package/dist/mqttot/mqttot.connect.response.packet.d.ts +7 -0
- package/dist/mqttot/mqttot.connect.response.packet.js +24 -0
- package/dist/mqttot/mqttot.connect.response.packet.js.map +1 -0
- package/dist/mqttot/mqttot.connection.d.ts +57 -0
- package/dist/mqttot/mqttot.connection.js +56 -0
- package/dist/mqttot/mqttot.connection.js.map +1 -0
- package/dist/package.json +59 -0
- package/dist/realtime/commands/commands.d.ts +15 -0
- package/dist/realtime/commands/commands.js +21 -0
- package/dist/realtime/commands/commands.js.map +1 -0
- package/dist/realtime/commands/direct.commands.d.ts +75 -0
- package/dist/realtime/commands/direct.commands.js +186 -0
- package/dist/realtime/commands/direct.commands.js.map +1 -0
- package/dist/realtime/commands/enhanced.direct.commands.js +987 -0
- package/dist/realtime/commands/index.d.ts +2 -0
- package/dist/realtime/commands/index.js +19 -0
- package/dist/realtime/commands/index.js.map +1 -0
- package/dist/realtime/delta-sync.manager.js +293 -0
- package/dist/realtime/features/dm-sender.js +88 -0
- package/dist/realtime/features/error-handler.js +73 -0
- package/dist/realtime/features/gap-handler.js +61 -0
- package/dist/realtime/features/presence.manager.js +66 -0
- package/dist/realtime/index.js +30 -0
- package/dist/realtime/messages/app-presence.event.d.ts +9 -0
- package/dist/realtime/messages/app-presence.event.js +3 -0
- package/dist/realtime/messages/app-presence.event.js.map +1 -0
- package/dist/realtime/messages/index.d.ts +3 -0
- package/dist/realtime/messages/index.js +20 -0
- package/dist/realtime/messages/index.js.map +1 -0
- package/dist/realtime/messages/message-sync.message.d.ts +222 -0
- package/dist/realtime/messages/message-sync.message.js +43 -0
- package/dist/realtime/messages/message-sync.message.js.map +1 -0
- package/dist/realtime/messages/realtime-sub.direct.data.d.ts +11 -0
- package/dist/realtime/messages/realtime-sub.direct.data.js +3 -0
- package/dist/realtime/messages/realtime-sub.direct.data.js.map +1 -0
- package/dist/realtime/messages/thread-update.message.d.ts +68 -0
- package/dist/realtime/messages/thread-update.message.js +3 -0
- package/dist/realtime/messages/thread-update.message.js.map +1 -0
- package/dist/realtime/mixins/index.d.ts +3 -0
- package/dist/realtime/mixins/index.js +20 -0
- package/dist/realtime/mixins/index.js.map +1 -0
- package/dist/realtime/mixins/message-sync.mixin.d.ts +8 -0
- package/dist/realtime/mixins/message-sync.mixin.js +381 -0
- package/dist/realtime/mixins/message-sync.mixin.js.map +1 -0
- package/dist/realtime/mixins/mixin.d.ts +19 -0
- package/dist/realtime/mixins/mixin.js +41 -0
- package/dist/realtime/mixins/mixin.js.map +1 -0
- package/dist/realtime/mixins/presence-typing.mixin.js +33 -0
- package/dist/realtime/mixins/realtime-sub.mixin.d.ts +8 -0
- package/dist/realtime/mixins/realtime-sub.mixin.js +55 -0
- package/dist/realtime/mixins/realtime-sub.mixin.js.map +1 -0
- package/dist/realtime/parsers/graphql-parser.js +43 -0
- package/dist/realtime/parsers/graphql.parser.d.ts +15 -0
- package/dist/realtime/parsers/graphql.parser.js +22 -0
- package/dist/realtime/parsers/graphql.parser.js.map +1 -0
- package/dist/realtime/parsers/index.d.ts +6 -0
- package/dist/realtime/parsers/index.js +23 -0
- package/dist/realtime/parsers/index.js.map +1 -0
- package/dist/realtime/parsers/iris-parser.js +43 -0
- package/dist/realtime/parsers/iris.parser.d.ts +17 -0
- package/dist/realtime/parsers/iris.parser.js +10 -0
- package/dist/realtime/parsers/iris.parser.js.map +1 -0
- package/dist/realtime/parsers/json-parser.js +43 -0
- package/dist/realtime/parsers/json.parser.d.ts +6 -0
- package/dist/realtime/parsers/json.parser.js +10 -0
- package/dist/realtime/parsers/json.parser.js.map +1 -0
- package/dist/realtime/parsers/parser.d.ts +9 -0
- package/dist/realtime/parsers/parser.js +3 -0
- package/dist/realtime/parsers/parser.js.map +1 -0
- package/dist/realtime/parsers/region-hint-parser.js +43 -0
- package/dist/realtime/parsers/region-hint.parser.d.ts +12 -0
- package/dist/realtime/parsers/region-hint.parser.js +15 -0
- package/dist/realtime/parsers/region-hint.parser.js.map +1 -0
- package/dist/realtime/parsers/skywalker-parser.js +43 -0
- package/dist/realtime/parsers/skywalker.parser.d.ts +12 -0
- package/dist/realtime/parsers/skywalker.parser.js +15 -0
- package/dist/realtime/parsers/skywalker.parser.js.map +1 -0
- package/dist/realtime/parsers-advanced.js +158 -0
- package/dist/realtime/proto/common.proto +38 -0
- package/dist/realtime/proto/direct.proto +65 -0
- package/dist/realtime/proto/ig-messages.proto +83 -0
- package/dist/realtime/proto/iris.proto +188 -0
- package/dist/realtime/proto-parser.js +195 -0
- package/dist/realtime/protocols/iris.handshake.js +74 -0
- package/dist/realtime/protocols/proto-definitions.js +80 -0
- package/dist/realtime/protocols/skywalker.protocol.js +91 -0
- package/dist/realtime/realtime.client.events.js +3 -0
- package/dist/realtime/realtime.client.js +449 -0
- package/dist/realtime/realtime.service.js +462 -0
- package/dist/realtime/reconnect.manager.js +94 -0
- package/dist/realtime/session.manager.js +121 -0
- package/dist/realtime/subscriptions/graphql.subscription.d.ts +47 -0
- package/dist/realtime/subscriptions/graphql.subscription.js +99 -0
- package/dist/realtime/subscriptions/graphql.subscription.js.map +1 -0
- package/dist/realtime/subscriptions/index.d.ts +2 -0
- package/dist/realtime/subscriptions/index.js +19 -0
- package/dist/realtime/subscriptions/index.js.map +1 -0
- package/dist/realtime/subscriptions/skywalker.subscription.d.ts +4 -0
- package/dist/realtime/subscriptions/skywalker.subscription.js +13 -0
- package/dist/realtime/subscriptions/skywalker.subscription.js.map +1 -0
- package/dist/realtime/topic-map.js +71 -0
- package/dist/realtime/topic.js +80 -0
- package/dist/repositories/account.repository.js +261 -0
- package/dist/repositories/direct-thread.repository.js +247 -0
- package/dist/repositories/direct.repository.js +153 -0
- package/dist/repositories/feed.repository.js +233 -0
- package/dist/repositories/friendship.repository.js +190 -0
- package/dist/repositories/hashtag.repository.js +101 -0
- package/dist/repositories/highlights.repository.js +127 -0
- package/dist/repositories/location.repository.js +84 -0
- package/dist/repositories/media.repository.js +165 -0
- package/dist/repositories/story.repository.js +156 -0
- package/dist/repositories/upload.repository.js +167 -0
- package/dist/repositories/user.repository.js +94 -0
- package/dist/sendmedia/index.js +11 -0
- package/dist/sendmedia/sendFile.js +154 -0
- package/dist/sendmedia/sendPhoto.js +145 -0
- package/dist/sendmedia/uploadPhoto.js +175 -0
- package/dist/sendmedia/uploadfFile.js +264 -0
- package/dist/services/live.service.js +147 -0
- package/dist/services/search.service.js +116 -0
- package/dist/shared/index.js +35 -0
- package/dist/shared/shared.js +86 -0
- package/dist/thrift/index.d.ts +3 -0
- package/dist/thrift/index.js +20 -0
- package/dist/thrift/index.js.map +1 -0
- package/dist/thrift/thrift.d.ts +59 -0
- package/dist/thrift/thrift.js +101 -0
- package/dist/thrift/thrift.js.map +1 -0
- package/dist/thrift/thrift.reading.d.ts +41 -0
- package/dist/thrift/thrift.reading.js +327 -0
- package/dist/thrift/thrift.reading.js.map +1 -0
- package/dist/thrift/thrift.writing.d.ts +44 -0
- package/dist/thrift/thrift.writing.js +342 -0
- package/dist/thrift/thrift.writing.js.map +1 -0
- package/dist/types/index.js +285 -0
- package/dist/useMultiFileAuthState.js +437 -0
- package/dist/utils/helper-1.js +1 -0
- package/dist/utils/helper-10.js +1 -0
- package/dist/utils/helper-11.js +1 -0
- package/dist/utils/helper-12.js +1 -0
- package/dist/utils/helper-13.js +1 -0
- package/dist/utils/helper-14.js +1 -0
- package/dist/utils/helper-15.js +1 -0
- package/dist/utils/helper-16.js +1 -0
- package/dist/utils/helper-17.js +1 -0
- package/dist/utils/helper-18.js +1 -0
- package/dist/utils/helper-19.js +1 -0
- package/dist/utils/helper-2.js +1 -0
- package/dist/utils/helper-20.js +1 -0
- package/dist/utils/helper-21.js +1 -0
- package/dist/utils/helper-22.js +1 -0
- package/dist/utils/helper-23.js +1 -0
- package/dist/utils/helper-24.js +1 -0
- package/dist/utils/helper-25.js +1 -0
- package/dist/utils/helper-26.js +1 -0
- package/dist/utils/helper-27.js +1 -0
- package/dist/utils/helper-28.js +1 -0
- package/dist/utils/helper-29.js +1 -0
- package/dist/utils/helper-3.js +1 -0
- package/dist/utils/helper-30.js +1 -0
- package/dist/utils/helper-4.js +1 -0
- package/dist/utils/helper-5.js +1 -0
- package/dist/utils/helper-6.js +1 -0
- package/dist/utils/helper-7.js +1 -0
- package/dist/utils/helper-8.js +1 -0
- package/dist/utils/helper-9.js +1 -0
- package/dist/utils/index.js +280 -0
- package/examples/listen-to-messages.js +86 -0
- package/package.json +79 -0
|
@@ -0,0 +1,786 @@
|
|
|
1
|
+
// Utils.js
|
|
2
|
+
// Big, comprehensive utility module for nodejs-insta-private-api style libraries.
|
|
3
|
+
// CommonJS module.
|
|
4
|
+
//
|
|
5
|
+
// NOTE: Depends only on Node built-ins: crypto, fs, path, os, util, zlib
|
|
6
|
+
// If you want additional features (image resizing, filesystem vault, secure storage),
|
|
7
|
+
// tell me and I can add optional integration points.
|
|
8
|
+
|
|
9
|
+
const crypto = require('crypto');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const os = require('os');
|
|
13
|
+
const util = require('util');
|
|
14
|
+
const zlib = require('zlib');
|
|
15
|
+
|
|
16
|
+
const pipeline = util.promisify(require('stream').pipeline);
|
|
17
|
+
const pbkdf2 = util.promisify(crypto.pbkdf2);
|
|
18
|
+
const scrypt = util.promisify(crypto.scrypt);
|
|
19
|
+
|
|
20
|
+
const DEFAULT_ENCODING = 'utf8';
|
|
21
|
+
|
|
22
|
+
class Utils {
|
|
23
|
+
/* -------------------------
|
|
24
|
+
Basic randomness & ids
|
|
25
|
+
------------------------- */
|
|
26
|
+
|
|
27
|
+
// Use crypto.randomUUID if available (Node 14.17+), else fallback
|
|
28
|
+
static generateUUID() {
|
|
29
|
+
if (crypto.randomUUID) return crypto.randomUUID();
|
|
30
|
+
// fallback RFC4122 v4
|
|
31
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
|
32
|
+
const r = (crypto.randomBytes(1)[0] & 0xf) >>> 0;
|
|
33
|
+
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
|
34
|
+
return v.toString(16);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Cryptographically secure random string (alphanumeric)
|
|
39
|
+
static generateRandomString(length = 32) {
|
|
40
|
+
const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
41
|
+
const bytes = crypto.randomBytes(length);
|
|
42
|
+
let str = '';
|
|
43
|
+
for (let i = 0; i < length; i++) {
|
|
44
|
+
str += ALPHABET[bytes[i] % ALPHABET.length];
|
|
45
|
+
}
|
|
46
|
+
return str;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Secure random hex
|
|
50
|
+
static generateRandomHex(length = 16) {
|
|
51
|
+
return crypto.randomBytes(Math.ceil(length / 2)).toString('hex').slice(0, length);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static generateDeviceId() {
|
|
55
|
+
// Android-like device id
|
|
56
|
+
return 'android-' + this.generateRandomHex(16);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static generatePhoneId() {
|
|
60
|
+
return this.generateUUID();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
static generateAdId() {
|
|
64
|
+
return this.generateUUID();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Device fingerprint: lightweight deterministic fingerprint from device object
|
|
68
|
+
static generateDeviceFingerprint(device = {}) {
|
|
69
|
+
const fields = [
|
|
70
|
+
device.manufacturer || '',
|
|
71
|
+
device.model || '',
|
|
72
|
+
device.android_release || '',
|
|
73
|
+
device.cpu || '',
|
|
74
|
+
device.resolution || '',
|
|
75
|
+
device.dpi || ''
|
|
76
|
+
].join('|');
|
|
77
|
+
return this.sha256(fields).substr(0, 32);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* -------------------------
|
|
81
|
+
Random ints and helpers
|
|
82
|
+
------------------------- */
|
|
83
|
+
|
|
84
|
+
static secureRandomInt(min = 0, max = 1) {
|
|
85
|
+
// inclusive
|
|
86
|
+
if (min > max) [min, max] = [max, min];
|
|
87
|
+
const range = max - min + 1;
|
|
88
|
+
if (range <= 0) return min;
|
|
89
|
+
const randBytes = crypto.randomBytes(4).readUInt32BE(0);
|
|
90
|
+
return min + (randBytes % range);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
static randomInt(min, max) {
|
|
94
|
+
// non-crypto fallback (for performance), keep available
|
|
95
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* -------------------------
|
|
99
|
+
Timing utilities
|
|
100
|
+
------------------------- */
|
|
101
|
+
|
|
102
|
+
static sleep(ms) {
|
|
103
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
static randomDelay(min = 1000, max = 3000) {
|
|
107
|
+
return this.sleep(this.secureRandomInt(min, max));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
static getCurrentTimestamp() {
|
|
111
|
+
return Math.floor(Date.now() / 1000);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
static getTimestampMs() {
|
|
115
|
+
return Date.now();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* -------------------------
|
|
119
|
+
Hashing & HMAC
|
|
120
|
+
------------------------- */
|
|
121
|
+
|
|
122
|
+
static md5(data) {
|
|
123
|
+
return crypto.createHash('md5').update(String(data)).digest('hex');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
static sha1(data) {
|
|
127
|
+
return crypto.createHash('sha1').update(String(data)).digest('hex');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
static sha256(data) {
|
|
131
|
+
return crypto.createHash('sha256').update(String(data)).digest('hex');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
static sha512(data) {
|
|
135
|
+
return crypto.createHash('sha512').update(String(data)).digest('hex');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
static hmacSha1(data, key, out = 'hex') {
|
|
139
|
+
return crypto.createHmac('sha1', String(key)).update(String(data)).digest(out);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
static hmacSha256(data, key, out = 'hex') {
|
|
143
|
+
return crypto.createHmac('sha256', String(key)).update(String(data)).digest(out);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
static hmacSha512(data, key, out = 'hex') {
|
|
147
|
+
return crypto.createHmac('sha512', String(key)).update(String(data)).digest(out);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/* -------------------------
|
|
151
|
+
Base64 helpers
|
|
152
|
+
------------------------- */
|
|
153
|
+
|
|
154
|
+
static base64Encode(data) {
|
|
155
|
+
if (Buffer.isBuffer(data)) return data.toString('base64');
|
|
156
|
+
return Buffer.from(String(data), DEFAULT_ENCODING).toString('base64');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
static base64Decode(data) {
|
|
160
|
+
return Buffer.from(String(data), 'base64').toString(DEFAULT_ENCODING);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
static base64UrlEncode(data) {
|
|
164
|
+
return this.base64Encode(data).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
static base64UrlDecode(data) {
|
|
168
|
+
let s = String(data).replace(/-/g, '+').replace(/_/g, '/');
|
|
169
|
+
while (s.length % 4) s += '=';
|
|
170
|
+
return this.base64Decode(s);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/* -------------------------
|
|
174
|
+
Symmetric encryption (AES-GCM)
|
|
175
|
+
------------------------- */
|
|
176
|
+
|
|
177
|
+
static aesGcmEncrypt(plainText, key) {
|
|
178
|
+
// key: Buffer or hex string of 32 bytes (256 bit)
|
|
179
|
+
const keyBuf = Buffer.isBuffer(key) ? key : Buffer.from(String(key), 'hex');
|
|
180
|
+
const iv = crypto.randomBytes(12);
|
|
181
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', keyBuf, iv);
|
|
182
|
+
const ciphertext = Buffer.concat([cipher.update(String(plainText), DEFAULT_ENCODING), cipher.final()]);
|
|
183
|
+
const tag = cipher.getAuthTag();
|
|
184
|
+
// return iv + tag + ciphertext in base64
|
|
185
|
+
return Buffer.concat([iv, tag, ciphertext]).toString('base64');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
static aesGcmDecrypt(b64Payload, key) {
|
|
189
|
+
const keyBuf = Buffer.isBuffer(key) ? key : Buffer.from(String(key), 'hex');
|
|
190
|
+
const data = Buffer.from(String(b64Payload), 'base64');
|
|
191
|
+
const iv = data.slice(0, 12);
|
|
192
|
+
const tag = data.slice(12, 28);
|
|
193
|
+
const ciphertext = data.slice(28);
|
|
194
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', keyBuf, iv);
|
|
195
|
+
decipher.setAuthTag(tag);
|
|
196
|
+
const plain = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
197
|
+
return plain.toString(DEFAULT_ENCODING);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// AES-CBC with PKCS#7 (for legacy)
|
|
201
|
+
static aesCbcEncrypt(plainText, key, iv = null) {
|
|
202
|
+
const keyBuf = Buffer.isBuffer(key) ? key : Buffer.from(String(key), 'hex');
|
|
203
|
+
iv = iv ? (Buffer.isBuffer(iv) ? iv : Buffer.from(String(iv), 'hex')) : crypto.randomBytes(16);
|
|
204
|
+
const cipher = crypto.createCipheriv('aes-256-cbc', keyBuf, iv);
|
|
205
|
+
const ciphertext = Buffer.concat([cipher.update(String(plainText), DEFAULT_ENCODING), cipher.final()]);
|
|
206
|
+
return Buffer.concat([iv, ciphertext]).toString('base64');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
static aesCbcDecrypt(b64Payload, key) {
|
|
210
|
+
const data = Buffer.from(String(b64Payload), 'base64');
|
|
211
|
+
const iv = data.slice(0, 16);
|
|
212
|
+
const ciphertext = data.slice(16);
|
|
213
|
+
const keyBuf = Buffer.isBuffer(key) ? key : Buffer.from(String(key), 'hex');
|
|
214
|
+
const decipher = crypto.createDecipheriv('aes-256-cbc', keyBuf, iv);
|
|
215
|
+
const plain = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
216
|
+
return plain.toString(DEFAULT_ENCODING);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/* -------------------------
|
|
220
|
+
RSA helpers
|
|
221
|
+
------------------------- */
|
|
222
|
+
|
|
223
|
+
static rsaGenerateKeyPairSync(modulusLength = 2048) {
|
|
224
|
+
return crypto.generateKeyPairSync('rsa', {
|
|
225
|
+
modulusLength,
|
|
226
|
+
publicExponent: 0x10001,
|
|
227
|
+
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
|
228
|
+
privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
static rsaEncrypt(publicKeyPem, text) {
|
|
233
|
+
return crypto.publicEncrypt({ key: publicKeyPem, padding: crypto.constants.RSA_PKCS1_OAEP_PADDING }, Buffer.from(String(text))).toString('base64');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
static rsaDecrypt(privateKeyPem, b64payload) {
|
|
237
|
+
const buf = Buffer.from(String(b64payload), 'base64');
|
|
238
|
+
return crypto.privateDecrypt({ key: privateKeyPem, padding: crypto.constants.RSA_PKCS1_OAEP_PADDING }, buf).toString(DEFAULT_ENCODING);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/* -------------------------
|
|
242
|
+
Key derivation
|
|
243
|
+
------------------------- */
|
|
244
|
+
|
|
245
|
+
static async pbkdf2Derive(password, salt, iterations = 100000, keylen = 32, digest = 'sha256') {
|
|
246
|
+
const derived = await pbkdf2(String(password), String(salt), iterations, keylen, digest);
|
|
247
|
+
return derived.toString('hex');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
static async scryptDerive(password, salt, keylen = 32) {
|
|
251
|
+
const derived = await scrypt(String(password), String(salt), keylen);
|
|
252
|
+
return derived.toString('hex');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/* -------------------------
|
|
256
|
+
JSON / file helpers (atomic)
|
|
257
|
+
------------------------- */
|
|
258
|
+
|
|
259
|
+
static safeJsonParse(str, fallback = null) {
|
|
260
|
+
try {
|
|
261
|
+
return JSON.parse(str);
|
|
262
|
+
} catch (e) {
|
|
263
|
+
return fallback;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
static safeJsonStringify(obj, replacer = null, space = 2) {
|
|
268
|
+
// scrub functions or Buffer
|
|
269
|
+
return JSON.stringify(obj, replacer, space);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
static ensureDirExists(filePath) {
|
|
273
|
+
const dir = path.dirname(filePath);
|
|
274
|
+
if (!fs.existsSync(dir)) {
|
|
275
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
static readJsonFile(filePath, fallback = null) {
|
|
280
|
+
try {
|
|
281
|
+
const raw = fs.readFileSync(filePath, DEFAULT_ENCODING);
|
|
282
|
+
return this.safeJsonParse(raw, fallback);
|
|
283
|
+
} catch (e) {
|
|
284
|
+
return fallback;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
static writeJsonFileAtomic(filePath, data, opt = {}) {
|
|
289
|
+
const tmp = `${filePath}.${process.pid}.${this.generateRandomHex(6)}.tmp`;
|
|
290
|
+
const dir = path.dirname(filePath);
|
|
291
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
292
|
+
const json = typeof data === 'string' ? data : this.safeJsonStringify(data, null, opt.space || 2);
|
|
293
|
+
fs.writeFileSync(tmp, json, { encoding: DEFAULT_ENCODING, mode: 0o600 });
|
|
294
|
+
fs.renameSync(tmp, filePath);
|
|
295
|
+
return true;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/* -------------------------
|
|
299
|
+
Session helpers (convenience)
|
|
300
|
+
------------------------- */
|
|
301
|
+
|
|
302
|
+
static readSession(sessionFile) {
|
|
303
|
+
return this.readJsonFile(sessionFile, {});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
static saveSession(sessionFile, sessionObject) {
|
|
307
|
+
return this.writeJsonFileAtomic(sessionFile, sessionObject, { space: 2 });
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
static removeSession(sessionFile) {
|
|
311
|
+
try {
|
|
312
|
+
if (fs.existsSync(sessionFile)) fs.unlinkSync(sessionFile);
|
|
313
|
+
return true;
|
|
314
|
+
} catch (e) {
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/* -------------------------
|
|
320
|
+
Cookies & headers
|
|
321
|
+
------------------------- */
|
|
322
|
+
|
|
323
|
+
static parseSetCookieHeader(setCookieHeader) {
|
|
324
|
+
// Accept single header or array
|
|
325
|
+
const arr = Array.isArray(setCookieHeader) ? setCookieHeader : [setCookieHeader];
|
|
326
|
+
const cookies = {};
|
|
327
|
+
for (const hdr of arr) {
|
|
328
|
+
if (!hdr) continue;
|
|
329
|
+
const parts = hdr.split(';').map(s => s.trim());
|
|
330
|
+
const [k, v] = parts[0].split('=');
|
|
331
|
+
if (!k) continue;
|
|
332
|
+
cookies[k] = decodeURIComponent(v || '');
|
|
333
|
+
}
|
|
334
|
+
return cookies;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
static serializeCookie(name, value, options = {}) {
|
|
338
|
+
let s = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
|
|
339
|
+
if (options.maxAge) s += `; Max-Age=${options.maxAge}`;
|
|
340
|
+
if (options.domain) s += `; Domain=${options.domain}`;
|
|
341
|
+
if (options.path) s += `; Path=${options.path}`;
|
|
342
|
+
if (options.expires) s += `; Expires=${options.expires.toUTCString()}`;
|
|
343
|
+
if (options.httpOnly) s += '; HttpOnly';
|
|
344
|
+
if (options.secure) s += '; Secure';
|
|
345
|
+
if (options.sameSite) s += `; SameSite=${options.sameSite}`;
|
|
346
|
+
return s;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
static mergeCookieObjects(...cookieObjs) {
|
|
350
|
+
return Object.assign({}, ...cookieObjs);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
static buildCookieHeader(cookieObj) {
|
|
354
|
+
return Object.entries(cookieObj).map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join('; ');
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
static buildHeaders({ userAgent, csrfToken, referer, extra = {} } = {}) {
|
|
358
|
+
const base = {
|
|
359
|
+
'User-Agent': userAgent || this.formatUserAgent('401.0.0.48.79', 'Pixel', 'en_US', '40104879'),
|
|
360
|
+
'Accept': 'application/json, text/javascript, */*; q=0.01',
|
|
361
|
+
'Accept-Language': 'en-US,en;q=0.9',
|
|
362
|
+
'X-Requested-With': 'XMLHttpRequest',
|
|
363
|
+
...extra
|
|
364
|
+
};
|
|
365
|
+
if (csrfToken) base['X-CSRFToken'] = csrfToken;
|
|
366
|
+
if (referer) base['Referer'] = referer;
|
|
367
|
+
return base;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/* -------------------------
|
|
371
|
+
User agent builders
|
|
372
|
+
------------------------- */
|
|
373
|
+
|
|
374
|
+
static formatUserAgent(appVersion = '401.0.0.48.79', deviceString = 'Pixel 5', language = 'en_US', appVersionCode = '40104879') {
|
|
375
|
+
return `Instagram ${appVersion} Android (${deviceString}; ${language}; ${appVersionCode})`;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
static formatWebUserAgent(devicePayload = {}, build = 'OPM1', appUserAgent = '') {
|
|
379
|
+
return `Mozilla/5.0 (Linux; Android ${devicePayload.android_release || '11'}; ${devicePayload.model || 'Pixel'}) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.110 Mobile Safari/537.36 ${appUserAgent}`;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
static createUserAgentFromDevice(device = {}) {
|
|
383
|
+
return `Instagram ${device.appVersion || '401.0.0.48.79'} Android (${device.android_version || device.android_release || '11'}/${device.android_release || '11'}; ${device.dpi || ''}dpi; ${device.resolution || '1080x1920'}; ${device.manufacturer || 'Google'}; ${device.model || 'Pixel'}; ${device.device || 'walleye'}; ${device.cpu || 'arm64-v8a'})`;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/* -------------------------
|
|
387
|
+
Error handling/humanize
|
|
388
|
+
------------------------- */
|
|
389
|
+
|
|
390
|
+
static humanizeError(error) {
|
|
391
|
+
if (!error) return 'Unknown error';
|
|
392
|
+
const name = error.name || (error.code ? String(error.code) : null);
|
|
393
|
+
const message = error.message || String(error);
|
|
394
|
+
const map = {
|
|
395
|
+
'IgLoginBadPasswordError': 'The password you entered is incorrect. Please check your password and try again.',
|
|
396
|
+
'IgLoginInvalidUserError': "The username you entered doesn't appear to belong to an account. Please check your username and try again.",
|
|
397
|
+
'IgLoginTwoFactorRequiredError': 'Two-factor authentication is required. Please enter the verification code.',
|
|
398
|
+
'IgCheckpointError': 'Instagram requires additional verification. Please complete the security challenge.',
|
|
399
|
+
'IgActionSpamError': 'This action has been blocked by Instagram\'s spam detection. Please try again later.',
|
|
400
|
+
'IgNotFoundError': 'The requested content could not be found.',
|
|
401
|
+
'IgPrivateUserError': 'This account is private. You must follow this user to see their content.',
|
|
402
|
+
'IgUserHasLoggedOutError': 'Your session has expired. Please log in again.',
|
|
403
|
+
'IgInactiveUserError': 'This account is inactive or has been suspended.',
|
|
404
|
+
'IgSentryBlockError': 'This request has been blocked by Instagram\'s security system.',
|
|
405
|
+
'IgNetworkError': 'A network error occurred. Please check your internet connection and try again.',
|
|
406
|
+
'IgUploadError': 'Failed to upload the file. Please check the file format and size.',
|
|
407
|
+
'IgConfigureMediaError': 'Failed to configure the media. Please try again.',
|
|
408
|
+
};
|
|
409
|
+
return map[name] || message;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
static parseInstagramError(responseBody) {
|
|
413
|
+
// safe parse for common Instagram error shapes
|
|
414
|
+
try {
|
|
415
|
+
const body = typeof responseBody === 'string' ? JSON.parse(responseBody) : responseBody;
|
|
416
|
+
if (!body) return null;
|
|
417
|
+
if (body.message && body.status) return { message: body.message, status: body.status, error: body.error || null };
|
|
418
|
+
if (body.error_type || body.error_message) return { message: body.error_message || body.error_type, status: 'fail' };
|
|
419
|
+
if (body.errors) return { message: JSON.stringify(body.errors), status: 'fail' };
|
|
420
|
+
return { message: JSON.stringify(body), status: 'unknown' };
|
|
421
|
+
} catch (e) {
|
|
422
|
+
return { message: String(responseBody), status: 'unknown' };
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/* -------------------------
|
|
427
|
+
File & media helpers
|
|
428
|
+
------------------------- */
|
|
429
|
+
|
|
430
|
+
static validateFileSize(filePath, maxSizeBytes) {
|
|
431
|
+
try {
|
|
432
|
+
const stats = fs.statSync(filePath);
|
|
433
|
+
return stats.size <= maxSizeBytes;
|
|
434
|
+
} catch (error) {
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
static getFileExtension(filePath) {
|
|
440
|
+
return (filePath || '').split('.').pop().toLowerCase();
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
static isImageFile(filePath) {
|
|
444
|
+
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'heic'];
|
|
445
|
+
const extension = this.getFileExtension(filePath);
|
|
446
|
+
return imageExtensions.includes(extension);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
static isVideoFile(filePath) {
|
|
450
|
+
const videoExtensions = ['mp4', 'mov', 'avi', 'mkv', 'webm', '3gp', 'mpeg'];
|
|
451
|
+
const extension = this.getFileExtension(filePath);
|
|
452
|
+
return videoExtensions.includes(extension);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
static mimeTypeForExtension(ext) {
|
|
456
|
+
const m = (ext || '').toLowerCase();
|
|
457
|
+
const map = {
|
|
458
|
+
jpg: 'image/jpeg',
|
|
459
|
+
jpeg: 'image/jpeg',
|
|
460
|
+
png: 'image/png',
|
|
461
|
+
gif: 'image/gif',
|
|
462
|
+
webp: 'image/webp',
|
|
463
|
+
mp4: 'video/mp4',
|
|
464
|
+
mov: 'video/quicktime',
|
|
465
|
+
mkv: 'video/x-matroska',
|
|
466
|
+
webm: 'video/webm',
|
|
467
|
+
txt: 'text/plain',
|
|
468
|
+
json: 'application/json',
|
|
469
|
+
html: 'text/html'
|
|
470
|
+
};
|
|
471
|
+
return map[m] || 'application/octet-stream';
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/* -------------------------
|
|
475
|
+
Arrays, chunking, flattening
|
|
476
|
+
------------------------- */
|
|
477
|
+
|
|
478
|
+
static chunkArray(array, chunkSize) {
|
|
479
|
+
if (!Array.isArray(array) || chunkSize <= 0) return [];
|
|
480
|
+
const chunks = [];
|
|
481
|
+
for (let i = 0; i < array.length; i += chunkSize) {
|
|
482
|
+
chunks.push(array.slice(i, i + chunkSize));
|
|
483
|
+
}
|
|
484
|
+
return chunks;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
static flattenArray(arr) {
|
|
488
|
+
return arr.reduce((acc, v) => acc.concat(Array.isArray(v) ? this.flattenArray(v) : v), []);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/* -------------------------
|
|
492
|
+
Retry/backoff utilities
|
|
493
|
+
------------------------- */
|
|
494
|
+
|
|
495
|
+
static async retryOperation(operation, maxRetries = 5, baseDelay = 500, jitter = true, onRetry = null) {
|
|
496
|
+
let attempt = 0;
|
|
497
|
+
while (true) {
|
|
498
|
+
try {
|
|
499
|
+
return await operation();
|
|
500
|
+
} catch (err) {
|
|
501
|
+
attempt++;
|
|
502
|
+
if (attempt > maxRetries) throw err;
|
|
503
|
+
let delay = baseDelay * Math.pow(2, attempt - 1);
|
|
504
|
+
if (jitter) {
|
|
505
|
+
const jitterMs = this.secureRandomInt(0, Math.floor(delay * 0.5));
|
|
506
|
+
delay = Math.max(0, delay - jitterMs);
|
|
507
|
+
}
|
|
508
|
+
if (typeof onRetry === 'function') {
|
|
509
|
+
try { onRetry(attempt, err, delay); } catch (e) {}
|
|
510
|
+
}
|
|
511
|
+
await this.sleep(delay);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
static backoffGenerator({ base = 500, factor = 2, max = 60000, jitter = true } = {}) {
|
|
517
|
+
let attempt = 0;
|
|
518
|
+
return () => {
|
|
519
|
+
attempt++;
|
|
520
|
+
let delay = Math.min(max, Math.floor(base * Math.pow(factor, attempt - 1)));
|
|
521
|
+
if (jitter) delay = Math.floor(delay * (0.5 + Math.random() * 0.5));
|
|
522
|
+
return delay;
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/* -------------------------
|
|
527
|
+
Concurrency helpers: Promise pool, semaphore
|
|
528
|
+
------------------------- */
|
|
529
|
+
|
|
530
|
+
static async promisePool(items = [], workerFn, concurrency = 5) {
|
|
531
|
+
const results = [];
|
|
532
|
+
let idx = 0;
|
|
533
|
+
const next = async () => {
|
|
534
|
+
while (idx < items.length) {
|
|
535
|
+
const current = idx++;
|
|
536
|
+
try {
|
|
537
|
+
results[current] = await workerFn(items[current], current);
|
|
538
|
+
} catch (err) {
|
|
539
|
+
results[current] = { error: err };
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
const workers = [];
|
|
544
|
+
for (let i = 0; i < Math.max(1, concurrency); i++) workers.push(next());
|
|
545
|
+
await Promise.all(workers);
|
|
546
|
+
return results;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
static createSemaphore(max = 1) {
|
|
550
|
+
let counter = max;
|
|
551
|
+
const waiting = [];
|
|
552
|
+
return {
|
|
553
|
+
async acquire() {
|
|
554
|
+
if (counter > 0) {
|
|
555
|
+
counter -= 1;
|
|
556
|
+
return () => {
|
|
557
|
+
counter += 1;
|
|
558
|
+
if (waiting.length > 0) {
|
|
559
|
+
const next = waiting.shift();
|
|
560
|
+
next();
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
return new Promise(resolve => {
|
|
565
|
+
waiting.push(() => {
|
|
566
|
+
counter -= 1;
|
|
567
|
+
resolve(() => {
|
|
568
|
+
counter += 1;
|
|
569
|
+
if (waiting.length > 0) {
|
|
570
|
+
const next = waiting.shift();
|
|
571
|
+
next();
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
});
|
|
575
|
+
});
|
|
576
|
+
},
|
|
577
|
+
available() { return counter; }
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/* -------------------------
|
|
582
|
+
Rate limiter: Token bucket
|
|
583
|
+
------------------------- */
|
|
584
|
+
|
|
585
|
+
static createTokenBucket({ capacity = 10, refillRate = 1 } = {}) {
|
|
586
|
+
// refillRate tokens per second
|
|
587
|
+
let tokens = capacity;
|
|
588
|
+
let last = Date.now();
|
|
589
|
+
return {
|
|
590
|
+
consume(amount = 1) {
|
|
591
|
+
const now = Date.now();
|
|
592
|
+
const elapsed = (now - last) / 1000;
|
|
593
|
+
last = now;
|
|
594
|
+
tokens = Math.min(capacity, tokens + elapsed * refillRate);
|
|
595
|
+
if (tokens >= amount) {
|
|
596
|
+
tokens -= amount;
|
|
597
|
+
return true;
|
|
598
|
+
}
|
|
599
|
+
return false;
|
|
600
|
+
},
|
|
601
|
+
waitForToken(timeoutMs = 60000) {
|
|
602
|
+
return new Promise((resolve, reject) => {
|
|
603
|
+
const start = Date.now();
|
|
604
|
+
const tryConsume = () => {
|
|
605
|
+
if (this.consume()) {
|
|
606
|
+
resolve(true);
|
|
607
|
+
} else if (Date.now() - start >= timeoutMs) {
|
|
608
|
+
reject(new Error('Timeout waiting for token'));
|
|
609
|
+
} else {
|
|
610
|
+
setTimeout(tryConsume, 100);
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
tryConsume();
|
|
614
|
+
});
|
|
615
|
+
},
|
|
616
|
+
getTokens() { return tokens; },
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/* -------------------------
|
|
621
|
+
Debounce / Throttle
|
|
622
|
+
------------------------- */
|
|
623
|
+
|
|
624
|
+
static debounce(fn, wait = 200) {
|
|
625
|
+
let timer = null;
|
|
626
|
+
return function(...args) {
|
|
627
|
+
clearTimeout(timer);
|
|
628
|
+
timer = setTimeout(() => fn.apply(this, args), wait);
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
static throttle(fn, wait = 200) {
|
|
633
|
+
let last = 0;
|
|
634
|
+
let timer = null;
|
|
635
|
+
return function(...args) {
|
|
636
|
+
const now = Date.now();
|
|
637
|
+
const remaining = wait - (now - last);
|
|
638
|
+
if (remaining <= 0) {
|
|
639
|
+
clearTimeout(timer);
|
|
640
|
+
timer = null;
|
|
641
|
+
last = now;
|
|
642
|
+
fn.apply(this, args);
|
|
643
|
+
} else if (!timer) {
|
|
644
|
+
timer = setTimeout(() => {
|
|
645
|
+
last = Date.now();
|
|
646
|
+
timer = null;
|
|
647
|
+
fn.apply(this, args);
|
|
648
|
+
}, remaining);
|
|
649
|
+
}
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/* -------------------------
|
|
654
|
+
Text / hex helpers
|
|
655
|
+
------------------------- */
|
|
656
|
+
|
|
657
|
+
static toHex(str) {
|
|
658
|
+
return Buffer.from(String(str), DEFAULT_ENCODING).toString('hex');
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
static fromHex(hex) {
|
|
662
|
+
return Buffer.from(String(hex), 'hex').toString(DEFAULT_ENCODING);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/* -------------------------
|
|
666
|
+
URL / querystring helpers
|
|
667
|
+
------------------------- */
|
|
668
|
+
|
|
669
|
+
static parseQueryString(qs) {
|
|
670
|
+
if (!qs) return {};
|
|
671
|
+
return String(qs).split('&').reduce((acc, part) => {
|
|
672
|
+
const [k, v] = part.split('=');
|
|
673
|
+
if (!k) return acc;
|
|
674
|
+
acc[decodeURIComponent(k)] = v ? decodeURIComponent(v) : '';
|
|
675
|
+
return acc;
|
|
676
|
+
}, {});
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
static buildQueryString(obj = {}) {
|
|
680
|
+
return Object.keys(obj).map(k => `${encodeURIComponent(k)}=${encodeURIComponent(String(obj[k]))}`).join('&');
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/* -------------------------
|
|
684
|
+
Logging helpers (colored)
|
|
685
|
+
------------------------- */
|
|
686
|
+
|
|
687
|
+
static colorize(text, color = 'reset') {
|
|
688
|
+
const codes = {
|
|
689
|
+
reset: '\x1b[0m',
|
|
690
|
+
red: '\x1b[31m',
|
|
691
|
+
green: '\x1b[32m',
|
|
692
|
+
yellow: '\x1b[33m',
|
|
693
|
+
blue: '\x1b[34m',
|
|
694
|
+
magenta: '\x1b[35m',
|
|
695
|
+
cyan: '\x1b[36m',
|
|
696
|
+
white: '\x1b[37m',
|
|
697
|
+
grey: '\x1b[90m'
|
|
698
|
+
};
|
|
699
|
+
return (codes[color] || codes.reset) + text + codes.reset;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
static logRed(...args) {
|
|
703
|
+
console.log(this.colorize(args.map(String).join(' '), 'red'));
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
static logGreen(...args) {
|
|
707
|
+
console.log(this.colorize(args.map(String).join(' '), 'green'));
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/* -------------------------
|
|
711
|
+
System / environment helpers
|
|
712
|
+
------------------------- */
|
|
713
|
+
|
|
714
|
+
static detectProxyFromEnv() {
|
|
715
|
+
const env = process.env;
|
|
716
|
+
return env.HTTP_PROXY || env.http_proxy || env.HTTPS_PROXY || env.https_proxy || null;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
static getDefaultSessionPath(fileName = 'session.json') {
|
|
720
|
+
const home = process.env.HOME || process.cwd();
|
|
721
|
+
return path.join(home, '.nodejs-insta-private-api', fileName);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/* -------------------------
|
|
725
|
+
Utilities for request signing / payloads
|
|
726
|
+
------------------------- */
|
|
727
|
+
|
|
728
|
+
static signPayloadHMAC(payload, key) {
|
|
729
|
+
// payload assumed to be string or Buffer
|
|
730
|
+
return this.hmacSha256(payload, key, 'hex');
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
static signJsonObject(obj, key) {
|
|
734
|
+
const json = this.safeJsonStringify(obj);
|
|
735
|
+
return this.hmacSha256(json, key, 'hex');
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/* -------------------------
|
|
739
|
+
Misc helpers
|
|
740
|
+
------------------------- */
|
|
741
|
+
|
|
742
|
+
static ensureArray(val) {
|
|
743
|
+
if (val == null) return [];
|
|
744
|
+
return Array.isArray(val) ? val : [val];
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
static nowIso() {
|
|
748
|
+
return new Date().toISOString();
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/* -------------------------
|
|
752
|
+
Compression helpers
|
|
753
|
+
------------------------- */
|
|
754
|
+
|
|
755
|
+
static async gzipBuffer(buffer) {
|
|
756
|
+
return new Promise((resolve, reject) => {
|
|
757
|
+
zlib.gzip(buffer, (err, res) => {
|
|
758
|
+
if (err) reject(err); else resolve(res);
|
|
759
|
+
});
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
static async gunzipBuffer(buffer) {
|
|
764
|
+
return new Promise((resolve, reject) => {
|
|
765
|
+
zlib.gunzip(buffer, (err, res) => {
|
|
766
|
+
if (err) reject(err); else resolve(res);
|
|
767
|
+
});
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/* -------------------------
|
|
772
|
+
Utility: print summary of this utils (debug)
|
|
773
|
+
------------------------- */
|
|
774
|
+
|
|
775
|
+
static describe() {
|
|
776
|
+
return {
|
|
777
|
+
version: 'utils-2025-10-14',
|
|
778
|
+
node: process.version,
|
|
779
|
+
platform: process.platform,
|
|
780
|
+
arch: process.arch,
|
|
781
|
+
now: this.nowIso()
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
module.exports = Utils;
|