Extract ingredient comments from food field, add import-to-both button
- Global post-processing in scrape() extracts trailing (comment) from ingredient food names into the extra/comment field. Works for all parsers. - Added "Importálás mindkettőbe" button on single import page when both Mealie and Tandoor are configured. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+24
-3
@@ -55,12 +55,19 @@ def scrape(url: str) -> dict:
|
|||||||
soup = BeautifulSoup(resp.text, "lxml")
|
soup = BeautifulSoup(resp.text, "lxml")
|
||||||
|
|
||||||
host = _host(url)
|
host = _host(url)
|
||||||
|
result = None
|
||||||
for substring, parser in _PARSERS:
|
for substring, parser in _PARSERS:
|
||||||
if substring in host:
|
if substring in host:
|
||||||
return parser(soup, url)
|
result = parser(soup, url)
|
||||||
|
break
|
||||||
|
|
||||||
# Fallback: try generic schema.org / og-tag extraction
|
if result is None:
|
||||||
return _parse_generic(soup, url)
|
# Fallback: try generic schema.org / og-tag extraction
|
||||||
|
result = _parse_generic(soup, url)
|
||||||
|
|
||||||
|
# Post-process: extract parenthesized comments from food into extra
|
||||||
|
_extract_ingredient_comments(result)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def supported_sites() -> list[str]:
|
def supported_sites() -> list[str]:
|
||||||
@@ -642,6 +649,20 @@ def _parse_generic(soup: BeautifulSoup, url: str) -> dict:
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_ingredient_comments(data: dict):
|
||||||
|
"""Move trailing (comment) from food field to extra field for all ingredients."""
|
||||||
|
for ing in data.get("ingredients", []):
|
||||||
|
if "group" in ing:
|
||||||
|
continue
|
||||||
|
food = ing.get("food", "")
|
||||||
|
extra = ing.get("extra", "")
|
||||||
|
if food and not extra:
|
||||||
|
m = re.match(r"^(.+?)\s*\(([^)]+)\)\s*$", food)
|
||||||
|
if m:
|
||||||
|
ing["food"] = m.group(1).strip()
|
||||||
|
ing["extra"] = m.group(2).strip()
|
||||||
|
|
||||||
|
|
||||||
def _host(url: str) -> str:
|
def _host(url: str) -> str:
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
return urlparse(url).hostname or ""
|
return urlparse(url).hostname or ""
|
||||||
|
|||||||
@@ -467,7 +467,7 @@
|
|||||||
<hr class="section-divider">
|
<hr class="section-divider">
|
||||||
|
|
||||||
<!-- Single mode action buttons -->
|
<!-- Single mode action buttons -->
|
||||||
<div class="flex mt-2" id="singleActions">
|
<div class="flex mt-2" id="singleActions" style="flex-wrap:wrap;">
|
||||||
{% if has_mealie %}
|
{% if has_mealie %}
|
||||||
<button class="btn btn-success" id="sendMealieBtn" onclick="sendToMealie()">
|
<button class="btn btn-success" id="sendMealieBtn" onclick="sendToMealie()">
|
||||||
Importálás Mealie-be
|
Importálás Mealie-be
|
||||||
@@ -478,6 +478,11 @@
|
|||||||
Importálás Tandoor-ba
|
Importálás Tandoor-ba
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if has_mealie and has_tandoor %}
|
||||||
|
<button class="btn btn-success" id="sendBothBtn" onclick="sendToBoth()">
|
||||||
|
Importálás mindkettőbe
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
<span id="sendStatus"></span>
|
<span id="sendStatus"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -775,6 +780,56 @@ async function sendToTandoor() {
|
|||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function sendToBoth() {
|
||||||
|
const recipe = gatherRecipe();
|
||||||
|
if (!recipe.title) { alert('A recept neve kötelező!'); return; }
|
||||||
|
|
||||||
|
const btn = document.getElementById('sendBothBtn');
|
||||||
|
const status = document.getElementById('sendStatus');
|
||||||
|
btn.disabled = true;
|
||||||
|
status.innerHTML = '<span class="spinner"></span> Importálás mindkettőbe...';
|
||||||
|
|
||||||
|
const errors = [];
|
||||||
|
try {
|
||||||
|
const mResp = await fetch('/send', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(recipe),
|
||||||
|
});
|
||||||
|
const mData = await mResp.json();
|
||||||
|
if (mData.ok) {
|
||||||
|
importedLinks['Mealie'] = mData.url;
|
||||||
|
} else {
|
||||||
|
errors.push('Mealie: ' + mData.error);
|
||||||
|
}
|
||||||
|
} catch (e) { errors.push('Mealie: ' + e.message); }
|
||||||
|
|
||||||
|
try {
|
||||||
|
const tResp = await fetch('/send-tandoor', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(recipe),
|
||||||
|
});
|
||||||
|
const tData = await tResp.json();
|
||||||
|
if (tData.ok) {
|
||||||
|
importedLinks['Tandoor'] = tData.url;
|
||||||
|
} else {
|
||||||
|
errors.push('Tandoor: ' + tData.error);
|
||||||
|
}
|
||||||
|
} catch (e) { errors.push('Tandoor: ' + e.message); }
|
||||||
|
|
||||||
|
if (errors.length > 0 && Object.keys(importedLinks).length === 0) {
|
||||||
|
status.innerHTML = '<span class="text-danger">Hiba: ' + errors.join('; ') + '</span>';
|
||||||
|
} else if (errors.length > 0) {
|
||||||
|
status.innerHTML = '<span class="text-warning">Részben sikeres: ' + errors.join('; ') + '</span>';
|
||||||
|
showResultCard();
|
||||||
|
} else {
|
||||||
|
status.innerHTML = '<span class="text-success">✓ Mindkettőbe importálva</span>';
|
||||||
|
showResultCard();
|
||||||
|
}
|
||||||
|
btn.disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
function showResultCard() {
|
function showResultCard() {
|
||||||
const links = Object.entries(importedLinks).map(([name, url]) =>
|
const links = Object.entries(importedLinks).map(([name, url]) =>
|
||||||
'<a href="' + escHtml(url) + '" target="_blank" style="color:var(--accent);">Megnyitás ' + name + '-ben →</a>'
|
'<a href="' + escHtml(url) + '" target="_blank" style="color:var(--accent);">Megnyitás ' + name + '-ben →</a>'
|
||||||
|
|||||||
Reference in New Issue
Block a user