feat: duplicate detection + original URL in description
- Add original recipe URL to Mealie description (appended after blank line) - Check for duplicate recipes on scrape (match orgURL or URL in description) - Show warning with link to existing recipe if duplicate found - User can still import anyway (warning only, not blocking) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+13
-1
@@ -81,7 +81,19 @@ def scrape_url():
|
||||
return jsonify({"ok": False, "error": "Nincs URL megadva."})
|
||||
try:
|
||||
data = scrape(url)
|
||||
return jsonify({"ok": True, "data": data})
|
||||
|
||||
# Check for duplicate in Mealie
|
||||
duplicate = None
|
||||
cfg = config.load()
|
||||
if cfg.get("mealie_url") and cfg.get("mealie_api_key"):
|
||||
try:
|
||||
client = MealieClient(cfg["mealie_url"], cfg["mealie_api_key"],
|
||||
api_url=config.MEALIE_INTERNAL_URL)
|
||||
duplicate = client.find_duplicate(url, data.get("title", ""))
|
||||
except Exception:
|
||||
pass # non-fatal
|
||||
|
||||
return jsonify({"ok": True, "data": data, "duplicate": duplicate})
|
||||
except Exception as exc:
|
||||
return jsonify({"ok": False, "error": str(exc), "trace": traceback.format_exc()})
|
||||
|
||||
|
||||
+41
-2
@@ -29,6 +29,40 @@ class MealieClient:
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
|
||||
def find_duplicate(self, url: str, title: str = "") -> dict | None:
|
||||
"""Check if a recipe with this original URL already exists.
|
||||
|
||||
Searches by URL in orgURL and description fields.
|
||||
Returns {"slug": ..., "name": ..., "url": ...} or None.
|
||||
"""
|
||||
if not url:
|
||||
return None
|
||||
# Search by title to find candidates (Mealie search matches name)
|
||||
search_term = title or url.split("/")[-1].replace("-", " ")
|
||||
r = self.session.get(
|
||||
f"{self.api_url}/api/recipes",
|
||||
params={"search": search_term, "perPage": 50},
|
||||
timeout=10,
|
||||
)
|
||||
if not r.ok:
|
||||
return None
|
||||
for item in r.json().get("items", []):
|
||||
detail = self.session.get(
|
||||
f"{self.api_url}/api/recipes/{item['slug']}", timeout=10
|
||||
)
|
||||
if not detail.ok:
|
||||
continue
|
||||
data = detail.json()
|
||||
org = data.get("orgURL", "") or ""
|
||||
desc = data.get("description", "") or ""
|
||||
if org == url or url in desc:
|
||||
return {
|
||||
"slug": data["slug"],
|
||||
"name": data.get("name", data["slug"]),
|
||||
"url": f"{self.base_url}/g/home/r/{data['slug']}",
|
||||
}
|
||||
return None
|
||||
|
||||
def create_recipe(self, recipe: dict) -> str:
|
||||
"""Create a recipe in Mealie from a scraper result dict.
|
||||
|
||||
@@ -175,12 +209,17 @@ class MealieClient:
|
||||
"ingredientReferences": [],
|
||||
})
|
||||
|
||||
description = recipe.get("description", "")
|
||||
original_url = recipe.get("original_url", "")
|
||||
if original_url and original_url not in description:
|
||||
description = f"{description}\n\n{original_url}".strip()
|
||||
|
||||
return {
|
||||
"name": recipe["title"],
|
||||
"description": recipe.get("description", ""),
|
||||
"description": description,
|
||||
"recipeIngredient": ingredients,
|
||||
"recipeInstructions": instructions,
|
||||
"orgURL": recipe.get("original_url", ""),
|
||||
"orgURL": original_url,
|
||||
"recipeYield": "",
|
||||
}
|
||||
|
||||
|
||||
@@ -162,6 +162,7 @@
|
||||
.text-dim { color: var(--text-dim); }
|
||||
.text-success { color: var(--success); }
|
||||
.text-danger { color: var(--danger); }
|
||||
.text-warning { color: var(--warning); }
|
||||
.flex { display: flex; gap: 0.75rem; align-items: center; }
|
||||
.flex-wrap { flex-wrap: wrap; }
|
||||
.grow { flex: 1; }
|
||||
|
||||
@@ -232,7 +232,14 @@ async function scrapeRecipe() {
|
||||
currentRecipe = data.data;
|
||||
populatePreview(currentRecipe);
|
||||
document.getElementById('previewCard').classList.add('visible');
|
||||
status.innerHTML = '<span class="text-success">✓ Beolvasva</span>';
|
||||
|
||||
if (data.duplicate) {
|
||||
status.innerHTML = '<span class="text-warning">⚠ Ez a recept már létezik Mealie-ben: '
|
||||
+ '<a href="' + escHtml(data.duplicate.url) + '" target="_blank" style="color:var(--accent)">'
|
||||
+ escHtml(data.duplicate.name) + '</a></span>';
|
||||
} else {
|
||||
status.innerHTML = '<span class="text-success">✓ Beolvasva</span>';
|
||||
}
|
||||
} catch (e) {
|
||||
status.innerHTML = '<span class="text-danger">Hálózati hiba: ' + e.message + '</span>';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user