Import Valdemarsro favorites into Mealie via API
This commit is contained in:
@@ -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())
|
||||
+1
-1
@@ -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"}}]}
|
||||
{"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"}}]}
|
||||
Reference in New Issue
Block a user