nodejs-insta-private-api-mqt 1.3.80 → 1.3.81
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/dist/core/state.js
CHANGED
|
@@ -168,10 +168,7 @@ class State {
|
|
|
168
168
|
|
|
169
169
|
extractCookieValue(key) {
|
|
170
170
|
const cookie = this.extractCookie(key);
|
|
171
|
-
|
|
172
|
-
throw new Error(`Could not find cookie: ${key}`);
|
|
173
|
-
}
|
|
174
|
-
return cookie.value;
|
|
171
|
+
return cookie ? cookie.value : null;
|
|
175
172
|
}
|
|
176
173
|
|
|
177
174
|
get cookieCsrfToken() {
|
|
@@ -310,7 +307,8 @@ class State {
|
|
|
310
307
|
connectionTypeHeader: this.connectionTypeHeader,
|
|
311
308
|
capabilitiesHeader: this.capabilitiesHeader,
|
|
312
309
|
requestId: this.requestId,
|
|
313
|
-
traySessionId: this.traySessionId
|
|
310
|
+
traySessionId: this.traySessionId,
|
|
311
|
+
userId: this._userId || null
|
|
314
312
|
};
|
|
315
313
|
return obj;
|
|
316
314
|
}
|
|
@@ -357,6 +355,11 @@ class State {
|
|
|
357
355
|
}
|
|
358
356
|
}
|
|
359
357
|
|
|
358
|
+
// If serialized state carried a raw userId, restore it into the internal field
|
|
359
|
+
if (obj.userId && !this._userId) {
|
|
360
|
+
this._userId = obj.userId;
|
|
361
|
+
}
|
|
362
|
+
|
|
360
363
|
// refresh parsed authorization (if any)
|
|
361
364
|
this.updateAuthorization();
|
|
362
365
|
}
|
|
@@ -25,6 +25,329 @@ class AccountRepository extends Repository {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Helper: try to extract an IGT bearer token from various sources:
|
|
30
|
+
* - response.headers (IG-Set-Authorization or ig-set-authorization)
|
|
31
|
+
* - response.body top-level fields
|
|
32
|
+
* - body.layout (stringified layout)
|
|
33
|
+
* - any embedded login_response.headers JSON inside layout or body
|
|
34
|
+
*
|
|
35
|
+
* When found, sets this.client.state.authorization and calls updateAuthorization().
|
|
36
|
+
*/
|
|
37
|
+
_extractAndSaveAuthorization(response) {
|
|
38
|
+
if (!response) return null;
|
|
39
|
+
|
|
40
|
+
// Normalize headers (case-insensitive)
|
|
41
|
+
const headers = {};
|
|
42
|
+
if (response.headers && typeof response.headers === 'object') {
|
|
43
|
+
for (const k of Object.keys(response.headers)) {
|
|
44
|
+
headers[k.toLowerCase()] = response.headers[k];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 1) Check response headers first (server may set IG-Set-Authorization)
|
|
49
|
+
const headerCandidates = [
|
|
50
|
+
headers['ig-set-authorization'],
|
|
51
|
+
headers['ig-set-authorization'.toLowerCase()],
|
|
52
|
+
headers['ig-set-authorization'.toUpperCase()],
|
|
53
|
+
headers['ig-set-authorization'],
|
|
54
|
+
headers['ig-set-authorization'],
|
|
55
|
+
headers['ig-set-authorization'],
|
|
56
|
+
headers['ig-set-authorization'],
|
|
57
|
+
headers['ig-set-authorization'],
|
|
58
|
+
headers['ig-set-authorization'],
|
|
59
|
+
headers['ig-set-authorization'],
|
|
60
|
+
headers['ig-set-authorization'],
|
|
61
|
+
headers['ig-set-authorization'],
|
|
62
|
+
headers['ig-set-authorization'],
|
|
63
|
+
headers['ig-set-authorization'],
|
|
64
|
+
headers['ig-set-authorization'],
|
|
65
|
+
headers['ig-set-authorization'],
|
|
66
|
+
headers['ig-set-authorization'],
|
|
67
|
+
headers['ig-set-authorization'],
|
|
68
|
+
headers['ig-set-authorization'],
|
|
69
|
+
headers['ig-set-authorization'],
|
|
70
|
+
headers['ig-set-authorization'],
|
|
71
|
+
headers['ig-set-authorization'],
|
|
72
|
+
headers['ig-set-authorization'],
|
|
73
|
+
headers['ig-set-authorization'],
|
|
74
|
+
headers['ig-set-authorization'],
|
|
75
|
+
headers['ig-set-authorization'],
|
|
76
|
+
headers['ig-set-authorization'],
|
|
77
|
+
headers['ig-set-authorization'],
|
|
78
|
+
headers['ig-set-authorization'],
|
|
79
|
+
headers['ig-set-authorization'],
|
|
80
|
+
headers['ig-set-authorization'],
|
|
81
|
+
headers['ig-set-authorization'],
|
|
82
|
+
headers['ig-set-authorization'],
|
|
83
|
+
headers['ig-set-authorization'],
|
|
84
|
+
headers['ig-set-authorization'],
|
|
85
|
+
headers['ig-set-authorization'],
|
|
86
|
+
headers['ig-set-authorization'],
|
|
87
|
+
headers['ig-set-authorization'],
|
|
88
|
+
headers['ig-set-authorization'],
|
|
89
|
+
headers['ig-set-authorization'],
|
|
90
|
+
]; // (we'll just check the normalized header below)
|
|
91
|
+
|
|
92
|
+
// Simpler: look for any header that contains 'IG-Set-Authorization' or 'ig-set-authorization'
|
|
93
|
+
for (const key of Object.keys(headers)) {
|
|
94
|
+
const val = headers[key];
|
|
95
|
+
if (!val) continue;
|
|
96
|
+
if (key.includes('ig-set-authorization') || key === 'ig-set-authorization' || key === 'ig-set-authorization'.toLowerCase()) {
|
|
97
|
+
const token = this._normalizeTokenString(val);
|
|
98
|
+
if (token) {
|
|
99
|
+
this.client.state.authorization = token;
|
|
100
|
+
try { this.client.state.updateAuthorization(); } catch (e) {}
|
|
101
|
+
return token;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Some servers include the header value inside a JSON-like string in other headers
|
|
105
|
+
if (typeof val === 'string' && val.includes('Bearer IGT:2:')) {
|
|
106
|
+
const token = this._findBearerInString(val);
|
|
107
|
+
if (token) {
|
|
108
|
+
this.client.state.authorization = token;
|
|
109
|
+
try { this.client.state.updateAuthorization(); } catch (e) {}
|
|
110
|
+
return token;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Extract IG-U headers (user id, region, claim) when present
|
|
116
|
+
const dsUserIdHeader =
|
|
117
|
+
headers['ig-set-ig-u-ds-user-id'] ||
|
|
118
|
+
headers['ig-u-ds-user-id'] ||
|
|
119
|
+
headers['x-ig-set-ig-u-ds-user-id'];
|
|
120
|
+
if (dsUserIdHeader) {
|
|
121
|
+
const m = String(dsUserIdHeader).match(/(\d{3,})/);
|
|
122
|
+
if (m) {
|
|
123
|
+
const uid = m[1];
|
|
124
|
+
this.client.state.cookieUserId = uid;
|
|
125
|
+
this.client.state._userId = uid;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const rurHeader =
|
|
130
|
+
headers['ig-set-ig-u-rur'] ||
|
|
131
|
+
headers['ig-u-rur'] ||
|
|
132
|
+
headers['x-ig-set-ig-u-rur'];
|
|
133
|
+
if (rurHeader) {
|
|
134
|
+
const rur = String(rurHeader).trim().replace(/^"|"$/g, '');
|
|
135
|
+
if (rur) {
|
|
136
|
+
this.client.state.igURur = rur;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const wwwClaimHeader =
|
|
141
|
+
headers['x-ig-set-www-claim'] ||
|
|
142
|
+
headers['ig-set-www-claim'] ||
|
|
143
|
+
headers['ig-u-www-claim'];
|
|
144
|
+
if (wwwClaimHeader) {
|
|
145
|
+
const claim = String(wwwClaimHeader).trim().replace(/^"|"$/g, '');
|
|
146
|
+
if (claim) {
|
|
147
|
+
this.client.state.igWWWClaim = claim;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const midHeader =
|
|
152
|
+
headers['ig-set-x-mid'] ||
|
|
153
|
+
headers['x-mid'] ||
|
|
154
|
+
headers['ig-set-mid'];
|
|
155
|
+
if (midHeader) {
|
|
156
|
+
const mid = String(midHeader).trim().replace(/^"|"$/g, '');
|
|
157
|
+
if (mid) {
|
|
158
|
+
this.client.state.mid = mid;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// 2) Check response.body for common fields
|
|
163
|
+
const body = response.body;
|
|
164
|
+
if (body) {
|
|
165
|
+
// If body has headers string (some responses embed headers JSON as a string)
|
|
166
|
+
if (body.headers && typeof body.headers === 'string') {
|
|
167
|
+
const token = this._findBearerInString(body.headers);
|
|
168
|
+
if (token) {
|
|
169
|
+
this.client.state.authorization = token;
|
|
170
|
+
try { this.client.state.updateAuthorization(); } catch (e) {}
|
|
171
|
+
return token;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// If body contains a layout object or string, stringify and search it
|
|
176
|
+
try {
|
|
177
|
+
let layoutStr = null;
|
|
178
|
+
if (body.layout) {
|
|
179
|
+
layoutStr = typeof body.layout === 'string' ? body.layout : JSON.stringify(body.layout);
|
|
180
|
+
} else if (body?.layout?.bloks_payload) {
|
|
181
|
+
layoutStr = JSON.stringify(body.layout.bloks_payload);
|
|
182
|
+
} else if (body?.bloks_payload) {
|
|
183
|
+
layoutStr = JSON.stringify(body.bloks_payload);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (!layoutStr && typeof body === 'string') {
|
|
187
|
+
layoutStr = body;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (layoutStr) {
|
|
191
|
+
const token = this._findBearerInString(layoutStr);
|
|
192
|
+
if (token) {
|
|
193
|
+
this.client.state.authorization = token;
|
|
194
|
+
try { this.client.state.updateAuthorization(); } catch (e) {}
|
|
195
|
+
return token;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Some responses embed a login_response JSON string inside layout; try to extract it
|
|
199
|
+
const loginResponseMatch = layoutStr.match(/"login_response"\s*:\s*"(\\?{.*?\\?})"/s);
|
|
200
|
+
if (loginResponseMatch) {
|
|
201
|
+
// Unescape JSON string
|
|
202
|
+
let jsonStr = loginResponseMatch[1];
|
|
203
|
+
try {
|
|
204
|
+
jsonStr = jsonStr.replace(/\\"/g, '"').replace(/\\n/g, '');
|
|
205
|
+
const parsed = JSON.parse(jsonStr);
|
|
206
|
+
if (parsed && parsed.headers) {
|
|
207
|
+
const token = this._findBearerInString(parsed.headers);
|
|
208
|
+
if (token) {
|
|
209
|
+
this.client.state.authorization = token;
|
|
210
|
+
try { this.client.state.updateAuthorization(); } catch (e) {}
|
|
211
|
+
return token;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
} catch (e) {
|
|
215
|
+
// ignore parse errors
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Also try to find IG-Set-Password-Encryption headers (not token but useful)
|
|
220
|
+
const encKeyIdMatch = layoutStr.match(/IG-Set-Password-Encryption-Key-Id[\\"\s:]+(\d+)/i) ||
|
|
221
|
+
layoutStr.match(/password.encryption.key.id[\\"\s:]+(\d+)/i);
|
|
222
|
+
if (encKeyIdMatch) {
|
|
223
|
+
this.client.state.passwordEncryptionKeyId = parseInt(encKeyIdMatch[1]);
|
|
224
|
+
}
|
|
225
|
+
const encPubKeyMatch = layoutStr.match(/IG-Set-Password-Encryption-Pub-Key[\\"\s:]+([A-Za-z0-9+\/=]+)/i) ||
|
|
226
|
+
layoutStr.match(/password.encryption.pub.key[\\"\s:]+([A-Za-z0-9+\/=]+)/i);
|
|
227
|
+
if (encPubKeyMatch) {
|
|
228
|
+
this.client.state.passwordEncryptionPubKey = encPubKeyMatch[1];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Extract user/account identifiers from layout string
|
|
232
|
+
const dsIdMatch =
|
|
233
|
+
layoutStr.match(/ig-set-ig-u-ds-user-id[\\"\s:]+(\d+)/i) ||
|
|
234
|
+
layoutStr.match(/ig-u-ds-user-id[\\"\s:]+(\d+)/i) ||
|
|
235
|
+
layoutStr.match(/"strong_id__"\s*:\s*"(\d+)"/i) ||
|
|
236
|
+
layoutStr.match(/"pk_id"\s*:\s*"(\d+)"/i) ||
|
|
237
|
+
layoutStr.match(/"pk"\s*:\s*(\d+)/i);
|
|
238
|
+
if (dsIdMatch) {
|
|
239
|
+
const uid = String(dsIdMatch[1]);
|
|
240
|
+
this.client.state.cookieUserId = uid;
|
|
241
|
+
this.client.state._userId = uid;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const rurMatch =
|
|
245
|
+
layoutStr.match(/ig-set-ig-u-rur[\\"\s:]+"([^"\\]+)/i) ||
|
|
246
|
+
layoutStr.match(/ig-u-rur[\\"\s:]+"([^"\\]+)/i);
|
|
247
|
+
if (rurMatch) {
|
|
248
|
+
this.client.state.igURur = rurMatch[1];
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const wwwClaimMatch = layoutStr.match(/x-ig-set-www-claim[\\"\s:]+"([^"\\]+)/i);
|
|
252
|
+
if (wwwClaimMatch) {
|
|
253
|
+
this.client.state.igWWWClaim = wwwClaimMatch[1];
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const midMatch = layoutStr.match(/"mid"\s*:\s*"([^"\\]+)"/i);
|
|
257
|
+
if (midMatch) {
|
|
258
|
+
this.client.state.mid = midMatch[1];
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
} catch (e) {
|
|
262
|
+
// ignore
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// 3) If body.logged_in_user exists and we have authorization already, return it
|
|
266
|
+
if (body.logged_in_user && this.client.state.authorization) {
|
|
267
|
+
return this.client.state.authorization;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Normalize token string: accept either raw token or "Bearer IGT:2:..." and return trimmed token string
|
|
275
|
+
_normalizeTokenString(val) {
|
|
276
|
+
if (!val || typeof val !== 'string') return null;
|
|
277
|
+
// If header already contains "Bearer IGT:2:..."
|
|
278
|
+
const bearer = this._findBearerInString(val);
|
|
279
|
+
if (bearer) return bearer;
|
|
280
|
+
// If header contains JSON with IG-Set-Authorization field
|
|
281
|
+
try {
|
|
282
|
+
const maybeJson = JSON.parse(val);
|
|
283
|
+
if (maybeJson['IG-Set-Authorization']) {
|
|
284
|
+
return this._findBearerInString(maybeJson['IG-Set-Authorization']);
|
|
285
|
+
}
|
|
286
|
+
} catch (e) {
|
|
287
|
+
// not JSON
|
|
288
|
+
}
|
|
289
|
+
// fallback: return trimmed value
|
|
290
|
+
return val.trim();
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Find bearer token in arbitrary string using multiple patterns
|
|
294
|
+
_findBearerInString(str) {
|
|
295
|
+
if (!str || typeof str !== 'string') return null;
|
|
296
|
+
// Patterns to match the Bearer token in many possible encodings/escapes
|
|
297
|
+
const bearerPatterns = [
|
|
298
|
+
/Bearer IGT:2:[A-Za-z0-9+\/=]+/,
|
|
299
|
+
/Bearer\s+IGT:2:[A-Za-z0-9+\/=]+/,
|
|
300
|
+
/\\?"Bearer IGT:2:([A-Za-z0-9+\/=]+)\\?"/,
|
|
301
|
+
/ig-set-authorization[\\"\s:]*Bearer IGT:2:([A-Za-z0-9+\/=]+)/i,
|
|
302
|
+
/"IG-Set-Authorization"\s*:\s*"(Bearer IGT:2:[A-Za-z0-9+\/=]+)"/i,
|
|
303
|
+
/IG-Set-Authorization[\s:]+(Bearer IGT:2:[A-Za-z0-9+\/=]+)/i,
|
|
304
|
+
/Bearer IGT:2:([A-Za-z0-9+\/=]+)/,
|
|
305
|
+
];
|
|
306
|
+
|
|
307
|
+
for (const pattern of bearerPatterns) {
|
|
308
|
+
const m = str.match(pattern);
|
|
309
|
+
if (m) {
|
|
310
|
+
// If capturing group present, prefer it
|
|
311
|
+
if (m[1]) {
|
|
312
|
+
const token = m[1].includes('Bearer IGT:2:') ? m[1] : `Bearer IGT:2:${m[1]}`;
|
|
313
|
+
return token.replace(/\\?"/g, '').trim();
|
|
314
|
+
}
|
|
315
|
+
// Otherwise m[0] contains the full token
|
|
316
|
+
return m[0].replace(/\\?"/g, '').trim();
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Ensure we have a valid csrftoken cookie (and mid) before sensitive endpoints.
|
|
324
|
+
// Instagram typically sets csrftoken via /api/v1/si/fetch_headers/.
|
|
325
|
+
async ensureCsrfToken() {
|
|
326
|
+
const cookieToken = this.client.state.cookieCsrfToken;
|
|
327
|
+
if (cookieToken && cookieToken !== 'missing' && cookieToken !== 'pending') {
|
|
328
|
+
try { this.client.state.csrfToken = cookieToken; } catch {}
|
|
329
|
+
return cookieToken;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Warmup endpoint that returns/set cookies (csrftoken, mid, etc.)
|
|
333
|
+
try {
|
|
334
|
+
await this.client.request.send({
|
|
335
|
+
method: 'GET',
|
|
336
|
+
url: `/api/v1/si/fetch_headers/?challenge_type=signup&guid=${this.client.state.uuid}`,
|
|
337
|
+
});
|
|
338
|
+
} catch (e) {
|
|
339
|
+
// ignore warmup failures
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const token = this.client.state.cookieCsrfToken;
|
|
343
|
+
if (token && token !== 'missing' && token !== 'pending') {
|
|
344
|
+
try { this.client.state.csrfToken = token; } catch {}
|
|
345
|
+
return token;
|
|
346
|
+
}
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
|
|
28
351
|
async login(credentialsOrUsername, passwordArg) {
|
|
29
352
|
let username, password;
|
|
30
353
|
if (typeof credentialsOrUsername === 'object' && credentialsOrUsername !== null) {
|
|
@@ -44,7 +367,9 @@ class AccountRepository extends Repository {
|
|
|
44
367
|
|
|
45
368
|
const { encrypted, time } = this.encryptPassword(password);
|
|
46
369
|
|
|
47
|
-
|
|
370
|
+
|
|
371
|
+
const csrfToken = await this.ensureCsrfToken();
|
|
372
|
+
return this.requestWithRetry(async () => {
|
|
48
373
|
const aacInitTimestamp = Math.floor(Date.now() / 1000) - Math.floor(Math.random() * 50);
|
|
49
374
|
const aacjid = crypto.randomUUID ? crypto.randomUUID() : require('uuid').v4();
|
|
50
375
|
const aaccs = crypto.randomBytes(32).toString('base64url');
|
|
@@ -92,35 +417,65 @@ class AccountRepository extends Repository {
|
|
|
92
417
|
const attestParams = AccountRepository.generateAttestParams(this.client.state);
|
|
93
418
|
|
|
94
419
|
const bloksHeaders = {
|
|
420
|
+
...(csrfToken ? { 'X-CSRFToken': csrfToken } : {}),
|
|
421
|
+
'Accept-Language': `${(this.client.state.language || 'ro_RO').replace('_', '-')} , en-US`,
|
|
422
|
+
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
|
423
|
+
|
|
424
|
+
'IG-Intended-User-Id': '0',
|
|
425
|
+
|
|
426
|
+
'X-Bloks-Is-Layout-Rtl': 'false',
|
|
95
427
|
'X-Bloks-Prism-Ax-Base-Colors-Enabled': 'false',
|
|
96
428
|
'X-Bloks-Prism-Button-Version': 'CONTROL',
|
|
97
429
|
'X-Bloks-Prism-Colors-Enabled': 'true',
|
|
98
430
|
'X-Bloks-Prism-Font-Enabled': 'false',
|
|
99
431
|
'X-Bloks-Prism-Indigo-Link-Version': '0',
|
|
432
|
+
'X-Bloks-Version-Id': this.client.state.bloksVersionId,
|
|
433
|
+
|
|
434
|
+
'X-FB-Client-Ip': 'True',
|
|
435
|
+
'X-FB-Connection-Type': 'WIFI',
|
|
100
436
|
'X-FB-Friendly-Name': 'IgApi: bloks/async_action/com.bloks.www.bloks.caa.login.async.send_login_request/',
|
|
437
|
+
'X-FB-Network-Properties': 'VPN;Validated;LocalAddrs=/10.0.0.2,;',
|
|
101
438
|
'X-FB-Request-Analytics-Tags': JSON.stringify({
|
|
102
439
|
network_tags: {
|
|
103
|
-
product: this.client.state.fbAnalyticsApplicationId,
|
|
440
|
+
product: String(this.client.state.fbAnalyticsApplicationId || ''),
|
|
104
441
|
purpose: 'fetch',
|
|
105
442
|
surface: 'undefined',
|
|
106
443
|
request_category: 'api',
|
|
107
444
|
retry_attempt: '0',
|
|
108
445
|
},
|
|
109
446
|
}),
|
|
110
|
-
'X-
|
|
111
|
-
'X-Tigon-Is-Retry': 'False',
|
|
447
|
+
'X-FB-Server-Cluster': 'True',
|
|
112
448
|
'X-FB-RMD': 'state=URL_ELIGIBLE',
|
|
113
|
-
'X-IG-Attest-Params': JSON.stringify(attestParams),
|
|
114
|
-
'X-FB-Connection-Type': 'MOBILE.LTE',
|
|
115
|
-
'X-FB-Network-Properties': 'VPN;Metered;Validated;LocalAddrs=/10.0.0.2,;',
|
|
116
449
|
'X-FB-Conn-UUID-Client': crypto.randomBytes(16).toString('hex'),
|
|
450
|
+
'X-FB-HTTP-Engine': 'MNS/TCP',
|
|
451
|
+
|
|
452
|
+
'X-IG-Client-Endpoint': 'com.bloks.www.caa.login.aymh_single_profile_screen_entry',
|
|
453
|
+
'X-IG-Connection-Type': 'WIFI',
|
|
454
|
+
'X-IG-Capabilities': '3brTv10=',
|
|
455
|
+
'X-IG-App-ID': String(this.client.state.fbAnalyticsApplicationId || ''),
|
|
456
|
+
'X-IG-App-Locale': (this.client.state.language || 'ro_RO'),
|
|
457
|
+
'X-IG-Device-Locale': (this.client.state.language || 'ro_RO'),
|
|
458
|
+
'X-IG-Mapped-Locale': (this.client.state.language || 'ro_RO'),
|
|
459
|
+
'X-IG-Android-Id': this.client.state.androidDeviceId || this.client.state.deviceId || '',
|
|
460
|
+
'X-IG-Device-Id': this.client.state.deviceId,
|
|
461
|
+
'X-IG-Family-Device-Id': this.client.state.phoneId,
|
|
462
|
+
'X-IG-Timezone-Offset': String(typeof this.client.state.timezoneOffset === 'number' ? this.client.state.timezoneOffset : 7200),
|
|
463
|
+
'X-IG-WWW-Claim': this.client.state.igWWWClaim || '0',
|
|
464
|
+
'X-IG-Nav-Chain': 'LockoutFragment:dogfooding_lockout:1:cold_start:...caa.login.aymh_single_profile_screen_entry:9:button:0::0',
|
|
465
|
+
|
|
466
|
+
'X-IG-Attest-Params': JSON.stringify(attestParams),
|
|
467
|
+
|
|
468
|
+
'X-Mid': this.client.state.mid || '',
|
|
469
|
+
'X-Pigeon-Rawclienttime': (Date.now() / 1000).toFixed(3),
|
|
470
|
+
'X-Pigeon-Session-Id': this.client.state.pigeonSessionId || 'UFS-' + (crypto.randomBytes(16).toString('hex')),
|
|
471
|
+
'X-Tigon-Is-Retry': 'False',
|
|
472
|
+
|
|
117
473
|
'X-IG-Bandwidth-Speed-KBPS': (Math.random() * 1500 + 1500).toFixed(3),
|
|
118
474
|
'X-IG-Bandwidth-TotalBytes-B': '0',
|
|
119
475
|
'X-IG-Bandwidth-TotalTime-MS': '0',
|
|
120
|
-
|
|
121
|
-
'X-IG-Capabilities': '3brTv10=',
|
|
476
|
+
|
|
122
477
|
'Accept-Encoding': 'gzip, deflate',
|
|
123
|
-
'
|
|
478
|
+
'User-Agent': this.client.state.userAgent || 'Instagram 371.0.0.0.23 Android (30/11; 320dpi; 720x1449; Xiaomi/Redmi; M2006C3MNG; angelican; mt6765; ro_RO; 703217507)',
|
|
124
479
|
};
|
|
125
480
|
|
|
126
481
|
const response = await this.client.request.send({
|
|
@@ -129,35 +484,65 @@ class AccountRepository extends Repository {
|
|
|
129
484
|
form: { params: paramsJson },
|
|
130
485
|
headers: bloksHeaders,
|
|
131
486
|
});
|
|
487
|
+
// === DEBUG LOGIN: salvați răspunsul brut de la server pentru analiză ===
|
|
488
|
+
try {
|
|
489
|
+
const fs = require('fs');
|
|
490
|
+
const path = require('path');
|
|
491
|
+
|
|
492
|
+
const debugDir = path.join(process.cwd(), 'authinfo_instagram');
|
|
493
|
+
const debugFile = path.join(debugDir, 'login-debug.json');
|
|
494
|
+
|
|
495
|
+
// asigură-te că folderul există
|
|
496
|
+
try {
|
|
497
|
+
fs.mkdirSync(debugDir, { recursive: true });
|
|
498
|
+
} catch (e) {}
|
|
499
|
+
|
|
500
|
+
const debugPayload = {
|
|
501
|
+
at: new Date().toISOString(),
|
|
502
|
+
statusCode: response.statusCode || response.status || null,
|
|
503
|
+
headers: response.headers || null,
|
|
504
|
+
body: response.body || null,
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
fs.writeFileSync(debugFile, JSON.stringify(debugPayload, null, 2), 'utf8');
|
|
508
|
+
} catch (e) {
|
|
509
|
+
// nu stricăm login-ul dacă nu merge debug-ul
|
|
510
|
+
}
|
|
511
|
+
// === SFÂRȘIT DEBUG LOGIN ===
|
|
132
512
|
|
|
133
513
|
const body = response.body;
|
|
134
514
|
|
|
135
|
-
|
|
515
|
+
// Immediately attempt to extract and save authorization token from the response
|
|
516
|
+
// This covers tokens returned in headers, in layout payloads, or embedded login_response headers.
|
|
517
|
+
this._extractAndSaveAuthorization(response);
|
|
518
|
+
|
|
519
|
+
if (body && body.two_factor_required) {
|
|
136
520
|
const err = new Error('Two factor authentication required');
|
|
137
521
|
err.name = 'IgLoginTwoFactorRequiredError';
|
|
138
522
|
err.twoFactorInfo = body.two_factor_info;
|
|
139
523
|
throw err;
|
|
140
524
|
}
|
|
141
|
-
if (body.error_type === 'bad_password') {
|
|
525
|
+
if (body && body.error_type === 'bad_password') {
|
|
142
526
|
const err = new Error('Bad password');
|
|
143
527
|
err.name = 'IgLoginBadPasswordError';
|
|
144
528
|
throw err;
|
|
145
529
|
}
|
|
146
|
-
if (body.error_type === 'invalid_user') {
|
|
530
|
+
if (body && body.error_type === 'invalid_user') {
|
|
147
531
|
const err = new Error('Invalid user');
|
|
148
532
|
err.name = 'IgLoginInvalidUserError';
|
|
149
533
|
throw err;
|
|
150
534
|
}
|
|
151
|
-
if (body.message === 'challenge_required') {
|
|
535
|
+
if (body && body.message === 'challenge_required') {
|
|
152
536
|
const err = new Error('Challenge required');
|
|
153
537
|
err.name = 'IgCheckpointError';
|
|
154
538
|
err.challengeInfo = body.challenge;
|
|
155
539
|
throw err;
|
|
156
540
|
}
|
|
157
541
|
|
|
158
|
-
if (body.layout) {
|
|
542
|
+
if (body && body.layout) {
|
|
159
543
|
const layoutStr = typeof body.layout === 'string' ? body.layout : JSON.stringify(body.layout);
|
|
160
544
|
|
|
545
|
+
// If layout contains two-factor markers, parse and throw
|
|
161
546
|
if (layoutStr.includes('two_factor_required') || layoutStr.includes('"two_factor_info"')) {
|
|
162
547
|
let twoFactorInfo = null;
|
|
163
548
|
try {
|
|
@@ -189,24 +574,14 @@ class AccountRepository extends Repository {
|
|
|
189
574
|
throw err;
|
|
190
575
|
}
|
|
191
576
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
];
|
|
198
|
-
for (const pattern of bearerPatterns) {
|
|
199
|
-
const bearerMatch = layoutStr.match(pattern);
|
|
200
|
-
if (bearerMatch) {
|
|
201
|
-
const token = bearerMatch[0].includes('Bearer IGT:2:')
|
|
202
|
-
? bearerMatch[0].replace(/^.*?(Bearer IGT:2:[A-Za-z0-9+\/=]+).*$/, '$1')
|
|
203
|
-
: `Bearer IGT:2:${bearerMatch[1]}`;
|
|
204
|
-
this.client.state.authorization = token.replace(/\\?"/g, '').trim();
|
|
205
|
-
this.client.state.updateAuthorization();
|
|
206
|
-
break;
|
|
207
|
-
}
|
|
577
|
+
// Additional attempt to extract token from layout string (redundant but robust)
|
|
578
|
+
const tokenFromLayout = this._findBearerInString(layoutStr);
|
|
579
|
+
if (tokenFromLayout) {
|
|
580
|
+
this.client.state.authorization = tokenFromLayout;
|
|
581
|
+
try { this.client.state.updateAuthorization(); } catch (e) {}
|
|
208
582
|
}
|
|
209
583
|
|
|
584
|
+
// Extract pk (user id) from layout if present
|
|
210
585
|
const pkPatterns = [
|
|
211
586
|
/\\"pk\\":\s*(\d+)/,
|
|
212
587
|
/"pk":\s*(\d+)/,
|
|
@@ -226,6 +601,7 @@ class AccountRepository extends Repository {
|
|
|
226
601
|
}
|
|
227
602
|
}
|
|
228
603
|
|
|
604
|
+
// Extract IG-Set-WWW-Claim if present
|
|
229
605
|
const wwwClaimPatterns = [
|
|
230
606
|
/IG-Set-WWW-Claim[\\"\s:]+([a-f0-9]+)/i,
|
|
231
607
|
/ig-set-www-claim[\\"\s:]+([a-f0-9]+)/i,
|
|
@@ -240,6 +616,7 @@ class AccountRepository extends Repository {
|
|
|
240
616
|
}
|
|
241
617
|
}
|
|
242
618
|
|
|
619
|
+
// Extract encryption key id and pub key (already attempted earlier in _extractAndSaveAuthorization)
|
|
243
620
|
const encKeyIdMatch = layoutStr.match(/IG-Set-Password-Encryption-Key-Id[\\"\s:]+(\d+)/i) ||
|
|
244
621
|
layoutStr.match(/password.encryption.key.id[\\"\s:]+(\d+)/i);
|
|
245
622
|
if (encKeyIdMatch) {
|
|
@@ -258,6 +635,7 @@ class AccountRepository extends Repository {
|
|
|
258
635
|
this.client.state.mid = midMatch[1];
|
|
259
636
|
}
|
|
260
637
|
|
|
638
|
+
// If layout contains a direct logged_in_user JSON, extract and return it
|
|
261
639
|
const loginResponsePatterns = [
|
|
262
640
|
/\\"logged_in_user\\":\{[^}]*\\"pk\\":(\d+)[^}]*\\"username\\":\\"([^"\\]+)\\"/,
|
|
263
641
|
/"logged_in_user":\{[^}]*"pk":(\d+)[^}]*"username":"([^"]+)"/,
|
|
@@ -276,6 +654,7 @@ class AccountRepository extends Repository {
|
|
|
276
654
|
}
|
|
277
655
|
}
|
|
278
656
|
|
|
657
|
+
// If we have extracted a pk and we also have authorization, try to fetch current user
|
|
279
658
|
if (extractedPk && this.client.state.authorization) {
|
|
280
659
|
try {
|
|
281
660
|
const userInfo = await this.currentUser();
|
|
@@ -291,15 +670,19 @@ class AccountRepository extends Repository {
|
|
|
291
670
|
}
|
|
292
671
|
}
|
|
293
672
|
|
|
294
|
-
|
|
673
|
+
// If server returned logged_in_user directly in body
|
|
674
|
+
if (body && body.logged_in_user) {
|
|
295
675
|
const lu = body.logged_in_user;
|
|
296
676
|
if (lu.pk) {
|
|
297
677
|
this.client.state.cookieUserId = String(lu.pk);
|
|
298
678
|
this.client.state._userId = String(lu.pk);
|
|
299
679
|
}
|
|
680
|
+
// Ensure authorization is saved if present in response headers
|
|
681
|
+
this._extractAndSaveAuthorization(response);
|
|
300
682
|
return lu;
|
|
301
683
|
}
|
|
302
684
|
|
|
685
|
+
// If we already have authorization, try to fetch current user to populate state
|
|
303
686
|
if (this.client.state.authorization) {
|
|
304
687
|
try {
|
|
305
688
|
const userInfo = await this.currentUser();
|
|
@@ -310,6 +693,7 @@ class AccountRepository extends Repository {
|
|
|
310
693
|
}
|
|
311
694
|
return user;
|
|
312
695
|
} catch (e) {
|
|
696
|
+
// fallback: return raw body
|
|
313
697
|
return body;
|
|
314
698
|
}
|
|
315
699
|
}
|
|
@@ -334,6 +718,8 @@ class AccountRepository extends Repository {
|
|
|
334
718
|
phone_id: this.client.state.phoneId,
|
|
335
719
|
}),
|
|
336
720
|
});
|
|
721
|
+
// Save authorization if present in two-factor response
|
|
722
|
+
this._extractAndSaveAuthorization(response);
|
|
337
723
|
return response.body;
|
|
338
724
|
});
|
|
339
725
|
}
|
|
@@ -1227,6 +1227,30 @@ async function extractStateData(igState) {
|
|
|
1227
1227
|
// try to obtain cookie fields
|
|
1228
1228
|
const cookieFields = await extractCookieFields(igState);
|
|
1229
1229
|
|
|
1230
|
+
// try to decode ds_user_id & sessionid from Bearer IGT:2 token (authorization), if present
|
|
1231
|
+
let bearerDsUserId = null;
|
|
1232
|
+
let bearerSessionId = null;
|
|
1233
|
+
try {
|
|
1234
|
+
if (creds.authorization && typeof creds.authorization === 'string') {
|
|
1235
|
+
const m = creds.authorization.match(/^Bearer IGT:2:(.+)$/);
|
|
1236
|
+
if (m && m[1]) {
|
|
1237
|
+
try {
|
|
1238
|
+
const json = Buffer.from(m[1], 'base64').toString('utf8');
|
|
1239
|
+
const parsed = JSON.parse(json);
|
|
1240
|
+
if (parsed && typeof parsed === 'object') {
|
|
1241
|
+
if (parsed.ds_user_id) bearerDsUserId = String(parsed.ds_user_id);
|
|
1242
|
+
if (parsed.sessionid) bearerSessionId = String(parsed.sessionid);
|
|
1243
|
+
}
|
|
1244
|
+
} catch (e2) {
|
|
1245
|
+
// ignore decode errors
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
} catch (e) {
|
|
1250
|
+
// ignore bearer parse error
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
|
|
1230
1254
|
// Primary identifiers
|
|
1231
1255
|
let userIdFromState = null;
|
|
1232
1256
|
try {
|
|
@@ -1235,8 +1259,8 @@ async function extractStateData(igState) {
|
|
|
1235
1259
|
if (!userIdFromState) {
|
|
1236
1260
|
userIdFromState = igState.userId || igState.user_id || null;
|
|
1237
1261
|
}
|
|
1238
|
-
const dsUserIdFromCookies = cookieFields.dsUserIdCookie || igState.dsUserId || igState.ds_user_id || null;
|
|
1239
|
-
const sessionIdFromCookies = cookieFields.sessionIdCookie || null;
|
|
1262
|
+
const dsUserIdFromCookies = cookieFields.dsUserIdCookie || igState.dsUserId || igState.ds_user_id || bearerDsUserId || null;
|
|
1263
|
+
const sessionIdFromCookies = cookieFields.sessionIdCookie || bearerSessionId || null;
|
|
1240
1264
|
const csrfFromCookies = cookieFields.csrfTokenCookie || null;
|
|
1241
1265
|
const midFromCookies = cookieFields.midCookie || igState.mid || null;
|
|
1242
1266
|
|
package/package.json
CHANGED