@vicociv/instaloader 0.2.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # @vicociv/instaloader
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/@vicociv/instaloader.svg)](https://www.npmjs.com/package/@vicociv/instaloader)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@vicociv/instaloader.svg)](https://www.npmjs.com/package/@vicociv/instaloader)
5
+ [![npm bundle size](https://img.shields.io/bundlephobia/minzip/@vicociv/instaloader)](https://bundlephobia.com/package/@vicociv/instaloader)
6
+ [![CI](https://github.com/star8ks/instaloader.js/actions/workflows/ci.yml/badge.svg)](https://github.com/star8ks/instaloader.js/actions/workflows/ci.yml)
7
+ [![codecov](https://codecov.io/gh/star8ks/instaloader.js/branch/main/graph/badge.svg)](https://codecov.io/gh/star8ks/instaloader.js)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.3-blue.svg)](https://www.typescriptlang.org/)
10
+ [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D18-green.svg)](https://nodejs.org/)
11
+
3
12
  TypeScript port of [instaloader](https://github.com/instaloader/instaloader) - Download Instagram content (posts, stories, profiles) with metadata.
4
13
 
5
14
  ## Installation
@@ -384,3 +393,9 @@ import {
384
393
  ## License
385
394
 
386
395
  MIT
396
+
397
+ ## Buy Me a Coffee
398
+
399
+ If you find this project helpful, consider supporting its development:
400
+
401
+ [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/nickyoung)
package/dist/index.d.mts CHANGED
@@ -543,7 +543,7 @@ declare class InstaloaderContext {
543
543
  readonly quiet: boolean;
544
544
  readonly iphoneSupport: boolean;
545
545
  readonly fatalStatusCodes: number[];
546
- private _cookieJar;
546
+ private _cookieStore;
547
547
  private _csrfToken;
548
548
  private _username;
549
549
  private _userId;
@@ -610,7 +610,7 @@ declare class InstaloaderContext {
610
610
  */
611
611
  updateCookies(cookies: CookieData): void;
612
612
  /**
613
- * Build cookie header string from cookie jar.
613
+ * Build cookie header string from cookie store.
614
614
  */
615
615
  private _getCookieHeader;
616
616
  /**
@@ -637,9 +637,11 @@ declare class InstaloaderContext {
637
637
  */
638
638
  graphql_query(queryHash: string, variables: JsonObject, referer?: string): Promise<JsonObject>;
639
639
  /**
640
- * Do a doc_id-based GraphQL Query using POST.
641
- * Each request uses fresh dynamic headers (timestamp, connection speed, session ID)
642
- * to appear as different clients, which helps bypass rate limiting on retries.
640
+ * Do a doc_id-based GraphQL Query.
641
+ *
642
+ * Uses GET for anonymous requests (works without login) and POST for authenticated
643
+ * requests. This is the correct behavior - Instagram's API accepts GET for public
644
+ * data but requires POST with session for authenticated operations.
643
645
  */
644
646
  doc_id_graphql_query(docId: string, variables: JsonObject, referer?: string): Promise<JsonObject>;
645
647
  /**
package/dist/index.d.ts CHANGED
@@ -543,7 +543,7 @@ declare class InstaloaderContext {
543
543
  readonly quiet: boolean;
544
544
  readonly iphoneSupport: boolean;
545
545
  readonly fatalStatusCodes: number[];
546
- private _cookieJar;
546
+ private _cookieStore;
547
547
  private _csrfToken;
548
548
  private _username;
549
549
  private _userId;
@@ -610,7 +610,7 @@ declare class InstaloaderContext {
610
610
  */
611
611
  updateCookies(cookies: CookieData): void;
612
612
  /**
613
- * Build cookie header string from cookie jar.
613
+ * Build cookie header string from cookie store.
614
614
  */
615
615
  private _getCookieHeader;
616
616
  /**
@@ -637,9 +637,11 @@ declare class InstaloaderContext {
637
637
  */
638
638
  graphql_query(queryHash: string, variables: JsonObject, referer?: string): Promise<JsonObject>;
639
639
  /**
640
- * Do a doc_id-based GraphQL Query using POST.
641
- * Each request uses fresh dynamic headers (timestamp, connection speed, session ID)
642
- * to appear as different clients, which helps bypass rate limiting on retries.
640
+ * Do a doc_id-based GraphQL Query.
641
+ *
642
+ * Uses GET for anonymous requests (works without login) and POST for authenticated
643
+ * requests. This is the correct behavior - Instagram's API accepts GET for public
644
+ * data but requires POST with session for authenticated operations.
643
645
  */
644
646
  doc_id_graphql_query(docId: string, variables: JsonObject, referer?: string): Promise<JsonObject>;
645
647
  /**
package/dist/index.js CHANGED
@@ -220,9 +220,113 @@ var InvalidIteratorException = class extends InstaloaderException {
220
220
  }
221
221
  };
222
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
+
223
329
  // src/instaloadercontext.ts
224
- var import_tough_cookie = require("tough-cookie");
225
- var import_uuid = require("uuid");
226
330
  function defaultUserAgent() {
227
331
  return "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36";
228
332
  }
@@ -250,7 +354,7 @@ function defaultIphoneHeaders() {
250
354
  "x-ig-mapped-locale": "en-US",
251
355
  "x-ig-timezone-offset": String(timezoneOffset),
252
356
  "x-ig-www-claim": "0",
253
- "x-pigeon-session-id": (0, import_uuid.v4)(),
357
+ "x-pigeon-session-id": generateUUID(),
254
358
  "x-tigon-is-retry": "False",
255
359
  "x-whatsapp": "0"
256
360
  };
@@ -259,7 +363,7 @@ function getPerRequestHeaders() {
259
363
  return {
260
364
  "x-pigeon-rawclienttime": (Date.now() / 1e3).toFixed(6),
261
365
  "x-ig-connection-speed": `${Math.floor(Math.random() * 19e3) + 1e3}kbps`,
262
- "x-pigeon-session-id": (0, import_uuid.v4)()
366
+ "x-pigeon-session-id": generateUUID()
263
367
  };
264
368
  }
265
369
  function sleep(ms) {
@@ -312,16 +416,10 @@ var RateController = class {
312
416
  this._queryTimestamps.set(queryType, []);
313
417
  }
314
418
  const timestamps = this._queryTimestamps.get(queryType);
315
- const filteredTimestamps = timestamps.filter(
316
- (t) => t > currentTime - 60 * 60
317
- );
419
+ const filteredTimestamps = timestamps.filter((t) => t > currentTime - 60 * 60);
318
420
  this._queryTimestamps.set(queryType, filteredTimestamps);
319
421
  const perTypeNextRequestTime = () => {
320
- const reqs = this._reqsInSlidingWindow(
321
- queryType,
322
- currentTime,
323
- perTypeSlidingWindow
324
- );
422
+ const reqs = this._reqsInSlidingWindow(queryType, currentTime, perTypeSlidingWindow);
325
423
  if (reqs.length < this.countPerSlidingWindow(queryType)) {
326
424
  return 0;
327
425
  } else {
@@ -334,11 +432,7 @@ var RateController = class {
334
432
  }
335
433
  const gqlAccumulatedSlidingWindow = 600;
336
434
  const gqlAccumulatedMaxCount = 275;
337
- const reqs = this._reqsInSlidingWindow(
338
- null,
339
- currentTime,
340
- gqlAccumulatedSlidingWindow
341
- );
435
+ const reqs = this._reqsInSlidingWindow(null, currentTime, gqlAccumulatedSlidingWindow);
342
436
  if (reqs.length < gqlAccumulatedMaxCount) {
343
437
  return 0;
344
438
  } else {
@@ -348,33 +442,18 @@ var RateController = class {
348
442
  const untrackedNextRequestTime = () => {
349
443
  if (untrackedQueries) {
350
444
  if (queryType === "iphone") {
351
- const reqs = this._reqsInSlidingWindow(
352
- queryType,
353
- currentTime,
354
- iphoneSlidingWindow
355
- );
445
+ const reqs = this._reqsInSlidingWindow(queryType, currentTime, iphoneSlidingWindow);
356
446
  this._iphoneEarliestNextRequestTime = Math.min(...reqs) + iphoneSlidingWindow + 18;
357
447
  } else {
358
- const reqs = this._reqsInSlidingWindow(
359
- queryType,
360
- currentTime,
361
- perTypeSlidingWindow
362
- );
448
+ const reqs = this._reqsInSlidingWindow(queryType, currentTime, perTypeSlidingWindow);
363
449
  this._earliestNextRequestTime = Math.min(...reqs) + perTypeSlidingWindow + 6;
364
450
  }
365
451
  }
366
- return Math.max(
367
- this._iphoneEarliestNextRequestTime,
368
- this._earliestNextRequestTime
369
- );
452
+ return Math.max(this._iphoneEarliestNextRequestTime, this._earliestNextRequestTime);
370
453
  };
371
454
  const iphoneNextRequest = () => {
372
455
  if (queryType === "iphone") {
373
- const reqs = this._reqsInSlidingWindow(
374
- queryType,
375
- currentTime,
376
- iphoneSlidingWindow
377
- );
456
+ const reqs = this._reqsInSlidingWindow(queryType, currentTime, iphoneSlidingWindow);
378
457
  if (reqs.length >= 199) {
379
458
  return Math.min(...reqs) + iphoneSlidingWindow + 18;
380
459
  }
@@ -442,7 +521,7 @@ var InstaloaderContext = class {
442
521
  quiet;
443
522
  iphoneSupport;
444
523
  fatalStatusCodes;
445
- _cookieJar;
524
+ _cookieStore;
446
525
  _csrfToken = null;
447
526
  _username = null;
448
527
  _userId = null;
@@ -463,13 +542,13 @@ var InstaloaderContext = class {
463
542
  this.quiet = options.quiet ?? false;
464
543
  this.iphoneSupport = options.iphoneSupport ?? true;
465
544
  this.fatalStatusCodes = options.fatalStatusCodes ?? [];
466
- this._cookieJar = new import_tough_cookie.CookieJar();
545
+ this._cookieStore = new SimpleCookieStore();
467
546
  this._iphoneHeaders = defaultIphoneHeaders();
468
547
  this._initAnonymousCookies();
469
548
  this._rateController = options.rateController ? options.rateController(this) : new RateController(this);
470
549
  }
471
550
  _initAnonymousCookies() {
472
- const domain = "www.instagram.com";
551
+ const url = "https://www.instagram.com/";
473
552
  const defaultCookies = {
474
553
  sessionid: "",
475
554
  mid: "",
@@ -479,15 +558,7 @@ var InstaloaderContext = class {
479
558
  s_network: "",
480
559
  ds_user_id: ""
481
560
  };
482
- for (const [name, value] of Object.entries(defaultCookies)) {
483
- const cookie = new import_tough_cookie.Cookie({
484
- key: name,
485
- value,
486
- domain,
487
- path: "/"
488
- });
489
- this._cookieJar.setCookieSync(cookie, `https://${domain}/`);
490
- }
561
+ this._cookieStore.setCookies(defaultCookies, url);
491
562
  }
492
563
  /** True if this instance is logged in. */
493
564
  get is_logged_in() {
@@ -580,27 +651,13 @@ var InstaloaderContext = class {
580
651
  * Get cookies as a plain object.
581
652
  */
582
653
  getCookies(url = "https://www.instagram.com/") {
583
- const cookies = this._cookieJar.getCookiesSync(url);
584
- const result = {};
585
- for (const cookie of cookies) {
586
- result[cookie.key] = cookie.value;
587
- }
588
- return result;
654
+ return this._cookieStore.getCookies(url);
589
655
  }
590
656
  /**
591
657
  * Set cookies from a plain object.
592
658
  */
593
659
  setCookies(cookies, url = "https://www.instagram.com/") {
594
- const domain = new URL(url).hostname;
595
- for (const [name, value] of Object.entries(cookies)) {
596
- const cookie = new import_tough_cookie.Cookie({
597
- key: name,
598
- value,
599
- domain,
600
- path: "/"
601
- });
602
- this._cookieJar.setCookieSync(cookie, url);
603
- }
660
+ this._cookieStore.setCookies(cookies, url);
604
661
  }
605
662
  /**
606
663
  * Save session data for later restoration.
@@ -612,7 +669,7 @@ var InstaloaderContext = class {
612
669
  * Load session data from a saved session.
613
670
  */
614
671
  loadSession(username, sessionData) {
615
- this._cookieJar = new import_tough_cookie.CookieJar();
672
+ this._cookieStore = new SimpleCookieStore();
616
673
  this.setCookies(sessionData);
617
674
  this._csrfToken = sessionData["csrftoken"] || null;
618
675
  this._username = username;
@@ -625,26 +682,16 @@ var InstaloaderContext = class {
625
682
  this.setCookies(cookies);
626
683
  }
627
684
  /**
628
- * Build cookie header string from cookie jar.
685
+ * Build cookie header string from cookie store.
629
686
  */
630
687
  _getCookieHeader(url) {
631
- const cookies = this._cookieJar.getCookiesSync(url);
632
- return cookies.map((c) => `${c.key}=${c.value}`).join("; ");
688
+ return this._cookieStore.getCookieHeader(url);
633
689
  }
634
690
  /**
635
691
  * Parse and store cookies from Set-Cookie headers.
636
692
  */
637
693
  _storeCookies(url, headers) {
638
- const setCookies = headers.getSetCookie?.() || [];
639
- for (const cookieStr of setCookies) {
640
- try {
641
- const cookie = import_tough_cookie.Cookie.parse(cookieStr);
642
- if (cookie) {
643
- this._cookieJar.setCookieSync(cookie, url);
644
- }
645
- } catch {
646
- }
647
- }
694
+ this._cookieStore.parseSetCookieHeaders(headers, url);
648
695
  }
649
696
  /**
650
697
  * Format response error message.
@@ -722,10 +769,7 @@ var InstaloaderContext = class {
722
769
  });
723
770
  } else {
724
771
  for (const [key, value] of Object.entries(params)) {
725
- url.searchParams.set(
726
- key,
727
- typeof value === "string" ? value : JSON.stringify(value)
728
- );
772
+ url.searchParams.set(key, typeof value === "string" ? value : JSON.stringify(value));
729
773
  }
730
774
  response = await fetch(url.toString(), {
731
775
  method: "GET",
@@ -748,9 +792,7 @@ var InstaloaderContext = class {
748
792
  HTTP redirect from ${url} to ${redirectUrl}`);
749
793
  if (redirectUrl.startsWith("https://www.instagram.com/accounts/login") || redirectUrl.startsWith("https://i.instagram.com/accounts/login")) {
750
794
  if (!this.is_logged_in) {
751
- throw new LoginRequiredException(
752
- "Redirected to login page. Use login() first."
753
- );
795
+ throw new LoginRequiredException("Redirected to login page. Use login() first.");
754
796
  }
755
797
  throw new AbortDownloadException(
756
798
  "Redirected to login page. You've been logged out, please wait some time, recreate the session and try again"
@@ -857,9 +899,11 @@ HTTP redirect from ${url} to ${redirectUrl}`);
857
899
  return respJson;
858
900
  }
859
901
  /**
860
- * Do a doc_id-based GraphQL Query using POST.
861
- * Each request uses fresh dynamic headers (timestamp, connection speed, session ID)
862
- * to appear as different clients, which helps bypass rate limiting on retries.
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.
863
907
  */
864
908
  async doc_id_graphql_query(docId, variables, referer) {
865
909
  const perRequestHeaders = getPerRequestHeaders();
@@ -876,10 +920,11 @@ HTTP redirect from ${url} to ${redirectUrl}`);
876
920
  headers["referer"] = encodeURIComponent(referer);
877
921
  }
878
922
  const variablesJson = JSON.stringify(variables);
923
+ const usePost = this.is_logged_in;
879
924
  const respJson = await this.getJson(
880
925
  "graphql/query",
881
926
  { variables: variablesJson, doc_id: docId, server_timestamps: "true" },
882
- { usePost: true, headers }
927
+ { usePost, headers }
883
928
  );
884
929
  if (!("status" in respJson)) {
885
930
  this.error('GraphQL response did not contain a "status" field.');
@@ -950,9 +995,7 @@ HTTP redirect from ${url} to ${redirectUrl}`);
950
995
  this._responseError(response.status, response.statusText, url)
951
996
  );
952
997
  }
953
- throw new ConnectionException(
954
- this._responseError(response.status, response.statusText, url)
955
- );
998
+ throw new ConnectionException(this._responseError(response.status, response.statusText, url));
956
999
  }
957
1000
  /**
958
1001
  * Test if logged in by querying the current user.
@@ -974,7 +1017,7 @@ HTTP redirect from ${url} to ${redirectUrl}`);
974
1017
  * Login to Instagram.
975
1018
  */
976
1019
  async login(username, password) {
977
- this._cookieJar = new import_tough_cookie.CookieJar();
1020
+ this._cookieStore = new SimpleCookieStore();
978
1021
  this._initAnonymousCookies();
979
1022
  const initUrl = "https://www.instagram.com/";
980
1023
  const initResponse = await fetch(initUrl, {
@@ -1094,7 +1137,7 @@ HTTP redirect from ${url} to ${redirectUrl}`);
1094
1137
  throw new InvalidArgumentException("No two-factor authentication pending.");
1095
1138
  }
1096
1139
  const { csrfToken, cookies, username, twoFactorId } = this._twoFactorAuthPending;
1097
- this._cookieJar = new import_tough_cookie.CookieJar();
1140
+ this._cookieStore = new SimpleCookieStore();
1098
1141
  this.setCookies(cookies);
1099
1142
  const loginUrl = "https://www.instagram.com/accounts/login/ajax/two_factor/";
1100
1143
  const response = await fetch(loginUrl, {
@@ -3280,7 +3323,9 @@ var Instaloader = class {
3280
3323
  ...options.sleep !== void 0 && { sleep: options.sleep },
3281
3324
  ...options.quiet !== void 0 && { quiet: options.quiet },
3282
3325
  ...options.userAgent !== void 0 && { userAgent: options.userAgent },
3283
- ...options.maxConnectionAttempts !== void 0 && { maxConnectionAttempts: options.maxConnectionAttempts },
3326
+ ...options.maxConnectionAttempts !== void 0 && {
3327
+ maxConnectionAttempts: options.maxConnectionAttempts
3328
+ },
3284
3329
  ...options.requestTimeout !== void 0 && { requestTimeout: options.requestTimeout },
3285
3330
  ...options.rateController !== void 0 && { rateController: options.rateController },
3286
3331
  ...options.fatalStatusCodes !== void 0 && { fatalStatusCodes: options.fatalStatusCodes },
@@ -3385,7 +3430,7 @@ var Instaloader = class {
3385
3430
  * Load session from file.
3386
3431
  */
3387
3432
  async loadSessionFromFile(username, filename) {
3388
- let targetFile = filename ?? getDefaultSessionFilename(username);
3433
+ const targetFile = filename ?? getDefaultSessionFilename(username);
3389
3434
  if (!fs.existsSync(targetFile)) {
3390
3435
  throw new Error(`Session file not found: ${targetFile}`);
3391
3436
  }
@@ -3440,7 +3485,9 @@ var Instaloader = class {
3440
3485
  }
3441
3486
  });
3442
3487
  if (!response.ok) {
3443
- throw new ConnectionException(`Failed to download: ${response.status} ${response.statusText}`);
3488
+ throw new ConnectionException(
3489
+ `Failed to download: ${response.status} ${response.statusText}`
3490
+ );
3444
3491
  }
3445
3492
  let finalFilename = nominalFilename;
3446
3493
  const contentType = response.headers.get("Content-Type");
@@ -3539,11 +3586,21 @@ var Instaloader = class {
3539
3586
  }
3540
3587
  if (node.is_video) {
3541
3588
  if (this.downloadVideos && node.video_url) {
3542
- 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
+ );
3543
3595
  downloaded = downloaded || dl;
3544
3596
  }
3545
3597
  } else if (node.display_url) {
3546
- 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
+ );
3547
3604
  downloaded = downloaded || dl;
3548
3605
  }
3549
3606
  index++;
@@ -3600,7 +3657,10 @@ var Instaloader = class {
3600
3657
  }
3601
3658
  if (displayedCount !== void 0) {
3602
3659
  const width = displayedCount.toString().length;
3603
- 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
+ );
3604
3664
  } else {
3605
3665
  this.context.log(`[${number.toString().padStart(3)}] `, false);
3606
3666
  }
@@ -3682,7 +3742,12 @@ var Instaloader = class {
3682
3742
  async downloadProfilePic(profile) {
3683
3743
  const url = await profile.getProfilePicUrl();
3684
3744
  if (!url) return;
3685
- 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
+ );
3686
3751
  const filename = path.join(dirname2, `${profile.username.toLowerCase()}_profile_pic`);
3687
3752
  await this.downloadPic(filename, url, /* @__PURE__ */ new Date());
3688
3753
  this.context.log("");
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,7 +266,7 @@ 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
  };
@@ -171,7 +275,7 @@ function getPerRequestHeaders() {
171
275
  return {
172
276
  "x-pigeon-rawclienttime": (Date.now() / 1e3).toFixed(6),
173
277
  "x-ig-connection-speed": `${Math.floor(Math.random() * 19e3) + 1e3}kbps`,
174
- "x-pigeon-session-id": uuidv4()
278
+ "x-pigeon-session-id": generateUUID()
175
279
  };
176
280
  }
177
281
  function sleep(ms) {
@@ -224,16 +328,10 @@ var RateController = class {
224
328
  this._queryTimestamps.set(queryType, []);
225
329
  }
226
330
  const timestamps = this._queryTimestamps.get(queryType);
227
- const filteredTimestamps = timestamps.filter(
228
- (t) => t > currentTime - 60 * 60
229
- );
331
+ const filteredTimestamps = timestamps.filter((t) => t > currentTime - 60 * 60);
230
332
  this._queryTimestamps.set(queryType, filteredTimestamps);
231
333
  const perTypeNextRequestTime = () => {
232
- const reqs = this._reqsInSlidingWindow(
233
- queryType,
234
- currentTime,
235
- perTypeSlidingWindow
236
- );
334
+ const reqs = this._reqsInSlidingWindow(queryType, currentTime, perTypeSlidingWindow);
237
335
  if (reqs.length < this.countPerSlidingWindow(queryType)) {
238
336
  return 0;
239
337
  } else {
@@ -246,11 +344,7 @@ var RateController = class {
246
344
  }
247
345
  const gqlAccumulatedSlidingWindow = 600;
248
346
  const gqlAccumulatedMaxCount = 275;
249
- const reqs = this._reqsInSlidingWindow(
250
- null,
251
- currentTime,
252
- gqlAccumulatedSlidingWindow
253
- );
347
+ const reqs = this._reqsInSlidingWindow(null, currentTime, gqlAccumulatedSlidingWindow);
254
348
  if (reqs.length < gqlAccumulatedMaxCount) {
255
349
  return 0;
256
350
  } else {
@@ -260,33 +354,18 @@ var RateController = class {
260
354
  const untrackedNextRequestTime = () => {
261
355
  if (untrackedQueries) {
262
356
  if (queryType === "iphone") {
263
- const reqs = this._reqsInSlidingWindow(
264
- queryType,
265
- currentTime,
266
- iphoneSlidingWindow
267
- );
357
+ const reqs = this._reqsInSlidingWindow(queryType, currentTime, iphoneSlidingWindow);
268
358
  this._iphoneEarliestNextRequestTime = Math.min(...reqs) + iphoneSlidingWindow + 18;
269
359
  } else {
270
- const reqs = this._reqsInSlidingWindow(
271
- queryType,
272
- currentTime,
273
- perTypeSlidingWindow
274
- );
360
+ const reqs = this._reqsInSlidingWindow(queryType, currentTime, perTypeSlidingWindow);
275
361
  this._earliestNextRequestTime = Math.min(...reqs) + perTypeSlidingWindow + 6;
276
362
  }
277
363
  }
278
- return Math.max(
279
- this._iphoneEarliestNextRequestTime,
280
- this._earliestNextRequestTime
281
- );
364
+ return Math.max(this._iphoneEarliestNextRequestTime, this._earliestNextRequestTime);
282
365
  };
283
366
  const iphoneNextRequest = () => {
284
367
  if (queryType === "iphone") {
285
- const reqs = this._reqsInSlidingWindow(
286
- queryType,
287
- currentTime,
288
- iphoneSlidingWindow
289
- );
368
+ const reqs = this._reqsInSlidingWindow(queryType, currentTime, iphoneSlidingWindow);
290
369
  if (reqs.length >= 199) {
291
370
  return Math.min(...reqs) + iphoneSlidingWindow + 18;
292
371
  }
@@ -354,7 +433,7 @@ var InstaloaderContext = class {
354
433
  quiet;
355
434
  iphoneSupport;
356
435
  fatalStatusCodes;
357
- _cookieJar;
436
+ _cookieStore;
358
437
  _csrfToken = null;
359
438
  _username = null;
360
439
  _userId = null;
@@ -375,13 +454,13 @@ var InstaloaderContext = class {
375
454
  this.quiet = options.quiet ?? false;
376
455
  this.iphoneSupport = options.iphoneSupport ?? true;
377
456
  this.fatalStatusCodes = options.fatalStatusCodes ?? [];
378
- this._cookieJar = new CookieJar();
457
+ this._cookieStore = new SimpleCookieStore();
379
458
  this._iphoneHeaders = defaultIphoneHeaders();
380
459
  this._initAnonymousCookies();
381
460
  this._rateController = options.rateController ? options.rateController(this) : new RateController(this);
382
461
  }
383
462
  _initAnonymousCookies() {
384
- const domain = "www.instagram.com";
463
+ const url = "https://www.instagram.com/";
385
464
  const defaultCookies = {
386
465
  sessionid: "",
387
466
  mid: "",
@@ -391,15 +470,7 @@ var InstaloaderContext = class {
391
470
  s_network: "",
392
471
  ds_user_id: ""
393
472
  };
394
- for (const [name, value] of Object.entries(defaultCookies)) {
395
- const cookie = new Cookie({
396
- key: name,
397
- value,
398
- domain,
399
- path: "/"
400
- });
401
- this._cookieJar.setCookieSync(cookie, `https://${domain}/`);
402
- }
473
+ this._cookieStore.setCookies(defaultCookies, url);
403
474
  }
404
475
  /** True if this instance is logged in. */
405
476
  get is_logged_in() {
@@ -492,27 +563,13 @@ var InstaloaderContext = class {
492
563
  * Get cookies as a plain object.
493
564
  */
494
565
  getCookies(url = "https://www.instagram.com/") {
495
- const cookies = this._cookieJar.getCookiesSync(url);
496
- const result = {};
497
- for (const cookie of cookies) {
498
- result[cookie.key] = cookie.value;
499
- }
500
- return result;
566
+ return this._cookieStore.getCookies(url);
501
567
  }
502
568
  /**
503
569
  * Set cookies from a plain object.
504
570
  */
505
571
  setCookies(cookies, url = "https://www.instagram.com/") {
506
- const domain = new URL(url).hostname;
507
- for (const [name, value] of Object.entries(cookies)) {
508
- const cookie = new Cookie({
509
- key: name,
510
- value,
511
- domain,
512
- path: "/"
513
- });
514
- this._cookieJar.setCookieSync(cookie, url);
515
- }
572
+ this._cookieStore.setCookies(cookies, url);
516
573
  }
517
574
  /**
518
575
  * Save session data for later restoration.
@@ -524,7 +581,7 @@ var InstaloaderContext = class {
524
581
  * Load session data from a saved session.
525
582
  */
526
583
  loadSession(username, sessionData) {
527
- this._cookieJar = new CookieJar();
584
+ this._cookieStore = new SimpleCookieStore();
528
585
  this.setCookies(sessionData);
529
586
  this._csrfToken = sessionData["csrftoken"] || null;
530
587
  this._username = username;
@@ -537,26 +594,16 @@ var InstaloaderContext = class {
537
594
  this.setCookies(cookies);
538
595
  }
539
596
  /**
540
- * Build cookie header string from cookie jar.
597
+ * Build cookie header string from cookie store.
541
598
  */
542
599
  _getCookieHeader(url) {
543
- const cookies = this._cookieJar.getCookiesSync(url);
544
- return cookies.map((c) => `${c.key}=${c.value}`).join("; ");
600
+ return this._cookieStore.getCookieHeader(url);
545
601
  }
546
602
  /**
547
603
  * Parse and store cookies from Set-Cookie headers.
548
604
  */
549
605
  _storeCookies(url, headers) {
550
- const setCookies = headers.getSetCookie?.() || [];
551
- for (const cookieStr of setCookies) {
552
- try {
553
- const cookie = Cookie.parse(cookieStr);
554
- if (cookie) {
555
- this._cookieJar.setCookieSync(cookie, url);
556
- }
557
- } catch {
558
- }
559
- }
606
+ this._cookieStore.parseSetCookieHeaders(headers, url);
560
607
  }
561
608
  /**
562
609
  * Format response error message.
@@ -634,10 +681,7 @@ var InstaloaderContext = class {
634
681
  });
635
682
  } else {
636
683
  for (const [key, value] of Object.entries(params)) {
637
- url.searchParams.set(
638
- key,
639
- typeof value === "string" ? value : JSON.stringify(value)
640
- );
684
+ url.searchParams.set(key, typeof value === "string" ? value : JSON.stringify(value));
641
685
  }
642
686
  response = await fetch(url.toString(), {
643
687
  method: "GET",
@@ -660,9 +704,7 @@ var InstaloaderContext = class {
660
704
  HTTP redirect from ${url} to ${redirectUrl}`);
661
705
  if (redirectUrl.startsWith("https://www.instagram.com/accounts/login") || redirectUrl.startsWith("https://i.instagram.com/accounts/login")) {
662
706
  if (!this.is_logged_in) {
663
- throw new LoginRequiredException(
664
- "Redirected to login page. Use login() first."
665
- );
707
+ throw new LoginRequiredException("Redirected to login page. Use login() first.");
666
708
  }
667
709
  throw new AbortDownloadException(
668
710
  "Redirected to login page. You've been logged out, please wait some time, recreate the session and try again"
@@ -769,9 +811,11 @@ HTTP redirect from ${url} to ${redirectUrl}`);
769
811
  return respJson;
770
812
  }
771
813
  /**
772
- * Do a doc_id-based GraphQL Query using POST.
773
- * Each request uses fresh dynamic headers (timestamp, connection speed, session ID)
774
- * to appear as different clients, which helps bypass rate limiting on retries.
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.
775
819
  */
776
820
  async doc_id_graphql_query(docId, variables, referer) {
777
821
  const perRequestHeaders = getPerRequestHeaders();
@@ -788,10 +832,11 @@ HTTP redirect from ${url} to ${redirectUrl}`);
788
832
  headers["referer"] = encodeURIComponent(referer);
789
833
  }
790
834
  const variablesJson = JSON.stringify(variables);
835
+ const usePost = this.is_logged_in;
791
836
  const respJson = await this.getJson(
792
837
  "graphql/query",
793
838
  { variables: variablesJson, doc_id: docId, server_timestamps: "true" },
794
- { usePost: true, headers }
839
+ { usePost, headers }
795
840
  );
796
841
  if (!("status" in respJson)) {
797
842
  this.error('GraphQL response did not contain a "status" field.');
@@ -862,9 +907,7 @@ HTTP redirect from ${url} to ${redirectUrl}`);
862
907
  this._responseError(response.status, response.statusText, url)
863
908
  );
864
909
  }
865
- throw new ConnectionException(
866
- this._responseError(response.status, response.statusText, url)
867
- );
910
+ throw new ConnectionException(this._responseError(response.status, response.statusText, url));
868
911
  }
869
912
  /**
870
913
  * Test if logged in by querying the current user.
@@ -886,7 +929,7 @@ HTTP redirect from ${url} to ${redirectUrl}`);
886
929
  * Login to Instagram.
887
930
  */
888
931
  async login(username, password) {
889
- this._cookieJar = new CookieJar();
932
+ this._cookieStore = new SimpleCookieStore();
890
933
  this._initAnonymousCookies();
891
934
  const initUrl = "https://www.instagram.com/";
892
935
  const initResponse = await fetch(initUrl, {
@@ -1006,7 +1049,7 @@ HTTP redirect from ${url} to ${redirectUrl}`);
1006
1049
  throw new InvalidArgumentException("No two-factor authentication pending.");
1007
1050
  }
1008
1051
  const { csrfToken, cookies, username, twoFactorId } = this._twoFactorAuthPending;
1009
- this._cookieJar = new CookieJar();
1052
+ this._cookieStore = new SimpleCookieStore();
1010
1053
  this.setCookies(cookies);
1011
1054
  const loginUrl = "https://www.instagram.com/accounts/login/ajax/two_factor/";
1012
1055
  const response = await fetch(loginUrl, {
@@ -3192,7 +3235,9 @@ var Instaloader = class {
3192
3235
  ...options.sleep !== void 0 && { sleep: options.sleep },
3193
3236
  ...options.quiet !== void 0 && { quiet: options.quiet },
3194
3237
  ...options.userAgent !== void 0 && { userAgent: options.userAgent },
3195
- ...options.maxConnectionAttempts !== void 0 && { maxConnectionAttempts: options.maxConnectionAttempts },
3238
+ ...options.maxConnectionAttempts !== void 0 && {
3239
+ maxConnectionAttempts: options.maxConnectionAttempts
3240
+ },
3196
3241
  ...options.requestTimeout !== void 0 && { requestTimeout: options.requestTimeout },
3197
3242
  ...options.rateController !== void 0 && { rateController: options.rateController },
3198
3243
  ...options.fatalStatusCodes !== void 0 && { fatalStatusCodes: options.fatalStatusCodes },
@@ -3297,7 +3342,7 @@ var Instaloader = class {
3297
3342
  * Load session from file.
3298
3343
  */
3299
3344
  async loadSessionFromFile(username, filename) {
3300
- let targetFile = filename ?? getDefaultSessionFilename(username);
3345
+ const targetFile = filename ?? getDefaultSessionFilename(username);
3301
3346
  if (!fs.existsSync(targetFile)) {
3302
3347
  throw new Error(`Session file not found: ${targetFile}`);
3303
3348
  }
@@ -3352,7 +3397,9 @@ var Instaloader = class {
3352
3397
  }
3353
3398
  });
3354
3399
  if (!response.ok) {
3355
- throw new ConnectionException(`Failed to download: ${response.status} ${response.statusText}`);
3400
+ throw new ConnectionException(
3401
+ `Failed to download: ${response.status} ${response.statusText}`
3402
+ );
3356
3403
  }
3357
3404
  let finalFilename = nominalFilename;
3358
3405
  const contentType = response.headers.get("Content-Type");
@@ -3451,11 +3498,21 @@ var Instaloader = class {
3451
3498
  }
3452
3499
  if (node.is_video) {
3453
3500
  if (this.downloadVideos && node.video_url) {
3454
- 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
+ );
3455
3507
  downloaded = downloaded || dl;
3456
3508
  }
3457
3509
  } else if (node.display_url) {
3458
- 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
+ );
3459
3516
  downloaded = downloaded || dl;
3460
3517
  }
3461
3518
  index++;
@@ -3512,7 +3569,10 @@ var Instaloader = class {
3512
3569
  }
3513
3570
  if (displayedCount !== void 0) {
3514
3571
  const width = displayedCount.toString().length;
3515
- 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
+ );
3516
3576
  } else {
3517
3577
  this.context.log(`[${number.toString().padStart(3)}] `, false);
3518
3578
  }
@@ -3594,7 +3654,12 @@ var Instaloader = class {
3594
3654
  async downloadProfilePic(profile) {
3595
3655
  const url = await profile.getProfilePicUrl();
3596
3656
  if (!url) return;
3597
- 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
+ );
3598
3663
  const filename = path.join(dirname2, `${profile.username.toLowerCase()}_profile_pic`);
3599
3664
  await this.downloadPic(filename, url, /* @__PURE__ */ new Date());
3600
3665
  this.context.log("");
package/package.json CHANGED
@@ -1,35 +1,52 @@
1
1
  {
2
2
  "name": "@vicociv/instaloader",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "TypeScript port of instaloader - Download pictures (or videos) along with their captions and other metadata from Instagram.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
7
7
  "types": "./dist/index.d.ts",
8
8
  "exports": {
9
9
  ".": {
10
+ "types": "./dist/index.d.ts",
10
11
  "import": "./dist/index.mjs",
11
- "require": "./dist/index.js",
12
- "types": "./dist/index.d.ts"
12
+ "require": "./dist/index.js"
13
13
  }
14
14
  },
15
+ "sideEffects": false,
15
16
  "files": [
16
17
  "dist"
17
18
  ],
18
19
  "publishConfig": {
19
- "access": "public"
20
+ "access": "public",
21
+ "provenance": true
20
22
  },
21
23
  "scripts": {
22
24
  "build": "tsup src/index.ts --format cjs,esm --dts --clean",
23
25
  "test": "vitest run",
24
26
  "test:watch": "vitest",
27
+ "test:coverage": "vitest run --coverage",
25
28
  "lint": "eslint src --ext .ts",
29
+ "lint:fix": "eslint src --ext .ts --fix",
30
+ "format": "prettier --write \"src/**/*.ts\"",
31
+ "format:check": "prettier --check \"src/**/*.ts\"",
26
32
  "typecheck": "tsc --noEmit",
27
- "prepublishOnly": "npm run build"
33
+ "prepublishOnly": "npm run build",
34
+ "prepare": "husky",
35
+ "changeset": "changeset",
36
+ "version": "changeset version",
37
+ "release": "npm run build && changeset publish"
38
+ },
39
+ "lint-staged": {
40
+ "*.ts": [
41
+ "eslint --fix",
42
+ "prettier --write"
43
+ ]
28
44
  },
29
45
  "keywords": [
30
46
  "instagram",
31
47
  "instagram-scraper",
32
48
  "instagram-client",
49
+ "instagram-api",
33
50
  "instagram-feed",
34
51
  "downloader",
35
52
  "videos",
@@ -40,30 +57,37 @@
40
57
  "instagram-metadata",
41
58
  "instagram-downloader",
42
59
  "instagram-stories",
43
- "typescript"
60
+ "typescript",
61
+ "nodejs"
44
62
  ],
45
- "author": "",
63
+ "author": "star8ks",
46
64
  "license": "MIT",
47
65
  "repository": {
48
66
  "type": "git",
49
- "url": ""
67
+ "url": "git+https://github.com/star8ks/instaloader.js.git"
68
+ },
69
+ "bugs": {
70
+ "url": "https://github.com/star8ks/instaloader.js/issues"
50
71
  },
72
+ "homepage": "https://github.com/star8ks/instaloader.js#readme",
51
73
  "engines": {
52
74
  "node": ">=18.0.0"
53
75
  },
54
76
  "devDependencies": {
77
+ "@changesets/changelog-github": "^0.5.2",
78
+ "@changesets/cli": "^2.29.8",
79
+ "@commitlint/cli": "^20.2.0",
80
+ "@commitlint/config-conventional": "^20.2.0",
55
81
  "@types/node": "^20.10.0",
56
- "@types/tough-cookie": "^4.0.5",
57
- "@types/uuid": "^10.0.0",
58
82
  "@typescript-eslint/eslint-plugin": "^6.13.0",
59
83
  "@typescript-eslint/parser": "^6.13.0",
84
+ "@vitest/coverage-v8": "^1.6.1",
60
85
  "eslint": "^8.55.0",
86
+ "husky": "^9.1.7",
87
+ "lint-staged": "^16.2.7",
88
+ "prettier": "^3.7.4",
61
89
  "tsup": "^8.0.1",
62
90
  "typescript": "^5.3.2",
63
91
  "vitest": "^1.0.4"
64
- },
65
- "dependencies": {
66
- "tough-cookie": "^4.1.3",
67
- "uuid": "^13.0.0"
68
92
  }
69
93
  }