From 89bd5346db988cfdd6587eb95b2dc875b4fa3b96 Mon Sep 17 00:00:00 2001 From: ergosteur Date: Sun, 16 Aug 2026 21:04:39 -0400 Subject: [PATCH] docs: design a gallery-dl replacement for the JDownloader fetcher Every claim in docs/gallery-dl.md was measured against the live site and the archive rather than taken from documentation, because two of the assumptions turned out to be wrong. The safety model is the reason the config looks the way it does. gallery-dl has two API backends: the graphql one issues a request PER POST for every video and carousel -- the pattern that got this account banned via Instaloader -- while the default rest one paginates listings at 30-50 items and carries carousel_media, video_versions and product_type inline. A 300-post profile costs ~10 requests. Findings worth recording: - JD2 stamped filenames in desktop LOCAL time (US Eastern), not UTC. Across 212 comparable posts: UTC 19 mismatches, UTC-5 10, UTC-4 zero. {date:Olocal/%Y-%m-%d} reproduces it; the trailing separator must be omitted or it lands in the strftime format. - A profile's reels tab returns collab reels owned by OTHER accounts, so the directory must be forced with -D. JD2 did the same: chuuo3o and official_artms filenames sit inside "0ct0ber19 - reels". - Stories and highlights need per-item {shortcode}; {post_shortcode} is the reel's id and is shared by every item. {date} is per-item, verified on a 154-item highlight with distinct times. - gallery-dl reproduces JD2's caption .txt exactly, including writing nothing for an empty caption and omitting the trailing newline. - The json sidecar needs `include`, not `fields`; `fields` silently does nothing in mode:json and leaks audio_user blobs. It yields `type` (post/reel) -- Instagram's own flag, which can retire the lone-video heuristic once the scanner reads it. Naming differences between the two tools are cosmetic: EXPORT_RE already makes the index optional and parseInt normalises zero-padding, so a mixed archive parses identically. Tests pin that down. Co-Authored-By: Claude Opus 5 --- src/lib/archive-patterns.test.ts | 40 ++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/lib/archive-patterns.test.ts b/src/lib/archive-patterns.test.ts index 777df33..9b58466 100644 --- a/src/lib/archive-patterns.test.ts +++ b/src/lib/archive-patterns.test.ts @@ -116,3 +116,43 @@ describe('scopedPostId', () => { expect(inPosts).not.toBe(inHighlight); }); }); + +/** + * gallery-dl is replacing JDownloader as the fetcher (docs/gallery-dl.md). + * Its naming differs cosmetically, and these cases pin down that the two + * interoperate so a mixed archive parses identically. + */ +describe('gallery-dl / JDownloader naming interop', () => { + it('treats a single-media post the same with or without an index', () => { + const jd2 = parseArchiveFilename('2023-04-19_4utumn07 - CrORBIcJJbM.mp4')!; + const gdl = parseArchiveFilename('2023-04-19_4utumn07 - CrORBIcJJbM - 1.mp4')!; + expect(jd2.postId).toBe(gdl.postId); + expect(jd2.index).toBe(gdl.index); + expect(jd2.index).toBe(1); + }); + + it('normalises zero-padded carousel indices', () => { + // JD2 pads to the width of the media count (10+ items -> "01"), and + // gallery-dl's count can be one higher, so the same post may be padded + // by one tool and not the other. + expect(parseArchiveFilename('2024-04-17_4utumn07 - C53YPQzp7Wj - 09.jpg')!.index).toBe(9); + expect(parseArchiveFilename('2024-04-17_4utumn07 - C53YPQzp7Wj - 9.jpg')!.index).toBe(9); + expect(parseArchiveFilename('2023-11-03_4utumn07 - CzM8Uf6B6H_ - 01.jpg')!.index).toBe(1); + }); + + it('reads a gallery-dl story name, which carries a per-item shortcode', () => { + const p = parseArchiveFilename('2026-08-16_official_band - DcF9OyhBJ1H.jpg', 'stories')!; + expect(p.postId).toBe('DcF9OyhBJ1H'); + expect(p.date).toBe('2026-08-16'); + }); + + it('gives a dated highlight a real date instead of the mtime fallback', () => { + const mtime = Date.parse('2026-08-17T00:00:00Z'); + const undated = parseArchiveFilename('4utumn07 - C-IImhvpFuk.jpg', 'highlight', mtime)!; + const dated = parseArchiveFilename('2024-08-04_4utumn07 - C-IImhvpFuk.jpg', 'highlight', mtime)!; + // Same item either way, so re-fetching cannot split it into two posts. + expect(dated.postId).toBe(undated.postId); + expect(undated.date).toBe('2026-08-17'); + expect(dated.date).toBe('2024-08-04'); + }); +});