@vicociv/instaloader 0.1.0 → 0.3.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/LICENSE +21 -0
- package/README.md +401 -0
- package/dist/index.d.mts +72 -4
- package/dist/index.d.ts +72 -4
- package/dist/index.js +240 -103
- package/dist/index.mjs +236 -103
- package/package.json +38 -14
package/dist/index.js
CHANGED
|
@@ -66,8 +66,11 @@ __export(index_exports, {
|
|
|
66
66
|
TwoFactorAuthRequiredException: () => TwoFactorAuthRequiredException,
|
|
67
67
|
defaultIphoneHeaders: () => defaultIphoneHeaders,
|
|
68
68
|
defaultUserAgent: () => defaultUserAgent,
|
|
69
|
+
extractHashtagFromUrl: () => extractHashtagFromUrl,
|
|
69
70
|
extractHashtags: () => extractHashtags,
|
|
70
71
|
extractMentions: () => extractMentions,
|
|
72
|
+
extractShortcode: () => extractShortcode,
|
|
73
|
+
extractUsername: () => extractUsername,
|
|
71
74
|
formatFilename: () => formatFilename,
|
|
72
75
|
formatStringContainsKey: () => formatStringContainsKey,
|
|
73
76
|
getConfigDir: () => getConfigDir,
|
|
@@ -76,6 +79,7 @@ __export(index_exports, {
|
|
|
76
79
|
getJsonStructure: () => getJsonStructure,
|
|
77
80
|
loadStructure: () => loadStructure,
|
|
78
81
|
mediaidToShortcode: () => mediaidToShortcode,
|
|
82
|
+
parseInstagramUrl: () => parseInstagramUrl,
|
|
79
83
|
resumableIteration: () => resumableIteration,
|
|
80
84
|
sanitizePath: () => sanitizePath,
|
|
81
85
|
shortcodeToMediaid: () => shortcodeToMediaid
|
|
@@ -216,9 +220,113 @@ var InvalidIteratorException = class extends InstaloaderException {
|
|
|
216
220
|
}
|
|
217
221
|
};
|
|
218
222
|
|
|
223
|
+
// src/utils.ts
|
|
224
|
+
function generateUUID() {
|
|
225
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
226
|
+
return crypto.randomUUID();
|
|
227
|
+
}
|
|
228
|
+
const bytes = new Uint8Array(16);
|
|
229
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
230
|
+
crypto.getRandomValues(bytes);
|
|
231
|
+
} else {
|
|
232
|
+
for (let i = 0; i < 16; i++) {
|
|
233
|
+
bytes[i] = Math.floor(Math.random() * 256);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
237
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
238
|
+
const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
239
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
240
|
+
}
|
|
241
|
+
var SimpleCookieStore = class {
|
|
242
|
+
cookies = /* @__PURE__ */ new Map();
|
|
243
|
+
/**
|
|
244
|
+
* Get cookies for a URL as a key-value object.
|
|
245
|
+
*/
|
|
246
|
+
getCookies(url) {
|
|
247
|
+
const domain = this.extractDomain(url);
|
|
248
|
+
const domainCookies = this.cookies.get(domain);
|
|
249
|
+
if (!domainCookies) {
|
|
250
|
+
return {};
|
|
251
|
+
}
|
|
252
|
+
return Object.fromEntries(domainCookies);
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Set cookies from a key-value object.
|
|
256
|
+
*/
|
|
257
|
+
setCookies(cookies, url) {
|
|
258
|
+
const domain = this.extractDomain(url);
|
|
259
|
+
let domainCookies = this.cookies.get(domain);
|
|
260
|
+
if (!domainCookies) {
|
|
261
|
+
domainCookies = /* @__PURE__ */ new Map();
|
|
262
|
+
this.cookies.set(domain, domainCookies);
|
|
263
|
+
}
|
|
264
|
+
for (const [key, value] of Object.entries(cookies)) {
|
|
265
|
+
domainCookies.set(key, value);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Parse and store cookies from Set-Cookie headers.
|
|
270
|
+
*/
|
|
271
|
+
parseSetCookieHeaders(headers, url) {
|
|
272
|
+
const setCookies = headers.getSetCookie?.() || [];
|
|
273
|
+
const domain = this.extractDomain(url);
|
|
274
|
+
let domainCookies = this.cookies.get(domain);
|
|
275
|
+
if (!domainCookies) {
|
|
276
|
+
domainCookies = /* @__PURE__ */ new Map();
|
|
277
|
+
this.cookies.set(domain, domainCookies);
|
|
278
|
+
}
|
|
279
|
+
for (const cookieStr of setCookies) {
|
|
280
|
+
const parsed = this.parseSetCookie(cookieStr);
|
|
281
|
+
if (parsed) {
|
|
282
|
+
domainCookies.set(parsed.name, parsed.value);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Generate Cookie header string for a URL.
|
|
288
|
+
*/
|
|
289
|
+
getCookieHeader(url) {
|
|
290
|
+
const cookies = this.getCookies(url);
|
|
291
|
+
return Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join("; ");
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Clear all cookies.
|
|
295
|
+
*/
|
|
296
|
+
clear() {
|
|
297
|
+
this.cookies.clear();
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Clear cookies for a specific domain.
|
|
301
|
+
*/
|
|
302
|
+
clearDomain(url) {
|
|
303
|
+
const domain = this.extractDomain(url);
|
|
304
|
+
this.cookies.delete(domain);
|
|
305
|
+
}
|
|
306
|
+
extractDomain(url) {
|
|
307
|
+
try {
|
|
308
|
+
return new URL(url).hostname;
|
|
309
|
+
} catch {
|
|
310
|
+
return url;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
parseSetCookie(cookieStr) {
|
|
314
|
+
const firstSemi = cookieStr.indexOf(";");
|
|
315
|
+
const nameValue = firstSemi === -1 ? cookieStr : cookieStr.slice(0, firstSemi);
|
|
316
|
+
const eqIndex = nameValue.indexOf("=");
|
|
317
|
+
if (eqIndex === -1) {
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
const name = nameValue.slice(0, eqIndex).trim();
|
|
321
|
+
const value = nameValue.slice(eqIndex + 1).trim();
|
|
322
|
+
if (!name) {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
return { name, value };
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
|
|
219
329
|
// src/instaloadercontext.ts
|
|
220
|
-
var import_tough_cookie = require("tough-cookie");
|
|
221
|
-
var import_uuid = require("uuid");
|
|
222
330
|
function defaultUserAgent() {
|
|
223
331
|
return "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36";
|
|
224
332
|
}
|
|
@@ -246,11 +354,18 @@ function defaultIphoneHeaders() {
|
|
|
246
354
|
"x-ig-mapped-locale": "en-US",
|
|
247
355
|
"x-ig-timezone-offset": String(timezoneOffset),
|
|
248
356
|
"x-ig-www-claim": "0",
|
|
249
|
-
"x-pigeon-session-id": (
|
|
357
|
+
"x-pigeon-session-id": generateUUID(),
|
|
250
358
|
"x-tigon-is-retry": "False",
|
|
251
359
|
"x-whatsapp": "0"
|
|
252
360
|
};
|
|
253
361
|
}
|
|
362
|
+
function getPerRequestHeaders() {
|
|
363
|
+
return {
|
|
364
|
+
"x-pigeon-rawclienttime": (Date.now() / 1e3).toFixed(6),
|
|
365
|
+
"x-ig-connection-speed": `${Math.floor(Math.random() * 19e3) + 1e3}kbps`,
|
|
366
|
+
"x-pigeon-session-id": generateUUID()
|
|
367
|
+
};
|
|
368
|
+
}
|
|
254
369
|
function sleep(ms) {
|
|
255
370
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
256
371
|
}
|
|
@@ -301,16 +416,10 @@ var RateController = class {
|
|
|
301
416
|
this._queryTimestamps.set(queryType, []);
|
|
302
417
|
}
|
|
303
418
|
const timestamps = this._queryTimestamps.get(queryType);
|
|
304
|
-
const filteredTimestamps = timestamps.filter(
|
|
305
|
-
(t) => t > currentTime - 60 * 60
|
|
306
|
-
);
|
|
419
|
+
const filteredTimestamps = timestamps.filter((t) => t > currentTime - 60 * 60);
|
|
307
420
|
this._queryTimestamps.set(queryType, filteredTimestamps);
|
|
308
421
|
const perTypeNextRequestTime = () => {
|
|
309
|
-
const reqs = this._reqsInSlidingWindow(
|
|
310
|
-
queryType,
|
|
311
|
-
currentTime,
|
|
312
|
-
perTypeSlidingWindow
|
|
313
|
-
);
|
|
422
|
+
const reqs = this._reqsInSlidingWindow(queryType, currentTime, perTypeSlidingWindow);
|
|
314
423
|
if (reqs.length < this.countPerSlidingWindow(queryType)) {
|
|
315
424
|
return 0;
|
|
316
425
|
} else {
|
|
@@ -323,11 +432,7 @@ var RateController = class {
|
|
|
323
432
|
}
|
|
324
433
|
const gqlAccumulatedSlidingWindow = 600;
|
|
325
434
|
const gqlAccumulatedMaxCount = 275;
|
|
326
|
-
const reqs = this._reqsInSlidingWindow(
|
|
327
|
-
null,
|
|
328
|
-
currentTime,
|
|
329
|
-
gqlAccumulatedSlidingWindow
|
|
330
|
-
);
|
|
435
|
+
const reqs = this._reqsInSlidingWindow(null, currentTime, gqlAccumulatedSlidingWindow);
|
|
331
436
|
if (reqs.length < gqlAccumulatedMaxCount) {
|
|
332
437
|
return 0;
|
|
333
438
|
} else {
|
|
@@ -337,33 +442,18 @@ var RateController = class {
|
|
|
337
442
|
const untrackedNextRequestTime = () => {
|
|
338
443
|
if (untrackedQueries) {
|
|
339
444
|
if (queryType === "iphone") {
|
|
340
|
-
const reqs = this._reqsInSlidingWindow(
|
|
341
|
-
queryType,
|
|
342
|
-
currentTime,
|
|
343
|
-
iphoneSlidingWindow
|
|
344
|
-
);
|
|
445
|
+
const reqs = this._reqsInSlidingWindow(queryType, currentTime, iphoneSlidingWindow);
|
|
345
446
|
this._iphoneEarliestNextRequestTime = Math.min(...reqs) + iphoneSlidingWindow + 18;
|
|
346
447
|
} else {
|
|
347
|
-
const reqs = this._reqsInSlidingWindow(
|
|
348
|
-
queryType,
|
|
349
|
-
currentTime,
|
|
350
|
-
perTypeSlidingWindow
|
|
351
|
-
);
|
|
448
|
+
const reqs = this._reqsInSlidingWindow(queryType, currentTime, perTypeSlidingWindow);
|
|
352
449
|
this._earliestNextRequestTime = Math.min(...reqs) + perTypeSlidingWindow + 6;
|
|
353
450
|
}
|
|
354
451
|
}
|
|
355
|
-
return Math.max(
|
|
356
|
-
this._iphoneEarliestNextRequestTime,
|
|
357
|
-
this._earliestNextRequestTime
|
|
358
|
-
);
|
|
452
|
+
return Math.max(this._iphoneEarliestNextRequestTime, this._earliestNextRequestTime);
|
|
359
453
|
};
|
|
360
454
|
const iphoneNextRequest = () => {
|
|
361
455
|
if (queryType === "iphone") {
|
|
362
|
-
const reqs = this._reqsInSlidingWindow(
|
|
363
|
-
queryType,
|
|
364
|
-
currentTime,
|
|
365
|
-
iphoneSlidingWindow
|
|
366
|
-
);
|
|
456
|
+
const reqs = this._reqsInSlidingWindow(queryType, currentTime, iphoneSlidingWindow);
|
|
367
457
|
if (reqs.length >= 199) {
|
|
368
458
|
return Math.min(...reqs) + iphoneSlidingWindow + 18;
|
|
369
459
|
}
|
|
@@ -431,7 +521,7 @@ var InstaloaderContext = class {
|
|
|
431
521
|
quiet;
|
|
432
522
|
iphoneSupport;
|
|
433
523
|
fatalStatusCodes;
|
|
434
|
-
|
|
524
|
+
_cookieStore;
|
|
435
525
|
_csrfToken = null;
|
|
436
526
|
_username = null;
|
|
437
527
|
_userId = null;
|
|
@@ -452,13 +542,13 @@ var InstaloaderContext = class {
|
|
|
452
542
|
this.quiet = options.quiet ?? false;
|
|
453
543
|
this.iphoneSupport = options.iphoneSupport ?? true;
|
|
454
544
|
this.fatalStatusCodes = options.fatalStatusCodes ?? [];
|
|
455
|
-
this.
|
|
545
|
+
this._cookieStore = new SimpleCookieStore();
|
|
456
546
|
this._iphoneHeaders = defaultIphoneHeaders();
|
|
457
547
|
this._initAnonymousCookies();
|
|
458
548
|
this._rateController = options.rateController ? options.rateController(this) : new RateController(this);
|
|
459
549
|
}
|
|
460
550
|
_initAnonymousCookies() {
|
|
461
|
-
const
|
|
551
|
+
const url = "https://www.instagram.com/";
|
|
462
552
|
const defaultCookies = {
|
|
463
553
|
sessionid: "",
|
|
464
554
|
mid: "",
|
|
@@ -468,15 +558,7 @@ var InstaloaderContext = class {
|
|
|
468
558
|
s_network: "",
|
|
469
559
|
ds_user_id: ""
|
|
470
560
|
};
|
|
471
|
-
|
|
472
|
-
const cookie = new import_tough_cookie.Cookie({
|
|
473
|
-
key: name,
|
|
474
|
-
value,
|
|
475
|
-
domain,
|
|
476
|
-
path: "/"
|
|
477
|
-
});
|
|
478
|
-
this._cookieJar.setCookieSync(cookie, `https://${domain}/`);
|
|
479
|
-
}
|
|
561
|
+
this._cookieStore.setCookies(defaultCookies, url);
|
|
480
562
|
}
|
|
481
563
|
/** True if this instance is logged in. */
|
|
482
564
|
get is_logged_in() {
|
|
@@ -569,27 +651,13 @@ var InstaloaderContext = class {
|
|
|
569
651
|
* Get cookies as a plain object.
|
|
570
652
|
*/
|
|
571
653
|
getCookies(url = "https://www.instagram.com/") {
|
|
572
|
-
|
|
573
|
-
const result = {};
|
|
574
|
-
for (const cookie of cookies) {
|
|
575
|
-
result[cookie.key] = cookie.value;
|
|
576
|
-
}
|
|
577
|
-
return result;
|
|
654
|
+
return this._cookieStore.getCookies(url);
|
|
578
655
|
}
|
|
579
656
|
/**
|
|
580
657
|
* Set cookies from a plain object.
|
|
581
658
|
*/
|
|
582
659
|
setCookies(cookies, url = "https://www.instagram.com/") {
|
|
583
|
-
|
|
584
|
-
for (const [name, value] of Object.entries(cookies)) {
|
|
585
|
-
const cookie = new import_tough_cookie.Cookie({
|
|
586
|
-
key: name,
|
|
587
|
-
value,
|
|
588
|
-
domain,
|
|
589
|
-
path: "/"
|
|
590
|
-
});
|
|
591
|
-
this._cookieJar.setCookieSync(cookie, url);
|
|
592
|
-
}
|
|
660
|
+
this._cookieStore.setCookies(cookies, url);
|
|
593
661
|
}
|
|
594
662
|
/**
|
|
595
663
|
* Save session data for later restoration.
|
|
@@ -601,7 +669,7 @@ var InstaloaderContext = class {
|
|
|
601
669
|
* Load session data from a saved session.
|
|
602
670
|
*/
|
|
603
671
|
loadSession(username, sessionData) {
|
|
604
|
-
this.
|
|
672
|
+
this._cookieStore = new SimpleCookieStore();
|
|
605
673
|
this.setCookies(sessionData);
|
|
606
674
|
this._csrfToken = sessionData["csrftoken"] || null;
|
|
607
675
|
this._username = username;
|
|
@@ -614,26 +682,16 @@ var InstaloaderContext = class {
|
|
|
614
682
|
this.setCookies(cookies);
|
|
615
683
|
}
|
|
616
684
|
/**
|
|
617
|
-
* Build cookie header string from cookie
|
|
685
|
+
* Build cookie header string from cookie store.
|
|
618
686
|
*/
|
|
619
687
|
_getCookieHeader(url) {
|
|
620
|
-
|
|
621
|
-
return cookies.map((c) => `${c.key}=${c.value}`).join("; ");
|
|
688
|
+
return this._cookieStore.getCookieHeader(url);
|
|
622
689
|
}
|
|
623
690
|
/**
|
|
624
691
|
* Parse and store cookies from Set-Cookie headers.
|
|
625
692
|
*/
|
|
626
693
|
_storeCookies(url, headers) {
|
|
627
|
-
|
|
628
|
-
for (const cookieStr of setCookies) {
|
|
629
|
-
try {
|
|
630
|
-
const cookie = import_tough_cookie.Cookie.parse(cookieStr);
|
|
631
|
-
if (cookie) {
|
|
632
|
-
this._cookieJar.setCookieSync(cookie, url);
|
|
633
|
-
}
|
|
634
|
-
} catch {
|
|
635
|
-
}
|
|
636
|
-
}
|
|
694
|
+
this._cookieStore.parseSetCookieHeaders(headers, url);
|
|
637
695
|
}
|
|
638
696
|
/**
|
|
639
697
|
* Format response error message.
|
|
@@ -657,7 +715,8 @@ var InstaloaderContext = class {
|
|
|
657
715
|
host = "www.instagram.com",
|
|
658
716
|
usePost = false,
|
|
659
717
|
attempt = 1,
|
|
660
|
-
headers: extraHeaders
|
|
718
|
+
headers: extraHeaders,
|
|
719
|
+
refreshDynamicHeaders = false
|
|
661
720
|
} = options;
|
|
662
721
|
const isGraphqlQuery = "query_hash" in params && path2.includes("graphql/query");
|
|
663
722
|
const isDocIdQuery = "doc_id" in params && path2.includes("graphql/query");
|
|
@@ -678,10 +737,17 @@ var InstaloaderContext = class {
|
|
|
678
737
|
await this._rateController.waitBeforeQuery("other");
|
|
679
738
|
}
|
|
680
739
|
const url = new URL(`https://${host}/${path2}`);
|
|
740
|
+
let headersToUse = extraHeaders;
|
|
741
|
+
if (refreshDynamicHeaders && extraHeaders) {
|
|
742
|
+
headersToUse = {
|
|
743
|
+
...extraHeaders,
|
|
744
|
+
...getPerRequestHeaders()
|
|
745
|
+
};
|
|
746
|
+
}
|
|
681
747
|
const headers = {
|
|
682
748
|
...this._defaultHttpHeader(true),
|
|
683
749
|
Cookie: this._getCookieHeader(url.toString()),
|
|
684
|
-
...
|
|
750
|
+
...headersToUse
|
|
685
751
|
};
|
|
686
752
|
if (this._csrfToken) {
|
|
687
753
|
headers["X-CSRFToken"] = this._csrfToken;
|
|
@@ -703,10 +769,7 @@ var InstaloaderContext = class {
|
|
|
703
769
|
});
|
|
704
770
|
} else {
|
|
705
771
|
for (const [key, value] of Object.entries(params)) {
|
|
706
|
-
url.searchParams.set(
|
|
707
|
-
key,
|
|
708
|
-
typeof value === "string" ? value : JSON.stringify(value)
|
|
709
|
-
);
|
|
772
|
+
url.searchParams.set(key, typeof value === "string" ? value : JSON.stringify(value));
|
|
710
773
|
}
|
|
711
774
|
response = await fetch(url.toString(), {
|
|
712
775
|
method: "GET",
|
|
@@ -729,9 +792,7 @@ var InstaloaderContext = class {
|
|
|
729
792
|
HTTP redirect from ${url} to ${redirectUrl}`);
|
|
730
793
|
if (redirectUrl.startsWith("https://www.instagram.com/accounts/login") || redirectUrl.startsWith("https://i.instagram.com/accounts/login")) {
|
|
731
794
|
if (!this.is_logged_in) {
|
|
732
|
-
throw new LoginRequiredException(
|
|
733
|
-
"Redirected to login page. Use login() first."
|
|
734
|
-
);
|
|
795
|
+
throw new LoginRequiredException("Redirected to login page. Use login() first.");
|
|
735
796
|
}
|
|
736
797
|
throw new AbortDownloadException(
|
|
737
798
|
"Redirected to login page. You've been logged out, please wait some time, recreate the session and try again"
|
|
@@ -803,8 +864,10 @@ HTTP redirect from ${url} to ${redirectUrl}`);
|
|
|
803
864
|
}
|
|
804
865
|
return this.getJson(path2, params, {
|
|
805
866
|
host,
|
|
806
|
-
usePost
|
|
867
|
+
// usePost is intentionally omitted to default to GET on retry
|
|
807
868
|
attempt: attempt + 1,
|
|
869
|
+
refreshDynamicHeaders: true,
|
|
870
|
+
// Refresh headers on retry to appear as different client
|
|
808
871
|
...extraHeaders !== void 0 && { headers: extraHeaders }
|
|
809
872
|
});
|
|
810
873
|
}
|
|
@@ -836,11 +899,17 @@ HTTP redirect from ${url} to ${redirectUrl}`);
|
|
|
836
899
|
return respJson;
|
|
837
900
|
}
|
|
838
901
|
/**
|
|
839
|
-
* Do a doc_id-based GraphQL Query
|
|
902
|
+
* Do a doc_id-based GraphQL Query.
|
|
903
|
+
*
|
|
904
|
+
* Uses GET for anonymous requests (works without login) and POST for authenticated
|
|
905
|
+
* requests. This is the correct behavior - Instagram's API accepts GET for public
|
|
906
|
+
* data but requires POST with session for authenticated operations.
|
|
840
907
|
*/
|
|
841
908
|
async doc_id_graphql_query(docId, variables, referer) {
|
|
909
|
+
const perRequestHeaders = getPerRequestHeaders();
|
|
842
910
|
const headers = {
|
|
843
911
|
...this._defaultHttpHeader(true),
|
|
912
|
+
...perRequestHeaders,
|
|
844
913
|
authority: "www.instagram.com",
|
|
845
914
|
scheme: "https",
|
|
846
915
|
accept: "*/*"
|
|
@@ -851,10 +920,11 @@ HTTP redirect from ${url} to ${redirectUrl}`);
|
|
|
851
920
|
headers["referer"] = encodeURIComponent(referer);
|
|
852
921
|
}
|
|
853
922
|
const variablesJson = JSON.stringify(variables);
|
|
923
|
+
const usePost = this.is_logged_in;
|
|
854
924
|
const respJson = await this.getJson(
|
|
855
925
|
"graphql/query",
|
|
856
926
|
{ variables: variablesJson, doc_id: docId, server_timestamps: "true" },
|
|
857
|
-
{ usePost
|
|
927
|
+
{ usePost, headers }
|
|
858
928
|
);
|
|
859
929
|
if (!("status" in respJson)) {
|
|
860
930
|
this.error('GraphQL response did not contain a "status" field.');
|
|
@@ -863,12 +933,14 @@ HTTP redirect from ${url} to ${redirectUrl}`);
|
|
|
863
933
|
}
|
|
864
934
|
/**
|
|
865
935
|
* JSON request to i.instagram.com.
|
|
936
|
+
* Each request uses fresh dynamic headers to appear as different clients.
|
|
866
937
|
*/
|
|
867
938
|
async get_iphone_json(path2, params) {
|
|
939
|
+
const perRequestHeaders = getPerRequestHeaders();
|
|
868
940
|
const headers = {
|
|
869
941
|
...this._iphoneHeaders,
|
|
870
|
-
|
|
871
|
-
"
|
|
942
|
+
...perRequestHeaders,
|
|
943
|
+
"ig-intended-user-id": this._userId || ""
|
|
872
944
|
};
|
|
873
945
|
const cookies = this.getCookies("https://i.instagram.com/");
|
|
874
946
|
const headerCookiesMapping = {
|
|
@@ -923,9 +995,7 @@ HTTP redirect from ${url} to ${redirectUrl}`);
|
|
|
923
995
|
this._responseError(response.status, response.statusText, url)
|
|
924
996
|
);
|
|
925
997
|
}
|
|
926
|
-
throw new ConnectionException(
|
|
927
|
-
this._responseError(response.status, response.statusText, url)
|
|
928
|
-
);
|
|
998
|
+
throw new ConnectionException(this._responseError(response.status, response.statusText, url));
|
|
929
999
|
}
|
|
930
1000
|
/**
|
|
931
1001
|
* Test if logged in by querying the current user.
|
|
@@ -947,7 +1017,7 @@ HTTP redirect from ${url} to ${redirectUrl}`);
|
|
|
947
1017
|
* Login to Instagram.
|
|
948
1018
|
*/
|
|
949
1019
|
async login(username, password) {
|
|
950
|
-
this.
|
|
1020
|
+
this._cookieStore = new SimpleCookieStore();
|
|
951
1021
|
this._initAnonymousCookies();
|
|
952
1022
|
const initUrl = "https://www.instagram.com/";
|
|
953
1023
|
const initResponse = await fetch(initUrl, {
|
|
@@ -1067,7 +1137,7 @@ HTTP redirect from ${url} to ${redirectUrl}`);
|
|
|
1067
1137
|
throw new InvalidArgumentException("No two-factor authentication pending.");
|
|
1068
1138
|
}
|
|
1069
1139
|
const { csrfToken, cookies, username, twoFactorId } = this._twoFactorAuthPending;
|
|
1070
|
-
this.
|
|
1140
|
+
this._cookieStore = new SimpleCookieStore();
|
|
1071
1141
|
this.setCookies(cookies);
|
|
1072
1142
|
const loginUrl = "https://www.instagram.com/accounts/login/ajax/two_factor/";
|
|
1073
1143
|
const response = await fetch(loginUrl, {
|
|
@@ -1449,6 +1519,47 @@ function extractMentions(text) {
|
|
|
1449
1519
|
}
|
|
1450
1520
|
return matches;
|
|
1451
1521
|
}
|
|
1522
|
+
var POST_URL_REGEX = /(?:https?:\/\/)?(?:www\.)?instagram\.com\/(?:p|reel|tv)\/([A-Za-z0-9_-]+)/;
|
|
1523
|
+
var PROFILE_URL_REGEX = /(?:https?:\/\/)?(?:www\.)?instagram\.com\/([A-Za-z0-9._]+)\/?(?:\?.*)?$/;
|
|
1524
|
+
var HASHTAG_URL_REGEX = /(?:https?:\/\/)?(?:www\.)?instagram\.com\/explore\/tags\/([^/?]+)/;
|
|
1525
|
+
function parseInstagramUrl(url) {
|
|
1526
|
+
const postMatch = url.match(POST_URL_REGEX);
|
|
1527
|
+
if (postMatch && postMatch[1]) {
|
|
1528
|
+
return { type: "post", shortcode: postMatch[1] };
|
|
1529
|
+
}
|
|
1530
|
+
const hashtagMatch = url.match(HASHTAG_URL_REGEX);
|
|
1531
|
+
if (hashtagMatch && hashtagMatch[1]) {
|
|
1532
|
+
return { type: "hashtag", hashtag: hashtagMatch[1] };
|
|
1533
|
+
}
|
|
1534
|
+
const profileMatch = url.match(PROFILE_URL_REGEX);
|
|
1535
|
+
if (profileMatch && profileMatch[1]) {
|
|
1536
|
+
const nonProfilePaths = [
|
|
1537
|
+
"explore",
|
|
1538
|
+
"accounts",
|
|
1539
|
+
"directory",
|
|
1540
|
+
"about",
|
|
1541
|
+
"legal",
|
|
1542
|
+
"developer",
|
|
1543
|
+
"stories"
|
|
1544
|
+
];
|
|
1545
|
+
if (!nonProfilePaths.includes(profileMatch[1].toLowerCase())) {
|
|
1546
|
+
return { type: "profile", username: profileMatch[1] };
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
return { type: "unknown" };
|
|
1550
|
+
}
|
|
1551
|
+
function extractShortcode(url) {
|
|
1552
|
+
const match = url.match(POST_URL_REGEX);
|
|
1553
|
+
return match ? match[1] : null;
|
|
1554
|
+
}
|
|
1555
|
+
function extractUsername(url) {
|
|
1556
|
+
const parsed = parseInstagramUrl(url);
|
|
1557
|
+
return parsed.type === "profile" ? parsed.username ?? null : null;
|
|
1558
|
+
}
|
|
1559
|
+
function extractHashtagFromUrl(url) {
|
|
1560
|
+
const match = url.match(HASHTAG_URL_REGEX);
|
|
1561
|
+
return match ? match[1] : null;
|
|
1562
|
+
}
|
|
1452
1563
|
function ellipsifyCaption(caption) {
|
|
1453
1564
|
const pcaption = caption.split("\n").filter((s) => s).map((s) => s.replace(/\//g, "\u2215")).join(" ").trim();
|
|
1454
1565
|
return pcaption.length > 31 ? pcaption.slice(0, 30) + "\u2026" : pcaption;
|
|
@@ -3212,7 +3323,9 @@ var Instaloader = class {
|
|
|
3212
3323
|
...options.sleep !== void 0 && { sleep: options.sleep },
|
|
3213
3324
|
...options.quiet !== void 0 && { quiet: options.quiet },
|
|
3214
3325
|
...options.userAgent !== void 0 && { userAgent: options.userAgent },
|
|
3215
|
-
...options.maxConnectionAttempts !== void 0 && {
|
|
3326
|
+
...options.maxConnectionAttempts !== void 0 && {
|
|
3327
|
+
maxConnectionAttempts: options.maxConnectionAttempts
|
|
3328
|
+
},
|
|
3216
3329
|
...options.requestTimeout !== void 0 && { requestTimeout: options.requestTimeout },
|
|
3217
3330
|
...options.rateController !== void 0 && { rateController: options.rateController },
|
|
3218
3331
|
...options.fatalStatusCodes !== void 0 && { fatalStatusCodes: options.fatalStatusCodes },
|
|
@@ -3317,7 +3430,7 @@ var Instaloader = class {
|
|
|
3317
3430
|
* Load session from file.
|
|
3318
3431
|
*/
|
|
3319
3432
|
async loadSessionFromFile(username, filename) {
|
|
3320
|
-
|
|
3433
|
+
const targetFile = filename ?? getDefaultSessionFilename(username);
|
|
3321
3434
|
if (!fs.existsSync(targetFile)) {
|
|
3322
3435
|
throw new Error(`Session file not found: ${targetFile}`);
|
|
3323
3436
|
}
|
|
@@ -3372,7 +3485,9 @@ var Instaloader = class {
|
|
|
3372
3485
|
}
|
|
3373
3486
|
});
|
|
3374
3487
|
if (!response.ok) {
|
|
3375
|
-
throw new ConnectionException(
|
|
3488
|
+
throw new ConnectionException(
|
|
3489
|
+
`Failed to download: ${response.status} ${response.statusText}`
|
|
3490
|
+
);
|
|
3376
3491
|
}
|
|
3377
3492
|
let finalFilename = nominalFilename;
|
|
3378
3493
|
const contentType = response.headers.get("Content-Type");
|
|
@@ -3471,11 +3586,21 @@ var Instaloader = class {
|
|
|
3471
3586
|
}
|
|
3472
3587
|
if (node.is_video) {
|
|
3473
3588
|
if (this.downloadVideos && node.video_url) {
|
|
3474
|
-
const dl = await this.downloadPic(
|
|
3589
|
+
const dl = await this.downloadPic(
|
|
3590
|
+
filename,
|
|
3591
|
+
node.video_url,
|
|
3592
|
+
post.date_utc,
|
|
3593
|
+
`${index + 1}`
|
|
3594
|
+
);
|
|
3475
3595
|
downloaded = downloaded || dl;
|
|
3476
3596
|
}
|
|
3477
3597
|
} else if (node.display_url) {
|
|
3478
|
-
const dl = await this.downloadPic(
|
|
3598
|
+
const dl = await this.downloadPic(
|
|
3599
|
+
filename,
|
|
3600
|
+
node.display_url,
|
|
3601
|
+
post.date_utc,
|
|
3602
|
+
`${index + 1}`
|
|
3603
|
+
);
|
|
3479
3604
|
downloaded = downloaded || dl;
|
|
3480
3605
|
}
|
|
3481
3606
|
index++;
|
|
@@ -3532,7 +3657,10 @@ var Instaloader = class {
|
|
|
3532
3657
|
}
|
|
3533
3658
|
if (displayedCount !== void 0) {
|
|
3534
3659
|
const width = displayedCount.toString().length;
|
|
3535
|
-
this.context.log(
|
|
3660
|
+
this.context.log(
|
|
3661
|
+
`[${number.toString().padStart(width)}/${displayedCount.toString().padStart(width)}] `,
|
|
3662
|
+
false
|
|
3663
|
+
);
|
|
3536
3664
|
} else {
|
|
3537
3665
|
this.context.log(`[${number.toString().padStart(3)}] `, false);
|
|
3538
3666
|
}
|
|
@@ -3614,7 +3742,12 @@ var Instaloader = class {
|
|
|
3614
3742
|
async downloadProfilePic(profile) {
|
|
3615
3743
|
const url = await profile.getProfilePicUrl();
|
|
3616
3744
|
if (!url) return;
|
|
3617
|
-
const dirname2 = formatFilename(
|
|
3745
|
+
const dirname2 = formatFilename(
|
|
3746
|
+
this.dirnamePattern,
|
|
3747
|
+
profile,
|
|
3748
|
+
profile.username.toLowerCase(),
|
|
3749
|
+
this.sanitizePaths
|
|
3750
|
+
);
|
|
3618
3751
|
const filename = path.join(dirname2, `${profile.username.toLowerCase()}_profile_pic`);
|
|
3619
3752
|
await this.downloadPic(filename, url, /* @__PURE__ */ new Date());
|
|
3620
3753
|
this.context.log("");
|
|
@@ -3710,8 +3843,11 @@ var Instaloader = class {
|
|
|
3710
3843
|
TwoFactorAuthRequiredException,
|
|
3711
3844
|
defaultIphoneHeaders,
|
|
3712
3845
|
defaultUserAgent,
|
|
3846
|
+
extractHashtagFromUrl,
|
|
3713
3847
|
extractHashtags,
|
|
3714
3848
|
extractMentions,
|
|
3849
|
+
extractShortcode,
|
|
3850
|
+
extractUsername,
|
|
3715
3851
|
formatFilename,
|
|
3716
3852
|
formatStringContainsKey,
|
|
3717
3853
|
getConfigDir,
|
|
@@ -3720,6 +3856,7 @@ var Instaloader = class {
|
|
|
3720
3856
|
getJsonStructure,
|
|
3721
3857
|
loadStructure,
|
|
3722
3858
|
mediaidToShortcode,
|
|
3859
|
+
parseInstagramUrl,
|
|
3723
3860
|
resumableIteration,
|
|
3724
3861
|
sanitizePath,
|
|
3725
3862
|
shortcodeToMediaid
|