feat: fallback to unsplash cover when article contains no image

This commit is contained in:
Max Richter
2025-11-09 23:52:53 +01:00
parent 6c6b69a46a
commit 655fc648e6
27 changed files with 687 additions and 224 deletions

View File

@@ -10,7 +10,6 @@ import { parseJsonLdToRecipeSchema } from "./parseJsonLd.ts";
import z from "zod";
import { createResource } from "@lib/marka/index.ts";
import { webScrape } from "@lib/webScraper.ts";
import { Defuddle } from "defuddle/node";
import { RecipeResource } from "@lib/marka/schema.ts";
const log = createLogger("api/article");
@@ -23,18 +22,14 @@ async function processCreateRecipeFromUrl(
) {
log.info("create article from url", { url: fetchUrl });
streamResponse.enqueue("downloading article");
streamResponse.info("downloading article");
const doc = await webScrape(fetchUrl, streamResponse);
const result = await webScrape(fetchUrl, streamResponse);
const result = await Defuddle(doc, fetchUrl, {
markdown: true,
});
streamResponse.enqueue("download success");
streamResponse.info("download success");
const jsonLds = Array.from(
doc?.querySelectorAll(
result.dom?.querySelectorAll(
"script[type='application/ld+json']",
),
) as unknown as HTMLScriptElement[];
@@ -48,11 +43,11 @@ async function processCreateRecipeFromUrl(
}
if (!recipe) {
const res = await openai.extractRecipe(result.content);
const res = await openai.extractRecipe(result.markdown);
if (!res || "errorMessages" in res) {
const errorMessage = res?.errorMessages?.[0] ||
"could not extract recipe";
streamResponse.enqueue(`failed to extract recipe: ${errorMessage}`);
streamResponse.error(`failed to extract recipe: ${errorMessage}`);
return;
}
recipe = res;
@@ -61,7 +56,7 @@ async function processCreateRecipeFromUrl(
const id = toUrlSafeString(recipe?.name || "");
if (!recipe) {
streamResponse.enqueue("failed to parse recipe");
streamResponse.error("failed to parse recipe");
streamResponse.cancel();
return;
}
@@ -80,11 +75,11 @@ async function processCreateRecipeFromUrl(
const finalPath = `resources/recipes/images/${
safeFileName(id)
}_cover.${extension}`;
streamResponse.enqueue("downloading image");
streamResponse.info("downloading image");
try {
streamResponse.enqueue("downloading image");
streamResponse.info("downloading image");
const res = await fetch(newRecipe.image);
streamResponse.enqueue("saving image");
streamResponse.info("saving image");
const buffer = await res.arrayBuffer();
await createResource(finalPath, buffer);
newRecipe.image = finalPath;
@@ -93,11 +88,11 @@ async function processCreateRecipeFromUrl(
}
}
streamResponse.enqueue("finished processing, creating file");
streamResponse.info("finished processing, creating file");
await createResource(`recipes/${id}.md`, newRecipe);
streamResponse.enqueue("id: " + id);
streamResponse.send({ type: "finished", url: id });
}
export const handler: Handlers = {
@@ -119,7 +114,7 @@ export const handler: Handlers = {
processCreateRecipeFromUrl({ fetchUrl, streamResponse }).then((article) => {
log.debug("created article from link", { article });
}).catch((err) => {
streamResponse.enqueue(`error creating recipe: ${err}`);
streamResponse.error(`creating recipe: ${err}`);
log.error(err);
}).finally(() => {
streamResponse.cancel();