@chilfish/gallery-dl-instagram 0.2.0 → 0.2.3
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/dl-ins.mjs +900 -977
- package/dist/index.cjs +180 -2
- package/dist/index.d.cts +87 -64
- package/dist/index.d.mts +87 -64
- package/dist/index.mjs +179 -1
- package/dist/node.cjs +7 -9
- package/dist/node.d.cts +3 -3
- package/dist/node.d.mts +3 -3
- package/dist/node.mjs +7 -9
- package/dist/{sdk-nzhAxf1O.cjs → sdk-D8q2Rjw2.cjs} +557 -523
- package/dist/{sdk-CK9x5wFL.d.mts → sdk-DyZz22bT.d.cts} +7 -4
- package/dist/{sdk-CK9x5wFL.d.cts → sdk-DyZz22bT.d.mts} +7 -4
- package/dist/{sdk-Bn0VCUIT.mjs → sdk-E0L5ISZC.mjs} +507 -521
- package/package.json +3 -10
- package/dist/adapter-CFsiiEpM.cjs +0 -83
- package/dist/adapter-tSleX8Cr.mjs +0 -59
|
@@ -52,129 +52,8 @@ var ConfigManager = class {
|
|
|
52
52
|
}
|
|
53
53
|
};
|
|
54
54
|
//#endregion
|
|
55
|
-
//#region src/core/
|
|
56
|
-
/**
|
|
57
|
-
const noopLogger = {
|
|
58
|
-
debug: () => {},
|
|
59
|
-
info: () => {},
|
|
60
|
-
warn: () => {},
|
|
61
|
-
error: () => {}
|
|
62
|
-
};
|
|
63
|
-
var Extractor = class {
|
|
64
|
-
/** Regex pattern to match against URLs */
|
|
65
|
-
static pattern = /^$/;
|
|
66
|
-
/** The input URL */
|
|
67
|
-
url;
|
|
68
|
-
/** Regex match groups from ``fromURL`` */
|
|
69
|
-
groups;
|
|
70
|
-
config;
|
|
71
|
-
/** HTTP client — public so Job can access for downloads */
|
|
72
|
-
http;
|
|
73
|
-
/** Storage backend — public so Job can access for writes */
|
|
74
|
-
storage;
|
|
75
|
-
/** Logger instance — public so Job can access for reporting */
|
|
76
|
-
log;
|
|
77
|
-
/** Delay range in seconds — random between [min, max] before each request */
|
|
78
|
-
requestInterval = [6, 12];
|
|
79
|
-
_initialized = false;
|
|
80
|
-
constructor(opts) {
|
|
81
|
-
this.url = opts.url;
|
|
82
|
-
this.groups = opts.match ? [...opts.match].slice(1) : [];
|
|
83
|
-
this.config = opts.config;
|
|
84
|
-
this.http = opts.http;
|
|
85
|
-
this.storage = opts.storage;
|
|
86
|
-
this.log = opts.log;
|
|
87
|
-
}
|
|
88
|
-
/** Initialization */
|
|
89
|
-
/**
|
|
90
|
-
* One-time async setup (cookies, session, internal state).
|
|
91
|
-
* Safe to call multiple times — after the first call it becomes a no-op.
|
|
92
|
-
*/
|
|
93
|
-
async initialize() {
|
|
94
|
-
if (this._initialized) return;
|
|
95
|
-
await this._init();
|
|
96
|
-
this._initialized = true;
|
|
97
|
-
this.initialize = async () => {};
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* Subclass hook for one-time setup.
|
|
101
|
-
*/
|
|
102
|
-
async _init() {}
|
|
103
|
-
/** Async iteration */
|
|
104
|
-
async *[Symbol.asyncIterator]() {
|
|
105
|
-
await this.initialize();
|
|
106
|
-
yield* this.items();
|
|
107
|
-
}
|
|
108
|
-
/** Config helpers */
|
|
109
|
-
/**
|
|
110
|
-
* Read a config value using the interpolated hierarchy.
|
|
111
|
-
*/
|
|
112
|
-
_cfg(key, defaultVal) {
|
|
113
|
-
const path = [
|
|
114
|
-
"extractor",
|
|
115
|
-
this.category,
|
|
116
|
-
this.subcategory
|
|
117
|
-
];
|
|
118
|
-
return this.config.interpolate(path, key, defaultVal);
|
|
119
|
-
}
|
|
120
|
-
/** HTTP */
|
|
121
|
-
_lastRequestTime = 0;
|
|
122
|
-
/**
|
|
123
|
-
* Rate-limited HTTP request wrapper.
|
|
124
|
-
*/
|
|
125
|
-
async request(url, cfg = {}) {
|
|
126
|
-
await this._throttle();
|
|
127
|
-
const response = await this.http.request({
|
|
128
|
-
url,
|
|
129
|
-
...cfg
|
|
130
|
-
});
|
|
131
|
-
this._lastRequestTime = Date.now();
|
|
132
|
-
return response;
|
|
133
|
-
}
|
|
134
|
-
/**
|
|
135
|
-
* Convenience: request + parse JSON body.
|
|
136
|
-
*/
|
|
137
|
-
async requestJSON(url, cfg = {}) {
|
|
138
|
-
const resp = await this.request(url, cfg);
|
|
139
|
-
if (typeof resp.data === "object") return resp.data;
|
|
140
|
-
try {
|
|
141
|
-
return JSON.parse(resp.data);
|
|
142
|
-
} catch {
|
|
143
|
-
return {};
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
/** Rate limiting */
|
|
147
|
-
/**
|
|
148
|
-
* Sleep long enough to keep the minimum interval between requests.
|
|
149
|
-
*/
|
|
150
|
-
async _throttle() {
|
|
151
|
-
const elapsed = Date.now() - this._lastRequestTime;
|
|
152
|
-
const [min, max] = this.requestInterval;
|
|
153
|
-
const target = min + Math.random() * (max - min);
|
|
154
|
-
const waitMs = Math.max(0, target * 1e3 - elapsed);
|
|
155
|
-
if (waitMs > 0) await new Promise((r) => setTimeout(r, waitMs));
|
|
156
|
-
}
|
|
157
|
-
/** Utility */
|
|
158
|
-
/**
|
|
159
|
-
* Convert a Unix timestamp (seconds or ms) to an ISO-8601 string.
|
|
160
|
-
*/
|
|
161
|
-
parseTimestamp(ts) {
|
|
162
|
-
if (ts == null) return "";
|
|
163
|
-
const asMs = ts > 25e8 ? ts : ts * 1e3;
|
|
164
|
-
return new Date(asMs).toISOString();
|
|
165
|
-
}
|
|
166
|
-
/**
|
|
167
|
-
* Generate a random hex token (used for CSRF).
|
|
168
|
-
*/
|
|
169
|
-
static generateToken(size = 16) {
|
|
170
|
-
const bytes = new Uint8Array(size);
|
|
171
|
-
if (typeof crypto !== "undefined" && crypto.getRandomValues) crypto.getRandomValues(bytes);
|
|
172
|
-
else for (let i = 0; i < size; i++) bytes[i] = Math.floor(Math.random() * 256);
|
|
173
|
-
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
174
|
-
}
|
|
175
|
-
};
|
|
176
|
-
//#endregion
|
|
177
|
-
//#region src/core/job.ts
|
|
55
|
+
//#region src/core/format.ts
|
|
56
|
+
/** Shared ANSI formatting and display utilities. */
|
|
178
57
|
function formatBytes(bytes) {
|
|
179
58
|
if (bytes === 0) return "0 B";
|
|
180
59
|
const units = [
|
|
@@ -204,19 +83,20 @@ function c(s) {
|
|
|
204
83
|
function g(s) {
|
|
205
84
|
return `${GREEN}${s}${RESET}`;
|
|
206
85
|
}
|
|
86
|
+
const _YELLOW = YELLOW;
|
|
87
|
+
const _RESET = RESET;
|
|
207
88
|
function pad(s, n) {
|
|
208
89
|
return s.length >= n ? s : s + " ".repeat(n - s.length);
|
|
209
90
|
}
|
|
91
|
+
//#endregion
|
|
92
|
+
//#region src/core/job.ts
|
|
210
93
|
var Job = class {
|
|
211
94
|
extractor;
|
|
212
95
|
status = 0;
|
|
213
96
|
constructor(extractor) {
|
|
214
97
|
this.extractor = extractor;
|
|
215
98
|
}
|
|
216
|
-
/**
|
|
217
|
-
* Main entry point. Calls ``extractor[Symbol.asyncIterator]()`` and
|
|
218
|
-
* dispatches every yielded message.
|
|
219
|
-
*/
|
|
99
|
+
/** Main entry point. Dispatches every yielded message. */
|
|
220
100
|
async run() {
|
|
221
101
|
this.extractor.log.info(`Starting ${this.extractor.category}/${this.extractor.subcategory} — ${this.extractor.url}`);
|
|
222
102
|
await this.extractor.initialize();
|
|
@@ -237,6 +117,8 @@ var Job = class {
|
|
|
237
117
|
/** Override in subclasses to print a summary. */
|
|
238
118
|
_report() {}
|
|
239
119
|
};
|
|
120
|
+
//#endregion
|
|
121
|
+
//#region src/core/download-job.ts
|
|
240
122
|
var DownloadJob = class DownloadJob extends Job {
|
|
241
123
|
/** Base output directory (prepended to all paths). */
|
|
242
124
|
basePath = "";
|
|
@@ -244,10 +126,6 @@ var DownloadJob = class DownloadJob extends Job {
|
|
|
244
126
|
_currentDir = {};
|
|
245
127
|
/** In-memory archive keyed by archive format. */
|
|
246
128
|
archive = /* @__PURE__ */ new Map();
|
|
247
|
-
/**
|
|
248
|
-
* Registry of per-category "archive formats" — the key is formed
|
|
249
|
-
* by interpolating this format string over the metadata.
|
|
250
|
-
*/
|
|
251
129
|
_archiveFmts = /* @__PURE__ */ new Map();
|
|
252
130
|
_postCount = 0;
|
|
253
131
|
_fileCount = 0;
|
|
@@ -256,23 +134,18 @@ var DownloadJob = class DownloadJob extends Job {
|
|
|
256
134
|
registerArchive(category, format) {
|
|
257
135
|
this._archiveFmts.set(category, format);
|
|
258
136
|
}
|
|
259
|
-
/** Simple format-string interpolation for archive keys. */
|
|
260
137
|
_interp(fmt, meta) {
|
|
261
138
|
return fmt.replace(/\{(\w+)\}/g, (_, key) => {
|
|
262
139
|
const v = meta[key];
|
|
263
140
|
return v == null ? "" : String(v);
|
|
264
141
|
});
|
|
265
142
|
}
|
|
266
|
-
/** Check whether this URL has already been downloaded (and skip). */
|
|
267
143
|
_isArchived(meta) {
|
|
268
144
|
const cat = meta.category ?? this.extractor.category;
|
|
269
145
|
const fmt = this._archiveFmts.get(cat) ?? "{media_id}";
|
|
270
146
|
const key = this._interp(fmt, meta);
|
|
271
|
-
|
|
272
|
-
if (set && set.has(key)) return true;
|
|
273
|
-
return false;
|
|
147
|
+
return !!this.archive.get(cat)?.has(key);
|
|
274
148
|
}
|
|
275
|
-
/** Mark a post/media as archived. */
|
|
276
149
|
_archive(meta) {
|
|
277
150
|
const cat = meta.category ?? this.extractor.category;
|
|
278
151
|
const fmt = this._archiveFmts.get(cat) ?? "{media_id}";
|
|
@@ -284,7 +157,6 @@ var DownloadJob = class DownloadJob extends Job {
|
|
|
284
157
|
}
|
|
285
158
|
set.add(key);
|
|
286
159
|
}
|
|
287
|
-
/** Handlers */
|
|
288
160
|
async handleDirectory(msg) {
|
|
289
161
|
this._currentDir = { ...msg.metadata };
|
|
290
162
|
this._postCount++;
|
|
@@ -313,7 +185,6 @@ var DownloadJob = class DownloadJob extends Job {
|
|
|
313
185
|
if (resp.data instanceof Uint8Array) data = resp.data;
|
|
314
186
|
else if (resp.data instanceof ArrayBuffer) data = new Uint8Array(resp.data);
|
|
315
187
|
else if (typeof resp.data === "string") data = resp.data;
|
|
316
|
-
else if (typeof resp.data === "object" && resp.data != null && "type" in resp.data && resp.data.type === "Buffer") data = new Uint8Array(resp.data);
|
|
317
188
|
else data = JSON.stringify(resp.data);
|
|
318
189
|
await this.extractor.storage.write(fullPath, data);
|
|
319
190
|
this._fileCount++;
|
|
@@ -357,13 +228,11 @@ var DownloadJob = class DownloadJob extends Job {
|
|
|
357
228
|
else this.archive.set(cat, set);
|
|
358
229
|
}
|
|
359
230
|
}
|
|
360
|
-
/** Report */
|
|
361
231
|
_report() {
|
|
362
232
|
const log = this.extractor.log;
|
|
363
233
|
log.info(`Done — ${this._postCount} post(s), ${this._fileCount} file(s) downloaded (${formatBytes(this._downloadedBytes)})`);
|
|
364
234
|
if (this._skippedCount > 0) log.info(` ${this._skippedCount} file(s) skipped (already archived)`);
|
|
365
235
|
}
|
|
366
|
-
/** Path builders */
|
|
367
236
|
_buildDirPath(meta) {
|
|
368
237
|
return `${meta.category ?? this.extractor.category}/${meta.username ?? "_"}`;
|
|
369
238
|
}
|
|
@@ -373,184 +242,129 @@ var DownloadJob = class DownloadJob extends Job {
|
|
|
373
242
|
return `${mid}${meta.num ? `_${meta.num}` : ""}.${ext}`;
|
|
374
243
|
}
|
|
375
244
|
};
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
245
|
+
//#endregion
|
|
246
|
+
//#region src/core/extractor.ts
|
|
247
|
+
/** A no-op logger */
|
|
248
|
+
const noopLogger = {
|
|
249
|
+
debug: () => {},
|
|
250
|
+
info: () => {},
|
|
251
|
+
warn: () => {},
|
|
252
|
+
error: () => {}
|
|
253
|
+
};
|
|
254
|
+
var Extractor = class {
|
|
255
|
+
/** Regex pattern to match against URLs */
|
|
256
|
+
static pattern = /^$/;
|
|
257
|
+
/** The input URL */
|
|
258
|
+
url;
|
|
259
|
+
/** Regex match groups from ``fromURL`` */
|
|
260
|
+
groups;
|
|
261
|
+
config;
|
|
262
|
+
/** HTTP client — public so Job can access for downloads */
|
|
263
|
+
http;
|
|
264
|
+
/** Storage backend — public so Job can access for writes */
|
|
265
|
+
storage;
|
|
266
|
+
/** Logger instance — public so Job can access for reporting */
|
|
267
|
+
log;
|
|
268
|
+
/** Delay range in seconds — random between [min, max] before each request */
|
|
269
|
+
requestInterval = [6, 12];
|
|
270
|
+
_initialized = false;
|
|
271
|
+
constructor(opts) {
|
|
272
|
+
this.url = opts.url;
|
|
273
|
+
this.groups = opts.match ? [...opts.match].slice(1) : [];
|
|
274
|
+
this.config = opts.config;
|
|
275
|
+
this.http = opts.http;
|
|
276
|
+
this.storage = opts.storage;
|
|
277
|
+
this.log = opts.log;
|
|
385
278
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
279
|
+
/** Initialization */
|
|
280
|
+
/**
|
|
281
|
+
* One-time async setup (cookies, session, internal state).
|
|
282
|
+
* Safe to call multiple times — after the first call it becomes a no-op.
|
|
283
|
+
*/
|
|
284
|
+
async initialize() {
|
|
285
|
+
if (this._initialized) return;
|
|
286
|
+
await this._init();
|
|
287
|
+
this._initialized = true;
|
|
288
|
+
this.initialize = async () => {};
|
|
391
289
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
290
|
+
/**
|
|
291
|
+
* Subclass hook for one-time setup.
|
|
292
|
+
*/
|
|
293
|
+
async _init() {}
|
|
294
|
+
/** Async iteration */
|
|
295
|
+
async *[Symbol.asyncIterator]() {
|
|
296
|
+
await this.initialize();
|
|
297
|
+
yield* this.items();
|
|
298
|
+
}
|
|
299
|
+
/** Config helpers */
|
|
300
|
+
/**
|
|
301
|
+
* Read a config value using the interpolated hierarchy.
|
|
302
|
+
*/
|
|
303
|
+
_cfg(key, defaultVal) {
|
|
304
|
+
const path = [
|
|
305
|
+
"extractor",
|
|
306
|
+
this.category,
|
|
307
|
+
this.subcategory
|
|
308
|
+
];
|
|
309
|
+
return this.config.interpolate(path, key, defaultVal);
|
|
310
|
+
}
|
|
311
|
+
/** HTTP */
|
|
312
|
+
_lastRequestTime = 0;
|
|
313
|
+
/**
|
|
314
|
+
* Rate-limited HTTP request wrapper.
|
|
315
|
+
*/
|
|
316
|
+
async request(url, cfg = {}) {
|
|
317
|
+
await this._throttle();
|
|
318
|
+
const response = await this.http.request({
|
|
319
|
+
url,
|
|
320
|
+
...cfg
|
|
407
321
|
});
|
|
322
|
+
this._lastRequestTime = Date.now();
|
|
323
|
+
return response;
|
|
408
324
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
const match = cls.pattern.exec(msg.url);
|
|
420
|
-
if (!match) return;
|
|
421
|
-
const parentExtr = this.extractor;
|
|
422
|
-
const childJob = new PrintJob(Reflect.construct(cls, [{
|
|
423
|
-
url: msg.url,
|
|
424
|
-
match,
|
|
425
|
-
config: parentExtr.config,
|
|
426
|
-
http: parentExtr.http,
|
|
427
|
-
storage: parentExtr.storage,
|
|
428
|
-
log: parentExtr.log
|
|
429
|
-
}]));
|
|
430
|
-
const childStatus = await childJob.run();
|
|
431
|
-
this.status |= childStatus;
|
|
432
|
-
this._postCount += childJob._postCount;
|
|
433
|
-
this._fileCount += childJob._fileCount;
|
|
434
|
-
}
|
|
435
|
-
/** Output */
|
|
436
|
-
_flushPost() {
|
|
437
|
-
const m = this._currentDir;
|
|
438
|
-
if (Object.keys(m).length === 0) return;
|
|
439
|
-
const w = this._width;
|
|
440
|
-
const labelW = 14;
|
|
441
|
-
const shortcode = m.post_shortcode ?? "?";
|
|
442
|
-
const header = ` Post #${this._postCount}: ${shortcode} `;
|
|
443
|
-
const padTotal = w - 2 - header.length;
|
|
444
|
-
const padL = Math.floor(padTotal / 2);
|
|
445
|
-
const padR = padTotal - padL;
|
|
446
|
-
process.stdout.write(`\n${dim("┌")}${"─".repeat(padL)}${b(header)}${"─".repeat(padR)}${dim("┐")}\n`);
|
|
447
|
-
const row = (label, value, color) => {
|
|
448
|
-
const colored = typeof color === "function" ? color(value) : color ? `${color}${value}${RESET}` : value;
|
|
449
|
-
process.stdout.write(` ${dim("│")} ${c(pad(label, labelW))} ${colored}\n`);
|
|
450
|
-
};
|
|
451
|
-
const username = m.username ?? "?";
|
|
452
|
-
const fullname = m.fullname ?? "";
|
|
453
|
-
row("Author:", fullname ? `${username} (${fullname})` : username, g);
|
|
454
|
-
row("Date:", m.date ?? m.post_date ?? "?");
|
|
455
|
-
row("Likes:", `${typeof m.likes === "number" ? m.likes.toLocaleString() : "?"} | Liked: ${m.liked ? "yes" : "no"}`);
|
|
456
|
-
row("Type:", `${m.type ?? "?"} (${this._files.length} files)`);
|
|
457
|
-
row("URL:", m.post_url ?? "?");
|
|
458
|
-
const desc = m.description ?? "";
|
|
459
|
-
if (desc) {
|
|
460
|
-
process.stdout.write(` ${dim("│")}\n`);
|
|
461
|
-
process.stdout.write(` ${dim("│")} ${b("Description:")}\n`);
|
|
462
|
-
const lines = desc.split("\n");
|
|
463
|
-
for (const line of lines) {
|
|
464
|
-
const wrapped = this._wrap(line, w - 8);
|
|
465
|
-
for (const wl of wrapped) process.stdout.write(` ${dim("│")} ${dim(wl)}\n`);
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
const tags = m.tags;
|
|
469
|
-
if (tags && tags.length > 0) {
|
|
470
|
-
process.stdout.write(` ${dim("│")}\n`);
|
|
471
|
-
process.stdout.write(` ${dim("│")} ${b("Tags:")} ${dim(tags.map((t) => `#${t}`).join(" "))}\n`);
|
|
472
|
-
}
|
|
473
|
-
const locName = m.location_slug ?? "";
|
|
474
|
-
const locId = m.location_id ?? "";
|
|
475
|
-
if (locName || locId) row("Location:", locId ? `${locName} (ID: ${locId})` : locName);
|
|
476
|
-
const coauthors = m.coauthors;
|
|
477
|
-
if (coauthors && coauthors.length > 0) row("Co-authors:", coauthors.map((c) => c.full_name ? `${c.username} (${c.full_name})` : c.username).join(", "));
|
|
478
|
-
const pinned = m.pinned;
|
|
479
|
-
if (pinned && pinned.length > 0) row("Pinned:", pinned.join(", "));
|
|
480
|
-
const expires = m.expires;
|
|
481
|
-
if (expires) row("Expires:", expires, YELLOW);
|
|
482
|
-
const hlTitle = m.highlight_title;
|
|
483
|
-
if (hlTitle) row("Highlight:", hlTitle);
|
|
484
|
-
const taggedUser = m.tagged_username ?? "";
|
|
485
|
-
if (taggedUser) {
|
|
486
|
-
const taggedFull = m.tagged_full_name ?? "";
|
|
487
|
-
row("Tagged by:", taggedFull ? `${taggedUser} (${taggedFull})` : taggedUser);
|
|
488
|
-
}
|
|
489
|
-
if (this._files.length > 0) {
|
|
490
|
-
process.stdout.write(` ${dim("│")}\n`);
|
|
491
|
-
process.stdout.write(` ${dim("│")} ${b(`Media (${this._files.length} files):`)}\n`);
|
|
492
|
-
const maxNumW = String(this._files.length).length;
|
|
493
|
-
const maxFileW = Math.max(...this._files.map((f) => f.filename.length));
|
|
494
|
-
const dimW = Math.min(maxFileW, 40);
|
|
495
|
-
for (const f of this._files) {
|
|
496
|
-
const numStr = `[${String(f.num).padStart(maxNumW)}]`;
|
|
497
|
-
const dimStr = f.filename.length > 40 ? `${f.filename.slice(0, 37)}...` : pad(f.filename, dimW);
|
|
498
|
-
const res = f.width ? `${f.width}x${f.height}` : "?x?";
|
|
499
|
-
const badges = [];
|
|
500
|
-
if (f.videoUrl) badges.push("video");
|
|
501
|
-
if (f.audioUrl) badges.push("audio");
|
|
502
|
-
let line = ` ${dim("│")} ${g(numStr)} ${dimStr} ${res}`;
|
|
503
|
-
if (badges.length > 0) line += ` ${YELLOW}(${badges.join("+")})${RESET}`;
|
|
504
|
-
process.stdout.write(`${line}\n`);
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
process.stdout.write(` ${dim("└")}${"─".repeat(w - 2)}${dim("┘")}\n`);
|
|
508
|
-
}
|
|
509
|
-
_wrap(text, maxLen) {
|
|
510
|
-
if (text.length <= maxLen) return [text];
|
|
511
|
-
const lines = [];
|
|
512
|
-
let remaining = text;
|
|
513
|
-
while (remaining.length > maxLen) {
|
|
514
|
-
let cut = maxLen;
|
|
515
|
-
while (cut > 0 && remaining[cut] !== " ") cut--;
|
|
516
|
-
if (cut === 0) cut = maxLen;
|
|
517
|
-
lines.push(remaining.slice(0, cut).trimEnd());
|
|
518
|
-
remaining = remaining.slice(cut).trimStart();
|
|
325
|
+
/**
|
|
326
|
+
* Convenience: request + parse JSON body.
|
|
327
|
+
*/
|
|
328
|
+
async requestJSON(url, cfg = {}) {
|
|
329
|
+
const resp = await this.request(url, cfg);
|
|
330
|
+
if (typeof resp.data === "object") return resp.data;
|
|
331
|
+
try {
|
|
332
|
+
return JSON.parse(resp.data);
|
|
333
|
+
} catch {
|
|
334
|
+
return {};
|
|
519
335
|
}
|
|
520
|
-
if (remaining) lines.push(remaining);
|
|
521
|
-
return lines;
|
|
522
336
|
}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
337
|
+
/** Rate limiting */
|
|
338
|
+
/**
|
|
339
|
+
* Sleep long enough to keep the minimum interval between requests.
|
|
340
|
+
*/
|
|
341
|
+
async _throttle() {
|
|
342
|
+
const elapsed = Date.now() - this._lastRequestTime;
|
|
343
|
+
const [min, max] = this.requestInterval;
|
|
344
|
+
const target = min + Math.random() * (max - min);
|
|
345
|
+
const waitMs = Math.max(0, target * 1e3 - elapsed);
|
|
346
|
+
if (waitMs > 0) await new Promise((r) => setTimeout(r, waitMs));
|
|
347
|
+
}
|
|
348
|
+
/** Utility */
|
|
349
|
+
/**
|
|
350
|
+
* Convert a Unix timestamp (seconds or ms) to an ISO-8601 string.
|
|
351
|
+
*/
|
|
352
|
+
parseTimestamp(ts) {
|
|
353
|
+
if (ts == null) return "";
|
|
354
|
+
const asMs = ts > 25e8 ? ts : ts * 1e3;
|
|
355
|
+
return new Date(asMs).toISOString();
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Generate a random hex token (used for CSRF).
|
|
359
|
+
*/
|
|
360
|
+
static generateToken(size = 16) {
|
|
361
|
+
const bytes = new Uint8Array(size);
|
|
362
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) crypto.getRandomValues(bytes);
|
|
363
|
+
else for (let i = 0; i < size; i++) bytes[i] = Math.floor(Math.random() * 256);
|
|
364
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
529
365
|
}
|
|
530
366
|
};
|
|
531
367
|
//#endregion
|
|
532
|
-
//#region src/message.ts
|
|
533
|
-
function directory(metadata = {}) {
|
|
534
|
-
return {
|
|
535
|
-
type: "directory",
|
|
536
|
-
metadata
|
|
537
|
-
};
|
|
538
|
-
}
|
|
539
|
-
function url(u, metadata = {}) {
|
|
540
|
-
return {
|
|
541
|
-
type: "url",
|
|
542
|
-
url: u,
|
|
543
|
-
metadata
|
|
544
|
-
};
|
|
545
|
-
}
|
|
546
|
-
function queue(u, metadata = {}) {
|
|
547
|
-
return {
|
|
548
|
-
type: "queue",
|
|
549
|
-
url: u,
|
|
550
|
-
metadata
|
|
551
|
-
};
|
|
552
|
-
}
|
|
553
|
-
//#endregion
|
|
554
368
|
//#region src/utils/id-codec.ts
|
|
555
369
|
/**
|
|
556
370
|
* Instagram-style Base64-variant ID ↔ shortcode conversion.
|
|
@@ -582,6 +396,28 @@ function shortcodeFromId(postId) {
|
|
|
582
396
|
return chars.reverse().join("");
|
|
583
397
|
}
|
|
584
398
|
//#endregion
|
|
399
|
+
//#region src/message.ts
|
|
400
|
+
function directory(metadata = {}) {
|
|
401
|
+
return {
|
|
402
|
+
type: "directory",
|
|
403
|
+
metadata
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
function url(u, metadata = {}) {
|
|
407
|
+
return {
|
|
408
|
+
type: "url",
|
|
409
|
+
url: u,
|
|
410
|
+
metadata
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
function queue(u, metadata = {}) {
|
|
414
|
+
return {
|
|
415
|
+
type: "queue",
|
|
416
|
+
url: u,
|
|
417
|
+
metadata
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
//#endregion
|
|
585
421
|
//#region src/utils/text.ts
|
|
586
422
|
/**
|
|
587
423
|
* Text utilities ported from gallery-dl's ``text`` module.
|
|
@@ -1053,8 +889,8 @@ var InstagramRestAPI = class {
|
|
|
1053
889
|
}
|
|
1054
890
|
};
|
|
1055
891
|
//#endregion
|
|
1056
|
-
//#region src/instagram/parsers.ts
|
|
1057
|
-
/** Main entry — REST */
|
|
892
|
+
//#region src/instagram/parsers/rest.ts
|
|
893
|
+
/** Main entry — parse a REST post response. */
|
|
1058
894
|
function parsePostRest(post, cfg) {
|
|
1059
895
|
if (post.items) return parseStoryRest(post, cfg);
|
|
1060
896
|
const owner = post.user;
|
|
@@ -1071,6 +907,7 @@ function parsePostRest(post, cfg) {
|
|
|
1071
907
|
owner_id: owner.pk,
|
|
1072
908
|
username: owner.username ?? "",
|
|
1073
909
|
fullname: owner.full_name ?? "",
|
|
910
|
+
user: owner,
|
|
1074
911
|
post_date: date,
|
|
1075
912
|
date,
|
|
1076
913
|
description: caption ? caption.text : "",
|
|
@@ -1082,10 +919,9 @@ function parsePostRest(post, cfg) {
|
|
|
1082
919
|
if (tags.length > 0) data.tags = [...new Set(tags)].sort();
|
|
1083
920
|
if (post.location) {
|
|
1084
921
|
const loc = post.location;
|
|
1085
|
-
const slug = loc.short_name.replace(/\s+/g, "-").toLowerCase();
|
|
1086
922
|
data.location_id = loc.pk;
|
|
1087
|
-
data.location_slug =
|
|
1088
|
-
data.location_url = `${cfg.root}/explore/locations/${loc.pk}/${
|
|
923
|
+
data.location_slug = loc.short_name.replace(/\s+/g, "-").toLowerCase();
|
|
924
|
+
data.location_url = `${cfg.root}/explore/locations/${loc.pk}/${data.location_slug}/`;
|
|
1089
925
|
}
|
|
1090
926
|
if (post.coauthor_producers) data.coauthors = post.coauthor_producers.map((u) => ({
|
|
1091
927
|
id: u.pk,
|
|
@@ -1117,7 +953,7 @@ function parsePostRest(post, cfg) {
|
|
|
1117
953
|
if (post.music_metadata) {
|
|
1118
954
|
const info = post.music_metadata.music_info;
|
|
1119
955
|
if (info) {
|
|
1120
|
-
const audio = extractAudio(post, data,
|
|
956
|
+
const audio = extractAudio(post, data, info, cfg);
|
|
1121
957
|
if (audio) {
|
|
1122
958
|
audio.num = items.length;
|
|
1123
959
|
data._files.push(audio);
|
|
@@ -1132,7 +968,7 @@ function parsePostRest(post, cfg) {
|
|
|
1132
968
|
if (post.subscription_media_visibility) data.subscription = post.subscription_media_visibility;
|
|
1133
969
|
return data;
|
|
1134
970
|
}
|
|
1135
|
-
/**
|
|
971
|
+
/** Parse a story or highlight REST response. */
|
|
1136
972
|
function parseStoryRest(post, cfg) {
|
|
1137
973
|
const items = post.items;
|
|
1138
974
|
const reelId = String(post.id).split(":").pop() ?? "0";
|
|
@@ -1158,9 +994,8 @@ function parseStoryRest(post, cfg) {
|
|
|
1158
994
|
expires: expires ? cfg.parseTimestamp(expires) : void 0,
|
|
1159
995
|
user: post.user
|
|
1160
996
|
};
|
|
1161
|
-
if (!isStory)
|
|
1162
|
-
|
|
1163
|
-
} else if (!post.seen) post.seen = expires - 86400;
|
|
997
|
+
if (!isStory && post.title) data.highlight_title = post.title;
|
|
998
|
+
else if (!post.seen) post.seen = expires - 86400;
|
|
1164
999
|
for (let num = 0; num < items.length; num++) {
|
|
1165
1000
|
const item = items[num];
|
|
1166
1001
|
const media = parseMediaItem(item, post, cfg, num + 1);
|
|
@@ -1170,7 +1005,7 @@ function parseStoryRest(post, cfg) {
|
|
|
1170
1005
|
}
|
|
1171
1006
|
return data;
|
|
1172
1007
|
}
|
|
1173
|
-
/**
|
|
1008
|
+
/** Parse a single media item (image/video) from a carousel or story. */
|
|
1174
1009
|
function parseMediaItem(item, parent, cfg, num) {
|
|
1175
1010
|
let image;
|
|
1176
1011
|
try {
|
|
@@ -1229,7 +1064,7 @@ function parseMediaItem(item, parent, cfg, num) {
|
|
|
1229
1064
|
if (itemRec.audience) media.audience = itemRec.audience;
|
|
1230
1065
|
return media;
|
|
1231
1066
|
}
|
|
1232
|
-
/**
|
|
1067
|
+
/** Extract tagged users from various field formats. */
|
|
1233
1068
|
function extractTaggedUsers(src, dest) {
|
|
1234
1069
|
dest.tagged_users = [];
|
|
1235
1070
|
const edges = src.edge_media_to_tagged_user;
|
|
@@ -1272,22 +1107,23 @@ function extractTaggedUsers(src, dest) {
|
|
|
1272
1107
|
}
|
|
1273
1108
|
}
|
|
1274
1109
|
const seen = /* @__PURE__ */ new Set();
|
|
1275
|
-
dest.tagged_users = dest.tagged_users.filter((t) =>
|
|
1276
|
-
if (seen.has(t.id)) return false;
|
|
1277
|
-
seen.add(t.id);
|
|
1278
|
-
return true;
|
|
1279
|
-
});
|
|
1110
|
+
dest.tagged_users = dest.tagged_users.filter((t) => seen.has(t.id) ? false : (seen.add(t.id), true));
|
|
1280
1111
|
}
|
|
1281
|
-
/**
|
|
1112
|
+
/** Extract audio/music metadata from a story sticker. */
|
|
1282
1113
|
function extractAudio(src, dest, sticker, cfg) {
|
|
1283
1114
|
const info = sticker.music_asset_info;
|
|
1284
1115
|
if (!info) return null;
|
|
1285
1116
|
const cinfo = sticker.music_consumption_info ?? info;
|
|
1286
|
-
dest.audio_title = info.title;
|
|
1117
|
+
dest.audio_title = info.title ?? info.sanitized_title;
|
|
1118
|
+
dest.audio_subtitle = info.subtitle;
|
|
1287
1119
|
dest.audio_duration = (info.duration_in_ms ?? 0) / 1e3;
|
|
1288
1120
|
dest.audio_timestamps = info.highlight_start_times_in_ms;
|
|
1289
1121
|
dest.audio_artist = info.display_artist ?? cinfo.display_artist;
|
|
1290
1122
|
dest.audio_user = info.ig_artist ?? cinfo.ig_artist;
|
|
1123
|
+
dest.audio_has_lyrics = info.has_lyrics;
|
|
1124
|
+
dest.audio_is_explicit = info.is_explicit;
|
|
1125
|
+
dest.audio_cover_artwork_uri = info.cover_artwork_uri;
|
|
1126
|
+
dest.audio_cover_artwork_thumbnail_uri = info.cover_artwork_thumbnail_uri;
|
|
1291
1127
|
const url = info.progressive_download_url;
|
|
1292
1128
|
if (!url) return null;
|
|
1293
1129
|
return {
|
|
@@ -1304,13 +1140,25 @@ function extractAudio(src, dest, sticker, cfg) {
|
|
|
1304
1140
|
height_original: 0,
|
|
1305
1141
|
tagged_users: [],
|
|
1306
1142
|
audio_user: info.ig_artist ?? cinfo.ig_artist,
|
|
1307
|
-
audio_title: info.title,
|
|
1143
|
+
audio_title: info.title ?? info.sanitized_title,
|
|
1144
|
+
audio_subtitle: info.subtitle,
|
|
1308
1145
|
audio_artist: info.display_artist ?? cinfo.display_artist,
|
|
1309
1146
|
audio_duration: (info.duration_in_ms ?? 0) / 1e3,
|
|
1310
|
-
audio_timestamps: info.highlight_start_times_in_ms
|
|
1147
|
+
audio_timestamps: info.highlight_start_times_in_ms,
|
|
1148
|
+
audio_cover_artwork_uri: info.cover_artwork_uri,
|
|
1149
|
+
audio_cover_artwork_thumbnail_uri: info.cover_artwork_thumbnail_uri,
|
|
1150
|
+
audio_has_lyrics: info.has_lyrics,
|
|
1151
|
+
audio_is_explicit: info.is_explicit
|
|
1311
1152
|
};
|
|
1312
1153
|
}
|
|
1313
|
-
|
|
1154
|
+
function extractPinned(post) {
|
|
1155
|
+
if (post.timeline_pinned_user_ids) return post.timeline_pinned_user_ids;
|
|
1156
|
+
if (post.clips_tab_pinned_user_ids) return post.clips_tab_pinned_user_ids;
|
|
1157
|
+
return [];
|
|
1158
|
+
}
|
|
1159
|
+
//#endregion
|
|
1160
|
+
//#region src/instagram/parsers/graphql.ts
|
|
1161
|
+
/** Parse a GraphQL post/edge response. */
|
|
1314
1162
|
function parsePostGraphql(post, cfg) {
|
|
1315
1163
|
const typename = post.__typename ?? "GraphImage";
|
|
1316
1164
|
const owner = post.owner;
|
|
@@ -1323,6 +1171,7 @@ function parsePostGraphql(post, cfg) {
|
|
|
1323
1171
|
owner_id: owner.id ?? owner.pk,
|
|
1324
1172
|
username: owner.username ?? "",
|
|
1325
1173
|
fullname: owner.full_name ?? "",
|
|
1174
|
+
user: owner,
|
|
1326
1175
|
post_id: post.id,
|
|
1327
1176
|
post_shortcode: post.shortcode,
|
|
1328
1177
|
post_url: `${cfg.root}/p/${post.shortcode}/`,
|
|
@@ -1395,11 +1244,6 @@ function parsePostGraphql(post, cfg) {
|
|
|
1395
1244
|
}
|
|
1396
1245
|
return data;
|
|
1397
1246
|
}
|
|
1398
|
-
function extractPinned(post) {
|
|
1399
|
-
if (post.timeline_pinned_user_ids) return post.timeline_pinned_user_ids;
|
|
1400
|
-
if (post.clips_tab_pinned_user_ids) return post.clips_tab_pinned_user_ids;
|
|
1401
|
-
return [];
|
|
1402
|
-
}
|
|
1403
1247
|
function parseUnicodeEscapes(text) {
|
|
1404
1248
|
if (!text.includes("\\u")) return text;
|
|
1405
1249
|
return text.replace(/\\u([0-9a-fA-F]{4})/g, (_, hex) => String.fromCharCode(Number.parseInt(hex, 16)));
|
|
@@ -1558,115 +1402,160 @@ var InstagramExtractor = class extends Extractor {
|
|
|
1558
1402
|
}
|
|
1559
1403
|
};
|
|
1560
1404
|
//#endregion
|
|
1561
|
-
//#region src/instagram/extractors.ts
|
|
1405
|
+
//#region src/instagram/extractors/helpers.ts
|
|
1406
|
+
/** Shared regex utilities for Instagram extractor URL patterns. */
|
|
1562
1407
|
const BASE_RE = /^(?:https?:\/\/)?(?:www\.)?instagram\.com/;
|
|
1563
1408
|
function re(base, path) {
|
|
1564
1409
|
const pathSrc = typeof path === "string" ? path : path.source;
|
|
1565
1410
|
return new RegExp(base.source + pathSrc, "i");
|
|
1566
1411
|
}
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1412
|
+
//#endregion
|
|
1413
|
+
//#region src/instagram/extractors/registry.ts
|
|
1414
|
+
const _registry = /* @__PURE__ */ new Map();
|
|
1415
|
+
function register(subcategory, cls) {
|
|
1416
|
+
_registry.set(subcategory, cls);
|
|
1417
|
+
}
|
|
1418
|
+
function get(subcategory) {
|
|
1419
|
+
return _registry.get(subcategory);
|
|
1420
|
+
}
|
|
1421
|
+
//#endregion
|
|
1422
|
+
//#region src/instagram/extractors/avatar.ts
|
|
1423
|
+
var InstagramAvatarExtractor = class InstagramAvatarExtractor extends InstagramExtractor {
|
|
1424
|
+
static subcategory = "avatar";
|
|
1425
|
+
static pattern = re(BASE_RE, /(\/[^/?#]+)\/avatar/);
|
|
1426
|
+
subcategory = InstagramAvatarExtractor.subcategory;
|
|
1571
1427
|
constructor(opts) {
|
|
1572
1428
|
super(opts);
|
|
1573
|
-
if (opts.match[2] != null || opts.match[3] != null) this.subcategory = "reel";
|
|
1574
1429
|
}
|
|
1575
1430
|
static fromURL(url, opts) {
|
|
1576
|
-
const match =
|
|
1431
|
+
const match = InstagramAvatarExtractor.pattern.exec(url);
|
|
1577
1432
|
if (!match) return null;
|
|
1578
|
-
return new
|
|
1433
|
+
return new InstagramAvatarExtractor({
|
|
1579
1434
|
...opts,
|
|
1580
1435
|
url,
|
|
1581
1436
|
match
|
|
1582
1437
|
});
|
|
1583
1438
|
}
|
|
1584
1439
|
async *posts() {
|
|
1585
|
-
const
|
|
1586
|
-
let
|
|
1587
|
-
if (
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1440
|
+
const screenName = (this.groups[0] ?? "").replace(/^\//, "");
|
|
1441
|
+
let user;
|
|
1442
|
+
if (screenName.startsWith("id:")) user = await this.api.userById(screenName.slice(3));
|
|
1443
|
+
else user = await this.api.userByScreenName(screenName);
|
|
1444
|
+
const avatar = user.hd_profile_pic_url_info ?? user.hd_profile_pic_versions?.[user.hd_profile_pic_versions.length - 1] ?? {
|
|
1445
|
+
url: user.profile_pic_url ?? "",
|
|
1446
|
+
width: 0,
|
|
1447
|
+
height: 0
|
|
1448
|
+
};
|
|
1449
|
+
let pk = user.profile_pic_id?.split("_")[0];
|
|
1450
|
+
let code;
|
|
1451
|
+
if (pk) code = shortcodeFromId(pk);
|
|
1452
|
+
else {
|
|
1453
|
+
pk = `avatar:${user.pk}`;
|
|
1454
|
+
code = pk;
|
|
1596
1455
|
}
|
|
1597
|
-
|
|
1598
|
-
|
|
1456
|
+
yield {
|
|
1457
|
+
pk,
|
|
1458
|
+
code,
|
|
1459
|
+
user,
|
|
1460
|
+
caption: null,
|
|
1461
|
+
like_count: 0,
|
|
1462
|
+
image_versions2: { candidates: [avatar] }
|
|
1463
|
+
};
|
|
1599
1464
|
}
|
|
1600
1465
|
};
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1466
|
+
register(InstagramAvatarExtractor.subcategory, InstagramAvatarExtractor);
|
|
1467
|
+
//#endregion
|
|
1468
|
+
//#region src/instagram/extractors/highlights.ts
|
|
1469
|
+
var InstagramHighlightsExtractor = class InstagramHighlightsExtractor extends InstagramExtractor {
|
|
1470
|
+
static subcategory = "highlights";
|
|
1471
|
+
static pattern = re(BASE_RE, /(\/[^/?#]+)\/highlights/);
|
|
1472
|
+
subcategory = InstagramHighlightsExtractor.subcategory;
|
|
1605
1473
|
constructor(opts) {
|
|
1606
1474
|
super(opts);
|
|
1607
1475
|
}
|
|
1608
1476
|
static fromURL(url, opts) {
|
|
1609
|
-
const match =
|
|
1477
|
+
const match = InstagramHighlightsExtractor.pattern.exec(url);
|
|
1610
1478
|
if (!match) return null;
|
|
1611
|
-
return new
|
|
1479
|
+
return new InstagramHighlightsExtractor({
|
|
1480
|
+
...opts,
|
|
1481
|
+
url,
|
|
1482
|
+
match
|
|
1483
|
+
});
|
|
1484
|
+
}
|
|
1485
|
+
async *posts() {
|
|
1486
|
+
const screenName = (this.groups[0] ?? "").replace(/^\//, "");
|
|
1487
|
+
const uid = await this.api.userId(screenName);
|
|
1488
|
+
yield* this.api.highlightsMedia(uid);
|
|
1489
|
+
}
|
|
1490
|
+
};
|
|
1491
|
+
register(InstagramHighlightsExtractor.subcategory, InstagramHighlightsExtractor);
|
|
1492
|
+
//#endregion
|
|
1493
|
+
//#region src/instagram/extractors/info.ts
|
|
1494
|
+
var InstagramInfoExtractor = class InstagramInfoExtractor extends InstagramExtractor {
|
|
1495
|
+
static subcategory = "info";
|
|
1496
|
+
static pattern = re(BASE_RE, /(\/[^/?#]+)\/info/);
|
|
1497
|
+
subcategory = InstagramInfoExtractor.subcategory;
|
|
1498
|
+
constructor(opts) {
|
|
1499
|
+
super(opts);
|
|
1500
|
+
}
|
|
1501
|
+
static fromURL(url, opts) {
|
|
1502
|
+
const match = InstagramInfoExtractor.pattern.exec(url);
|
|
1503
|
+
if (!match) return null;
|
|
1504
|
+
return new InstagramInfoExtractor({
|
|
1612
1505
|
...opts,
|
|
1613
1506
|
url,
|
|
1614
1507
|
match
|
|
1615
1508
|
});
|
|
1616
1509
|
}
|
|
1617
1510
|
async *items() {
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
}
|
|
1657
|
-
|
|
1658
|
-
cls: InstagramTaggedExtractor,
|
|
1659
|
-
url: `${base}tagged/`
|
|
1660
|
-
}
|
|
1661
|
-
};
|
|
1662
|
-
for (const cat of categories) {
|
|
1663
|
-
const entry = extractors[cat];
|
|
1664
|
-
if (entry) yield queue(entry.url, { _extractor: entry.cls });
|
|
1665
|
-
else this.log.warn(`Invalid include '${cat}'`);
|
|
1511
|
+
const screenName = (this.groups[0] ?? "").replace(/^\//, "");
|
|
1512
|
+
let user;
|
|
1513
|
+
if (screenName.startsWith("id:")) user = await this.api.userById(screenName.slice(3));
|
|
1514
|
+
else user = await this.api.userByScreenName(screenName);
|
|
1515
|
+
yield directory(user);
|
|
1516
|
+
}
|
|
1517
|
+
async *posts() {}
|
|
1518
|
+
};
|
|
1519
|
+
register(InstagramInfoExtractor.subcategory, InstagramInfoExtractor);
|
|
1520
|
+
//#endregion
|
|
1521
|
+
//#region src/instagram/extractors/post.ts
|
|
1522
|
+
var InstagramPostExtractor = class InstagramPostExtractor extends InstagramExtractor {
|
|
1523
|
+
static subcategory = "post";
|
|
1524
|
+
static pattern = re(/^(?:https?:\/\/)?(?:www\.)?instagram\.com\//, /(?:share(?:\/(?:p|tv|reels?))?|(?:[^/?#]+\/)?(?:p|tv|reels?))\/([^/?#]+)/);
|
|
1525
|
+
subcategory = InstagramPostExtractor.subcategory;
|
|
1526
|
+
constructor(opts) {
|
|
1527
|
+
super(opts);
|
|
1528
|
+
if (opts.match[2] != null || opts.match[3] != null) this.subcategory = "reel";
|
|
1529
|
+
}
|
|
1530
|
+
static fromURL(url, opts) {
|
|
1531
|
+
const match = InstagramPostExtractor.pattern.exec(url);
|
|
1532
|
+
if (!match) return null;
|
|
1533
|
+
return new InstagramPostExtractor({
|
|
1534
|
+
...opts,
|
|
1535
|
+
url,
|
|
1536
|
+
match
|
|
1537
|
+
});
|
|
1538
|
+
}
|
|
1539
|
+
async *posts() {
|
|
1540
|
+
const groups = this.groups;
|
|
1541
|
+
let shortcode = groups[0];
|
|
1542
|
+
if (!shortcode) return;
|
|
1543
|
+
if (groups[1] === "") {
|
|
1544
|
+
this.log.info(`Resolving share link: ${this.url}`);
|
|
1545
|
+
const parts = (await this.request(ensureHttpScheme(this.url), { headers: {
|
|
1546
|
+
"Sec-Fetch-Dest": "empty",
|
|
1547
|
+
"Sec-Fetch-Mode": "navigate",
|
|
1548
|
+
"Sec-Fetch-Site": "same-origin"
|
|
1549
|
+
} })).url?.split("/");
|
|
1550
|
+
shortcode = parts?.[parts.length - 2] ?? shortcode;
|
|
1666
1551
|
}
|
|
1552
|
+
this.log.debug(`Fetching post: ${shortcode}`);
|
|
1553
|
+
yield* this.api.media(shortcode);
|
|
1667
1554
|
}
|
|
1668
|
-
async *posts() {}
|
|
1669
1555
|
};
|
|
1556
|
+
register(InstagramPostExtractor.subcategory, InstagramPostExtractor);
|
|
1557
|
+
//#endregion
|
|
1558
|
+
//#region src/instagram/extractors/posts-list.ts
|
|
1670
1559
|
var InstagramPostsExtractor = class InstagramPostsExtractor extends InstagramExtractor {
|
|
1671
1560
|
static subcategory = "posts";
|
|
1672
1561
|
static pattern = re(BASE_RE, /(\/[^/?#]+)\/posts/);
|
|
@@ -1689,6 +1578,9 @@ var InstagramPostsExtractor = class InstagramPostsExtractor extends InstagramExt
|
|
|
1689
1578
|
yield* this.api.userFeed(uid);
|
|
1690
1579
|
}
|
|
1691
1580
|
};
|
|
1581
|
+
register(InstagramPostsExtractor.subcategory, InstagramPostsExtractor);
|
|
1582
|
+
//#endregion
|
|
1583
|
+
//#region src/instagram/extractors/reels-list.ts
|
|
1692
1584
|
var InstagramReelsExtractor = class InstagramReelsExtractor extends InstagramExtractor {
|
|
1693
1585
|
static subcategory = "reels";
|
|
1694
1586
|
static pattern = re(BASE_RE, /(\/[^/?#]+)\/reels/);
|
|
@@ -1711,44 +1603,32 @@ var InstagramReelsExtractor = class InstagramReelsExtractor extends InstagramExt
|
|
|
1711
1603
|
yield* this.api.userClips(uid);
|
|
1712
1604
|
}
|
|
1713
1605
|
};
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1606
|
+
register(InstagramReelsExtractor.subcategory, InstagramReelsExtractor);
|
|
1607
|
+
//#endregion
|
|
1608
|
+
//#region src/instagram/extractors/saved.ts
|
|
1609
|
+
var InstagramSavedExtractor = class InstagramSavedExtractor extends InstagramExtractor {
|
|
1610
|
+
static subcategory = "saved";
|
|
1611
|
+
static pattern = re(BASE_RE, /(\/[^/?#]+)\/saved(?:\/all-posts)?\/?$/);
|
|
1612
|
+
subcategory = InstagramSavedExtractor.subcategory;
|
|
1719
1613
|
constructor(opts) {
|
|
1720
1614
|
super(opts);
|
|
1721
1615
|
}
|
|
1722
1616
|
static fromURL(url, opts) {
|
|
1723
|
-
const match =
|
|
1617
|
+
const match = InstagramSavedExtractor.pattern.exec(url);
|
|
1724
1618
|
if (!match) return null;
|
|
1725
|
-
return new
|
|
1619
|
+
return new InstagramSavedExtractor({
|
|
1726
1620
|
...opts,
|
|
1727
1621
|
url,
|
|
1728
1622
|
match
|
|
1729
1623
|
});
|
|
1730
1624
|
}
|
|
1731
|
-
async metadata() {
|
|
1732
|
-
const screenName = (this.groups[0] ?? "").replace(/^\//, "");
|
|
1733
|
-
let user;
|
|
1734
|
-
if (screenName.startsWith("id:")) {
|
|
1735
|
-
this._taggedUserId = screenName.slice(3);
|
|
1736
|
-
user = await this.api.userById(screenName.slice(3));
|
|
1737
|
-
} else {
|
|
1738
|
-
this._taggedUserId = await this.api.userId(screenName);
|
|
1739
|
-
user = await this.api.userByScreenName(screenName);
|
|
1740
|
-
}
|
|
1741
|
-
return {
|
|
1742
|
-
tagged_owner_id: user.id ?? user.pk,
|
|
1743
|
-
tagged_username: user.username,
|
|
1744
|
-
tagged_full_name: user.full_name
|
|
1745
|
-
};
|
|
1746
|
-
}
|
|
1747
1625
|
async *posts() {
|
|
1748
|
-
|
|
1749
|
-
yield* this.api.userTagged(this._taggedUserId);
|
|
1626
|
+
yield* this.api.userSaved();
|
|
1750
1627
|
}
|
|
1751
1628
|
};
|
|
1629
|
+
register(InstagramSavedExtractor.subcategory, InstagramSavedExtractor);
|
|
1630
|
+
//#endregion
|
|
1631
|
+
//#region src/instagram/extractors/stories.ts
|
|
1752
1632
|
var InstagramStoriesExtractor = class InstagramStoriesExtractor extends InstagramExtractor {
|
|
1753
1633
|
static subcategory = "stories";
|
|
1754
1634
|
static pattern = /^(?:https?:\/\/)?(?:www\.)?instagram\.com\/(?:stories\/(?:highlights\/(\d+)|([^/?#]+)(?:\/(\d+))?)|\/(aGlnaGxpZ2h0[^?#]+)(?:\?story_media_id=(\d+))?)/;
|
|
@@ -1804,28 +1684,9 @@ var InstagramStoriesExtractor = class InstagramStoriesExtractor extends Instagra
|
|
|
1804
1684
|
} else yield* reels;
|
|
1805
1685
|
}
|
|
1806
1686
|
};
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
subcategory = InstagramHighlightsExtractor.subcategory;
|
|
1811
|
-
constructor(opts) {
|
|
1812
|
-
super(opts);
|
|
1813
|
-
}
|
|
1814
|
-
static fromURL(url, opts) {
|
|
1815
|
-
const match = InstagramHighlightsExtractor.pattern.exec(url);
|
|
1816
|
-
if (!match) return null;
|
|
1817
|
-
return new InstagramHighlightsExtractor({
|
|
1818
|
-
...opts,
|
|
1819
|
-
url,
|
|
1820
|
-
match
|
|
1821
|
-
});
|
|
1822
|
-
}
|
|
1823
|
-
async *posts() {
|
|
1824
|
-
const screenName = (this.groups[0] ?? "").replace(/^\//, "");
|
|
1825
|
-
const uid = await this.api.userId(screenName);
|
|
1826
|
-
yield* this.api.highlightsMedia(uid);
|
|
1827
|
-
}
|
|
1828
|
-
};
|
|
1687
|
+
register(InstagramStoriesExtractor.subcategory, InstagramStoriesExtractor);
|
|
1688
|
+
//#endregion
|
|
1689
|
+
//#region src/instagram/extractors/tag.ts
|
|
1829
1690
|
var InstagramTagExtractor = class InstagramTagExtractor extends InstagramExtractor {
|
|
1830
1691
|
static subcategory = "tag";
|
|
1831
1692
|
static pattern = re(BASE_RE, /\/explore\/tags\/([^/?#]+)/);
|
|
@@ -1851,94 +1712,219 @@ var InstagramTagExtractor = class InstagramTagExtractor extends InstagramExtract
|
|
|
1851
1712
|
yield* this.api.tagsMedia(decodeURIComponent(tag));
|
|
1852
1713
|
}
|
|
1853
1714
|
};
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1715
|
+
register(InstagramTagExtractor.subcategory, InstagramTagExtractor);
|
|
1716
|
+
//#endregion
|
|
1717
|
+
//#region src/instagram/extractors/tagged.ts
|
|
1718
|
+
var InstagramTaggedExtractor = class InstagramTaggedExtractor extends InstagramExtractor {
|
|
1719
|
+
static subcategory = "tagged";
|
|
1720
|
+
static pattern = re(BASE_RE, /(\/[^/?#]+)\/tagged/);
|
|
1721
|
+
subcategory = InstagramTaggedExtractor.subcategory;
|
|
1722
|
+
_taggedUserId = "";
|
|
1858
1723
|
constructor(opts) {
|
|
1859
1724
|
super(opts);
|
|
1860
1725
|
}
|
|
1861
1726
|
static fromURL(url, opts) {
|
|
1862
|
-
const match =
|
|
1727
|
+
const match = InstagramTaggedExtractor.pattern.exec(url);
|
|
1863
1728
|
if (!match) return null;
|
|
1864
|
-
return new
|
|
1729
|
+
return new InstagramTaggedExtractor({
|
|
1865
1730
|
...opts,
|
|
1866
1731
|
url,
|
|
1867
1732
|
match
|
|
1868
1733
|
});
|
|
1869
1734
|
}
|
|
1870
|
-
async
|
|
1735
|
+
async metadata() {
|
|
1871
1736
|
const screenName = (this.groups[0] ?? "").replace(/^\//, "");
|
|
1872
1737
|
let user;
|
|
1873
|
-
if (screenName.startsWith("id:"))
|
|
1874
|
-
|
|
1875
|
-
|
|
1738
|
+
if (screenName.startsWith("id:")) {
|
|
1739
|
+
this._taggedUserId = screenName.slice(3);
|
|
1740
|
+
user = await this.api.userById(screenName.slice(3));
|
|
1741
|
+
} else {
|
|
1742
|
+
this._taggedUserId = await this.api.userId(screenName);
|
|
1743
|
+
user = await this.api.userByScreenName(screenName);
|
|
1744
|
+
}
|
|
1745
|
+
return {
|
|
1746
|
+
tagged_owner_id: user.id ?? user.pk,
|
|
1747
|
+
tagged_username: user.username,
|
|
1748
|
+
tagged_full_name: user.full_name
|
|
1749
|
+
};
|
|
1750
|
+
}
|
|
1751
|
+
async *posts() {
|
|
1752
|
+
if (!this._taggedUserId) await this.metadata();
|
|
1753
|
+
yield* this.api.userTagged(this._taggedUserId);
|
|
1876
1754
|
}
|
|
1877
|
-
async *posts() {}
|
|
1878
1755
|
};
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1756
|
+
register(InstagramTaggedExtractor.subcategory, InstagramTaggedExtractor);
|
|
1757
|
+
//#endregion
|
|
1758
|
+
//#region src/instagram/extractors/user.ts
|
|
1759
|
+
var InstagramUserExtractor = class InstagramUserExtractor extends InstagramExtractor {
|
|
1760
|
+
static subcategory = "user";
|
|
1761
|
+
static pattern = re(BASE_RE, /(\/[^/?#]+)\/?(?:$|[?#])/);
|
|
1762
|
+
subcategory = InstagramUserExtractor.subcategory;
|
|
1883
1763
|
constructor(opts) {
|
|
1884
1764
|
super(opts);
|
|
1885
1765
|
}
|
|
1886
1766
|
static fromURL(url, opts) {
|
|
1887
|
-
const match =
|
|
1767
|
+
const match = InstagramUserExtractor.pattern.exec(url);
|
|
1888
1768
|
if (!match) return null;
|
|
1889
|
-
return new
|
|
1769
|
+
return new InstagramUserExtractor({
|
|
1890
1770
|
...opts,
|
|
1891
1771
|
url,
|
|
1892
1772
|
match
|
|
1893
1773
|
});
|
|
1894
1774
|
}
|
|
1895
|
-
async *
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
const
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1775
|
+
async *items() {
|
|
1776
|
+
await this.login();
|
|
1777
|
+
const userPath = this.groups[0] ?? "/";
|
|
1778
|
+
const base = `${this.root}${userPath}/`;
|
|
1779
|
+
const storiesUrl = `${this.root}/stories/${userPath.slice(1)}/`;
|
|
1780
|
+
const include = this._cfg("include", ["posts"]);
|
|
1781
|
+
const categories = include === "all" ? [
|
|
1782
|
+
"posts",
|
|
1783
|
+
"reels",
|
|
1784
|
+
"tagged",
|
|
1785
|
+
"stories",
|
|
1786
|
+
"highlights",
|
|
1787
|
+
"info",
|
|
1788
|
+
"avatar"
|
|
1789
|
+
] : typeof include === "string" ? include.replace(/\s+/g, "").split(",") : include;
|
|
1790
|
+
const urls = {
|
|
1791
|
+
info: `${base}info/`,
|
|
1792
|
+
avatar: `${base}avatar/`,
|
|
1793
|
+
stories: storiesUrl,
|
|
1794
|
+
highlights: `${base}highlights/`,
|
|
1795
|
+
posts: `${base}posts/`,
|
|
1796
|
+
reels: `${base}reels/`,
|
|
1797
|
+
tagged: `${base}tagged/`
|
|
1904
1798
|
};
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
code = pk;
|
|
1799
|
+
for (const cat of categories) {
|
|
1800
|
+
const cls = get(cat);
|
|
1801
|
+
const url = urls[cat];
|
|
1802
|
+
if (cls && url) yield queue(url, { _extractor: cls });
|
|
1803
|
+
else this.log.warn(`Invalid include '${cat}'`);
|
|
1911
1804
|
}
|
|
1912
|
-
yield {
|
|
1913
|
-
pk,
|
|
1914
|
-
code,
|
|
1915
|
-
user,
|
|
1916
|
-
caption: null,
|
|
1917
|
-
like_count: 0,
|
|
1918
|
-
image_versions2: { candidates: [avatar] }
|
|
1919
|
-
};
|
|
1920
1805
|
}
|
|
1806
|
+
async *posts() {}
|
|
1921
1807
|
};
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
}
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1808
|
+
register(InstagramUserExtractor.subcategory, InstagramUserExtractor);
|
|
1809
|
+
//#endregion
|
|
1810
|
+
//#region src/fetcher.ts
|
|
1811
|
+
/** Build URL with query params appended as URLSearchParams. */
|
|
1812
|
+
function buildUrl(base, params) {
|
|
1813
|
+
if (!params) return base;
|
|
1814
|
+
const cleaned = {};
|
|
1815
|
+
for (const [k, v] of Object.entries(params)) if (v != null) cleaned[k] = String(v);
|
|
1816
|
+
const entries = Object.entries(cleaned);
|
|
1817
|
+
if (entries.length === 0) return base;
|
|
1818
|
+
const qs = new URLSearchParams(entries).toString();
|
|
1819
|
+
return `${base}${base.includes("?") ? "&" : "?"}${qs}`;
|
|
1820
|
+
}
|
|
1821
|
+
/** Merge cookie strings with append semantics: a=1 + b=2 → a=1; b=2 */
|
|
1822
|
+
function mergeCookie(base, extra) {
|
|
1823
|
+
if (!base) return extra;
|
|
1824
|
+
return `${base}; ${extra}`;
|
|
1825
|
+
}
|
|
1826
|
+
/** Extract csrftoken value from a Cookie header string. */
|
|
1827
|
+
function extractCsrf(cookies) {
|
|
1828
|
+
return cookies.match(/(?:^|;\s*)csrftoken=([^;]+)/)?.[1] ?? "";
|
|
1829
|
+
}
|
|
1830
|
+
/** Convert fetch Headers to a plain Record. */
|
|
1831
|
+
function headersToRecord(headers) {
|
|
1832
|
+
const rec = {};
|
|
1833
|
+
headers.forEach((v, k) => {
|
|
1834
|
+
rec[k] = v;
|
|
1835
|
+
});
|
|
1836
|
+
return rec;
|
|
1837
|
+
}
|
|
1838
|
+
/** Read response body according to the requested type. */
|
|
1839
|
+
async function readBody(resp, responseType) {
|
|
1840
|
+
switch (responseType) {
|
|
1841
|
+
case "arraybuffer": {
|
|
1842
|
+
const buf = await resp.arrayBuffer();
|
|
1843
|
+
return Buffer.from(buf);
|
|
1844
|
+
}
|
|
1845
|
+
case "text": return resp.text();
|
|
1846
|
+
default: return resp.json();
|
|
1940
1847
|
}
|
|
1941
|
-
}
|
|
1848
|
+
}
|
|
1849
|
+
/** Serialize a request body value for fetch. */
|
|
1850
|
+
function serializeBody(data) {
|
|
1851
|
+
if (data == null) return void 0;
|
|
1852
|
+
if (typeof data === "string") return data;
|
|
1853
|
+
if (data instanceof URLSearchParams) return data;
|
|
1854
|
+
return JSON.stringify(data);
|
|
1855
|
+
}
|
|
1856
|
+
const UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
|
|
1857
|
+
/**
|
|
1858
|
+
* Create a platform-agnostic HttpClient backed by native ``fetch``.
|
|
1859
|
+
*
|
|
1860
|
+
* Zero dependencies — works in Node.js 18+, browsers, Deno, and Edge.
|
|
1861
|
+
*
|
|
1862
|
+
* @example Plain (no cookies)
|
|
1863
|
+
* ```ts
|
|
1864
|
+
* const http = createFetchHttpClient()
|
|
1865
|
+
* ```
|
|
1866
|
+
*
|
|
1867
|
+
* @example With static cookies (CLI session mode)
|
|
1868
|
+
* ```ts
|
|
1869
|
+
* const http = createFetchHttpClient({ cookie: 'sessionid=abc; csrftoken=xyz' })
|
|
1870
|
+
* ```
|
|
1871
|
+
*
|
|
1872
|
+
* @example With cookie jar (anonymous session)
|
|
1873
|
+
* ```ts
|
|
1874
|
+
* const jar = createCookieJar()
|
|
1875
|
+
* const http = createFetchHttpClient({
|
|
1876
|
+
* cookieProvider: () => jar.getCookieHeader(),
|
|
1877
|
+
* onResponse: (headers) => jar.setFromResponse(headers),
|
|
1878
|
+
* })
|
|
1879
|
+
* ```
|
|
1880
|
+
*/
|
|
1881
|
+
function createFetchHttpClient(opts = {}) {
|
|
1882
|
+
const { cookie, cookieProvider, userAgent = UA, timeout = 3e4, onResponse } = opts;
|
|
1883
|
+
return { async request(config) {
|
|
1884
|
+
const method = config.method ?? "GET";
|
|
1885
|
+
const url = buildUrl(config.url, config.params);
|
|
1886
|
+
const headers = new Headers(config.headers);
|
|
1887
|
+
const reqCookie = cookieProvider?.() ?? cookie;
|
|
1888
|
+
if (reqCookie) {
|
|
1889
|
+
const existing = headers.get("Cookie");
|
|
1890
|
+
headers.set("Cookie", existing ? mergeCookie(reqCookie, existing) : reqCookie);
|
|
1891
|
+
}
|
|
1892
|
+
if (!headers.has("User-Agent")) headers.set("User-Agent", userAgent);
|
|
1893
|
+
const body = serializeBody(config.data);
|
|
1894
|
+
if (typeof body === "string" && !headers.has("Content-Type")) headers.set("Content-Type", "application/json");
|
|
1895
|
+
let controller = null;
|
|
1896
|
+
let timer = null;
|
|
1897
|
+
let signal = config.signal ?? null;
|
|
1898
|
+
const timeoutMs = config.timeout ?? timeout;
|
|
1899
|
+
if (!signal) {
|
|
1900
|
+
controller = new AbortController();
|
|
1901
|
+
timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1902
|
+
signal = controller.signal;
|
|
1903
|
+
}
|
|
1904
|
+
try {
|
|
1905
|
+
const resp = await fetch(url, {
|
|
1906
|
+
method,
|
|
1907
|
+
headers,
|
|
1908
|
+
body,
|
|
1909
|
+
signal
|
|
1910
|
+
});
|
|
1911
|
+
onResponse?.(headersToRecord(resp.headers));
|
|
1912
|
+
const data = await readBody(resp, config.responseType);
|
|
1913
|
+
return {
|
|
1914
|
+
status: resp.status,
|
|
1915
|
+
data,
|
|
1916
|
+
headers: headersToRecord(resp.headers),
|
|
1917
|
+
url: resp.url
|
|
1918
|
+
};
|
|
1919
|
+
} catch (err) {
|
|
1920
|
+
if (controller?.signal.aborted && !config.signal?.aborted) throw new Error(`Request timeout after ${timeoutMs}ms: ${url}`);
|
|
1921
|
+
if (String(err).includes("too many redirect")) throw new Error("Too many redirects — session may be expired or invalid. Export a fresh session from your browser.");
|
|
1922
|
+
throw err;
|
|
1923
|
+
} finally {
|
|
1924
|
+
if (timer) clearTimeout(timer);
|
|
1925
|
+
}
|
|
1926
|
+
} };
|
|
1927
|
+
}
|
|
1942
1928
|
//#endregion
|
|
1943
1929
|
//#region src/sdk.ts
|
|
1944
1930
|
var InstagramSDK = class {
|
|
@@ -1947,8 +1933,8 @@ var InstagramSDK = class {
|
|
|
1947
1933
|
log;
|
|
1948
1934
|
config;
|
|
1949
1935
|
_csrfToken;
|
|
1950
|
-
constructor(opts) {
|
|
1951
|
-
this.http = opts.http;
|
|
1936
|
+
constructor(opts = {}) {
|
|
1937
|
+
this.http = opts.http ?? createFetchHttpClient();
|
|
1952
1938
|
this.storage = opts.storage ?? void 0;
|
|
1953
1939
|
this.log = opts.log ?? noopLogger;
|
|
1954
1940
|
this.config = new ConfigManager();
|
|
@@ -2124,10 +2110,40 @@ Object.defineProperty(exports, "Job", {
|
|
|
2124
2110
|
return Job;
|
|
2125
2111
|
}
|
|
2126
2112
|
});
|
|
2127
|
-
Object.defineProperty(exports, "
|
|
2113
|
+
Object.defineProperty(exports, "_RESET", {
|
|
2128
2114
|
enumerable: true,
|
|
2129
2115
|
get: function() {
|
|
2130
|
-
return
|
|
2116
|
+
return _RESET;
|
|
2117
|
+
}
|
|
2118
|
+
});
|
|
2119
|
+
Object.defineProperty(exports, "_YELLOW", {
|
|
2120
|
+
enumerable: true,
|
|
2121
|
+
get: function() {
|
|
2122
|
+
return _YELLOW;
|
|
2123
|
+
}
|
|
2124
|
+
});
|
|
2125
|
+
Object.defineProperty(exports, "b", {
|
|
2126
|
+
enumerable: true,
|
|
2127
|
+
get: function() {
|
|
2128
|
+
return b;
|
|
2129
|
+
}
|
|
2130
|
+
});
|
|
2131
|
+
Object.defineProperty(exports, "c", {
|
|
2132
|
+
enumerable: true,
|
|
2133
|
+
get: function() {
|
|
2134
|
+
return c;
|
|
2135
|
+
}
|
|
2136
|
+
});
|
|
2137
|
+
Object.defineProperty(exports, "createFetchHttpClient", {
|
|
2138
|
+
enumerable: true,
|
|
2139
|
+
get: function() {
|
|
2140
|
+
return createFetchHttpClient;
|
|
2141
|
+
}
|
|
2142
|
+
});
|
|
2143
|
+
Object.defineProperty(exports, "dim", {
|
|
2144
|
+
enumerable: true,
|
|
2145
|
+
get: function() {
|
|
2146
|
+
return dim;
|
|
2131
2147
|
}
|
|
2132
2148
|
});
|
|
2133
2149
|
Object.defineProperty(exports, "directory", {
|
|
@@ -2160,6 +2176,12 @@ Object.defineProperty(exports, "extractAudio", {
|
|
|
2160
2176
|
return extractAudio;
|
|
2161
2177
|
}
|
|
2162
2178
|
});
|
|
2179
|
+
Object.defineProperty(exports, "extractCsrf", {
|
|
2180
|
+
enumerable: true,
|
|
2181
|
+
get: function() {
|
|
2182
|
+
return extractCsrf;
|
|
2183
|
+
}
|
|
2184
|
+
});
|
|
2163
2185
|
Object.defineProperty(exports, "extractTaggedUsers", {
|
|
2164
2186
|
enumerable: true,
|
|
2165
2187
|
get: function() {
|
|
@@ -2172,6 +2194,12 @@ Object.defineProperty(exports, "findTags", {
|
|
|
2172
2194
|
return findTags;
|
|
2173
2195
|
}
|
|
2174
2196
|
});
|
|
2197
|
+
Object.defineProperty(exports, "g", {
|
|
2198
|
+
enumerable: true,
|
|
2199
|
+
get: function() {
|
|
2200
|
+
return g;
|
|
2201
|
+
}
|
|
2202
|
+
});
|
|
2175
2203
|
Object.defineProperty(exports, "idFromShortcode", {
|
|
2176
2204
|
enumerable: true,
|
|
2177
2205
|
get: function() {
|
|
@@ -2190,6 +2218,12 @@ Object.defineProperty(exports, "noopLogger", {
|
|
|
2190
2218
|
return noopLogger;
|
|
2191
2219
|
}
|
|
2192
2220
|
});
|
|
2221
|
+
Object.defineProperty(exports, "pad", {
|
|
2222
|
+
enumerable: true,
|
|
2223
|
+
get: function() {
|
|
2224
|
+
return pad;
|
|
2225
|
+
}
|
|
2226
|
+
});
|
|
2193
2227
|
Object.defineProperty(exports, "parseInt", {
|
|
2194
2228
|
enumerable: true,
|
|
2195
2229
|
get: function() {
|