Scriptable icon indicating copy to clipboard operation
Scriptable copied to clipboard

Alexa to Reminders Access.js quits with an error

Open andereeicheln0z opened this issue 1 year ago • 6 comments

Unfortunately, the script quits with an error. Do you have any tips on what could be wrong?

IMG_3842

Log says:

2024-05-18 13:59:23: index of "Sign in"
is -1
2024-05-18 13:59:23: logged in
2024-05-18 13:59:23: /private/var/mobile/
¡ Library/Mobile Documents/
iCloud~dk~simonbs~Scriptable/Documents/ lastCreatedDateTime.txt
2024-05-18 13:59:23: lastDate is 0
2024-05-18 13:59:26: Error: Die Daten konnten nicht gelesen werden, da sie nicht das korrekte Format haben.

andereeicheln0z avatar May 18 '24 12:05 andereeicheln0z

Try to find out what's going wrong. I found out that loadJSON does not work because no JSON is sent back. "Authentication Failure" is the return value when replacing loadJSON by loadString.

ghost avatar May 18 '24 13:05 ghost

Have you logged into Amazon with your account via Scriptable? Sorry for my long delay. I've been away from GitHub for some time unfortunately

mvan231 avatar Jun 13 '24 14:06 mvan231

The problem was that the shopping list did not work for the German Amazon site. I revised your script to ensure no duplicate entries, case sensitivity, synchronized entries resolved, no duplicate entries, and optimized performance. The script now looks like this:

// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: blue; icon-glyph: magic;

async function checkIfUserIsAuthenticated() {
  try {
    const url = 'https://www.amazon.de/alexashoppinglists/api/getlistitems';
    const request = new Request(url);
    await request.load();

    if (request.response.statusCode === 401 || request.response.statusCode === 403) {
      return false;
    }

    return true;
  } catch (error) {
    console.error(error);
    return false;
  }
}

async function makeLogin() {
  const url = 'https://www.amazon.de';
  const webView = new WebView();
  
  try {
    await webView.loadURL(url);
    const html = await webView.getHTML();
    
    if (html.includes("Anmelden")) {
      await webView.present(false);
      return false;
    } 
    
    return true;
  } catch (error) {
    console.error(error);
    return false;
  }
}

async function synchronizeReminders() {
  try {
    const reminderCalendar = await Calendar.forRemindersByTitle("Einkaufsliste");

    const url = 'https://www.amazon.de/alexashoppinglists/api/getlistitems';
    const deleteUrl = "https://www.amazon.de/alexashoppinglists/api/deletelistitem";
    const json = await new Request(url).loadJSON();
    const listItems = json[Object.keys(json)[0]].listItems;
    const existingReminders = await Reminder.all([reminderCalendar]);

    for (const item of listItems) {
      const reminderTitle = item.value.split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
      const reminderExists = existingReminders.some(reminder => reminder.title === reminderTitle);

      if (!reminderExists) {
        const reminder = new Reminder();
        reminder.title = reminderTitle;
        reminder.calendar = reminderCalendar;
        await reminder.save();

      }

      const request = new Request(deleteUrl);
      request.method = "DELETE";
      request.headers = {
        "Content-Type": "application/json"
      };
      request.body = JSON.stringify(item);
      
      try {
        const response = await request.loadString();
      } catch (deleteError) {
        console.error(deleteError);
      }
    }
  } catch (error) {
    console.error(error);
  }
}


async function main() {
  const isAuthenticated = await checkIfUserIsAuthenticated();
  log(isAuthenticated);
  
  if (!isAuthenticated) {
    const loggedIn = await makeLogin();
    log(loggedIn);
    if (!loggedIn) {
      console.log('Login failed. Exiting.');
      return;
    }
  }

  await synchronizeReminders();
}

main();
Script.complete();

ghost avatar Jun 13 '24 19:06 ghost

The problem was that the shopping list did not work for the German Amazon site. I revised your script to ensure no duplicate entries, case sensitivity, synchronized entries resolved, no duplicate entries, and optimized performance. The script now looks like this:

// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: blue; icon-glyph: magic;

async function checkIfUserIsAuthenticated() {
  try {
    const url = 'https://www.amazon.de/alexashoppinglists/api/getlistitems';
    const request = new Request(url);
    await request.load();

    if (request.response.statusCode === 401 || request.response.statusCode === 403) {
      return false;
    }

    return true;
  } catch (error) {
    console.error(error);
    return false;
  }
}

async function makeLogin() {
  const url = 'https://www.amazon.de';
  const webView = new WebView();
  
  try {
    await webView.loadURL(url);
    const html = await webView.getHTML();
    
    if (html.includes("Anmelden")) {
      await webView.present(false);
      return false;
    } 
    
    return true;
  } catch (error) {
    console.error(error);
    return false;
  }
}

async function synchronizeReminders() {
  try {
    const reminderCalendar = await Calendar.forRemindersByTitle("Einkaufsliste");

    const url = 'https://www.amazon.de/alexashoppinglists/api/getlistitems';
    const deleteUrl = "https://www.amazon.de/alexashoppinglists/api/deletelistitem";
    const json = await new Request(url).loadJSON();
    const listItems = json[Object.keys(json)[0]].listItems;
    const existingReminders = await Reminder.all([reminderCalendar]);

    for (const item of listItems) {
      const reminderTitle = item.value.split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
      const reminderExists = existingReminders.some(reminder => reminder.title === reminderTitle);

      if (!reminderExists) {
        const reminder = new Reminder();
        reminder.title = reminderTitle;
        reminder.calendar = reminderCalendar;
        await reminder.save();

      }

      const request = new Request(deleteUrl);
      request.method = "DELETE";
      request.headers = {
        "Content-Type": "application/json"
      };
      request.body = JSON.stringify(item);
      
      try {
        const response = await request.loadString();
      } catch (deleteError) {
        console.error(deleteError);
      }
    }
  } catch (error) {
    console.error(error);
  }
}


async function main() {
  const isAuthenticated = await checkIfUserIsAuthenticated();
  log(isAuthenticated);
  
  if (!isAuthenticated) {
    const loggedIn = await makeLogin();
    log(loggedIn);
    if (!loggedIn) {
      console.log('Login failed. Exiting.');
      return;
    }
  }

  await synchronizeReminders();
}

main();
Script.complete();

Sorry for just seeing this now. Is this still working for you as of today? I like the method you've implemented to remove the synced items instead of just filtering them out on future runs.

Is the "Anmelden" part the same check I was doing before with looking for "Sign In"?

I assume so. I made some updates:

//set baseURL based on your home country url
const baseURL = 'https://www.amazon.com'
const reminderListName = 'Grocery and Shopping'
//signInKey should be specific for your language. English uses "Sign In". German uses "Anmelden"
const signInKey = "Sign In"

main();
Script.complete();

async function checkIfUserIsAuthenticated() {
  try {
    const url = `${baseURL}/alexashoppinglists/api/getlistitems`;
    const request = new Request(url);
    await request.load();

    if (request.response.statusCode === 401 || request.response.statusCode === 403) {
      return false;
    }

    return true;
  } catch (error) {
    console.error(error);
    return false;
  }
}

async function makeLogin() {
  const url = `${baseURL}`;
  const webView = new WebView();
  
  try {
    await webView.loadURL(url);
    const html = await webView.getHTML();
    
    if (html.includes(signInKey)) {
      await webView.present(false);
      return false;
    } 
    
    return true;
  } catch (error) {
    console.error(error);
    return false;
  }
}

async function synchronizeReminders() {
  try {
    const reminderCalendar = await Calendar.forRemindersByTitle(reminderListName);

    const url = `${baseURL}/alexashoppinglists/api/getlistitems`;
    const deleteUrl = `${baseURL}/alexashoppinglists/api/deletelistitem`;
    const json = await new Request(url).loadJSON();
    const listItems = json[Object.keys(json)[0]].listItems;
    const existingReminders = await Reminder.all([reminderCalendar]);

    for (const item of listItems) {
      const reminderTitle = item.value.split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
      const reminderExists = existingReminders.some(reminder => reminder.title === reminderTitle);

      if (!reminderExists) {
        const reminder = new Reminder();
        reminder.title = reminderTitle;
        reminder.calendar = reminderCalendar;
        await reminder.save();

      }

      const request = new Request(deleteUrl);
      request.method = "DELETE";
      request.headers = {
        "Content-Type": "application/json"
      };
      request.body = JSON.stringify(item);
      
      try {
        const response = await request.loadString();
      } catch (deleteError) {
        console.error(deleteError);
      }
    }
  } catch (error) {
    console.error(error);
  }
}


async function main() {
  const isAuthenticated = await checkIfUserIsAuthenticated();
  log(`authenticated? ${isAuthenticated}`);
  
  if (!isAuthenticated) {
    const loggedIn = await makeLogin();
    log(`loggedIn? ${loggedIn}`);
    if (!loggedIn) {
      console.log('Login failed. Exiting.');
      return;
    }
  }

  await synchronizeReminders();
}``` 

mvan231 avatar Aug 11 '24 08:08 mvan231

Yes, it works as expected. And yes, “Anmelden” is equivalent to “Sign In.” In the meantime, I’ve made two adjustments:

  1. The first letter of each item is now capitalized (except for the word “ohne”). In English, this would result in “Banana with Sugar.”
  2. If there are two “Banana” entries on the Alexa list, only one Banana is synced to the Apple list, but both entries on the Alexa list are deleted.
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: blue; icon-glyph: magic;

async function checkIfUserIsAuthenticated() {
  try {
    const url = 'https://www.amazon.de/alexashoppinglists/api/getlistitems';
    const request = new Request(url);
    await request.load();

    if (request.response.statusCode === 401 || request.response.statusCode === 403) {
      return false;
    }

    return true;
  } catch (error) {
    console.error(error);
    return false;
  }
}

async function makeLogin() {
  const url = 'https://www.amazon.de';
  const webView = new WebView();
  
  try {
    await webView.loadURL(url);
    const html = await webView.getHTML();
    
    if (html.includes("Anmelden")) {
      await webView.present(false);
      return false;
    } 
    
    return true;
  } catch (error) {
    console.error(error);
    return false;
  }
}

async function synchronizeReminders() {
  try {
    const reminderCalendar = await Calendar.forRemindersByTitle("Einkaufsliste");

    const url = 'https://www.amazon.de/alexashoppinglists/api/getlistitems';
    const deleteUrl = "https://www.amazon.de/alexashoppinglists/api/deletelistitem";
    const json = await new Request(url).loadJSON();
    const listItems = json[Object.keys(json)[0]].listItems;


    for (const item of listItems) {
      const reminderTitle = item.value.split(' ').map(word => {
        if (word.toLowerCase() === "mit" || word.toLowerCase() === "ohne") {
          return word.toLowerCase();
        } else {
          return word.charAt(0).toUpperCase() + word.slice(1);
        }
      }).join(' ');
      const allReminders = await Reminder.all([reminderCalendar]);
      const incompleteReminders = allReminders.filter(reminder => !reminder.isCompleted);
      const reminderExists = incompleteReminders.some(reminder => reminder.title === reminderTitle);

      if (!reminderExists) {
        const reminder = new Reminder();
        reminder.title = reminderTitle;
        reminder.calendar = reminderCalendar;
        await reminder.save();

      }

      const request = new Request(deleteUrl);
      request.method = "DELETE";
      request.headers = {
        "Content-Type": "application/json"
      };
      request.body = JSON.stringify(item);
      
      try {
        const response = await request.loadString();
      } catch (deleteError) {
        console.error(deleteError);
      }
    }
  } catch (error) {
    console.error(error);
  }
}


async function main() {
  const isAuthenticated = await checkIfUserIsAuthenticated();
  log(isAuthenticated);
  
  if (!isAuthenticated) {
    const loggedIn = await makeLogin();
    log(loggedIn);
    if (!loggedIn) {
      console.log('Login failed. Exiting.');
      return;
    }
  }

  await synchronizeReminders();
}

main();
Script.complete();

ghost avatar Aug 12 '24 19:08 ghost

I've update the script on the repo now to include variables for the items you implemented. Thanks again for your help with this

mvan231 avatar Aug 13 '24 07:08 mvan231