From 6e673963c9bc91e1087feebaf34ba430e5d1de8d Mon Sep 17 00:00:00 2001 From: dadaloop82 Date: Tue, 10 Mar 2026 13:46:40 +0000 Subject: [PATCH] =?UTF-8?q?Bring!=20catalogo=20IT=E2=86=94DE:=20nomi=20ric?= =?UTF-8?q?onosciuti=20con=20icone=20e=20categorie?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + api/index.php | 115 ++++++++++++++++++++++++++++++++++++++++++++--- assets/js/app.js | 16 ++++--- 3 files changed, 121 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index 6d5ed0a..9d3771b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ data/*.db-shm # Bring! auth token cache data/bring_token.json +data/bring_catalog.json # SSL CA cert (local only) ca.crt diff --git a/api/index.php b/api/index.php index cfedd51..1e0227c 100644 --- a/api/index.php +++ b/api/index.php @@ -1078,6 +1078,76 @@ function bringRequest(string $method, string $url, ?string $body = null): ?array return $data ?? ['_raw' => $response]; } +/** + * Load and cache the Bring! IT↔DE catalog mapping. + * Returns ['de2it' => [German => Italian], 'it2de' => [italian_lower => German]] + */ +function bringCatalog(): array { + $cacheFile = __DIR__ . '/../data/bring_catalog.json'; + + // Cache for 24 hours + if (file_exists($cacheFile) && filemtime($cacheFile) > time() - 86400) { + return json_decode(file_get_contents($cacheFile), true) ?: ['de2it' => [], 'it2de' => []]; + } + + $json = @file_get_contents('https://web.getbring.com/locale/articles.it-IT.json'); + if (!$json) return ['de2it' => [], 'it2de' => []]; + + $data = json_decode($json, true); + if (!$data) return ['de2it' => [], 'it2de' => []]; + + $de2it = []; + $it2de = []; + foreach ($data as $deKey => $itVal) { + if (!is_string($itVal) || empty($itVal)) continue; + $de2it[$deKey] = $itVal; + $it2de[mb_strtolower($itVal)] = $deKey; + } + + $catalog = ['de2it' => $de2it, 'it2de' => $it2de]; + @file_put_contents($cacheFile, json_encode($catalog, JSON_UNESCAPED_UNICODE)); + + return $catalog; +} + +/** Translate a Bring! item name from German key to Italian display name */ +function bringToItalian(string $name): string { + $catalog = bringCatalog(); + return $catalog['de2it'][$name] ?? $name; +} + +/** Translate an Italian product name to the Bring! German catalog key (fuzzy match) */ +function italianToBring(string $italianName): string { + $catalog = bringCatalog(); + $lower = mb_strtolower(trim($italianName)); + + // Exact match + if (isset($catalog['it2de'][$lower])) { + return $catalog['it2de'][$lower]; + } + + // Try partial match: "Spinaci freschi" → match "Spinaci" + foreach ($catalog['it2de'] as $itLower => $deKey) { + if (str_contains($lower, $itLower) || str_contains($itLower, $lower)) { + return $deKey; + } + } + + // Try matching first word: "Petto di pollo" → "Pollo" = Poulet + $words = explode(' ', $lower); + foreach ($words as $word) { + if (mb_strlen($word) < 3) continue; + foreach ($catalog['it2de'] as $itLower => $deKey) { + if ($itLower === $word) { + return $deKey; + } + } + } + + // No match - return original (Bring! will show as custom item) + return $italianName; +} + function bringGetList(): void { $auth = bringAuth(); if (!$auth) { @@ -1108,16 +1178,20 @@ function bringGetList(): void { if (isset($data['purchase'])) { foreach ($data['purchase'] as $item) { + $rawName = $item['name'] ?? ''; $purchase[] = [ - 'name' => $item['name'] ?? '', + 'name' => bringToItalian($rawName), + 'rawName' => $rawName, 'specification' => $item['specification'] ?? '', ]; } } if (isset($data['recently'])) { foreach ($data['recently'] as $item) { + $rawName = $item['name'] ?? ''; $recently[] = [ - 'name' => $item['name'] ?? '', + 'name' => bringToItalian($rawName), + 'rawName' => $rawName, 'specification' => $item['specification'] ?? '', ]; } @@ -1128,7 +1202,7 @@ function bringGetList(): void { 'listUUID' => $listUUID, 'purchase' => $purchase, 'recently' => $recently, - ]); + ], JSON_UNESCAPED_UNICODE); } function bringAddItems(): void { @@ -1155,9 +1229,12 @@ function bringAddItems(): void { $spec = $item['specification'] ?? ''; if (empty($name)) continue; + // Map Italian name to Bring! catalog key (German) for proper recognition + $bringName = italianToBring($name); + $body = http_build_query([ 'uuid' => $listUUID, - 'purchase' => $name, + 'purchase' => $bringName, 'specification' => $spec, ]); @@ -1188,9 +1265,13 @@ function bringRemoveItem(): void { return; } + // Use rawName (German key) if provided, otherwise try to map + $rawName = $input['rawName'] ?? ''; + $removeName = !empty($rawName) ? $rawName : italianToBring($name); + $body = http_build_query([ 'uuid' => $listUUID, - 'remove' => $name, + 'remove' => $removeName, ]); $result = bringRequest('PUT', "https://api.getbring.com/rest/v2/bringlists/{$listUUID}", $body); @@ -1222,7 +1303,8 @@ function bringSuggestItems(PDO $db): void { $data = bringRequest('GET', "https://api.getbring.com/rest/v2/bringlists/{$listUUID}"); if ($data && isset($data['purchase'])) { foreach ($data['purchase'] as $item) { - $bringItems[] = $item['name'] ?? ''; + $rawName = $item['name'] ?? ''; + $bringItems[] = bringToItalian($rawName); } } } @@ -1287,6 +1369,17 @@ function bringSuggestItems(PDO $db): void { $meseIt = $mesi_it[date('F')] ?? date('F'); $anno = date('Y'); + // Get catalog Italian names for AI to use + $catalog = bringCatalog(); + $catalogNames = array_values($catalog['de2it']); + // Filter only food-related items (exclude categories and non-food) + $catalogNames = array_filter($catalogNames, function($n) { + $skip = ['Fai da te', 'Giardino', 'Atrezzi', 'Annaffiatoio', 'Rasaerba', 'Sementi', 'Propangas', 'Vernice', 'Pennello', 'Viti', 'Chiodi', 'Barbecue', 'Ombrellone', 'Terriccio', 'Concime', 'Articoli propri', 'Usati di recente']; + foreach ($skip as $s) { if (str_contains($n, $s)) return false; } + return mb_strlen($n) > 1; + }); + $catalogList = implode(', ', array_slice($catalogNames, 0, 200)); + $prompt = << { + container.innerHTML = shoppingItems.map((item, idx) => { const catIcon = CATEGORY_ICONS[guessCategoryFromName(item.name)] || '🛒'; return `
@@ -2233,16 +2233,22 @@ function renderShoppingItems() {
${escapeHtml(item.name)}
${item.specification ? `
${escapeHtml(item.specification)}
` : ''}
- + `; }).join(''); } -async function removeBringItem(name) { +async function removeBringItem(idx) { + const item = shoppingItems[idx]; + if (!item) return; try { - const data = await api('bring_remove', {}, 'POST', { name, listUUID: shoppingListUUID }); + const data = await api('bring_remove', {}, 'POST', { + name: item.name, + rawName: item.rawName || '', + listUUID: shoppingListUUID + }); if (data.success) { - shoppingItems = shoppingItems.filter(i => i.name !== name); + shoppingItems.splice(idx, 1); renderShoppingItems(); showToast('Rimosso dalla lista', 'success'); }