Skip to content

Commit 0396df8

Browse files
committed
fix: exclude the account owner from their own analysis
Liking or commenting on your own content showed your own account as something that "captures" you. Derive the owner username from the export filename (instagram-<username>-<date>-<hash>.zip) and drop self from both interactions and follows.
1 parent a0f07a0 commit 0396df8

3 files changed

Lines changed: 64 additions & 1 deletion

File tree

src/main.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import JSZip from 'jszip'
22
import { detectAdapter } from './adapters/registry'
3+
import { excludeSelf, ownerFromFilename } from './owner'
34
import { analyze } from './report-model'
45
import { renderReport } from './ui/view'
56
import './style.css'
@@ -38,7 +39,9 @@ async function handleFile(file: File, results: HTMLElement): Promise<void> {
3839
status.textContent = 'Unrecognized export. Is this an Instagram .zip downloaded in JSON format?'
3940
return
4041
}
41-
const data = await adapter.parse(zip)
42+
// Exclude the account owner — you don't influence yourself. The owner is taken
43+
// from the export filename (instagram-<username>-<date>-<hash>.zip).
44+
const data = excludeSelf(await adapter.parse(zip), ownerFromFilename(file.name))
4245
if (data.interactions.length === 0) {
4346
status.textContent = 'No likes/comments found. Did you download in HTML instead of JSON format?'
4447
return

src/owner.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import type { NormalizedData } from './schema'
2+
3+
/**
4+
* Instagram names the export file `instagram-<username>-<YYYY>-<MM>-<DD>-<hash>.zip`.
5+
* The username is the account owner — we use it to exclude self-interactions.
6+
*/
7+
export function ownerFromFilename(filename: string): string | null {
8+
const match = filename.match(/^instagram-(.+?)-\d{4}-\d{2}-\d{2}-/i)
9+
return match ? match[1] : null
10+
}
11+
12+
/**
13+
* Drop the account owner from their own data: commenting on or liking your own
14+
* content is not "being influenced by yourself". Comparison is case-insensitive.
15+
*/
16+
export function excludeSelf(data: NormalizedData, self: string | null): NormalizedData {
17+
if (!self) return data
18+
const key = self.toLowerCase()
19+
return {
20+
interactions: data.interactions.filter((i) => i.account.toLowerCase() !== key),
21+
follows: new Set([...data.follows].filter((a) => a.toLowerCase() !== key)),
22+
unattributed: data.unattributed,
23+
}
24+
}

tests/owner.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { describe, expect, it } from 'vitest'
2+
import type { NormalizedData } from '../src/schema'
3+
import { excludeSelf, ownerFromFilename } from '../src/owner'
4+
5+
describe('ownerFromFilename', () => {
6+
it('extracts the username from an Instagram export filename', () => {
7+
expect(ownerFromFilename('instagram-endika_iglesias-2026-05-30-QSxZWh2s.zip')).toBe(
8+
'endika_iglesias',
9+
)
10+
})
11+
it('returns null for an unrelated filename', () => {
12+
expect(ownerFromFilename('my-export.zip')).toBeNull()
13+
})
14+
})
15+
16+
describe('excludeSelf', () => {
17+
const data: NormalizedData = {
18+
interactions: [
19+
{ account: 'friend', kind: 'story_like', timestamp: 1 },
20+
{ account: 'Endika_Iglesias', kind: 'comment', timestamp: 2 },
21+
],
22+
follows: new Set(['friend', 'endika_iglesias']),
23+
unattributed: 3,
24+
}
25+
26+
it('removes the owner from interactions and follows, case-insensitively', () => {
27+
const out = excludeSelf(data, 'endika_iglesias')
28+
expect(out.interactions.map((i) => i.account)).toEqual(['friend'])
29+
expect(out.follows).toEqual(new Set(['friend']))
30+
expect(out.unattributed).toBe(3)
31+
})
32+
33+
it('is a no-op when the owner is unknown', () => {
34+
expect(excludeSelf(data, null)).toBe(data)
35+
})
36+
})

0 commit comments

Comments
 (0)