NEWS
Alexa Shopping List mit Bring synchronisieren
-
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?
-
@martin_olw Ich hab es jetzt doch getestet und noch was geändert:
Hier die Beschreibung:
Der Skript überwacht den Datenpunkt alexa2.0.Lists.SHOP.json.
Sobald Alexa z. B. sagt:
„Alexa, setze vier Äpfel auf die Einkaufsliste“
wird das als neuer Eintrag in dieser Liste erkannt.Zahlwort-Umwandlung:
Aus „vier Äpfel“ wird „4 Äpfel“.
Auch zusammengesetzte Zahlen wie „fünfzehn“ oder „dreiundzwanzig“ werden korrekt erkannt.
Das erste Wort nach der Zahl wird großgeschrieben: z. B. „4 Äpfel“ statt „4 äpfel“.
Todoist-Eintrag:
Der bereinigte und umgewandelte Eintrag wird direkt über die REST-API an Todoist gesendet und dort im definierten Projekt als Aufgabe eingetragen.
Automatisches Entfernen in Alexa:
Nach 60 Sekunden wird der passende Alexa-Listeneintrag automatisch als „abgehakt“ markiert.
Es muss nur die ListID und der Token eingesetzt werden.
const axios = require('axios'); // Todoist API-Konfiguration const todoistProjectId = 'XXXXXXXX'; // Deine Projekt-ID const todoistToken = 'XXXXXXXXXXXXXXXXXXXXXXX'; // Dein Token let previousList = []; // 1. Alexa-Änderungen überwachen on({ id: 'alexa2.0.Lists.SHOP.json', change: 'any' }, function (obj) { try { const currentList = JSON.parse(obj.state.val); if (currentList && currentList.length > 0) { if (previousList.length < currentList.length) { const newItem = currentList.find(item => !previousList.some(prevItem => prevItem.id === item.id) ); if (newItem) { const umgewandelt = wordsToNumbersSmart(newItem.value); const aufgabe = capitalizeFirst(umgewandelt); addTaskToTodoist(aufgabe); // Nach 60 Sekunden Alexa-Eintrag auf "completed" setzen setTimeout(() => { const alexaList = JSON.parse(getState('alexa2.0.Lists.SHOP.json').val); const matchingItem = alexaList.find(item => { const itemText = wordsToNumbersSmart(item.value).trim().toLowerCase(); return itemText === aufgabe.trim().toLowerCase(); }); if (matchingItem) { const completeState = `alexa2.0.Lists.SHOP.items.${matchingItem.id}.completed`; setState(completeState, true); } else { console.warn(`⚠️ Kein passender Alexa-Eintrag zu "${aufgabe}" gefunden.`); } }, 60 * 1000); // 60 Sekunden } } previousList = currentList; } } catch (e) { console.error('Fehler beim Parsen der Alexa-Liste:', e.message || e); } }); // 2. Aufgaben an Todoist senden function addTaskToTodoist(itemValue) { const todoistData = { content: itemValue, project_id: todoistProjectId }; axios.post('https://api.todoist.com/rest/v2/tasks', todoistData, { headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${todoistToken}` } }) .then(() => { console.log(`✅ "${itemValue}" zu Todoist hinzugefügt.`); }) .catch(error => { console.error('Fehler beim Hinzufügen zu Todoist:', error.message || error); }); } // 3. Erstes Wort groß function capitalizeFirst(text) { if (!text || typeof text !== 'string') return ''; return text.charAt(0).toUpperCase() + text.slice(1); } // 4. Wörter → Zahlen (z. B. „vierzehn“ → 14) function wordsToNumbersSmart(text) { const ones = { 'null': 0, 'eins': 1, 'eine': 1, 'einen': 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, '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 = text.toLowerCase().split(/\s+/); const finalText = []; let i = 0; let capitalizeNext = 0; while (i < words.length) { const word = words[i]; if (ones[word] !== undefined) { if (i + 2 < words.length && words[i + 1] === 'und' && tens[words[i + 2]]) { const value = ones[word] + tens[words[i + 2]]; finalText.push(value.toString()); capitalizeNext = 2; i += 3; continue; } if (i + 1 < words.length && multipliers[words[i + 1]]) { const value = ones[word] * multipliers[words[i + 1]]; finalText.push(value.toString()); capitalizeNext = 2; i += 2; continue; } finalText.push(ones[word].toString()); capitalizeNext = 2; i++; } else if (tens[word] !== undefined) { finalText.push(tens[word].toString()); capitalizeNext = 2; i++; } else if (!isNaN(word)) { finalText.push(word); capitalizeNext = 2; i++; } else { if (capitalizeNext > 0 && !skipWords.includes(word)) { finalText.push(word.charAt(0).toUpperCase() + word.slice(1)); capitalizeNext--; } else { finalText.push(word); } i++; } } return finalText.join(' '); }Hallo @Heimweh und vielen Dank noch einmal für das Script und die Anpassungen. Lief bis vor kurzem alles ohne Probleme bei mir. Jetzt aber bekomme ich immer folgende Fehlermeldung:
script.js.common.ToDoist.Einkaufsliste_Alexa_Todoist: Fehler beim Hinzufügen zu Todoist:Mehr nicht. Woran kann das liegen?
Prinzipiell funktioniert dein Skript aber:Updating item "bananentest" ({"id":"ad0e6a37-8dc2-4085-83d6-b282773292ac","listId":"YW16bjEuYWNjb3VudC5BRTVPSlgyRFk0UUhEVUJUNEJOWUpPUEJVSjNBLVNIT1BQSU5HX0lURU0=","value":"bananentest","customerId":"A2FTPWL63RKNMN","completed":true,"createdDateTime":1770990517898,"updatedDateTime":1770990517898,"version":1,"note":null,"index":0,"#delete":false,"listName":"SHOP"}) of the list SHOP.Funktioniert das bei dir noch alles?
Danke für die ernuete Hilfe!
VG Martin -
Todoist hat die API angepasst, v2 gibt es nicht mehr.
https://developer.todoist.com/api/v1/#tag/TasksDu musst die API-URL auf https://api.todoist.com/api/v1/tasks ändern und die Projekt-IDs aktualisieren.
-
Todoist hat die API angepasst, v2 gibt es nicht mehr.
https://developer.todoist.com/api/v1/#tag/TasksDu musst die API-URL auf https://api.todoist.com/api/v1/tasks ändern und die Projekt-IDs aktualisieren.
@no6mis Ich danke dir! Genau das hat geholfen!