Nouvelle source de recettes : ohmonbento.com #40

Closed
opened 2026-09-05 17:43:23 +02:00 by kyuno · 0 comments
Owner

Contexte

Ajouter https://ohmonbento.com (blog de cuisine japonaise/bento) comme
nouvelle source de recettes importable — 5e adaptateur après TheMealDB,
Marmiton, 750g, Manger Bouger (+ Quitoque déjà planifié mais pas encore
implémenté, issue #37).

Vérification de fraîcheur faite avant de planifier celui-ci : aucun drift
depuis le ticket Quitoque — toujours exactement les 4 mêmes adaptateurs
enregistrés dans apps/api/src/sources/index.ts, quitoque.ts n'existe
toujours pas, recipe-source-adapter.ts/json-ld-recipe.ts/marmiton.ts
inchangés. Détail non noté sur #37 : il existe aussi
apps/api/test/recipe-sources/json-ld-recipe.test.ts, recipe-source.test.ts
et recipe-source-sync.test.ts — tests du bloc générique/registre, pas
d'un site précis, à ne pas confondre avec le futur ohmonbento.test.ts.

Vérifications effectuées sur ohmonbento.com

C'est le candidat le plus simple des 5 — plus proche de Marmiton
(délégation intégrale, zéro patch JSON-LD) que de 750g/Manger Bouger (qui
corrigent chacun un vrai bug du JSON-LD de leur site) :

  • robots.txt : autorise tout sauf /wp-admin/ — site WordPress
    classique (confirmé aussi par admin-ajax.php, les chemins
    /wp-content/uploads/...).
  • Recherche vide = tout le catalogue, confirmé par la demande et
    vérifié en direct : https://ohmonbento.com/?s= liste bien toutes les
    recettes ("SEARCH RESULTS").
  • Pagination : ?s=&paged=N — vérifié en direct, fonctionne
    identiquement pour une recherche vide ou non (pas de branchement
    nécessaire côté adaptateur, contrairement à 750g). Le site n'a que 23
    pages
    au total aujourd'hui (?s=&paged=2 → titre de page "Page 2 à
    23"). Page hors limites (paged=24) → vraie 404 (vérifié via le
    réseau) — même signal de fin que Marmiton, pas l'heuristique plus fragile
    de 750g ("page sans carte").
  • Page détail d'une recette (/kamameshi/ — permalien plat, sans
    préfixe) : JSON-LD WP Recipe Maker (confirmé par la signature
    #wprm-recipe-<id>-step-... dans les URLs d'étapes) enveloppé dans un
    @graph Yoast SEO (Person, WebSite, ImageObject, BreadcrumbList, WebPage, Person, BlogPosting, Recipe) — vérifié contre le code réel
    que findRecipeNode (json-ld-recipe.ts:79-92) gère déjà nativement ce
    cas précis : un objet racine sans @type mais avec un @graph tableau
    → récursion dans le tableau → trouve le nœud Recipe. Zéro retouche
    nécessaire
    , comme Marmiton, contrairement à 750g/Manger Bouger.
    • recipeYield: ["2", "2 portions"] → extractPortions prend déjà le
      premier élément d'un tableau puis regex \d+ → 2. Géré nativement,
      aucun changement requis.
    • recipeIngredient : lignes françaises propres ("300 g de riz rond
      japonais (Japonica)", "1 c. à soupe de sauce soja claire (usukuchi
      shōyu)"...). Deux petites bizarreries WPRM à surveiller (pas à
      corriger dans l'adaptateur, même sort que les cas similaires déjà
      documentés sur le ticket Quitoque) : double espace quand aucune unité
      n'est précisée ("3 champignons shiitake"), et notation en fraction
      ("1/2 c. à café de sel") — au pire un ingrédient finit non résolu,
      dans le flux de revue manuelle déjà existant.
    • recipeInstructions : HowToStep[] avec text/name identiques —
      flattenInstructions prend déjà .text en priorité, sans conséquence.
  • Cartes de la page de recherche : pas de ItemList JSON-LD
    (contrairement à Marmiton — vérifié : seulement Person, WebSite, BreadcrumbList, SearchResultsPage sur cette page), mais chaque carte est
    un seul élément simple à scraper, encore plus direct qu'avec Quitoque
    (pas de piège lazy-load data-src) :
    <div class="entry-thumb"><a href="/<slug>/"><img alt="<titre réel>" src="<url image réelle>"></a></div>.

Conception proposée

Nouveau fichier apps/api/src/sources/ohmonbento.ts, structure calquée sur
marmiton.ts pour fetchDetail/parse (délégation intégrale + helper
rekeySourceError dupliqué comme dans les 3 autres adaptateurs scrapés),
mais avec un list() par scraping de cartes HTML façon 750g/Quitoque —
plus simple ici, un seul élément par carte :

const SEARCH_URL = "https://ohmonbento.com/";

export const ohMonBentoAdapter: RecipeSourceAdapter<{ html: string; url: string }> = {
  key: "ohMonBento",
  name: "Oh mon Bento",
  official: false, // même raisonnement que les 4 adaptateurs scrapés existants
  iconUrl: "https://ohmonbento.com/favicon.ico", // à confirmer à l'implémentation
  locale: "fr",

  async list(params) {
    // GET {SEARCH_URL}?s={query}&paged={page} — page=1 implicite si cursor
    // absent ; MÊME URL pour requête vide ou non (pas de branchement comme
    // 750g). 404 → { items: [], nextCursor: null } (fin de liste, comme
    // marmiton.ts). Sinon, un regex par carte
    // <div class="entry-thumb"><a href="...">...<img alt="..." src="...">
    // récupère externalId (l'URL elle-même)/title/picture/url.
    // nextCursor: items.length > 0 ? String(page + 1) : null.
  },

  async fetchDetail(externalId) {
    // Délègue à jsonLdRecipeAdapter.fetchDetail, rekeySourceError comme marmiton.ts:188-197.
  },

  parse(raw) {
    // Délègue à jsonLdRecipeAdapter.parse — vérifié ci-dessus : aucune
    // retouche nécessaire — même wrapping rekeySourceError.
  },
};

Fichiers à toucher

  • apps/api/src/sources/ohmonbento.ts (nouveau) — l'adaptateur ci-dessus.
  • apps/api/src/sources/index.ts — ajoute
    registerRecipeSource(ohMonBentoAdapter) dans registerAllRecipeSources().
  • apps/api/test/recipe-sources/ohmonbento.test.ts (nouveau) — même
    convention stubFetchHtml/globalThis.fetch que marmiton.test.ts/
    750g.test.ts/manger-bouger.test.ts. Cas à couvrir : list() parse
    bien les cartes d'une page fixture, requête vide et non vide sur la même
    URL, 404 → fin de liste (items: [], nextCursor: null) ;
    fetchDetail/parse délèguent bien et re-labellisent sourceKey en cas
    d'erreur ; parse sur une fixture JSON-LD réelle (forme @graph Yoast +
    Recipe WPRM) produit les bons portions/ingredients/steps.
  • specs/backend-architecture.md — ajoute la section ohmonbento au même
    format que les autres adaptateurs (sans corriger la staleness déjà
    connue du reste de cette section — signalée sur le ticket #37, hors
    sujet ici aussi).

Hors scope

  • Recherche par mot-clé réel non explorée au-delà de la confirmation que
    ?s=<terme>&paged=N est la même URL que la recherche vide — le
    mécanisme est assez simple pour ne pas nécessiter plus d'investigation
    avant l'implémentation.
  • Généricisation du scraping de cartes (Quitoque, 750g et maintenant
    ohmonbento partagent un motif proche) en helper commun — à envisager si
    un futur 6e site suit le même besoin, pas avant.

🤖 Filed by Claude Code

## Contexte Ajouter https://ohmonbento.com (blog de cuisine japonaise/bento) comme nouvelle source de recettes importable — 5e adaptateur après TheMealDB, Marmiton, 750g, Manger Bouger (+ Quitoque déjà planifié mais pas encore implémenté, [issue #37](https://git.dev.kyuno.fr/kyuno/batchCooking/issues/37)). Vérification de fraîcheur faite avant de planifier celui-ci : aucun drift depuis le ticket Quitoque — toujours exactement les 4 mêmes adaptateurs enregistrés dans `apps/api/src/sources/index.ts`, `quitoque.ts` n'existe toujours pas, `recipe-source-adapter.ts`/`json-ld-recipe.ts`/`marmiton.ts` inchangés. Détail non noté sur #37 : il existe aussi `apps/api/test/recipe-sources/json-ld-recipe.test.ts`, `recipe-source.test.ts` et `recipe-source-sync.test.ts` — tests du bloc générique/registre, pas d'un site précis, à ne pas confondre avec le futur `ohmonbento.test.ts`. ## Vérifications effectuées sur ohmonbento.com C'est le candidat le plus simple des 5 — plus proche de **Marmiton** (délégation intégrale, zéro patch JSON-LD) que de 750g/Manger Bouger (qui corrigent chacun un vrai bug du JSON-LD de leur site) : - **robots.txt** : autorise tout sauf `/wp-admin/` — site WordPress classique (confirmé aussi par `admin-ajax.php`, les chemins `/wp-content/uploads/...`). - **Recherche vide = tout le catalogue**, confirmé par la demande et vérifié en direct : `https://ohmonbento.com/?s=` liste bien toutes les recettes ("SEARCH RESULTS"). - **Pagination** : `?s=&paged=N` — vérifié en direct, fonctionne **identiquement** pour une recherche vide ou non (pas de branchement nécessaire côté adaptateur, contrairement à 750g). Le site n'a que **23 pages** au total aujourd'hui (`?s=&paged=2` → titre de page "Page 2 à 23"). Page hors limites (`paged=24`) → **vraie 404** (vérifié via le réseau) — même signal de fin que Marmiton, pas l'heuristique plus fragile de 750g ("page sans carte"). - **Page détail d'une recette** (`/kamameshi/` — permalien plat, sans préfixe) : JSON-LD **WP Recipe Maker** (confirmé par la signature `#wprm-recipe-<id>-step-...` dans les URLs d'étapes) enveloppé dans un `@graph` Yoast SEO (`Person, WebSite, ImageObject, BreadcrumbList, WebPage, Person, BlogPosting, Recipe`) — **vérifié contre le code réel** que `findRecipeNode` (`json-ld-recipe.ts:79-92`) gère déjà nativement ce cas précis : un objet racine sans `@type` mais avec un `@graph` tableau → récursion dans le tableau → trouve le nœud `Recipe`. **Zéro retouche nécessaire**, comme Marmiton, contrairement à 750g/Manger Bouger. - `recipeYield: ["2", "2 portions"]` → `extractPortions` prend déjà le premier élément d'un tableau puis regex `\d+` → `2`. Géré nativement, aucun changement requis. - `recipeIngredient` : lignes françaises propres ("300 g de riz rond japonais (Japonica)", "1 c. à soupe de sauce soja claire (usukuchi shōyu)"...). Deux petites bizarreries WPRM à surveiller (pas à corriger dans l'adaptateur, même sort que les cas similaires déjà documentés sur le ticket Quitoque) : double espace quand aucune unité n'est précisée ("3 champignons shiitake"), et notation en fraction ("1/2 c. à café de sel") — au pire un ingrédient finit non résolu, dans le flux de revue manuelle déjà existant. - `recipeInstructions` : `HowToStep[]` avec `text`/`name` identiques — `flattenInstructions` prend déjà `.text` en priorité, sans conséquence. - **Cartes de la page de recherche** : pas de `ItemList` JSON-LD (contrairement à Marmiton — vérifié : seulement `Person, WebSite, BreadcrumbList, SearchResultsPage` sur cette page), mais chaque carte est un seul élément simple à scraper, encore plus direct qu'avec Quitoque (pas de piège lazy-load `data-src`) : `<div class="entry-thumb"><a href="/<slug>/"><img alt="<titre réel>" src="<url image réelle>"></a></div>`. ## Conception proposée Nouveau fichier `apps/api/src/sources/ohmonbento.ts`, structure calquée sur `marmiton.ts` pour `fetchDetail`/`parse` (délégation intégrale + helper `rekeySourceError` dupliqué comme dans les 3 autres adaptateurs scrapés), mais avec un `list()` par scraping de cartes HTML façon 750g/Quitoque — plus simple ici, un seul élément par carte : ```ts const SEARCH_URL = "https://ohmonbento.com/"; export const ohMonBentoAdapter: RecipeSourceAdapter<{ html: string; url: string }> = { key: "ohMonBento", name: "Oh mon Bento", official: false, // même raisonnement que les 4 adaptateurs scrapés existants iconUrl: "https://ohmonbento.com/favicon.ico", // à confirmer à l'implémentation locale: "fr", async list(params) { // GET {SEARCH_URL}?s={query}&paged={page} — page=1 implicite si cursor // absent ; MÊME URL pour requête vide ou non (pas de branchement comme // 750g). 404 → { items: [], nextCursor: null } (fin de liste, comme // marmiton.ts). Sinon, un regex par carte // <div class="entry-thumb"><a href="...">...<img alt="..." src="..."> // récupère externalId (l'URL elle-même)/title/picture/url. // nextCursor: items.length > 0 ? String(page + 1) : null. }, async fetchDetail(externalId) { // Délègue à jsonLdRecipeAdapter.fetchDetail, rekeySourceError comme marmiton.ts:188-197. }, parse(raw) { // Délègue à jsonLdRecipeAdapter.parse — vérifié ci-dessus : aucune // retouche nécessaire — même wrapping rekeySourceError. }, }; ``` ## Fichiers à toucher - `apps/api/src/sources/ohmonbento.ts` (nouveau) — l'adaptateur ci-dessus. - `apps/api/src/sources/index.ts` — ajoute `registerRecipeSource(ohMonBentoAdapter)` dans `registerAllRecipeSources()`. - `apps/api/test/recipe-sources/ohmonbento.test.ts` (nouveau) — même convention `stubFetchHtml`/`globalThis.fetch` que `marmiton.test.ts`/ `750g.test.ts`/`manger-bouger.test.ts`. Cas à couvrir : `list()` parse bien les cartes d'une page fixture, requête vide et non vide sur la même URL, 404 → fin de liste (`items: []`, `nextCursor: null`) ; `fetchDetail`/`parse` délèguent bien et re-labellisent `sourceKey` en cas d'erreur ; `parse` sur une fixture JSON-LD réelle (forme `@graph` Yoast + `Recipe` WPRM) produit les bons `portions`/`ingredients`/`steps`. - `specs/backend-architecture.md` — ajoute la section ohmonbento au même format que les autres adaptateurs (sans corriger la staleness déjà connue du reste de cette section — signalée sur le ticket #37, hors sujet ici aussi). ## Hors scope - Recherche par mot-clé réel non explorée au-delà de la confirmation que `?s=<terme>&paged=N` est la même URL que la recherche vide — le mécanisme est assez simple pour ne pas nécessiter plus d'investigation avant l'implémentation. - Généricisation du scraping de cartes (Quitoque, 750g et maintenant ohmonbento partagent un motif proche) en helper commun — à envisager si un futur 6e site suit le même besoin, pas avant. --- 🤖 Filed by [Claude Code](https://claude.com/claude-code)
kyuno added the
enhancement
planned
labels 2026-09-05 17:43:23 +02:00
kyuno added the
Medium
label 2026-09-05 18:51:29 +02:00
kyuno closed this issue 2026-09-06 02:47:35 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: kyuno/batchCooking#40
No description provided.