fix: stop showing a highlight item twice under two naming conventions

JDownloader wrote story-shaped names for highlights during one period of
its life, so the same item exists on disk as both

    0ct0ber19 - C5dQPEYpd9W.mp4
    2024-04-07_0ct0ber19 - 01 - C5dQPEYpd9W.mp4

which parsed to the ids "C5dQPEYpd9W" and "01 - C5dQPEYpd9W" -- two posts
for one item. The leading ordinal is a position within a day's stories
and carries nothing the shortcode does not, so story and highlight ids
drop it. Post ids are untouched, since those are permalinks.

Measured on the two real files, same archive, cache cleared between:
without the fix the profile reads "Heestory - 2 items", with it
"Heestory - 1 item".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-17 13:57:42 -04:00
co-authored by Claude Opus 5
parent 816fa970b5
commit 5d5dea10c8
2 changed files with 63 additions and 3 deletions
+37 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
import { parseArchiveFilename, scopedPostId } from './archive-patterns';
import { canonicalItemId, parseArchiveFilename, scopedPostId } from './archive-patterns';
describe('parseArchiveFilename — Instagram export format', () => {
it('parses a single-image post', () => {
@@ -189,3 +189,39 @@ describe('dateFromMtime', () => {
expect(p.dateFromMtime).toBe(false);
});
});
/**
* JDownloader wrote story-shaped names for highlights during one period, so
* the same item exists under two conventions. They must be one post.
*/
describe('canonicalItemId', () => {
it('collapses the two highlight naming conventions onto one id', () => {
const dir = 'story highlights - 4utumn07 - Sunstory';
const undated = parseArchiveFilename('4utumn07 - C5dQPEYpd9W.mp4', 'highlight', 1)!;
const dated = parseArchiveFilename('2024-04-07_4utumn07 - 01 - C5dQPEYpd9W.mp4', 'highlight')!;
expect(scopedPostId(dated.postId, 'highlight', dir))
.toBe(scopedPostId(undated.postId, 'highlight', dir));
});
it('does the same for stories', () => {
const a = parseArchiveFilename('2025-10-26_u - 2 - DQRuDx9iW5Q.jpg', 'stories')!;
expect(scopedPostId(a.postId, 'stories', 'story - u')).toBe('story - u/DQRuDx9iW5Q');
});
it('keeps distinct story items distinct', () => {
const a = parseArchiveFilename('2026-08-13_u - 1 - Db-UTJcCUUr.mp4', 'stories')!;
const b = parseArchiveFilename('2026-08-13_u - 2 - Db-oNJ1CWQ4.mp4', 'stories')!;
expect(scopedPostId(a.postId, 'stories', 'story - u'))
.not.toBe(scopedPostId(b.postId, 'stories', 'story - u'));
});
it('leaves a shortcode that merely starts with digits alone', () => {
expect(canonicalItemId('4utumn07')).toBe('4utumn07');
expect(canonicalItemId('C5dQPEYpd9W')).toBe('C5dQPEYpd9W');
expect(canonicalItemId('12345')).toBe('12345');
});
it('does not touch posts, whose ids are permalinks', () => {
expect(scopedPostId('01 - ABC', 'posts')).toBe('01 - ABC');
});
});