Google App Script to sync gmail inbox with sheets - google-apps-script

I have a script pulling out data from a gmail account. The script scans the inbox for mails and finds the relevant lines of text and puts it in a Google sheet.
The email looks something like:
Vehicle: 5761364, Position: (URL to Google Maps)
The script i use to get the data to sheets is:
function processInboxToSheet() {
var start = 0;
var threads = GmailApp.getInboxThreads(start, 100);
var sheet = SpreadsheetApp.getActiveSheet();
var result = [];
for (var i = 0; i < threads.length; i++) {
var messages = threads[i].getMessages();
var content = messages[0].getPlainBody();
if (content) {
var tmp;
tmp = content.match(/Vehicle:\s*([A-Za-z0-9\s]+)(\r?\n)/);
var username = (tmp && tmp[1]) ? tmp[1].trim() : 'No vehicle';
tmp = content.match(/Map Link:\s*([A-Za-z ][A-Za-z0-9!##$%?=^.,:&*/ ]+)/);
var comment = (tmp && tmp[1]) ? tmp[1] : 'No url';
sheet.appendRow([username, comment]);
Utilities.sleep(500);
}
}
};
Would it be possible to make a kind of synchronization function, where the Google sheet gets updated automatically with the emails in the inbox. Right now it makes duplicates every time it runs.
Also, could someone tell me if it is possible to get the script to delete the lines created if the email is deleted. So the sheet list always is in sync with the inbox?
Please ask me if it does not make sense.

To solve the issue of duplicate emails, you need to check whether a message is unread or not. If it is unread, then read it and make it as read afterwards. I have made video series on YouTube to explain how this is possible and published the full code on github. You may watch it here:
https://youtu.be/nI1OH3pAz6s?t=9
You can also get the full code from GitHub from the following link:
GitHub: Get Gmail Emails into Google Sheet
GitHub: Extract Body Contents from Gmail Emails

You could run your script from a timebased trigger and if you just rewrote the entire sheet each time then that would take care of eliminating the entries from deleted emails. If you could add the received date to the spreadsheet then you could order them by date.

Related

Protect sheets after time

I have a spreadsheet with multiple sheets inside. What I want to achieve is for the editors to not be able to edit the sheets after a certain date.
That I can do with creating a script lock function for a sheet but what about the other sheets? Do I create a lock script for each individual sheet? Then how do I program them to run. Basically, I want for 1st script which locks the sheet1 to run today for example, then the next script which locks the sheet2 to run tomorrow same time, the 3rd script which locks sheet3 to run day after tomorrow and so on.
How do I do that, if that's even possible. Or maybe there's an easier way.
Thanks,
You can use the simple trigger onOpen(), this will run this script every-time a user opens the file:
function onOpen() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheets = ss.getSheets(); //Getting all the sheets from the file.
const lockDates = ss.getSheetByName("LockDates").getDataRange().getValues(); //Getting list of sheets and their lockdates.
const now = new Date(); //Getting today's date.
for (i=0; i < sheets.length; i++){
var currentSheet = sheets[i];
var sheetIndex = (lockDates.flat().indexOf(currentSheet.getName())/2); //This is to get the index where the current sheet name is located.
if(sheetIndex >= 0){ //If the sheet is not on the list we get -1.
var sheetLockDate = lockDates[sheetIndex][1]; //Assiging the lockDate to a variable.
if (now >= sheetLockDate && sheetLockDate > 0){ //Evaluating if today's date is on or after the lockDate.
currentSheet.protect();
console.log('Sheet -' + currentSheet.getName() + '- was protected');
break;
}
else { //The sheet is unprotected if it's still not time to protect it.
currentSheet.protect().remove();
}
}
}
}
Note the following:
This script will determine the lock dates based on a table at "LockDates" sheet, the code might break if you add additional columns.
If the sheet is not included in the list it will not be affected.
If the sheet is included in the list but doesn't have a lockDate it will be unprotected. This will let you modify the lockdate of specific sheets if needed.
You could protect the control sheet "LockDates" and it will not be affected by the script while it is not added to the list.
This is the setup where the code worked:
I think there are 2 ways we can achieve that result:
You can share the file as always but set an access expiration date, you will share access to a file but the access will expire after a specified date https://support.google.com/a/users/answer/9308784.
You can create an Apps Script project, give it a time-driven trigger so a certain function is executed after some period. This function in question should read a list somewhere (perhaps a form or sheet) and remove the access permissions.
#Bryan approach is very similar to mine. Here is my solution:
The code works with a Form with this structure (change the order by modifying the code under the reviewPermissions() function):
And using the Script Editor in the form add the following code:
let deletionSwitch;
function readResponses() {
var responses = FormApp.getActiveForm().getResponses();
responses.forEach(function (response) {
deletionSwitch = false;
reviewPermissions(response);
if (deletionSwitch)
FormApp.getActiveForm().deleteResponse(response.getId());
});
}
function reviewPermissions(response) {
var fileId = response.getItemResponses()[0].getResponse();
var email = response.getItemResponses()[1].getResponse();
var date = response.getItemResponses()[2].getResponse();
var nextPageToken;
if (Date.now() > new Date(date))
do {
var response = getPermissions(fileId, nextPageToken);
var permissions = response.items;
permissions.forEach(function (permission) {
if (permission.emailAddress.toLowerCase() == email.toLowerCase()) {
deletionSwitch = true;
deletePermission(fileId,permission);
}
});
} while (nextPageToken = response.nextPageToken)
}
function getPermissions(fileId, token = null) {
return permissions = Drive.Permissions.list(fileId, {
fields: "nextPageToken,items(id,emailAddress,role)",
pageToken: token
});
}
function deletePermission(fileId,permission){
if (permission.role != "owner")
Drive.Permissions.remove(fileId,permission.id);
}
This code needs Google Drive to be added as an Advanced Google service, add it with the name "Drive". Information about Advanced services is available in this documentation https://developers.google.com/apps-script/guides/services/advanced.
Necessary triggers:
Form onSubmit, execute the readResponses() function.
Time-driven (clock), execute the readResponses() function at the interval you prefer, I recommend every day.
Short code explanation:
The trigger will read all Form entries.
If there is a response that has an older date than today (expired) the code will check all the permissions of the file and will delete all permissions assigned to that email address address in the entry (not case sensitive).
Note:
Entries will be removed once their date expires.
Entries with dates in the future are ignored and checked in future runs.
Permission deletion is retroactive so submitting an entry with a date in the past will cause the permission to be deleted immediately (if exists).
The owner permission can't be removed, the deletion won't be attempted and the entry removed.
This code only works with files you own or have permission editor access to, you can request other people to copy the form with the script and use it with their own files.
Linking the Form responses to a Google Sheet file will allow you to have a historical record of what permissions should expire, this is not necessary for the code to work, just convenient for record purposes. Requesting the email address in the Form should not affect functionality.

Please look at the below code I wrote to make an appointment schedule in Google Apps Script

I am working on an appointment schedule on Google Sheets. My sheet is connected to a Google form so I can get form responses in my project. My vision for this project is that people select a time slot from the form and that time slot changes to "Booked" in the weekly schedule instead of Dr A or Dr B etc. Please see the lower part of the image for reference. I am doing this through coding in Google Apps Script. But the code is not working. It says execution completed but nothing happens on my sheet. Please look at the code below and help me. Thanks!
function booking() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var cusRec = ss.getSheetByName("Customer Record");
var appSheet = ss.getSheetByName("Appointment sheet for psychologists");
var cusDate = cusRec.getRange(2,7).getValue();
var cusTime = cusRec.getRange(2,8).getValue();
var cusPsy = cusRec.getRange(2,9).getValue();
var appDate = appSheet.getRange(3,2).getValue();
var appTime = appSheet.getRange(5,1).getValue();
var appPsy = appSheet.getRange(5,2).getValue();
if (cusDate == appDate && cusTime == appTime && cusPsy == appPsy)
{
appSheet.getRange("B5").setValue("Booked");
}
}
[This is a picture of the two sheets I am working with][1]
[1]: https://i.stack.imgur.com/xCRid.jpg
I suggest you run some loggers to check with part of your data is not matching. Most like it's an issue with formatting or empty spaces, etc.
Try adding this
console.log(cusDate, appDate, cusDate == appDate)
console.log(cusTime, appTime, cusTime == appTime)
console.log(cusPsy, appPsy, cusPsy == appPsy)
And see what the output is.

Recurring generic error message Google Apps Script

EDIT: Not sure if this matters but I am using G Suite for Nonprofits.END EDIT:::
I keep getting this error message upon attempting to manually run this script for the first time:
We're sorry, a server error occurred. Please wait a bit and try again. (line 2, file "Code")).
I have saved it and authorized its permissions already. I am simply trying to execute the script to get all of the existing responses' edit URLs.
There are no triggers associated with this. The script is intended to get the edit response URLs from a Google Form. "Edit after submit" is enabled in the Form, and it is set to public in regards to who can complete the form (not that this should matter since I am the owner of the form).
I use this script in this exact form on many other projects with no issues at all. I have also tried a different script for this task that I know works, with the same error message returned. The error message always references the line of code with the form ID/URL. I have verified the form ID. Thanks for your help.
function assignEditUrls() {
var form = FormApp.openById('some id');
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Form Responses 1');
var data = sheet.getDataRange().getValues();
var urlCol = 6;
var responses = form.getResponses();
var timestamps = [], urls = [], resultUrls = [];
for (var i = 0; i < responses.length; i++) {
timestamps.push(responses[i].getTimestamp().setMilliseconds(0));
urls.push(responses[i].getEditResponseUrl());
}
for (var j = 1; j < data.length; j++) {
resultUrls.push([data[j][0] ?
urls[timestamps.indexOf(data[j][0].setMilliseconds(0))] : ''
]);
}
sheet.getRange(2, urlCol, resultUrls.length).setValues(resultUrls);
}
Thanks to Rubén, I was able to solve my problem. In my script I was using the incorrect Form ID. When running a script to obtain the edit response URLs from a Google Form you must view the edit form URL in the editing mode of your specific form. Extract the Form ID from this edit URL NOT the view URL or share URL etc. This is the only way your script will access the correct form with the correct permissions. Thanks again to Rubén and all others who assisted.

How to resolve "Too much calls for this service today : gmail" error in Google Apps script?

My question is about an error in Google's spreadsheet using gmail service in a function.
Since a while, an error occur when I run a function (On Google Spreadsheet) for retrieve mails on a label founded in the MailBox (Gmail).
The error message is : "Too much calls for this service today : gmail".
I want to specify that function worked fine before and it hasn't been modified.
The function is launched one time per month (Except in exceptional case)
I did some research on the error message, and the answers found confirmed what I thought,
daily quotas for Google's gmail services are exceed and can not be used until 24 hours.
However, it's the only one that has not worked, while others are working properly with these services without any errors.
Following this, I created a copy of the spreadsheet with the function to test if it isn't the sheet that does not work, but it has not changed.
And I launched it with another Google account, and it worked.
Does anyone know why this message appears please ?
Should we do a special manipulation to make it work again ?
Here is the row that sends an error :
var threads = GmailApp.getUserLabelByName("Label").getThreads();
And the function :
function readMail(){
var threads = GmailApp.getUserLabelByName("Label").getThreads();
var messages = GmailApp.getMessagesForThreads(threads);
for(var i in messages){
var message = messages[i];
for(var j in message){
var mess = message[j];
var sub = mess.getSubject();
if(mess.getTo().indexOf("email#gmail.com") > -1)
continue;
var attach = mess.getAttachments()[0];
var file = {
title: attach.getName()
};
var fileDoc = Drive.Files.insert(file, attach, {convert: false}); // Use Drive API
mess.markRead();
}
}
}

Google Script check a column if contains a certain word

I am new to Google Scripting.
I created a script where Google Spreadsheet should connect to GMail. Everything is working well.
My problem is, I need to check if an email content has the word "ORDER". If yes, it should get the word "ORDER" and the numbers after it.
For example, an email contains "ORDER 90104" in the message body, then the Google script should insert ORDER 90104 to a column in the sheet. An example email could be:
Hi, the customer is requesting for ORDER 90104. Thanks.
Is this even possible? As of now, I used getPlainBody() and I can get all the contents of the email and put it in a column. My plan was to check that column and look for the word ORDER. Though I can't find/end with a solution for it.
Here's my working code getting the email content of the 1st three emails:
function getGMail() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var threads = GmailApp.getInboxThreads(0,3);
cell = sheet.getRange("A1");
for (var i = 0; i < threads.length; i++) {
threadMessage = threads[i].getMessages();
var emailContent = threadMessage[0].getPlainBody();
cell.offset(i,0).setValue(emailContent);
}
}
Any advice or comments will be greatly appreciated.
Thanks!
Hello theladydevbugger,
As you didn't gave a lot of code my answer will be quite short: yes you can do that!
How: use a regexp: /ORDER [0-9]+/g.
How to use the regexp: bellow a sample code
var emailBody = yourMail.getPlainBody(); // supposing "yourMail" is the mail you retrieved
var isThereAnyOrder = false;
if(mailBody.search(/ORDER [0-9]+/g)>-1){
isThereAnyOrder=true;
var order=mailBody.match(/ORDER [0-9]+/g)>-1)[0];
Logger.log(order);
// do something with the order
}
else{
// there is no order do nothing
}