Google Apps script - syntax of passed in ranges - google-apps-script

I use Google Apps Scripts to analyse some data in a Google Sheet.
I want to obtain the first date of purchase and the last date of sale of a group of tickers "Tickers" extracted from a list of tickers "TickerRange". The dates are in "dateRange".
The tickers should be included in the calculation only if the related tickbox is checked in "includeRange".
After a lot of trying, I believe there is a syntax problem in the line:
if (TickerRange[i] == Tickers[j]) {
I have also tried
if (TickerRange[i].value == Tickers[j].value) {
and still it does not work.
Here the full code:
function TickersDates(Tickers,TickerRange,dateRange,includeRange) {
var Datesoutput = new Array(1);
// Compute StartDate and EndDate for the current tickers
var dateTemp = new Date(1900, 01, 01);
var minDate= new Date(2900, 01, 01);
var maxDate= new Date(1900, 01, 01);
for(var j=0, jLen=Tickers.length; j<jLen; j++) {
if (includeRange[j]=="true" && Tickers[j]!="") {
for(var i=0, iLen=dateRange.length; i<iLen; i++) {
dateTemp = new Date(dateRange[i]);
if (isValidDate(dateTemp)){
if (TickerRange[i] == Tickers[j]) {
minDate = Math.min(dateTemp.valueOf(), minDate.valueOf());
maxDate = Math.max(dateTemp.valueOf(), maxDate.valueOf());
}
}
}
}
}
var StartDate = new Date(minDate)
var EndDate = new Date(maxDate)
Datesoutput[0]=StartDate
Datesoutput[1]=EndDate
return Datesoutput;
}

The arguments of a custom function is a 2 dimensional array. You should use
if (TickerRange[i][0] == Tickers[j][0]) {
as objects cannot be compared: []!==[]

Related

How to find a time for a meeting with several participants using Google Calendar API and Apps Script?

I am working on a chatbot project using Dialogflow API, Apps Script, Calendar API. The chatbot should be able to organize a meeting between two or more participants directly from the chat. For example, the user says something like "organize a meeting with john.smith#mail.com for tomorrow at 5 pm" and the chatbot should go to both my and John's calendars, check availability for this time frame and book the meeting. So far so good. I already have the solution up to this stage (check the code snippet below). My question is if the users are busy for this time frame, how can I get the suggested time where all the participants are free. I am using the Free/Busy call but it only returns when the users are busy.
//the function search in spreadshet containing all timezones and selects the corresponding offset
function setCorrectTimeZone(rawTime,rawDate){
var spreadSheet = SpreadsheetApp.openById("10tJCi5bRHs3Cl8Gvw8NGMeHhfRBDnvPD338peH2MWyg"); //the timezones sheed ID stored in CEE Google Assistant shared drive
var sheet = spreadSheet.getSheetByName("timezones");
var timezoneRange = sheet.getRange(2, 1, 513, 2).getValues();
//getting the user's timezone
var userTimeZone = CalendarApp.getDefaultCalendar().getTimeZone();
Logger.log("User time zone: " + userTimeZone);
var userTimeZoneOffset = "";
var correctDateTimeAndZone = "";
//iterating over the timezones from the sheet and comparing with user's to find the correct offset
for(var i = 0; i<timezoneRange.length; i++){
if(timezoneRange[i][1] == userTimeZone){
userTimeZoneOffset = timezoneRange[i][0];
}
}
//taking the date only
var date = rawDate.split('T')[0];
//taking the time only
var timeNoTimeZone = rawTime.split('+')[0].split('T')[1];
//concatenating the date, time and the correct timezone together
correctDateTimeAndZone = date + 'T' + timeNoTimeZone + userTimeZoneOffset;
return correctDateTimeAndZone;
}
function organizeMeeting(dialogflowRawResponse, email) {
var guestEmail = dialogflowRawResponse.queryResult.parameters.email; //the list of all guests
var rawDate = dialogflowRawResponse.queryResult.parameters.date;
var rawTime = dialogflowRawResponse.queryResult.parameters.time;
var eventTitle = dialogflowRawResponse.queryResult.parameters.meetingName;
var hasAllParams = dialogflowRawResponse.queryResult.hasOwnProperty('allRequiredParamsPresent'); //checker for all parameters
var correctedTimezone = setCorrectTimeZone(rawTime,rawDate);
Logger.log("Has all required parameters? " + hasAllParams);
//check if all parameters are passed
while(hasAllParams == false){
Logger.log("Parameters are missing");
Logger.log(dialogflowRawResponse.queryResult.fulfillmentText);
return { text: dialogflowRawResponse.queryResult.fulfillmentText };
}
Logger.log("Guests email list detected: " + JSON.stringify(guestEmail) + "\nDate-time detected: " + rawTime + "\nCorrect date-time timezone: " + correctedTimezone +"\nTitle detected: " + eventTitle);
//setting the date-time for the start and the end of the event
var dateTimeStart = new Date(correctedTimezone);
var dateTimeEnd = new Date(correctedTimezone);
dateTimeEnd.setHours(dateTimeEnd.getHours() + 1);
dateTimeStart = dateTimeStart.toISOString();
dateTimeEnd = dateTimeEnd.toISOString();
Logger.log("ISO dateTimeStart: " + dateTimeStart);
Logger.log("ISO dateTimeEnd: " + dateTimeEnd);
var participants = [{"id": email}]; //array of objects. Each object is a particpant for the event
for(var i = 0; i < guestEmail.length; i++){
participants.push({"id": guestEmail[i]}); //filling the participant array
}
//preparing the body for the Calendar API free-busy request
var requestBody = {
"timeMin": dateTimeStart,
"timeMax": dateTimeEnd,
"items": participants
}
//Calendar freebusy request to check if the slot is available for all particiaptns
var response = Calendar.Freebusy.query(requestBody);
for(var i = 0; i < participants.length; i++){
var calendarId = participants[i].id;
if(response.calendars[calendarId]['busy'].length != 0){
Logger.log(calendarId + " is busy at this time");
return { text: calendarId + " is busy at this time" };
break;
}
}
//guest array of objects for each participant
var guestsArr = [{"email":email}];
for(var i = 0; i < guestEmail.length; i++){
guestsArr.push({"email": guestEmail[i]});
}
//preparing the event details for the Calendar API call
var event = {
"summary": eventTitle,
"end": {
"dateTime": dateTimeEnd
},
"start": {
"dateTime": dateTimeStart
},
"attendees": guestsArr
}
//preapring the event options for the Calendar API call
var eventOptions = {
"sendNotifications": true,
"sendUpdates": "all"
}
//Calendar API call
var calendarEventRequest = Calendar.Events.insert(event, "primary",eventOptions);
//logs the Calendar API response to the logs
Logger.log(JSON.stringify(calendarEventRequest));
return { text: "Done! Check you calendar." };
}
The code above takes the parameters from Dialogflow API - date, time, meeting title, and participants and uses this information to make free/busy call and then Calendar API call eventually. It is also using spreadsheet db to find the correct user timezone based on the user location.
Any help will be highly appreciated if someone has already done such feature to get available time slots.
You can check each one-hour timeslot if everyone is free, if they are all free, then send the invitations in Calendar.
Sample Code:
var dateTimeStart = new Date(correctedTimezone);
var dateTimeEnd = new Date(correctedTimezone);
do {
dateTimeEnd.setHours(dateTimeStart.getHours() + 1);
dateTimeStart = dateTimeStart.toISOString();
dateTimeEnd = dateTimeEnd.toISOString();
Logger.log("ISO dateTimeStart: " + dateTimeStart);
Logger.log("ISO dateTimeEnd: " + dateTimeEnd);
var participants = [{"id": email}]; //array of objects. Each object is a particpant for the event
for(var i = 0; i < guestEmail.length; i++){
participants.push({"id": guestEmail[i]}); //filling the participant array
}
//preparing the body for the Calendar API free-busy request
var requestBody = {
"timeMin": dateTimeStart,
"timeMax": dateTimeEnd,
"items": participants
}
//Calendar freebusy request to check if the slot is available for all particiaptns
var response = Calendar.Freebusy.query(requestBody);
for(var i = 0; i < participants.length; i++){
var calendarId = participants[i].id;
if(response.calendars[calendarId]['busy'].length != 0){
dateTimeStart.setHours(dateTimeStart.getHours() + 1);
Logger.log(calendarId + " is busy at this time");
//return { text: calendarId + " is busy at this time" };
break;
}
}
}
while (response.calendars[calendarId]['busy'].length != 0);

Move emails with labels to trash after 1 day fail

When I use the script below, sourced from the web, it only works for one (Cam1) of the two labels . The labels within Gmail are associated with the emails and they are older than 1 day.
Why is this script only working as written on one label?
Im new at this so please keep it simple! Thanks
function oldEmailDeletion() {
//Age of email threads that will be deleted (i.e. older_than: # days)
var daysAgo = 1;
//Expiration date variable
var expirationDate = new Date();
//Set the older_than date. Any email older than this date will be deleted
expirationDate.setDate(expirationDate.getDate()-daysAgo);
//Labels associated with emails to be included in deletion
var labels = [
'Cam1',
'Cam2'
];
//Loop through each email label found in the "labels" variable array
for(var i = 0; i < labels.length; i++){
//Retrieve label information based on value in "labels" variable array
var label = GmailApp.getUserLabelByName(labels[i].toString());
//Access all email threads associated with the retrieved label
var emailThreads = label.getThreads(); //getThreads(###,###) if a specific range of email threads to retrieve
//Loop through each email thread set to "emailThreads" variable
for(var j = 0; j < emailThreads.length; j++){
//If an email thread is older than the expiration date, then delete
if(emailThreads[j].getLastMessageDate() < expirationDate){
emailThreads[j].moveToTrash();
}
}
}
}
Try it this way:
function oldEmailDeletion() {
var dt=new Date();
var exp=new Date(dt.getFullYear(),dt.getMonth(),dt.getDate()-1).valueOf();
var labels=['Cam1','Cam2'];
for(var i=0;i<labels.length;i++){
var label=GmailApp.getUserLabelByName(labels[i].toString());
var emailThreads=label.getThreads();
for(var j=0;j<emailThreads.length;j++){
if(emailThreads[j].getLastMessageDate().valueOf()<exp){
emailThreads[j].moveToTrash();
}
}
}
}
If you actually want to delete the messages delete the message older than one day then this approach should work for you. And you will need to Enable the Gmail API.
function oldEmailDeletion() {
const dt=new Date();
const exp=new Date(dt.getFullYear(),dt.getMonth(),dt.getDate()-1).valueOf();
const labels=['Cam1','Cam2'];
var idA=[];
for(var i=0;i<labels.length;i++){
var label=GmailApp.getUserLabelByName(labels[i].toString());
var emailThreads=label.getThreads();
for(var j=0;j<emailThreads.length;j++){
if(emailThreads[j].getMessageCount()>0) {
var msgs=emailThreads[j].getMessages();
for(var k=0;i<msgs.length;j++) {
var msg=msgs[k];
if(new Date(msg.getDate()).valuefOf()<exp) {
idA.push(msg.getId());
}
}
}
}
}
if(idA.length>0) {
var request={"ids":idA};
Gmail.Users.Messages.batchDelete(request, "me");
}
}
If you just want to move them to the trash. Then this approach should work for you.
function oldEmailTrash() {
const dt=new Date();
const exp=new Date(dt.getFullYear(),dt.getMonth(),dt.getDate()-1).valueOf();
const labels=['Cam1','Cam2'];
var idA=[];
for(var i=0;i<labels.length;i++){
var label=GmailApp.getUserLabelByName(labels[i].toString());
var emailThreads=label.getThreads();
for(var j=0;j<emailThreads.length;j++){
if(emailThreads[j].getMessageCount()>0) {
var msgs=emailThreads[j].getMessages();
for(var k=0;i<msgs.length;j++) {
var msg=msgs[k];
if(new Date(msg.getDate()).valueOf()<exp) {
idA.push(msg.getId());
}
}
}
}
}
if(idA.length>0) {
idA.forEach(function(msg){msg.moveToTrash();});
}
}
Thanks Cooper. I tried for days but lacked the skills to debug your scripts. I found and modified the script below and it works.
First I created a new filter in gmail that labeled all cam emails 'Clean' then set the script below on a trigger. This was the only way I was able to achieve my goal.
Thanks again!
function gmailCleaningRobot() {
var delayDays = 1; // will only impact emails more than 24h old
var maxDate = new Date();
maxDate.setDate(maxDate.getDate()-delayDays); // what was the date at that time?
// creating an array containing all the search strings matching the emails we want to be treated automatically
var searches = [
'label:Clean older_than:1d' //with label clean and older than 1d
//'"is now available on Spotify" from:spotify is:unread', // Spotify new album notification
//'YOUR NEW SEARCH STRING HERE', // any other search string
//'YOUR NEW SEARCH STRING HERE', // any other search string
//'YOUR NEW SEARCH STRING HERE', // any other search string
//'YOUR NEW SEARCH STRING HERE' // any other search string
];
// creating an array containing all the threads matching the searches above
var threads = [];
for (var i = 0; i < searches.length; i++) {
var tmp_threads = GmailApp.search(searches[i], 0, 500); // I limit the search to 500 results but you can adjust this one
var threads = threads.concat(tmp_threads);
}
// we archive all the threads if they're older than the time limit we set in delayDays
for (var i = 0; i < threads.length; i++) {
if (threads[i].getLastMessageDate()<maxDate)
{
threads[i].moveToTrash();
}
}
}

User Drive's usage without shared files

I'd like to create a report of storage usage for all my users. To do so I use AdminReports app, like so (found in google example, somewhere. Just had to adapt the "parameters" and the "row" arrays) :
function generateUserUsageReport() {
var today = new Date();
var oneWeekAgo = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000);
var timezone = Session.getScriptTimeZone();
var date = Utilities.formatDate(oneWeekAgo, timezone, 'yyyy-MM-dd');
var parameters = [
'accounts:gmail_used_quota_in_mb',
'accounts:drive_used_quota_in_mb',
'accounts:total_quota_in_mb ',
'accounts:used_quota_in_percentage'
];
var rows = [];
var pageToken;
var page;
do {
page = AdminReports.UserUsageReport.get('all', date, {
parameters: parameters.join(','),
maxResults: 500,
pageToken: pageToken
});
if (page.warnings) {
for (var i = 0; i < page.warnings.length; i++) {
var warning = page.warnings[i];
Logger.log(warning.message);
}
}
var reports = page.usageReports;
if (reports) {
for (var i = 0; i < reports.length; i++) {
var report = reports[i];
var parameterValues = getParameterValues(report.parameters);
var row = [
report.date,
report.entity.userEmail,
parseInt(parameterValues['accounts:drive_used_quota_in_mb']),
parseInt(parameterValues['accounts:gmail_used_quota_in_mb']),
parseInt(parameterValues['accounts:total_quota_in_mb']),
((parseInt(parameterValues['accounts:gmail_used_quota_in_mb'])+parseInt(parameterValues['accounts:drive_used_quota_in_mb']))/parseInt(parameterValues['accounts:total_quota_in_mb']))*100
];
rows.push(row);
}
}
pageToken = page.nextPageToken;
} while (pageToken);
if (rows.length > 0) {
var spreadsheet = SpreadsheetApp.getActive();
var sheet = spreadsheet.getActiveSheet();
// Append the headers.
var headers = [['Date', 'User mail', 'Drive use','Gmail use', 'Total available',
'Total(%)']];
sheet.getRange(1, 1, 1, 6).setValues(headers);
// Append the results.
sheet.getRange(2, 1, rows.length, 6).setValues(rows);
Logger.log('Report spreadsheet created: %s', spreadsheet.getUrl());
} else {
Logger.log('No results returned.');
}
}
/**
* Gets a map of parameter names to values from an array of parameter objects.
* #param {Array} parameters An array of parameter objects.
* #return {Object} A map from parameter names to their values.
*/
function getParameterValues(parameters) {
return parameters.reduce(function(result, parameter) {
var name = parameter.name;
var value;
if (parameter.intValue !== undefined) {
value = parameter.intValue;
} else if (parameter.stringValue !== undefined) {
value = parameter.stringValue;
} else if (parameter.datetimeValue !== undefined) {
value = new Date(parameter.datetimeValue);
} else if (parameter.boolValue !== undefined) {
value = parameter.boolValue;
}
result[name] = value;
return result;
}, {});
}
The issue I have is that the parameters "accounts:drive_used_quota_in_mb" gives you the drive usage WITH the shared files (which is irrelevant to calculate the storage used by a user ( to determine whether he needs more space or not)).
I even tried to use 'accounts:used_quota_in_percentage' which seemed to be exactly what I need, but it calculate the percentage the same way i do : ((drive + mail)/total space)*100, and no way to ignore shared files to do so.
I'm working on the possibility to check every files of the drive, but you know the next problem : slowness.. (just for 1User with few docs, it take 1-2minutes)
Is there a way to do so by script, with another class, or something that is done for it in google that I didn't see ?
Thanks for your reading, forgive my english.
Ok, there is no issue, I just freaked out because a user was at 30Go consumption although he has his account for only 2month.
But after discussing with him, he did upload heavy files one week ago, and since, he deleted it.
And executing the script for only 2days ago gives the correct result, since he deleted these files between these two dates.
The reason of my mistake is that my script was providing stats that was 1 Week old (without me being conscious of that), and I was checking the veracity of theses stats on the web interface, that incidates nowadays stats.

How to filter out all emails that came from a mailing list in Gmail

Is there a way to filter out all emails that came from a mailing list within Gmail or Google Apps Script using a search query. I know you can filter out a specific email address using list:info#example.com. But I want a catch-all type of query or even a query to catch-all from a specific domain such as list:#example.com. However, this does not work. Any ideas? Any help is greatly appreciated, thank you!
This function will trash all messages from all inbox thread that are not in the list.
function emailFilter() {
var list=['a#company.com','b#company.com','c#company.com','d#company.com','e#company.com'];
var threads=GmailApp.getInboxThreads();
var token=null;
for(var i=0;i<threads.length;i++) {
if(threads[i].getMessageCount()) {
var messages=threads[i].getMessages();
for(var j=0;j<messages.length;j++) {
if(list.indexOf(messages[j].getFrom()==-1)) {
messages[j].moveToTrash();
}
}
}
}
}
I haven't tested it because I keep my inbox empty all of the time. You might want to replace 'moveToTrash()' to 'star()' for testing
What I could understand from your question and your comments, you need to filter the emails in a user's inbox that he has received, which don't only contain a certain label, but also a certain domain. If I understood well this code can help you:
function checkLabels() {
// Get the threads from the label you want
var label = GmailApp.getUserLabelByName("Label Test List");
var threadArr = label.getThreads();
// Init variable for later use
var emailDomain = '';
// Iterate over all the threads
for (var i = 0; i < threadArr.length; i++) {
// for each message in a thread, do something
threadArr[i].getMessages().forEach(function(message){
// Let's get the domains from the the users the messages were from
// example: list:#example.com -> Result: example.com
emailDomain = message.getFrom().split('<').pop().split('>')[0].split('#')[1];
// if emailDomain is equal to example.com, then do something
if(emailDomain === 'example.com'){
Logger.log(message.getFrom());
}
});
}
}
Using the Class GmailApp I got a certain label with the .getUserLabels() method and iterate through the threads thanks to the .getInboxThreads method. With a second loop and the .getMessages() you can get all the messages in a thread and for knowing the one who sent them, just use the .getFrom() method.
Docs
For more info check:
Gmail Service.
Class GmailMessage.
Class GmailThread.
So I was able to avoid replying to emails that come from a mailing list address by using the getRawContent() method and then searching that string for "Mailing-list:". So far the script is working like a charm.
function autoReply() {
var interval = 5; // if the script runs every 5 minutes; change otherwise
var date = new Date();
var day = date.getDay();
var hour = date.getHours();
var noReply = ["email1#example.com",
"email2#example.com"];
var replyMessage = "Hello!\n\nYou have reached me during non-business hours. I will respond by 9 AM next business day.\n\nIf you have any Compass.com related questions, check out Compass Academy! Learn about Compass' tools and get your questions answered at academy.compass.com.\n\nBest,\n\nShamir Wehbe";
var noReplyId = [];
if ([6,0].indexOf(day) > -1 || (hour < 9) || (hour >= 17)) {
var timeFrom = Math.floor(date.valueOf()/1000) - 60 * interval;
var threads = GmailApp.search('from:#example.com is:inbox after:' + timeFrom);
var label = GmailApp.getUserLabelByName("autoReplied");
var repliedThreads = GmailApp.search('label:autoReplied newer_than:4d');
// loop through emails from the last 4 days that have already been replied to
for (var i = 0; i < repliedThreads.length; i++) {
var repliedThreadsId = repliedThreads[i].getMessages()[0].getId();
noReplyId.push(repliedThreadsId);
}
for (var i = 0; i < threads.length; i++) {
var message = threads[i].getMessages()[0];
var messagesFrom = message.getFrom();
var email = messagesFrom.substring(messagesFrom.lastIndexOf("<") + 1, messagesFrom.lastIndexOf(">"));
var threadsId = message.getId();
var rawMessage = message.getRawContent();
var searchForList = rawMessage.search("Mailing-list:");
var searchForUnsubscribe = rawMessage.search("Unsubscribe now");
// if the message is unread, not on the no reply list, hasn't already been replied to, doesn't come from a mailing list, and not a marketing email then auto reply
if (message.isUnread() && noReply.indexOf(email) == -1 && noReplyId.indexOf(threadsId) == -1 && searchForList === -1 && searchForUnsubscribe === -1){
message.reply(replyMessage);
threads[i].addLabel(label);
}
}
}
}

Multiple calendar ID's in a google script

New to programming and I have been hitting my head against the wall when trying to add multiple calendar ID's to the script below.
I need it to iterate through the rows and if the event hasn't been added to the calendar yet, add it to mine and to others' calendars, as the sheet gets updated.
function addToCal() {
var ss = SpreadsheetApp.getActiveSpreadsheet(),
sheet = ss.getSheetByName("ProductionSchedule"),
range = sheet.getActiveRange(),
row = range.getRow(),
data = sheet.getRange(row, 1, 1, 26).getValues(),
date = data[0][6];
if (date =='') return;
var today = new Date(),
startDate = new Date(today.setMonth(today.getMonth()-1)),
endDate = new Date(today.setMonth(today.getMonth()+6)),
calId = 'calendarID',
cal = CalendarApp.getCalendarById(calId),
events = cal.getEvents(startDate, endDate),
titles = [];
for (var i = 0, len = events.length; i < len; i++)
titles.push(events[i].getTitle());
var item = ('Produção de' + ' ' + data[0][3]),
qtd = ('Qtd (und): ' + data[0][5]),
local = ('Local de Produção: ' + data[0][4]);
var index = titles.indexOf(item),
ok = 1;
if (index > -1)
{
var eventDate = events[index].getAllDayStartDate();
if (eventDate.getTime() == date.getTime()) var ok = 0;
else events[index].deleteEvent();
}
if (ok)
cal.createAllDayEvent(item, date, {description: qtd, location: local})
.removeAllReminders();
}
Currently, it sets the events to my calendar containing Item Description as the event title, Product name, qty and Production Location in the description field. I would need the same information to be added to others' calendars.
Besides, this can't mark me as Busy and the event doesn't need to be an All Day event.
Any help is appreciated.
Cheers,
In order to repeat the calendar interactions for multiple calendars, you need to create an array of calendar IDs and loop through them.
One approach would be:
var CALENDER_IDS = [
"calendarID1",
"calendarID2",
"calendarID3",
]
func addToCal() {
... spreadsheet data extraction and date calculations ...
for (var i = 0, clen = CALENDER_IDS.length; i < clen; i++) {
var cal = CalendarApp.getCalendarById(CALENDER_IDS[i]);
... calendar interactions ...
}
}
If you use the above code, make sure to update the counter variable in the title loop (i.e. they both can't be i). Common practice is to make inner loops use j, k, etc.