@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.mjs CHANGED
@@ -132,9 +132,113 @@ var InvalidIteratorException = class extends InstaloaderException {
132
132
  }
133
133
  };
134
134
 
135
+ // src/utils.ts
136
+ function generateUUID() {
137
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
138
+ return crypto.randomUUID();
139
+ }
140
+ const bytes = new Uint8Array(16);
141
+ if (typeof crypto !== "undefined" && crypto.getRandomValues) {
142
+ crypto.getRandomValues(bytes);
143
+ } else {
144
+ for (let i = 0; i < 16; i++) {
145
+ bytes[i] = Math.floor(Math.random() * 256);
146
+ }
147
+ }
148
+ bytes[6] = bytes[6] & 15 | 64;
149
+ bytes[8] = bytes[8] & 63 | 128;
150
+ const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
151
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
152
+ }
153
+ var SimpleCookieStore = class {
154
+ cookies = /* @__PURE__ */ new Map();
155
+ /**
156
+ * Get cookies for a URL as a key-value object.
157
+ */
158
+ getCookies(url) {
159
+ const domain = this.extractDomain(url);
160
+ const domainCookies = this.cookies.get(domain);
161
+ if (!domainCookies) {
162
+ return {};
163
+ }
164
+ return Object.fromEntries(domainCookies);
165
+ }
166
+ /**
167
+ * Set cookies from a key-value object.
168
+ */
169
+ setCookies(cookies, url) {
170
+ const domain = this.extractDomain(url);
171
+ let domainCookies = this.cookies.get(domain);
172
+ if (!domainCookies) {
173
+ domainCookies = /* @__PURE__ */ new Map();
174
+ this.cookies.set(domain, domainCookies);
175
+ }
176
+ for (const [key, value] of Object.entries(cookies)) {
177
+ domainCookies.set(key, value);
178
+ }
179
+ }
180
+ /**
181
+ * Parse and store cookies from Set-Cookie headers.
182
+ */
183
+ parseSetCookieHeaders(headers, url) {
184
+ const setCookies = headers.getSetCookie?.() || [];
185
+ const domain = this.extractDomain(url);
186
+ let domainCookies = this.cookies.get(domain);
187
+ if (!domainCookies) {
188
+ domainCookies = /* @__PURE__ */ new Map();
189
+ this.cookies.set(domain, domainCookies);
190
+ }
191
+ for (const cookieStr of setCookies) {
192
+ const parsed = this.parseSetCookie(cookieStr);
193
+ if (parsed) {
194
+ domainCookies.set(parsed.name, parsed.value);
195
+ }
196
+ }
197
+ }
198
+ /**
199
+ * Generate Cookie header string for a URL.
200
+ */
201
+ getCookieHeader(url) {
202
+ const cookies = this.getCookies(url);
203
+ return Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join("; ");
204
+ }
205
+ /**
206
+ * Clear all cookies.
207
+ */
208
+ clear() {
209
+ this.cookies.clear();
210
+ }
211
+ /**
212
+ * Clear cookies for a specific domain.
213
+ */
214
+ clearDomain(url) {
215
+ const domain = this.extractDomain(url);
216
+ this.cookies.delete(domain);
217
+ }
218
+ extractDomain(url) {
219
+ try {
220
+ return new URL(url).hostname;
221
+ } catch {
222
+ return url;
223
+ }
224
+ }
225
+ parseSetCookie(cookieStr) {
226
+ const firstSemi = cookieStr.indexOf(";");
227
+ const nameValue = firstSemi === -1 ? cookieStr : cookieStr.slice(0, firstSemi);
228
+ const eqIndex = nameValue.indexOf("=");
229
+ if (eqIndex === -1) {
230
+ return null;
231
+ }
232
+ const name = nameValue.slice(0, eqIndex).trim();
233
+ const value = nameValue.slice(eqIndex + 1).trim();
234
+ if (!name) {
235
+ return null;
236
+ }
237
+ return { name, value };
238
+ }
239
+ };
240
+
135
241
  // src/instaloadercontext.ts
136
- import { CookieJar, Cookie } from "tough-cookie";
137
- import { v4 as uuidv4 } from "uuid";
138
242
  function defaultUserAgent() {
139
243
  return "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36";
140
244
  }
@@ -162,11 +266,18 @@ function defaultIphoneHeaders() {
162
266
  "x-ig-mapped-locale": "en-US",
163
267
  "x-ig-timezone-offset": String(timezoneOffset),
164
268
  "x-ig-www-claim": "0",
165
- "x-pigeon-session-id": uuidv4(),
269
+ "x-pigeon-session-id": generateUUID(),
166
270
  "x-tigon-is-retry": "False",
167
271
  "x-whatsapp": "0"
168
272
  };
169
273
  }
274
+ function getPerRequestHeaders() {
275
+ return {
276
+ "x-pigeon-rawclienttime": (Date.now() / 1e3).toFixed(6),
277
+ "x-ig-connection-speed": `${Math.floor(Math.random() * 19e3) + 1e3}kbps`,
278
+ "x-pigeon-session-id": generateUUID()
279
+ };
280
+ }
170
281
  function sleep(ms) {
171
282
  return new Promise((resolve) => setTimeout(resolve, ms));
172
283
  }
@@ -217,16 +328,10 @@ var RateController = class {
217
328
  this._queryTimestamps.set(queryType, []);
218
329
  }
219
330
  const timestamps = this._queryTimestamps.get(queryType);
220
- const filteredTimestamps = timestamps.filter(
221
- (t) => t > currentTime - 60 * 60
222
- );
331
+ const filteredTimestamps = timestamps.filter((t) => t > currentTime - 60 * 60);
223
332
  this._queryTimestamps.set(queryType, filteredTimestamps);
224
333
  const perTypeNextRequestTime = () => {
225
- const reqs = this._reqsInSlidingWindow(
226
- queryType,
227
- currentTime,
228
- perTypeSlidingWindow
229
- );
334
+ const reqs = this._reqsInSlidingWindow(queryType, currentTime, perTypeSlidingWindow);
230
335
  if (reqs.length < this.countPerSlidingWindow(queryType)) {
231
336
  return 0;
232
337
  } else {
@@ -239,11 +344,7 @@ var RateController = class {
239
344
  }
240
345
  const gqlAccumulatedSlidingWindow = 600;
241
346
  const gqlAccumulatedMaxCount = 275;
242
- const reqs = this._reqsInSlidingWindow(
243
- null,
244
- currentTime,
245
- gqlAccumulatedSlidingWindow
246
- );
347
+ const reqs = this._reqsInSlidingWindow(null, currentTime, gqlAccumulatedSlidingWindow);
247
348
  if (reqs.length < gqlAccumulatedMaxCount) {
248
349
  return 0;
249
350
  } else {
@@ -253,33 +354,18 @@ var RateController = class {
253
354
  const untrackedNextRequestTime = () => {
254
355
  if (untrackedQueries) {
255
356
  if (queryType === "iphone") {
256
- const reqs = this._reqsInSlidingWindow(
257
- queryType,
258
- currentTime,
259
- iphoneSlidingWindow
260
- );
357
+ const reqs = this._reqsInSlidingWindow(queryType, currentTime, iphoneSlidingWindow);
261
358
  this._iphoneEarliestNextRequestTime = Math.min(...reqs) + iphoneSlidingWindow + 18;
262
359
  } else {
263
- const reqs = this._reqsInSlidingWindow(
264
- queryType,
265
- currentTime,
266
- perTypeSlidingWindow
267
- );
360
+ const reqs = this._reqsInSlidingWindow(queryType, currentTime, perTypeSlidingWindow);
268
361
  this._earliestNextRequestTime = Math.min(...reqs) + perTypeSlidingWindow + 6;
269
362
  }
270
363
  }
271
- return Math.max(
272
- this._iphoneEarliestNextRequestTime,
273
- this._earliestNextRequestTime
274
- );
364
+ return Math.max(this._iphoneEarliestNextRequestTime, this._earliestNextRequestTime);
275
365
  };
276
366
  const iphoneNextRequest = () => {
277
367
  if (queryType === "iphone") {
278
- const reqs = this._reqsInSlidingWindow(
279
- queryType,
280
- currentTime,
281
- iphoneSlidingWindow
282
- );
368
+ const reqs = this._reqsInSlidingWindow(queryType, currentTime, iphoneSlidingWindow);
283
369
  if (reqs.length >= 199) {
284
370
  return Math.min(...reqs) + iphoneSlidingWindow + 18;
285
371
  }
@@ -347,7 +433,7 @@ var InstaloaderContext = class {
347
433
  quiet;
348
434
  iphoneSupport;
349
435
  fatalStatusCodes;
350
- _cookieJar;
436
+ _cookieStore;
351
437
  _csrfToken = null;
352
438
  _username = null;
353
439
  _userId = null;
@@ -368,13 +454,13 @@ var InstaloaderContext = class {
368
454
  this.quiet = options.quiet ?? false;
369
455
  this.iphoneSupport = options.iphoneSupport ?? true;
370
456
  this.fatalStatusCodes = options.fatalStatusCodes ?? [];
371
- this._cookieJar = new CookieJar();
457
+ this._cookieStore = new SimpleCookieStore();
372
458
  this._iphoneHeaders = defaultIphoneHeaders();
373
459
  this._initAnonymousCookies();
374
460
  this._rateController = options.rateController ? options.rateController(this) : new RateController(this);
375
461
  }
376
462
  _initAnonymousCookies() {
377
- const domain = "www.instagram.com";
463
+ const url = "https://www.instagram.com/";
378
464
  const defaultCookies = {
379
465
  sessionid: "",
380
466
  mid: "",
@@ -384,15 +470,7 @@ var InstaloaderContext = class {
384
470
  s_network: "",
385
471
  ds_user_id: ""
386
472
  };
387
- for (const [name, value] of Object.entries(defaultCookies)) {
388
- const cookie = new Cookie({
389
- key: name,
390
- value,
391
- domain,
392
- path: "/"
393
- });
394
- this._cookieJar.setCookieSync(cookie, `https://${domain}/`);
395
- }
473
+ this._cookieStore.setCookies(defaultCookies, url);
396
474
  }
397
475
  /** True if this instance is logged in. */
398
476
  get is_logged_in() {
@@ -485,27 +563,13 @@ var InstaloaderContext = class {
485
563
  * Get cookies as a plain object.
486
564
  */
487
565
  getCookies(url = "https://www.instagram.com/") {
488
- const cookies = this._cookieJar.getCookiesSync(url);
489
- const result = {};
490
- for (const cookie of cookies) {
491
- result[cookie.key] = cookie.value;
492
- }
493
- return result;
566
+ return this._cookieStore.getCookies(url);
494
567
  }
495
568
  /**
496
569
  * Set cookies from a plain object.
497
570
  */
498
571
  setCookies(cookies, url = "https://www.instagram.com/") {
499
- const domain = new URL(url).hostname;
500
- for (const [name, value] of Object.entries(cookies)) {
501
- const cookie = new Cookie({
502
- key: name,
503
- value,
504
- domain,
505
- path: "/"
506
- });
507
- this._cookieJar.setCookieSync(cookie, url);
508
- }
572
+ this._cookieStore.setCookies(cookies, url);
509
573
  }
510
574
  /**
511
575
  * Save session data for later restoration.
@@ -517,7 +581,7 @@ var InstaloaderContext = class {
517
581
  * Load session data from a saved session.
518
582
  */
519
583
  loadSession(username, sessionData) {
520
- this._cookieJar = new CookieJar();
584
+ this._cookieStore = new SimpleCookieStore();
521
585
  this.setCookies(sessionData);
522
586
  this._csrfToken = sessionData["csrftoken"] || null;
523
587
  this._username = username;
@@ -530,26 +594,16 @@ var InstaloaderContext = class {
530
594
  this.setCookies(cookies);
531
595
  }
532
596
  /**
533
- * Build cookie header string from cookie jar.
597
+ * Build cookie header string from cookie store.
534
598
  */
535
599
  _getCookieHeader(url) {
536
- const cookies = this._cookieJar.getCookiesSync(url);
537
- return cookies.map((c) => `${c.key}=${c.value}`).join("; ");
600
+ return this._cookieStore.getCookieHeader(url);
538
601
  }
539
602
  /**
540
603
  * Parse and store cookies from Set-Cookie headers.
541
604
  */
542
605
  _storeCookies(url, headers) {
543
- const setCookies = headers.getSetCookie?.() || [];
544
- for (const cookieStr of setCookies) {
545
- try {
546
- const cookie = Cookie.parse(cookieStr);
547
- if (cookie) {
548
- this._cookieJar.setCookieSync(cookie, url);
549
- }
550
- } catch {
551
- }
552
- }
606
+ this._cookieStore.parseSetCookieHeaders(headers, url);
553
607
  }
554
608
  /**
555
609
  * Format response error message.
@@ -573,7 +627,8 @@ var InstaloaderContext = class {
573
627
  host = "www.instagram.com",
574
628
  usePost = false,
575
629
  attempt = 1,
576
- headers: extraHeaders
630
+ headers: extraHeaders,
631
+ refreshDynamicHeaders = false
577
632
  } = options;
578
633
  const isGraphqlQuery = "query_hash" in params && path2.includes("graphql/query");
579
634
  const isDocIdQuery = "doc_id" in params && path2.includes("graphql/query");
@@ -594,10 +649,17 @@ var InstaloaderContext = class {
594
649
  await this._rateController.waitBeforeQuery("other");
595
650
  }
596
651
  const url = new URL(`https://${host}/${path2}`);
652
+ let headersToUse = extraHeaders;
653
+ if (refreshDynamicHeaders && extraHeaders) {
654
+ headersToUse = {
655
+ ...extraHeaders,
656
+ ...getPerRequestHeaders()
657
+ };
658
+ }
597
659
  const headers = {
598
660
  ...this._defaultHttpHeader(true),
599
661
  Cookie: this._getCookieHeader(url.toString()),
600
- ...extraHeaders
662
+ ...headersToUse
601
663
  };
602
664
  if (this._csrfToken) {
603
665
  headers["X-CSRFToken"] = this._csrfToken;
@@ -619,10 +681,7 @@ var InstaloaderContext = class {
619
681
  });
620
682
  } else {
621
683
  for (const [key, value] of Object.entries(params)) {
622
- url.searchParams.set(
623
- key,
624
- typeof value === "string" ? value : JSON.stringify(value)
625
- );
684
+ url.searchParams.set(key, typeof value === "string" ? value : JSON.stringify(value));
626
685
  }
627
686
  response = await fetch(url.toString(), {
628
687
  method: "GET",
@@ -645,9 +704,7 @@ var InstaloaderContext = class {
645
704
  HTTP redirect from ${url} to ${redirectUrl}`);
646
705
  if (redirectUrl.startsWith("https://www.instagram.com/accounts/login") || redirectUrl.startsWith("https://i.instagram.com/accounts/login")) {
647
706
  if (!this.is_logged_in) {
648
- throw new LoginRequiredException(
649
- "Redirected to login page. Use login() first."
650
- );
707
+ throw new LoginRequiredException("Redirected to login page. Use login() first.");
651
708
  }
652
709
  throw new AbortDownloadException(
653
710
  "Redirected to login page. You've been logged out, please wait some time, recreate the session and try again"
@@ -719,8 +776,10 @@ HTTP redirect from ${url} to ${redirectUrl}`);
719
776
  }
720
777
  return this.getJson(path2, params, {
721
778
  host,
722
- usePost,
779
+ // usePost is intentionally omitted to default to GET on retry
723
780
  attempt: attempt + 1,
781
+ refreshDynamicHeaders: true,
782
+ // Refresh headers on retry to appear as different client
724
783
  ...extraHeaders !== void 0 && { headers: extraHeaders }
725
784
  });
726
785
  }
@@ -752,11 +811,17 @@ HTTP redirect from ${url} to ${redirectUrl}`);
752
811
  return respJson;
753
812
  }
754
813
  /**
755
- * Do a doc_id-based GraphQL Query using POST.
814
+ * Do a doc_id-based GraphQL Query.
815
+ *
816
+ * Uses GET for anonymous requests (works without login) and POST for authenticated
817
+ * requests. This is the correct behavior - Instagram's API accepts GET for public
818
+ * data but requires POST with session for authenticated operations.
756
819
  */
757
820
  async doc_id_graphql_query(docId, variables, referer) {
821
+ const perRequestHeaders = getPerRequestHeaders();
758
822
  const headers = {
759
823
  ...this._defaultHttpHeader(true),
824
+ ...perRequestHeaders,
760
825
  authority: "www.instagram.com",
761
826
  scheme: "https",
762
827
  accept: "*/*"
@@ -767,10 +832,11 @@ HTTP redirect from ${url} to ${redirectUrl}`);
767
832
  headers["referer"] = encodeURIComponent(referer);
768
833
  }
769
834
  const variablesJson = JSON.stringify(variables);
835
+ const usePost = this.is_logged_in;
770
836
  const respJson = await this.getJson(
771
837
  "graphql/query",
772
838
  { variables: variablesJson, doc_id: docId, server_timestamps: "true" },
773
- { usePost: true, headers }
839
+ { usePost, headers }
774
840
  );
775
841
  if (!("status" in respJson)) {
776
842
  this.error('GraphQL response did not contain a "status" field.');
@@ -779,12 +845,14 @@ HTTP redirect from ${url} to ${redirectUrl}`);
779
845
  }
780
846
  /**
781
847
  * JSON request to i.instagram.com.
848
+ * Each request uses fresh dynamic headers to appear as different clients.
782
849
  */
783
850
  async get_iphone_json(path2, params) {
851
+ const perRequestHeaders = getPerRequestHeaders();
784
852
  const headers = {
785
853
  ...this._iphoneHeaders,
786
- "ig-intended-user-id": this._userId || "",
787
- "x-pigeon-rawclienttime": (Date.now() / 1e3).toFixed(6)
854
+ ...perRequestHeaders,
855
+ "ig-intended-user-id": this._userId || ""
788
856
  };
789
857
  const cookies = this.getCookies("https://i.instagram.com/");
790
858
  const headerCookiesMapping = {
@@ -839,9 +907,7 @@ HTTP redirect from ${url} to ${redirectUrl}`);
839
907
  this._responseError(response.status, response.statusText, url)
840
908
  );
841
909
  }
842
- throw new ConnectionException(
843
- this._responseError(response.status, response.statusText, url)
844
- );
910
+ throw new ConnectionException(this._responseError(response.status, response.statusText, url));
845
911
  }
846
912
  /**
847
913
  * Test if logged in by querying the current user.
@@ -863,7 +929,7 @@ HTTP redirect from ${url} to ${redirectUrl}`);
863
929
  * Login to Instagram.
864
930
  */
865
931
  async login(username, password) {
866
- this._cookieJar = new CookieJar();
932
+ this._cookieStore = new SimpleCookieStore();
867
933
  this._initAnonymousCookies();
868
934
  const initUrl = "https://www.instagram.com/";
869
935
  const initResponse = await fetch(initUrl, {
@@ -983,7 +1049,7 @@ HTTP redirect from ${url} to ${redirectUrl}`);
983
1049
  throw new InvalidArgumentException("No two-factor authentication pending.");
984
1050
  }
985
1051
  const { csrfToken, cookies, username, twoFactorId } = this._twoFactorAuthPending;
986
- this._cookieJar = new CookieJar();
1052
+ this._cookieStore = new SimpleCookieStore();
987
1053
  this.setCookies(cookies);
988
1054
  const loginUrl = "https://www.instagram.com/accounts/login/ajax/two_factor/";
989
1055
  const response = await fetch(loginUrl, {
@@ -1365,6 +1431,47 @@ function extractMentions(text) {
1365
1431
  }
1366
1432
  return matches;
1367
1433
  }
1434
+ var POST_URL_REGEX = /(?:https?:\/\/)?(?:www\.)?instagram\.com\/(?:p|reel|tv)\/([A-Za-z0-9_-]+)/;
1435
+ var PROFILE_URL_REGEX = /(?:https?:\/\/)?(?:www\.)?instagram\.com\/([A-Za-z0-9._]+)\/?(?:\?.*)?$/;
1436
+ var HASHTAG_URL_REGEX = /(?:https?:\/\/)?(?:www\.)?instagram\.com\/explore\/tags\/([^/?]+)/;
1437
+ function parseInstagramUrl(url) {
1438
+ const postMatch = url.match(POST_URL_REGEX);
1439
+ if (postMatch && postMatch[1]) {
1440
+ return { type: "post", shortcode: postMatch[1] };
1441
+ }
1442
+ const hashtagMatch = url.match(HASHTAG_URL_REGEX);
1443
+ if (hashtagMatch && hashtagMatch[1]) {
1444
+ return { type: "hashtag", hashtag: hashtagMatch[1] };
1445
+ }
1446
+ const profileMatch = url.match(PROFILE_URL_REGEX);
1447
+ if (profileMatch && profileMatch[1]) {
1448
+ const nonProfilePaths = [
1449
+ "explore",
1450
+ "accounts",
1451
+ "directory",
1452
+ "about",
1453
+ "legal",
1454
+ "developer",
1455
+ "stories"
1456
+ ];
1457
+ if (!nonProfilePaths.includes(profileMatch[1].toLowerCase())) {
1458
+ return { type: "profile", username: profileMatch[1] };
1459
+ }
1460
+ }
1461
+ return { type: "unknown" };
1462
+ }
1463
+ function extractShortcode(url) {
1464
+ const match = url.match(POST_URL_REGEX);
1465
+ return match ? match[1] : null;
1466
+ }
1467
+ function extractUsername(url) {
1468
+ const parsed = parseInstagramUrl(url);
1469
+ return parsed.type === "profile" ? parsed.username ?? null : null;
1470
+ }
1471
+ function extractHashtagFromUrl(url) {
1472
+ const match = url.match(HASHTAG_URL_REGEX);
1473
+ return match ? match[1] : null;
1474
+ }
1368
1475
  function ellipsifyCaption(caption) {
1369
1476
  const pcaption = caption.split("\n").filter((s) => s).map((s) => s.replace(/\//g, "\u2215")).join(" ").trim();
1370
1477
  return pcaption.length > 31 ? pcaption.slice(0, 30) + "\u2026" : pcaption;
@@ -3128,7 +3235,9 @@ var Instaloader = class {
3128
3235
  ...options.sleep !== void 0 && { sleep: options.sleep },
3129
3236
  ...options.quiet !== void 0 && { quiet: options.quiet },
3130
3237
  ...options.userAgent !== void 0 && { userAgent: options.userAgent },
3131
- ...options.maxConnectionAttempts !== void 0 && { maxConnectionAttempts: options.maxConnectionAttempts },
3238
+ ...options.maxConnectionAttempts !== void 0 && {
3239
+ maxConnectionAttempts: options.maxConnectionAttempts
3240
+ },
3132
3241
  ...options.requestTimeout !== void 0 && { requestTimeout: options.requestTimeout },
3133
3242
  ...options.rateController !== void 0 && { rateController: options.rateController },
3134
3243
  ...options.fatalStatusCodes !== void 0 && { fatalStatusCodes: options.fatalStatusCodes },
@@ -3233,7 +3342,7 @@ var Instaloader = class {
3233
3342
  * Load session from file.
3234
3343
  */
3235
3344
  async loadSessionFromFile(username, filename) {
3236
- let targetFile = filename ?? getDefaultSessionFilename(username);
3345
+ const targetFile = filename ?? getDefaultSessionFilename(username);
3237
3346
  if (!fs.existsSync(targetFile)) {
3238
3347
  throw new Error(`Session file not found: ${targetFile}`);
3239
3348
  }
@@ -3288,7 +3397,9 @@ var Instaloader = class {
3288
3397
  }
3289
3398
  });
3290
3399
  if (!response.ok) {
3291
- throw new ConnectionException(`Failed to download: ${response.status} ${response.statusText}`);
3400
+ throw new ConnectionException(
3401
+ `Failed to download: ${response.status} ${response.statusText}`
3402
+ );
3292
3403
  }
3293
3404
  let finalFilename = nominalFilename;
3294
3405
  const contentType = response.headers.get("Content-Type");
@@ -3387,11 +3498,21 @@ var Instaloader = class {
3387
3498
  }
3388
3499
  if (node.is_video) {
3389
3500
  if (this.downloadVideos && node.video_url) {
3390
- const dl = await this.downloadPic(filename, node.video_url, post.date_utc, `${index + 1}`);
3501
+ const dl = await this.downloadPic(
3502
+ filename,
3503
+ node.video_url,
3504
+ post.date_utc,
3505
+ `${index + 1}`
3506
+ );
3391
3507
  downloaded = downloaded || dl;
3392
3508
  }
3393
3509
  } else if (node.display_url) {
3394
- const dl = await this.downloadPic(filename, node.display_url, post.date_utc, `${index + 1}`);
3510
+ const dl = await this.downloadPic(
3511
+ filename,
3512
+ node.display_url,
3513
+ post.date_utc,
3514
+ `${index + 1}`
3515
+ );
3395
3516
  downloaded = downloaded || dl;
3396
3517
  }
3397
3518
  index++;
@@ -3448,7 +3569,10 @@ var Instaloader = class {
3448
3569
  }
3449
3570
  if (displayedCount !== void 0) {
3450
3571
  const width = displayedCount.toString().length;
3451
- this.context.log(`[${number.toString().padStart(width)}/${displayedCount.toString().padStart(width)}] `, false);
3572
+ this.context.log(
3573
+ `[${number.toString().padStart(width)}/${displayedCount.toString().padStart(width)}] `,
3574
+ false
3575
+ );
3452
3576
  } else {
3453
3577
  this.context.log(`[${number.toString().padStart(3)}] `, false);
3454
3578
  }
@@ -3530,7 +3654,12 @@ var Instaloader = class {
3530
3654
  async downloadProfilePic(profile) {
3531
3655
  const url = await profile.getProfilePicUrl();
3532
3656
  if (!url) return;
3533
- const dirname2 = formatFilename(this.dirnamePattern, profile, profile.username.toLowerCase(), this.sanitizePaths);
3657
+ const dirname2 = formatFilename(
3658
+ this.dirnamePattern,
3659
+ profile,
3660
+ profile.username.toLowerCase(),
3661
+ this.sanitizePaths
3662
+ );
3534
3663
  const filename = path.join(dirname2, `${profile.username.toLowerCase()}_profile_pic`);
3535
3664
  await this.downloadPic(filename, url, /* @__PURE__ */ new Date());
3536
3665
  this.context.log("");
@@ -3625,8 +3754,11 @@ export {
3625
3754
  TwoFactorAuthRequiredException,
3626
3755
  defaultIphoneHeaders,
3627
3756
  defaultUserAgent,
3757
+ extractHashtagFromUrl,
3628
3758
  extractHashtags,
3629
3759
  extractMentions,
3760
+ extractShortcode,
3761
+ extractUsername,
3630
3762
  formatFilename,
3631
3763
  formatStringContainsKey,
3632
3764
  getConfigDir,
@@ -3635,6 +3767,7 @@ export {
3635
3767
  getJsonStructure,
3636
3768
  loadStructure,
3637
3769
  mediaidToShortcode,
3770
+ parseInstagramUrl,
3638
3771
  resumableIteration,
3639
3772
  sanitizePath,
3640
3773
  shortcodeToMediaid