all repos — momix @ main

A CLI tool to manage recipes for Thermomix

services.ts (view raw)

 1import type { DatabaseSync } from "node:sqlite";
 2import { callLLM } from "./utils/llm.ts";
 3import { decodeHtmlChars } from "./utils/html.ts";
 4
 5export const getRecipesList = (db: DatabaseSync, ids?: number[]): Recipe[] => {
 6  if (ids)
 7    return db
 8      .prepare(
 9        `SELECT * FROM recipes WHERE id IN (${ids
10          .map(() => "?")
11          .join(",")}) ORDER BY name`
12      )
13      .all(...ids);
14  else return db.prepare("SELECT * FROM recipes ORDER BY name").all();
15};
16
17export const storeRecipe = async (db: DatabaseSync, recipeId: string) => {
18  let recipeUrl = "";
19  if (!recipeId) throw new Error("Please provide a recipe ID");
20  else if (recipeId.startsWith("https://")) recipeUrl = recipeId;
21  else recipeUrl = `https://cookidoo.fr/recipes/recipe/fr-FR/${recipeId}`;
22
23  const recipePage = await fetch(recipeUrl).then((res) => res.text());
24  const jsonLdTag = recipePage.match(
25    /<script type="application\/ld\+json">(.*?)<\/script>/s
26  )?.[1];
27  if (!jsonLdTag) throw new Error("No JSON-LD tag found");
28
29  const recipeJson = JSON.parse(jsonLdTag);
30
31  const existingItem = db
32    .prepare("SELECT * FROM recipes WHERE url = ?")
33    .get(recipeUrl);
34  if (existingItem) {
35    console.warn("Recipe already exists in db");
36    return null;
37  }
38
39  const rawIngredientsJson = await callLLM(`
40        À partir de la liste suivante, renvoi un objet JSON avec unit, quantity, name pour chacun des éléments.
41        ${decodeHtmlChars(recipeJson.recipeIngredient.join("\n"))}
42        Si la quantité est un nombre décimal, convertis le.
43        Utilise de préférences les unités les plus courantes comme g, pincée, cuillère à café, gousse.
44        Les unités doivent toujours être au singulier.
45      `);
46
47  const recipe = {
48    name: recipeJson.name,
49    ingredients: rawIngredientsJson,
50    imageUrl: recipeJson.image,
51    url: recipeUrl,
52  };
53
54  const storedRecipe = db
55    .prepare(
56      `INSERT INTO recipes (url, name, ingredients, imageUrl) VALUES (?, ?, ?, ?)`
57    )
58    .run(recipe.url, recipe.name, recipe.ingredients, recipe.imageUrl);
59
60  console.log(`New recipe "${recipe.name}" added to db.`);
61
62  return storedRecipe;
63};
64
65export const getGroceryList = (
66  db: DatabaseSync,
67  recipeIds: number[]
68): GroceryItem[] => {
69  const recipes = db
70    .prepare(`SELECT * FROM recipes WHERE id IN (${recipeIds.join(",")})`)
71    .all();
72
73  const rawIngredients = recipes.flatMap((recipe) =>
74    JSON.parse(recipe.ingredients as string)
75  );
76
77  return rawIngredients.reduce((acc, ingredient) => {
78    const existingIngredient = acc.find(
79      (i) => i.name === ingredient.name && i.unit === ingredient.unit
80    );
81    if (existingIngredient) existingIngredient.value += ingredient.value;
82    else acc.push(ingredient);
83    return acc;
84  }, [] as { name: string; value: number; unit: string }[]);
85};