@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/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": (0, import_uuid.v4)(),
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
- _cookieJar;
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._cookieJar = new import_tough_cookie.CookieJar();
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 domain = "www.instagram.com";
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
- for (const [name, value] of Object.entries(defaultCookies)) {
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
- const cookies = this._cookieJar.getCookiesSync(url);
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
- const domain = new URL(url).hostname;
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._cookieJar = new import_tough_cookie.CookieJar();
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 jar.
685
+ * Build cookie header string from cookie store.
618
686
  */
619
687
  _getCookieHeader(url) {
620
- const cookies = this._cookieJar.getCookiesSync(url);
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
- const setCookies = headers.getSetCookie?.() || [];
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
- ...extraHeaders
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 using POST.
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: true, headers }
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
- "ig-intended-user-id": this._userId || "",
871
- "x-pigeon-rawclienttime": (Date.now() / 1e3).toFixed(6)
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._cookieJar = new import_tough_cookie.CookieJar();
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._cookieJar = new import_tough_cookie.CookieJar();
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 && { maxConnectionAttempts: options.maxConnectionAttempts },
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
- let targetFile = filename ?? getDefaultSessionFilename(username);
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(`Failed to download: ${response.status} ${response.statusText}`);
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(filename, node.video_url, post.date_utc, `${index + 1}`);
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(filename, node.display_url, post.date_utc, `${index + 1}`);
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(`[${number.toString().padStart(width)}/${displayedCount.toString().padStart(width)}] `, false);
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(this.dirnamePattern, profile, profile.username.toLowerCase(), this.sanitizePaths);
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