From ea97b824e7d913105529784909c539224b8034c8 Mon Sep 17 00:00:00 2001 From: Claus Dethlefsen Date: Wed, 22 Apr 2026 16:34:43 +0200 Subject: [PATCH] Import Valdemarsro favorites into Mealie via API --- .../import_valdemarsro_favorites.py | 176 ++++++++++++++++++ www/mealie.json | 2 +- 2 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 python_scripts/import_valdemarsro_favorites.py diff --git a/python_scripts/import_valdemarsro_favorites.py b/python_scripts/import_valdemarsro_favorites.py new file mode 100644 index 0000000..d61eb08 --- /dev/null +++ b/python_scripts/import_valdemarsro_favorites.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python3 +"""Import Valdemarsro favorites into Mealie (deduplicated by source URL). + +This script imports a fixed list of favorite recipes from Valdemarsro by URL, +skipping recipes already present in Mealie. +""" + +from __future__ import annotations + +import json +import sys +import urllib.error +import urllib.parse +import urllib.request +from pathlib import Path + +BASE_URL = "http://10.0.0.142:9925" +SECRETS = Path("/Volumes/homeassistant/secrets.yaml") + +FAVORITE_SLUGS = [ + "hjemmelavet-pizza", + "lasagne", + "musli-opskrift", + "lakselasagne", + "mexicansk-burger-med-hjemmelavet-guacamole", + "grontsagsfad", + "humus", + "indisk_curry_med_kylling", + "pariserbof", + "skipperlabskovs", + "pizzasnegle", + "luksus-stjerneskud", + "jordbaer-og-fetasalat-med-glaserede-pecannoedder", + "spaghetti-bolognese", + "kylling-med-cornflakes", + "tarteletter-hoens-asparges", + "one-pot-pasta", + "cacio-e-pepe", + "citronpasta", + "blomkaalssalat", + "koedsovs-onepotpasta", + "kikaertegryde", + "moerbradboeffer-med-bloede-loeg", + "stegt-spidskaal", + "bagt-kylling", + "ristede-kartoffelskiver-fad", + "vikingegryde", + "spidskaalssalat-opskrift", + "kylling-med-parmesan", + "bagt-broccoli", + "pastasalat-med-pesto", + "nachos-bowl", + "barbecuesauce", + "congee-rissuppe-kylling", + "macaroni-and-cheese", + "halloween-dessert", + "feta-pasta-med-tomat", + "tortellini-i-fad", + "flyvende-jacob", +] + + +def read_token() -> str: + for line in SECRETS.read_text().splitlines(): + if line.strip().startswith("mealie_bearer_token:"): + return line.split(":", 1)[1].strip().strip('"') + raise RuntimeError("mealie_bearer_token not found in secrets.yaml") + + +def api_request(path: str, token: str, method: str = "GET", payload: dict | None = None) -> dict: + headers = {"Authorization": token} + data = None + if payload is not None: + headers["Content-Type"] = "application/json" + data = json.dumps(payload).encode("utf-8") + + req = urllib.request.Request(f"{BASE_URL}{path}", headers=headers, data=data, method=method) + with urllib.request.urlopen(req, timeout=90) as resp: + raw = resp.read() + return json.loads(raw) if raw else {} + + +def fetch_existing_org_urls(token: str) -> set[str]: + urls: set[str] = set() + page = 1 + per_page = 100 + + while True: + path = f"/api/recipes?page={page}&perPage={per_page}" + payload = api_request(path, token) + items = payload.get("items", []) or [] + if not items: + break + + for recipe in items: + org_url = (recipe.get("orgURL") or "").strip() + if org_url: + urls.add(org_url.rstrip("/")) + + if len(items) < per_page: + break + page += 1 + + return urls + + +def import_recipe_by_url(token: str, url: str) -> tuple[bool, str]: + try: + api_request( + "/api/recipes/create/url", + token, + method="POST", + payload={"url": url, "includeTags": True}, + ) + return True, "imported" + except urllib.error.HTTPError as exc: + body = "" + try: + body = exc.read().decode("utf-8", errors="ignore") + except Exception: + pass + if exc.code == 422: + return False, f"422 {body[:200]}" + return False, f"HTTP {exc.code} {body[:200]}" + except Exception as exc: # noqa: BLE001 + return False, str(exc) + + +def main() -> int: + token = read_token() + urls = [f"https://www.valdemarsro.dk/{slug}/" for slug in FAVORITE_SLUGS] + + existing_urls = fetch_existing_org_urls(token) + + to_import = [u for u in urls if u.rstrip("/") not in existing_urls] + skipped = [u for u in urls if u.rstrip("/") in existing_urls] + + imported: list[str] = [] + failed: list[tuple[str, str]] = [] + + for url in to_import: + ok, msg = import_recipe_by_url(token, url) + if ok: + imported.append(url) + else: + failed.append((url, msg)) + + print(f"TOTAL_LISTED={len(urls)}") + print(f"SKIPPED_EXISTING={len(skipped)}") + print(f"IMPORTED={len(imported)}") + print(f"FAILED={len(failed)}") + + if skipped: + print("-- skipped existing --") + for url in skipped: + print(url) + + if imported: + print("-- imported --") + for url in imported: + print(url) + + if failed: + print("-- failed --") + for url, reason in failed: + print(url) + print(f" reason: {reason}") + + # Exit non-zero only if everything failed. + if failed and not imported: + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/www/mealie.json b/www/mealie.json index deb332f..ac0d7e5 100644 --- a/www/mealie.json +++ b/www/mealie.json @@ -1 +1 @@ -{"count": 7, "items": [{"date": "2026-04-28", "recipe": {"name": "Lasagne", "slug": "lasagne"}}, {"date": "2026-04-27", "recipe": {"name": "Kylling i cremet sennepssauce", "slug": "kylling-i-cremet-sennepssauce"}}, {"date": "2026-04-26", "recipe": {"name": "Lasagne", "slug": "lasagne"}}, {"date": "2026-04-25", "recipe": {"name": "Spr\u00f8de for\u00e5rsruller", "slug": "sprode-forarsruller"}}, {"date": "2026-04-24", "recipe": {"name": "Marry Me Chicken", "slug": "marry-me-chicken"}}, {"date": "2026-04-22", "recipe": {"name": "Kylling i cremet sennepssauce", "slug": "kylling-i-cremet-sennepssauce"}}, {"date": "2026-04-23", "recipe": {"name": "K\u00e5lfad med hakket oksek\u00f8d", "slug": "kalfad-med-hakket-oksekod"}}]} \ No newline at end of file +{"count": 6, "items": [{"date": "2026-04-28", "recipe": {"name": "Lasagne", "slug": "lasagne"}}, {"date": "2026-04-27", "recipe": {"name": "Kylling i cremet sennepssauce", "slug": "kylling-i-cremet-sennepssauce"}}, {"date": "2026-04-26", "recipe": {"name": "Lasagne", "slug": "lasagne"}}, {"date": "2026-04-24", "recipe": {"name": "Marry Me Chicken", "slug": "marry-me-chicken"}}, {"date": "2026-04-22", "recipe": {"name": "Kylling i cremet sennepssauce", "slug": "kylling-i-cremet-sennepssauce"}}, {"date": "2026-04-23", "recipe": {"name": "K\u00e5lfad med hakket oksek\u00f8d", "slug": "kalfad-med-hakket-oksekod"}}]} \ No newline at end of file