GmailThread.getlables() fails randomly with the exception "Exception: Gmail operation not allowed." - google-apps-script

This might be a weird one but I have some code that goes through my email and uses labels to trigger certain tasks. For example, if someone applies via Indeed, Indeed sends me an email. A filter will see this this email and apply the "Applications/indeedApplication" label. Then, a script that runs once per minute will find this label, and based on the label, perform some actions automatically.
This works perfectly about 99.97% of the time. The script runs with no issues and does the tasks, applies a completed label, and everything works as intended. 0.03% of the time, the task will fail, always at the same point, and always with the same exception:
Exception: Gmail operation not allowed.
It fails when the scrip tries to run the getLables() function on a GmmailThread (class of GmailApp API).
Code in question:
function addICandidate() {
var label = GmailApp.getUserLabelByName("Applications/indeedApplication"); //Defines the label that identifies indeed applicatoins
var requests = label.getThreads(); //Returns an array of threads which have this label
if(requests.length > 3){requests.length = 3}; //This makes it so the script doesn't waste time looking through every application ever.
for ( var i in requests ) { //Starting a normal for loop
iLabels = requests[i].getLabels(); //Gets the labels of the Randomly fails here and only here
var checker = false; //used later to check if tasks were already completed
for (var k = 0 in iLabels) { //Starting another normal for loop
var individualLabel = iLabels[k].getName(); //Gets a list of the labels
if (individualLabel === completedLabelName) { //Checks if the label for completes tasks is present
var checker = true; //If they are, set checker to true (also used later)
}
}
if(checker){ //If the checker above is set to true
Logger.log("true"); //Do nothing (logger is just used in debugging)
}
else{ //Else (if the label is not completed and therefore, the tasks have not been done yet)
var thread = requests[i].Dostuff(); //Confidential stuff lives here
}
requests[i].addLabel(completedlabel); //After everything, apply the completed label so tasks are not done again on the next pass
}
}
}
Does anyone know why it would fail here and only 1 in 3000 attempts. I obviously cannot debug this with brute force so any ideas as to why this might happen would be really helpful.

Related

Add label to an Gmail thread with only 1 message (the original message)

When I send a quotation to a client, I want to write a script that will add a label to the email, easy. The catch is, I only want to add the label one time to the thread and then never again. I have another script that will take all the emails with that label and add a calendar reminder and will remove the label so it won't keep adding an event. The problem I am having is that when the client replies to that first email, the label is then reapplied, which causes yet another calendar event to be made. Is there any way to only add a label to a thread with a message count of 1? So in the case of the image below, the label should only apply to the second thread and not the first.
Two threads, one with a single email, and one with 5
function QuoteReminder() {
var reminderLabel = "STS Quotes", //Substitute your label here
calendarName = "Quote Follow-ups", ////Substitute your Calendar name here
label = GmailApp.getUserLabelByName(reminderLabel),
threads = label.getThreads();
if (threads.length > 0) {
for (i in threads) {
if (i.getMessageCount() == 1) {
//get calendar by name
var cals = CalendarApp.getCalendarsByName(calendarName);
//This is run the next day around 9am, calendar invite will be created for 13 days form then to account for the next day
var now = new Date().getTime();
var MILLIS_PER_DAY = 1000 * 60 * 60 * 24;
for (i in threads) {
cals[0].createEvent(reminderLabel + '- '+ i.getFirstMessageSubject(),
new Date(now+(MILLIS_PER_DAY*13)),
new Date(now+(MILLIS_PER_DAY*13)+900000), {description: i.getPermalink()});
}
//Remove the label from the mails to avoid duplicate event creation on next run
label.removeFromThreads(threads);
}
}
}
}
Yes, there is.
I cannot provide you a copy-paste solution since I have not seen your code and do not even know which programming language your are using, but
there is the method Users.threads: get which returns you all the
messages of a thread.
So, you can incorporate a simple if statement to verify either the length of the message array is bigger than 1 and run the rest of you code accordingly.
UPDATE:
For retrieving the number of messages in a thread in Apps Script, you can use the method getMessageCount()
This message needs to be applied to a single thread
The method label.getThreads() returns you an array of threads
To access single elements of the thread array you need to address them with their position in the array, e.g. threads[0], threads[1] or - in a loop threads[i]
In your case:
you need to modify if (i.getMessageCount() == 1) {... to if (threads[i].getMessageCount() == 1) {....
Otherwise, the if statement just verifies either he counter variable i is equal to 1 - which will always be the case in one of the iterations if threads.length > 0

Inconsistent error message ''Action not allowed (line 298, file "<functionName>")"

Context
Using g-suite, I have a google form that users submit information to a google sheet. Code aligned to the google sheet takes the submitted information and outputs a google doc/pdf. This final doc/pdf is created by copying a master doc template, merging/appending sections from other template docs, and finally search and replacing a number of text-merge fields in the final doc. The part of the code that is failing is a section that is responsible from merging/appending sections from other template docs as referenced in the sample code.
The code is run from a 'trigger'. The trigger is configured as:
Select event source : From spreadsheet
Select event type: on form submit
Issue
Without making any code changes, recently the code has stopped working when run from the trigger, error-ing out with the:
Action not allowed (line 298, file "")
The actual line of code at 298 is:
'TargetDocBody.appendParagraph(element);'
I can run the function in question successfully in the GAS script editor by selecting Run -> Run Function -> . The function does NOT run when configured as a trigger. Normally I would chalk this up to a permission issue, however even I, the script owner/writer, cannot get the function to run when I am the submitter of the form. It does run successfully as mentioned above within the GAS editor.
I've given full write permissions to all template dates, directories where the files are ultimately saved, write permission to the google form, write permission to the google sheets etc etc and I still can't seem to get around 'Action not allowed' when run from a trigger. I've logged the active and effective users on failures and successes and they're the same output both times.
[19-04-24 09:52:28:837 EDT] Active user : tim_monaco#company.com
[19-04-24 09:52:28:839 EDT] EffectiveUser : tim_monaco#company.com
function MergeSections(ServiceTypeDoc,TargetDocBody)
{
var ServiceTypeDocNumElements = ServiceTypeDoc.getNumChildren();
for(var x =0; x < ServiceTypeDocNumElements; ++x)
{
var element = ServiceTypeDoc.getChild(x).copy();
var type = element.getType();
if(type == DocumentApp.ElementType.PARAGRAPH) //this is the problem line
TargetDocBody.appendParagraph(element);
if(type == DocumentApp.ElementType.LIST_ITEM)
TargetDocBody.appendListItem(element);
if(type == DocumentApp.ElementType.FOOTER_SECTION)
TargetDocBody.appendFooterSection(element);
}
}
Function should/has run without issues in the past. With no code changes, I would expect the same.
Actual results are a failure and stop mid execution with
Action not allowed (line 298, file "")
The actual line of code at 298 is:
'TargetDocBody.appendParagraph(element);'
This could be a situation where the error message is inaccurate. It often happens that quota limit errors don't happen initially, and then mysteriously kick in. Also, because it happens inconsistently, that would also point to a quota limit error. This could be a situation where your for loop is calling a service too often in a short period of time, and hitting a quota limit. I could be wrong, but I'd try putting in a try / catch and if there is an error, sleep the code and try again.
If the following code that sleeps when there is an error, doesn't work at all, then I'd consider that it might be a bug, and submit an issue.
function MergeSections(ServiceTypeDoc,TargetDocBody)
{
var ServiceTypeDocNumElements = ServiceTypeDoc.getNumChildren();
for(var x =0; x < ServiceTypeDocNumElements; ++x)
{
var element = ServiceTypeDoc.getChild(x).copy();
var type = element.getType();
for (var i2 = 0;i2<3;i2++) {//Try 3 times
try{
switch(true) {
case type == DocumentApp.ElementType.PARAGRAPH:
TargetDocBody.appendParagraph(element);
break;
case type == DocumentApp.ElementType.LIST_ITEM:
TargetDocBody.appendListItem(element);
break;
case type == DocumentApp.ElementType.FOOTER_SECTION:
TargetDocBody.appendFooterSection(element);
break;
}
break;//If there is no error then break the loop
}catch(e) {
Utilities.sleep(1500);//Wait x number of seconds
}
}
}
}

GmailApp Won't Unstar Gmail Message [duplicate]

OK, I think web Gmail is being screwy. I run a Google apps script that in part adds my "To-do" label to any thread I manually star, then archives and unstars it. A snippet is below. I'd appreciate any help.
After running the script the thread gets the label and is unstarred, except the star icon next to the thread/message in web Gmail still shows as selected. If I go to Starred messages label/folder nothing is displayed. If I immediately rerun the script it doesn't find any Starred threads. This seems to indicate the script is working OK. The problem seems to be that web Gmail still wants to show it as Starred even though it isn't. Gmail Android app doesn't show the star applied to the thread. Pictures are worth more...
What my inbox looks like after the script runs. Note the star:
Yet no Starred messages:
function addStarred2ToDo() {
var threads = GmailApp.search('is:starred');
for (var h in threads) {
var messages = threads[h].getMessages();
for (var i in messages) {
if (messages[i].isStarred()) {
messages[i].unstar();
}
}
}
}
EDIT:
I also tried this and neither produce what is expected.
function thisOne() {
var threads = GmailApp.search('is:starred');
for (var h in threads) {
var messages = threads[h].getMessages();
for (var i in messages) {
if (messages[i].isStarred()) {
messages[i].unstar().refresh();
}
}
}
}
function andThisOne() {
var threads = GmailApp.search('is:starred');
var toUnstar = [];
threads.forEach(function (thread) {
thread.getMessages().forEach(function (message) {
if (message.isStarred()) {
toUnstar.push(message);
}
});
});
GmailApp.unstarMessages(toUnstar);
}
If you refresh gmail and hover over the star, you will see that the popup says it is Not Stared. Also, this seems to be an issue when you select the star from gmail, as stars that are set by my filters work correctly when my script unstars them.
This display issue is caused because you do not force Gmail to update the message with a call to refresh() after your call to unstar().
Per documentation of GmailMessage#refresh():
Reloads this message and associated state from Gmail (useful in case the labels, read state, etc., have changed).
messages[i].unstar().refresh();
Should be sufficient to inform Gmail of the new starred status.
Alternately, a batch call to modify the messages will be more efficient in terms of quota usage:
var toUnstar = [];
threads.forEach(function (thread) {
thread.getMessages().forEach(function (message) {
if (message.isStarred()) {
toUnstar.push(message);
...
}
});
});
GmailApp.unstarMessages(toUnstar);
In my sample I avoid the assumption that iterating an array is safe with for .. in.. and use the more expressive Array.forEach() to indicate that the code is something that we want to apply to every thread and every message in said thread.
Documentation on GmailApp.unstarMessages():
Removes stars from these messages and forces the messages to refresh.
I'm having a similar problem. I have enabled a superstar, the green-check. I manually set them.
My script finds the relevant threads using "l:^ss_cg" in a search. It finds the starred email, sends a copy of the email somewhere, and then does the unstar.
Afterward, in the web gmail, if i search for the same message again, it shows up with a green star visually, but if I hover over the star icon, it shows 'not starred'.
However, if I run the script again, the thread is found using the search. It doesn't send another copy of the email, however, because I have a check for ".isStarred()" before it sends a copy of the specific email. I have also been able to reduce the number of threads it double-checks by adding a .hasStarredEmails() check to the thread before it starts looking at individual emails.
Doing a search in the web interface for has:green-check reveals only the emails it should.
There is something about .unStar() that doesn't work properly, I think.
I had considered trying to remove the star at the thread level by removing the ^ss_cg label, but that won't work because there is no way to get a GMailLabel object to send to the function.
Still having same issue where the Gmail web app shows the star. But just made interesting finding.
Assuming
var threads = GmailApp.getStarredThreads()
var thread = threads[0]
var message = thread.getMessages()[0]
GmailApp.unstarMessage(message) would immediately give the following results:
var isMessageStarred = message.isStarred() true
var isThreadStarred = thread.hasStarredMessages() true
GmailApp.unstarMessage(message).refreshMessage(message) would immediately give the following results:
var isMessageStarred = message.isStarred() false
var isThreadStarred = thread.hasStarredMessages() true
And GmailApp.unstarMessage(message).refreshMessage(message).refreshThread(thread) would immediately give the following results:
var isMessageStarred = message.isStarred() false
var isThreadStarred = thread.hasStarredMessages() false
I had this case. Actually what was happening was I was starring everything that hit the inbox with a filter. Once my script processed the message I unstarred it. But my script was also forwarding the message and that new sent message was being starred by the filter. So the thread still contained a starred message. Argh.
My solution was to create another filter to delete any messages from me.
Same problem here.
I am unable to unstar() emails which I starred manually in my browser/on my phone.
If I star an email via a rule or a script and then unstar it with a gapps script, unstarring works.
I also tried doing refresh right after unstarring - no difference:
message.unstar()
message.refresh()
I wrote a script which stars always the last message of a previously starred thread to keep my starred threads with the most recent replies at the top of my mailbox. The only problem is that it's not really working if I star an email manually at the beginning and not with a rule or another script.
The code going through my threads:
for(var z=0;z<messages.length;z++){
var message = messages[z]
var messageTime = message.getDate()
var messageTimeInCET = Utilities.formatDate(messageTime,'CET','yyyy-MM-dd HH:mm:ss')
//Logger.log('messageTime: '+ messageTime)
//Let's add star to the last email in the thread and remove stars from all previous emails
if(threadLastMessageDateInCET==messageTimeInCET){
Logger.log('Starred email from: '+ messageTimeInCET)
message.star()
}
else{
message.unstar()
message.refresh()
Logger.log('Unstarred email from: '+messageTimeInCET)
}
}

Web Gmail shows message as starred even after Apps Script unstars it

OK, I think web Gmail is being screwy. I run a Google apps script that in part adds my "To-do" label to any thread I manually star, then archives and unstars it. A snippet is below. I'd appreciate any help.
After running the script the thread gets the label and is unstarred, except the star icon next to the thread/message in web Gmail still shows as selected. If I go to Starred messages label/folder nothing is displayed. If I immediately rerun the script it doesn't find any Starred threads. This seems to indicate the script is working OK. The problem seems to be that web Gmail still wants to show it as Starred even though it isn't. Gmail Android app doesn't show the star applied to the thread. Pictures are worth more...
What my inbox looks like after the script runs. Note the star:
Yet no Starred messages:
function addStarred2ToDo() {
var threads = GmailApp.search('is:starred');
for (var h in threads) {
var messages = threads[h].getMessages();
for (var i in messages) {
if (messages[i].isStarred()) {
messages[i].unstar();
}
}
}
}
EDIT:
I also tried this and neither produce what is expected.
function thisOne() {
var threads = GmailApp.search('is:starred');
for (var h in threads) {
var messages = threads[h].getMessages();
for (var i in messages) {
if (messages[i].isStarred()) {
messages[i].unstar().refresh();
}
}
}
}
function andThisOne() {
var threads = GmailApp.search('is:starred');
var toUnstar = [];
threads.forEach(function (thread) {
thread.getMessages().forEach(function (message) {
if (message.isStarred()) {
toUnstar.push(message);
}
});
});
GmailApp.unstarMessages(toUnstar);
}
If you refresh gmail and hover over the star, you will see that the popup says it is Not Stared. Also, this seems to be an issue when you select the star from gmail, as stars that are set by my filters work correctly when my script unstars them.
This display issue is caused because you do not force Gmail to update the message with a call to refresh() after your call to unstar().
Per documentation of GmailMessage#refresh():
Reloads this message and associated state from Gmail (useful in case the labels, read state, etc., have changed).
messages[i].unstar().refresh();
Should be sufficient to inform Gmail of the new starred status.
Alternately, a batch call to modify the messages will be more efficient in terms of quota usage:
var toUnstar = [];
threads.forEach(function (thread) {
thread.getMessages().forEach(function (message) {
if (message.isStarred()) {
toUnstar.push(message);
...
}
});
});
GmailApp.unstarMessages(toUnstar);
In my sample I avoid the assumption that iterating an array is safe with for .. in.. and use the more expressive Array.forEach() to indicate that the code is something that we want to apply to every thread and every message in said thread.
Documentation on GmailApp.unstarMessages():
Removes stars from these messages and forces the messages to refresh.
I'm having a similar problem. I have enabled a superstar, the green-check. I manually set them.
My script finds the relevant threads using "l:^ss_cg" in a search. It finds the starred email, sends a copy of the email somewhere, and then does the unstar.
Afterward, in the web gmail, if i search for the same message again, it shows up with a green star visually, but if I hover over the star icon, it shows 'not starred'.
However, if I run the script again, the thread is found using the search. It doesn't send another copy of the email, however, because I have a check for ".isStarred()" before it sends a copy of the specific email. I have also been able to reduce the number of threads it double-checks by adding a .hasStarredEmails() check to the thread before it starts looking at individual emails.
Doing a search in the web interface for has:green-check reveals only the emails it should.
There is something about .unStar() that doesn't work properly, I think.
I had considered trying to remove the star at the thread level by removing the ^ss_cg label, but that won't work because there is no way to get a GMailLabel object to send to the function.
Still having same issue where the Gmail web app shows the star. But just made interesting finding.
Assuming
var threads = GmailApp.getStarredThreads()
var thread = threads[0]
var message = thread.getMessages()[0]
GmailApp.unstarMessage(message) would immediately give the following results:
var isMessageStarred = message.isStarred() true
var isThreadStarred = thread.hasStarredMessages() true
GmailApp.unstarMessage(message).refreshMessage(message) would immediately give the following results:
var isMessageStarred = message.isStarred() false
var isThreadStarred = thread.hasStarredMessages() true
And GmailApp.unstarMessage(message).refreshMessage(message).refreshThread(thread) would immediately give the following results:
var isMessageStarred = message.isStarred() false
var isThreadStarred = thread.hasStarredMessages() false
I had this case. Actually what was happening was I was starring everything that hit the inbox with a filter. Once my script processed the message I unstarred it. But my script was also forwarding the message and that new sent message was being starred by the filter. So the thread still contained a starred message. Argh.
My solution was to create another filter to delete any messages from me.
Same problem here.
I am unable to unstar() emails which I starred manually in my browser/on my phone.
If I star an email via a rule or a script and then unstar it with a gapps script, unstarring works.
I also tried doing refresh right after unstarring - no difference:
message.unstar()
message.refresh()
I wrote a script which stars always the last message of a previously starred thread to keep my starred threads with the most recent replies at the top of my mailbox. The only problem is that it's not really working if I star an email manually at the beginning and not with a rule or another script.
The code going through my threads:
for(var z=0;z<messages.length;z++){
var message = messages[z]
var messageTime = message.getDate()
var messageTimeInCET = Utilities.formatDate(messageTime,'CET','yyyy-MM-dd HH:mm:ss')
//Logger.log('messageTime: '+ messageTime)
//Let's add star to the last email in the thread and remove stars from all previous emails
if(threadLastMessageDateInCET==messageTimeInCET){
Logger.log('Starred email from: '+ messageTimeInCET)
message.star()
}
else{
message.unstar()
message.refresh()
Logger.log('Unstarred email from: '+messageTimeInCET)
}
}

Google Spreadsheets: Script to check for completion of ImportHTML

I am trying to scrape data of a website once day automatically. In Google Spreadsheets, i use the =ImportHTML() function to import data tables, and then I extract the relevant data with a =query(). These functions take between 10 and 30 seconds to complete calculation, every time I open the spreadsheet.
I use a scheduled Google Apps Script, to copy the data into a different sheet (where it is stored, so i can run statistics) every day.
My problem is that I am having trouble to make the script wait for the calculations to be finished, before the data is copied. The Result is that my script just copies the error Message "N/A".
I tried just adding a Utilities.sleep(60000);, but it didn't work.
Is it possible to create a loop, that checks for the calculation to finish? I tried this without success:
function checkForError() {
var spreadsheet = SpreadsheetApp.getActive();
var source = spreadsheet.getRange ("Today!A1");
if (source = "N/A") {
Utilities.sleep(6000);
checkForError();
} else {
moveValuesOnly();
}
}
Locks are for this. Look up lock services in the docs. Use a public lock.
Here's how I used Zig's suggestion (combined with my own check loop) to solve my similar problem:
// Get lock for public shared resourse
var lock = LockService.getPublicLock();
// Wait for up to 120 seconds for other processes to finish.
lock.waitLock(120000);
// Load my values below
// something like sheet.getRange("A1").setFormula('= etc...
// Now force script to wait until cell D55 set to false (0) before
// performing copy / pastes
var current = SpreadsheetApp.setActiveSheet(sheet.getSheets()[1]);
var ready = 1;
var count = 0;
while (true) {
// break out of function if D55 value has changed to zero or counter
// has hit 250
if (count >= 250) break;
// otherwise keep counting...
ready = current.getRange("D55").getValue();
if (ready == 0) {count = 400;}
Utilities.sleep(100);
++count;
}
// wait for spreadsheet to finish... sigh...
Utilities.sleep(200);
// Do my copy and pastes stuff here
// for example sheet.getRange("a1:b1").copyTo(sheet.getRange("a3"), {contentsOnly:true});
// Key cells are updated so release the lock so that other processes can continue.
lock.releaseLock();
// end script
return;
}
This has worked fantastic for me, stopped Google's sporadic service from ruining my work!
Thanks goes to Zig's suggestion!