@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 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 ADDED
@@ -0,0 +1,401 @@
1
+ # @vicociv/instaloader
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
+
12
+ TypeScript port of [instaloader](https://github.com/instaloader/instaloader) - Download Instagram content (posts, stories, profiles) with metadata.
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install @vicociv/instaloader
18
+ ```
19
+
20
+ **Requirements:** Node.js >= 18.0.0
21
+
22
+ ## Quick Start
23
+
24
+ ```typescript
25
+ import { Instaloader, Profile, Post, Hashtag } from '@vicociv/instaloader';
26
+
27
+ const L = new Instaloader();
28
+
29
+ // Get a post by shortcode (works without login)
30
+ const post = await L.getPost('DSsaqgbkhAd');
31
+ console.log(post.caption);
32
+ console.log(post.typename); // 'GraphImage' | 'GraphVideo' | 'GraphSidecar'
33
+
34
+ // Get a profile
35
+ const profile = await L.getProfile('instagram');
36
+ console.log(await profile.getFollowers()); // follower count
37
+
38
+ // Iterate posts
39
+ for await (const post of profile.getPosts()) {
40
+ console.log(post.shortcode, post.likes);
41
+ }
42
+ ```
43
+
44
+ ## Authentication
45
+
46
+ Most operations work without login. However, login is required for:
47
+ - Accessing private profiles
48
+ - Viewing stories and highlights
49
+ - Saved posts
50
+ - Some rate-limited operations
51
+
52
+ ```typescript
53
+ // Login with credentials
54
+ await L.login('username', 'password');
55
+
56
+ // Handle 2FA if required
57
+ try {
58
+ await L.login('username', 'password');
59
+ } catch (e) {
60
+ if (e instanceof TwoFactorAuthRequiredException) {
61
+ await L.twoFactorLogin('123456');
62
+ }
63
+ }
64
+
65
+ // Save/load session
66
+ await L.saveSessionToFile('session.json');
67
+ await L.loadSessionFromFile('username', 'session.json');
68
+
69
+ // Test if session is valid
70
+ const username = await L.testLogin(); // returns username or null
71
+ ```
72
+
73
+ ## API Reference
74
+
75
+ ### Instaloader
76
+
77
+ Main class for downloading Instagram content.
78
+
79
+ ```typescript
80
+ const L = new Instaloader(options?: InstaloaderOptions);
81
+ ```
82
+
83
+ **Options:**
84
+ | Option | Type | Default | Description |
85
+ |--------|------|---------|-------------|
86
+ | `sleep` | boolean | true | Enable rate limiting delays |
87
+ | `quiet` | boolean | false | Suppress console output |
88
+ | `userAgent` | string | - | Custom user agent |
89
+ | `downloadPictures` | boolean | true | Download images |
90
+ | `downloadVideos` | boolean | true | Download videos |
91
+ | `saveMetadata` | boolean | true | Save JSON metadata |
92
+
93
+ #### Get Content
94
+
95
+ ```typescript
96
+ // Get profile/post/hashtag
97
+ const profile = await L.getProfile('username');
98
+ const post = await L.getPost('shortcode');
99
+ const hashtag = await L.getHashtag('nature');
100
+ ```
101
+
102
+ #### Download Content
103
+
104
+ ```typescript
105
+ // Download a single post
106
+ await L.downloadPost(post, 'target_folder');
107
+
108
+ // Download profile posts
109
+ await L.downloadProfile(profile, {
110
+ maxCount: 10,
111
+ fastUpdate: true, // stop at already downloaded
112
+ postFilter: (p) => p.likes > 100,
113
+ });
114
+
115
+ // Download hashtag posts
116
+ await L.downloadHashtag(hashtag, { maxCount: 50 });
117
+ ```
118
+
119
+ ### Profile
120
+
121
+ Represents an Instagram user profile.
122
+
123
+ ```typescript
124
+ import { Profile } from '@vicociv/instaloader';
125
+
126
+ // Create from username
127
+ const profile = await Profile.fromUsername(context, 'instagram');
128
+
129
+ // Properties (sync)
130
+ profile.username // lowercase username
131
+ profile.userid // numeric user ID
132
+ profile.is_private // is private account
133
+ profile.followed_by_viewer
134
+ profile.follows_viewer
135
+
136
+ // Properties (async - may fetch metadata)
137
+ await profile.getFollowers()
138
+ await profile.getFollowees()
139
+ await profile.getMediacount()
140
+ await profile.getBiography()
141
+ await profile.getFullName()
142
+ await profile.getProfilePicUrl()
143
+ await profile.getIsVerified()
144
+ await profile.getExternalUrl()
145
+ ```
146
+
147
+ #### Iterate Posts
148
+
149
+ ```typescript
150
+ // All posts
151
+ for await (const post of profile.getPosts()) {
152
+ console.log(post.shortcode);
153
+ }
154
+
155
+ // Saved posts (requires login as owner)
156
+ for await (const post of profile.getSavedPosts()) {
157
+ console.log(post.shortcode);
158
+ }
159
+
160
+ // Tagged posts
161
+ for await (const post of profile.getTaggedPosts()) {
162
+ console.log(post.shortcode);
163
+ }
164
+ ```
165
+
166
+ ### Post
167
+
168
+ Represents an Instagram post.
169
+
170
+ ```typescript
171
+ import { Post } from '@vicociv/instaloader';
172
+
173
+ // Create from shortcode
174
+ const post = await Post.fromShortcode(context, 'ABC123');
175
+
176
+ // Properties
177
+ post.shortcode // URL shortcode
178
+ post.mediaid // BigInt media ID
179
+ post.typename // 'GraphImage' | 'GraphVideo' | 'GraphSidecar'
180
+ post.url // image/thumbnail URL
181
+ post.video_url // video URL (if video)
182
+ post.is_video // boolean
183
+ post.caption // caption text
184
+ post.caption_hashtags // ['tag1', 'tag2']
185
+ post.caption_mentions // ['user1', 'user2']
186
+ post.likes // like count
187
+ post.comments // comment count
188
+ post.date_utc // Date object
189
+ post.tagged_users // tagged usernames
190
+
191
+ // Get owner profile
192
+ const owner = await post.getOwnerProfile();
193
+
194
+ // Sidecar (carousel) posts
195
+ for (const node of post.getSidecarNodes()) {
196
+ console.log(node.is_video, node.display_url, node.video_url);
197
+ }
198
+ ```
199
+
200
+ ### Hashtag
201
+
202
+ Represents an Instagram hashtag.
203
+
204
+ ```typescript
205
+ import { Hashtag } from '@vicociv/instaloader';
206
+
207
+ const hashtag = await Hashtag.fromName(context, 'photography');
208
+
209
+ // Properties
210
+ hashtag.name // lowercase name (without #)
211
+ await hashtag.getHashtagId()
212
+ await hashtag.getMediacount()
213
+ await hashtag.getProfilePicUrl()
214
+
215
+ // Get posts (use getPostsResumable for reliable pagination)
216
+ const iterator = hashtag.getPostsResumable();
217
+ for await (const post of iterator) {
218
+ console.log(post.shortcode);
219
+ }
220
+
221
+ // Top posts
222
+ for await (const post of hashtag.getTopPosts()) {
223
+ console.log(post.shortcode);
224
+ }
225
+ ```
226
+
227
+ ### Story & StoryItem
228
+
229
+ ```typescript
230
+ import { Story, StoryItem } from '@vicociv/instaloader';
231
+
232
+ // Story contains multiple StoryItems
233
+ for await (const item of story.getItems()) {
234
+ console.log(item.mediaid);
235
+ console.log(item.typename); // 'GraphStoryImage' | 'GraphStoryVideo'
236
+ console.log(item.url); // image URL
237
+ console.log(item.video_url); // video URL (if video)
238
+ console.log(item.date_utc);
239
+ console.log(item.expiring_utc);
240
+ }
241
+ ```
242
+
243
+ ### Highlight
244
+
245
+ Extends Story for profile highlights.
246
+
247
+ ```typescript
248
+ import { Highlight } from '@vicociv/instaloader';
249
+
250
+ highlight.title // highlight title
251
+ highlight.cover_url // cover image URL
252
+
253
+ for await (const item of highlight.getItems()) {
254
+ // ...
255
+ }
256
+ ```
257
+
258
+ ### TopSearchResults
259
+
260
+ Search Instagram for profiles, hashtags, and locations.
261
+
262
+ ```typescript
263
+ import { TopSearchResults } from '@vicociv/instaloader';
264
+
265
+ const search = new TopSearchResults(context, 'query');
266
+
267
+ for await (const profile of search.getProfiles()) {
268
+ console.log(profile.username);
269
+ }
270
+
271
+ for await (const hashtag of search.getHashtags()) {
272
+ console.log(hashtag.name);
273
+ }
274
+
275
+ for await (const location of search.getLocations()) {
276
+ console.log(location.name, location.lat, location.lng);
277
+ }
278
+ ```
279
+
280
+ ### NodeIterator
281
+
282
+ Paginated iterator with resume support.
283
+
284
+ ```typescript
285
+ import { NodeIterator, FrozenNodeIterator } from '@vicociv/instaloader';
286
+
287
+ const iterator = profile.getPosts();
288
+
289
+ // Iterate
290
+ for await (const post of iterator) {
291
+ // Save state for resume
292
+ const state = iterator.freeze();
293
+ fs.writeFileSync('state.json', JSON.stringify(state));
294
+ break;
295
+ }
296
+
297
+ // Resume later
298
+ const savedState = JSON.parse(fs.readFileSync('state.json', 'utf-8'));
299
+ const frozen = new FrozenNodeIterator(savedState);
300
+ const resumed = frozen.thaw(context);
301
+ ```
302
+
303
+ ### Helper Functions
304
+
305
+ ```typescript
306
+ import {
307
+ shortcodeToMediaid,
308
+ mediaidToShortcode,
309
+ extractHashtags,
310
+ extractMentions,
311
+ parseInstagramUrl,
312
+ extractShortcode,
313
+ extractUsername,
314
+ extractHashtagFromUrl,
315
+ getJsonStructure,
316
+ loadStructure,
317
+ } from '@vicociv/instaloader';
318
+
319
+ // Convert between shortcode and mediaid
320
+ const mediaid = shortcodeToMediaid('ABC123'); // BigInt
321
+ const shortcode = mediaidToShortcode(mediaid); // string
322
+
323
+ // Extract from text
324
+ extractHashtags('Hello #world #test'); // ['world', 'test']
325
+ extractMentions('Hello @user1 @user2'); // ['user1', 'user2']
326
+
327
+ // Parse Instagram URLs
328
+ parseInstagramUrl('https://www.instagram.com/p/DSsaqgbkhAd/')
329
+ // => { type: 'post', shortcode: 'DSsaqgbkhAd' }
330
+
331
+ parseInstagramUrl('https://www.instagram.com/instagram/')
332
+ // => { type: 'profile', username: 'instagram' }
333
+
334
+ parseInstagramUrl('https://www.instagram.com/explore/tags/nature/')
335
+ // => { type: 'hashtag', hashtag: 'nature' }
336
+
337
+ // Extract specific identifiers from URLs
338
+ extractShortcode('https://www.instagram.com/p/DSsaqgbkhAd/?img_index=1')
339
+ // => 'DSsaqgbkhAd'
340
+
341
+ extractUsername('https://www.instagram.com/instagram/')
342
+ // => 'instagram'
343
+
344
+ extractHashtagFromUrl('https://www.instagram.com/explore/tags/nature/')
345
+ // => 'nature'
346
+
347
+ // JSON serialization
348
+ const json = getJsonStructure(post);
349
+ const restored = loadStructure(context, json);
350
+ ```
351
+
352
+ ### InstaloaderContext
353
+
354
+ Low-level API for direct Instagram requests. Usually accessed via `Instaloader.context`.
355
+
356
+ ```typescript
357
+ const context = L.context;
358
+
359
+ // Check login status
360
+ context.is_logged_in // boolean
361
+ context.username // string | null
362
+
363
+ // Make GraphQL queries
364
+ const data = await context.graphql_query(queryHash, variables);
365
+ const data = await context.doc_id_graphql_query(docId, variables);
366
+
367
+ // Generic JSON request
368
+ const data = await context.getJson('path', params);
369
+
370
+ // iPhone API
371
+ const data = await context.get_iphone_json('api/v1/...', params);
372
+ ```
373
+
374
+ ### Exceptions
375
+
376
+ ```typescript
377
+ import {
378
+ InstaloaderException, // Base exception
379
+ LoginException, // Login failed
380
+ LoginRequiredException, // Action requires login
381
+ TwoFactorAuthRequiredException,
382
+ BadCredentialsException, // Wrong password
383
+ ConnectionException, // Network error
384
+ TooManyRequestsException, // Rate limited (429)
385
+ ProfileNotExistsException, // Profile not found
386
+ QueryReturnedNotFoundException,
387
+ QueryReturnedForbiddenException,
388
+ PostChangedException, // Post changed during download
389
+ InvalidArgumentException,
390
+ } from '@vicociv/instaloader';
391
+ ```
392
+
393
+ ## License
394
+
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
@@ -474,6 +474,8 @@ interface FrozenIteratorState {
474
474
  declare function defaultUserAgent(): string;
475
475
  /**
476
476
  * Returns default iPhone headers for API requests.
477
+ * Note: x-pigeon-session-id and x-ig-connection-speed are randomized per call
478
+ * to make each request appear as a different client (important for bypassing rate limits).
477
479
  */
478
480
  declare function defaultIphoneHeaders(): HttpHeaders;
479
481
  /**
@@ -541,7 +543,7 @@ declare class InstaloaderContext {
541
543
  readonly quiet: boolean;
542
544
  readonly iphoneSupport: boolean;
543
545
  readonly fatalStatusCodes: number[];
544
- private _cookieJar;
546
+ private _cookieStore;
545
547
  private _csrfToken;
546
548
  private _username;
547
549
  private _userId;
@@ -608,7 +610,7 @@ declare class InstaloaderContext {
608
610
  */
609
611
  updateCookies(cookies: CookieData): void;
610
612
  /**
611
- * Build cookie header string from cookie jar.
613
+ * Build cookie header string from cookie store.
612
614
  */
613
615
  private _getCookieHeader;
614
616
  /**
@@ -627,17 +629,24 @@ declare class InstaloaderContext {
627
629
  usePost?: boolean;
628
630
  attempt?: number;
629
631
  headers?: HttpHeaders;
632
+ /** If true, refresh dynamic headers (timestamp, session ID, speed) on each attempt */
633
+ refreshDynamicHeaders?: boolean;
630
634
  }): Promise<JsonObject>;
631
635
  /**
632
636
  * Do a GraphQL Query.
633
637
  */
634
638
  graphql_query(queryHash: string, variables: JsonObject, referer?: string): Promise<JsonObject>;
635
639
  /**
636
- * Do a doc_id-based GraphQL Query using POST.
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.
637
645
  */
638
646
  doc_id_graphql_query(docId: string, variables: JsonObject, referer?: string): Promise<JsonObject>;
639
647
  /**
640
648
  * JSON request to i.instagram.com.
649
+ * Each request uses fresh dynamic headers to appear as different clients.
641
650
  */
642
651
  get_iphone_json(path: string, params: JsonObject): Promise<JsonObject>;
643
652
  /**
@@ -964,6 +973,65 @@ declare function extractHashtags(text: string): string[];
964
973
  * Extract @mentions from text.
965
974
  */
966
975
  declare function extractMentions(text: string): string[];
976
+ /**
977
+ * Result of parsing an Instagram URL
978
+ */
979
+ interface ParsedInstagramUrl {
980
+ type: 'post' | 'profile' | 'hashtag' | 'unknown';
981
+ shortcode?: string;
982
+ username?: string;
983
+ hashtag?: string;
984
+ }
985
+ /**
986
+ * Parse an Instagram URL to extract the type and identifier.
987
+ *
988
+ * @param url - The Instagram URL to parse
989
+ * @returns Parsed URL information
990
+ *
991
+ * @example
992
+ * parseInstagramUrl('https://www.instagram.com/p/DSsaqgbkhAd/')
993
+ * // => { type: 'post', shortcode: 'DSsaqgbkhAd' }
994
+ *
995
+ * parseInstagramUrl('https://www.instagram.com/instagram/')
996
+ * // => { type: 'profile', username: 'instagram' }
997
+ *
998
+ * parseInstagramUrl('https://www.instagram.com/explore/tags/nature/')
999
+ * // => { type: 'hashtag', hashtag: 'nature' }
1000
+ */
1001
+ declare function parseInstagramUrl(url: string): ParsedInstagramUrl;
1002
+ /**
1003
+ * Extract shortcode from a post URL.
1004
+ *
1005
+ * @param url - The Instagram post URL
1006
+ * @returns The shortcode, or null if not found
1007
+ *
1008
+ * @example
1009
+ * extractShortcode('https://www.instagram.com/p/DSsaqgbkhAd/?img_index=1')
1010
+ * // => 'DSsaqgbkhAd'
1011
+ */
1012
+ declare function extractShortcode(url: string): string | null;
1013
+ /**
1014
+ * Extract username from a profile URL.
1015
+ *
1016
+ * @param url - The Instagram profile URL
1017
+ * @returns The username, or null if not found
1018
+ *
1019
+ * @example
1020
+ * extractUsername('https://www.instagram.com/instagram/')
1021
+ * // => 'instagram'
1022
+ */
1023
+ declare function extractUsername(url: string): string | null;
1024
+ /**
1025
+ * Extract hashtag from a hashtag URL.
1026
+ *
1027
+ * @param url - The Instagram hashtag URL
1028
+ * @returns The hashtag (without #), or null if not found
1029
+ *
1030
+ * @example
1031
+ * extractHashtagFromUrl('https://www.instagram.com/explore/tags/nature/')
1032
+ * // => 'nature'
1033
+ */
1034
+ declare function extractHashtagFromUrl(url: string): string | null;
967
1035
  /**
968
1036
  * Represents a comment on a post.
969
1037
  */
@@ -1661,4 +1729,4 @@ declare class Instaloader {
1661
1729
  getHashtag(name: string): Promise<Hashtag>;
1662
1730
  }
1663
1731
 
1664
- export { AbortDownloadException, BadCredentialsException, BadResponseException, CheckpointRequiredException, type CommentNode, ConnectionException, type CookieData, type EdgeConnection, type FrozenIteratorState, FrozenNodeIterator, type GraphQLResponse, Hashtag, type HashtagNode, Highlight, type HighlightNode, type HttpHeaders, type IPhoneHeaders, IPhoneSupportDisabledException, Instaloader, InstaloaderContext, type InstaloaderContextOptions, InstaloaderException, type InstaloaderOptions, InvalidArgumentException, InvalidIteratorException, type JsonExportable, type JsonObject, type JsonValue, type LocationNode, LoginException, LoginRequiredException, type LoginResponse, NodeIterator, type NodeIteratorOptions, type PageInfo, type PhoneVerificationSettings, Post, PostChangedException, PostComment, type PostCommentAnswer, type PostLocation, type PostNode, type PostSidecarNode, PrivateProfileNotFollowedException, Profile, ProfileHasNoPicsException, type ProfileNode, ProfileNotExistsException, QueryReturnedBadRequestException, QueryReturnedForbiddenException, QueryReturnedNotFoundException, type QueryTimestamps, RateController, type RequestOptions, type ResponseInfo, type ResumableIterationOptions, type ResumableIterationResult, type SessionData, SessionNotFoundException, Story, StoryItem, type StoryItemNode, NodeIterator as StructureNodeIterator, TooManyRequestsException, TopSearchResults, TwoFactorAuthRequiredException, type TwoFactorInfo, defaultIphoneHeaders, defaultUserAgent, extractHashtags, extractMentions, formatFilename, formatStringContainsKey, getConfigDir, getDefaultSessionFilename, getDefaultStampsFilename, getJsonStructure, loadStructure, mediaidToShortcode, resumableIteration, sanitizePath, shortcodeToMediaid };
1732
+ export { AbortDownloadException, BadCredentialsException, BadResponseException, CheckpointRequiredException, type CommentNode, ConnectionException, type CookieData, type EdgeConnection, type FrozenIteratorState, FrozenNodeIterator, type GraphQLResponse, Hashtag, type HashtagNode, Highlight, type HighlightNode, type HttpHeaders, type IPhoneHeaders, IPhoneSupportDisabledException, Instaloader, InstaloaderContext, type InstaloaderContextOptions, InstaloaderException, type InstaloaderOptions, InvalidArgumentException, InvalidIteratorException, type JsonExportable, type JsonObject, type JsonValue, type LocationNode, LoginException, LoginRequiredException, type LoginResponse, NodeIterator, type NodeIteratorOptions, type PageInfo, type ParsedInstagramUrl, type PhoneVerificationSettings, Post, PostChangedException, PostComment, type PostCommentAnswer, type PostLocation, type PostNode, type PostSidecarNode, PrivateProfileNotFollowedException, Profile, ProfileHasNoPicsException, type ProfileNode, ProfileNotExistsException, QueryReturnedBadRequestException, QueryReturnedForbiddenException, QueryReturnedNotFoundException, type QueryTimestamps, RateController, type RequestOptions, type ResponseInfo, type ResumableIterationOptions, type ResumableIterationResult, type SessionData, SessionNotFoundException, Story, StoryItem, type StoryItemNode, NodeIterator as StructureNodeIterator, TooManyRequestsException, TopSearchResults, TwoFactorAuthRequiredException, type TwoFactorInfo, defaultIphoneHeaders, defaultUserAgent, extractHashtagFromUrl, extractHashtags, extractMentions, extractShortcode, extractUsername, formatFilename, formatStringContainsKey, getConfigDir, getDefaultSessionFilename, getDefaultStampsFilename, getJsonStructure, loadStructure, mediaidToShortcode, parseInstagramUrl, resumableIteration, sanitizePath, shortcodeToMediaid };
package/dist/index.d.ts CHANGED
@@ -474,6 +474,8 @@ interface FrozenIteratorState {
474
474
  declare function defaultUserAgent(): string;
475
475
  /**
476
476
  * Returns default iPhone headers for API requests.
477
+ * Note: x-pigeon-session-id and x-ig-connection-speed are randomized per call
478
+ * to make each request appear as a different client (important for bypassing rate limits).
477
479
  */
478
480
  declare function defaultIphoneHeaders(): HttpHeaders;
479
481
  /**
@@ -541,7 +543,7 @@ declare class InstaloaderContext {
541
543
  readonly quiet: boolean;
542
544
  readonly iphoneSupport: boolean;
543
545
  readonly fatalStatusCodes: number[];
544
- private _cookieJar;
546
+ private _cookieStore;
545
547
  private _csrfToken;
546
548
  private _username;
547
549
  private _userId;
@@ -608,7 +610,7 @@ declare class InstaloaderContext {
608
610
  */
609
611
  updateCookies(cookies: CookieData): void;
610
612
  /**
611
- * Build cookie header string from cookie jar.
613
+ * Build cookie header string from cookie store.
612
614
  */
613
615
  private _getCookieHeader;
614
616
  /**
@@ -627,17 +629,24 @@ declare class InstaloaderContext {
627
629
  usePost?: boolean;
628
630
  attempt?: number;
629
631
  headers?: HttpHeaders;
632
+ /** If true, refresh dynamic headers (timestamp, session ID, speed) on each attempt */
633
+ refreshDynamicHeaders?: boolean;
630
634
  }): Promise<JsonObject>;
631
635
  /**
632
636
  * Do a GraphQL Query.
633
637
  */
634
638
  graphql_query(queryHash: string, variables: JsonObject, referer?: string): Promise<JsonObject>;
635
639
  /**
636
- * Do a doc_id-based GraphQL Query using POST.
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.
637
645
  */
638
646
  doc_id_graphql_query(docId: string, variables: JsonObject, referer?: string): Promise<JsonObject>;
639
647
  /**
640
648
  * JSON request to i.instagram.com.
649
+ * Each request uses fresh dynamic headers to appear as different clients.
641
650
  */
642
651
  get_iphone_json(path: string, params: JsonObject): Promise<JsonObject>;
643
652
  /**
@@ -964,6 +973,65 @@ declare function extractHashtags(text: string): string[];
964
973
  * Extract @mentions from text.
965
974
  */
966
975
  declare function extractMentions(text: string): string[];
976
+ /**
977
+ * Result of parsing an Instagram URL
978
+ */
979
+ interface ParsedInstagramUrl {
980
+ type: 'post' | 'profile' | 'hashtag' | 'unknown';
981
+ shortcode?: string;
982
+ username?: string;
983
+ hashtag?: string;
984
+ }
985
+ /**
986
+ * Parse an Instagram URL to extract the type and identifier.
987
+ *
988
+ * @param url - The Instagram URL to parse
989
+ * @returns Parsed URL information
990
+ *
991
+ * @example
992
+ * parseInstagramUrl('https://www.instagram.com/p/DSsaqgbkhAd/')
993
+ * // => { type: 'post', shortcode: 'DSsaqgbkhAd' }
994
+ *
995
+ * parseInstagramUrl('https://www.instagram.com/instagram/')
996
+ * // => { type: 'profile', username: 'instagram' }
997
+ *
998
+ * parseInstagramUrl('https://www.instagram.com/explore/tags/nature/')
999
+ * // => { type: 'hashtag', hashtag: 'nature' }
1000
+ */
1001
+ declare function parseInstagramUrl(url: string): ParsedInstagramUrl;
1002
+ /**
1003
+ * Extract shortcode from a post URL.
1004
+ *
1005
+ * @param url - The Instagram post URL
1006
+ * @returns The shortcode, or null if not found
1007
+ *
1008
+ * @example
1009
+ * extractShortcode('https://www.instagram.com/p/DSsaqgbkhAd/?img_index=1')
1010
+ * // => 'DSsaqgbkhAd'
1011
+ */
1012
+ declare function extractShortcode(url: string): string | null;
1013
+ /**
1014
+ * Extract username from a profile URL.
1015
+ *
1016
+ * @param url - The Instagram profile URL
1017
+ * @returns The username, or null if not found
1018
+ *
1019
+ * @example
1020
+ * extractUsername('https://www.instagram.com/instagram/')
1021
+ * // => 'instagram'
1022
+ */
1023
+ declare function extractUsername(url: string): string | null;
1024
+ /**
1025
+ * Extract hashtag from a hashtag URL.
1026
+ *
1027
+ * @param url - The Instagram hashtag URL
1028
+ * @returns The hashtag (without #), or null if not found
1029
+ *
1030
+ * @example
1031
+ * extractHashtagFromUrl('https://www.instagram.com/explore/tags/nature/')
1032
+ * // => 'nature'
1033
+ */
1034
+ declare function extractHashtagFromUrl(url: string): string | null;
967
1035
  /**
968
1036
  * Represents a comment on a post.
969
1037
  */
@@ -1661,4 +1729,4 @@ declare class Instaloader {
1661
1729
  getHashtag(name: string): Promise<Hashtag>;
1662
1730
  }
1663
1731
 
1664
- export { AbortDownloadException, BadCredentialsException, BadResponseException, CheckpointRequiredException, type CommentNode, ConnectionException, type CookieData, type EdgeConnection, type FrozenIteratorState, FrozenNodeIterator, type GraphQLResponse, Hashtag, type HashtagNode, Highlight, type HighlightNode, type HttpHeaders, type IPhoneHeaders, IPhoneSupportDisabledException, Instaloader, InstaloaderContext, type InstaloaderContextOptions, InstaloaderException, type InstaloaderOptions, InvalidArgumentException, InvalidIteratorException, type JsonExportable, type JsonObject, type JsonValue, type LocationNode, LoginException, LoginRequiredException, type LoginResponse, NodeIterator, type NodeIteratorOptions, type PageInfo, type PhoneVerificationSettings, Post, PostChangedException, PostComment, type PostCommentAnswer, type PostLocation, type PostNode, type PostSidecarNode, PrivateProfileNotFollowedException, Profile, ProfileHasNoPicsException, type ProfileNode, ProfileNotExistsException, QueryReturnedBadRequestException, QueryReturnedForbiddenException, QueryReturnedNotFoundException, type QueryTimestamps, RateController, type RequestOptions, type ResponseInfo, type ResumableIterationOptions, type ResumableIterationResult, type SessionData, SessionNotFoundException, Story, StoryItem, type StoryItemNode, NodeIterator as StructureNodeIterator, TooManyRequestsException, TopSearchResults, TwoFactorAuthRequiredException, type TwoFactorInfo, defaultIphoneHeaders, defaultUserAgent, extractHashtags, extractMentions, formatFilename, formatStringContainsKey, getConfigDir, getDefaultSessionFilename, getDefaultStampsFilename, getJsonStructure, loadStructure, mediaidToShortcode, resumableIteration, sanitizePath, shortcodeToMediaid };
1732
+ export { AbortDownloadException, BadCredentialsException, BadResponseException, CheckpointRequiredException, type CommentNode, ConnectionException, type CookieData, type EdgeConnection, type FrozenIteratorState, FrozenNodeIterator, type GraphQLResponse, Hashtag, type HashtagNode, Highlight, type HighlightNode, type HttpHeaders, type IPhoneHeaders, IPhoneSupportDisabledException, Instaloader, InstaloaderContext, type InstaloaderContextOptions, InstaloaderException, type InstaloaderOptions, InvalidArgumentException, InvalidIteratorException, type JsonExportable, type JsonObject, type JsonValue, type LocationNode, LoginException, LoginRequiredException, type LoginResponse, NodeIterator, type NodeIteratorOptions, type PageInfo, type ParsedInstagramUrl, type PhoneVerificationSettings, Post, PostChangedException, PostComment, type PostCommentAnswer, type PostLocation, type PostNode, type PostSidecarNode, PrivateProfileNotFollowedException, Profile, ProfileHasNoPicsException, type ProfileNode, ProfileNotExistsException, QueryReturnedBadRequestException, QueryReturnedForbiddenException, QueryReturnedNotFoundException, type QueryTimestamps, RateController, type RequestOptions, type ResponseInfo, type ResumableIterationOptions, type ResumableIterationResult, type SessionData, SessionNotFoundException, Story, StoryItem, type StoryItemNode, NodeIterator as StructureNodeIterator, TooManyRequestsException, TopSearchResults, TwoFactorAuthRequiredException, type TwoFactorInfo, defaultIphoneHeaders, defaultUserAgent, extractHashtagFromUrl, extractHashtags, extractMentions, extractShortcode, extractUsername, formatFilename, formatStringContainsKey, getConfigDir, getDefaultSessionFilename, getDefaultStampsFilename, getJsonStructure, loadStructure, mediaidToShortcode, parseInstagramUrl, resumableIteration, sanitizePath, shortcodeToMediaid };