Weiter zum Inhalt
  • Home
  • Aktuell
  • Tags
  • 0 Ungelesen 0
  • Kategorien
  • Unreplied
  • Beliebt
  • GitHub
  • Docu
  • Hilfe
Skins
  • Hell
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dunkel
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Standard: (Kein Skin)
  • Kein Skin
Einklappen
ioBroker Logo

Community Forum

donate donate
  1. ioBroker Community Home
  2. Deutsch
  3. Skripten / Logik
  4. Alexa Shopping List mit Bring synchronisieren

NEWS

  • wichtiges UPDATE für controller 7.2.2 im stable
    HomoranH
    Homoran
    10
    1
    2.4k

  • Neues YouTube-Video: Visualisierung im Devices-Adapter
    BluefoxB
    Bluefox
    16
    1
    4.3k

  • Neuer ioBroker-Blog online: Monatsrückblick März/April 2026
    BluefoxB
    Bluefox
    8
    1
    3.8k

Alexa Shopping List mit Bring synchronisieren

Geplant Angeheftet Gesperrt Verschoben Skripten / Logik
187 Beiträge 32 Kommentatoren 52.2k Aufrufe 31 Beobachtet
  • Älteste zuerst
  • Neuste zuerst
  • Meiste Stimmen
Antworten
  • In einem neuen Thema antworten
Anmelden zum Antworten
Dieses Thema wurde gelöscht. Nur Nutzer mit entsprechenden Rechten können es sehen.
  • N no6mis

    Todoist hat die API angepasst, v2 gibt es nicht mehr.
    https://developer.todoist.com/api/v1/#tag/Tasks

    Du musst die API-URL auf https://api.todoist.com/api/v1/tasks ändern und die Projekt-IDs aktualisieren.

    M Offline
    M Offline
    martin_olw
    schrieb am zuletzt editiert von
    #178

    @no6mis Ich danke dir! Genau das hat geholfen!

    1 Antwort Letzte Antwort
    0
    • HeimwehH Offline
      HeimwehH Offline
      Heimweh
      schrieb am zuletzt editiert von
      #179

      Sorry für meine späte Antwort - bei mir geht es auch nicht mehr. Die Projekt ID (zumindest die aus dem Browser) ist keine reine numerische Zahl mehr?

      N 1 Antwort Letzte Antwort
      0
      • HeimwehH Heimweh

        Sorry für meine späte Antwort - bei mir geht es auch nicht mehr. Die Projekt ID (zumindest die aus dem Browser) ist keine reine numerische Zahl mehr?

        N Offline
        N Offline
        no6mis
        schrieb am zuletzt editiert von
        #180

        @Heimweh sagte in Alexa Shopping List mit Bring synchronisieren:

        Die Projekt ID (zumindest die aus dem Browser) ist keine reine numerische Zahl mehr?

        ja, die Zeichenfolge aus dem Browser (der Teil nach dem Klarnamen-) kannst du 1:1 in dein Script übernehmen.

        1 Antwort Letzte Antwort
        0
        • HeimwehH Offline
          HeimwehH Offline
          Heimweh
          schrieb am zuletzt editiert von
          #181

          Ich habe diese Änderung der Todoist API zum Anlass genommen den Script zu überarbeiten, da mittlerweile auch wieder die Listen in den Alexa Adapter eingelesen werden. Vielleicht kann den Script ja jemand brauchen.....

          Alexa Shoppingliste ↔ Todoist (Projekt-Sync)

          Dieses Script synchronisiert die Alexa Shoppingliste
          (alexa2.0.Lists.SHOP.json) mit einem definierten Todoist-Projekt.

          Funktionen

          Beim Scriptstart werden alle offenen Alexa-Einträge in das Todoist-Projekt übernommen.

          Neue Einträge in Alexa werden automatisch in Todoist angelegt.

          Wird ein Task in Todoist erledigt, wird er in Alexa als „completed“ markiert.

          Gelöschte oder erledigte Tasks in Todoist verschwinden damit auch aus der offenen Alexa-Liste.

          Doppelimporte werden durch ein internes Mapping verhindert.

          Deutsche Zahlwörter werden automatisch in Zahlen umgewandelt (z. B. „zwei Liter“ → „2 Liter“).

          Voraussetzungen

          ioBroker mit javascript-Adapter

          alexa2-Adapter mit aktivierter Listenfunktion

          Todoist API Token (REST v1)

          axios (muss installiert sein)

          Unterschied zur älteren Version

          Es wird nicht mehr der Datenpunkt alexa2.0.History.summary ausgewertet.

          Es wird direkt der vom alexa2-Adapter bereitgestellte Listen-JSON verwendet.

          Die Synchronisation funktioniert jetzt sauber in beide Richtungen.

          Es wird ausschließlich die aktuelle Todoist REST v1 API verwendet.

          const axios = require("axios");
          
          const TAG = "A2S-SHOP-PROD-2";
          
          const ALEXA_JSON_DP = "alexa2.0.Lists.SHOP.json";
          const ALEXA_ITEMS_ROOT = "alexa2.0.Lists.SHOP.items";
          
          const TODOIST_TOKEN = "HIER_DEIN_TODOIST_TOKEN";
          const TODOIST_BASE = "https://api.todoist.com/api/v1";
          const TODOIST_PROJECT_ID = "HIER_DEINE_TODOIST_PROJEKT_ID";
          
          const POLL_SEC = 20;
          const MIN_TASK_AGE_MS = 5_000;
          
          const STORE_ROOT = "0_userdata.0.alexaTodoistBiSync";
          const MAP_STATE = `${STORE_ROOT}.map_shop`;
          
          function headers(extra = {}) {
            return { Authorization: `Bearer ${TODOIST_TOKEN}`, ...extra };
          }
          
          async function todoistCreateTask(content, projectId) {
            const res = await axios.post(
              `${TODOIST_BASE}/tasks`,
              { content, project_id: projectId },
              { headers: headers({ "Content-Type": "application/json" }), timeout: 15000 }
            );
            return res.data;
          }
          
          async function todoistGetTask(taskId) {
            const res = await axios.get(`${TODOIST_BASE}/tasks/${taskId}`, {
              headers: headers(),
              timeout: 15000,
              validateStatus: (s) => (s >= 200 && s < 300) || s === 404,
            });
            return res;
          }
          
          function ensureStateIfMissing(id, def, common) {
            if (!existsState(id)) createState(id, def, common);
          }
          
          function readJsonSafe(id, fallback) {
            const st = getState(id);
            if (!st || st.val === null || st.val === undefined || st.val === "") return fallback;
            try { return JSON.parse(st.val); } catch { return fallback; }
          }
          
          function writeJson(id, obj) {
            setState(id, JSON.stringify(obj), true);
          }
          
          function parseAlexaJson(raw) {
            if (raw === null || raw === undefined) return [];
            const data = typeof raw === "string" ? JSON.parse(raw) : raw;
            return Array.isArray(data) ? data : [];
          }
          
          function isCompletedAlexa(it) {
            const v = it?.completed;
            return v === true || v === 1 || v === "1" || v === "true";
          }
          
          function normalizeMap(map) {
            let changed = false;
            const now = Date.now();
            for (const [alexaId, info] of Object.entries(map)) {
              if (!info || typeof info !== "object") {
                map[alexaId] = { todoistId: null, value: "", createdAt: now, alexaCompletedPushed: false };
                changed = true;
                continue;
              }
              if (info.todoistId === undefined) { info.todoistId = null; changed = true; }
              if (info.value === undefined) { info.value = ""; changed = true; }
              if (!info.createdAt || isNaN(Number(info.createdAt))) { info.createdAt = now; changed = true; }
              if (info.alexaCompletedPushed === undefined) { info.alexaCompletedPushed = false; changed = true; }
            }
            return changed;
          }
          
          // ---- Text normalisieren (Zahlwörter) ----
          function capitalizeFirst(text) {
            if (!text || typeof text !== "string") return "";
            return text.charAt(0).toUpperCase() + text.slice(1);
          }
          
          function wordsToNumbersSmart(text) {
            const ones = {
              "null": 0, "eins": 1, "eine": 1, "einen": 1, "ein": 1,
              "zwei": 2, "drei": 3, "vier": 4, "fünf": 5,
              "sechs": 6, "sieben": 7, "acht": 8, "neun": 9,
              "zehn": 10, "elf": 11, "zwölf": 12, "dreizehn": 13,
              "vierzehn": 14, "fünfzehn": 15, "sechzehn": 16,
              "siebzehn": 17, "achtzehn": 18, "neunzehn": 19
            };
            const tens = {
              "zwanzig": 20, "dreißig": 30, "dreissig": 30,
              "vierzig": 40, "fünfzig": 50, "sechzig": 60,
              "siebzig": 70, "achtzig": 80, "neunzig": 90
            };
            const multipliers = { "hundert": 100, "tausend": 1000 };
            const skipWords = ["und", "oder", "mit", "für", "pro"];
          
            const words = String(text).toLowerCase().split(/\s+/).filter(Boolean);
            const out = [];
            let i = 0;
            let capNext = 0;
          
            while (i < words.length) {
              const w = words[i];
          
              if (ones[w] !== undefined) {
                if (i + 2 < words.length && words[i + 1] === "und" && tens[words[i + 2]] !== undefined) {
                  out.push(String(ones[w] + tens[words[i + 2]]));
                  capNext = 2;
                  i += 3;
                  continue;
                }
                if (i + 1 < words.length && multipliers[words[i + 1]] !== undefined) {
                  out.push(String(ones[w] * multipliers[words[i + 1]]));
                  capNext = 2;
                  i += 2;
                  continue;
                }
                out.push(String(ones[w]));
                capNext = 2;
                i++;
                continue;
              }
          
              if (tens[w] !== undefined) {
                out.push(String(tens[w]));
                capNext = 2;
                i++;
                continue;
              }
          
              if (!isNaN(w)) {
                out.push(w);
                capNext = 2;
                i++;
                continue;
              }
          
              if (capNext > 0 && !skipWords.includes(w)) {
                out.push(w.charAt(0).toUpperCase() + w.slice(1));
                capNext--;
              } else {
                out.push(w);
              }
              i++;
            }
          
            return out.join(" ");
          }
          
          function normalizeTaskText(raw) {
            const withNums = wordsToNumbersSmart(String(raw || "").trim());
            return capitalizeFirst(withNums);
          }
          
          // ---- Debounce/Locks ----
          let alexaSyncRunning = false;
          let pollRunning = false;
          let debounceTimer = null;
          
          function triggerAlexaSyncDebounced(fullImport = false) {
            if (debounceTimer) clearTimeout(debounceTimer);
            debounceTimer = setTimeout(() => {
              syncAlexaToTodoist(fullImport).catch(e => log(`[${TAG}] [Alexa→Todoist] ERROR ${e.message}`, "error"));
            }, 800);
          }
          
          // ---- Alexa -> Todoist ----
          async function syncAlexaToTodoist(fullImport) {
            if (alexaSyncRunning) return;
            alexaSyncRunning = true;
          
            try {
              const raw = getState(ALEXA_JSON_DP)?.val;
              const items = parseAlexaJson(raw);
          
              const map = readJsonSafe(MAP_STATE, {});
              const changed = normalizeMap(map);
              if (changed) writeJson(MAP_STATE, map);
          
              for (const it of items) {
                const alexaId = it?.id ? String(it.id) : null;
                const valueRaw = it?.value ? String(it.value).trim() : "";
                if (!alexaId || !valueRaw) continue;
          
                if (isCompletedAlexa(it)) continue;
                if (map[alexaId]?.todoistId) continue;
          
                const value = normalizeTaskText(valueRaw);
                const task = await todoistCreateTask(value, TODOIST_PROJECT_ID);
          
                map[alexaId] = {
                  todoistId: String(task.id),
                  value,
                  createdAt: Date.now(),
                  alexaCompletedPushed: false,
                };
          
                writeJson(MAP_STATE, map);
                log(`[${TAG}] [Alexa→Todoist] Angelegt: "${value}"`, "info");
              }
          
              if (fullImport) log(`[${TAG}] [Init] Start-Import abgeschlossen.`, "info");
            } finally {
              alexaSyncRunning = false;
            }
          }
          
          // ---- Todoist -> Alexa ----
          async function pollTodoistAndMirror() {
            if (pollRunning) return;
            pollRunning = true;
          
            try {
              const map = readJsonSafe(MAP_STATE, {});
              const changed = normalizeMap(map);
              if (changed) writeJson(MAP_STATE, map);
          
              const now = Date.now();
              const entries = Object.entries(map).filter(([, info]) => info?.todoistId && !info.alexaCompletedPushed);
          
              for (const [alexaId, info] of entries) {
                const todoistId = String(info.todoistId);
                const age = now - Number(info.createdAt || now);
                if (age < MIN_TASK_AGE_MS) continue;
          
                const res = await todoistGetTask(todoistId);
          
                let markDone = false;
          
                if (res.status === 404) {
                  // Task nicht mehr abrufbar -> behandeln wir als "weg/erledigt"
                  markDone = true;
                } else if (res.status >= 200 && res.status < 300) {
                  const t = res.data || {};
                  const checked = t.checked === true;
                  const deleted = t.is_deleted === true;
          
                  // Wenn erledigt ODER gelöscht, dann Alexa abhaken
                  markDone = (checked || deleted);
                } else {
                  continue;
                }
          
                if (!markDone) continue;
          
                const completedDp = `${ALEXA_ITEMS_ROOT}.${alexaId}.completed`;
                if (existsState(completedDp)) setState(completedDp, true, false);
          
                info.alexaCompletedPushed = true;
                writeJson(MAP_STATE, map);
          
                log(`[${TAG}] [Todoist→Alexa] Abgehakt (TodoistId=${todoistId})`, "info");
              }
            } catch (e) {
              const status = e.response?.status;
              const body = e.response?.data ? JSON.stringify(e.response.data) : e.message;
              log(`[${TAG}] [Poll] ERROR status=${status} body=${body}`, "error");
            } finally {
              pollRunning = false;
            }
          }
          
          // ---- INIT ----
          ensureStateIfMissing(MAP_STATE, "{}", { type: "string", role: "json", read: true, write: true });
          
          log(`[${TAG}] START poll=${POLL_SEC}s`, "info");
          
          triggerAlexaSyncDebounced(true);
          on({ id: ALEXA_JSON_DP, change: "any" }, () => triggerAlexaSyncDebounced(false));
          schedule(`*/${POLL_SEC} * * * * *`, () => pollTodoistAndMirror());
          
          
          1 Antwort Letzte Antwort
          0
          • grrfieldG grrfield

            @mcBirne Es ist zwar schon einige Zeit her, aber hast Du das Skript als TypeScript eingefügt? Die Fehlermeldungen sehen nach JavaScript aus.

            mcBirneM Offline
            mcBirneM Offline
            mcBirne
            schrieb am zuletzt editiert von
            #182

            @grrfield sagte in Alexa Shopping List mit Bring synchronisieren:

            @mcBirne Es ist zwar schon einige Zeit her, aber hast Du das Skript als TypeScript eingefügt? Die Fehlermeldungen sehen nach JavaScript aus.

            nein, das wars, danke für den Tipp!

            1 Antwort Letzte Antwort
            0
            • S Offline
              S Offline
              SchiefVanCleef
              schrieb am zuletzt editiert von
              #183

              Hallo zusammen, 🤗 nach dem ich hier viel über IO Broker in Verbindung mit Bring und der Alexa Shopping Liste gelernt habe 🤠 und immer mal wieder "tweaks" an meinem abgewandelten Script vorgenommen habe und es nun super geschmeidigt läuft, möchte ich gerne das aktuelle ALEXA BRING SCRIPT mit euch teilen und der hervorragenden Community auf diesem Weg etwas zurück geben und einfach nur DANKE 🙌 sagen.

              Zum Script:

              Bei der brindBaseID müsst ihr XXX wie gewohnt durch eure Bring Shopping List ID ersetzen, damit die richtige Liste befüllt wird. Ansonste alles wie immer: Alexa2.0 Adapter und bring.0 Adapter sind Voraussetzung, dass es funktioniert. Steht aber auch auskommentiert im Script.

              // =====================================================================
              //  Alexa Shopping List  <->  Bring!  Zwei-Wege-Sync
              //  Engine: 3-Wege-Merge gegen persistierten Schatten + Zeitstempel-Konfliktlösung
              //  Behandelt korrekt: Hinzufügen, Abhaken, Reaktivieren, Löschen (beidseitig)
              // =====================================================================
              
              // --- KONFIGURATION: an die eigene Installation anpassen ---
              // Bring-Listen-ID findest du in den Objekten unter "bring.0" -> Channel deiner Liste (UUID):
              const bringBaseId  = 'bring.0.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
              // Alexa-Einkaufsliste, bei den meisten Setups:
              const alexa2BaseId = 'alexa2.0.Lists.SHOPPING_LIST';
              // Voraussetzungen: Adapter "alexa2" + "bring" eingerichtet, 0_userdata.0 vorhanden.
              
              // Persistenter Speicher für den Schatten (0_userdata.0 existiert in modernen ioBroker-Installs)
              const SHADOW_STATE  = '0_userdata.0.alexaBringSync.shadow';
              
              // Timing
              const DEBOUNCE_MS   = 2000;   // warte nach der letzten Änderung, bevor synchronisiert wird
              const LOCK_MS       = 5000;   // Beruhigungsfenster, bis async Schreibvorgänge der Adapter durch sind
              
              // Bei einem echten Konflikt (beide Seiten gleichzeitig geändert, Zeitstempel uneindeutig):
              // true  = im Zweifel "active" gewinnen lassen (nichts verlieren, was man evtl. noch braucht)
              const CONFLICT_PREFER_ACTIVE = true;
              
              // Optionaler Watchdog: warnt im Log, wenn die alexa2-Cloud-Verbindung wegbricht
              const WATCH_ALEXA_CONNECTION = true;
              
              // --- ABGELEITETE IDs ---
              const bringListId          = bringBaseId + '.content';
              const bringListCompletedId = bringBaseId + '.recentContent';
              const bringAddToList       = bringBaseId + '.saveItem';            // hinzufügen / aus recent reaktivieren
              const bringCompleteItem    = bringBaseId + '.moveToRecentContent'; // abhaken (-> recent)
              const bringRemoveItem      = bringBaseId + '.removeItem';          // komplett entfernen (content + recent)
              const alexaAddToList       = alexa2BaseId + '.#New';
              const alexaListId          = alexa2BaseId + '.json';
              
              // --- LAUFZEIT-ZUSTAND ---
              let isSyncing   = false;
              let pendingSync = false;
              let syncTimeout = null;
              let debounceTimer = null;
              
              // --- HILFSFUNKTIONEN ---
              function cleanName(str) { return str ? String(str).trim().toLowerCase() : ''; }
              function formatName(str) {
                  if (!str) return '';
                  str = String(str).trim();
                  return str.charAt(0).toUpperCase() + str.slice(1);
              }
              
              function loadShadow() {
                  try { const s = getState(SHADOW_STATE); return (s && s.val) ? JSON.parse(s.val) : {}; }
                  catch (e) { log('Schatten nicht lesbar, starte leer: ' + e, 'warn'); return {}; }
              }
              function saveShadow(sh) { setState(SHADOW_STATE, JSON.stringify(sh), true); }
              
              // Konfliktlösung: jüngerer Zeitstempel gewinnt, sonst konfigurierte Vorzugsregel
              function resolveConflict(aStatus, bStatus, aTs, bTs, displayName) {
                  let winner;
                  if (aTs > bTs)      winner = aStatus;
                  else if (bTs > aTs) winner = bStatus;
                  else                winner = CONFLICT_PREFER_ACTIVE
                                                ? ((aStatus === 'active' || bStatus === 'active') ? 'active' : 'done')
                                                : ((aStatus === 'done'   || bStatus === 'done')   ? 'done'   : 'active');
                  log(`[KONFLIKT] "${displayName}": Alexa=${aStatus}(${aTs}) vs Bring=${bStatus}(${bTs}) -> ${winner}`, 'warn');
                  return winner;
              }
              
              function finishLock() {
                  if (syncTimeout) clearTimeout(syncTimeout);
                  syncTimeout = setTimeout(() => {
                      isSyncing = false;
                      if (pendingSync) { pendingSync = false; triggerSync(); } // Nachlauf für während der Sperre Eingetroffenes
                  }, LOCK_MS);
              }
              
              // --- HAUPTLOGIK ---
              function doSync() {
                  if (isSyncing) { pendingSync = true; return; }
                  isSyncing = true;
              
                  try {
                      const aState = getState(alexaListId);
                      const bState = getState(bringListId);
                      const rState = getState(bringListCompletedId);
              
                      if (!aState || aState.val == null || !bState || bState.val == null) { finishLock(); return; }
              
                      const alexaList   = JSON.parse(aState.val);
                      const bringActive = JSON.parse(bState.val);
                      const bringRecent = (rState && rState.val) ? JSON.parse(rState.val) : [];
              
                      // grobe Bring-Zeitstempel (Listenebene) nur für die Konfliktlösung
                      const bringContentLc = bState.lc || bState.ts || 0;
                      const bringRecentLc  = (rState && (rState.lc || rState.ts)) || 0;
              
                      // --- Maps der aktuellen Zustände ---
                      const alexaByName = {};   // active gewinnt bei Namens-Duplikaten
                      alexaList.forEach(it => {
                          const k = cleanName(it.value); if (!k) return;
                          const st = it.completed ? 'done' : 'active';
                          if (!alexaByName[k] || st === 'active') alexaByName[k] = it;
                      });
                      const bringActiveMap = {}; bringActive.forEach(it => { const k = cleanName(it.name); if (k) bringActiveMap[k] = it; });
                      const bringRecentMap = {}; bringRecent.forEach(it => { const k = cleanName(it.name); if (k) bringRecentMap[k] = it; });
              
                      const shadow    = loadShadow();
                      const newShadow = {};
              
                      const names = new Set([
                          ...Object.keys(alexaByName), ...Object.keys(bringActiveMap),
                          ...Object.keys(bringRecentMap), ...Object.keys(shadow)
                      ]);
              
                      names.forEach(name => {
                          const aItem   = alexaByName[name];
                          const aStatus = aItem ? (aItem.completed ? 'done' : 'active') : 'absent';
                          const bStatus = bringActiveMap[name] ? 'active' : (bringRecentMap[name] ? 'done' : 'absent');
                          const prev    = shadow[name] || { a: 'absent', b: 'absent', c: 'absent' };
              
                          const aChanged = aStatus !== prev.a;
                          const bChanged = bStatus !== prev.b;
              
                          const displayName = (bringActiveMap[name] || bringRecentMap[name] || {}).name
                                              || (aItem ? aItem.value : null) || prev.name || formatName(name);
              
                          const aTs = aItem ? (aItem.updatedDateTime || 0) : 0;
                          const bTs = (bStatus === 'active') ? bringContentLc : (bStatus === 'done' ? bringRecentLc : 0);
              
                          // --- Zielzustand T bestimmen ---
                          let T;
                          if      (aChanged && !bChanged) T = aStatus;
                          else if (bChanged && !aChanged) T = bStatus;
                          else if (aChanged &&  bChanged) T = (aStatus === bStatus) ? aStatus
                                                                : resolveConflict(aStatus, bStatus, aTs, bTs, displayName);
                          else                            T = prev.c; // nichts geändert -> Kanon halten
                          if (!T) T = (aStatus !== 'absent') ? aStatus : bStatus;
              
                          // --- Alexa angleichen ---
                          let aResult = aStatus;
                          if (T === 'active') {
                              if (aStatus === 'absent') { log(`[ALEXA +]          ${displayName}`); setState(alexaAddToList, displayName); aResult = 'active'; }
                              else if (aStatus === 'done') { log(`[ALEXA reaktiv.]    ${displayName}`); setState(`${alexa2BaseId}.items.${aItem.id}.completed`, false); aResult = 'active'; }
                          } else if (T === 'done') {
                              if (aStatus === 'active') { log(`[ALEXA abgehakt]    ${displayName}`); setState(`${alexa2BaseId}.items.${aItem.id}.completed`, true); aResult = 'done'; }
                              // aStatus 'absent' -> erledigtes Item lässt sich in Alexa nicht erzeugen, bleibt absent
                          } else if (T === 'absent') {
                              if (aStatus !== 'absent') { log(`[ALEXA gelöscht]    ${displayName}`); setState(`${alexa2BaseId}.items.${aItem.id}.#delete`, true); aResult = 'absent'; }
                          }
              
                          // --- Bring angleichen ---
                          let bResult = bStatus;
                          if (T === 'active') {
                              if (bStatus !== 'active') { log(`[BRING +]           ${displayName}`); setState(bringAddToList, displayName); bResult = 'active'; }
                          } else if (T === 'done') {
                              if (bStatus === 'active') { log(`[BRING abgehakt]    ${displayName}`); setState(bringCompleteItem, displayName); bResult = 'done'; }
                              // bStatus 'absent' -> kann nicht als erledigt erzeugt werden, bleibt absent
                          } else if (T === 'absent') {
                              if (bStatus !== 'absent') { log(`[BRING gelöscht]    ${displayName}`); setState(bringRemoveItem, displayName); bResult = 'absent'; }
                          }
              
                          // --- neuen Schatten schreiben (komplett verschwundene Items fallen raus) ---
                          if (!(aResult === 'absent' && bResult === 'absent')) {
                              newShadow[name] = { a: aResult, b: bResult, c: T, name: displayName, ts: Date.now() };
                          }
                      });
              
                      saveShadow(newShadow);
              
                  } catch (e) {
                      log('Fehler im Sync-Skript: ' + e, 'error');
                  }
                  finishLock();
              }
              
              // --- TRIGGER mit Debounce ---
              function triggerSync() {
                  if (debounceTimer) clearTimeout(debounceTimer);
                  debounceTimer = setTimeout(doSync, DEBOUNCE_MS);
              }
              
              on({ id: bringListId,          change: 'ne' }, triggerSync);
              on({ id: bringListCompletedId, change: 'ne' }, triggerSync);
              on({ id: alexaListId,          change: 'ne' }, triggerSync);
              
              if (WATCH_ALEXA_CONNECTION) {
                  on({ id: 'alexa2.0.info.connection', change: 'ne' }, obj => {
                      if (obj.state.val === false) log('alexa2-Cloud-Verbindung verloren – Sync evtl. inaktiv bis zum Reconnect.', 'warn');
                  });
              }
              
              // --- START: Schatten-State sicherstellen, dann initial abgleichen ---
              createState(SHADOW_STATE, '{}', false, { name: 'Alexa-Bring Sync Shadow', type: 'string', read: true, write: true }, () => {
                  log('Alexa <-> Bring Sync (Shadow-Merge) gestartet. Initialer Abgleich …');
                  triggerSync();
              });
              

              Das Script müsste ihr einfach als JS Script in IO Broker kopieren und starten. Der Rest funktioniert dann automatisch. WICHTIG NOCHMAL: Die Bring Base ID bzw. der Platzhalter XXX mit eurer echten Listen ID ersetzen.

              DANKE nochmal an die gesamte Community, mit deren Hilfe dieses angepasste und für meine Zwecke hervorragend funktionierende Script entstanden ist. KUDOS an alle die ihre Scripts geteilt haben. 🤙 👏

              1 Antwort Letzte Antwort
              0
              • I Offline
                I Offline
                IOMax
                schrieb am zuletzt editiert von
                #184

                @schiefvancleef danke, eine Umsetzung auf Groß/Klein wäre noch super

                1 Antwort Letzte Antwort
                0
                • LocosL Offline
                  LocosL Offline
                  Locos
                  schrieb am zuletzt editiert von Locos
                  #185

                  Hi zusammen,

                  ich habe mir ein eigenes schlankes Skript gebaut, weil ich Alexa einfach nur als reines Spracheingabe-Mikrofon für meine Bring!-Liste nutzen will. Nach intensiven Tests im Alltag wurde der Code noch einmal überarbeitet und optimiert.

                  UPDATE (02.07.2026):

                  Was ist neu?

                  • 🚀 Multi-Item-Support (Großeinkäufe):
                    Sagt ihr z. B. "Alexa, füge Äpfel und Bananen hinzu", zerschneidet das Skript den Satz ab sofort automatisch bei jedem "und". Es schickt getrennte Artikel an Bring! und löscht auch alle einzeln bei Alexa.

                  • 🧮 Intelligenter Mengen-Addierer:
                    Wenn ihr schon "2 Flaschen Bier" auf der Liste habt und später "Schreibe zwei Flaschen Bier auf die Liste" sagt, liest das Skript die aktuelle Bring-Liste aus und rechnet zusammen ➔ Es landen "Bier, 4 Flaschen" in der App!

                  • 📦 Riesiges Verpackungs-Wörterbuch:
                    Der Filter erkennt jetzt auch unzählige Haushalts-Einheiten wie Packungen, Dosen, Gläser, Rollen, Tuben, Kisten und Kartons.

                  • ⏱️ Anti-Verschluck-Garantie (Staggering):
                    Werden mehrere Artikel gleichzeitig angesagt, schickt das Skript diese nun mit 1,5 Sekunden Abstand an den Adapter. So geht garantiert nichts mehr verloren.

                  • 🛠️ Feinschliff & Bugfixes:
                    Das Skript reagiert nun auch auf wiederholte, identische Sätze (change: "any"), erkennt Trigger-Wörter wie "hinzu" und löscht hängende Cloud-Warteschleifen beim Skript-Neustart sauber auf.

                  Prinzip:

                  • Erkennen:
                    Ihr sagt: "Alexa, pack drei Bananen auf die Einkaufsliste". Das Skript reagiert auf den neuen Text in eurem Alexa-Verlauf (alexa2.0.History.summary).

                  • Eintragen:
                    Das Skript schnappt sich den Artikel, formatiert ihn und schickt ihn sofort an euren Bring!-Adapter.

                  • Löschen (Hier kommt die Alexa-Liste ins Spiel):
                    Da Alexa den Artikel parallel natürlich trotzdem auf ihre eigene Liste setzt, wartet das Skript ein paar Sekunden, bis die Amazon-Cloud synchronisiert hat. Dann durchsucht es das JSON eurer echten Alexa-Einkaufsliste (alexa2.0.Lists.SHOP.json), sucht dort nach den "Bananen", schnappt sich die von Amazon vergebene Artikel-ID und löscht den Eintrag direkt von der Alexa-Liste.

                  • Ergebnis:
                    Das Produkt landet perfekt in eurer Bring!-App und eure Alexa-Liste wird im Hintergrund automatisch bereinigt.

                  Funktionen:

                  • Mathematischer Zahlen-Gesamt-Parser:
                    Macht aus gesprochenen Wörtern echte Zahlen. Die Logik basiert auf der genialen Grundidee des Users Heimweh, wurde von uns aber komplett neu aufgebaut, damit nun auch komplexe, zusammengesetzte Zahlenketten (z. B. "zwei hundert fünfzig gramm butter" ➔ 250) im selben Durchlauf fehlerfrei aufaddiert und zusammengefasst werden!

                  • Bruchzahlen-Erkennung:
                    Unterstützt jetzt auch native Bruchteile wie "ein halbes Kilo" (➔ 0.5Kg) oder "ein viertel Pfund".

                  • Automatische Groß-/Kleinschreibung:
                    Der Produktname in Bring! wird mit einem großen Anfangsbuchstaben übernommen (z. B. "frische milch" ➔ "Frische milch").

                  • Saubere Mengentrennung:
                    Zahlen und Einheiten am Anfang werden erkannt und so an den Bring!-Adapter übergeben, dass sie in der Bring!-App perfekt getrennt im Feld specification (Menge) und name (Produkt) landen. Kein Textmatsch!

                  • Cloud-Warteschleife (Dynamic Retry):
                    Weil die Amazon-Cloud manchmal pennt und ein paar Sekunden braucht, um das JSON im ioBroker zu aktualisieren, prüft mein Skript bis zu 5-mal alle 3 Sekunden nach, ob der Artikel da ist. Erst wenn er im JSON auftaucht, wird er per .#delete-Befehl weggebeamt.

                  • Error-Handling & Logging:
                    Jede Texttransformation und Regex-Filterung ist einzeln per try-catch abgesichert. Sollte die Spracherkennung von Alexa mal totalen Kauderwelsch liefern, stürzt das Skript nicht mehr ab, sondern loggt den Fehler sauber und schickt den Artikel im Notfall-Fallback einfach als Rohtext an Bring!.

                  • Schont die Ressourcen:
                    Kein permanentes JSON-Dauer-Polling im Hintergrund. Das Skript springt nur an, wenn ich auch wirklich was ansage.

                  Voraussetzungen:
                  JavaScript-Adapter, alexa2-Adapter (Listenfunktion aktiv), bring-Adapter.

                  Code (Einfach als neues JavaScript (JS) anlegen, eure eigene Bring-ID oben eintragen und fertig!):

                  /**
                   * Alexa Voice-Input to Bring! List Pushing Engine
                   * 
                   * Erkennt Sprachbefehle aus der Alexa-History, wandelt Zahlwörter um,
                   * trennt Mengen sauber ab (z.B. "3 Erdbeeren" -> "Erdbeeren, 3" für Bring!),
                   * teilt Aufzählungen ("und", ",") in einzelne Artikel auf,
                   * addiert Mengen bestehender Artikel intelligent zusammen,
                   * formatiert den Artikel und löscht ihn anschließend sofort von Alexa.
                   */
                   
                  // --- KONFIGURATION ---
                   
                  // ID des Datenpunkts für die Alexa History Zusammenfassung und Antworttext
                  const alexaHistorySummaryId = 'alexa2.0.History.summary';
                  const alexaHistoryAnswerId = 'alexa2.0.History.answerText';
                   
                  // Basis-ID deiner Bring! Adapter-Instanz (HIER DEINE EIGENE ID EINTRAGEN)
                  const bringBaseId = 'bring.0.deine-bring-liste-id-hier'; 
                  const bringAddItemStateId = bringBaseId + '.saveItem';
                   
                  // Pfad zur Alexa-Einkaufsliste (Basis und JSON)
                  const alexa2BaseId = 'alexa2.0.Lists.SHOP'; 
                  const alexaShoppingListJsonId = `${alexa2BaseId}.json`;
                   
                  // Schalter für Debug-Ausgaben im ioBroker-Log
                  const printDebug = true;
                  
                  // Array für laufende Timeouts (verhindert Fehler bei Skript-Neustart)
                  let activeTimeouts = [];
                   
                   
                  // --- HILFSFUNKTIONEN ---
                   
                  function debug(msg) {
                      if (printDebug) {
                          log('[Alexa->Bring Push] ' + msg);
                      }
                  }
                   
                  // Schneidet führende Alexa-Befehlswörter ab
                  function stripPrefixes(text) {
                      if (!text) return '';
                      const prefixesToRemove = [
                          /^(?:hey\s+)?alexa,\s*/i,
                          /^(?:hey\s+)?alexa\s+/i,
                          /^füge\s+/i, /^setz\s+/i, /^setze\s+/i, /^packe\s+/i, /^pack\s+/i, 
                          /^schreib\s+/i, /^schreibe\s+/i, /^notiere\s+/i
                      ];
                      let result = String(text).trim();
                      let changed = true;
                      
                      while (changed) {
                          let startLen = result.length;
                          for (const prefixRegex of prefixesToRemove) {
                              result = result.replace(prefixRegex, '').trim();
                          }
                          changed = result.length !== startLen;
                      }
                      return result;
                  }
                  
                  // Prüft alte und neue Mengen und addiert sie, falls die Einheiten passen
                  function mergeQuantities(oldSpec, newSpec) {
                      if (!oldSpec) return newSpec;
                      if (!newSpec) return oldSpec;
                  
                      const regex = /^(\d+(?:[.,]\d+)?)\s*(.*)$/i;
                      const matchOld = String(oldSpec).match(regex);
                      const matchNew = String(newSpec).match(regex);
                  
                      if (matchOld && matchNew) {
                          let numOld = parseFloat(matchOld[1].replace(',', '.'));
                          let numNew = parseFloat(matchNew[1].replace(',', '.'));
                          let unitOld = matchOld[2].trim().toLowerCase();
                          let unitNew = matchNew[2].trim().toLowerCase();
                  
                          // Wenn die Einheiten gleich sind (oder eine fehlt), wird addiert
                          if (unitOld === unitNew || unitOld === '' || unitNew === '') {
                              let total = numOld + numNew;
                              let unitToUse = matchNew[2].trim() || matchOld[2].trim();
                              return unitToUse ? `${total} ${unitToUse}` : `${total}`;
                          } else {
                              // Wenn Einheiten unterschiedlich sind (z.B. "500 g" und "1 Packung"),
                              // hängen wir sie einfach mit einem "+" zusammen
                              return `${oldSpec} + ${newSpec}`;
                          }
                      }
                      return newSpec; // Fallback, falls keine Zahlen gefunden wurden
                  }
                   
                  // Zuverlässiger Parser für deutsche Zahlwörter
                  function wordsToNumbersSmart(text) {
                      if (!text) return '';
                      try {
                          let t = String(text).toLowerCase().replace(/\s+/g, ' ');
                   
                          const ones = {
                              "null": 0, "eins": 1, "eine": 1, "einen": 1, "ein": 1, "milli": 1,
                              "zwei": 2, "drei": 3, "vier": 4, "fünf": 5, "sechs": 6, "sieben": 7, "acht": 8, "neun": 9,
                              "zehn": 10, "elf": 11, "zwölf": 12, "dreizehn": 13, "vierzehn": 14, "fünfzehn": 15,
                              "sechzehn": 16, "siebzehn": 17, "achtzehn": 18, "neunzehn": 19
                          };
                          const tens = {
                              "zwanzig": 20, "dreißig": 30, "dreissig": 30, "vierzig": 40, "fünfzig": 50,
                              "sechzig": 60, "siebzig": 70, "achtzig": 80, "neunzig": 90
                          };
                          const multipliers = { "hundert": 100, "tausend": 1000 };
                          const skipWords = ["und"];
                   
                          const words = t.split(/\s+/).filter(Boolean);
                          const out = [];
                          
                          let currentNumber = 0;
                          let hasNumber = false;
                   
                          for (let i = 0; i < words.length; i++) {
                              const w = words[i];
                   
                              if (ones[w] !== undefined) {
                                  currentNumber += ones[w];
                                  hasNumber = true;
                              } else if (tens[w] !== undefined) {
                                  currentNumber += tens[w];
                                  hasNumber = true;
                              } else if (multipliers[w] !== undefined) {
                                  if (currentNumber === 0) currentNumber = 1;
                                  currentNumber *= multipliers[w];
                                  hasNumber = true;
                              } else if (!isNaN(Number(w))) {
                                  if (hasNumber) {
                                      out.push(String(currentNumber));
                                      currentNumber = 0;
                                      hasNumber = false;
                                  }
                                  out.push(w);
                              } else if (skipWords.includes(w) && hasNumber) {
                                  continue;
                              } else {
                                  if (hasNumber) {
                                      out.push(String(currentNumber));
                                      currentNumber = 0;
                                      hasNumber = false;
                                  }
                                  if (out.length === 0 || !isNaN(Number(out[out.length - 1]))) {
                                      out.push(w.charAt(0).toUpperCase() + w.slice(1));
                                  } else {
                                      out.push(w);
                                  }
                              }
                          }
                   
                          if (hasNumber) {
                              out.push(String(currentNumber));
                          }
                   
                          let resultString = out.join(" ");
                   
                          // Brüche umwandeln
                          resultString = resultString.replace(/\b(1\s+)?halbe?s?\b/gi, '0.5');
                          resultString = resultString.replace(/\b(1\s+)?viertel\b/gi, '0.25');
                   
                          return resultString;
                      } catch (err) {
                          log(`Fehler in wordsToNumbersSmart bei Text "${text}": ${err.message}`, 'warn');
                          return String(text);
                      }
                  }
                   
                  // Bereinigt den Text, trennt Mengen ab und sorgt für korrekte Groß-/Kleinschreibung
                  function ListCleaner(Eintrag = '') {
                      if (!Eintrag) return { formatted: '', product: '', quantity: '' };
                      
                      try {
                          let textWithNumbers = wordsToNumbersSmart(Eintrag);
                          if (!textWithNumbers || typeof textWithNumbers !== 'string') {
                              textWithNumbers = String(Eintrag);
                          }
                          
                          textWithNumbers = textWithNumbers.trim();
                  
                          const quantityRegex = /^(\d+(?:[.,]\d+)?)(?:\s*(mal|x|stck|stück|l|ltr|liter|kg|kilo|g|gramm|pfund|flasche|flaschen|packung|packungen|päckchen|dose|dosen|becher|glas|gläser|rolle|rollen|tube|tuben|tafel|tafeln|kiste|kisten|karton|kartons))?\s+(.+)$/i;
                          const match = textWithNumbers.match(quantityRegex);
                          
                          if (match && match[1] && match[3]) {
                              let quantity = String(match[1]).trim();
                              let unit = match[2] ? String(match[2]).trim().toLowerCase() : '';
                              let product = String(match[3]).trim();
                              
                              if (product.length > 0) {
                                  product = product.charAt(0).toUpperCase() + product.slice(1);
                                  
                                  if (['kilo', 'kg'].includes(unit)) {
                                      unit = 'kg';
                                  } else if (['gramm', 'g'].includes(unit)) {
                                      unit = 'g';
                                  } else if (['liter', 'l', 'ltr'].includes(unit)) {
                                      unit = 'l';
                                  } else if (['stck', 'stück'].includes(unit)) {
                                      unit = 'Stück';
                                  } else if (['mal', 'x'].includes(unit)) {
                                      unit = ''; 
                                  } else if (unit) {
                                      unit = unit.charAt(0).toUpperCase() + unit.slice(1);
                                  }
                                  
                                  let finalQuantity = unit ? `${quantity} ${unit}` : quantity;
                                  
                                  debug(`Menge erkannt: "${finalQuantity}", Produkt: "${product}" -> Formatiere für Bring als "${product}, ${finalQuantity}"`);
                                  return { formatted: `${product}, ${finalQuantity}`, product: product, quantity: finalQuantity };
                              }
                          }
                          
                          let normProduct = textWithNumbers.charAt(0).toUpperCase() + textWithNumbers.slice(1);
                          return { formatted: normProduct, product: normProduct, quantity: '' };
                      } catch (err) {
                          log(`Fehler in ListCleaner bei Eintrag "${Eintrag}": ${err.message}`, 'error');
                          let fallbackProduct = String(Eintrag).charAt(0).toUpperCase() + String(Eintrag).slice(1);
                          return { formatted: fallbackProduct, product: fallbackProduct, quantity: '' };
                      }
                  }
                   
                  // Intelligente Löschfunktion mit Cloud-Warteschleife und Multi-Match-Logik
                  function removeItemFromAlexaList(itemName, extractedProduct, attempt = 1) {
                      const maxAttempts = 5; 
                      const retryDelay = 3000; 
                   
                      let cleanedAlexaName = stripPrefixes(itemName);
                      debug(`Versuche, "${cleanedAlexaName}" (Produkt-Match: "${extractedProduct}") von der Alexa-Liste zu entfernen (Versuch ${attempt}/${maxAttempts}).`);
                      
                      try {
                          const listState = getState(alexaShoppingListJsonId);
                          
                          if (!listState || !listState.val) {
                              debug("Alexa Einkaufslisten-JSON ist leer. Warte auf Adapter-Update...");
                              if (attempt < maxAttempts) {
                                  let timer = setTimeout(() => removeItemFromAlexaList(itemName, extractedProduct, attempt + 1), retryDelay);
                                  activeTimeouts.push(timer);
                              }
                              return;
                          }
                   
                          const shoppingList = JSON.parse(listState.val);
                          let itemIdToDelete = null;
                   
                          const search1 = cleanedAlexaName.toLowerCase();
                          const search2 = extractedProduct.toLowerCase();
                   
                          for (const item of shoppingList) {
                              if (item && item.value) {
                                  const valLower = item.value.toLowerCase();
                                  if (valLower === search1 || valLower === search2 || search1.includes(valLower) || valLower.includes(search2)) {
                                      itemIdToDelete = item.id;
                                      cleanedAlexaName = item.value; 
                                      break;
                                  }
                              }
                          }
                   
                          if (itemIdToDelete) {
                              const deleteStateId = `${alexa2BaseId}.items.${itemIdToDelete}.#delete`;
                              debug(`Artikel-ID gefunden. Sende Löschbefehl an: ${deleteStateId}`);
                              setState(deleteStateId, true);
                              debug(`Löschbefehl für "${cleanedAlexaName}" erfolgreich an Amazon übermittelt.`);
                          } else {
                              if (attempt < maxAttempts) {
                                  debug(`Artikel "${cleanedAlexaName}" / "${extractedProduct}" noch nicht im JSON. Cloud synchronisiert noch... Erneuter Versuch in 3 Sek.`);
                                  let timer = setTimeout(() => removeItemFromAlexaList(itemName, extractedProduct, attempt + 1), retryDelay);
                                  activeTimeouts.push(timer);
                              } else {
                                  debug(`Artikel wurde nach ${maxAttempts} Versuchen nicht im JSON gefunden.`);
                              }
                          }
                   
                      } catch (e) {
                          log(`Fehler beim Entfernen des Artikels von der Alexa-Liste: ${e}`, 'error');
                      }
                  }
                   
                   
                  // --- HAUPTLOGIK ---
                   
                  on({ id: alexaHistorySummaryId, change: "any", ack: true }, function (obj) {
                      try {
                          if (!obj.state || !obj.state.val || typeof obj.state.val !== 'string') return;
                   
                          const summaryTextFull = obj.state.val;
                          const summaryText = summaryTextFull.toLowerCase();
                          
                          const alexaAnswer = getState(alexaHistoryAnswerId)?.val || 'Keine Antwort aufgezeichnet';
                          debug(`Neuer History Summary Eintrag: "${summaryTextFull}" | Alexa Antwort: "${alexaAnswer}"`);
                   
                          // Trigger-Phrasen inklusive "hinzu" aus dem Forum
                          const triggerPhrases = [' auf die einkaufsliste', ' zur einkaufsliste', ' hinzu', ' hinzufügen'];
                          let itemFound = false;
                          let itemNameRaw = '';
                   
                          for (const phrase of triggerPhrases) {
                              const index = summaryText.lastIndexOf(phrase);
                              if (index !== -1 && index > 0) {
                                  itemNameRaw = summaryTextFull.substring(0, index).trim();
                                  itemNameRaw = stripPrefixes(itemNameRaw);
                                  itemFound = true;
                                  break;
                              }
                          }
                   
                          if (itemFound && itemNameRaw) {
                              // 1. Text bei Komma oder " und " aufteilen für Multi-Item-Support
                              const itemsToProcess = itemNameRaw.split(/,\s*|\s+und\s+/i).filter(Boolean);
                              
                              debug(`Multi-Item-Erkennung: Satz wurde in ${itemsToProcess.length} Artikel geteilt.`);
                  
                              // 2. Jeden Artikel einzeln verarbeiten
                              itemsToProcess.forEach((singleItemRaw, index) => {
                                  const cleanResult = ListCleaner(singleItemRaw);
                                  const cleanedItemName = cleanResult.formatted; 
                                  const originalItemName = singleItemRaw.trim();            
                  
                                  if (cleanedItemName) {
                                      
                                      // --- NEU: Bring! JSON prüfen und Mengen addieren ---
                                      let finalItemNameToBring = cleanedItemName;
                                      
                                      try {
                                          const bringContentState = getState(bringBaseId + '.content');
                                          if (bringContentState && bringContentState.val) {
                                              let bringList = typeof bringContentState.val === 'string' ? JSON.parse(bringContentState.val) : bringContentState.val;
                                              
                                              // Sucht, ob das Produkt schon auf der Bring!-Liste steht
                                              let existingItem = bringList.find(i => i.name.toLowerCase() === cleanResult.product.toLowerCase());
                                              
                                              if (existingItem && existingItem.specification) {
                                                  let mergedSpec = mergeQuantities(existingItem.specification, cleanResult.quantity);
                                                  finalItemNameToBring = `${cleanResult.product}, ${mergedSpec}`;
                                                  debug(`Artikel "${cleanResult.product}" existiert bereits. Addiere Menge: "${existingItem.specification}" + "${cleanResult.quantity}" -> "${mergedSpec}"`);
                                              }
                                          }
                                      } catch (e) {
                                          debug(`Fehler beim Auslesen der bestehenden Bring-Liste: ${e.message}`);
                                      }
                                      // ---------------------------------------------------
                  
                                      // 3. Gestaffelte Ausführung (1.5 Sekunden Abstand pro Artikel),
                                      // damit der Bring! Adapter sich nicht verschluckt.
                                      let processingDelay = index * 1500; 
                  
                                      let processTimer = setTimeout(() => {
                                          debug(`[Artikel ${index + 1}/${itemsToProcess.length}] Roh: "${originalItemName}" -> Bring!: "${finalItemNameToBring}".`);
                                          
                                          // Schritt 1: Zu Bring! hinzufügen (mit eventuell addierter Menge)
                                          setState(bringAddItemStateId, finalItemNameToBring, false);
                                          
                                          // Schritt 2: Verzögertes Löschen auf Alexa-Seite triggern
                                          let deleteTimer = setTimeout(() => {
                                              removeItemFromAlexaList(originalItemName, cleanResult.product);
                                          }, 4000);
                                          activeTimeouts.push(deleteTimer);
                  
                                      }, processingDelay);
                                      activeTimeouts.push(processTimer);
                   
                                  } else {
                                      debug(`Artikel "${originalItemName}" ist nach Bereinigung leer. Ignoriere.`);
                                  }
                              });
                          }
                      } catch (globalErr) {
                          log(`Unerwarteter globaler Fehler in der Hauptschleife: ${globalErr.message}`, 'error');
                      }
                  });
                  
                  
                  // --- CLEANUP BEIM BEENDEN ---
                  onStop(function () {
                      activeTimeouts.forEach(timer => clearTimeout(timer));
                      activeTimeouts = [];
                      debug("Skript gestoppt. Alle laufenden Timeouts wurden sicher beendet.");
                  }, 10);
                   
                   
                  // --- SKRIPTSTART ---
                  debug("Skript gestartet. Multi-Item-Parser, Mengen-Addierer und erweiterte Triggerwörter aktiv.");
                  
                  1 Antwort Letzte Antwort
                  0
                  • mcBirneM Offline
                    mcBirneM Offline
                    mcBirne
                    schrieb am zuletzt editiert von mcBirne
                    #186

                    Super, vielen Dank! Klappt super bei mir.
                    Aber mir ist aufgefallen, dass es nur funktioniert wenn ich sage:
                    "packe 3 Äpfel auf die Eingaufsliste". Wenn ich sage: "füge 3 Äpel hinzu" passiert leider nichts. Alexa packt sie auf die internen Einkaufsliste aber das Skript reagiert nicht. Gibt es eine Möglichgeit auch das noch abzugreifen?
                    Habe es gefunden.
                    Zeile 252 sieht bei mir jetzt so aus:

                    const triggerPhrases = [' auf die einkaufsliste', ' zur einkaufsliste',' hinzu',' hinzufügen'];
                    

                    Werde es weiter testen.

                    LocosL 1 Antwort Letzte Antwort
                    1
                    • mcBirneM mcBirne

                      Super, vielen Dank! Klappt super bei mir.
                      Aber mir ist aufgefallen, dass es nur funktioniert wenn ich sage:
                      "packe 3 Äpfel auf die Eingaufsliste". Wenn ich sage: "füge 3 Äpel hinzu" passiert leider nichts. Alexa packt sie auf die internen Einkaufsliste aber das Skript reagiert nicht. Gibt es eine Möglichgeit auch das noch abzugreifen?
                      Habe es gefunden.
                      Zeile 252 sieht bei mir jetzt so aus:

                      const triggerPhrases = [' auf die einkaufsliste', ' zur einkaufsliste',' hinzu',' hinzufügen'];
                      

                      Werde es weiter testen.

                      LocosL Offline
                      LocosL Offline
                      Locos
                      schrieb am zuletzt editiert von
                      #187

                      @mcBirne danke für dein Feedback. habe das Skript nochmal etwas überarbeitet. Siehe vorherigen Post.

                      Liebe Grüße

                      1 Antwort Letzte Antwort
                      0

                      Hey! Du scheinst an dieser Unterhaltung interessiert zu sein, hast aber noch kein Konto.

                      Hast du es satt, bei jedem Besuch durch die gleichen Beiträge zu scrollen? Wenn du dich für ein Konto anmeldest, kommst du immer genau dorthin zurück, wo du zuvor warst, und kannst dich über neue Antworten benachrichtigen lassen (entweder per E-Mail oder Push-Benachrichtigung). Du kannst auch Lesezeichen speichern und Beiträge positiv bewerten, um anderen Community-Mitgliedern deine Wertschätzung zu zeigen.

                      Mit deinem Input könnte dieser Beitrag noch besser werden 💗

                      Registrieren Anmelden
                      Antworten
                      • In einem neuen Thema antworten
                      Anmelden zum Antworten
                      • Älteste zuerst
                      • Neuste zuerst
                      • Meiste Stimmen


                      Support us

                      ioBroker
                      Community Adapters
                      Donate

                      322

                      Online

                      33.0k

                      Benutzer

                      83.4k

                      Themen

                      1.3m

                      Beiträge
                      Community
                      Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen | Einwilligungseinstellungen
                      ioBroker Community 2014-2026
                      logo
                      • Anmelden

                      • Du hast noch kein Konto? Registrieren

                      • Anmelden oder registrieren, um zu suchen
                      • Erster Beitrag
                        Letzter Beitrag
                      0
                      • Home
                      • Aktuell
                      • Tags
                      • Ungelesen 0
                      • Kategorien
                      • Unreplied
                      • Beliebt
                      • GitHub
                      • Docu
                      • Hilfe