Console.Log and Discord Message displaying different results - random

I have a relatively basic set of code here that is programmed to roll 3 dice and give me the results whenever I type "/roll" in discord.
However, the results displayed on my command terminal are always different from those in the discord message from the bot.
async execute(interaction)
{
var num = 3;
num = Number(num);
function rollingDice(num) {
// make the dice rolling "num" times.
// return the results.
var diceResults = "";
for (var i = 0; i < num; i++) {
var resultOfEachDice = "";
resultOfEachDice = Math.floor((Math.random() * 6) + 1);
diceResults += resultOfEachDice + ", ";
}
var lastComma = diceResults.lastIndexOf(", ");
diceResults = diceResults.slice(0, lastComma);
return diceResults;
}
var diceResults = rollingDice()
console.log("Rolled " + num + " dice: " + rollingDice(num));
console.log(process.argv);
await interaction.reply('You rolled: ' + rollingDice(num));
So I will type /roll and my terminal will say I rolled "3, 5, 2" while the message would have something entirely different like "1, 6, 4".
This happens every time I run the command and I am not sure what the issue is.

Related

I want to compare the price of stock with stoploss and alert me by email

I want to compare my list of stocks price with my set Stoploss which is stored and once the condition trigger alert by email. Below is my code
function emailAlert()
{
var stock1nameRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Watchlist").getRange("A5");
var stock1name = stock1nameRange.getValues();
var stock1cmpRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Watchlist").getRange("B5");
var stock1cmp = stock1cmpRange.getValues();
var stock1s1Range = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Watchlist").getRange("AK5");
var s1 = stock1s1Range.getValues();
var stock1s2Range = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Watchlist").getRange("AL5");
var s2 = stock1s2Range.getValues();
var ui = SpreadsheetApp.getUi();
if(stock1cmp<s1)
{
if(stock1cmp<s2)
{
ui.alert( stock1name + stock1cmp + 'is less than' +s2 );
var message = stock1name + stock1cmp + 'is less than' +s2 ;
MailApp.sendEmail("#gmail.com", "Stock Watchlist alert", message)
return s2
}
else
{
ui.alert( stock1name + stock1cmp + 'is less than' +s1 );
var message = stock1name + stock1cmp + 'is less than' +s1 ;
MailApp.sendEmail("#gmail.com", "Stock Watchlist alert", message )
return s1
}
}
}
This is for single stock. How can I make it more generic and compile all the stock list which pass the condition into single mail. Thanks.
Sen
In order to be able to select all the values you will be needing a for loop.
Snippet
function emailAlert() {
var ui = SpreadsheetApp.getUi();
var sheet = SpreadsheetApp.getActive().getSheetByName("Watchlist");
var stockName = sheet.getRange("A5:FINAL_RANGE").getValues();
var stockCmp = sheet.getRange("B5:FINAL_RANGE").getValues();
var s1 = sheet.getRange("AK5:FINAL_RANGE").getValues();
var s2 = sheet.getRange("AL5:FINAL_RANGE").getValues();
for (var i = 0; i < stockName.length; i++) {
if (stockCmp[i][0] < s1[i][0]) {
if (stockCmp[i][0] < s2[i][0]) {
ui.alert(stockName[i][0] + stockCmp[i][0] + ' is less than ' + s2[i][0]);
var message = stockName[i][0] + stockCmp[i][0] + ' is less than ' + s2[i][0];
MailApp.sendEmail("#gmail.com", "Stock Watchlist Alert", message);
return s2[i][0];
} else {
ui.alert(stockName[i][0] + stockCmp[i][0] + ' is less than ' + s1[i][0]);
var message = stockName[i][0] + stockCmp[i][0] + ' is less than ' + s1[i][0];
MailApp.sendEmail("#gmail.com", "Stock Watchlist Alert", message);
return s1[i][0];
}
}
}
}
Explanation
The above code loops through all the values from your columns by using a for loop and then based on the conditions you set, is sending the email and alerting the user. The range is retrieved by using the getRange() method with the a1Notation parameter. The a1Notation parameter here is represented by the start and the end of the range in which you have the values you need for the script.
Note
The above script is built considering the fact that the stockName, stockCmp, s1, s2 are all associated, meaning that they all have the same number of values stored in them.
Reference
Apps Script Range Class - getValues();
Apps Script Sheet Class - getRange(a1Notation);
JavaScript For Loop.

Fastest way to search for a row in a large Google Sheet using/in Google Apps Script

GAS is quite powerful and you could write a full fledged web-app using a Google Sheet as the DB back-end. There are many reasons not to do this but I figure in some cases it is okay.
I think the biggest issue will be performance issues when looking for rows based on some criteria in a sheet with a lot of rows. I know there are many ways to "query" a sheet but I can't find reliable information on which is the fastest.
One of the complexities is that many people can edit a sheet which means there are a variable number of situations you'd have to account for. For the sake of simplicity, I want to assume the sheet:
Is locked down so only one person can see it
The first column has the row number (=row())
The most basic query is finding a row where a specific column equals some value.
Which method would be the fastest?
I have a sheet with ~19k rows and ~38 columns, filled with all sorts of unsorted real-world data. That is almost 700k rows so I figured it would be a good sheet to time a few methods and see which is the fastest.
method 1: get sheet as a 2D array then go through each row
method 2: get sheet as a 2D array, sort it, then using a binary search algorithm to find the row
method 3: make a UrlFetch call to Google visualization query and don't provide last row
method 4: make a UrlFetch call to Google visualization query and provide last row
Here are the my query functions.
function method1(spreadsheetID, sheetName, columnIndex, query)
{
// get the sheet values excluding header,
var rowValues = SpreadsheetApp.openById(spreadsheetID).getSheetByName(sheetName).getSheetValues(2, 1, -1, -1);
// loop through each row
for(var i = 0, numRows = rowValues.length; i < numRows; ++i)
{
// return it if found
if(rowValues[i][columnIndex] == query) return rowValues[i]
}
return false;
}
function method2(spreadsheetID, sheetName, columnIndex, query)
{
// get the sheet values excluding header
var rowValues = SpreadsheetApp.openById(spreadsheetID).getSheetByName(sheetName).getSheetValues(2, 1, -1, -1);
// sort it
rowValues.sort(function(a, b){
if(a[columnIndex] < b[columnIndex]) return -1;
if(a[columnIndex] > b[columnIndex]) return 1;
return 0;
});
// search using binary search
var foundRow = matrixBinarySearch(rowValues, columnIndex, query, 0, rowValues.length - 1);
// return if found
if(foundRow != -1)
{
return rowValues[foundRow];
}
return false;
}
function method3(spreadsheetID, sheetName, queryColumnLetterStart, queryColumnLetterEnd, queryColumnLetterSearch, query)
{
// SQL like query
myQuery = "SELECT * WHERE " + queryColumnLetterSearch + " = '" + query + "'";
// the query URL
// don't provide last row in range selection
var qvizURL = 'https://docs.google.com/spreadsheets/d/' + spreadsheetID + '/gviz/tq?tqx=out:json&headers=1&sheet=' + sheetName + '&range=' + queryColumnLetterStart + ":" + queryColumnLetterEnd + '&tq=' + encodeURIComponent(myQuery);
// fetch the data
var ret = UrlFetchApp.fetch(qvizURL, {headers: {Authorization: 'Bearer ' + ScriptApp.getOAuthToken()}}).getContentText();
// remove some crap from the return string
return JSON.parse(ret.replace("/*O_o*/", "").replace("google.visualization.Query.setResponse(", "").slice(0, -2));
}
function method4(spreadsheetID, sheetName, queryColumnLetterStart, queryColumnLetterEnd, queryColumnLetterSearch, query)
{
// find the last row in the sheet
var lastRow = SpreadsheetApp.openById(spreadsheetID).getSheetByName(sheetName).getLastRow();
// SQL like query
myQuery = "SELECT * WHERE " + queryColumnLetterSearch + " = '" + query + "'";
// the query URL
var qvizURL = 'https://docs.google.com/spreadsheets/d/' + spreadsheetID + '/gviz/tq?tqx=out:json&headers=1&sheet=' + sheetName + '&range=' + queryColumnLetterStart + "1:" + queryColumnLetterEnd + lastRow + '&tq=' + encodeURIComponent(myQuery);
// fetch the data
var ret = UrlFetchApp.fetch(qvizURL, {headers: {Authorization: 'Bearer ' + ScriptApp.getOAuthToken()}}).getContentText();
// remove some crap from the return string
return JSON.parse(ret.replace("/*O_o*/", "").replace("google.visualization.Query.setResponse(", "").slice(0, -2));
}
My binary search algorithm:
function matrixBinarySearch(matrix, columnIndex, query, firstIndex, lastIndex)
{
// find the value using binary search
// https://www.w3resource.com/javascript-exercises/javascript-array-exercise-18.php
// first make sure the query string is valid
// if it is less than the smallest value
// or larger than the largest value
// it is not valid
if(query < matrix[firstIndex][columnIndex] || query > matrix[lastIndex][columnIndex]) return -1;
// if its the first row
if(query == matrix[firstIndex][columnIndex]) return firstIndex;
// if its the last row
if(query == matrix[lastIndex][columnIndex]) return lastIndex;
// now start doing binary search
var middleIndex = Math.floor((lastIndex + firstIndex)/2);
while(matrix[middleIndex][columnIndex] != query && firstIndex < lastIndex)
{
if(query < matrix[middleIndex][columnIndex])
{
lastIndex = middleIndex - 1;
}
else if(query > matrix[middleIndex][columnIndex])
{
firstIndex = middleIndex + 1;
}
middleIndex = Math.floor((lastIndex + firstIndex)/2);
}
return matrix[middleIndex][columnIndex] == query ? middleIndex : -1;
}
This is the function I used to test them all:
// each time this function is called it will try one method
// the first time it is called it will try method1
// then method2, then method3, then method4
// after it does method4 it will start back at method1
// we will use script properties to save which method is next
// we also want to use the same query string for each batch so we'll save that in script properties too
function testIt()
{
// get the sheet where we're staving run times
var runTimesSheet = SpreadsheetApp.openById("...").getSheetByName("times");
// we want to see true speed tests and don't want server side caching so we a copy of our data sheet
// make a copy of our data sheet and get its ID
var tempSheetID = SpreadsheetApp.openById("...").copy("temp sheet").getId();
// get script properties
var scriptProperties = PropertiesService.getScriptProperties();
// the counter
var searchCounter = Number(scriptProperties.getProperty("searchCounter"));
// index of search list we want to query for
var searchListIndex = Number(scriptProperties.getProperty("searchListIndex"));
// if we're at 0 then we need to get the index of the query string
if(searchCounter == 0)
{
searchListIndex = Math.floor(Math.random() * searchList.length);
scriptProperties.setProperty("searchListIndex", searchListIndex);
}
// query string
var query = searchList[searchListIndex];
// save relevant data
var timerRow = ["method" + (searchCounter + 1), searchListIndex, query, 0, "", "", "", ""];
// run the appropriate method
switch(searchCounter)
{
case 0:
// start time
var start = (new Date()).getTime();
// run the query
var ret = method1(tempSheetID, "Extract", 1, query);
// end time
timerRow[3] = ((new Date()).getTime() - start) / 1000;
// if we found the row save its values in the timer output so we can confirm it was found
if(ret)
{
timerRow[4] = ret[0];
timerRow[5] = ret[1];
timerRow[6] = ret[2];
timerRow[7] = ret[3];
}
break;
case 1:
var start = (new Date()).getTime();
var ret = method2(tempSheetID, "Extract", 1, query);
timerRow[3] = ((new Date()).getTime() - start) / 1000;
if(ret)
{
timerRow[4] = ret[0];
timerRow[5] = ret[1];
timerRow[6] = ret[2];
timerRow[7] = ret[3];
}
break;
case 2:
var start = (new Date()).getTime();
var ret = method3(tempSheetID, "Extract", "A", "AL", "B", query);
timerRow[3] = ((new Date()).getTime() - start) / 1000;
if(ret.table.rows.length)
{
timerRow[4] = ret.table.rows[0].c[0].v;
timerRow[5] = ret.table.rows[0].c[1].v;
timerRow[6] = ret.table.rows[0].c[2].v;
timerRow[7] = ret.table.rows[0].c[3].v;
}
break;
case 3:
var start = (new Date()).getTime();
var ret = method3(tempSheetID, "Extract", "A", "AL", "B", query);
timerRow[3] = ((new Date()).getTime() - start) / 1000;
if(ret.table.rows.length)
{
timerRow[4] = ret.table.rows[0].c[0].v;
timerRow[5] = ret.table.rows[0].c[1].v;
timerRow[6] = ret.table.rows[0].c[2].v;
timerRow[7] = ret.table.rows[0].c[3].v;
}
break;
}
// delete the temp file
DriveApp.getFileById(tempSheetID).setTrashed(true);
// save run times
runTimesSheet.appendRow(timerRow);
// start back at 0 if we're the end
if(++searchCounter == 4) searchCounter = 0;
// save the search counter
scriptProperties.setProperty("searchCounter", searchCounter);
}
I have a global variable searchList that is an array of various query strings -- some are in the sheet, some are not.
I ran testit on a trigger to run every minute. After 152 iterations I had 38 batches. Looking at the result, this is what I see for each method:
| Method | Minimum Seconds | Maximum Seconds | Average Seconds |
|---------|-----------------|-----------------|-----------------|
| method1 | 8.24 | 36.94 | 11.86 |
| method2 | 9.93 | 23.38 | 14.09 |
| method3 | 1.92 | 5.48 | 3.06 |
| method4 | 2.20 | 11.14 | 3.36 |
So it appears that, at least for my data-set, is using Google visualization query is the fastest.

Adding delay in Processing

so I am new to Processing and basically I am doing a program that when it runs, it opens a window with 4 different images, each of the image have description underneath. In the methods below, I created two random methods, one for the reviews number and the other for the review comments, I would like the comments to not be generated all the time for every film - more like popping up randomly, because it cause too much chaos trying to read them all. Also to check weather I can add arrays together of string and integer for the average value of the review number.
Below is the code, would appreciate your help. thanks.
import g4p_controls.*;
import ddf.minim.*;
PFont font;
PImage img1,img2; // background images for two different windows
PImage fimg1, fimg2, fimg3, fimg4, fimg5, fimg6; //images of movies
int rectX,rectY;
GButton btn;
GWindow window;
Minim minim;
AudioPlayer player;
String[] rev_film1 = {"The Best Wolverine Movie","Logan is another level","Disappointment","Such a sad farewell"}; //Logan
String[] rev_film2 = {"A scary movie that isn't scary.","Terrifyingly brilliant.","The perfect blend of comedy and horror","The IT Factor"}; //IT
String[] rev_film3 = {"Soul-less,Confused,Loud.","Devastatingly Disappointed","A technical masterpiece","A visual wonder that lacks depth"}; //Dunkirk
String[] rev_film4 = {"Disgrace", "Worst Star Wars movie", "TERRIBLE","A Betrayal to the Legacy"}; //Starwars
int[] rat_film1 = {9,8,2,2};
float r;
void setup()
{
size(1150,600,JAVA2D);
surface.setTitle(" - The Theatre of Dreams Database - ");
font = loadFont("BerlinSansFB-Reg-48.vlw");
img1 = loadImage("film2.jpg");
background(img1);
btn = new GButton(this,900,350,100,50, "Enter Website");
minim = new Minim(this);
player = minim.loadFile("sound.mp3");
player.play();
}
void draw()
{
fill(255);
textFont(font,32);
text("Welcome to", 850, 85);
text("The Theatre of Dreams", 760, 175);
text("Database", 870, 220);
}
void handleButtonEvents(GButton button, GEvent event)
{
if (button == btn && event == GEvent.CLICKED)
{
createWindow();
btn.setEnabled(false);
}
}
void createWindow() // creating new window with mouse click
{
window = GWindow.getWindow(this, " - Top 4 Movies in 2017 - ", 100, 50, 1150, 600, JAVA2D);
window.addDrawHandler(this, "windowDraw");
window.addOnCloseHandler(this, "windowClosing");
window.setActionOnClose(GWindow.CLOSE_WINDOW);
}
void windowDraw(PApplet app, GWinData data)
{
img2 = loadImage("film3.jpg");
app.background(img2);
app.text(" - Top 4 Movies in 2017 - ",440,85);
app.fill(255);
app.textFont(font,25);
fimg1 = loadImage("logan.jpg");
fimg2 = loadImage("it.jpg");
fimg3 = loadImage("dunkirk.jpg");
fimg4 = loadImage("starwars.jpg");
//////////Film 1 - LOGAN
app.image(fimg1,5,140,180,170);
app.text("Rating: 8.1 / 10",5,340); //fixed rating
app.text("Genres: Action | Drama", 5, 365);
app.text("| Sci-Fi | Thriller",5,390);
//Ratings that are constantly changing using the random function
for (int i = 0; i < 50; i++)
{
r = random(0, 6);
}
String user = "Ratings by users: " + nf(r,0,1) + " / 10";
app.text(user, 5,430);
// the random function of the comments
int index = int(random(rev_film1.length));
String user11 = "Reviews: " + "\n" + rev_film1[index];
app.text(user11, 5,460);
////////////////////Film 2 - IT
app.image(fimg2,960,360,180,170);
app.text("Rating: 7.6 / 10", 700,400);
app.text("Genres: Drama | Horror",700,430);
app.text("| Thriller",700,460);
//Ratings that are constantly changing using the random function
for (int i = 0; i < 50; i++)
{
r = random(5, 10);
}
String user2 = "Ratings by users: " + nf(r,0,1) + " / 10";
app.text(user2, 700,500);
int index2 = int(random(rev_film2.length)); // the random function of the comments
String user22 = "Reviews: " + "\n" + rev_film2[index2];
app.text(user22, 700,540);
/////////Film 3 - DUNKIRK
app.image(fimg3,320,250,180,170);
app.text("Rating: 8.1 / 10",320,445); //fixed rating
app.text("Genres: Action | Drama", 320, 470);
app.text("| History | Thriller | War",320,495);
//Ratings that are constantly changing using the random function
for (int i = 0; i < 50; i++)
{
r = random(0, 5);
}
String user3 = "Ratings by users: " + nf(r,0,1) + " / 10";
app.text(user3, 320,530);
int index3 = int(random(rev_film3.length)); // the random function of the comments
String user33 = "Reviews: " + "\n" + rev_film3[index3];
app.text(user33, 320,560);
/////////////Film 4 - STAR WARS
app.image(fimg4,570,120,180,170);
app.text("Rating: 7.6 / 10", 760,140); //fixed rating
app.text("Genres: Action | Adventure | Fantasy ", 760,168);
app.text("| Sci-Fi", 760,195);
//Ratings that are constantly changing using the random function
for (int i = 0; i < 50; i++)
{
r = random(0, 2);
}
String user4 = "Ratings by users: " + nf(r,0,1) + " / 10";
app.text(user4, 760,220);
int index4 = int(random(rev_film4.length)); // the random function of the comments
String user44 = "Reviews: " + "\n" + rev_film4[index4];
app.text(user44, 760,250);
}
public void windowClosing(GWindow w)
{
btn.setEnabled(false);
player.close();
minim.stop();
exit();
}
Please try to post a MCVE instead of your full program. For example, try creating a simple sketch that shows a circle every X seconds. That way we can focus on your problem instead of all the extra stuff that has nothing to do with your question.
But to answer your question, you can use the millis() function or the frameCount variable to check how much time has gone by, then do something every X seconds or every X frames.
Related posts:
How to make a delay in processing project?
How can I draw only every x frames?
Removing element from ArrayList every 500 frames
Timing based events in Processing
How to add +1 to variable every 10 seconds in Processing?
How to create something happen when time = x
making a “poke back” program in processing
Processing: How do i create an object every “x” time
Timer using frameRate and frame counter reliable?
Please also consult the Processing reference for more information.
If you still can't get it working, please post a MCVE (not your full project!) in a new question and we'll go from there. Good luck.

Bing custom search apis returning only limited results from one location and full result from different location

I am trying to use Bing Custom Search's API for documents from Cognitive Services. The strange thing is that when I run it from India, it gives me more than a thousand results, but when I run it from a US server, it returns only 25 (sometimes 50 results). Here is the sample code for that:
var totalCount = 0;
var filetypes = new List<string> { "pdf", "docx", "doc" };
foreach (var filetype in filetypes)
{
var searchTerm = "microsoft%20.net%20resume+filetype%3a" + filetype;
Console.WriteLine("Searching for : " + filetype);
for (var i = 0; i < 40; i++)
{
var nextCount = 0;
var url = "https://api.cognitive.microsoft.com/bingcustomsearch/v7.0/search?" +
"q=" + searchTerm +
"&customconfig=" + customConfigId +
"&count=25" + "&offset=" + ((i * 25) + nextCount);
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", subscriptionKey);
var httpResponseMessage = client.GetAsync(url).Result;
var responseContent = httpResponseMessage.Content.ReadAsStringAsync().Result;
BingCustomSearchResponse response =
JsonConvert.DeserializeObject<BingCustomSearchResponse>(responseContent);
if (response.webPages == null || response.webPages.value.Length <= 0)
{
Console.WriteLine("response.webPages is null ");
break;
}
foreach (var webPage in response.webPages.value)
{
Console.WriteLine("name: " + webPage.name);
Console.WriteLine("url: " + webPage.url);
Console.WriteLine("displayUrl: " + webPage.displayUrl);
Console.WriteLine("snippet: " + webPage.snippet);
Console.WriteLine("dateLastCrawled: " + webPage.dateLastCrawled);
Console.WriteLine();
}
totalCount = totalCount + response.webPages.value.Length;
}
}
}
The subscription key I am using is a trial key.
I got the reason of this behavior. Actually it had nothing to do with region/country/market.
After looking into the response i got this message.
"Rate limit is exceeded. Try again in 1 seconds"
It means for after each call in the loop i have to wait for 1 second to give next call. Now need to know is this limit for trial subscription or this is kept for all calls to prevent DDOS attack or something.
May be from India it was working because may one iteraction is already taking one or more second.
Two things you can try: 1) In searchTerm, no need to use %20 and %3a, just use punctuations as you type in Bing, e.g. var searchTerm = "microsoft.net resume filetype::"+filetype, and 2) Enforce market by appending mkt=en-in (for India) or en-us (for US) in the query. You can do this by appending +"&mkt=en-in" at the end of url.
I presume for custom search you have selected domains (for both en-in and en-us markets) that return thousands of results for this query.

Picking up where the script ended when time limit is reached

I'm trying to implement the solution that Anton Soradoi proposed on 12/22/11 to enable a mail merge script that will exceed its maximum execution time terminate gracefully, wait a period of time, then pick up where it left off and continue doing so until its run is complete. Here is the link to the referenced post.
The script runs fine for the aloted time (5 min), then throws a "Execution failed: Invalid argument: value (line 80). Also, I'm not sure what the "else" part of the script that Anton Soradoi discussed is supposed to do (run my menuItem1 function again?). I feel like I'm pretty close, and any help would be greatly appreciated. My code is below:
//Creates the custom menu in the spreadsheet "Run Script"
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Script')
.addItem('Create Certs', 'menuItem1')
.addToUi();
}//Ends the custom menu in the spreadsheet
//Runs the menuItem 1 operation (Create Certs)
function menuItem1() {
//Defines the start row and calculates the number of rows to be processed
var sheet = SpreadsheetApp.getActiveSheet();
var startRow = Browser.inputBox("Enter Start Row");
var endRow = Browser.inputBox("Enter End Row");
var numRows = (endRow - startRow) + 1; //this is the row height
var dataRange = sheet.getRange(startRow, 1, numRows, 7);
var counter =0;
var data = dataRange.getValues();
var templateDoc = DriveApp.getFileById("1baxSUxfSdzcVheR3Y2qgieWeSAqNybPfWct1913uRIc");
var templateCopy = templateDoc.makeCopy();
var templateCopyId = templateCopy.getId();
var dateOld;
var courseOld;
var fullNameOld;
var mailFrom = GmailApp.getAliases()
var team = "NWC Online PME Help Desk"
var startTime= (new Date()).getTime();
for (var i = 0; i < data.length; ++i) {
var doc = DocumentApp.openById(templateCopyId);
var body = doc.getActiveSection();
var row = data[i];
var date = row[0];
var nic = row[1];
var course = row[2];
var lastname = row[3];
var firstname = row[4];
var middle = row[5]
var email = row[6];
var subjectTxt = "NWC Online PME Course Certificate";
var fullBody = "PME COURSE COMPLETION CERTIFICATE" + "\n\n";
fullBody += "Your course completion certificate is attached." + "\n\n";
fullBody += "NOTES:" + "\n";
fullBody += "1. DO NOT telephone NWC to resolve PME certificate issues, email our Help Desk: pmecerthelp#usnwc.edu." + "\n";
fullBody += "2. NWC does NOT mail hardcopy certificates." + "\n";
fullBody += "3. NWC does not award certificates for the SNCO JPME courses." + "\n";
fullBody += "4. NWC course completion certificates are not automatically entered into your electronic training or service records." + "\n\n";
fullBody += "Regards," + "\n\n";
fullBody += "U.S. Naval War College Online PME Program Team"+ "\n\n";
fullBody += "Learn more about NWC's Online PME Program at the link below:" + "\n";
fullBody += "http://www.usnwc.edu/Academics/College-of-Distance-Education/PME-(1).aspx" + "\n";
var fullName = firstname+' '+middle+''+lastname
var fdate = Utilities.formatDate(new Date(date), "UTC", "d MMMM yyyy"); //converts UTC date
if(counter ==0){
body.replaceText('fullName',fullName);
body.replaceText('course', course);
body.replaceText('date', fdate);
}//Ends the if condition
else {
body.replaceText(fullNameOld,fullName);
body.replaceText(courseOld, course);
body.replaceText(dateOld, fdate);
}//Ends the else condition
dateOld = fdate;
courseOld = course;
fullNameOld = fullName;
counter ++
doc.saveAndClose();
var attachment = doc.getAs('application/pdf');
GmailApp.sendEmail(email, subjectTxt, fullBody, {name: team, attachments: attachment, from: mailFrom[1]});
var scriptProperties = PropertiesService.getScriptProperties();
var newStartRow= scriptProperties.getProperty('row');
for(var ii = newStartRow; ii <= data.length; ii++) {
var currTime = (new Date()).getTime();
if(currTime - startTime >= 300000) {
scriptProperties.setProperty("row", ii);
ScriptApp.newTrigger("menuItem1")
.timeBased()
.at(new Date(currTime+30000))
.create();
break;
}//Ends the if loop
}//Ends the second for loop
}//Ends the first for loop
}//Ends menuItem1
The below code should do the trick, you can not use the code as is. Please read below:
//Creates the custom menu in the spreadsheet "Run Script"
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Script')
.addItem('Create Certs', 'menuItem1')
.addToUi();
}//Ends the custom menu in the spreadsheet
//Runs the menuItem 1 operation (Create Certs)
function menuItem1() {
//Defines the start row and calculates the number of rows to be processed
var sheet = SpreadsheetApp.getActiveSheet();
var scriptProperties = PropertiesService.getScriptProperties();
var startRow= scriptProperties.getProperty('StartRow');
var endRow = scriptProperties.getProperty('EndRow');
// Check to see if any property called startRow is present
if (startRow == null || endRow == null){ //If not present ask for the values from user
startRow = Number(Browser.inputBox("Enter Start Row"));
endRow = Number(Browser.inputBox("Enter End Row"));
scriptProperties.setProperty("EndRow", endRow)
} else { // if present ues those values for this run
startRow = Number(startRow) // Convert String to numbers
endRow = Number(endRow)
}
var numRows = (endRow - startRow) + 1; //this is the row height
var dataRange = sheet.getRange(startRow, 1, numRows, 7);
var counter =0;
var data = dataRange.getValues();
var templateDoc = DriveApp.getFileById("1baxSUxfSdzcVheR3Y2qgieWeSAqNybPfWct1913uRIc");
var templateCopy = templateDoc.makeCopy();
var templateCopyId = templateCopy.getId();
var dateOld;
var courseOld;
var fullNameOld;
var mailFrom = GmailApp.getAliases()
var team = "NWC Online PME Help Desk"
var startTime= (new Date()).getTime(); //set Start time
for (var i = 0; i < data.length; ++i) {
var doc = DocumentApp.openById(templateCopyId);
var body = doc.getActiveSection();
var row = data[i];
var date = row[0];
var nic = row[1];
var course = row[2];
var lastname = row[3];
var firstname = row[4];
var middle = row[5]
var email = row[6];
var subjectTxt = "NWC Online PME Course Certificate";
var fullBody = "PME COURSE COMPLETION CERTIFICATE" + "\n\n";
fullBody += "Your course completion certificate is attached." + "\n\n";
fullBody += "NOTES:" + "\n";
fullBody += "1. DO NOT telephone NWC to resolve PME certificate issues, email our Help Desk: pmecerthelp#usnwc.edu." + "\n";
fullBody += "2. NWC does NOT mail hardcopy certificates." + "\n";
fullBody += "3. NWC does not award certificates for the SNCO JPME courses." + "\n";
fullBody += "4. NWC course completion certificates are not automatically entered into your electronic training or service records." + "\n\n";
fullBody += "Regards," + "\n\n";
fullBody += "U.S. Naval War College Online PME Program Team"+ "\n\n";
fullBody += "Learn more about NWC's Online PME Program at the link below:" + "\n";
fullBody += "http://www.usnwc.edu/Academics/College-of-Distance-Education/PME-(1).aspx" + "\n";
var fullName = firstname+' '+middle+''+lastname
var fdate = Utilities.formatDate(new Date(date), "UTC", "d MMMM yyyy"); //converts UTC date
if(counter ==0){
body.replaceText('fullName',fullName);
body.replaceText('course', course);
body.replaceText('date', fdate);
}//Ends the if condition
else {
body.replaceText(fullNameOld,fullName);
body.replaceText(courseOld, course);
body.replaceText(dateOld, fdate);
}//Ends the else condition
dateOld = fdate;
courseOld = course;
fullNameOld = fullName;
counter ++
doc.saveAndClose();
var attachment = doc.getAs('application/pdf');
//GmailApp.sendEmail(email, subjectTxt, fullBody, {name: team, attachments: attachment, from: mailFrom[1]});
Utilities.sleep(30000)
var currTime = (new Date()).getTime();
if(currTime - startTime >= 240000) { //Check if the script run is over 4minutes , at 5 min the excution might as well might have been terminated
scriptProperties.setProperty("StartRow", startRow + i+1); //The new start, just number of iteration done plus 1 to start from row after that
ScriptApp.newTrigger("menuItem1")
.timeBased()
.at(new Date(currTime+30000)) //restart in 30 secs!
.create();
Logger.log("Last email sent to: " + email)
Logger.log("Next Run start at row: " + startRow + i+1)
return; // End current run.
}//Ends the if Block
}//Ends the first for loop
}//Ends menu
The way this works is to use the scriptProperties to store your lastRow that was processed and your endRow. If it cannot find those values it will ask the user to enter it!
var scriptProperties = PropertiesService.getScriptProperties();
var startRow= scriptProperties.getProperty('StartRow');
var endRow = scriptProperties.getProperty('EndRow');
// Check to see if any property called startRow is present
if (startRow == null || endRow == null){ //If not present ask for the values from user
startRow = Browser.inputBox("Enter Start Row");
endRow = Browser.inputBox("Enter End Row");
scriptProperties.setProperty("EndRow", endRow)
} else { // if present ues those values for this run
startRow = Number(startRow) // Convert String to numbers
endRow = Number(endRow)
}
The below code will check to see if it has gone past the 4-minute mark if so, set up a time trigger and modify the startrow property to the new row after the last processed one. Then exit the function using return.
var currTime = (new Date()).getTime();
if(currTime - startTime >= 240000) { //Check if the script run is over 4minutes , at 5 min the excution might as well might have been terminated
scriptProperties.setProperty("StartRow", startRow + i+1); //The new start, just number of iteration done plus 1 to start from row after that
ScriptApp.newTrigger("menuItem1")
.timeBased()
.at(new Date(currTime+30000)) //restart in 30 secs!
.create();
Logger.log("Last email sent to: " + email)
Logger.log("Next Run start at row: " + startRow + i+1)
return; // End current run.
}//Ends the if Block
Debugging:
You will notice these lines near your GmailApp Lines:
//GmailApp.sendEmail(email, subjectTxt, fullBody, {name: team, attachments: attachment, from: mailFrom[1]});
Utilities.sleep(30000)
Commented out your GamilApp line and instead put a sleep for 30 seconds, this is will help test the code. Once you run the code find the execution transcript under "View". And find these values:
Logger.log("Last email sent to: " + email)
Logger.log("Next Run start at row: " + startRow + i+1)
That way you can manually match the last email sent and next startRow to make sure everything works fine before you go live.
Edit
Finally, to reset your script properties if you run into any issues, run the below function.
function resetScript(){
var scriptProperties = PropertiesService.getScriptProperties();
Logger.log(scriptProperties.getProperties())
scriptProperties.deleteAllProperties()
}
Hope that helps
Working with Jack Brown for most of the day, here is the final answer:
//Creates the custom menu in the spreadsheet "Run Script"
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Script')
.addItem('Create Certs', 'menuItem1')
.addToUi();
}//Ends the custom menu in the spreadsheet
//Runs the menuItem 1 operation (Create Certs)
function menuItem1() { //Defines the start row and calculates the number of rows to be processed
var sheet = SpreadsheetApp.getActiveSheet();
var scriptProperties = PropertiesService.getScriptProperties(); // starts the Script Properties service for storing the last row completed
var startRow= scriptProperties.getProperty('StartRow');
var endRow = scriptProperties.getProperty('EndRow');
// Check to see if any property called startRow is present
if (startRow == null || endRow == null){ //If not present ask for the startRow and endRow values from user
startRow = Number(Browser.inputBox("Enter Start Row"));
endRow = Number(Browser.inputBox("Enter End Row"));
scriptProperties.setProperty("EndRow", endRow)
}// ends the if condition
else { // if present ues those values for this run
startRow = Number(startRow) // Convert startRow string to a number
endRow = Number(endRow) // Convert endRow string to a number
} //end the else condition
var numRows = (endRow - startRow) + 1; //this is the row height
if(numRows < 1){
scriptProperties.deleteAllProperties()
return;
} //ends the if condition
var dataRange = sheet.getRange(startRow, 1, numRows, 7);
var counter =0;
var data = dataRange.getValues();
var templateDoc = DriveApp.getFileById("1baxSUxfSdzcVheR3Y2qgieWeSAqNybPfWct1913uRIc");
var templateCopy = templateDoc.makeCopy();
var templateCopyId = templateCopy.getId();
var dateOld;
var courseOld;
var fullNameOld;
var mailFrom = GmailApp.getAliases() //gets the alias address the email is sent from
var team = "NWC Online PME Help Desk"
var startTime= (new Date()).getTime(); //sets the script Start time
for (var i = 0; i < data.length; ++i) { // Populates the certificate with row data and builds the custom email
var doc = DocumentApp.openById(templateCopyId);
var body = doc.getActiveSection();
var row = data[i]; // specifies the active row [i]
var date = row[0]; // date on the cert for row [i] (column 0)
var nic = row[1]; // course nickname for row [i] (column 1)
var course = row[2]; // course name for row [i] (column 2)
var lastname = row[3]; // learner's last name for row [i] (column 3)
var firstname = row[4]; // learner's first name for row [i] (column 4)
var middle = row[5]; // learner's middle initial for row [i] (column 5)
var email = row[6]; // learner's email address for row [i] (column 6)
var subjectTxt = "NWC Online PME Course Certificate"; // email's subject line
var fullBody = "PME COURSE COMPLETION CERTIFICATE" + "\n\n"; //email's header
fullBody += "Your course completion certificate is attached." + "\n\n";
fullBody += "NOTES:" + "\n";
fullBody += "1. DO NOT telephone NWC to resolve PME certificate issues, email our Help Desk: pmecerthelp#usnwc.edu." + "\n";
fullBody += "2. NWC does NOT mail hardcopy certificates." + "\n";
fullBody += "3. NWC does not award certificates for the SNCO JPME courses." + "\n";
fullBody += "4. NWC course completion certificates are not automatically entered into your electronic training or service records." + "\n\n";
fullBody += "Regards," + "\n\n";
fullBody += "U.S. Naval War College Online PME Program Team"+ "\n\n";
fullBody += "Learn more about NWC's Online PME Program at the link below:" + "\n";
fullBody += "http://www.usnwc.edu/Academics/College-of-Distance-Education/PME-(1).aspx" + "\n";
var fullName = firstname+' '+middle+''+lastname
var fdate = Utilities.formatDate(new Date(date), "UTC", "d MMMM yyyy"); //converts the UTC date to the desired format
if(counter == 0){
body.replaceText('fullName',fullName);
body.replaceText('course', course);
body.replaceText('date', fdate);
}//Ends the if condition
else {
body.replaceText(fullNameOld,fullName);
body.replaceText(courseOld, course);
body.replaceText(dateOld, fdate);
}//Ends the else condition
dateOld = fdate;
courseOld = course;
fullNameOld = fullName;
counter ++
doc.saveAndClose(); //creates the learner's certificate
var attachment = doc.getAs('application/pdf'); // converts the learner's certificate from a doc file to a PDF
GmailApp.sendEmail(email, subjectTxt, fullBody, {name: team, attachments: attachment, from: mailFrom[1]}); // sends the learner's certificate as an attachment to the email
// Utilities.sleep(2500) // When sleeping the GmailApp line, enables testing of the script without impact on Gmail's email quota
sheet.getRange(startRow + i , 8).setValue("Done at: "+ new Date()) // puts the sent date/time in column 8 of the spreadsheet
var currTime = (new Date()).getTime(); //gets the current date/time for the script to determine run time
if(currTime - startTime >= 240000) { //Checks if the script run is over 4 minutes
clearTriggers() //clears the script's triggers
scriptProperties.setProperty("StartRow", startRow + i+1); // The new startRow, just number of iterations done + 1 to start with the next row
ScriptApp.newTrigger("menuItem1")
.timeBased()
.at(new Date(currTime+60000)) //restarts the script in 60 sec to finish cleanly
.create();
Logger.log("Last email sent to: " + email)
Logger.log("Next Run start at row: " + (startRow + i+1))
return; // Ends the current run.
}//Ends the if condition
}//Ends the first for loop
// Logger.log("This shouldnt run") was used in testing, not needed now.
DriveApp.getFileById(templateCopyId).setTrashed(true); // deletes the working copy of the document template
clearTriggers() // function defined in Reset Script
resetScript() // function defined in Reset Script
}//Ends menuItem1
//Comments
//Jagannathan
//Ok this seems to work, also note i modified this:
//.at(new Date(currTime+60000))
//To run after minute, the trigger are not sensititve to 30 sec, so they where not triggering properply.
//Again copy this as is run it your spreadsheet when you get a chance.
//Once you are satisfied with the mock run, remove the comment form Gmail and comment sleep and run it
//Leave the sheet.getRange() line to so that you keep on eye on how it triggers. And in case it fails you know where to start from.
And here is the additional script file that defines resetScript() and clearTriggers()
function resetScript(){
var scriptProperties = PropertiesService.getScriptProperties();
Logger.log(scriptProperties.getProperties())
scriptProperties.deleteAllProperties()
}
function clearTriggers(){
var triggers = ScriptApp.getUserTriggers(SpreadsheetApp.getActive())
for (var i =0 ; i< triggers.length ; i++)
{
if(triggers[i].getHandlerFunction() == "menuItem1") {
ScriptApp.deleteTrigger(triggers[i])
}
}
}

Resources