Skip to content

Commit 2de4486

Browse files
committed
Merge branch 'main' into release
2 parents 522d1bb + 3dae3ad commit 2de4486

File tree

7 files changed

+184
-113
lines changed

7 files changed

+184
-113
lines changed

package-lock.json

Lines changed: 0 additions & 48 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
"//note: ts-node": "really is a runtime dependency",
2727
"dependencies": {
2828
"@notionhq/client": "2.2.3",
29-
"axios": "^1.6.8",
3029
"chalk": "^4.1.2",
3130
"commander": "^9.2.0",
3231
"cosmiconfig": "^8.0.0",

src/assets.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import * as fs from "fs-extra";
2+
import * as Path from "path";
3+
import { verbose } from "./log";
4+
5+
export enum AssetType {
6+
Image = "image",
7+
Video = "video",
8+
}
9+
10+
export function writeAsset(path: string, buffer: Buffer): void {
11+
// Note: it's tempting to not spend time writing this out if we already have
12+
// it from a previous run. But we don't really know it's the same. A) it
13+
// could just have the same name, B) it could have been previously
14+
// unlocalized and thus filled with a copy of the primary language image
15+
// while and now is localized.
16+
if (fs.pathExistsSync(path)) {
17+
verbose("Replacing asset " + path);
18+
} else {
19+
verbose("Adding asset " + path);
20+
fs.mkdirsSync(Path.dirname(path));
21+
}
22+
fs.createWriteStream(path).write(buffer); // async but we're not waiting
23+
}

src/images.ts

Lines changed: 20 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
import * as fs from "fs-extra";
22
import FileType, { FileTypeResult } from "file-type";
3-
import axios from "axios";
4-
import * as Path from "path";
53
import { makeImagePersistencePlan } from "./MakeImagePersistencePlan";
6-
import { warning, logDebug, verbose, info } from "./log";
4+
import { logDebug, verbose } from "./log";
75
import { ListBlockChildrenResponseResult } from "notion-to-md/build/types";
86
import {
97
IDocuNotionContext,
108
IDocuNotionContextPageInfo,
119
IPlugin,
1210
} from "./plugins/pluginTypes";
11+
import { writeAsset } from "./assets";
1312

1413
// We handle several things here:
1514
// 1) copy images locally instead of leaving them in Notion
@@ -150,19 +149,26 @@ async function processImageBlock(
150149
async function readPrimaryImage(imageSet: ImageSet) {
151150
// In Mar 2024, we started having a problem getting a particular gif from imgur using
152151
// node-fetch. Switching to axios resolved it. I don't know why.
153-
const response = await axios.get(imageSet.primaryUrl, {
154-
responseType: "arraybuffer",
155-
});
156-
imageSet.primaryBuffer = Buffer.from(response.data, "utf-8");
152+
// Then, in Apr 2025, we started getting 429 responses from imgur through axios,
153+
// so we switched to node's built-in fetch (different than the node-fetch package).
154+
// Just a guess, but probably imgur keeps locking down what it suspects as code running
155+
// to scrape images.
156+
// Apparently, imgur is getting to be more and more of a liability,
157+
// so we should probably stop using it.
158+
const response = await fetch(imageSet.primaryUrl);
159+
const arrayBuffer = await response.arrayBuffer();
160+
imageSet.primaryBuffer = Buffer.from(arrayBuffer);
157161
imageSet.fileType = await FileType.fromBuffer(imageSet.primaryBuffer);
158162
}
159163

160164
async function saveImage(imageSet: ImageSet): Promise<void> {
161-
writeImageIfNew(imageSet.primaryFileOutputPath!, imageSet.primaryBuffer!);
165+
const path = imageSet.primaryFileOutputPath!;
166+
imageWasSeen(path);
167+
writeAsset(path, imageSet.primaryBuffer!);
162168

163169
for (const localizedImage of imageSet.localizedUrls) {
164170
let buffer = imageSet.primaryBuffer!;
165-
// if we have a urls for the localized screenshot, download it
171+
// if we have a url for the localized screenshot, download it
166172
if (localizedImage?.url.length > 0) {
167173
verbose(`Retrieving ${localizedImage.iso632Code} version...`);
168174
const response = await fetch(localizedImage.url);
@@ -180,30 +186,15 @@ async function saveImage(imageSet: ImageSet): Promise<void> {
180186
imageSet.pageInfo!.relativeFilePathToFolderContainingPage
181187
}`;
182188

183-
writeImageIfNew(
184-
(directory + "/" + imageSet.outputFileName!).replaceAll("//", "/"),
185-
buffer
189+
const newPath = (directory + "/" + imageSet.outputFileName!).replaceAll(
190+
"//",
191+
"/"
186192
);
193+
imageWasSeen(newPath);
194+
writeAsset(newPath, buffer);
187195
}
188196
}
189197

190-
function writeImageIfNew(path: string, buffer: Buffer) {
191-
imageWasSeen(path);
192-
193-
// Note: it's tempting to not spend time writing this out if we already have
194-
// it from a previous run. But we don't really know it's the same. A) it
195-
// could just have the same name, B) it could have been previously
196-
// unlocalized and thus filled with a copy of the primary language image
197-
// while and now is localized.
198-
if (fs.pathExistsSync(path)) {
199-
verbose("Replacing image " + path);
200-
} else {
201-
verbose("Adding image " + path);
202-
fs.mkdirsSync(Path.dirname(path));
203-
}
204-
fs.createWriteStream(path).write(buffer); // async but we're not waiting
205-
}
206-
207198
export function parseImageBlock(image: any): ImageSet {
208199
if (!locales) throw Error("Did you call initImageHandling()?");
209200
const imageSet: ImageSet = {

src/plugins/VideoTransformer.spec.ts

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,28 @@
1+
import * as fs from "fs-extra";
12
import { setLogLevel } from "../log";
23
import { NotionBlock } from "../types";
34
import { standardVideoTransformer } from "./VideoTransformer";
4-
import { blocksToMarkdown } from "./pluginTestRun";
5+
import { blocksToMarkdown, kTemporaryTestDirectory } from "./pluginTestRun";
6+
7+
beforeAll(async () => {
8+
try {
9+
if (await fs.pathExists(kTemporaryTestDirectory)) {
10+
await fs.emptyDir(kTemporaryTestDirectory);
11+
} else {
12+
await fs.mkdirp(kTemporaryTestDirectory);
13+
}
14+
} catch (err) {
15+
console.error("Error in beforeAll:", err);
16+
}
17+
});
18+
19+
afterAll(async () => {
20+
try {
21+
await fs.remove(kTemporaryTestDirectory);
22+
} catch (err) {
23+
console.error("Error in afterAll:", err);
24+
}
25+
});
526

627
test("youtube embedded", async () => {
728
const config = { plugins: [standardVideoTransformer] };
@@ -89,6 +110,9 @@ test("video link, not embedded", async () => {
89110
test("direct upload to to Notion (embedded)", async () => {
90111
setLogLevel("verbose");
91112
const config = { plugins: [standardVideoTransformer] };
113+
114+
const fileName1 = "first_video.mp4";
115+
const fileName2 = "second_video.mp4";
92116
const result = await blocksToMarkdown(config, [
93117
{
94118
object: "block",
@@ -103,13 +127,39 @@ test("direct upload to to Notion (embedded)", async () => {
103127
caption: [],
104128
type: "file",
105129
file: {
106-
url: "https://s3.us-west-2.amazonaws.com/secure.notion-static.com/f6bc4746-011e-2124-86ca-ed4337d70891/people_fre_motionAsset_p3.mp4?X-Blah-blah",
130+
url: `https://s3.us-west-2.amazonaws.com/secure.notion-static.com/f6bc4746-011e-2124-86ca-ed4337d70891/${fileName1}?X-Blah-blah`,
131+
},
132+
},
133+
} as unknown as NotionBlock,
134+
{
135+
object: "block",
136+
id: "12f7db3b-4412-4be9-a3f7-6ac423fee94b",
137+
parent: {
138+
type: "page_id",
139+
page_id: "edaffeb2-ece8-4d44-976f-351e6b5757bb",
140+
},
141+
142+
type: "video",
143+
video: {
144+
caption: [],
145+
type: "file",
146+
file: {
147+
url: `https://s3.us-west-2.amazonaws.com/secure.notion-static.com/f6bc4746-011e-2124-86ca-ed4337d70891/${fileName2}?X-Blah-blah`,
107148
},
108149
},
109150
} as unknown as NotionBlock,
110151
]);
152+
111153
expect(result).toContain(`import ReactPlayer from "react-player";`);
112-
expect(result).toContain(
113-
`<ReactPlayer controls url="https://s3.us-west-2.amazonaws.com/secure.notion-static.com/f6bc4746-011e-2124-86ca-ed4337d70891/people_fre_motionAsset_p3.mp4?X-Blah-blah" />`
114-
);
154+
expect(result).toContain(`import video1 from "./${fileName1}";`);
155+
expect(result).toContain(`import video2 from "./${fileName2}";`);
156+
expect(result).toContain(`<ReactPlayer controls url={video1} />`);
157+
expect(result).toContain(`<ReactPlayer controls url={video2} />`);
158+
159+
// Wait half a second for the files to be written
160+
await new Promise(resolve => setTimeout(resolve, 500));
161+
162+
// We should have actually created files in "tempTestFileDir/"
163+
expect(await fs.pathExists("tempTestFileDir/" + fileName1)).toBe(true);
164+
expect(await fs.pathExists("tempTestFileDir/" + fileName2)).toBe(true);
115165
});

0 commit comments

Comments
 (0)