Use history.replaceState ageVerified query and improve export-story script

This commit is contained in:
Bad Manners 2024-08-26 14:53:09 -03:00
parent 21a77ed254
commit fb30f1b416
16 changed files with 96 additions and 83 deletions

View file

@ -1,10 +1,13 @@
import { spawn, spawnSync } from "node:child_process";
import { readdir, mkdir, mkdtemp, writeFile, readFile, copyFile } from "node:fs/promises";
import { readdirSync, mkdirSync } from "node:fs";
import { mkdtemp, writeFile, readFile, copyFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join, normalize } from "node:path";
import { createInterface } from "node:readline";
import { program } from "commander";
import fetchRetryWrapper from "fetch-retry";
import type { HealthcheckResponse } from "../src/pages/api/healthcheck";
import type { ExportStoryResponse } from "../src/pages/api/export-story/[...slug]";
function getRTFStyles(rtfSource: string) {
const matches = rtfSource.matchAll(
@ -53,10 +56,10 @@ async function exportStory(slug: string, options: { outputDir: string }) {
const outputDir = normalize(options.outputDir);
let files: string[];
try {
files = await readdir(outputDir);
files = readdirSync(outputDir);
} catch {
files = [];
console.log(`Created directory at ${await mkdir(outputDir, { recursive: true })}`);
console.log(`Created directory at ${mkdirSync(outputDir, { recursive: true })}`);
}
if (files.length > 0) {
console.error(`ERROR: Directory ${outputDir} is not empty!`);
@ -84,7 +87,7 @@ async function exportStory(slug: string, options: { outputDir: string }) {
if (!response.ok) {
throw new Error(response.statusText);
}
const healthcheck: { isAlive: boolean } = await response.json();
const healthcheck: HealthcheckResponse = await response.json();
if (healthcheck.isAlive !== true) {
throw new Error(JSON.stringify(healthcheck));
}
@ -102,32 +105,41 @@ async function exportStory(slug: string, options: { outputDir: string }) {
console.log("Getting data from Astro...");
const response = await fetch(new URL(`/api/export-story/${slug}`, astroURL));
if (!response.ok) {
throw new Error(`Failed to reach API (status code ${response.status})`);
throw new Error(`Failed to reach export-story API (status code ${response.status})`);
}
const data: { story: string; description: Record<string, string>; thumbnail: string | null } =
await response.json();
const data: ExportStoryResponse = await response.json();
// Process response fields in parallel
await Promise.all(
Object.entries(data.description).map(async ([filename, description]) => {
return await writeFile(join(outputDir, filename), description);
}),
[
// Story
(async () => {
storyText = data.story;
await writeFile(join(outputDir, `${slug}.txt`), storyText);
})(),
// Descriptions
Object.entries(data.description).map(
async ([filename, description]) => await writeFile(join(outputDir, filename), description),
),
// Thumbnail
(async () => {
if (data.thumbnail) {
if (data.thumbnail.startsWith("/@fs/")) {
const thumbnailPath = data.thumbnail
.replace(/^\/@fs/, "")
.replace(/\?(&?[a-z][a-zA-Z0-9_-]+=[a-zA-Z0-9_-]*)*$/, "");
await copyFile(thumbnailPath, join(outputDir, `thumbnail${thumbnailPath.match(/\.[^.]+$/)![0]}`));
} else {
const thumbnail = await fetchRetry(data.thumbnail, { retries: 2, retryDelay: 10000 });
if (!thumbnail.ok) {
throw new Error("Failed to get thumbnail");
}
const thumbnailExt = thumbnail.headers.get("Content-Type")!.startsWith("image/png") ? "png" : "jpg";
await writeFile(join(outputDir, `thumbnail.${thumbnailExt}`), Buffer.from(await thumbnail.arrayBuffer()));
}
}
})(),
].flat(),
);
if (data.thumbnail) {
if (data.thumbnail.startsWith("/@fs/")) {
const thumbnailPath = data.thumbnail
.replace(/^\/@fs/, "")
.replace(/\?(&?[a-z][a-zA-Z0-9_-]+=[a-zA-Z0-9_-]*)*$/, "");
await copyFile(thumbnailPath, join(outputDir, `thumbnail${thumbnailPath.match(/\.[^.]+$/)![0]}`));
} else {
const thumbnail = await fetch(data.thumbnail);
if (!thumbnail.ok) {
throw new Error("Failed to get thumbnail");
}
const thumbnailExt = thumbnail.headers.get("Content-Type")!.startsWith("image/png") ? "png" : "jpg";
await writeFile(join(outputDir, `thumbnail.${thumbnailExt}`), Buffer.from(await thumbnail.arrayBuffer()));
}
}
storyText = data.story;
writeFile(join(outputDir, `${slug}.txt`), storyText);
} finally {
if (devServerProcess) {
console.log("Shutting down the Astro development server...");
@ -139,18 +151,29 @@ async function exportStory(slug: string, options: { outputDir: string }) {
/* Parse story into output formats */
console.log("Parsing story into output formats...");
await writeFile(join(outputDir, `${slug}.md`), storyText.replaceAll(/=(?==)/g, "= ").replaceAll("*", "\\*"));
const tempDir = await mkdtemp(join(tmpdir(), "export-story-"));
await writeFile(join(tempDir, "temp.txt"), storyText.replaceAll(/\n\n+/g, "\n"));
spawnSync("libreoffice", ["--convert-to", "rtf:Rich Text Format", "--outdir", tempDir, join(tempDir, "temp.txt")], {
stdio: "ignore",
});
const rtfText = await readFile(join(tempDir, "temp.rtf"), "utf-8");
const rtfStyles = getRTFStyles(rtfText);
await writeFile(
join(outputDir, `${slug}.rtf`),
rtfText.replaceAll(rtfStyles["Preformatted Text"], rtfStyles["Normal"]),
);
// Process output files in parallel
await Promise.all([
// ${slug}.md
writeFile(join(outputDir, `${slug}.md`), storyText.replaceAll(/=(?==)/g, "= ").replaceAll("*", "\\*")),
// ${slug}.rtf
(async () => {
const tempDir = await mkdtemp(join(tmpdir(), "export-story-"));
await writeFile(join(tempDir, "temp.txt"), storyText.replaceAll(/\n\n+/g, "\n"));
spawnSync(
"libreoffice",
["--convert-to", "rtf:Rich Text Format", "--outdir", tempDir, join(tempDir, "temp.txt")],
{
stdio: "ignore",
},
);
const rtfText = await readFile(join(tempDir, "temp.rtf"), "utf-8");
const rtfStyles = getRTFStyles(rtfText);
await writeFile(
join(outputDir, `${slug}.rtf`),
rtfText.replaceAll(rtfStyles["Preformatted Text"], rtfStyles["Normal"]),
);
})(),
]);
console.log("Success!");
process.exit(0);
}