all repos — momix @ a4589ea3d5ac14f43d7a7bfff7ffb8b71b170a88

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