Eure Beiträge haben mich stark inspiriert auch was zu bauen. - Danke!
Daher wollte ich auch meine Lösung teilen.
Ich habe meine Listen in Home Assistant hinzugefügt. Beide existieren ohne extra Zwischenliste.
In Node-Red habe ich mir eine Logik überlegt, die die beiden Listen mit extra Features synct.
Mein Trigger ist: Eine der beiden Entitäten der Einkaufslisten (Bring/Cookidoo) ändert sich - oder jede volle Stunde (das ist zwar nicht notwendig, gibt mir aber peace of mind + n debug-injection-knopf):
Dabei werden alle Items von Cookidoo nach Bring gesynct, wenn sie dort noch nicht vorhanden sind.
Beim Abhaken in Bring, werden sie auch in Cookidoo abgehakt.
Beim Abhaken in Cookidoo, werden sie auch in Bring abgehakt.
Wichtig sind die Synonyme… denn Bring wandelt “Rahm” automatisch in “Sahne” um.
Wir hatten einmal einen Sahnevorfall. - ca. 1000 Sahne auf der Bring liste, weil Cookidoo unbedingt “Rahm” syncen wollte, es aber nicht in Bring erschien, es also erneut Rahm draufsetzte. 
Feature: die Blacklist. Dinge die wir eh zuhause haben, werden automatisch abgehakt und nicht auf Bring gesetzt. (z.B. Wasser, Salz, Öl, …) - Wenn wir es aber manuell auf Bring setzen, bleibt es natürlich.
CompareLists function
/*************************
BLACKLIST
*************************/
const BLACKLIST = [
“Wasser”,
“Salz”,
“Pfeffer”,
“Öl”,
“Eiswürfel”,
“”
].map(s => s.toLowerCase());
const isBlacklisted = (summary) =>
BLACKLIST.includes(summary.toLowerCase());
/*************************
SYNONYME
*************************/
const SYNONYMS = {
“rahm”: [“sahne”],
“sahne”: [“rahm”],
“kartoffeln”: [“erdäpfel”],
“erdäpfel”: [“kartoffeln”]
};
// gibt alle zulässigen Summary-Varianten zurück
const getSummaryVariants = (summary) => {
const key = summary.toLowerCase();
return [key, …(SYNONYMS[key] ||
)];
};
/*************************
DATEN
*************************/
const bringItems =
msg.payload.bring[“todo.bring”].items;
const cookidooItems =
msg.payload.cookidoo[“todo.cookidoo_einkaufsliste”].items;
/*************************
HILFSFUNKTIONEN
*************************/
const keyOf = (summary, description) =>
${summary}||${description ?? ""};
/*************************
INDEXE
*************************/
const bringKeySet = new Set();
bringItems.forEach(item => {
bringKeySet.add(
keyOf(item.summary.toLowerCase(), item.description ?? “”)
);
});
/*************************
ERGEBNISLISTEN
*************************/
const add_to_bring =
;
const complete_in_bring =
;
const complete_in_cookidoo =
;
/*************************
Cookidoo needs_action
*************************/
cookidooItems.forEach(cItem => {
if (cItem.status !== “needs_action”) return;
// BLACKLIST → direkt in Cookidoo abhaken
if (isBlacklisted(cItem.summary)) {
complete_in_cookidoo.push({
cookidoo_uid: cItem.uid,
reason: “blacklist”
});
return;
}
const description = cItem.description ?? “”;
const variants = getSummaryVariants(cItem.summary);
// Prüfen, ob irgendeine Variante bereits in Bring existiert
const existsInBring = variants.some(variant =>
bringKeySet.has(
keyOf(variant, description)
)
);
if (!existsInBring) {
add_to_bring.push({
summary: cItem.summary,
description
});
}
});
/*************************
Cookidoo completed
*************************/
cookidooItems.forEach(cItem => {
if (cItem.status !== “completed”) return;
const bItem = bringItems.find(b =>
b.summary === cItem.summary &&
(b.description ?? “”) === (cItem.description ?? “”)
);
if (bItem && bItem.status === “needs_action”) {
complete_in_bring.push({
bring_uid: bItem.uid
});
}
});
/*************************
Bring completed
*************************/
bringItems.forEach(bItem => {
if (bItem.status !== “completed”) return;
const cItem = cookidooItems.find(c =>
c.summary === bItem.summary &&
(c.description ?? “”) === (bItem.description ?? “”)
);
if (cItem && cItem.status === “needs_action”) {
complete_in_cookidoo.push({
cookidoo_uid: cItem.uid
});
}
});
/*************************
OUTPUT
*************************/
msg.payload = {
add_to_bring,
complete_in_bring,
complete_in_cookidoo
};
return msg;
Die Split Nodes teilen das dann entsprechend in einzelne Items auf (Ich kann leider nur 2 bilder anhängen):
split property: payload.add_to_bring
String / Buffer split using: \n
Array split using fixed length of 1
AddToBring Action
todo.add_item
{“item”:“{{payload.add_to_bring.summary}}”,“description”:“{{payload.add_to_bring.description}}”}
Complete in Bring / Cookidoo
jeweils todo.update_item
{“item”:“{{payload.complete_in_bring.bring_uid}}”,“status”:“completed”}
{“item”: “{{{payload.complete_in_cookidoo.cookidoo_uid}}}”,“status”: “completed”}
Der nächtliche Reset
Wichtig: Nachts wird geprüft, ob die Listen leer sind, und sie werden geleert, denn meine Automation basiert auch auf den abgehakten Items der Listen, die müssen regelmäßig weg.
const bringItems =
msg.payload.bring[“todo.sonnenhof”].items;
const cookidooItems =
msg.payload.cookidoo[“todo.cookidoo_einkaufsliste”].items;
const bring_completed = bringItems.every(
item => item.status === “completed”
);
const cookidoo_completed = cookidooItems.every(
item => item.status === “completed”
);
msg.payload = {
…msg.payload,
bring_completed,
cookidoo_completed
};
return msg;
Die gelben switch nodes checken dann auf true.
bei Bring werden btw. nur die abgehakten Items (last used) entfernt (remove_completed).
Ich hoffe ich helfe dem ein oder anderen mit den Funktionen 