@vicociv/instaloader 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +401 -0
- package/dist/index.d.mts +72 -4
- package/dist/index.d.ts +72 -4
- package/dist/index.js +240 -103
- package/dist/index.mjs +236 -103
- package/package.json +38 -14
package/dist/index.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":
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
597
|
+
* Build cookie header string from cookie store.
|
|
534
598
|
*/
|
|
535
599
|
_getCookieHeader(url) {
|
|
536
|
-
|
|
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
|
-
|
|
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
|
-
...
|
|
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
|
|
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
|
|
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
|
-
|
|
787
|
-
"
|
|
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.
|
|
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.
|
|
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 && {
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|