I have developed a Google Apps script in a Google Drive spreadsheet that processes e-mails with a certain label (download notifications) and adds these to the spreadsheet. I'm running this through the script editor of the spreadsheet.
My initial solution was quite inefficient - the whole analysis was repeated each time on all e-mails, which caused the runtime to increase for each day. A few days ago I got an error message "runtime exceeded", which is not strange.
My problem is that when trying to update the script to be more efficient, I get some weird problems. The script either stops randomly after processing a few e-mails, or simply refuses to start. Especially the script debugger, it begins to load but never starts.
I have tried several times over the last few days, and even created a new spreadsheet with the same code (in the same account), but still having these problems.
Sometimes when the script manages to run for a while, I have noted that the script output log does not match recent changes in the logging. I have of course saved the script before running it. It feels like there is some lock preventing my script from running/updating/refreshing?
Is there anyone here that recognize these problems?
The code is attached below.
The two relevant entry points are:
processInbox: Updates the spreadsheet based on new (starred) e-mails with a specific label. The label and star is set by an e-mail filter on reception. The star (indicating "new") is removed from each message after processing.
resetAllMsgs: Clears the spreadsheet and stars all relevant messages, causing processInbox to process all messages received with the relevant label.
function resetAllMsgs() {
Logger.log("Starting ResetAll");
var d = SpreadsheetApp.getActive();
var dl_sheet = d.getSheetByName("Download List");
var dlperday_sheet = d.getSheetByName("DownloadsPerDay");
dl_sheet.clear();
dlperday_sheet.clear();
Logger.log("Clearing spreadsheet");
dl_sheet.appendRow(["", "", "Downloads", ""]);
dl_sheet.appendRow(["", "", "Downloaders", ""]);
dl_sheet.appendRow(["Last Download Date", "First Download Date", "Email" , "Product", "Download Count"]);
dlperday_sheet.appendRow(["Date", "Download Count"]);
var label = GmailApp.getUserLabelByName("Download Notification");
// get all threads of the label
var threads = label.getThreads();
for (var i = 0; i < threads.length; i++) {
// get all messages in a given thread
var messages = threads[i].getMessages();
Logger.log("Starring thread " + i);
for (var j = 0; j < messages.length; j++) {
Logger.log(" Starring message " + j);
// Only starred messages are processed by processInbox
messages[j].star();
Utilities.sleep(100);
}
}
};
function processInbox() {
var d = SpreadsheetApp.getActive();
var dl_sheet = d.getSheetByName("Download List");
var dlperday_sheet = d.getSheetByName("DownloadsPerDay");
// If empty spreadsheet, reset the status of all relevant e-mails and add the spreadsheet headers
if (dl_sheet.getLastRow() <= 1) {
resetAll();
}
var label = GmailApp.getUserLabelByName("Download Notification");
var k = 0;
// get all threads of the label
var threads = label.getThreads();
for (var i = 0; i < threads.length; i++) {
if (threads[i].hasStarredMessages()) {
// get all messages in a given thread
var messages = threads[i].getMessages();
// iterate over each message
for (var j = 0; j < messages.length; j++) {
// Unread messages are not previously processed...
if (messages[j].isStarred()) {
var msg = messages[j].getBody();
msg = msg.replace(/\r?\n/g, "");
var email = getDownloader(msg);
if (email == "") {
Logger.log("Found no downloader info: " + messages[j].getSubject() + " " + messages[j].getDate());
}
var date = formatDate(getDownloadDate(msg));
// Check if a new date
var dateCell = find(date, dlperday_sheet.getDataRange(), 0);
if (dateCell == null) {
// If new date, append row in "downloads per day"
dlperday_sheet.appendRow([date, 1]);
dlperday_sheet.getRange(2, 1, dl_sheet.getLastRow()-1, 2).sort([1]);
}
else
{
// If existing date, update row in "downloads per day"
var dlcount = dlperday_sheet.getRange(dateCell.getRow(), dateCell.getColumn()+1).getValue();
}
var productname = getProductName(msg);
// Check if e-mail (user) already exists in the downloads list
var matchingCell = find(email, dl_sheet.getDataRange(), 0);
if ( matchingCell != null ) {
// If user e-mail exists, update this row
var lastDownloadDate = dl_sheet.getRange(matchingCell.getRow(), matchingCell.getColumn()-1).getValue();
var lastDownloadCount = dl_sheet.getRange(matchingCell.getRow(), matchingCell.getColumn()+2).getValue();
if (lastDownloadDate != date) {
dl_sheet.getRange(matchingCell.getRow(), matchingCell.getColumn()-1).setValue(date);
}
dl_sheet.getRange(matchingCell.getRow(), matchingCell.getColumn()+2).setValue(lastDownloadCount+1);
}
else // If new user e-mail, add new download row
{
dl_sheet.appendRow([date, date, email, productname, 1]);
dl_sheet.getRange(2, 4).setValue(dl_sheet.getRange(2, 4).getValue() + 1);
dl_sheet.getRange(4, 1, dl_sheet.getLastRow()-3, 5).sort([1]);
}
// Mark message as processed, to avoid processing it on the next run
messages[j].unstar();
}
}
}
}
};
/**
* Finds a value within a given range.
* #param value The value to find.
* #param range The range to search in.
* #return A range pointing to the first cell containing the value,
* or null if not found.
*/
function find(value, range, log) {
var data = range.getValues();
for (var i = 0; i < data.length; i++) {
for (var j = 0; j < data[i].length; j++) {
if (log == 1)
{
Logger.log("Comparing " + data[i][j] + " and " + value);
}
if (data[i][j] == value) {
return range.getCell(i + 1, j + 1);
}
}
}
return null;
};
function getDownloader(bodystring) {
var marker = "Buyer Info";
var marker_begin_index = bodystring.indexOf(marker, 1000);
var email_begin_index = bodystring.indexOf("mailto:", marker_begin_index) + 7;
var email_end_index = bodystring.indexOf("\"", email_begin_index);
if (email_end_index < 1000)
{
return "";
}
var email = bodystring.substring(email_begin_index, email_end_index);
if (log == 1)
{
Logger.log("Found e-mail address: " + email + "");
Logger.log(" marker_begin_index: " + marker_begin_index);
Logger.log(" email_begin_index: " + email_begin_index);
Logger.log(" email_end_index: " + email_end_index);
}
latestIndex = email_end_index;
return email;
};
function formatDate(mydate)
{
var str = "" + mydate;
var dateParts = str.split("/");
var day = dateParts[1];
if (day.length == 1)
day = "0" + day;
var month = dateParts[0];
if (month.length == 1)
month = "0" + month;
return dateParts[2] + "-" + month + "-" + day;
};
function getDownloadDate(bodystring) {
var marker = "Download Date:</strong>";
var marker_begin_index = bodystring.indexOf(marker, latestIndex);
var date_begin_index = marker_begin_index + marker.length;
var date_end_index = bodystring.indexOf("<br>", date_begin_index);
latestIndex = date_end_index;
return bodystring.substring(date_begin_index, date_end_index).trim();
};
function getProductName(bodystring) {
var marker = "Item:</strong>";
var marker_begin_index = bodystring.indexOf(marker, latestIndex);
var pname_begin_index = marker_begin_index + marker.length;
var pname_end_index = bodystring.indexOf("</td>", pname_begin_index);
latestIndex = pname_end_index;
return bodystring.substring(pname_begin_index, pname_end_index).trim();
};
Update: Any script I run stops after about 5 seconds, even if it does not call any services.
I tried the following code:
function test() {
Logger.log("Test begins");
Utilities.sleep(5000);
Logger.log("Test ends");
}
The script terminates after about 5 sec, but the last line is not printed. If decreasing the delay to 3 seconds it behaves as expected.
Moreover, to make the script update properly after modifying it, I need to save it, start it, click cancel, and then start it again, otherwise it the log output seems to come from the old version. (I'm running this through the script editor.)
Also, the debugger refuses to start, even for the small script above. Seems to be some problem with my account (johan.kraft#percepio.se). Are there any google employee out there that can check this?
Related
I've a google sheet script that can extract emails from gmail labels it work very well on small number of emails but if the emails to in large number it gives me time out error "Exceeded maximum execution time" Is there anyone that can help me out from this problem? following i'm attaching the code that is woking fine with small number of emails but not on large number of emails.
I copy this script form here.
function GetAddresses ()
{
// Get the active spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
// Label to search
var userInputSheet = ss.getSheets()[0];
var labelName = userInputSheet.getRange("B2").getValue();
// Create / empty the target sheet
var sheetName = "Label: " + labelName;
var sheet = ss.getSheetByName (sheetName) || ss.insertSheet (sheetName, ss.getSheets().length);
sheet.clear();
// Get all messages in a nested array (threads -> messages)
var addressesOnly = [];
var messageData = [];
var startIndex = 0;
var pageSize = 100;
while (1)
{
// Search in pages of 100
var threads = GmailApp.search ("label:" + labelName, startIndex, pageSize);
if (threads.length == 0)
break;
else
startIndex += pageSize;
// Get all messages for the current batch of threads
var messages = GmailApp.getMessagesForThreads (threads);
// Loop over all messages
for (var i = 0; i < messages.length ; i++)
{
// Loop over all messages in this thread
for (var j = 0; j < messages[i].length; j++)
{
var mailFrom = messages[i][j].getFrom ();
var mailDate = messages[i][j].getDate ();
// mailFrom format may be either one of these:
// name#domain.com
// any text <name#domain.com>
// "any text" <name#domain.com>
var name = "";
var email = "";
var matches = mailFrom.match (/\s*"?([^"]*)"?\s+<(.+)>/);
if (matches)
{
name = matches[1];
email = matches[2];
}
else
{
email = mailFrom;
}
// Check if (and where) we have this already
var index = addressesOnly.indexOf (mailFrom);
if (index > -1)
{
// We already have this address -> remove it (so that the result is ordered by data from new to old)
addressesOnly.splice(index, 1);
messageData.splice(index, 1);
}
// Add the data
addressesOnly.push (mailFrom);
messageData.push ([name, email, mailDate]);
}
}
}
// Add data to corresponding sheet
sheet.getRange (1, 1, messageData.length, 3).setValues (messageData);
}
//
// Adds a menu to easily call the script
//
function onOpen ()
{
var sheet = SpreadsheetApp.getActiveSpreadsheet ();
var menu = [
{name: "Extract email addresses",functionName: "GetAddresses"}
];
sheet.addMenu ("Start Extracting", menu);
}
In this case you are receiving this error message as your code is exceeding the maximum execution time for Apps Script. Currently the limit is of 6 minutes. You can verify this information in the Current limitations.
You will notice that this is the information displayed for the runtime quota:
I have a sheet with ~18k rows in. I have written a script to divide the number of rows by the number of users who want to get involved in calculating values in the rows in the sheet and then allocating those rows to those users. A collaborative effort to speed things up as each row takes approximately 1s and it takes ~ 6-9 hours to go through it on my own with network issues etc.
When a user opens the sheet, they are allocated a user number and a chunk of rows to work on with 5 users 18k rows breaks down to 3600 rows each.
The issue is that when more than 1 or 2 users are working on the sheet, the performance becomes erratic. Sometimes a row takes 15-20s to be processed. I am saving nothing by having friends share the task.
I read somewhere that only 100 users can work on a sheet at the same time but here I am talking about 5 (up to a maximum of maybe 10 or 11) people working on the sheet, each running maybe 5-8 functions each on a trigger. Right now I have 5 users connected to the sheet, each running 8 functions and the work has slowed almost to a stop.
Does anyone have any experience with this and know of any limits google place on accounts working on a script? Any way to work with apps script to make this work properly?
Thanks for your insights!
Chad
/*NEW CODE WITH TRIGGER - WE START IT UP WITH A CALL TO runTriggersTwoTimes()*/
function runTriggersTwoTimes() {
createSplitWorkTrigger();
ScriptApp.newTrigger('createSplitWorkTrigger')
.timeBased()
.after(80 * 60 * 1000)
.create();
}
function createSplitWorkTrigger() {
ScriptApp.newTrigger('splitWork2')
.timeBased()
.everyMinutes(5)
.create();
ScriptApp.newTrigger('deleteAllTriggers')
.timeBased()
.after(60 * 60 * 1000)
.create();
}
/*This function splits the work of getting the "From" prices between multiple accounts*/
function splitWork2() {
var accountsArray = [user1, user2, user3, user4, user5#gmail.com];
var numberAccounts = accountsArray.length;
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = spreadsheet.getSheetByName("CK Formatted");
var RANGE = sheet.getDataRange();
var rangeVals = RANGE.getValues();
var numberRows = rangeVals.length;
var totalChunkSize = Math.floor(numberRows / numberAccounts);
var userName = Session.getActiveUser().getEmail();
var userNumber;
for (i = 0; i < numberAccounts; i++) {
if (userName == accountsArray[i]) {
userNumber = i;
}
}
var usersStartRow = userNumber * totalChunkSize + 1
var usersLastRow = usersStartRow + totalChunkSize - 1;
if (userName == accountsArray[numberAccounts - 1]) {
usersLastRow = numberRows;
}
// This one does the main work
findFromPricesByChunks2(userNumber, usersStartRow, usersLastRow, 110, totalChunkSize);
}
/*This function adds the "From" prices in chunks*/
function findFromPricesByChunks2(userNumber, startRow, lastRow, chunkSize, totalChunkSize, checkingRound = false) {
if (startRow >= lastRow) {
var allTriggers = ScriptApp.getProjectTriggers();
for (var i = 0; i < allTriggers.length; i++) {
ScriptApp.deleteTrigger(allTriggers[i]);
}
return;
}
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = spreadsheet.getSheetByName("CK Formatted");
var RANGE = sheet.getDataRange();
var rangeVals = RANGE.getValues();
Logger.log(rangeVals.length);
var addedPrices = 0;
for (var i = startRow; i <= lastRow; i++) {
if (addedPrices == chunkSize) {
return;
}
var cellValue = sheet.getRange(`A${i + 1}`).getValue();
Logger.log('Cell Value:' + cellValue);
if (cellValue == '') {
Utilities.sleep(1000);
var html = UrlFetchApp.fetch(URL).getContentText();
var $ = Cheerio.load(html);
var s = $('#table .price-container .font-weight-bold').first().text();
// if we fail to get a value because of network issues or whatever this next piece is going ahead and putting a full stop in what should be an empty box. WIll think about this
if (s) {
s = s.replace(".", ",");
s = s.substring(0, s.lastIndexOf(',')) + '.' + s.substring(s.lastIndexOf(',') + 1);
}
fromArrayCell = sheet.getRange(`A${i + 1}`);
fromArrayCell.setValue(s);
addedPrices++;
}
}
var sheetCk = spreadsheet.getSheetByName("CK");
// This adds the amount of processed items to the processed rows counter (because now there are multiple workers working
// at the same time)
if (!checkingRound) {
sheetCk.getRange(userNumber + 2, 9).setValue(sheetCk.getRange(userNumber + 2, 9).getValue() + addedPrices);
} else {
sheetCk.getRange(userNumber + 2, 10).setValue(sheetCk.getRange(userNumber + 2, 10).getValue() + addedPrices);
}
}
function deleteAllTriggers() {
var allTriggers = ScriptApp.getProjectTriggers();
for (var i = 0; i < allTriggers.length; i++) {
ScriptApp.deleteTrigger(allTriggers[i]);
}
}
The single most important thing to come out of this post was the limit of 30 executions which I was told before I posted any code :p I didn't know about that and it was the crux of the problem I was having. Bear in mind that the original code (a few versions back) was trying to cram 13 users in with 8 processes each :)
After drifting for a few days I had a good sleep and came up with this new improved version of the above:
function splitWork() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheetCk = spreadsheet.getSheetByName("CK");
var sheet = spreadsheet.getSheetByName("CK Formatted");
var RANGE = sheet.getDataRange();
var rangeVals = RANGE.getValues();
var userName = Session.getActiveUser().getEmail();
Logger.log(userName)
var accountsArray = [gmail1, gmail2, gmail3, gmail4, gmail5];
var numberRows = rangeVals.length;
var numberAccounts = accountsArray.length;
var totalChunkSize = Math.floor(numberRows / numberAccounts);
var userNumber;
for (i = 0; i < numberAccounts; i++) {
if (userName == accountsArray[i]) {
userNumber = i;
}
}
var usersStartRow = (userNumber * totalChunkSize) + 1; // = 1
var usersLastRow = usersStartRow + totalChunkSize - 1; // = 211
if(userName == accountsArray[numberAccounts - 1]) { usersLastRow = numberRows; }
var completeRowsOriginal = sheetCk.getRange(userNumber + 2, 9).getValue() // = 109
usersStartRow = usersStartRow + completeRowsOriginal // = 110
for (var i = usersStartRow; i <= usersLastRow; i++) { // 110 - 211
var cellValue = sheet.getRange(`A${i + 1}`).getValue();
var completeRows = 0
if (cellValue == '') {
Utilities.sleep(1000);
var html = UrlFetchApp.fetch(URL).getContentText();
var $ = Cheerio.load(html);
var fromArrayCell = sheet.getRange(`A${i + 1}`);
if(sheetCk.getRange(userNumber + 2, 10).getValue() != 1){
var s = $('#table .price-container .font-weight-bold').first().text();
}
else if(sheetCk.getRange(userNumber + 2, 10).getValue() == 1) {
var s = $('#table .price-container .font-weight-bold').first().text();
if(!s) {
var s = $('#tabContent-info .info-list-container dd:nth-child(12)').text();
if(s) { fromArrayCell.setBackground('#ff8680'); }
}
}
if (s) {
s = s.replace(".", ",");
s = s.substring(0, s.lastIndexOf(',')) + '.' + s.substring(s.lastIndexOf(',') + 1);
}
fromArrayCell.setValue(s);
completeRows++
sheetCk.getRange(userNumber + 2, 9).setValue(sheetCk.getRange(userNumber + 2, 9).getValue() + 1)
}
if(completeRows >= 100 || usersStartRow + completeRows >= usersLastRow) {break}
}
if(sheetCk.getRange(userNumber + 2, 9).getValue() + usersStartRow >= usersLastRow) {
if(sheetCk.getRange(userNumber + 2, 10).getValue() != 1) {
sheetCk.getRange(userNumber + 2, 9).setValue(0)
sheetCk.getRange(userNumber + 2, 10).setValue(1)
}
}
else { deleteAllTriggers() }
}
Which works like a charm.
A user opens the sheet, is allocated a chunk in the background, presses a button to start a trigger to work on their chunk every 5 minutes and if they are feeling frisky they can run the function above while waiting for the trigger to start (also from a button). They'll scrape values for 100 rows then wait for another trigger. After they have gone through their whole chunk their counter resets and their master counter is set to 1. They then make a second pass over their chunk filling in any gaps due to network errors. If they can't get a value from the regular place on the second pass they can get a value from another place but it is marked with a red background as it is not reliable.
Don't think there is anything else left to say about this one. Try it yourselves if you have a large sheet of data to churn through and want to do it in less time with help from your friends with google accounts.
PS: CK is the sheet which is used to hold counters only
PPS!! If you literally do it as above, you will manage to process about 1 row per second. The real power coms from getting all the helper account to make their own copy of the main sheet and work on that. Here we'd get 13 rows per second processed, for instance. Then it's a simple matter to recombine everything into single sheet when they're done.
I'm using this script for deleting old messages from gmail every X days.
It functions correctly, however messages are sent to trash.
I want to delete the messages permanently without sending them to trash.
Someone can modify this script?
// The name of the Gmail Label that is to be autopurged?
var GMAIL_LABEL = "mylabel";
// Purge messages automatically after how many days?
var PURGE_AFTER = "21";
function purgeGmail() {
var age = new Date();
age.setDate(age.getDate() - PURGE_AFTER);
var purge = Utilities.formatDate(age, Session.getTimeZone(), "yyyy-MM-dd");
var search = "label:" + GMAIL_LABEL + " before:" + purge;
// This will create a simple Gmail search
// query like label:Newsletters before:10/12/2012
try {
// We are processing 100 messages in a batch to prevent script errors.
// Else it may throw Exceed Maximum Execution Time exception in Apps Script
var threads = GmailApp.search(search, 0, 100);
// For large batches, create another time-based trigger that will
// activate the auto-purge process after 'n' minutes.
// if (threads.length == 100) {
// ScriptApp.newTrigger("purgeGmail")
// .timeBased()
// .at(new Date((new Date()).getTime() + 1000*60*10))
// .create();
// }
// An email thread may have multiple messages and the timestamp of
// individual messages can be different.
for (var i=0; i<threads.length; i++) {
var messages = GmailApp.getMessagesForThread(threads[i]);
for (var j=0; j<messages.length; j++) {
var email = messages[j];
if (email.getDate() < age) {
email.moveToTrash();
}
}
}
// If the script fails for some reason or catches an exception,
// it will simply defer auto-purge until the next day.
} catch (e) {}
}
thanks
It's pretty simple all you have to do is get all of your message id's in an array and then use the following two lines.
var request={"ids":messageIdArray};
Gmail.Users.Messages.batchDelete(request, "me");
You will have to enable the Advanced Gmail API
batchDelete
So if the rest of your code actually works then this should do it:
var GMAIL_LABEL = "mylabel";
var PURGE_AFTER = "21";
function purgeGmail() {
var age = new Date();
age.setDate(age.getDate() - PURGE_AFTER);
var purge = Utilities.formatDate(age, Session.getTimeZone(), "yyyy-MM-dd");
var search = "label:" + GMAIL_LABEL + " before:" + purge;
try {
var msgA=[];
for (var i=0; i<threads.length; i++) {
var messages = GmailApp.getMessagesForThread(threads[i]);
for (var j=0; j<messages.length; j++) {
var email = messages[j];
if (email.getDate() < age) {
msgA.push(email.getId());
}
}
}
} catch (e) {}
}
I am working on a "vehicle scheduler" for my workplace that will display the dates and times a truck will be unavailable. Here's the link to the Google Sheets where the script runs. I have set up two example "busy times" to show what I am doing
One adjustment that was requested from me is to do 30 minute increments, but then the amount of columns gets absolutely bonkers.
I am open to any recommendations to improve this tool, even if it means something better (and free) already exists.
I appreciate your input!
See code below... (I am RIDICULOUSLY novice at coding, so be kind :)
function processForm(formObject){
var day = formObject.dow;
var vehicle = formObject.vehicle;
var jobName = formObject.job;
var driverName = formObject.driver;
var duration = formObject.duration;
var startTime = formObject.startTime;
var ampm = formObject.ampm;
var location = formObject.location;
var returnArray = [day, vehicle, jobName, driverName, duration, startTime, ampm, location]
var startTimeColumn = 0;
var startDayColumn = 2;
var sduration = startTime;
var ss = SpreadsheetApp.getActive().getSheetByName("Week View");
if (day=="Monday"){
startDayColumn = 26;
}
else if (day=="Tuesday"){
startDayColumn = 50;
}
else if (day=="Wednesday"){
startDayColumn = 74;
}
else if (day=="Thursday"){
startDayColumn = 98;
}
else if (day=="Friday"){
startDayColumn = 122;
}
else if (day=="Saturday"){
startDayColumn = 146;
}
if (ampm=="PM"){
startDayColumn+=12;
}
if (startTime==12){
startTime = 0;
}
startTimeColumn = parseInt(startTimeColumn) + parseInt(startDayColumn) + parseInt(startTime);
var columnA = ss.getRange(1,1,ss.getLastRow(),1).getValues();
for (var rr=0;rr<ss.getLastRow();rr++){
if (columnA[rr]==vehicle){
break;
}
}
rr +=1;
for (var cc=0; cc<duration; cc++){
var checkColumn = parseInt(cc) + parseInt(startTimeColumn);
if (ss.getRange(rr, checkColumn).isBlank()){}
else return "It looks like that time is booked :(";
}
var displayString = "Job: " + jobName + String.fromCharCode(10) + "Driver: " + driverName + String.fromCharCode(10) + sduration + ":00 " + ampm + String.fromCharCode(10) + location;
var merging = ss.getRange(rr,startTimeColumn,1,duration).mergeAcross();
var enterInfo = ss.getRange(rr, startTimeColumn).setValue(displayString);
if (rr % 2 == 0){
enterInfo.setBackgroundRGB(45, 114, 157);
}
else {enterInfo.setBackgroundRGB(26, 153, 136);}
enterInfo.setFontColor('white')
return "Success! Load has been booked to the scheduler.";
}
Instead of making Sheets jump thru hoops and create a interface for inserting data and a Calendar to show it, why not use Forms and Calendar to do it all?
What I have in mind
Sheets as a long-time database for Truck Information and schedules
Calendar as a way to visualize the availability of Trucks
Forms as a interface for data-input
Set-up
A Spreadsheet file with a "List of Trucks" Sheet.
A Form attached to it that allows users to insert information about the truck booking.
To make this work, I have inputted some data on out List of Trucks:
Then I associated a form with that sheet and setup some basic questions:
I also modified the forms settings to allow people to edit it on the future, this will allow us to add information to the calendar to tie an event with a response form the form.
I added a script to the Forms that populates the first question with the data from our sheets:
function populateForm() {
var form = FormApp.getActiveForm();
var TruckIdQ = form.getItems()[0];
var truckList = SpreadsheetApp.openByUrl("YOUR SHEET URL").getSheetByName("List of Trucks").getDataRange().getValues().map(function (row) { return row[0]});
truckList.shift(); //Remove header
var choices = [];
for (var i=0; i< truckList.length; i++) {
choices.push(TruckIdQ.asListItem().createChoice(truckList[i]));
}
TruckIdQ.asListItem().setChoices(choices);
}
And set it to be run when the form is opened:
After that I went back to my sheets and modified the automatic Responses sheet to have a more understandable name: "Bookings".
Now we need to create a shared calendar to put all this information on it.
I created one named "Truck Booking Calendar" in the following way:
I then created a new Script on the Forms responsible for populating the calendar with information.
function populateCalendar(e) {
var bookingCalendar = CalendarApp.getCalendarById("CALENDAR ID");
var questions = FormApp.getActiveForm().getItems();
var response = e.response.getItemResponses();
var eventDescription = "";
var truckname;
var fromDate, toDate;
var fromTime, toTime;
for (var i=0; i< questions.length; i++) {
eventDescription = eventDescription + questions[i].getTitle() + " : " + response[i].getResponse() + "\n";
switch (i) {
case 0:
//Truck Id
truckname = response[i].getResponse();
break;
case 1:
//From Date
fromDate = response[i].getResponse();
break;
case 2:
//To Date
toDate = response[i].getResponse();
break;
case 3:
//From Time
fromTime = response[i].getResponse();
break;
case 4:
//To Time
toTime = response[i].getResponse();
break;
}
}
var startDateTime = new Date(fromDate + " " + fromTime);
var endDateTime = new Date(toDate + " " + toTime);
var newEvent = bookingCalendar.createEvent("Booking "+truckname, startDateTime, endDateTime, {description:eventDescription});
newEvent.setTag("truck", truckname);
newEvent.setTag("responseId", e.response.getEditResponseUrl());
newEvent.setTag("responseUrl", e.response.getId());
}
And created a trigger for it like this:
Now, when I submit this into the form:
I get this entry on my calendar:
Hope this helps you!
I am trying to use a google apps script to dump emails that I have under a specific label to a Google Docs Spreadsheet. I want to list each email message body in a thread as a separate row, such that if a thread has a chain of 9 messages, each one is listed separately (without the chain) in a row.
I have managed get it to where each message body + its entire previous thread is stored, in one cell and I can get the entire thread in a cell. But this is not what I want.
This code will put the entire body of the thread in a row.
function getEmails() {
clearCanvas();
var label = GmailApp.getUserLabelByName(LabelWithEmails);
var threads = label.getThreads();
// var threads = GmailApp.getInboxThreads(0, 50);
var row = getFirstRow() + 1;
var firstmessageId = getfirstmsgid();
UserProperties.setProperty("firstmsgid", firstmessageId);
spreadsheet.toast("Loading emails..Please wait. It could take few seconds", "Status", -1);
var messages = GmailApp.getMessagesForThreads(threads); //gets messages in 2D array
for (i = 0; i < 5; ++i)
{
try {
j = messages[i].length; //to process most recent conversation in thread (contains messages from previous conversations as well, reduces redundancy
messageBody = messages[i][j-1].getBody(); //gets body of message in HTML
messageSubject = messages[i][j-1].getSubject();
messageDate = messages[i][j-1].getDate();
messageFrom = messages[i][j-1].getFrom();
Logger.log("Message Subject:" + messageSubject);
Logger.log("Message Date:" + messageDate);
Logger.log("Message From:" + messageFrom);
sheet.getRange(row, 1).setValue(messageFrom);
sheet.getRange(row, 2).setValue(messageSubject);
sheet.getRange(row, 3).setValue(messageDate);
sheet.getRange(row, 4).setValue(getTextFromHtml(messageBody));
row++;
} catch (error) {
spreadsheet.toast("Error Occured. Report it # http://techawakening.org/", "Status", -1);
}
if (i == threads.length - 1) {
spreadsheet.toast("Successfully loaded emails.", "Status", -1);
spreadsheet.toast("Now mark emails to be forwarded by changing the background color of the cells to green. Then select Forward->Forward selected emails", "Status", -1);
}
}
}
This will put each message body including it's previous thread/message chain in a row.
function getEmails() {
clearCanvas();
var label = GmailApp.getUserLabelByName(LabelWithEmails);
var threads = label.getThreads();
// var threads = GmailApp.getInboxThreads(0, 50);
var row = getFirstRow() + 1;
var firstmessageId = getfirstmsgid();
UserProperties.setProperty("firstmsgid", firstmessageId);
spreadsheet.toast("Loading emails..Please wait. It could take few seconds", "Status", -1);
var messages = GmailApp.getMessagesForThreads(threads); //gets messages in 2D array
// messages.length
// jknipp - working except it keeps the thread chain
for (var i = 0; i < threads.length; i++) {
try {
var messages = threads[i].getMessages();
for (var m = 0; m < messages.length; m++) {
sheet.getRange(row, 1).setValue(messages[m].getFrom());
sheet.getRange(row, 2).setValue(messages[m].getSubject());
sheet.getRange(row, 3).setValue(messages[m].getDate());
sheet.getRange(row, 4).setValue(getTextFromHtml(messages[m].getBody()));
row++;
}
} catch (error) {
spreadsheet.toast("Error Occured. Report it # http://techawakening.org/", "Status", -1);
}
if (i == threads.length - 1) {
spreadsheet.toast("Successfully loaded emails.", "Status", -1);
spreadsheet.toast("Now mark emails to be forwarded by changing the background color of the cells to green. Then select Forward->Forward selected emails", "Status", -1);
}
}
}
References
https://stackoverflow.com/a/11034461/39803
I was able to pull out only the body of the emails by identifying where the 'previous conversation' started.
var sheet = SpreadsheetApp.getActiveSheet();
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var LabelWithEmails = sheet.getRange(3, 2).getValue();
function getEmails() {
clearCanvas();
var label = GmailApp.getUserLabelByName(LabelWithEmails);
var threads = label.getThreads();
var row = getFirstRow() + 1;
var firstmessageId = getfirstmsgid();
UserProperties.setProperty("firstmsgid", firstmessageId);
spreadsheet.toast("Loading emails..Please wait. It could take few seconds", "Status", -1);
var messages = GmailApp.getMessagesForThreads(threads); //gets messages in 2D array
for (var i = 0; i < 2;/*threads.length;*/ i++) {
try {
var messages = threads[i].getMessages();
for (var m = 0; m < messages.length; m++) {
var msg = messages[m];
var isForward = msg.getBody().search(/---------- Forwarded message/i) != -1;
if(!isValidMessage) continue;
sheet.getRange(row, 1).setValue(msg.getFrom());
sheet.getRange(row, 2).setValue(msg.getTo() + ";" + msg.getCc() + ";" + msg.getBcc());
sheet.getRange(row, 3).setValue(msg.getSubject());
sheet.getRange(row, 4).setValue(msg.getDate());
if(!isForward) {
// Get only this messages body, ignore the previous chain
var body = msg.getBody();
var firstIndexOfThread = body.search(/gmail_quote/i);
body = (firstIndexOfThread == -1) ? body : body.substring(0, firstIndexOfThread);
sheet.getRange(row, 5).setValue(getTextFromHtml(body));
} else {
// Use the whole body if its a forward
sheet.getRange(row, 5).setValue(getTextFromHtml(msg.getBody()));
sheet.getRange(row, 6).setValue("***");
}
row++;
}
} catch (error) {
Logger.log(error);
spreadsheet.toast("Error Occured - please see the logs.", "Status", -1);
}
if (i == threads.length - 1) {
spreadsheet.toast("Successfully loaded emails.", "Status", -1);
}
}
}
This is a case of "garbage-in, garbage-out". When you're using the gmail app in thread view, Google's servers are parsing the body of emails and cleverly hiding the bodies of old messages. This makes it appear that the latest message in a thread consists of only the new lines of that message, and that you have a "chain" of smaller messages.
This is an illusion. The last message in a thread typically contains the new content first, followed by the content of all previous message bodies, as a single message body. Different email services and clients use different patterns for this.
You would need to be able to identify most or all of the ways that the content from previous messages in a thread are represented in the current message body, and use that to extract only the new content.