Skip to content

Commit f077368

Browse files
committed
fix: #76 - Prevent image filename thrashing after Notion url change
1 parent 75500d7 commit f077368

File tree

2 files changed

+58
-15
lines changed

2 files changed

+58
-15
lines changed

src/MakeImagePersistencePlan.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,12 @@ export function makeImagePersistencePlan(
1212
// Since most images come from pasting screenshots, there isn't normally a filename. That's fine, we just make a hash of the url
1313
// Images that are stored by notion come to us with a complex url that changes over time, so we pick out the UUID that doesn't change. Example:
1414
// https://s3.us-west-2.amazonaws.com/secure.notion-static.com/d1058f46-4d2f-4292-8388-4ad393383439/Untitled.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20220516%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20220516T233630Z&X-Amz-Expires=3600&X-Amz-Signature=f215704094fcc884d37073b0b108cf6d1c9da9b7d57a898da38bc30c30b4c4b5&X-Amz-SignedHeaders=host&x-id=GetObject
15+
// But around Sept 2023, they changed the url to be something like:
16+
// https://prod-files-secure.s3.us-west-2.amazonaws.com/d9a2b712-cf69-4bd6-9d65-87a4ceeacca2/d1bcdc8c-b065-4e40-9a11-392aabeb220e/Untitled.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20230915%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20230915T161258Z&X-Amz-Expires=3600&X-Amz-Signature=28fca48e65fba86d539c3c4b7676fce1fa0857aa194f7b33dd4a468ecca6ab24&X-Amz-SignedHeaders=host&x-id=GetObject
17+
// The thing we want is the last UUID before the ?
1518

16-
let thingToHash = imageSet.primaryUrl;
17-
const m = /.*secure\.notion-static\.com\/(.*)\//gm.exec(
18-
imageSet.primaryUrl
19-
);
20-
if (m && m.length > 1) {
21-
thingToHash = m[1];
22-
}
19+
const urlBeforeQuery = imageSet.primaryUrl.split("?")[0];
20+
const thingToHash = findLastUuid(urlBeforeQuery) ?? urlBeforeQuery;
2321

2422
const hash = hashOfString(thingToHash);
2523
imageSet.outputFileName = `${hash}.${imageSet.fileType.ext}`;
@@ -50,7 +48,18 @@ export function makeImagePersistencePlan(
5048
}
5149
}
5250

53-
function hashOfString(s: string) {
51+
function findLastUuid(url: string): string | null {
52+
// Regex for a UUID surrounded by slashes
53+
const uuidPattern =
54+
/(?<=\/)[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}(?=\/)/gi;
55+
56+
// Find all UUIDs
57+
const uuids = url.match(uuidPattern);
58+
// Return the last UUID if any exist, else return null
59+
return uuids ? uuids[uuids.length - 1].trim() : null;
60+
}
61+
62+
export function hashOfString(s: string): number {
5463
let hash = 0;
5564
for (let i = 0; i < s.length; ++i)
5665
hash = Math.imul(31, hash) + s.charCodeAt(i);

src/makeImagePersistencePlan.spec.ts

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { makeImagePersistencePlan } from "./MakeImagePersistencePlan";
1+
import {
2+
hashOfString,
3+
makeImagePersistencePlan,
4+
} from "./MakeImagePersistencePlan";
25
import { ImageSet } from "./images";
36

47
test("primary file with explicit file output path and prefix", () => {
@@ -9,11 +12,16 @@ test("primary file with explicit file output path and prefix", () => {
912
fileType: { ext: "png", mime: "image/png" },
1013
};
1114
makeImagePersistencePlan(imageSet, "./static/notion_imgs", "/notion_imgs");
12-
expect(imageSet.outputFileName).toBe("463556435.png");
15+
const expectedHash = hashOfString(
16+
"https://s3.us-west-2.amazonaws.com/primaryImage"
17+
);
18+
expect(imageSet.outputFileName).toBe(`${expectedHash}.png`);
1319
expect(imageSet.primaryFileOutputPath).toBe(
14-
"static/notion_imgs/463556435.png"
20+
`static/notion_imgs/${expectedHash}.png`
21+
);
22+
expect(imageSet.filePathToUseInMarkdown).toBe(
23+
`/notion_imgs/${expectedHash}.png`
1524
);
16-
expect(imageSet.filePathToUseInMarkdown).toBe("/notion_imgs/463556435.png");
1725
});
1826
test("primary file with defaults for image output path and prefix", () => {
1927
const imageSet: ImageSet = {
@@ -23,13 +31,39 @@ test("primary file with defaults for image output path and prefix", () => {
2331
fileType: { ext: "png", mime: "image/png" },
2432
};
2533
makeImagePersistencePlan(imageSet, "", "");
26-
expect(imageSet.outputFileName).toBe("463556435.png");
34+
const expectedHash = hashOfString(
35+
"https://s3.us-west-2.amazonaws.com/primaryImage"
36+
);
37+
expect(imageSet.outputFileName).toBe(`${expectedHash}.png`);
2738

2839
// the default behavior is to put the image next to the markdown file
2940
expect(imageSet.primaryFileOutputPath).toBe(
30-
"/pathToParentSomewhere/463556435.png"
41+
`/pathToParentSomewhere/${expectedHash}.png`
3142
);
32-
expect(imageSet.filePathToUseInMarkdown).toBe("./463556435.png");
43+
expect(imageSet.filePathToUseInMarkdown).toBe(`./${expectedHash}.png`);
44+
});
45+
46+
test("properly extract UUID from old-style notion image url", () => {
47+
const imageSet: ImageSet = {
48+
primaryUrl:
49+
"https://s3.us-west-2.amazonaws.com/secure.notion-static.com/e1058f46-4d2f-4292-8388-4ad393383439/Untitled.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20220516%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20220516T233630Z&X-Amz-Expires=3600&X-Amz-Signature=f215704094fcc884d37073b0b108cf6d1c9da9b7d57a898da38bc30c30b4c4b5&X-Amz-SignedHeaders=host&x-id=GetObject",
50+
localizedUrls: [],
51+
fileType: { ext: "png", mime: "image/png" },
52+
};
53+
makeImagePersistencePlan(imageSet, "./static/notion_imgs", "/notion_imgs");
54+
const expectedHash = hashOfString("e1058f46-4d2f-4292-8388-4ad393383439");
55+
expect(imageSet.outputFileName).toBe(`${expectedHash}.png`);
56+
});
57+
test("properly extract UUID from new-style (Sept 2023) notion image url", () => {
58+
const imageSet: ImageSet = {
59+
primaryUrl:
60+
"https://prod-files-secure.s3.us-west-2.amazonaws.com/d9a2b712-cf69-4bd6-9d65-87a4ceeacca2/d1bcdc8c-b065-4e40-9a11-392aabeb220e/Untitled.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20230915%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20230915T161258Z&X-Amz-Expires=3600&X-Amz-Signature=28fca48e65fba86d539c3c4b7676fce1fa0857aa194f7b33dd4a468ecca6ab24&X-Amz-SignedHeaders=host&x-id=GetObject",
61+
localizedUrls: [],
62+
fileType: { ext: "png", mime: "image/png" },
63+
};
64+
makeImagePersistencePlan(imageSet, "./static/notion_imgs", "/notion_imgs");
65+
const expectedHash = hashOfString("d1bcdc8c-b065-4e40-9a11-392aabeb220e");
66+
expect(imageSet.outputFileName).toBe(`${expectedHash}.png`);
3367
});
3468

3569
// In order to make image fallback work with other languages, we have to have

0 commit comments

Comments
 (0)