@chilfish/gallery-dl-instagram 0.2.2 → 0.2.4

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 CHANGED
@@ -2968,7 +2968,7 @@ function useColor() {
2968
2968
  new Command();
2969
2969
  //#endregion
2970
2970
  //#region package.json
2971
- var version = "0.2.1";
2971
+ var version = "0.2.4";
2972
2972
  //#endregion
2973
2973
  //#region src/utils/id-codec.ts
2974
2974
  /**
@@ -3550,6 +3550,7 @@ function parsePostRest(post, cfg) {
3550
3550
  owner_id: owner.pk,
3551
3551
  username: owner.username ?? "",
3552
3552
  fullname: owner.full_name ?? "",
3553
+ user: owner,
3553
3554
  post_date: date,
3554
3555
  date,
3555
3556
  description: caption ? caption.text : "",
@@ -3595,7 +3596,7 @@ function parsePostRest(post, cfg) {
3595
3596
  if (post.music_metadata) {
3596
3597
  const info = post.music_metadata.music_info;
3597
3598
  if (info) {
3598
- const audio = extractAudio(post, data, { music_asset_info: info }, cfg);
3599
+ const audio = extractAudio(post, data, info, cfg);
3599
3600
  if (audio) {
3600
3601
  audio.num = items.length;
3601
3602
  data._files.push(audio);
@@ -3642,9 +3643,38 @@ function parseStoryRest(post, cfg) {
3642
3643
  const item = items[num];
3643
3644
  const media = parseMediaItem(item, post, cfg, num + 1);
3644
3645
  if (!media) continue;
3645
- extractTaggedUsers(item, media);
3646
+ const itemRec = item;
3647
+ extractTaggedUsers(itemRec, media);
3648
+ const musicStickers = itemRec.story_music_stickers;
3649
+ if (musicStickers?.[0]) {
3650
+ const audio = extractAudio(itemRec, data, musicStickers[0], cfg);
3651
+ if (audio) {
3652
+ audio.num = num + 1;
3653
+ if (musicStickers[0].attribution) audio.music_attribution = musicStickers[0].attribution;
3654
+ if (musicStickers[0].display_type) audio.music_display_type = musicStickers[0].display_type;
3655
+ data._files.push(audio);
3656
+ }
3657
+ }
3658
+ const linkStickers = itemRec.story_link_stickers;
3659
+ if (linkStickers?.[0]) {
3660
+ const sl = linkStickers[0].story_link;
3661
+ media.story_link_url = sl.url;
3662
+ media.story_link_display = sl.display_url;
3663
+ media.story_link_title = sl.link_title;
3664
+ media.story_link_type = sl.link_type;
3665
+ }
3646
3666
  data._files.push(media);
3647
3667
  }
3668
+ if (post.music_metadata) {
3669
+ const info = post.music_metadata.music_info;
3670
+ if (info) {
3671
+ const audio = extractAudio(post, data, info, cfg);
3672
+ if (audio) {
3673
+ audio.num = items.length;
3674
+ data._files.push(audio);
3675
+ }
3676
+ }
3677
+ }
3648
3678
  return data;
3649
3679
  }
3650
3680
  /** Parse a single media item (image/video) from a carousel or story. */
@@ -3756,11 +3786,16 @@ function extractAudio(src, dest, sticker, cfg) {
3756
3786
  const info = sticker.music_asset_info;
3757
3787
  if (!info) return null;
3758
3788
  const cinfo = sticker.music_consumption_info ?? info;
3759
- dest.audio_title = info.title;
3789
+ dest.audio_title = info.title ?? info.sanitized_title;
3790
+ dest.audio_subtitle = info.subtitle;
3760
3791
  dest.audio_duration = (info.duration_in_ms ?? 0) / 1e3;
3761
3792
  dest.audio_timestamps = info.highlight_start_times_in_ms;
3762
3793
  dest.audio_artist = info.display_artist ?? cinfo.display_artist;
3763
3794
  dest.audio_user = info.ig_artist ?? cinfo.ig_artist;
3795
+ dest.audio_has_lyrics = info.has_lyrics;
3796
+ dest.audio_is_explicit = info.is_explicit;
3797
+ dest.audio_cover_artwork_uri = info.cover_artwork_uri;
3798
+ dest.audio_cover_artwork_thumbnail_uri = info.cover_artwork_thumbnail_uri;
3764
3799
  const url = info.progressive_download_url;
3765
3800
  if (!url) return null;
3766
3801
  return {
@@ -3777,10 +3812,15 @@ function extractAudio(src, dest, sticker, cfg) {
3777
3812
  height_original: 0,
3778
3813
  tagged_users: [],
3779
3814
  audio_user: info.ig_artist ?? cinfo.ig_artist,
3780
- audio_title: info.title,
3815
+ audio_title: info.title ?? info.sanitized_title,
3816
+ audio_subtitle: info.subtitle,
3781
3817
  audio_artist: info.display_artist ?? cinfo.display_artist,
3782
3818
  audio_duration: (info.duration_in_ms ?? 0) / 1e3,
3783
- audio_timestamps: info.highlight_start_times_in_ms
3819
+ audio_timestamps: info.highlight_start_times_in_ms,
3820
+ audio_cover_artwork_uri: info.cover_artwork_uri,
3821
+ audio_cover_artwork_thumbnail_uri: info.cover_artwork_thumbnail_uri,
3822
+ audio_has_lyrics: info.has_lyrics,
3823
+ audio_is_explicit: info.is_explicit
3784
3824
  };
3785
3825
  }
3786
3826
  function extractPinned(post) {
@@ -3803,6 +3843,7 @@ function parsePostGraphql(post, cfg) {
3803
3843
  owner_id: owner.id ?? owner.pk,
3804
3844
  username: owner.username ?? "",
3805
3845
  fullname: owner.full_name ?? "",
3846
+ user: owner,
3806
3847
  post_id: post.id,
3807
3848
  post_shortcode: post.shortcode,
3808
3849
  post_url: `${cfg.root}/p/${post.shortcode}/`,
@@ -3976,7 +4017,14 @@ var InstagramExtractor = class extends Extractor {
3976
4017
  yield url(file.audio_url, combined);
3977
4018
  }
3978
4019
  if (previewsAud) combined.media_id = `${combined.media_id}p`;
3979
- else continue;
4020
+ if (!audio && !previewsAud) {
4021
+ const coverUrl = file.display_url;
4022
+ if (coverUrl) {
4023
+ nameExtFromURL(coverUrl, combined);
4024
+ yield url(coverUrl, combined);
4025
+ }
4026
+ continue;
4027
+ }
3980
4028
  }
3981
4029
  if (file.video_url) {
3982
4030
  if (shouldDownloadVideos) {
@@ -4500,9 +4548,9 @@ var DownloadJob = class DownloadJob extends Job {
4500
4548
  ...msg.metadata
4501
4549
  };
4502
4550
  const extrClass = meta._extractor;
4503
- if (!extrClass || typeof extrClass !== "object") return;
4551
+ if (!extrClass || typeof extrClass !== "function") return;
4504
4552
  const cls = extrClass;
4505
- const match = cls.pattern.exec(msg.url);
4553
+ const match = cls.pattern.exec(msg.url) ?? cls.pattern.exec(msg.url.replace(/\/$/, ""));
4506
4554
  if (!match) return;
4507
4555
  const parentExtr = this.extractor;
4508
4556
  const childJob = new DownloadJob(Reflect.construct(cls, [{
@@ -4571,7 +4619,18 @@ var PrintJob = class PrintJob extends Job {
4571
4619
  width: meta.width ?? 0,
4572
4620
  height: meta.height ?? 0,
4573
4621
  videoUrl: meta.video_url ?? null,
4574
- audioUrl: meta.audio_url ?? null
4622
+ audioUrl: meta.audio_url ?? null,
4623
+ audioTitle: meta.audio_title ?? void 0,
4624
+ audioArtist: meta.audio_artist ?? void 0,
4625
+ audioDuration: meta.audio_duration ?? void 0,
4626
+ audioHasLyrics: meta.audio_has_lyrics ?? void 0,
4627
+ audioIsExplicit: meta.audio_is_explicit ?? void 0,
4628
+ coverArtworkUri: meta.audio_cover_artwork_uri ?? meta.audio_cover_artwork_thumbnail_uri ?? void 0,
4629
+ storyLinkUrl: meta.story_link_url ?? void 0,
4630
+ storyLinkDisplay: meta.story_link_display ?? void 0,
4631
+ storyLinkTitle: meta.story_link_title ?? void 0,
4632
+ storyLinkType: meta.story_link_type ?? void 0,
4633
+ musicAttribution: meta.music_attribution ?? void 0
4575
4634
  });
4576
4635
  }
4577
4636
  async handleQueue(msg) {
@@ -4582,9 +4641,9 @@ var PrintJob = class PrintJob extends Job {
4582
4641
  ...this._currentDir,
4583
4642
  ...msg.metadata
4584
4643
  }._extractor;
4585
- if (!extrClass || typeof extrClass !== "object") return;
4644
+ if (!extrClass || typeof extrClass !== "function") return;
4586
4645
  const cls = extrClass;
4587
- const match = cls.pattern.exec(msg.url);
4646
+ const match = cls.pattern.exec(msg.url) ?? cls.pattern.exec(msg.url.replace(/\/$/, ""));
4588
4647
  if (!match) return;
4589
4648
  const parentExtr = this.extractor;
4590
4649
  const childJob = new PrintJob(Reflect.construct(cls, [{
@@ -4623,6 +4682,28 @@ var PrintJob = class PrintJob extends Job {
4623
4682
  row("Type:", `${m.type ?? "?"} (${this._files.length} files)`);
4624
4683
  row("URL:", m.post_url ?? "?");
4625
4684
  const desc = m.description ?? "";
4685
+ const isPrivate = m.is_private;
4686
+ const isVerified = m.is_verified;
4687
+ const followerCount = m.follower_count;
4688
+ const followingCount = m.following_count;
4689
+ const mediaCount = m.media_count;
4690
+ const bio = m.biography ?? "";
4691
+ const externalUrl = m.external_url;
4692
+ if (isPrivate !== void 0 || isVerified !== void 0 || followerCount !== void 0) {
4693
+ const badges = [];
4694
+ if (isPrivate) badges.push("🔒 Private");
4695
+ if (isVerified) badges.push("✅ Verified");
4696
+ if (followerCount !== void 0) badges.push(`${followerCount.toLocaleString()} followers`);
4697
+ if (followingCount !== void 0) badges.push(`${followingCount.toLocaleString()} following`);
4698
+ if (mediaCount !== void 0) badges.push(`${mediaCount.toLocaleString()} posts`);
4699
+ row("Profile:", badges.join(" · "));
4700
+ }
4701
+ if (bio) {
4702
+ process.stdout.write(` ${dim("│")}\n`);
4703
+ process.stdout.write(` ${dim("│")} ${b("Bio:")}\n`);
4704
+ for (const line of bio.split("\n")) for (const wl of this._wrap(line, w - 8)) process.stdout.write(` ${dim("│")} ${dim(wl)}\n`);
4705
+ }
4706
+ if (externalUrl) row("Website:", externalUrl);
4626
4707
  if (desc) {
4627
4708
  process.stdout.write(` ${dim("│")}\n`);
4628
4709
  process.stdout.write(` ${dim("│")} ${b("Description:")}\n`);
@@ -4667,6 +4748,37 @@ var PrintJob = class PrintJob extends Job {
4667
4748
  process.stdout.write(`${line}\n`);
4668
4749
  }
4669
4750
  }
4751
+ const linkFiles = this._files.filter((f) => f.storyLinkUrl);
4752
+ if (linkFiles.length > 0) {
4753
+ process.stdout.write(` ${dim("│")}\n`);
4754
+ process.stdout.write(` ${dim("│")} ${b("Link:")}\n`);
4755
+ for (const lf of linkFiles) {
4756
+ if (lf.storyLinkDisplay) process.stdout.write(` ${dim("│")} ${g("🔗")} ${lf.storyLinkDisplay}\n`);
4757
+ if (lf.storyLinkTitle && lf.storyLinkTitle !== "Visit Link") process.stdout.write(` ${dim("│")} ${dim("Title:")} ${lf.storyLinkTitle}\n`);
4758
+ if (lf.storyLinkType) process.stdout.write(` ${dim("│")} ${dim("Type:")} ${lf.storyLinkType}\n`);
4759
+ }
4760
+ }
4761
+ const audioFiles = this._files.filter((f) => f.audioUrl);
4762
+ if (audioFiles.length > 0) {
4763
+ process.stdout.write(` ${dim("│")}\n`);
4764
+ process.stdout.write(` ${dim("│")} ${b("Music:")}\n`);
4765
+ for (const af of audioFiles) {
4766
+ if (af.audioTitle) {
4767
+ const title = af.audioArtist ? `${af.audioTitle} — ${af.audioArtist}` : af.audioTitle;
4768
+ process.stdout.write(` ${dim("│")} ${g("♪")} ${title}\n`);
4769
+ }
4770
+ if (af.musicAttribution) process.stdout.write(` ${dim("│")} ${dim("via:")} ${af.musicAttribution}\n`);
4771
+ if (af.audioDuration) {
4772
+ const mins = Math.floor(af.audioDuration / 60);
4773
+ const secs = Math.round(af.audioDuration % 60);
4774
+ const badges = [`${mins}:${String(secs).padStart(2, "0")}`];
4775
+ if (af.audioHasLyrics) badges.push("lyrics");
4776
+ if (af.audioIsExplicit) badges.push(`${_YELLOW}explicit${_RESET}`);
4777
+ process.stdout.write(` ${dim("│")} ${dim(badges.join(" · "))}\n`);
4778
+ }
4779
+ if (af.coverArtworkUri) process.stdout.write(` ${dim("│")} ${dim("Art:")} ${dim(`${af.coverArtworkUri.slice(0, 60)}…`)}\n`);
4780
+ }
4781
+ }
4670
4782
  process.stdout.write(` ${dim("└")}${"─".repeat(w - 2)}${dim("┘")}\n`);
4671
4783
  }
4672
4784
  _wrap(text, maxLen) {
@@ -4937,6 +5049,11 @@ function resolveExtractor(url) {
4937
5049
  throw new Error(`No extractor matched URL: ${url}. Supported: /p/, /reel/, /{user}/, /stories/, /highlights/, /explore/tags/, /saved/`);
4938
5050
  }
4939
5051
  async function runExtractor(url, extrClass, opts) {
5052
+ if (opts.info && extrClass === InstagramUserExtractor) {
5053
+ const parts = (opts.include ?? "info").split(",").map((s) => s.trim());
5054
+ if (!parts.includes("info")) parts.push("info");
5055
+ opts.include = parts.join(",");
5056
+ }
4940
5057
  const config = buildConfig(opts);
4941
5058
  const log = createLogger(opts.verbose ?? false);
4942
5059
  let http;
package/dist/index.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_sdk = require("./sdk-BClg0Kv2.cjs");
2
+ const require_sdk = require("./sdk-B9mRv7JE.cjs");
3
3
  //#region src/core/print-job.ts
4
4
  var PrintJob = class PrintJob extends require_sdk.Job {
5
5
  _currentDir = {};
@@ -31,7 +31,18 @@ var PrintJob = class PrintJob extends require_sdk.Job {
31
31
  width: meta.width ?? 0,
32
32
  height: meta.height ?? 0,
33
33
  videoUrl: meta.video_url ?? null,
34
- audioUrl: meta.audio_url ?? null
34
+ audioUrl: meta.audio_url ?? null,
35
+ audioTitle: meta.audio_title ?? void 0,
36
+ audioArtist: meta.audio_artist ?? void 0,
37
+ audioDuration: meta.audio_duration ?? void 0,
38
+ audioHasLyrics: meta.audio_has_lyrics ?? void 0,
39
+ audioIsExplicit: meta.audio_is_explicit ?? void 0,
40
+ coverArtworkUri: meta.audio_cover_artwork_uri ?? meta.audio_cover_artwork_thumbnail_uri ?? void 0,
41
+ storyLinkUrl: meta.story_link_url ?? void 0,
42
+ storyLinkDisplay: meta.story_link_display ?? void 0,
43
+ storyLinkTitle: meta.story_link_title ?? void 0,
44
+ storyLinkType: meta.story_link_type ?? void 0,
45
+ musicAttribution: meta.music_attribution ?? void 0
35
46
  });
36
47
  }
37
48
  async handleQueue(msg) {
@@ -42,9 +53,9 @@ var PrintJob = class PrintJob extends require_sdk.Job {
42
53
  ...this._currentDir,
43
54
  ...msg.metadata
44
55
  }._extractor;
45
- if (!extrClass || typeof extrClass !== "object") return;
56
+ if (!extrClass || typeof extrClass !== "function") return;
46
57
  const cls = extrClass;
47
- const match = cls.pattern.exec(msg.url);
58
+ const match = cls.pattern.exec(msg.url) ?? cls.pattern.exec(msg.url.replace(/\/$/, ""));
48
59
  if (!match) return;
49
60
  const parentExtr = this.extractor;
50
61
  const childJob = new PrintJob(Reflect.construct(cls, [{
@@ -83,6 +94,28 @@ var PrintJob = class PrintJob extends require_sdk.Job {
83
94
  row("Type:", `${m.type ?? "?"} (${this._files.length} files)`);
84
95
  row("URL:", m.post_url ?? "?");
85
96
  const desc = m.description ?? "";
97
+ const isPrivate = m.is_private;
98
+ const isVerified = m.is_verified;
99
+ const followerCount = m.follower_count;
100
+ const followingCount = m.following_count;
101
+ const mediaCount = m.media_count;
102
+ const bio = m.biography ?? "";
103
+ const externalUrl = m.external_url;
104
+ if (isPrivate !== void 0 || isVerified !== void 0 || followerCount !== void 0) {
105
+ const badges = [];
106
+ if (isPrivate) badges.push("🔒 Private");
107
+ if (isVerified) badges.push("✅ Verified");
108
+ if (followerCount !== void 0) badges.push(`${followerCount.toLocaleString()} followers`);
109
+ if (followingCount !== void 0) badges.push(`${followingCount.toLocaleString()} following`);
110
+ if (mediaCount !== void 0) badges.push(`${mediaCount.toLocaleString()} posts`);
111
+ row("Profile:", badges.join(" · "));
112
+ }
113
+ if (bio) {
114
+ process.stdout.write(` ${require_sdk.dim("│")}\n`);
115
+ process.stdout.write(` ${require_sdk.dim("│")} ${require_sdk.b("Bio:")}\n`);
116
+ for (const line of bio.split("\n")) for (const wl of this._wrap(line, w - 8)) process.stdout.write(` ${require_sdk.dim("│")} ${require_sdk.dim(wl)}\n`);
117
+ }
118
+ if (externalUrl) row("Website:", externalUrl);
86
119
  if (desc) {
87
120
  process.stdout.write(` ${require_sdk.dim("│")}\n`);
88
121
  process.stdout.write(` ${require_sdk.dim("│")} ${require_sdk.b("Description:")}\n`);
@@ -127,6 +160,37 @@ var PrintJob = class PrintJob extends require_sdk.Job {
127
160
  process.stdout.write(`${line}\n`);
128
161
  }
129
162
  }
163
+ const linkFiles = this._files.filter((f) => f.storyLinkUrl);
164
+ if (linkFiles.length > 0) {
165
+ process.stdout.write(` ${require_sdk.dim("│")}\n`);
166
+ process.stdout.write(` ${require_sdk.dim("│")} ${require_sdk.b("Link:")}\n`);
167
+ for (const lf of linkFiles) {
168
+ if (lf.storyLinkDisplay) process.stdout.write(` ${require_sdk.dim("│")} ${require_sdk.g("🔗")} ${lf.storyLinkDisplay}\n`);
169
+ if (lf.storyLinkTitle && lf.storyLinkTitle !== "Visit Link") process.stdout.write(` ${require_sdk.dim("│")} ${require_sdk.dim("Title:")} ${lf.storyLinkTitle}\n`);
170
+ if (lf.storyLinkType) process.stdout.write(` ${require_sdk.dim("│")} ${require_sdk.dim("Type:")} ${lf.storyLinkType}\n`);
171
+ }
172
+ }
173
+ const audioFiles = this._files.filter((f) => f.audioUrl);
174
+ if (audioFiles.length > 0) {
175
+ process.stdout.write(` ${require_sdk.dim("│")}\n`);
176
+ process.stdout.write(` ${require_sdk.dim("│")} ${require_sdk.b("Music:")}\n`);
177
+ for (const af of audioFiles) {
178
+ if (af.audioTitle) {
179
+ const title = af.audioArtist ? `${af.audioTitle} — ${af.audioArtist}` : af.audioTitle;
180
+ process.stdout.write(` ${require_sdk.dim("│")} ${require_sdk.g("♪")} ${title}\n`);
181
+ }
182
+ if (af.musicAttribution) process.stdout.write(` ${require_sdk.dim("│")} ${require_sdk.dim("via:")} ${af.musicAttribution}\n`);
183
+ if (af.audioDuration) {
184
+ const mins = Math.floor(af.audioDuration / 60);
185
+ const secs = Math.round(af.audioDuration % 60);
186
+ const badges = [`${mins}:${String(secs).padStart(2, "0")}`];
187
+ if (af.audioHasLyrics) badges.push("lyrics");
188
+ if (af.audioIsExplicit) badges.push(`${require_sdk._YELLOW}explicit${require_sdk._RESET}`);
189
+ process.stdout.write(` ${require_sdk.dim("│")} ${require_sdk.dim(badges.join(" · "))}\n`);
190
+ }
191
+ if (af.coverArtworkUri) process.stdout.write(` ${require_sdk.dim("│")} ${require_sdk.dim("Art:")} ${require_sdk.dim(`${af.coverArtworkUri.slice(0, 60)}…`)}\n`);
192
+ }
193
+ }
130
194
  process.stdout.write(` ${require_sdk.dim("└")}${"─".repeat(w - 2)}${require_sdk.dim("┘")}\n`);
131
195
  }
132
196
  _wrap(text, maxLen) {
package/dist/index.d.cts CHANGED
@@ -97,6 +97,7 @@ interface InstagramUser {
97
97
  count: number;
98
98
  };
99
99
  followed_by_viewer?: boolean;
100
+ is_verified?: boolean;
100
101
  }
101
102
  interface ImageCandidate {
102
103
  url: string;
@@ -137,23 +138,131 @@ interface BloksSticker {
137
138
  interface MusicSticker {
138
139
  music_asset_info?: MusicAssetInfo;
139
140
  music_consumption_info?: MusicConsumptionInfo;
141
+ /** Text attribution (e.g. "Sticker by 前島亜美"). */
142
+ attribution?: string;
143
+ /** Display type (e.g. "music_hidden", "music_with_lyrics"). */
144
+ display_type?: string;
145
+ /** Placement on story canvas (0-1 fractional). */
146
+ x?: number;
147
+ y?: number;
148
+ width?: number;
149
+ height?: number;
150
+ rotation?: number;
151
+ start_time_ms?: number;
152
+ end_time_ms?: number;
153
+ }
154
+ /** A link sticker on a story (swipe-up / CTA link). */
155
+ interface StoryLink {
156
+ display_url: string;
157
+ link_title: string;
158
+ link_type: string;
159
+ url: string;
160
+ }
161
+ interface StoryLinkSticker {
162
+ id: string;
163
+ x: number;
164
+ y: number;
165
+ width: number;
166
+ height: number;
167
+ rotation: number;
168
+ start_time_ms: number;
169
+ end_time_ms: number;
170
+ story_link: StoryLink;
140
171
  }
141
172
  interface MusicAssetInfo {
142
173
  id: string;
143
174
  title?: string;
175
+ sanitized_title?: string;
176
+ subtitle?: string;
144
177
  display_artist?: string;
145
178
  ig_artist?: string;
179
+ ig_username?: string;
146
180
  duration_in_ms?: number;
147
181
  highlight_start_times_in_ms?: number[];
148
182
  progressive_download_url: string;
183
+ /** Lower-bitrate alternative download URL. */
184
+ fast_start_progressive_download_url?: string;
149
185
  cover_artwork_uri?: string;
186
+ cover_artwork_thumbnail_uri?: string;
187
+ has_lyrics?: boolean;
188
+ is_explicit?: boolean;
189
+ /** Profile picture placeholder for the artist. */
190
+ placeholder_profile_pic_url?: string;
191
+ /** Audio asset ID (differs from ``id`` which is the track ID). */
192
+ audio_asset_id?: string;
193
+ /** Where in the track the playback starts (ms). */
194
+ audio_asset_start_time_in_ms?: number;
195
+ /** How long the music overlaps with the video. */
196
+ overlap_duration_in_ms?: number;
197
+ /** Music cluster ID for trending / recommendations. */
198
+ audio_cluster_id?: string;
199
+ /** Artist ID in Instagram's music catalog. */
200
+ artist_id?: string;
201
+ /** Whether the track is trending in Reels. */
202
+ is_trending_in_clips?: boolean;
203
+ /** Whether the user has bookmarked this track. */
204
+ is_bookmarked?: boolean;
205
+ /** Licensing subtype (e.g. "DEFAULT"). */
206
+ licensed_music_subtype?: string;
207
+ /** Whether media creation with this music is allowed. */
208
+ allow_media_creation_with_music?: boolean;
209
+ /** Whether saving/downloading the audio is allowed. */
210
+ allows_saving?: boolean;
211
+ /** Audio muting configuration. */
212
+ audio_muting_info?: {
213
+ mute_audio: boolean;
214
+ mute_reason_str: string;
215
+ allow_audio_editing: boolean;
216
+ show_muted_audio_toast: boolean;
217
+ };
218
+ /** Whether audio should be muted and why. */
219
+ should_mute_audio?: boolean;
220
+ should_mute_audio_reason?: string;
221
+ should_mute_audio_reason_type?: string | null;
222
+ /** Whether to render the soundwave visual. */
223
+ should_render_soundwave?: boolean;
224
+ /** Reactive/streaming audio download URL (may be null). */
225
+ reactive_audio_download_url?: string | null;
226
+ /** 30-second web preview download URL. */
227
+ web_30s_preview_download_url?: string | null;
228
+ /** Monetization info for the song. */
229
+ song_monetization_info?: unknown;
230
+ /** Trend rank info (current and previous). */
231
+ trend_rank?: unknown;
232
+ previous_trend_rank?: unknown;
233
+ /** Display labels for the track. */
234
+ display_labels?: unknown;
235
+ /** Related derived content. */
236
+ derived_content_id?: string | null;
237
+ derived_content_start_time_in_composition_in_ms?: number | null;
238
+ /** Number of clips that use this music. */
239
+ formatted_clips_media_count?: unknown;
240
+ /** Eligibility for vinyl sticker / audio effects. */
241
+ is_eligible_for_audio_effects?: unknown;
242
+ is_eligible_for_vinyl_sticker?: boolean;
243
+ /** Audio filter configurations. */
244
+ audio_filter_infos?: unknown[];
245
+ /** Lyrics data (if available). */
246
+ lyrics?: unknown;
247
+ /** Spotify track metadata (if linked). */
248
+ spotify_track_metadata?: unknown;
249
+ /** User notes on the track. */
250
+ user_notes?: unknown;
251
+ /** Related audio recommendations. */
252
+ related_audios?: unknown;
253
+ /** Dark mode message override. */
254
+ dark_message?: unknown;
255
+ /** DASH manifest for audio. */
256
+ dash_manifest?: unknown;
257
+ /** Music creation restriction reason. */
258
+ music_creation_restriction_reason?: unknown;
150
259
  }
151
260
  interface MusicConsumptionInfo {
152
261
  display_artist?: string;
153
262
  ig_artist?: string;
154
263
  }
155
264
  interface MusicMetadata {
156
- music_info?: MusicAssetInfo;
265
+ music_info?: MusicSticker;
157
266
  }
158
267
  interface InstagramPost {
159
268
  pk: string;
@@ -183,7 +292,14 @@ interface InstagramPost {
183
292
  reel_mentions?: ReelMention[];
184
293
  story_bloks_stickers?: BloksSticker[];
185
294
  story_music_stickers?: MusicSticker[];
295
+ story_link_stickers?: StoryLinkSticker[];
186
296
  music_metadata?: MusicMetadata;
297
+ highlights_info?: {
298
+ added_to: Array<{
299
+ reel_id: string;
300
+ title: string;
301
+ }>;
302
+ };
187
303
  expiring_at?: number;
188
304
  seen?: number;
189
305
  items?: InstagramCarouselItem[];
@@ -214,13 +330,13 @@ interface InstagramCarouselItem {
214
330
  subscription_media_visibility?: string;
215
331
  audience?: string;
216
332
  story_music_stickers?: MusicSticker[];
333
+ story_link_stickers?: StoryLinkSticker[];
217
334
  usertags?: {
218
335
  in: UserTag[];
219
336
  };
220
337
  reel_mentions?: ReelMention[];
221
338
  story_bloks_stickers?: BloksSticker[];
222
339
  }
223
- /** Parsed post (normalized output) */
224
340
  interface ParsedPost {
225
341
  post_id: string;
226
342
  post_shortcode: string;
@@ -274,12 +390,26 @@ interface ParsedMedia {
274
390
  audio_url?: string;
275
391
  audio_user?: string;
276
392
  audio_title?: string;
393
+ audio_subtitle?: string;
277
394
  audio_artist?: string;
278
395
  audio_duration?: number;
279
396
  audio_timestamps?: number[];
397
+ audio_cover_artwork_uri?: string;
398
+ audio_cover_artwork_thumbnail_uri?: string;
399
+ audio_has_lyrics?: boolean;
400
+ audio_is_explicit?: boolean;
280
401
  _ytdl_manifest_data?: string;
281
402
  sidecar_media_id?: string;
282
403
  sidecar_shortcode?: string;
404
+ /** Story link sticker data. */
405
+ story_link_url?: string;
406
+ story_link_display?: string;
407
+ story_link_title?: string;
408
+ story_link_type?: string;
409
+ /** Music sticker attribution (e.g. "Sticker by 前島亜美"). */
410
+ music_attribution?: string;
411
+ /** Music sticker display type (e.g. "music_hidden"). */
412
+ music_display_type?: string;
283
413
  }
284
414
  interface Coauthor {
285
415
  id: string;
package/dist/index.d.mts CHANGED
@@ -97,6 +97,7 @@ interface InstagramUser {
97
97
  count: number;
98
98
  };
99
99
  followed_by_viewer?: boolean;
100
+ is_verified?: boolean;
100
101
  }
101
102
  interface ImageCandidate {
102
103
  url: string;
@@ -137,23 +138,131 @@ interface BloksSticker {
137
138
  interface MusicSticker {
138
139
  music_asset_info?: MusicAssetInfo;
139
140
  music_consumption_info?: MusicConsumptionInfo;
141
+ /** Text attribution (e.g. "Sticker by 前島亜美"). */
142
+ attribution?: string;
143
+ /** Display type (e.g. "music_hidden", "music_with_lyrics"). */
144
+ display_type?: string;
145
+ /** Placement on story canvas (0-1 fractional). */
146
+ x?: number;
147
+ y?: number;
148
+ width?: number;
149
+ height?: number;
150
+ rotation?: number;
151
+ start_time_ms?: number;
152
+ end_time_ms?: number;
153
+ }
154
+ /** A link sticker on a story (swipe-up / CTA link). */
155
+ interface StoryLink {
156
+ display_url: string;
157
+ link_title: string;
158
+ link_type: string;
159
+ url: string;
160
+ }
161
+ interface StoryLinkSticker {
162
+ id: string;
163
+ x: number;
164
+ y: number;
165
+ width: number;
166
+ height: number;
167
+ rotation: number;
168
+ start_time_ms: number;
169
+ end_time_ms: number;
170
+ story_link: StoryLink;
140
171
  }
141
172
  interface MusicAssetInfo {
142
173
  id: string;
143
174
  title?: string;
175
+ sanitized_title?: string;
176
+ subtitle?: string;
144
177
  display_artist?: string;
145
178
  ig_artist?: string;
179
+ ig_username?: string;
146
180
  duration_in_ms?: number;
147
181
  highlight_start_times_in_ms?: number[];
148
182
  progressive_download_url: string;
183
+ /** Lower-bitrate alternative download URL. */
184
+ fast_start_progressive_download_url?: string;
149
185
  cover_artwork_uri?: string;
186
+ cover_artwork_thumbnail_uri?: string;
187
+ has_lyrics?: boolean;
188
+ is_explicit?: boolean;
189
+ /** Profile picture placeholder for the artist. */
190
+ placeholder_profile_pic_url?: string;
191
+ /** Audio asset ID (differs from ``id`` which is the track ID). */
192
+ audio_asset_id?: string;
193
+ /** Where in the track the playback starts (ms). */
194
+ audio_asset_start_time_in_ms?: number;
195
+ /** How long the music overlaps with the video. */
196
+ overlap_duration_in_ms?: number;
197
+ /** Music cluster ID for trending / recommendations. */
198
+ audio_cluster_id?: string;
199
+ /** Artist ID in Instagram's music catalog. */
200
+ artist_id?: string;
201
+ /** Whether the track is trending in Reels. */
202
+ is_trending_in_clips?: boolean;
203
+ /** Whether the user has bookmarked this track. */
204
+ is_bookmarked?: boolean;
205
+ /** Licensing subtype (e.g. "DEFAULT"). */
206
+ licensed_music_subtype?: string;
207
+ /** Whether media creation with this music is allowed. */
208
+ allow_media_creation_with_music?: boolean;
209
+ /** Whether saving/downloading the audio is allowed. */
210
+ allows_saving?: boolean;
211
+ /** Audio muting configuration. */
212
+ audio_muting_info?: {
213
+ mute_audio: boolean;
214
+ mute_reason_str: string;
215
+ allow_audio_editing: boolean;
216
+ show_muted_audio_toast: boolean;
217
+ };
218
+ /** Whether audio should be muted and why. */
219
+ should_mute_audio?: boolean;
220
+ should_mute_audio_reason?: string;
221
+ should_mute_audio_reason_type?: string | null;
222
+ /** Whether to render the soundwave visual. */
223
+ should_render_soundwave?: boolean;
224
+ /** Reactive/streaming audio download URL (may be null). */
225
+ reactive_audio_download_url?: string | null;
226
+ /** 30-second web preview download URL. */
227
+ web_30s_preview_download_url?: string | null;
228
+ /** Monetization info for the song. */
229
+ song_monetization_info?: unknown;
230
+ /** Trend rank info (current and previous). */
231
+ trend_rank?: unknown;
232
+ previous_trend_rank?: unknown;
233
+ /** Display labels for the track. */
234
+ display_labels?: unknown;
235
+ /** Related derived content. */
236
+ derived_content_id?: string | null;
237
+ derived_content_start_time_in_composition_in_ms?: number | null;
238
+ /** Number of clips that use this music. */
239
+ formatted_clips_media_count?: unknown;
240
+ /** Eligibility for vinyl sticker / audio effects. */
241
+ is_eligible_for_audio_effects?: unknown;
242
+ is_eligible_for_vinyl_sticker?: boolean;
243
+ /** Audio filter configurations. */
244
+ audio_filter_infos?: unknown[];
245
+ /** Lyrics data (if available). */
246
+ lyrics?: unknown;
247
+ /** Spotify track metadata (if linked). */
248
+ spotify_track_metadata?: unknown;
249
+ /** User notes on the track. */
250
+ user_notes?: unknown;
251
+ /** Related audio recommendations. */
252
+ related_audios?: unknown;
253
+ /** Dark mode message override. */
254
+ dark_message?: unknown;
255
+ /** DASH manifest for audio. */
256
+ dash_manifest?: unknown;
257
+ /** Music creation restriction reason. */
258
+ music_creation_restriction_reason?: unknown;
150
259
  }
151
260
  interface MusicConsumptionInfo {
152
261
  display_artist?: string;
153
262
  ig_artist?: string;
154
263
  }
155
264
  interface MusicMetadata {
156
- music_info?: MusicAssetInfo;
265
+ music_info?: MusicSticker;
157
266
  }
158
267
  interface InstagramPost {
159
268
  pk: string;
@@ -183,7 +292,14 @@ interface InstagramPost {
183
292
  reel_mentions?: ReelMention[];
184
293
  story_bloks_stickers?: BloksSticker[];
185
294
  story_music_stickers?: MusicSticker[];
295
+ story_link_stickers?: StoryLinkSticker[];
186
296
  music_metadata?: MusicMetadata;
297
+ highlights_info?: {
298
+ added_to: Array<{
299
+ reel_id: string;
300
+ title: string;
301
+ }>;
302
+ };
187
303
  expiring_at?: number;
188
304
  seen?: number;
189
305
  items?: InstagramCarouselItem[];
@@ -214,13 +330,13 @@ interface InstagramCarouselItem {
214
330
  subscription_media_visibility?: string;
215
331
  audience?: string;
216
332
  story_music_stickers?: MusicSticker[];
333
+ story_link_stickers?: StoryLinkSticker[];
217
334
  usertags?: {
218
335
  in: UserTag[];
219
336
  };
220
337
  reel_mentions?: ReelMention[];
221
338
  story_bloks_stickers?: BloksSticker[];
222
339
  }
223
- /** Parsed post (normalized output) */
224
340
  interface ParsedPost {
225
341
  post_id: string;
226
342
  post_shortcode: string;
@@ -274,12 +390,26 @@ interface ParsedMedia {
274
390
  audio_url?: string;
275
391
  audio_user?: string;
276
392
  audio_title?: string;
393
+ audio_subtitle?: string;
277
394
  audio_artist?: string;
278
395
  audio_duration?: number;
279
396
  audio_timestamps?: number[];
397
+ audio_cover_artwork_uri?: string;
398
+ audio_cover_artwork_thumbnail_uri?: string;
399
+ audio_has_lyrics?: boolean;
400
+ audio_is_explicit?: boolean;
280
401
  _ytdl_manifest_data?: string;
281
402
  sidecar_media_id?: string;
282
403
  sidecar_shortcode?: string;
404
+ /** Story link sticker data. */
405
+ story_link_url?: string;
406
+ story_link_display?: string;
407
+ story_link_title?: string;
408
+ story_link_type?: string;
409
+ /** Music sticker attribution (e.g. "Sticker by 前島亜美"). */
410
+ music_attribution?: string;
411
+ /** Music sticker display type (e.g. "music_hidden"). */
412
+ music_display_type?: string;
283
413
  }
284
414
  interface Coauthor {
285
415
  id: string;
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { A as directory, B as _YELLOW, C as extract, D as parseUnicodeEscapes, E as parseInt, F as Extractor, G as pad, H as c, I as noopLogger, K as ConfigManager, L as DownloadJob, M as url, N as idFromShortcode, O as unescape, P as shortcodeFromId, R as Job, S as extr, T as nameExtFromURL, U as dim, V as b, W as g, _ as extractAudio, a as InstagramTaggedExtractor, b as InstagramRestAPI, c as InstagramSavedExtractor, d as InstagramPostExtractor, f as InstagramInfoExtractor, g as parsePostGraphql, h as InstagramExtractor, i as InstagramUserExtractor, j as queue, k as unquote, l as InstagramReelsExtractor, m as InstagramAvatarExtractor, o as InstagramTagExtractor, p as InstagramHighlightsExtractor, s as InstagramStoriesExtractor, t as InstagramSDK, u as InstagramPostsExtractor, v as extractTaggedUsers, w as findTags, x as ensureHttpScheme, y as parsePostRest, z as _RESET } from "./sdk-CovBsEps.mjs";
1
+ import { A as directory, B as _YELLOW, C as extract, D as parseUnicodeEscapes, E as parseInt, F as Extractor, G as pad, H as c, I as noopLogger, K as ConfigManager, L as DownloadJob, M as url, N as idFromShortcode, O as unescape, P as shortcodeFromId, R as Job, S as extr, T as nameExtFromURL, U as dim, V as b, W as g, _ as extractAudio, a as InstagramTaggedExtractor, b as InstagramRestAPI, c as InstagramSavedExtractor, d as InstagramPostExtractor, f as InstagramInfoExtractor, g as parsePostGraphql, h as InstagramExtractor, i as InstagramUserExtractor, j as queue, k as unquote, l as InstagramReelsExtractor, m as InstagramAvatarExtractor, o as InstagramTagExtractor, p as InstagramHighlightsExtractor, s as InstagramStoriesExtractor, t as InstagramSDK, u as InstagramPostsExtractor, v as extractTaggedUsers, w as findTags, x as ensureHttpScheme, y as parsePostRest, z as _RESET } from "./sdk-Dr4PJwiS.mjs";
2
2
  //#region src/core/print-job.ts
3
3
  var PrintJob = class PrintJob extends Job {
4
4
  _currentDir = {};
@@ -30,7 +30,18 @@ var PrintJob = class PrintJob extends Job {
30
30
  width: meta.width ?? 0,
31
31
  height: meta.height ?? 0,
32
32
  videoUrl: meta.video_url ?? null,
33
- audioUrl: meta.audio_url ?? null
33
+ audioUrl: meta.audio_url ?? null,
34
+ audioTitle: meta.audio_title ?? void 0,
35
+ audioArtist: meta.audio_artist ?? void 0,
36
+ audioDuration: meta.audio_duration ?? void 0,
37
+ audioHasLyrics: meta.audio_has_lyrics ?? void 0,
38
+ audioIsExplicit: meta.audio_is_explicit ?? void 0,
39
+ coverArtworkUri: meta.audio_cover_artwork_uri ?? meta.audio_cover_artwork_thumbnail_uri ?? void 0,
40
+ storyLinkUrl: meta.story_link_url ?? void 0,
41
+ storyLinkDisplay: meta.story_link_display ?? void 0,
42
+ storyLinkTitle: meta.story_link_title ?? void 0,
43
+ storyLinkType: meta.story_link_type ?? void 0,
44
+ musicAttribution: meta.music_attribution ?? void 0
34
45
  });
35
46
  }
36
47
  async handleQueue(msg) {
@@ -41,9 +52,9 @@ var PrintJob = class PrintJob extends Job {
41
52
  ...this._currentDir,
42
53
  ...msg.metadata
43
54
  }._extractor;
44
- if (!extrClass || typeof extrClass !== "object") return;
55
+ if (!extrClass || typeof extrClass !== "function") return;
45
56
  const cls = extrClass;
46
- const match = cls.pattern.exec(msg.url);
57
+ const match = cls.pattern.exec(msg.url) ?? cls.pattern.exec(msg.url.replace(/\/$/, ""));
47
58
  if (!match) return;
48
59
  const parentExtr = this.extractor;
49
60
  const childJob = new PrintJob(Reflect.construct(cls, [{
@@ -82,6 +93,28 @@ var PrintJob = class PrintJob extends Job {
82
93
  row("Type:", `${m.type ?? "?"} (${this._files.length} files)`);
83
94
  row("URL:", m.post_url ?? "?");
84
95
  const desc = m.description ?? "";
96
+ const isPrivate = m.is_private;
97
+ const isVerified = m.is_verified;
98
+ const followerCount = m.follower_count;
99
+ const followingCount = m.following_count;
100
+ const mediaCount = m.media_count;
101
+ const bio = m.biography ?? "";
102
+ const externalUrl = m.external_url;
103
+ if (isPrivate !== void 0 || isVerified !== void 0 || followerCount !== void 0) {
104
+ const badges = [];
105
+ if (isPrivate) badges.push("🔒 Private");
106
+ if (isVerified) badges.push("✅ Verified");
107
+ if (followerCount !== void 0) badges.push(`${followerCount.toLocaleString()} followers`);
108
+ if (followingCount !== void 0) badges.push(`${followingCount.toLocaleString()} following`);
109
+ if (mediaCount !== void 0) badges.push(`${mediaCount.toLocaleString()} posts`);
110
+ row("Profile:", badges.join(" · "));
111
+ }
112
+ if (bio) {
113
+ process.stdout.write(` ${dim("│")}\n`);
114
+ process.stdout.write(` ${dim("│")} ${b("Bio:")}\n`);
115
+ for (const line of bio.split("\n")) for (const wl of this._wrap(line, w - 8)) process.stdout.write(` ${dim("│")} ${dim(wl)}\n`);
116
+ }
117
+ if (externalUrl) row("Website:", externalUrl);
85
118
  if (desc) {
86
119
  process.stdout.write(` ${dim("│")}\n`);
87
120
  process.stdout.write(` ${dim("│")} ${b("Description:")}\n`);
@@ -126,6 +159,37 @@ var PrintJob = class PrintJob extends Job {
126
159
  process.stdout.write(`${line}\n`);
127
160
  }
128
161
  }
162
+ const linkFiles = this._files.filter((f) => f.storyLinkUrl);
163
+ if (linkFiles.length > 0) {
164
+ process.stdout.write(` ${dim("│")}\n`);
165
+ process.stdout.write(` ${dim("│")} ${b("Link:")}\n`);
166
+ for (const lf of linkFiles) {
167
+ if (lf.storyLinkDisplay) process.stdout.write(` ${dim("│")} ${g("🔗")} ${lf.storyLinkDisplay}\n`);
168
+ if (lf.storyLinkTitle && lf.storyLinkTitle !== "Visit Link") process.stdout.write(` ${dim("│")} ${dim("Title:")} ${lf.storyLinkTitle}\n`);
169
+ if (lf.storyLinkType) process.stdout.write(` ${dim("│")} ${dim("Type:")} ${lf.storyLinkType}\n`);
170
+ }
171
+ }
172
+ const audioFiles = this._files.filter((f) => f.audioUrl);
173
+ if (audioFiles.length > 0) {
174
+ process.stdout.write(` ${dim("│")}\n`);
175
+ process.stdout.write(` ${dim("│")} ${b("Music:")}\n`);
176
+ for (const af of audioFiles) {
177
+ if (af.audioTitle) {
178
+ const title = af.audioArtist ? `${af.audioTitle} — ${af.audioArtist}` : af.audioTitle;
179
+ process.stdout.write(` ${dim("│")} ${g("♪")} ${title}\n`);
180
+ }
181
+ if (af.musicAttribution) process.stdout.write(` ${dim("│")} ${dim("via:")} ${af.musicAttribution}\n`);
182
+ if (af.audioDuration) {
183
+ const mins = Math.floor(af.audioDuration / 60);
184
+ const secs = Math.round(af.audioDuration % 60);
185
+ const badges = [`${mins}:${String(secs).padStart(2, "0")}`];
186
+ if (af.audioHasLyrics) badges.push("lyrics");
187
+ if (af.audioIsExplicit) badges.push(`${_YELLOW}explicit${_RESET}`);
188
+ process.stdout.write(` ${dim("│")} ${dim(badges.join(" · "))}\n`);
189
+ }
190
+ if (af.coverArtworkUri) process.stdout.write(` ${dim("│")} ${dim("Art:")} ${dim(`${af.coverArtworkUri.slice(0, 60)}…`)}\n`);
191
+ }
192
+ }
129
193
  process.stdout.write(` ${dim("└")}${"─".repeat(w - 2)}${dim("┘")}\n`);
130
194
  }
131
195
  _wrap(text, maxLen) {
package/dist/node.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_sdk = require("./sdk-BClg0Kv2.cjs");
2
+ const require_sdk = require("./sdk-B9mRv7JE.cjs");
3
3
  //#region src/node-factory.ts
4
4
  /**
5
5
  * Create an SDK instance with Node.js defaults.
package/dist/node.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { I as noopLogger, n as createFetchHttpClient, r as extractCsrf, t as InstagramSDK } from "./sdk-CovBsEps.mjs";
1
+ import { I as noopLogger, n as createFetchHttpClient, r as extractCsrf, t as InstagramSDK } from "./sdk-Dr4PJwiS.mjs";
2
2
  //#region src/node-factory.ts
3
3
  /**
4
4
  * Create an SDK instance with Node.js defaults.
@@ -203,9 +203,9 @@ var DownloadJob = class DownloadJob extends Job {
203
203
  ...msg.metadata
204
204
  };
205
205
  const extrClass = meta._extractor;
206
- if (!extrClass || typeof extrClass !== "object") return;
206
+ if (!extrClass || typeof extrClass !== "function") return;
207
207
  const cls = extrClass;
208
- const match = cls.pattern.exec(msg.url);
208
+ const match = cls.pattern.exec(msg.url) ?? cls.pattern.exec(msg.url.replace(/\/$/, ""));
209
209
  if (!match) return;
210
210
  const parentExtr = this.extractor;
211
211
  const childJob = new DownloadJob(Reflect.construct(cls, [{
@@ -907,6 +907,7 @@ function parsePostRest(post, cfg) {
907
907
  owner_id: owner.pk,
908
908
  username: owner.username ?? "",
909
909
  fullname: owner.full_name ?? "",
910
+ user: owner,
910
911
  post_date: date,
911
912
  date,
912
913
  description: caption ? caption.text : "",
@@ -952,7 +953,7 @@ function parsePostRest(post, cfg) {
952
953
  if (post.music_metadata) {
953
954
  const info = post.music_metadata.music_info;
954
955
  if (info) {
955
- const audio = extractAudio(post, data, { music_asset_info: info }, cfg);
956
+ const audio = extractAudio(post, data, info, cfg);
956
957
  if (audio) {
957
958
  audio.num = items.length;
958
959
  data._files.push(audio);
@@ -999,9 +1000,38 @@ function parseStoryRest(post, cfg) {
999
1000
  const item = items[num];
1000
1001
  const media = parseMediaItem(item, post, cfg, num + 1);
1001
1002
  if (!media) continue;
1002
- extractTaggedUsers(item, media);
1003
+ const itemRec = item;
1004
+ extractTaggedUsers(itemRec, media);
1005
+ const musicStickers = itemRec.story_music_stickers;
1006
+ if (musicStickers?.[0]) {
1007
+ const audio = extractAudio(itemRec, data, musicStickers[0], cfg);
1008
+ if (audio) {
1009
+ audio.num = num + 1;
1010
+ if (musicStickers[0].attribution) audio.music_attribution = musicStickers[0].attribution;
1011
+ if (musicStickers[0].display_type) audio.music_display_type = musicStickers[0].display_type;
1012
+ data._files.push(audio);
1013
+ }
1014
+ }
1015
+ const linkStickers = itemRec.story_link_stickers;
1016
+ if (linkStickers?.[0]) {
1017
+ const sl = linkStickers[0].story_link;
1018
+ media.story_link_url = sl.url;
1019
+ media.story_link_display = sl.display_url;
1020
+ media.story_link_title = sl.link_title;
1021
+ media.story_link_type = sl.link_type;
1022
+ }
1003
1023
  data._files.push(media);
1004
1024
  }
1025
+ if (post.music_metadata) {
1026
+ const info = post.music_metadata.music_info;
1027
+ if (info) {
1028
+ const audio = extractAudio(post, data, info, cfg);
1029
+ if (audio) {
1030
+ audio.num = items.length;
1031
+ data._files.push(audio);
1032
+ }
1033
+ }
1034
+ }
1005
1035
  return data;
1006
1036
  }
1007
1037
  /** Parse a single media item (image/video) from a carousel or story. */
@@ -1113,11 +1143,16 @@ function extractAudio(src, dest, sticker, cfg) {
1113
1143
  const info = sticker.music_asset_info;
1114
1144
  if (!info) return null;
1115
1145
  const cinfo = sticker.music_consumption_info ?? info;
1116
- dest.audio_title = info.title;
1146
+ dest.audio_title = info.title ?? info.sanitized_title;
1147
+ dest.audio_subtitle = info.subtitle;
1117
1148
  dest.audio_duration = (info.duration_in_ms ?? 0) / 1e3;
1118
1149
  dest.audio_timestamps = info.highlight_start_times_in_ms;
1119
1150
  dest.audio_artist = info.display_artist ?? cinfo.display_artist;
1120
1151
  dest.audio_user = info.ig_artist ?? cinfo.ig_artist;
1152
+ dest.audio_has_lyrics = info.has_lyrics;
1153
+ dest.audio_is_explicit = info.is_explicit;
1154
+ dest.audio_cover_artwork_uri = info.cover_artwork_uri;
1155
+ dest.audio_cover_artwork_thumbnail_uri = info.cover_artwork_thumbnail_uri;
1121
1156
  const url = info.progressive_download_url;
1122
1157
  if (!url) return null;
1123
1158
  return {
@@ -1134,10 +1169,15 @@ function extractAudio(src, dest, sticker, cfg) {
1134
1169
  height_original: 0,
1135
1170
  tagged_users: [],
1136
1171
  audio_user: info.ig_artist ?? cinfo.ig_artist,
1137
- audio_title: info.title,
1172
+ audio_title: info.title ?? info.sanitized_title,
1173
+ audio_subtitle: info.subtitle,
1138
1174
  audio_artist: info.display_artist ?? cinfo.display_artist,
1139
1175
  audio_duration: (info.duration_in_ms ?? 0) / 1e3,
1140
- audio_timestamps: info.highlight_start_times_in_ms
1176
+ audio_timestamps: info.highlight_start_times_in_ms,
1177
+ audio_cover_artwork_uri: info.cover_artwork_uri,
1178
+ audio_cover_artwork_thumbnail_uri: info.cover_artwork_thumbnail_uri,
1179
+ audio_has_lyrics: info.has_lyrics,
1180
+ audio_is_explicit: info.is_explicit
1141
1181
  };
1142
1182
  }
1143
1183
  function extractPinned(post) {
@@ -1160,6 +1200,7 @@ function parsePostGraphql(post, cfg) {
1160
1200
  owner_id: owner.id ?? owner.pk,
1161
1201
  username: owner.username ?? "",
1162
1202
  fullname: owner.full_name ?? "",
1203
+ user: owner,
1163
1204
  post_id: post.id,
1164
1205
  post_shortcode: post.shortcode,
1165
1206
  post_url: `${cfg.root}/p/${post.shortcode}/`,
@@ -1333,7 +1374,14 @@ var InstagramExtractor = class extends Extractor {
1333
1374
  yield url(file.audio_url, combined);
1334
1375
  }
1335
1376
  if (previewsAud) combined.media_id = `${combined.media_id}p`;
1336
- else continue;
1377
+ if (!audio && !previewsAud) {
1378
+ const coverUrl = file.display_url;
1379
+ if (coverUrl) {
1380
+ nameExtFromURL(coverUrl, combined);
1381
+ yield url(coverUrl, combined);
1382
+ }
1383
+ continue;
1384
+ }
1337
1385
  }
1338
1386
  if (file.video_url) {
1339
1387
  if (shouldDownloadVideos) {
@@ -1497,10 +1545,37 @@ var InstagramInfoExtractor = class InstagramInfoExtractor extends InstagramExtra
1497
1545
  }
1498
1546
  async *items() {
1499
1547
  const screenName = (this.groups[0] ?? "").replace(/^\//, "");
1500
- let user;
1501
- if (screenName.startsWith("id:")) user = await this.api.userById(screenName.slice(3));
1502
- else user = await this.api.userByScreenName(screenName);
1503
- yield directory(user);
1548
+ const userId = screenName.startsWith("id:") ? screenName.slice(3) : await this.api.userId(screenName, false);
1549
+ const user = await this.api.userById(userId);
1550
+ const ur = user;
1551
+ const num = (key) => ur[key] ?? ur[key]?.count ?? 0;
1552
+ yield directory({
1553
+ post_id: user.pk,
1554
+ post_shortcode: user.username,
1555
+ post_url: `${this.root}/${user.username}/`,
1556
+ owner_id: user.pk,
1557
+ username: user.username,
1558
+ fullname: user.full_name ?? "",
1559
+ post_date: "",
1560
+ date: "",
1561
+ description: "",
1562
+ likes: 0,
1563
+ liked: false,
1564
+ pinned: [],
1565
+ type: "post",
1566
+ count: 0,
1567
+ _files: [],
1568
+ user,
1569
+ is_private: user.is_private ?? false,
1570
+ is_verified: user.is_verified ?? false,
1571
+ profile_pic_url: user.profile_pic_url ?? "",
1572
+ profile_pic_url_hd: user.hd_profile_pic_url_info?.url ?? user.profile_pic_url_hd ?? "",
1573
+ follower_count: num("follower_count"),
1574
+ following_count: num("following_count"),
1575
+ media_count: num("media_count"),
1576
+ biography: ur.biography ?? "",
1577
+ external_url: ur.external_url ?? ""
1578
+ });
1504
1579
  }
1505
1580
  async *posts() {}
1506
1581
  };
@@ -203,9 +203,9 @@ var DownloadJob = class DownloadJob extends Job {
203
203
  ...msg.metadata
204
204
  };
205
205
  const extrClass = meta._extractor;
206
- if (!extrClass || typeof extrClass !== "object") return;
206
+ if (!extrClass || typeof extrClass !== "function") return;
207
207
  const cls = extrClass;
208
- const match = cls.pattern.exec(msg.url);
208
+ const match = cls.pattern.exec(msg.url) ?? cls.pattern.exec(msg.url.replace(/\/$/, ""));
209
209
  if (!match) return;
210
210
  const parentExtr = this.extractor;
211
211
  const childJob = new DownloadJob(Reflect.construct(cls, [{
@@ -907,6 +907,7 @@ function parsePostRest(post, cfg) {
907
907
  owner_id: owner.pk,
908
908
  username: owner.username ?? "",
909
909
  fullname: owner.full_name ?? "",
910
+ user: owner,
910
911
  post_date: date,
911
912
  date,
912
913
  description: caption ? caption.text : "",
@@ -952,7 +953,7 @@ function parsePostRest(post, cfg) {
952
953
  if (post.music_metadata) {
953
954
  const info = post.music_metadata.music_info;
954
955
  if (info) {
955
- const audio = extractAudio(post, data, { music_asset_info: info }, cfg);
956
+ const audio = extractAudio(post, data, info, cfg);
956
957
  if (audio) {
957
958
  audio.num = items.length;
958
959
  data._files.push(audio);
@@ -999,9 +1000,38 @@ function parseStoryRest(post, cfg) {
999
1000
  const item = items[num];
1000
1001
  const media = parseMediaItem(item, post, cfg, num + 1);
1001
1002
  if (!media) continue;
1002
- extractTaggedUsers(item, media);
1003
+ const itemRec = item;
1004
+ extractTaggedUsers(itemRec, media);
1005
+ const musicStickers = itemRec.story_music_stickers;
1006
+ if (musicStickers?.[0]) {
1007
+ const audio = extractAudio(itemRec, data, musicStickers[0], cfg);
1008
+ if (audio) {
1009
+ audio.num = num + 1;
1010
+ if (musicStickers[0].attribution) audio.music_attribution = musicStickers[0].attribution;
1011
+ if (musicStickers[0].display_type) audio.music_display_type = musicStickers[0].display_type;
1012
+ data._files.push(audio);
1013
+ }
1014
+ }
1015
+ const linkStickers = itemRec.story_link_stickers;
1016
+ if (linkStickers?.[0]) {
1017
+ const sl = linkStickers[0].story_link;
1018
+ media.story_link_url = sl.url;
1019
+ media.story_link_display = sl.display_url;
1020
+ media.story_link_title = sl.link_title;
1021
+ media.story_link_type = sl.link_type;
1022
+ }
1003
1023
  data._files.push(media);
1004
1024
  }
1025
+ if (post.music_metadata) {
1026
+ const info = post.music_metadata.music_info;
1027
+ if (info) {
1028
+ const audio = extractAudio(post, data, info, cfg);
1029
+ if (audio) {
1030
+ audio.num = items.length;
1031
+ data._files.push(audio);
1032
+ }
1033
+ }
1034
+ }
1005
1035
  return data;
1006
1036
  }
1007
1037
  /** Parse a single media item (image/video) from a carousel or story. */
@@ -1113,11 +1143,16 @@ function extractAudio(src, dest, sticker, cfg) {
1113
1143
  const info = sticker.music_asset_info;
1114
1144
  if (!info) return null;
1115
1145
  const cinfo = sticker.music_consumption_info ?? info;
1116
- dest.audio_title = info.title;
1146
+ dest.audio_title = info.title ?? info.sanitized_title;
1147
+ dest.audio_subtitle = info.subtitle;
1117
1148
  dest.audio_duration = (info.duration_in_ms ?? 0) / 1e3;
1118
1149
  dest.audio_timestamps = info.highlight_start_times_in_ms;
1119
1150
  dest.audio_artist = info.display_artist ?? cinfo.display_artist;
1120
1151
  dest.audio_user = info.ig_artist ?? cinfo.ig_artist;
1152
+ dest.audio_has_lyrics = info.has_lyrics;
1153
+ dest.audio_is_explicit = info.is_explicit;
1154
+ dest.audio_cover_artwork_uri = info.cover_artwork_uri;
1155
+ dest.audio_cover_artwork_thumbnail_uri = info.cover_artwork_thumbnail_uri;
1121
1156
  const url = info.progressive_download_url;
1122
1157
  if (!url) return null;
1123
1158
  return {
@@ -1134,10 +1169,15 @@ function extractAudio(src, dest, sticker, cfg) {
1134
1169
  height_original: 0,
1135
1170
  tagged_users: [],
1136
1171
  audio_user: info.ig_artist ?? cinfo.ig_artist,
1137
- audio_title: info.title,
1172
+ audio_title: info.title ?? info.sanitized_title,
1173
+ audio_subtitle: info.subtitle,
1138
1174
  audio_artist: info.display_artist ?? cinfo.display_artist,
1139
1175
  audio_duration: (info.duration_in_ms ?? 0) / 1e3,
1140
- audio_timestamps: info.highlight_start_times_in_ms
1176
+ audio_timestamps: info.highlight_start_times_in_ms,
1177
+ audio_cover_artwork_uri: info.cover_artwork_uri,
1178
+ audio_cover_artwork_thumbnail_uri: info.cover_artwork_thumbnail_uri,
1179
+ audio_has_lyrics: info.has_lyrics,
1180
+ audio_is_explicit: info.is_explicit
1141
1181
  };
1142
1182
  }
1143
1183
  function extractPinned(post) {
@@ -1160,6 +1200,7 @@ function parsePostGraphql(post, cfg) {
1160
1200
  owner_id: owner.id ?? owner.pk,
1161
1201
  username: owner.username ?? "",
1162
1202
  fullname: owner.full_name ?? "",
1203
+ user: owner,
1163
1204
  post_id: post.id,
1164
1205
  post_shortcode: post.shortcode,
1165
1206
  post_url: `${cfg.root}/p/${post.shortcode}/`,
@@ -1333,7 +1374,14 @@ var InstagramExtractor = class extends Extractor {
1333
1374
  yield url(file.audio_url, combined);
1334
1375
  }
1335
1376
  if (previewsAud) combined.media_id = `${combined.media_id}p`;
1336
- else continue;
1377
+ if (!audio && !previewsAud) {
1378
+ const coverUrl = file.display_url;
1379
+ if (coverUrl) {
1380
+ nameExtFromURL(coverUrl, combined);
1381
+ yield url(coverUrl, combined);
1382
+ }
1383
+ continue;
1384
+ }
1337
1385
  }
1338
1386
  if (file.video_url) {
1339
1387
  if (shouldDownloadVideos) {
@@ -1497,10 +1545,37 @@ var InstagramInfoExtractor = class InstagramInfoExtractor extends InstagramExtra
1497
1545
  }
1498
1546
  async *items() {
1499
1547
  const screenName = (this.groups[0] ?? "").replace(/^\//, "");
1500
- let user;
1501
- if (screenName.startsWith("id:")) user = await this.api.userById(screenName.slice(3));
1502
- else user = await this.api.userByScreenName(screenName);
1503
- yield directory(user);
1548
+ const userId = screenName.startsWith("id:") ? screenName.slice(3) : await this.api.userId(screenName, false);
1549
+ const user = await this.api.userById(userId);
1550
+ const ur = user;
1551
+ const num = (key) => ur[key] ?? ur[key]?.count ?? 0;
1552
+ yield directory({
1553
+ post_id: user.pk,
1554
+ post_shortcode: user.username,
1555
+ post_url: `${this.root}/${user.username}/`,
1556
+ owner_id: user.pk,
1557
+ username: user.username,
1558
+ fullname: user.full_name ?? "",
1559
+ post_date: "",
1560
+ date: "",
1561
+ description: "",
1562
+ likes: 0,
1563
+ liked: false,
1564
+ pinned: [],
1565
+ type: "post",
1566
+ count: 0,
1567
+ _files: [],
1568
+ user,
1569
+ is_private: user.is_private ?? false,
1570
+ is_verified: user.is_verified ?? false,
1571
+ profile_pic_url: user.profile_pic_url ?? "",
1572
+ profile_pic_url_hd: user.hd_profile_pic_url_info?.url ?? user.profile_pic_url_hd ?? "",
1573
+ follower_count: num("follower_count"),
1574
+ following_count: num("following_count"),
1575
+ media_count: num("media_count"),
1576
+ biography: ur.biography ?? "",
1577
+ external_url: ur.external_url ?? ""
1578
+ });
1504
1579
  }
1505
1580
  async *posts() {}
1506
1581
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@chilfish/gallery-dl-instagram",
3
3
  "type": "module",
4
- "version": "0.2.2",
4
+ "version": "0.2.4",
5
5
  "description": "Instagram extraction pipeline — platform-agnostic SDK + CLI",
6
6
  "license": "GPL-2.0-only",
7
7
  "keywords": [