NEWS
Alexa Shopping List mit Bring synchronisieren
-
Danke an alle für diese tolle Lösung. Ich hatte nur das Problem, dass das sync script alle 2 Minuten Änderungen erkannt hatte, obwohl sich nur das Update Date geändert hatte. Ich habe keine Sachen in Bring oder Alexa hinzugefügt/gelöscht. Daher wollte ich unnötige updates der Listen vermeiden und habe das Skript für mich wie folgt angepasst.
/****************************************************** * Bring ↔ Alexa List Sync (bidirectional) – MASTER VERSION * Author: SiL4S ******************************************************/ // List Object IDs const bringBaseId = 'bring.0.2c7bf13d-3896-4632-8a4e-c1d5a4993ce5'; const alexa2BaseId = 'alexa2.1.Lists.SHOP'; // Bring datapoints const bringListId = bringBaseId + '.content'; const bringListCompletedId = bringBaseId + '.recentContent'; const bringAddToList = bringBaseId + '.saveItem'; const bringCompleteItem = bringBaseId + '.moveToRecentContent'; // Alexa datapoints const alexaAddToList = alexa2BaseId + '.#New'; const alexaListId = alexa2BaseId + '.json'; // Loop-protection let lastAlexaJson = ""; let lastBringJson = ""; // Debug logging const printDebug = true; function debug(msg) { if (printDebug) log(msg); } // Bring Status const TodoItemStatus = { NeedsAction: 'needs_action', Completed: 'completed', }; // Normalize item names (trim, collapse spaces, fix casing) function normalize(name = '') { return name .trim() .replace(/\s+/g, ' ') .toLowerCase() .replace(/(^\w|\s\w)/g, m => m.toUpperCase()); } // Compare completed-state between Alexa and Bring items function compareCompleted(alexaItem, bringItem) { return ( (alexaItem.completed && bringItem.status === TodoItemStatus.Completed) || (!alexaItem.completed && bringItem.status === TodoItemStatus.NeedsAction) ); } // Remove duplicate Alexa entries function eliminateDuplicated() { const raw = getState(alexaListId).val; if (!raw) return; const list = JSON.parse(raw); const seen = {}; for (const i of list) { const normalized = normalize(i.value); if (seen[normalized]) { debug(`Deleting duplicate Alexa item: ${i.value}`); setState(`${alexa2BaseId}.items.${i.id}.#delete`, true); } else { seen[normalized] = true; } } } //----------------------------------------------- // Main Sync Function //----------------------------------------------- function syncLists(alexaList, bringList, bringRecent, timestampBring, source) { //---------------------------------------------------- // 1) Step: Process Alexa items (add / complete) //---------------------------------------------------- for (const alexaItem of alexaList) { const nameNorm = normalize(alexaItem.value); let foundInBring = false; let foundInRecent = false; // Bring "active" items for (const b of bringList) { if (normalize(b.name) === nameNorm) { foundInBring = true; // Completed syncing if (source === 'Alexa' && alexaItem.completed && !compareCompleted(alexaItem, b)) { debug(`→ Alexa COMPLETED → Mark completed in Bring: ${b.name}`); setState(bringCompleteItem, b.name); } if (source === 'Bring' && !compareCompleted(alexaItem, b)) { debug(`→ Bring COMPLETED → Mark completed in Alexa: ${alexaItem.value}`); setState(`${alexa2BaseId}.items.${alexaItem.id}.completed`, b.status === TodoItemStatus.Completed); } } } // Bring recentContent for (const r of bringRecent) { if (normalize(r.name) === nameNorm) { foundInRecent = true; if (source === 'Alexa' && !alexaItem.completed) { debug(`→ Alexa ACTIVE → Add item back to Bring: ${r.name}`); setState(bringAddToList, r.name); } if (source === 'Bring' && alexaItem.completed === false) { debug(`→ Bring has this item completed → Delete from Alexa: ${alexaItem.value}`); setState(`${alexa2BaseId}.items.${alexaItem.id}.#delete`, true); } } } //---------------------------------------------------------- // Item exists in Alexa but not in Bring → delete or push //---------------------------------------------------------- if (!foundInBring && !foundInRecent) { if (source === 'Alexa') { debug(`→ New Alexa item → Adding to Bring: ${alexaItem.value}`); setState(bringAddToList, nameNorm); } if (source === 'Bring') { debug(`→ Bring deleted item → Deleting from Alexa: ${alexaItem.value}`); setState(`${alexa2BaseId}.items.${alexaItem.id}.#delete`, true); } } } //---------------------------------------------------- // 2) Step: Push Bring items to Alexa //---------------------------------------------------- if (source === 'Bring') { const alexaNames = alexaList.map(a => normalize(a.value)); for (const b of bringList) { const bNorm = normalize(b.name); if (!alexaNames.includes(bNorm) && b.status !== TodoItemStatus.Completed) { debug(`→ New Bring item → Adding to Alexa: ${b.name}`); setState(alexaAddToList, b.name); } } } } //----------------------------------------------- // Full Sync Runner //----------------------------------------------- function doSync(source) { debug(`--- SYNC START (${source}) ---`); eliminateDuplicated(); const rawAlexa = getState(alexaListId).val; const rawBring = getState(bringListId).val; const rawRecent = getState(bringListCompletedId).val; if (!rawAlexa || !rawBring || !rawRecent) { debug('Missing datapoints!'); return; } const alexaList = JSON.parse(rawAlexa); const bringList = JSON.parse(rawBring); const bringRecent = JSON.parse(rawRecent); const bringTS = getState(bringListId).ts; debug('Processing…'); syncLists(alexaList, bringList, bringRecent, bringTS, source); debug(`--- SYNC FINISHED (${source}) ---`); } //----------------------------------------------- // Bring Trigger //----------------------------------------------- on({ id: bringListId, change: "any" }, () => { const now = getState(bringListId).val; if (now === lastBringJson) { debug('Bring: Update ignored (not real change)'); return; } lastBringJson = now; debug('Bring → Real change detected'); doSync('Bring'); }); //----------------------------------------------- // Alexa Trigger //----------------------------------------------- on({ id: alexaListId, change: "any" }, () => { const now = getState(alexaListId).val; if (now === lastAlexaJson) { debug('Alexa: Update ignored (polling only)'); return; } lastAlexaJson = now; debug('Alexa → Real change detected'); doSync('Alexa'); }); //----------------------------------------------- // Initial sync at startup //----------------------------------------------- doSync('Startup'); -
Danke an alle für diese tolle Lösung. Ich hatte nur das Problem, dass das sync script alle 2 Minuten Änderungen erkannt hatte, obwohl sich nur das Update Date geändert hatte. Ich habe keine Sachen in Bring oder Alexa hinzugefügt/gelöscht. Daher wollte ich unnötige updates der Listen vermeiden und habe das Skript für mich wie folgt angepasst.
/****************************************************** * Bring ↔ Alexa List Sync (bidirectional) – MASTER VERSION * Author: SiL4S ******************************************************/ // List Object IDs const bringBaseId = 'bring.0.2c7bf13d-3896-4632-8a4e-c1d5a4993ce5'; const alexa2BaseId = 'alexa2.1.Lists.SHOP'; // Bring datapoints const bringListId = bringBaseId + '.content'; const bringListCompletedId = bringBaseId + '.recentContent'; const bringAddToList = bringBaseId + '.saveItem'; const bringCompleteItem = bringBaseId + '.moveToRecentContent'; // Alexa datapoints const alexaAddToList = alexa2BaseId + '.#New'; const alexaListId = alexa2BaseId + '.json'; // Loop-protection let lastAlexaJson = ""; let lastBringJson = ""; // Debug logging const printDebug = true; function debug(msg) { if (printDebug) log(msg); } // Bring Status const TodoItemStatus = { NeedsAction: 'needs_action', Completed: 'completed', }; // Normalize item names (trim, collapse spaces, fix casing) function normalize(name = '') { return name .trim() .replace(/\s+/g, ' ') .toLowerCase() .replace(/(^\w|\s\w)/g, m => m.toUpperCase()); } // Compare completed-state between Alexa and Bring items function compareCompleted(alexaItem, bringItem) { return ( (alexaItem.completed && bringItem.status === TodoItemStatus.Completed) || (!alexaItem.completed && bringItem.status === TodoItemStatus.NeedsAction) ); } // Remove duplicate Alexa entries function eliminateDuplicated() { const raw = getState(alexaListId).val; if (!raw) return; const list = JSON.parse(raw); const seen = {}; for (const i of list) { const normalized = normalize(i.value); if (seen[normalized]) { debug(`Deleting duplicate Alexa item: ${i.value}`); setState(`${alexa2BaseId}.items.${i.id}.#delete`, true); } else { seen[normalized] = true; } } } //----------------------------------------------- // Main Sync Function //----------------------------------------------- function syncLists(alexaList, bringList, bringRecent, timestampBring, source) { //---------------------------------------------------- // 1) Step: Process Alexa items (add / complete) //---------------------------------------------------- for (const alexaItem of alexaList) { const nameNorm = normalize(alexaItem.value); let foundInBring = false; let foundInRecent = false; // Bring "active" items for (const b of bringList) { if (normalize(b.name) === nameNorm) { foundInBring = true; // Completed syncing if (source === 'Alexa' && alexaItem.completed && !compareCompleted(alexaItem, b)) { debug(`→ Alexa COMPLETED → Mark completed in Bring: ${b.name}`); setState(bringCompleteItem, b.name); } if (source === 'Bring' && !compareCompleted(alexaItem, b)) { debug(`→ Bring COMPLETED → Mark completed in Alexa: ${alexaItem.value}`); setState(`${alexa2BaseId}.items.${alexaItem.id}.completed`, b.status === TodoItemStatus.Completed); } } } // Bring recentContent for (const r of bringRecent) { if (normalize(r.name) === nameNorm) { foundInRecent = true; if (source === 'Alexa' && !alexaItem.completed) { debug(`→ Alexa ACTIVE → Add item back to Bring: ${r.name}`); setState(bringAddToList, r.name); } if (source === 'Bring' && alexaItem.completed === false) { debug(`→ Bring has this item completed → Delete from Alexa: ${alexaItem.value}`); setState(`${alexa2BaseId}.items.${alexaItem.id}.#delete`, true); } } } //---------------------------------------------------------- // Item exists in Alexa but not in Bring → delete or push //---------------------------------------------------------- if (!foundInBring && !foundInRecent) { if (source === 'Alexa') { debug(`→ New Alexa item → Adding to Bring: ${alexaItem.value}`); setState(bringAddToList, nameNorm); } if (source === 'Bring') { debug(`→ Bring deleted item → Deleting from Alexa: ${alexaItem.value}`); setState(`${alexa2BaseId}.items.${alexaItem.id}.#delete`, true); } } } //---------------------------------------------------- // 2) Step: Push Bring items to Alexa //---------------------------------------------------- if (source === 'Bring') { const alexaNames = alexaList.map(a => normalize(a.value)); for (const b of bringList) { const bNorm = normalize(b.name); if (!alexaNames.includes(bNorm) && b.status !== TodoItemStatus.Completed) { debug(`→ New Bring item → Adding to Alexa: ${b.name}`); setState(alexaAddToList, b.name); } } } } //----------------------------------------------- // Full Sync Runner //----------------------------------------------- function doSync(source) { debug(`--- SYNC START (${source}) ---`); eliminateDuplicated(); const rawAlexa = getState(alexaListId).val; const rawBring = getState(bringListId).val; const rawRecent = getState(bringListCompletedId).val; if (!rawAlexa || !rawBring || !rawRecent) { debug('Missing datapoints!'); return; } const alexaList = JSON.parse(rawAlexa); const bringList = JSON.parse(rawBring); const bringRecent = JSON.parse(rawRecent); const bringTS = getState(bringListId).ts; debug('Processing…'); syncLists(alexaList, bringList, bringRecent, bringTS, source); debug(`--- SYNC FINISHED (${source}) ---`); } //----------------------------------------------- // Bring Trigger //----------------------------------------------- on({ id: bringListId, change: "any" }, () => { const now = getState(bringListId).val; if (now === lastBringJson) { debug('Bring: Update ignored (not real change)'); return; } lastBringJson = now; debug('Bring → Real change detected'); doSync('Bring'); }); //----------------------------------------------- // Alexa Trigger //----------------------------------------------- on({ id: alexaListId, change: "any" }, () => { const now = getState(alexaListId).val; if (now === lastAlexaJson) { debug('Alexa: Update ignored (polling only)'); return; } lastAlexaJson = now; debug('Alexa → Real change detected'); doSync('Alexa'); }); //----------------------------------------------- // Initial sync at startup //----------------------------------------------- doSync('Startup'); -
Danke, hatte auch die regelmäßigen Aktualisierungen.
Hab mal auf deine geänderte Version umgestellt. Bisher schaut alles gut aus!Danke dafür!
-
@elron
Kannst du bitte auf das originale Script verlinken? Ich finde es hier leider nicht.
Danke für die Anpassung. -
@MartyBr sagte in Alexa Shopping List mit Bring synchronisieren:
@elron
Kannst du bitte auf das originale Script verlinken? Ich finde es hier leider nicht.
Danke für die Anpassung.Hi Martin,
hier bzw. auch im Threadverlauf.
-
@MartyBr sagte in Alexa Shopping List mit Bring synchronisieren:
@elron
Der Link funktioniert leider nicht (zumindest bei mir). Ich habe das Script von @sil4s genommen.Vielen Dank
Okay, komisch, hier nochmal der gesamte Code inkl. der aktuellen Änderung:
/*********************************************************************************************************************************************************************************** * GENERIC INFO: * Created by Daniel Drießen @ DDProductions * * Date of creation: 17.07.2024 * Version: 0.1.0.0 * * DESCRIPTION: * This script synchronizes the shopping lists of 'Alexa' and 'Bring'. * It monitors changes in both shopping lists and ensures that they stay updated with each other. * The script uses events to detect changes in the shopping lists of both 'Alexa' and 'Bring'. * When changes are detected, it initiates a synchronization to reconcile any discrepancies between the lists. * The synchronization process involves comparing the items on both lists and updating each list to reflect the combined items from both sources. * * DEPENDENCIES: * - Alexa2 adapter * - Bring adapter * * LIMITATIONS: * Due to the nature of the 'Bring' adapter (which does not store an 'added' or 'updated' timestamp for items on the shopping list), a 'real' synchronization is not possible * because there is no way to detect the order of changes to the shopping lists of 'Alexa' and 'Bring'. * * TODO & FUTURE GOALS: * - Add better error handling. * - Move functions 'getItemsOnAlexaShoppingList' & 'getItemsOnBringShoppingList' into classes 'AlexaShoppingList' & 'BringShoppingList'. * - Move functions 'addItemToShoppingList', 'removeItemFromShoppingList' into classes 'AlexaShoppingList' & 'BringShoppingList'. * - Enhance the synchronization logic to minimize potential syncing errors and improve reliability given the above limitations. * - Maybe add a sync schedule for periodic syncing. * * CHANGELOG: * 18.07.2024 - Initial version completed. * ***********************************************************************************************************************************************************************************/ export default this; /*++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++++ + SCRIPT SETUP + *++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++++*/ /*---------- OBJECT-ID CONSTANTS ------------------------------------------------------------------------------------------------*/ const objectID_alexa_shoppingList_folder = 'alexa2.0.Lists.SHOP'; const objectID_alexa_shoppingList_addItem = 'alexa2.0.Lists.SHOP.#New'; const objectID_bring_shoppingList_sentenceString = 'bring.0.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.enumSentence'; const objectID_bring_shoppingList_addItem = 'bring.0.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.saveItem'; const objectID_bring_shoppingList_removeItem = 'bring.0.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.removeItem'; /*++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++++ *++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++++*/ /*++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++++ + START OF SCRIPT LOGIC + *++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++++*/ var initComplete = false; var syncingInProgress = false; var resyncRequests = []; startScriptLogic(); async function startScriptLogic() { try { await init(); initComplete = true; } catch (error) { console.error(error.message); console.debug(`SCRIPT WILL TERMINATE NOW!`); await this.stopScriptAsync(); } } /*++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++++ *++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++++*/ /*---------- INIT ---------------------------------------------------------------------------------------------------------------*/ async function init(): Promise <void> { console.info(`Script initialization...`); await synchronizeShoppingLists('init', false); console.info(`Script initialization completed successfully!`); await checkForResyncRequests(); } /*---------- TRIGGER ------------------------------------------------------------------------------------------------------------*/ // Alexa on({ id: [].concat([objectID_alexa_shoppingList_folder + '.json']), change: 'ne' }, async (obj) => { let value = obj.state.val; let oldValue = obj.oldState.val; console.debug(`'Alexa' shopping list changed!`); if (initComplete && !syncingInProgress) { await synchronizeShoppingLists('alexa', false); await checkForResyncRequests(); } else { resyncRequests.push('alexa'); } }); // Bring on({ id: [].concat([objectID_bring_shoppingList_sentenceString]), change: 'ne' }, async (obj) => { let value = obj.state.val; let oldValue = obj.oldState.val; console.debug(`'Bring' shopping list changed!`); if (initComplete && !syncingInProgress) { await synchronizeShoppingLists('bring', false); await checkForResyncRequests(); } else { resyncRequests.push('bring'); } }); /*---------- SYNC ---------------------------------------------------------------------------------------------------------------*/ async function synchronizeShoppingLists(syncInitiator:string, isResync:boolean) { console.info(`Sync started`); console.debug(`Sync initiator: '${capitalizeFirstLetter(syncInitiator)}'`); console.debug(`Sync is a resync: '${isResync}'`); syncingInProgress = true; var itemsOnAlexaShoppingList = await getItemsOnAlexaShoppingList(); var itemsOnBringShoppingList = await getItemsOnBringShoppingList(); console.debug(`Items on "Alexa" shopping list (${itemsOnAlexaShoppingList.length}): ${itemsOnAlexaShoppingList.map(item => item.value)}`); console.debug(`Items on "Bring" shopping list (${itemsOnBringShoppingList.length}): ${itemsOnBringShoppingList.map(item => item.value)}`); if (syncInitiator.toLowerCase() === 'init') { // If sync initiator is "init" // create a combined item list and add each item missing on each list to each list. // Create combined shopping list var itemsOnCombinedShoppingList = []; for (const itemOnAlexaShoppingList of itemsOnAlexaShoppingList) { if (!await ShoppingListUtils.checkIfShoppingListArrayContainsShoppingListItem(itemsOnCombinedShoppingList, itemOnAlexaShoppingList)) { itemsOnCombinedShoppingList.push(itemOnAlexaShoppingList); } } for (const itemOnBringShoppingList of itemsOnBringShoppingList) { if (!await ShoppingListUtils.checkIfShoppingListArrayContainsShoppingListItem(itemsOnCombinedShoppingList, itemOnBringShoppingList)) { itemsOnCombinedShoppingList.push(itemOnBringShoppingList); } } //console.debug(`Items on "Combined" shopping list: ${itemsOnCombinedShoppingList.map(item => item.value)}`); // Add each missing item on each list to each list var itemsToAddToAlexaShoppingList = []; var itemsToAddToBringShoppingList = []; for (const itemOnCombinedShoppingList of itemsOnCombinedShoppingList) { if (!await ShoppingListUtils.checkIfShoppingListArrayContainsShoppingListItem(itemsOnAlexaShoppingList, itemOnCombinedShoppingList)) { itemsToAddToAlexaShoppingList.push(itemOnCombinedShoppingList); } if (!await ShoppingListUtils.checkIfShoppingListArrayContainsShoppingListItem(itemsOnBringShoppingList, itemOnCombinedShoppingList)) { itemsToAddToBringShoppingList.push(itemOnCombinedShoppingList); } } if (itemsToAddToAlexaShoppingList.length > 0) { console.debug(`Items to add to "Alexa" shopping list (${itemsToAddToAlexaShoppingList.length}): ${itemsToAddToAlexaShoppingList.map(item => item.value)}`); } if (itemsToAddToBringShoppingList.length > 0) { console.debug(`Items to add to "Bring" shopping list (${itemsToAddToBringShoppingList.length}): ${itemsToAddToBringShoppingList.map(item => item.value)}`); } for (const itemToAddToAlexaShoppingList of itemsToAddToAlexaShoppingList) { await AlexaShoppingListItem.addItemToShoppingList(itemToAddToAlexaShoppingList); } for (const itemToAddToBringShoppingList of itemsToAddToBringShoppingList) { await BringShoppingListItem.addItemToShoppingList(itemToAddToBringShoppingList); } } else if (syncInitiator.toLowerCase() === 'alexa') { // If sync initiator is "alexa" // add each item from the alexa shopping list that is missing on the bring shopping list to the bring shopping list. // Then remove each item from the bring shopping list that is not on the alexa shopping list. // Add each item from the alexa shopping list that is missing on the bring shopping list to the bring shopping list. var itemsToAddToBringShoppingList = []; for (const itemOnAlexaShoppingList of itemsOnAlexaShoppingList) { if (!await ShoppingListUtils.checkIfShoppingListArrayContainsShoppingListItem(itemsOnBringShoppingList, itemOnAlexaShoppingList)) { itemsToAddToBringShoppingList.push(itemOnAlexaShoppingList); } } if (itemsToAddToBringShoppingList.length > 0) { console.debug(`Items to add to "Bring" shopping list (${itemsToAddToBringShoppingList.length}): ${itemsToAddToBringShoppingList.map(item => item.value)}`); } for (const itemToAddToBringShoppingList of itemsToAddToBringShoppingList) { await BringShoppingListItem.addItemToShoppingList(itemToAddToBringShoppingList); } // Get an update of the bring shopping list itemsOnBringShoppingList = await getItemsOnBringShoppingList(); // Remove each item from the bring shopping list that is not on the alexa shopping list. var itemsToRemoveFromBringShoppingList = []; for (const itemOnBringShoppingList of itemsOnBringShoppingList) { if (!await ShoppingListUtils.checkIfShoppingListArrayContainsShoppingListItem(itemsOnAlexaShoppingList, itemOnBringShoppingList)) { itemsToRemoveFromBringShoppingList.push(itemOnBringShoppingList); } } if (itemsToRemoveFromBringShoppingList.length > 0) { console.debug(`Items to remove from "Bring" shopping list (${itemsToRemoveFromBringShoppingList.length}): ${itemsToRemoveFromBringShoppingList.map(item => item.value)}`); } for (const itemToRemoveFromBringShoppingList of itemsToRemoveFromBringShoppingList) { await BringShoppingListItem.removeItemFromShoppingList(itemToRemoveFromBringShoppingList); } } else if (syncInitiator.toLowerCase() === 'bring') { // If sync initiator is "bring" // add each item from the bring shopping list that is missing on the alexa shopping list to the alexa shopping list. // Then remove each item from the alexa shopping list that is not on the bring shopping list. // Add each item from the bring shopping list that is missing on the alexa shopping list to the alexa shopping list. var itemsToAddToAlexaShoppingList = []; for (const itemOnBringShoppingList of itemsOnBringShoppingList) { if (!await ShoppingListUtils.checkIfShoppingListArrayContainsShoppingListItem(itemsOnAlexaShoppingList, itemOnBringShoppingList)) { itemsToAddToAlexaShoppingList.push(itemOnBringShoppingList); } } if (itemsToAddToAlexaShoppingList.length > 0) { console.debug(`Items to add to "Alexa" shopping list (${itemsToAddToAlexaShoppingList.length}): ${itemsToAddToAlexaShoppingList.map(item => item.value)}`); } for (const itemToAddToAlexaShoppingList of itemsToAddToAlexaShoppingList) { await AlexaShoppingListItem.addItemToShoppingList(itemToAddToAlexaShoppingList); } // Get an update of the alexa shopping list itemsOnAlexaShoppingList = await getItemsOnAlexaShoppingList(); // Remove each item from the alexa shopping list that is not on the bring shopping list. var itemsToRemoveFromAlexaShoppingList = []; for (const itemOnAlexaShoppingList of itemsOnAlexaShoppingList) { if (!await ShoppingListUtils.checkIfShoppingListArrayContainsShoppingListItem(itemsOnBringShoppingList, itemOnAlexaShoppingList)) { itemsToRemoveFromAlexaShoppingList.push(itemOnAlexaShoppingList); } } if (itemsToRemoveFromAlexaShoppingList.length > 0) { console.debug(`Items to remove from "Alexa" shopping list (${itemsToRemoveFromAlexaShoppingList.length}): ${itemsToRemoveFromAlexaShoppingList.map(item => item.value)}`); } for (const itemToRemoveFromAlexaShoppingList of itemsToRemoveFromAlexaShoppingList) { await AlexaShoppingListItem.removeItemFromShoppingList(itemToRemoveFromAlexaShoppingList); } } console.info(`Sync completed!`); syncingInProgress = false; } async function checkForResyncRequests() { if (resyncRequests.length > 0) { // Get the first resync request. var firstResyncRequest = resyncRequests[0]; // Remove the first resync request from the array. resyncRequests.shift(); // Remove all values from the array that match the firstResyncRequest value resyncRequests = resyncRequests.filter(request => request !== firstResyncRequest); // Perform resync await synchronizeShoppingLists(firstResyncRequest, true); } } /*---------- GET SHOPPING LIST ITEMS --------------------------------------------------------------------------------------------*/ // Alexa async function getItemsOnAlexaShoppingList(): Promise <Array<AlexaShoppingListItem>> { var alexaShoppingListItems = []; var completedAlexaShoppingListItemsToRemove = []; var jsonShoppingListString = await getStateAsync(objectID_alexa_shoppingList_folder + '.json'); var jsonShoppingList = JSON.parse(jsonShoppingListString.val); jsonShoppingList.forEach((item, index) => { var item_id = item.id; var item_listID = item.listId; var item_customerID = item.customerId; var item_shoppingListItem = item.shoppingListItem; var item_value = item.value; var item_completed = item.completed; var item_version = item.version; var item_createdDateTime = item.createdDateTime; var item_updatedDateTime = item.updatedDateTime; var item_idOfFolderObject = objectID_alexa_shoppingList_folder + '.items.' + item.id; var item_idOfDeleteDatapoint = objectID_alexa_shoppingList_folder + '.items.' + item.id + '.#delete'; /* console.debug(`item_id: ${item_id}`); console.debug(`item_listID: ${item_listID}`); console.debug(`item_customerID: ${item_customerID}`); console.debug(`item_shoppingListItem: ${item_shoppingListItem}`); console.debug(`item_value: ${item_value}`); console.debug(`item_completed: ${item_completed}`); console.debug(`item_version: ${item_version}`); console.debug(`item_createdDateTime: ${item_createdDateTime}`); console.debug(`item_updatedDateTime: ${item_updatedDateTime}`); console.debug(`item_idOfFolderObject: ${item_idOfFolderObject}`); console.debug(`item_idOfDeleteDatapoint: ${item_idOfDeleteDatapoint}`); */ try { const newAlexaShoppingListItem = new AlexaShoppingListItem( item_id, item_listID, item_customerID, item_shoppingListItem, item_value, item_completed, item_version, item_createdDateTime, item_updatedDateTime, item_idOfFolderObject, item_idOfDeleteDatapoint); if (!newAlexaShoppingListItem.completed) { alexaShoppingListItems.push(newAlexaShoppingListItem); } else { completedAlexaShoppingListItemsToRemove.push(newAlexaShoppingListItem); } } catch (error) { console.error(`Error while creating Alexa Shopping-List Item Object! - Original Error: ${error.message}`); } }); if (completedAlexaShoppingListItemsToRemove.length > 0) { for (const completedAlexaShoppingListItemToRemove of completedAlexaShoppingListItemsToRemove) { await AlexaShoppingListItem.removeItemFromShoppingList(completedAlexaShoppingListItemToRemove); } } // Sort the array of alexa shopping list items (case insensitive sorting) alexaShoppingListItems.sort((a, b) => a.value.toLowerCase().localeCompare(b.value.toLowerCase())); return alexaShoppingListItems; } // Bring async function getItemsOnBringShoppingList(): Promise <Array<BringShoppingListItem>> { var bringShoppingListItems = []; try { var humanReadableShoppingListObject = await getStateAsync(objectID_bring_shoppingList_sentenceString); var humanReadableShoppingList = humanReadableShoppingListObject.val; if (humanReadableShoppingList !== null && humanReadableShoppingList !== '' && humanReadableShoppingList.length > 0) { humanReadableShoppingList = replaceAllOccurrencesOfASubstringWithinAString(humanReadableShoppingList, ' und ', ', '); humanReadableShoppingList = replaceAllOccurrencesOfASubstringWithinAString(humanReadableShoppingList, ', ', ';'); const bringShoppingListItemStrings = splitStringIntoArray(humanReadableShoppingList, ';'); for (const bringShoppingListItemString of bringShoppingListItemStrings) { try { const newBringShoppingListItem = new BringShoppingListItem(bringShoppingListItemString); bringShoppingListItems.push(newBringShoppingListItem); } catch (error) { console.error(`Error while creating Alexa Shopping-List Item Object! - Original Error: ${error.message}`); continue; } } } } catch (error) { console.error(`Error while getting Bring Shopping-List Items! - Original Error: ${error.message}`); } // Sort the array of alexa shopping list items (case insensitive sorting) bringShoppingListItems.sort((a, b) => a.value.toLowerCase().localeCompare(b.value.toLowerCase())); return bringShoppingListItems; } /*++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++++ + CLASSES + *++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++++*/ class AlexaShoppingListItem { public id: string; public listID: string; public customerID: string; public shoppingListItem: boolean; public value: string; public completed: boolean; public version: number; public createdDateTime: number; public updatedDateTime: number; public idOfFolderObject: string; public idOfDeleteDatapoint: string; constructor( id:string, listID:string, customerID:string, shoppingListItem:boolean, value:string, completed:boolean, version:number, createdDateTime:number, updatedDateTime:number, idOfFolderObject:string, idOfDeleteDatapoint:string) { this.id = id; this.listID = listID; this.customerID = customerID; this.shoppingListItem = shoppingListItem; this.value = value; this.completed = completed; this.version = version; this.createdDateTime = createdDateTime; this.updatedDateTime = updatedDateTime; this.idOfFolderObject = idOfFolderObject; this.idOfDeleteDatapoint = idOfDeleteDatapoint; } /******************** * Adds an item to the Alexa shopping list. * * @param item - The item to save to the Alexa shopping list. Must be either an instance of 'AlexaShoppingListItem' or 'BringShoppingListItem'. * @return Promise<boolean> - True if the function completed running its code. ********************/ static async addItemToShoppingList(item:AlexaShoppingListItem | BringShoppingListItem): Promise <boolean> { // Parameter validation if (!(item instanceof AlexaShoppingListItem || item instanceof BringShoppingListItem)) { throw new Error("Invalid parameter: Parameter 'item' must be an instance of 'AlexaShoppingListItem' or 'BringShoppingListItem'!"); } await setStateAsync(objectID_alexa_shoppingList_addItem, capitalizeFirstLetter(item.value), false); return true; } /******************** * Removes an item from the Alexa shopping list. * * @param item - The item to remove from the Alexa shopping list. Must be an instance of 'AlexaShoppingListItem'. * @return Promise<boolean> - True if the function completed running its code. ********************/ static async removeItemFromShoppingList(item:AlexaShoppingListItem): Promise <boolean> { // Parameter validation if (!(item instanceof AlexaShoppingListItem)) { throw new Error("Invalid parameter: Parameter 'item' must be an instance of 'AlexaShoppingListItem'!"); } await setStateAsync(item.idOfDeleteDatapoint, true, false); return true; } } class BringShoppingListItem { public value: string; constructor(value:string) { this.value = value; } /******************** * Adds an item to the Bring shopping list. * * @param item - The item to save to the Bring shopping list. Must be either an instance of 'AlexaShoppingListItem' or 'BringShoppingListItem'. * @return Promise<boolean> - True if the function completed running its code. ********************/ static async addItemToShoppingList(item:AlexaShoppingListItem | BringShoppingListItem): Promise <boolean> { // Parameter validation if (!(item instanceof AlexaShoppingListItem || item instanceof BringShoppingListItem)) { throw new Error("Invalid parameter: Parameter 'item' must be an instance of 'AlexaShoppingListItem' or 'BringShoppingListItem'!"); } await setStateAsync(objectID_bring_shoppingList_addItem, capitalizeFirstLetter(item.value), false); return true; } /******************** * Removes an item from the Bring shopping list. * * @param item - The item to remove from the Bring shopping list. Must be an instance of 'BringShoppingListItem'. * @return Promise<boolean> - True if the function completed running its code. ********************/ static async removeItemFromShoppingList(item:BringShoppingListItem): Promise <boolean> { // Parameter validation if (!(item instanceof BringShoppingListItem)) { throw new Error("Invalid parameter: Parameter 'item' must be an instance of 'BringShoppingListItem'!"); } await setStateAsync(objectID_bring_shoppingList_removeItem, item.value, false); return true; } } class ShoppingListUtils { /******************** * Checks if a given shopping list array contains a specific shopping list item. * Herefore it compares all items in the given shopping list array with the given shopping list item * (either an 'AlexaShoppingListItem' or a 'BringShoppingListItem') * in a case-insensitive way. * * @param shoppingListArray - Array of AlexaShoppingListItem or BringShoppingListItem objects to check within. * @param shoppingListItem - The shopping list item to find in the array. * @returns Promise<boolean> - True if the item is found, otherwise false. ********************/ static async checkIfShoppingListArrayContainsShoppingListItem(shoppingListArray: Array<AlexaShoppingListItem | BringShoppingListItem>, shoppingListItem: AlexaShoppingListItem | BringShoppingListItem): Promise <boolean> { // Parameter validation if (!Array.isArray(shoppingListArray)) { throw new Error("Invalid parameter: 'shoppingListArray' must be an array."); } if (!(shoppingListItem instanceof AlexaShoppingListItem || shoppingListItem instanceof BringShoppingListItem)) { throw new Error("Invalid parameter: 'shoppingListItem' must be an instance of 'AlexaShoppingListItem' or 'BringShoppingListItem'."); } for (const item of shoppingListArray) { if (!(item instanceof AlexaShoppingListItem || item instanceof BringShoppingListItem)) { throw new Error("Invalid parameter: All elements in 'shoppingListArray' must be instances of 'AlexaShoppingListItem' or 'BringShoppingListItem'."); } } // Normalize the value of the shopping list item to lower case for case-insensitive comparison const itemValueToCheck = shoppingListItem.value.toLowerCase(); // Check if any item in the array matches the shopping list item value for (const item of shoppingListArray) { if (item.value.toLowerCase() === itemValueToCheck) { return true; } } return false; } } /*++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++++ *++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++++*/ /*---------- HELPER FUNCTIONS ---------------------------------------------------------------------------------------------------*/ function capitalizeFirstLetter(input: string): string { if (input.length === 0) { return input; // Return the empty string if input is empty } return input.charAt(0).toUpperCase() + input.slice(1); } function replaceAllOccurrencesOfASubstringWithinAString(originalString: string, searchValue: string, replaceValue: string): string { const regex = new RegExp(searchValue, 'g'); return originalString.replace(regex, replaceValue); } function splitStringIntoArray(input: string, delimiter: string): string[] { return input.split(delimiter); } -
@MartyBr sagte in Alexa Shopping List mit Bring synchronisieren:
@elron
Der Link funktioniert leider nicht (zumindest bei mir). Ich habe das Script von @sil4s genommen.Vielen Dank
Okay, komisch, hier nochmal der gesamte Code inkl. der aktuellen Änderung:
/*********************************************************************************************************************************************************************************** * GENERIC INFO: * Created by Daniel Drießen @ DDProductions * * Date of creation: 17.07.2024 * Version: 0.1.0.0 * * DESCRIPTION: * This script synchronizes the shopping lists of 'Alexa' and 'Bring'. * It monitors changes in both shopping lists and ensures that they stay updated with each other. * The script uses events to detect changes in the shopping lists of both 'Alexa' and 'Bring'. * When changes are detected, it initiates a synchronization to reconcile any discrepancies between the lists. * The synchronization process involves comparing the items on both lists and updating each list to reflect the combined items from both sources. * * DEPENDENCIES: * - Alexa2 adapter * - Bring adapter * * LIMITATIONS: * Due to the nature of the 'Bring' adapter (which does not store an 'added' or 'updated' timestamp for items on the shopping list), a 'real' synchronization is not possible * because there is no way to detect the order of changes to the shopping lists of 'Alexa' and 'Bring'. * * TODO & FUTURE GOALS: * - Add better error handling. * - Move functions 'getItemsOnAlexaShoppingList' & 'getItemsOnBringShoppingList' into classes 'AlexaShoppingList' & 'BringShoppingList'. * - Move functions 'addItemToShoppingList', 'removeItemFromShoppingList' into classes 'AlexaShoppingList' & 'BringShoppingList'. * - Enhance the synchronization logic to minimize potential syncing errors and improve reliability given the above limitations. * - Maybe add a sync schedule for periodic syncing. * * CHANGELOG: * 18.07.2024 - Initial version completed. * ***********************************************************************************************************************************************************************************/ export default this; /*++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++++ + SCRIPT SETUP + *++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++++*/ /*---------- OBJECT-ID CONSTANTS ------------------------------------------------------------------------------------------------*/ const objectID_alexa_shoppingList_folder = 'alexa2.0.Lists.SHOP'; const objectID_alexa_shoppingList_addItem = 'alexa2.0.Lists.SHOP.#New'; const objectID_bring_shoppingList_sentenceString = 'bring.0.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.enumSentence'; const objectID_bring_shoppingList_addItem = 'bring.0.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.saveItem'; const objectID_bring_shoppingList_removeItem = 'bring.0.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.removeItem'; /*++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++++ *++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++++*/ /*++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++++ + START OF SCRIPT LOGIC + *++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++++*/ var initComplete = false; var syncingInProgress = false; var resyncRequests = []; startScriptLogic(); async function startScriptLogic() { try { await init(); initComplete = true; } catch (error) { console.error(error.message); console.debug(`SCRIPT WILL TERMINATE NOW!`); await this.stopScriptAsync(); } } /*++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++++ *++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++++*/ /*---------- INIT ---------------------------------------------------------------------------------------------------------------*/ async function init(): Promise <void> { console.info(`Script initialization...`); await synchronizeShoppingLists('init', false); console.info(`Script initialization completed successfully!`); await checkForResyncRequests(); } /*---------- TRIGGER ------------------------------------------------------------------------------------------------------------*/ // Alexa on({ id: [].concat([objectID_alexa_shoppingList_folder + '.json']), change: 'ne' }, async (obj) => { let value = obj.state.val; let oldValue = obj.oldState.val; console.debug(`'Alexa' shopping list changed!`); if (initComplete && !syncingInProgress) { await synchronizeShoppingLists('alexa', false); await checkForResyncRequests(); } else { resyncRequests.push('alexa'); } }); // Bring on({ id: [].concat([objectID_bring_shoppingList_sentenceString]), change: 'ne' }, async (obj) => { let value = obj.state.val; let oldValue = obj.oldState.val; console.debug(`'Bring' shopping list changed!`); if (initComplete && !syncingInProgress) { await synchronizeShoppingLists('bring', false); await checkForResyncRequests(); } else { resyncRequests.push('bring'); } }); /*---------- SYNC ---------------------------------------------------------------------------------------------------------------*/ async function synchronizeShoppingLists(syncInitiator:string, isResync:boolean) { console.info(`Sync started`); console.debug(`Sync initiator: '${capitalizeFirstLetter(syncInitiator)}'`); console.debug(`Sync is a resync: '${isResync}'`); syncingInProgress = true; var itemsOnAlexaShoppingList = await getItemsOnAlexaShoppingList(); var itemsOnBringShoppingList = await getItemsOnBringShoppingList(); console.debug(`Items on "Alexa" shopping list (${itemsOnAlexaShoppingList.length}): ${itemsOnAlexaShoppingList.map(item => item.value)}`); console.debug(`Items on "Bring" shopping list (${itemsOnBringShoppingList.length}): ${itemsOnBringShoppingList.map(item => item.value)}`); if (syncInitiator.toLowerCase() === 'init') { // If sync initiator is "init" // create a combined item list and add each item missing on each list to each list. // Create combined shopping list var itemsOnCombinedShoppingList = []; for (const itemOnAlexaShoppingList of itemsOnAlexaShoppingList) { if (!await ShoppingListUtils.checkIfShoppingListArrayContainsShoppingListItem(itemsOnCombinedShoppingList, itemOnAlexaShoppingList)) { itemsOnCombinedShoppingList.push(itemOnAlexaShoppingList); } } for (const itemOnBringShoppingList of itemsOnBringShoppingList) { if (!await ShoppingListUtils.checkIfShoppingListArrayContainsShoppingListItem(itemsOnCombinedShoppingList, itemOnBringShoppingList)) { itemsOnCombinedShoppingList.push(itemOnBringShoppingList); } } //console.debug(`Items on "Combined" shopping list: ${itemsOnCombinedShoppingList.map(item => item.value)}`); // Add each missing item on each list to each list var itemsToAddToAlexaShoppingList = []; var itemsToAddToBringShoppingList = []; for (const itemOnCombinedShoppingList of itemsOnCombinedShoppingList) { if (!await ShoppingListUtils.checkIfShoppingListArrayContainsShoppingListItem(itemsOnAlexaShoppingList, itemOnCombinedShoppingList)) { itemsToAddToAlexaShoppingList.push(itemOnCombinedShoppingList); } if (!await ShoppingListUtils.checkIfShoppingListArrayContainsShoppingListItem(itemsOnBringShoppingList, itemOnCombinedShoppingList)) { itemsToAddToBringShoppingList.push(itemOnCombinedShoppingList); } } if (itemsToAddToAlexaShoppingList.length > 0) { console.debug(`Items to add to "Alexa" shopping list (${itemsToAddToAlexaShoppingList.length}): ${itemsToAddToAlexaShoppingList.map(item => item.value)}`); } if (itemsToAddToBringShoppingList.length > 0) { console.debug(`Items to add to "Bring" shopping list (${itemsToAddToBringShoppingList.length}): ${itemsToAddToBringShoppingList.map(item => item.value)}`); } for (const itemToAddToAlexaShoppingList of itemsToAddToAlexaShoppingList) { await AlexaShoppingListItem.addItemToShoppingList(itemToAddToAlexaShoppingList); } for (const itemToAddToBringShoppingList of itemsToAddToBringShoppingList) { await BringShoppingListItem.addItemToShoppingList(itemToAddToBringShoppingList); } } else if (syncInitiator.toLowerCase() === 'alexa') { // If sync initiator is "alexa" // add each item from the alexa shopping list that is missing on the bring shopping list to the bring shopping list. // Then remove each item from the bring shopping list that is not on the alexa shopping list. // Add each item from the alexa shopping list that is missing on the bring shopping list to the bring shopping list. var itemsToAddToBringShoppingList = []; for (const itemOnAlexaShoppingList of itemsOnAlexaShoppingList) { if (!await ShoppingListUtils.checkIfShoppingListArrayContainsShoppingListItem(itemsOnBringShoppingList, itemOnAlexaShoppingList)) { itemsToAddToBringShoppingList.push(itemOnAlexaShoppingList); } } if (itemsToAddToBringShoppingList.length > 0) { console.debug(`Items to add to "Bring" shopping list (${itemsToAddToBringShoppingList.length}): ${itemsToAddToBringShoppingList.map(item => item.value)}`); } for (const itemToAddToBringShoppingList of itemsToAddToBringShoppingList) { await BringShoppingListItem.addItemToShoppingList(itemToAddToBringShoppingList); } // Get an update of the bring shopping list itemsOnBringShoppingList = await getItemsOnBringShoppingList(); // Remove each item from the bring shopping list that is not on the alexa shopping list. var itemsToRemoveFromBringShoppingList = []; for (const itemOnBringShoppingList of itemsOnBringShoppingList) { if (!await ShoppingListUtils.checkIfShoppingListArrayContainsShoppingListItem(itemsOnAlexaShoppingList, itemOnBringShoppingList)) { itemsToRemoveFromBringShoppingList.push(itemOnBringShoppingList); } } if (itemsToRemoveFromBringShoppingList.length > 0) { console.debug(`Items to remove from "Bring" shopping list (${itemsToRemoveFromBringShoppingList.length}): ${itemsToRemoveFromBringShoppingList.map(item => item.value)}`); } for (const itemToRemoveFromBringShoppingList of itemsToRemoveFromBringShoppingList) { await BringShoppingListItem.removeItemFromShoppingList(itemToRemoveFromBringShoppingList); } } else if (syncInitiator.toLowerCase() === 'bring') { // If sync initiator is "bring" // add each item from the bring shopping list that is missing on the alexa shopping list to the alexa shopping list. // Then remove each item from the alexa shopping list that is not on the bring shopping list. // Add each item from the bring shopping list that is missing on the alexa shopping list to the alexa shopping list. var itemsToAddToAlexaShoppingList = []; for (const itemOnBringShoppingList of itemsOnBringShoppingList) { if (!await ShoppingListUtils.checkIfShoppingListArrayContainsShoppingListItem(itemsOnAlexaShoppingList, itemOnBringShoppingList)) { itemsToAddToAlexaShoppingList.push(itemOnBringShoppingList); } } if (itemsToAddToAlexaShoppingList.length > 0) { console.debug(`Items to add to "Alexa" shopping list (${itemsToAddToAlexaShoppingList.length}): ${itemsToAddToAlexaShoppingList.map(item => item.value)}`); } for (const itemToAddToAlexaShoppingList of itemsToAddToAlexaShoppingList) { await AlexaShoppingListItem.addItemToShoppingList(itemToAddToAlexaShoppingList); } // Get an update of the alexa shopping list itemsOnAlexaShoppingList = await getItemsOnAlexaShoppingList(); // Remove each item from the alexa shopping list that is not on the bring shopping list. var itemsToRemoveFromAlexaShoppingList = []; for (const itemOnAlexaShoppingList of itemsOnAlexaShoppingList) { if (!await ShoppingListUtils.checkIfShoppingListArrayContainsShoppingListItem(itemsOnBringShoppingList, itemOnAlexaShoppingList)) { itemsToRemoveFromAlexaShoppingList.push(itemOnAlexaShoppingList); } } if (itemsToRemoveFromAlexaShoppingList.length > 0) { console.debug(`Items to remove from "Alexa" shopping list (${itemsToRemoveFromAlexaShoppingList.length}): ${itemsToRemoveFromAlexaShoppingList.map(item => item.value)}`); } for (const itemToRemoveFromAlexaShoppingList of itemsToRemoveFromAlexaShoppingList) { await AlexaShoppingListItem.removeItemFromShoppingList(itemToRemoveFromAlexaShoppingList); } } console.info(`Sync completed!`); syncingInProgress = false; } async function checkForResyncRequests() { if (resyncRequests.length > 0) { // Get the first resync request. var firstResyncRequest = resyncRequests[0]; // Remove the first resync request from the array. resyncRequests.shift(); // Remove all values from the array that match the firstResyncRequest value resyncRequests = resyncRequests.filter(request => request !== firstResyncRequest); // Perform resync await synchronizeShoppingLists(firstResyncRequest, true); } } /*---------- GET SHOPPING LIST ITEMS --------------------------------------------------------------------------------------------*/ // Alexa async function getItemsOnAlexaShoppingList(): Promise <Array<AlexaShoppingListItem>> { var alexaShoppingListItems = []; var completedAlexaShoppingListItemsToRemove = []; var jsonShoppingListString = await getStateAsync(objectID_alexa_shoppingList_folder + '.json'); var jsonShoppingList = JSON.parse(jsonShoppingListString.val); jsonShoppingList.forEach((item, index) => { var item_id = item.id; var item_listID = item.listId; var item_customerID = item.customerId; var item_shoppingListItem = item.shoppingListItem; var item_value = item.value; var item_completed = item.completed; var item_version = item.version; var item_createdDateTime = item.createdDateTime; var item_updatedDateTime = item.updatedDateTime; var item_idOfFolderObject = objectID_alexa_shoppingList_folder + '.items.' + item.id; var item_idOfDeleteDatapoint = objectID_alexa_shoppingList_folder + '.items.' + item.id + '.#delete'; /* console.debug(`item_id: ${item_id}`); console.debug(`item_listID: ${item_listID}`); console.debug(`item_customerID: ${item_customerID}`); console.debug(`item_shoppingListItem: ${item_shoppingListItem}`); console.debug(`item_value: ${item_value}`); console.debug(`item_completed: ${item_completed}`); console.debug(`item_version: ${item_version}`); console.debug(`item_createdDateTime: ${item_createdDateTime}`); console.debug(`item_updatedDateTime: ${item_updatedDateTime}`); console.debug(`item_idOfFolderObject: ${item_idOfFolderObject}`); console.debug(`item_idOfDeleteDatapoint: ${item_idOfDeleteDatapoint}`); */ try { const newAlexaShoppingListItem = new AlexaShoppingListItem( item_id, item_listID, item_customerID, item_shoppingListItem, item_value, item_completed, item_version, item_createdDateTime, item_updatedDateTime, item_idOfFolderObject, item_idOfDeleteDatapoint); if (!newAlexaShoppingListItem.completed) { alexaShoppingListItems.push(newAlexaShoppingListItem); } else { completedAlexaShoppingListItemsToRemove.push(newAlexaShoppingListItem); } } catch (error) { console.error(`Error while creating Alexa Shopping-List Item Object! - Original Error: ${error.message}`); } }); if (completedAlexaShoppingListItemsToRemove.length > 0) { for (const completedAlexaShoppingListItemToRemove of completedAlexaShoppingListItemsToRemove) { await AlexaShoppingListItem.removeItemFromShoppingList(completedAlexaShoppingListItemToRemove); } } // Sort the array of alexa shopping list items (case insensitive sorting) alexaShoppingListItems.sort((a, b) => a.value.toLowerCase().localeCompare(b.value.toLowerCase())); return alexaShoppingListItems; } // Bring async function getItemsOnBringShoppingList(): Promise <Array<BringShoppingListItem>> { var bringShoppingListItems = []; try { var humanReadableShoppingListObject = await getStateAsync(objectID_bring_shoppingList_sentenceString); var humanReadableShoppingList = humanReadableShoppingListObject.val; if (humanReadableShoppingList !== null && humanReadableShoppingList !== '' && humanReadableShoppingList.length > 0) { humanReadableShoppingList = replaceAllOccurrencesOfASubstringWithinAString(humanReadableShoppingList, ' und ', ', '); humanReadableShoppingList = replaceAllOccurrencesOfASubstringWithinAString(humanReadableShoppingList, ', ', ';'); const bringShoppingListItemStrings = splitStringIntoArray(humanReadableShoppingList, ';'); for (const bringShoppingListItemString of bringShoppingListItemStrings) { try { const newBringShoppingListItem = new BringShoppingListItem(bringShoppingListItemString); bringShoppingListItems.push(newBringShoppingListItem); } catch (error) { console.error(`Error while creating Alexa Shopping-List Item Object! - Original Error: ${error.message}`); continue; } } } } catch (error) { console.error(`Error while getting Bring Shopping-List Items! - Original Error: ${error.message}`); } // Sort the array of alexa shopping list items (case insensitive sorting) bringShoppingListItems.sort((a, b) => a.value.toLowerCase().localeCompare(b.value.toLowerCase())); return bringShoppingListItems; } /*++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++++ + CLASSES + *++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++++*/ class AlexaShoppingListItem { public id: string; public listID: string; public customerID: string; public shoppingListItem: boolean; public value: string; public completed: boolean; public version: number; public createdDateTime: number; public updatedDateTime: number; public idOfFolderObject: string; public idOfDeleteDatapoint: string; constructor( id:string, listID:string, customerID:string, shoppingListItem:boolean, value:string, completed:boolean, version:number, createdDateTime:number, updatedDateTime:number, idOfFolderObject:string, idOfDeleteDatapoint:string) { this.id = id; this.listID = listID; this.customerID = customerID; this.shoppingListItem = shoppingListItem; this.value = value; this.completed = completed; this.version = version; this.createdDateTime = createdDateTime; this.updatedDateTime = updatedDateTime; this.idOfFolderObject = idOfFolderObject; this.idOfDeleteDatapoint = idOfDeleteDatapoint; } /******************** * Adds an item to the Alexa shopping list. * * @param item - The item to save to the Alexa shopping list. Must be either an instance of 'AlexaShoppingListItem' or 'BringShoppingListItem'. * @return Promise<boolean> - True if the function completed running its code. ********************/ static async addItemToShoppingList(item:AlexaShoppingListItem | BringShoppingListItem): Promise <boolean> { // Parameter validation if (!(item instanceof AlexaShoppingListItem || item instanceof BringShoppingListItem)) { throw new Error("Invalid parameter: Parameter 'item' must be an instance of 'AlexaShoppingListItem' or 'BringShoppingListItem'!"); } await setStateAsync(objectID_alexa_shoppingList_addItem, capitalizeFirstLetter(item.value), false); return true; } /******************** * Removes an item from the Alexa shopping list. * * @param item - The item to remove from the Alexa shopping list. Must be an instance of 'AlexaShoppingListItem'. * @return Promise<boolean> - True if the function completed running its code. ********************/ static async removeItemFromShoppingList(item:AlexaShoppingListItem): Promise <boolean> { // Parameter validation if (!(item instanceof AlexaShoppingListItem)) { throw new Error("Invalid parameter: Parameter 'item' must be an instance of 'AlexaShoppingListItem'!"); } await setStateAsync(item.idOfDeleteDatapoint, true, false); return true; } } class BringShoppingListItem { public value: string; constructor(value:string) { this.value = value; } /******************** * Adds an item to the Bring shopping list. * * @param item - The item to save to the Bring shopping list. Must be either an instance of 'AlexaShoppingListItem' or 'BringShoppingListItem'. * @return Promise<boolean> - True if the function completed running its code. ********************/ static async addItemToShoppingList(item:AlexaShoppingListItem | BringShoppingListItem): Promise <boolean> { // Parameter validation if (!(item instanceof AlexaShoppingListItem || item instanceof BringShoppingListItem)) { throw new Error("Invalid parameter: Parameter 'item' must be an instance of 'AlexaShoppingListItem' or 'BringShoppingListItem'!"); } await setStateAsync(objectID_bring_shoppingList_addItem, capitalizeFirstLetter(item.value), false); return true; } /******************** * Removes an item from the Bring shopping list. * * @param item - The item to remove from the Bring shopping list. Must be an instance of 'BringShoppingListItem'. * @return Promise<boolean> - True if the function completed running its code. ********************/ static async removeItemFromShoppingList(item:BringShoppingListItem): Promise <boolean> { // Parameter validation if (!(item instanceof BringShoppingListItem)) { throw new Error("Invalid parameter: Parameter 'item' must be an instance of 'BringShoppingListItem'!"); } await setStateAsync(objectID_bring_shoppingList_removeItem, item.value, false); return true; } } class ShoppingListUtils { /******************** * Checks if a given shopping list array contains a specific shopping list item. * Herefore it compares all items in the given shopping list array with the given shopping list item * (either an 'AlexaShoppingListItem' or a 'BringShoppingListItem') * in a case-insensitive way. * * @param shoppingListArray - Array of AlexaShoppingListItem or BringShoppingListItem objects to check within. * @param shoppingListItem - The shopping list item to find in the array. * @returns Promise<boolean> - True if the item is found, otherwise false. ********************/ static async checkIfShoppingListArrayContainsShoppingListItem(shoppingListArray: Array<AlexaShoppingListItem | BringShoppingListItem>, shoppingListItem: AlexaShoppingListItem | BringShoppingListItem): Promise <boolean> { // Parameter validation if (!Array.isArray(shoppingListArray)) { throw new Error("Invalid parameter: 'shoppingListArray' must be an array."); } if (!(shoppingListItem instanceof AlexaShoppingListItem || shoppingListItem instanceof BringShoppingListItem)) { throw new Error("Invalid parameter: 'shoppingListItem' must be an instance of 'AlexaShoppingListItem' or 'BringShoppingListItem'."); } for (const item of shoppingListArray) { if (!(item instanceof AlexaShoppingListItem || item instanceof BringShoppingListItem)) { throw new Error("Invalid parameter: All elements in 'shoppingListArray' must be instances of 'AlexaShoppingListItem' or 'BringShoppingListItem'."); } } // Normalize the value of the shopping list item to lower case for case-insensitive comparison const itemValueToCheck = shoppingListItem.value.toLowerCase(); // Check if any item in the array matches the shopping list item value for (const item of shoppingListArray) { if (item.value.toLowerCase() === itemValueToCheck) { return true; } } return false; } } /*++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++++ *++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++*++++++++++++++++++++++++++++++++++++++++++++++++++++*/ /*---------- HELPER FUNCTIONS ---------------------------------------------------------------------------------------------------*/ function capitalizeFirstLetter(input: string): string { if (input.length === 0) { return input; // Return the empty string if input is empty } return input.charAt(0).toUpperCase() + input.slice(1); } function replaceAllOccurrencesOfASubstringWithinAString(originalString: string, searchValue: string, replaceValue: string): string { const regex = new RegExp(searchValue, 'g'); return originalString.replace(regex, replaceValue); } function splitStringIntoArray(input: string, delimiter: string): string[] { return input.split(delimiter); } -
Ich habe die beiden Datenpunkte angepasst. Leider erhalte ich folgende Fehlermeldung:
javascript.0 16:24:56.439 error compile failed at: script.js.Alexas.Bring_Synchronisieren2:36 javascript.0 16:24:56.439 error export default this; javascript.0 16:24:56.439 error ^^^^^^ javascript.0 16:24:56.439 error SyntaxError: Unexpected token 'export' javascript.0 16:24:56.439 error at new Script (node:vm:117:7) javascript.0 16:24:56.439 error at JavaScript.createVM (/opt/iobroker/node_modules/iobroker.javascript/src/main.ts:2174:25) javascript.0 16:24:56.439 error at JavaScript.prepareScript (/opt/iobroker/node_modules/iobroker.javascript/src/main.ts:2472:44) javascript.0 16:24:56.439 error at processTicksAndRejections (node:internal/process/task_queues:105:5) javascript.0 16:24:56.439 error at JavaScript.onObjectChange (/opt/iobroker/node_modules/iobroker.javascript/src/main.ts:659:25)Hat das Problem noch jemand? Oder weiß jemand, was ich machen kann?
-
Ich habe die beiden Datenpunkte angepasst. Leider erhalte ich folgende Fehlermeldung:
javascript.0 16:24:56.439 error compile failed at: script.js.Alexas.Bring_Synchronisieren2:36 javascript.0 16:24:56.439 error export default this; javascript.0 16:24:56.439 error ^^^^^^ javascript.0 16:24:56.439 error SyntaxError: Unexpected token 'export' javascript.0 16:24:56.439 error at new Script (node:vm:117:7) javascript.0 16:24:56.439 error at JavaScript.createVM (/opt/iobroker/node_modules/iobroker.javascript/src/main.ts:2174:25) javascript.0 16:24:56.439 error at JavaScript.prepareScript (/opt/iobroker/node_modules/iobroker.javascript/src/main.ts:2472:44) javascript.0 16:24:56.439 error at processTicksAndRejections (node:internal/process/task_queues:105:5) javascript.0 16:24:56.439 error at JavaScript.onObjectChange (/opt/iobroker/node_modules/iobroker.javascript/src/main.ts:659:25)Hat das Problem noch jemand? Oder weiß jemand, was ich machen kann?