Find Google Meet specific startTime to take attendance - google-apps-script

I was trying to use "Google Apps Script Quickstart" to take attendance on Meet. This works well, but it does not identify the specific range for the startTime or endTime of a Meet, so I added the startTime.
With much support from stackoverflow users, the code developed allows teachers and other education staff to take attendance for a meet session within a specific time frame for a specific set of Meet codes.
Chrome extensions require adding things to your computer, demanding time to administer the data that is obtained, and can only be accessed for the purpose of taking attendance during the actual session.
var emailAddress = sheet[i+1][1]
var optionalArgs = {
startTime: formatStartDate(),
event_name: "call_ended",
filters: "identifier==" + emailAddress + ",meeting_code==" + meetCode
};
function formatStartDate(){
let date = new Date();
const offset = date.getTimezoneOffset();
let offsetDate = new Date(date.getTime() - (offset*60*1000));
return offsetDate.toISOString();
}
...

The full "Google Apps Script Quickstart" code plus the data above looks like this:
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Asistencia')
.addItem("Cursos", 'importCourses')
.addItem('Asistencia', 'checkAll')
.addToUi();
}
function importCourses() {
var optionalArgs = {
teacherId: 'me',
pageSize: 5
};
var response = Classroom.Courses.list(optionalArgs);
var courses = response.courses;
for (var i = 0; i < 2; i++) {
var courseName = courses[i].name;
var courseId = courses[i].id;
insertCourse(courseName, courseId)
}
}
function insertCourse(courseName, courseId) {
var spreadsheetName = courseName + "(" + courseId + ")"
var activeSpreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var yourNewSheet = activeSpreadsheet.getSheetByName(spreadsheetName);
if (yourNewSheet != null) {
return
}
yourNewSheet = activeSpreadsheet.insertSheet();
yourNewSheet.setName(spreadsheetName);
yourNewSheet.appendRow(['Nombre', 'Email', 'Asistencia'])
yourNewSheet.setFrozenRows(1)
var studentNames = getRoster(courseId)["studentNames"]
var studentEmails = getRoster(courseId)["studentEmails"]
for (var i = 0; i < studentNames.length; i++) {
yourNewSheet.appendRow([studentNames[i],studentEmails[i]])
}
yourNewSheet.autoResizeColumns(1, 2)
yourNewSheet.setFrozenColumns(2)
}
function getRoster(courseId) {
var studentNames = []
var studentEmails = []
var optionalArgs = {
pageSize: 100
};
var response = Classroom.Courses.Students.list(courseId, optionalArgs)
var students = response.students
for (var i = 0; i < 100; i++) {
try {
studentNames.push(students[i].profile.name.fullName)
studentEmails.push(students[i].profile.emailAddress)
} catch (err) {
return { "studentNames":studentNames, "studentEmails":studentEmails }
}
}
}
function checkAll() {
var ss = SpreadsheetApp.getActiveSheet();
var sheet = ss.getDataRange().getValues();
for (var i = 2; i < sheet.length * 100; i++){
var meetCode = getCleanCode(sheet[0][i])
if (meetCode == null) {
break;
}
else {
checkMeet(meetCode, i+1);
}
}
}
function checkMeet(meetCode, index) {
var userKey = 'all';
var applicationName = 'meet';
var ss = SpreadsheetApp.getActiveSheet();
var sheet = ss.getDataRange().getValues();
var DateValue = ss.getRange(1, 1).getValues();
for (var i = 0; i < sheet.length-1; i++) {
var emailAddress = sheet[i+1][1]
var optionalArgs = {
startTime: formatStartDate(),
event_name: "call_ended",
filters: "identifier==" + emailAddress + ",meeting_code==" + meetCode
};
try {
var response = AdminReports.Activities.list(userKey, applicationName,
optionalArgs);
var activities = response.items;
if (activities == undefined) {
markAbsent(ss,i+2,index)
}
else {
markPresent(ss,i+2,index)
}
} catch (err) {
continue
}
}
}
function getCleanCode(meetCode) {
try{
return meetCode.replace(/-/g, "");
} catch (err) { return meetCode; }
}
function markAbsent(sheet, i, j) {
var cell = sheet.getRange(i, j);
cell.setValue("A");
}
function markPresent(sheet, i, j) {
var cell = sheet.getRange(i, j);
cell.setValue("");
}
function formatStartDate(){
let date = new Date();
const offset = date.getTimezoneOffset();
let offsetDate = new Date(date.getTime() - (offset*145*1000));
return offsetDate.toISOString();
}

Related

Google Sheet : The result could not be auto-expanded. Please insert new columns (1)

This script import opportunities that we get. It populates rows which then calculates some numbers such as interest and stuff. However, I now have errors when populating my cells (#REF error, see pictures below). Nothing has changed so my first guess is it has do with the processing of the sheet which has become too heavy.
`
function onInstall(e) {
onOpen(e);
}
function onOpen(e) {
var menu = SpreadsheetApp.getUi().createAddonMenu();
menu.addItem('Créer étude', 'init');
menu.addToUi();
}
function init() {
ss = SpreadsheetApp.getActiveSpreadsheet();
identifyJe();
sheetMaker = ss.getActiveSheet();
currentRow = ss.getActiveCell().getRowIndex();
ssEtudes = SpreadsheetApp.openById(getSheetEtudesId());
var ui = SpreadsheetApp.getUi();
var respon se = ui.alert(
'Ligne sélectionnée : ' + currentRow + '. Exécuter ?',
ui.ButtonSet.OK_CANCEL
);
if (response !== ui.Button.OK) return;
doEtude();
doContrat();
doPhases();
}
function doEtude(nomEtude) {
var etudeData = [];
for (var i = 0; i < etudeNamedRanges.length; i++) {
var column = ss.getRangeByName(etudeNamedRanges[i]).getColumn();
etudeData.push(sheetMaker.getRange(currentRow, column).getValue());
}
var destSheet = ssEtudes.getSheetByName('etudes');
var rowToAppend = getFirstEmptyRow(destSheet, 0);
for (var i = 0; i < etudeData.length; i++) {
var column = ssEtudes.getRangeByName(etudeNamedRanges[i]).getColumn();
destSheet.getRange(rowToAppend, column).setValue(etudeData[i]);
}
}
function doContrat() {
var contratData = [];
for (var i = 0; i < contratNamedRanges.length; i++) {
var column = ss.getRangeByName(contratNamedRanges[i]).getColumn();
contratData.push(sheetMaker.getRange(currentRow, column).getValue());
}
var destSheet = ssEtudes.getSheetByName('contrats');
var rowToAppend = getFirstEmptyRow(destSheet, 11);
for (var i = 0; i < contratData.length; i++) {
destSheet.getRange(rowToAppend, i + 4).setValue(contratData[i]);
}
}
function doPhases() {
var phasesCoords = getPhasesCoords();
var phasesData = [];
for (var i = 0; i < phasesCoords.length; i++) {
var phaseData = getPhaseData(phasesCoords[i]);
phaseData.unshift(i + 1);
phasesData.push(phaseData);
}
var destSheet = ssEtudes.getSheetByName('phases');
var rowToAppend = getFirstEmptyRow(destSheet, 9);
var columns = [];
for (var nPhase = 0; nPhase < phasesData.length; nPhase++) {
var phaseData = phasesData[nPhase];
for (var i = 0; i < phaseData.length; i++) {
if (columns[i] === undefined)
columns[i] = ssEtudes.getRangeByName(phasesNamedRanges[i]).getColumn();
destSheet
.getRange(rowToAppend + nPhase, columns[i])
.setValue(phaseData[i]);
}
}
}
`
Type of formula
The results
The error, which translates into The result could not be auto-expanded. Please insert new columns (1).
The results actually appear for some time and disappear after a few seconds, constantly. Does it have something to do with the size of the spreadsheet ?

Apps Script trigger function keeps on running though the trigger has been deleted

I have created a trigger which will first execute after 5 minutes, the when the trigger function starts executing, the trigger is deleted at first place , then the required code executes, and then new trigger is created which executes in 1 minutes. Also I have a condition where in the trigger is deleted and never created.. but the trigger still continues to execute.
I get total_uploads value as 29393, but it ended up adding nearly 100k records.
function onOpen() {
SpreadsheetApp.getUi() // Or DocumentApp or SlidesApp or FormApp.
.createMenu('Fetch CoA')
.addItem('Fetch CoA', 'main')
.addToUi();
}
function main() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var config = ss.getSheetByName("Config");
var hostname = config.getRange(2, 2).getValue();
var authorization = config.getRange(3, 2).getValue();
var coaID = config.getRange(4, 2).getValue();
var documentProperties = PropertiesService.getDocumentProperties();
documentProperties.setProperties({
'hostname': hostname,
'authorization': authorization,
'coaID': coaID,
'offset': '0',
'is_last_set': 0
});
var coaSheet = ss.getSheetByName("COA");
if (coaSheet.getLastRow() > 1) {
coaSheet.deleteRows(1, coaSheet.getLastRow() - 1);
}
console.log("Getting CoA Validaton");
writeCOA();
}
function writeCOA() {
var documentProperties = PropertiesService.getDocumentProperties();
var hostname = documentProperties.getProperty('hostname');
var authorization = documentProperties.getProperty('authorization');
var coaID = documentProperties.getProperty('coaID');
var offset = parseInt(documentProperties.getProperty('offset'));
var is_last_set = parseInt(documentProperties.getProperty('is_last_set'));
var options = {
"method": "GET",
"headers": {
"Authorization": "Token token=" + authorization
}
};
var today = new Date();
console.log("Writing the uploads array");
var ss = SpreadsheetApp.getActiveSpreadsheet();
var coaSheet = ss.getSheetByName("COA");
coaSheet.getRange("A1:D1").setValues([["id", "account_number", "created_at", "updated_at"]]);
var rowNum = 2;
var uploads = [];
var next_page = true;
var total_uploads = "...";
console.log("Getting all Account numbers for CoA: " + coaID);
while (next_page) {
documentProperties.setProperty('next_page', next_page);
if (isTimeUp(today) && is_last_set == 0) {
console.log("RepeatWriteCoA triggered");
// schedule a trigger for a different function
ScriptApp.newTrigger("repeatWriteCoA")
.timeBased()
.everyMinutes(5)
.create();
break;
}
console.log(`Getting uploads ${offset} to ${offset + 500} of ${total_uploads}`);
var platformUrl = `${hostname}/api/v3/chart_of_accounts/${coaID}/account_numbers?limit=500&offset=${offset}`;
//console.log(platformUrl);
var response = UrlFetchApp.fetch(platformUrl, options);
var jsonResponse = response.getContentText();
var page_of_uploads = JSON.parse(jsonResponse);
total_uploads = page_of_uploads['meta']['total'];
documentProperties.setProperty('total_uploads', total_uploads);
uploads = page_of_uploads['account_numbers'];
try {
offset = page_of_uploads['meta']['next']['offset'];
documentProperties.setProperty('offset', offset);
} catch (Exception) {
next_page = false;
documentProperties.setProperty('next_page', next_page);
console.log("Exception in getting the next page offset hence stopping the loop");
deleteTriggers();
// For last set of records that needs to added in our spreadsheet
console.log("Adding Last set of Records..");
for (i = 0; i < uploads.length; i++) {
acc_number = uploads[i].account_number.replace(/[^a-zA-Z0-9\s\p{P}]/g, '-');
coaSheet.getRange("A" + rowNum + ":D" + rowNum).setValues([[uploads[i].id, acc_number, uploads[i].created_at, uploads[i].updated_at]]);
rowNum += 1;
documentProperties.setProperty('rowNum', rowNum);
}
console.log("End of Last set of Records..");
is_last_set = 1;
}
console.log("Adding to spreadsheet");
for (i = 0; i < uploads.length; i++) {
acc_number = uploads[i].account_number.replace(/[^a-zA-Z0-9\s\p{P}]/g, '-');
coaSheet.getRange("A" + rowNum + ":D" + rowNum).setValues([[uploads[i].id, acc_number, uploads[i].created_at, uploads[i].updated_at]]);
rowNum += 1;
documentProperties.setProperty('rowNum', rowNum);
}
console.log("End of adding rows");
if (offset >= total_uploads) {
deleteTriggers();
}
}
documentProperties.setProperty('is_last_set', is_last_set);
}
function repeatWriteCoA() {
console.log("Triggered RepeatFunction... Running from Repeat function");
// Delete trigger , so that new function wont be trigger untill this function ends
deleteTriggers();
var documentProperties = PropertiesService.getDocumentProperties();
var hostname = documentProperties.getProperty('hostname');
var authorization = documentProperties.getProperty('authorization');
var coaID = documentProperties.getProperty('coaID');
var offset = parseInt(documentProperties.getProperty('offset'));
var next_page = documentProperties.getProperty('next_page');
var rowNum = parseInt(documentProperties.getProperty('rowNum'));
var total_uploads = parseInt(documentProperties.getProperty('total_uploads'));
var options = {
"method": "GET",
"headers": {
"Authorization": "Token token=" + authorization
}
};
var today = new Date();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var coaSheet = ss.getSheetByName("COA");
var is_last_set = parseInt(documentProperties.getProperty('is_last_set'));
console.log("Getting all Account numbers for CoA: " + coaID);
while (next_page) {
if (is_last_set == 1) {
console.log('last set already executed');
break;
}
console.log(`Getting uploads ${offset} to ${offset + 500} of ${total_uploads}`);
var platformUrl = `${hostname}/api/v3/chart_of_accounts/${coaID}/account_numbers?limit=500&offset=${offset}`;
var response = UrlFetchApp.fetch(platformUrl, options);
var jsonResponse = response.getContentText();
var page_of_uploads = JSON.parse(jsonResponse);
total_uploads = page_of_uploads['meta']['total'];
uploads = page_of_uploads['account_numbers'];
try {
offset = page_of_uploads['meta']['next']['offset'];
documentProperties.setProperty('offset', offset);
} catch (Exception) {
next_page = false;
documentProperties.setProperty('next_page', next_page);
console.log("Exception in getting the next page offset hence stopping the loop");
// For last set of records that needs to added in our spreadsheet
console.log("Adding Last set of Records..");
for (i = 0; i < uploads.length; i++) {
acc_number = uploads[i].account_number.replace(/[^a-zA-Z0-9\s\p{P}]/g, '-');
coaSheet.getRange("A" + rowNum + ":D" + rowNum).setValues([[uploads[i].id, acc_number, uploads[i].created_at, uploads[i].updated_at]]);
rowNum += 1;
documentProperties.setProperty('rowNum', rowNum);
}
console.log("End of Last set of Records..");
is_last_set = 1;
}
for (i = 0; i < uploads.length; i++) {
acc_number = uploads[i].account_number.replace(/[^a-zA-Z0-9\s\p{P}]/g, '-');
coaSheet.getRange("A" + rowNum + ":D" + rowNum).setValues([[uploads[i].id, acc_number, uploads[i].created_at, uploads[i].updated_at]]);
rowNum += 1;
documentProperties.setProperty('rowNum', rowNum);
}
// Creating new trigger so that it will trigger this function next time ,
// this time we are creating the trigger with 1 min
if (offset <= total_uploads && isTimeUp(today) && is_last_set == 0) {
console.log("RepeatWriteCoA triggered");
// schedule a trigger for a different function
ScriptApp.newTrigger("repeatWriteCoA")
.timeBased()
.everyMinutes(1)
.create();
break;
}
}
documentProperties.setProperty('is_last_set', is_last_set);
}
function isTimeUp(today) {
var now = new Date();
console.log(now.getTime() - today.getTime());
return now.getTime() - today.getTime() > 240000;
// 240000 = 4 mins; this is the threshold limit
// you are free to setup your own threshold limit
}
function deleteTriggers() {
// Loop over all triggers.
var allTriggers = ScriptApp.getProjectTriggers();
if (allTriggers.length > 0) {
console.log("Deleting trigger....");
for (var i = 0; i < allTriggers.length; i++) {
// If the current trigger is the correct one, delete it.
ScriptApp.deleteTrigger(allTriggers[i]);
}
}
}

Stuck at G Suite Developer Quickstart for "check student attendance in Google Meet courses"

I would be very appreciative of any input. Thank you;
I'm using the G Suite Developer Quickstart for a Meet attendance script
https://developers.google.com/gsuite/add-ons/editors/sheets/quickstart/attendance
Unfortunately, upon using the provided code, and having made adjustments, I either always get "Absent" or "Present" for all students (regardless of whether they were present or not) depending on if I use "null" of "false" in an if statement for activities for applicationName 'meet'.
Here is the code I'm using as a superuser:
...
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Opciones SG')
.addItem("Importar Curso", 'importCourses')
.addItem('Verificar Asistencia', 'checkAll')
.addToUi();
}
function importCourses() {
var optionalArgs = {
teacherId: 'me',
pageSize: 5
};
var response = Classroom.Courses.list();
var courses = response.courses;
for (var i = 0; i < 1; i++) {
var courseName = courses[i].name;
var courseId = courses[i].id;
insertCourse(courseName, courseId)
}
}
function insertCourse(courseName, courseId) {
var spreadsheetName = courseName + "(" + courseId + ")"
var activeSpreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var yourNewSheet = activeSpreadsheet.getSheetByName(spreadsheetName);
if (yourNewSheet != null) {
return
}
yourNewSheet = activeSpreadsheet.insertSheet();
yourNewSheet.setName(spreadsheetName);
yourNewSheet.appendRow(['Nombre', 'Email', 'Asistencia'])
yourNewSheet.setFrozenRows(1)
var studentNames = getRoster(courseId)["studentNames"]
var studentEmails = getRoster(courseId)["studentEmails"]
for (var i = 0; i < studentNames.length; i++) {
yourNewSheet.appendRow([studentNames[i],studentEmails[i]])
}
yourNewSheet.autoResizeColumns(1, 2)
yourNewSheet.setFrozenColumns(2)
}
function getRoster(courseId) {
var studentNames = []
var studentEmails = []
var optionalArgs = {
pageSize: 100
};
var response = Classroom.Courses.Students.list(courseId, optionalArgs)
var students = response.students
for (var i = 0; i < 100; i++) {
try {
studentNames.push(students[i].profile.name.fullName)
studentEmails.push(students[i].profile.emailAddress)
} catch (err) {
return { "studentNames":studentNames, "studentEmails":studentEmails }
}
}
}
function checkAll() {
var ss = SpreadsheetApp.getActiveSheet();
var sheet = ss.getDataRange().getValues();
for (var i = 2; i < sheet.length * 100; i++){
var meetCode = getCleanCode(sheet[0][i])
// No Meet code given
if (meetCode == null) {
break;
}
else {
// check whether each student was present in Meet
checkMeet(meetCode, i+1);
}
}
}
function checkMeet(meetCode, index) {
// universal settings - static
var userKey = 'all';
var applicationName = 'meet';
var ss = SpreadsheetApp.getActiveSheet();
var sheet = ss.getDataRange().getValues();
for (var i = 0; i < sheet.length-1; i++) {
var emailAddress = sheet[i+1][1]
var optionalArgs = {
event_name: "call_ended",
filters: "identifier==" + emailAddress + ",meeting_code==" + meetCode
};
try {
var response = AdminReports.Activities.list(userKey, applicationName, optionalArgs);
var activities = response.items;
if (activities == false) {
markAbsent(ss,i+2,index)
}
else {
markPresent(ss,i+2,index)
}
} catch (err) {
continue
}
}
}
function getCleanCode(meetCode) {
try{
return meetCode.replace("/-/g","")
} catch (err) { return meetCode; }
}
function markAbsent(sheet, i, j) {
var cell = sheet.getRange(i, j);
cell.setValue("Absent");
}
function markPresent(sheet, i, j) {
var cell = sheet.getRange(i, j);
cell.setValue("Present");
}
...
For the correct functionality of the code, check for if (activities == null) or if (activities == undefined) - NOT if (activities == false)
If there are no response items (that is no items have been found for a given user and meeting code) - activities will be undefined, but not false.
if (activities == false) will never be fulfilled and if you use it - all participants will be marked as present
On the other hand, if activities == undefined for other reasons - for example because you introduced the wrong meet code (don't forget to replace 'Asistencia' through a valid meet code), all participants will be marked as absent
Note that the Reports API has some delay, so do not expect to retrieve participation data in live time.
If all participants are marked as absent - probably the data did not propagate yet, wait some time and try again.
Make sure you pass the correct meet code to the script. Your meet code should look something like xxx-xxxx-xxx.
The script needs to remove the - for correct functionality. For this please change in function getCleanCode(meetCode) the line return meetCode.replace("/-/g","") to return meetCode.replace(/-/g, "");
As for the question in your comment: here you can see all available Hangouts Meet Audit Activity Events that you can use for filtering instead of the meeting_code. Unfortunately the timestamp is not one of possible query parameters.
Here is the code we have been using for a few months now, no glitches:
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Asistencia')
.addItem('Asistencia', 'checkAll')
.addItem('*Crear*', 'createNewSheets')
.addToUi();
}
function createNewSheets() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
// Get the range of cells that store employee data.
var employeeDataRange = ss.getRangeByName("A:A");
var employeeObjects = employeeDataRange.getValues();
var template = ss.getSheetByName('Template');
for (var i=0; i < employeeObjects.length; i++) {
// Put the sheet you want to create in a variable
var sheet = ss.getSheetByName(employeeObjects[i]);
// Check if the sheet you want to create already exists. If so,
// log this and loop back. If not, create the new sheet.
if (sheet) {
Logger.log("Sheet " + employeeObjects[i] + "already exists");
} else {
template.copyTo(ss).setName(employeeObjects[i]);
}
}
return;
}
function importCourses() {
var optionalArgs = {
teacherId: 'me',
pageSize: 5
};
var response = Classroom.Courses.list(optionalArgs);
var courses = response.courses;
for (var i = 0; i < 2; i++) {
var courseName = courses[i].name;
var courseId = courses[i].id;
insertCourse(courseName, courseId)
}
}
function insertCourse(courseName, courseId) {
var spreadsheetName = courseName + "(" + courseId + ")"
var activeSpreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var yourNewSheet = activeSpreadsheet.getSheetByName(spreadsheetName);
if (yourNewSheet != null) {
return
}
yourNewSheet = activeSpreadsheet.insertSheet();
yourNewSheet.setName(spreadsheetName);
yourNewSheet.appendRow(['Nombre', 'Email', 'Asistencia'])
yourNewSheet.setFrozenRows(1)
var studentNames = getRoster(courseId)["studentNames"]
var studentEmails = getRoster(courseId)["studentEmails"]
for (var i = 0; i < studentNames.length; i++) {
yourNewSheet.appendRow([studentNames[i],studentEmails[i]])
}
yourNewSheet.autoResizeColumns(1, 2)
yourNewSheet.setFrozenColumns(2)
}
function getRoster(courseId) {
var studentNames = []
var studentEmails = []
var optionalArgs = {
pageSize: 100
};
var response = Classroom.Courses.Students.list(courseId, optionalArgs)
var students = response.students
for (var i = 0; i < 100; i++) {
try {
studentNames.push(students[i].profile.name.fullName)
studentEmails.push(students[i].profile.emailAddress)
} catch (err) {
return { "studentNames":studentNames, "studentEmails":studentEmails }
}
}
}
function checkAll() {
var ss = SpreadsheetApp.getActiveSheet();
var sheet = ss.getDataRange().getValues();
for (var i = 2; i < sheet.length * 100; i++){
var meetCode = getCleanCode(sheet[0][i])
// No Meet code given
if (meetCode == null) {
break;
}
else {
// check whether each student was present in Meet
checkMeet(meetCode, i+1);
}
}
}
function checkMeet(meetCode, index) {
// universal settings - static
var userKey = 'all';
var applicationName = 'meet';
var ss = SpreadsheetApp.getActiveSheet();
var sheet = ss.getDataRange().getValues();
//let today = new Date()
//var staringTime = new Date().toISOString();
// var DateValue = ss.getRange(1, 1).getValues();
for (var i = 0; i < sheet.length-1; i++) {
var emailAddress = sheet[i+1][1]
var optionalArgs = {
startTime: formatStartDate(),
event_name: "call_ended",
filters: "identifier==" + emailAddress + ",meeting_code==" + meetCode
};
try {
var response = AdminReports.Activities.list(userKey, applicationName, optionalArgs);
var activities = response.items;
if (activities == undefined) {
markAbsent(ss,i+2,index)
}
else {
markPresent(ss,i+2,index)
}
} catch (err) {
continue
}
}
}
function getCleanCode(meetCode) {
try{
return meetCode.replace(/-/g, "");
} catch (err) { return meetCode; }
}
function markAbsent(sheet, i, j) {
var cell = sheet.getRange(i, j);
cell.setValue("A");
}
function markPresent(sheet, i, j) {
var cell = sheet.getRange(i, j);
cell.setValue("");
}
function formatStartDate(){
let date = new Date();
const offset = date.getTimezoneOffset();
let offsetDate = new Date(date.getTime() - (offset*145*1000));
return offsetDate.toISOString();
}

How can I collect all of the data on a Google spreadsheet and then clear it after the script has been run?

I am creating a script that will grab all unread e-mails, move the bodies of these e-mails into a Google spreadsheet, push all of this data through to Slack via webhook, then clear the sheet.
The first part of the script that grabs the unread e-mails and puts it in the appropriate cells is working fine. The part where it is pushing the data through is not working. I have it set to a loop to make sure that it grabs all of the data, but it is not working. I have tried to make it not show up as an array, but that didn't seem to work either.
var SEARCH_QUERY = "label:inbox is:unread to:me";
function getEmails_(q) {
var emails = [];
var threads = GmailApp.search(q);
for (var i in threads) {
var msgs = threads[i].getMessages();
for (var j in msgs) {
emails.push([msgs[j].getBody().replace(/<.*?>/g, '\n')
.replace(/^\s*\n/gm, '').replace(/^\s*/gm, '').replace(/\s*\n/gm, '\n')
]);
}
}
return emails;
}
function appendData_(sheet, array2d) {
sheet.getRange(sheet.getLastRow() + 1, 1, array2d.length, array2d[0].length).setValues(array2d);
}
function saveEmails() {
var array2d = getEmails_(SEARCH_QUERY);
if (array2d) {
appendData_(SpreadsheetApp.getActiveSheet(), array2d);
}
}
function postToSlack() {
var url = "SLACK WEBHOOK URL HERE";
var sheet = SpreadsheetApp.getActiveSheet();
// This represents ALL the rows
var range = sheet.getDataRange();
var values = range.getValues();
for (var i = 0; i < values.length; i++) {
var row = [];
for (var j = 0; j < values[i].length; j++) {
if (values[i][j]) {
row.push(values[i][j]);
}
}
Logger.log(row);
var str = values;
var payload = {
"text": str
}
var post = {
"method": "post",
"payload": JSON.stringify(payload)
};
return UrlFetchApp.fetch(url, post);
}
}
I expect that the data in the cells will push right out into Slack, but nothing is happening. When I run the Logs for the rows that it finds, it is also pulling up no data.
I figured it out! It wanted a toString() command. The following works perfectly EXCEPT that it does not clear the sheet at the end. It will only clear if a separate function is running.
var SEARCH_QUERY = "label:inbox is:unread to:me";
var url = "SLACK WEBHOOK URL";
function getEmails_(q)
{
var emails = [];
var threads = GmailApp.search(q);
for (var i in threads) {
var msgs = threads[i].getMessages();
for (var j in msgs)
{
emails.push([msgs[j].getPlainBody().replace(/<.*?>/g, '\n')
.replace(/^\s*\n/gm, '').replace(/^\s*/gm, '').replace(/\s*\n/gm, '\n')]);
msgs[j].markRead();
}
}
return emails;
}
function appendData_(sheet, array2d) {
sheet.getRange(sheet.getLastRow() + 1, 1, array2d.length, array2d[0].length).setValues(array2d);
}
function saveEmails() {
var array2d = getEmails_(SEARCH_QUERY);
if (array2d) {
appendData_(SpreadsheetApp.getActiveSheet(), array2d);
}
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getDataRange();
var values = range.getValues();
var row = [];
for (var i = 0; i < values.length; i++) {
for (var j = 0; j < values[i].length; j++) {
if (values[i][j]) {
row.push(values[i][j]);
}
}
}
Logger.log(row);
var str = values.toString();
var payload = {
"text": str
}
// var post = {
// "method": "post",
// "payload": JSON.stringify(payload)
//
// };
// return UrlFetchApp.fetch(url, post);
function clear() {
var app = SpreadsheetApp;
var sheet = app.getActiveSpreadsheet().getActiveSheet();
sheet.getRange("A1:A40").clearContent();}
}
Edit: In your added answer, you can either delete clear() and add in sheet.getRange("A1:A40").clearContent(); below Logger.log(row);. Another way is that you can also take out the clear() out of the saveEmails() and call like this,
function saveEmails() {
var array2d = getEmails_(SEARCH_QUERY);
if (array2d) {
appendData_(SpreadsheetApp.getActiveSheet(), array2d);
}
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getDataRange();
var values = range.getValues();
var row = [];
for (var i = 0; i < values.length; i++) {
for (var j = 0; j < values[i].length; j++) {
if (values[i][j]) {
row.push(values[i][j]);
}
}
}
Logger.log(row);
var str = values.toString();
var payload = {
"text": str
}
clear();
// var post = {
// "method": "post",
// "payload": JSON.stringify(payload)
//
// };
// return UrlFetchApp.fetch(url, post);
}
function clear() {
var app = SpreadsheetApp;
var sheet = app.getActiveSpreadsheet().getActiveSheet();
sheet.getRange("A1:A40").clearContent();
}

Using Directory API in Google-Apps-Script, how to get groups inside a group

I'm working on a script that gets all goups in my domain and lists all users in these groups.
I rewrote this script because GroupsManager is deprecated so my old script will stop working soon.
All is working well (although this API is quite slow...) but the issue I have is that some groups have not only users but include also other groups... some of them have only subgroups (and no users (way of speaking !)
I can't find any way to catch that situation, the list method return users, it ignores completely groups inside.
So my question is :
Is there a way to get the groups inside a group ? Am I missing something obvious ?
Below and for info is the code I use to list all members in groups in a spreadsheet, 1 sheet / group so that I can manipulate these data faster and easier later on. I used background color flags as a track keeper to handle a (relatively) small batch operation and a timer trigger that make it work until it's done. (yes, we have a lot of groups in our domain ;-)
function listGroupMembers() {
var start = new Date().getTime();
var ss = SpreadsheetApp.openById('1k-o4IVKEhW2zkk_________f2rH4UUt3OAC8I0ZoM');// set your SS ID (runs with timer trigger > needs to open by ID
var pageToken, page;
var count=0;
var groupList = [];
do {
page = AdminDirectory.Groups.list({
domain: 'xxxxx.be',
maxResults: 100,
pageToken: pageToken
});
var gr = page.groups;
if (gr) {
for (var i = 0; i < gr.length; i++) {
var group = gr[i];
count++;
//Logger.log(group);
groupList.push([group.email,count])
}
} else {
Logger.log('No group found.');
}
pageToken = page.nextPageToken;
}
while (pageToken);
try{
var GroupAddress = ss.getSheetByName('GroupAddress');// if GroupAddress Sheet already exist, open it
Logger.log('Use existing sheet "'+GroupAddress.getName()+'"');
}catch(err){
Logger.log('create sheet "GroupAddress"');
ss.deleteSheet(ss.getSheets()[0]);
var GroupAddress = ss.insertSheet('GroupAddress',0); //else create it as first sheet
}
GroupAddress.getDataRange().clearContent();
ss.getSheetByName('GroupAddress').getRange(1,1,groupList.length,groupList[0].length).setValues(groupList);
var groupNames = ss.getSheetByName("GroupAddress").getDataRange().getValues();
var groupDone = ss.getSheetByName("GroupAddress").getDataRange().getBackgrounds();
var progress;
for(var n in groupNames){
if(groupDone[n][0] != 'white' || (new Date().getTime()-start)/60 > 5000){ // limit to 5 seconds + 1 group
continue;
}
try{
var outputSheet = ss.insertSheet(groupNames[n][0],Number(n)+1)
}catch(err){
var outputSheet = ss.getSheetByName(groupNames[n][0]);
}
var group = GroupsApp.getGroupByEmail(groupNames[n][0]);
//Logger.log('groupNames[n][0] = '+groupNames[n][0]);
try{
var users = group.getUsers();
var output = [];
for (var i in users){
var user = AdminDirectory.Users.get(users[i].getEmail());
output.push([Number(i)+1,user.name.fullName, user.primaryEmail,user.aliases!=null?user.aliases:'',new Date(user.creationTime),user.isAdmin,new Date(user.lastLoginTime)]);
}
ss.getSheetByName("GroupAddress").getRange(Number(n)+1,1).setBackground('#fff2cc');
SpreadsheetApp.flush();
if(output.length>0){
output.push(['durée :',parseInt((new Date().getTime()-start)/60),'millisecondes','','','','']);
outputSheet.getRange(1,1,output.length,output[0].length).setValues(output);
}
}catch(err){
continue}
}
groupDone = ss.getSheetByName("GroupAddress").getDataRange().getBackgrounds();
for(progress = groupDone.length-1;progress>=0;progress--){
if(groupDone[progress][0]!='white'){break};
}
Logger.log(n+'='+progress+'?');
if(n==progress){
MailApp.sendEmail(Session.getEffectiveUser().getEmail(),'All jobs done in GroupList','All tasks completed on '+new Date().toLocaleString());
var trig = ScriptApp.getProjectTriggers();
for(var t in trig){
try{
ScriptApp.deleteTrigger(trig[t]);
}catch(e){}
}
}else{
ScriptApp.newTrigger('listGroupMembers').timeBased().after(3000).create();// wait 3 secs and continue
}
}
EDIT
Thanks to Göran's answer I have a version that gets all members of all groups including subgroups.
I changed (very) slightly his code to fit my requirements and I also needed to limit the number of cells in each sheet to what I needed because the total (1000*26 cells/sheet) was exceeding the 200000 cells limit (I have more than 100 sheets).
Full code below for anybody who would be interested (including menu and a few utilities):
function onOpen() {
var menu = SpreadsheetApp.getUi().createMenu("Utilitaires INSAS")
.addItem("reset colors", "resetColors")
.addItem("create groupUser lists", "listGroupMembers")
.addItem("delete all sheets", "delsheets");
menu.addToUi();
}
function listGroupMembers() {
var start = new Date().getTime();
var ss = SpreadsheetApp.openById('1k-o4IVKEhW2zkkEu0gAvb92rQf2rH4UUt3OAC8I0ZoM');// set your SS ID (runs with timer trigger > needs to open by ID
var pageToken, page;
var count=0;
var groupList = [];
do {
page = AdminDirectory.Groups.list({
domain: 'insas.be',
maxResults: 100,
pageToken: pageToken
});
var gr = page.groups;
if (gr) {
for (var i = 0; i < gr.length; i++) {
var group = gr[i];
count++;
//Logger.log(group);
groupList.push([group.email,count])
}
} else {
Logger.log('No group found.');
}
pageToken = page.nextPageToken;
}
while (pageToken);
try{
var GroupAddress = ss.getSheetByName('GroupAddress');// if GroupAddress Sheet already exist, open it
Logger.log('Use existing sheet "'+GroupAddress.getName()+'"');
}catch(err){
Logger.log('create sheet "GroupAddress"');
ss.deleteSheet(ss.getSheets()[0]);
var GroupAddress = ss.insertSheet('GroupAddress',0); //else create it as first sheet
}
GroupAddress.getDataRange().clearContent();
ss.getSheetByName('GroupAddress').getRange(1,1,groupList.length,groupList[0].length).setValues(groupList);
var groupNames = ss.getSheetByName("GroupAddress").getDataRange().getValues();
var groupDone = ss.getSheetByName("GroupAddress").getDataRange().getBackgrounds();
var progress;
for(var n in groupNames){
if(groupDone[n][0] != 'white' || (new Date().getTime()-start)/60 > 5000){ // limit to 5 seconds + 1 group
continue;
}
Logger.log('groupNames[n][0] = '+groupNames[n][0]);
try{
var outputSheet = ss.getSheetByName(groupNames[n][0]);
Logger.log('sheet '+outputSheet.getName()+' created');
}catch(err){
var outputSheet = ss.insertSheet(groupNames[n][0],ss.getSheets().length+1);
}
try{
var users = getUsersInGroup(groupNames[n][0]);
var output = [];
for (var i in users){
var user = users[i];
// Logger.log(user);
output.push([Number(i)+1,user.email,user.role,user.groupName]);
}
ss.getSheetByName("GroupAddress").getRange(Number(n)+1,1).setBackground('#fff2cc');
SpreadsheetApp.flush();
if(output.length>0){
output.push(['Ex.T = '+parseInt((new Date().getTime()-start)/60)+' mS','','','']);
outputSheet.getRange(1,1,output.length,output[0].length).setValues(output);
outputSheet.deleteRows(output.length+2, outputSheet.getMaxRows()-output.length-4);
outputSheet.deleteColumns(6, outputSheet.getMaxColumns()-6);
}
}catch(err){
continue}
}
groupDone = ss.getSheetByName("GroupAddress").getDataRange().getBackgrounds();
for(progress = groupDone.length-1;progress>=0;progress--){
if(groupDone[progress][0]!='white'){break};
}
Logger.log(n+'='+progress+'?');
if(n==progress){
MailApp.sendEmail(Session.getEffectiveUser().getEmail(),'All jobs done in GroupList','All tasks completed on '+new Date().toLocaleString());
var trig = ScriptApp.getProjectTriggers();
for(var t in trig){
try{
ScriptApp.deleteTrigger(trig[t]);
}catch(e){}
}
}else{
ScriptApp.newTrigger('listGroupMembers').timeBased().after(10000).create();// wait 3 secs and continue
}
}
function resetColors(){
var ss = SpreadsheetApp.getActive();
ss.getSheets()[0].getDataRange().setBackground(null);
}
function getUsersInGroup(rootGroup) {
var groupTreeUsers = [];
var groups = [];
groups.push(rootGroup);
while (groups.length > 0) {
var currentGroup = groups.pop();
var groupName = AdminDirectory.Groups.get(currentGroup).name;
var groupMembers = getAllMembers_(currentGroup);
for (var i in groupMembers) {
if (groupMembers[i].type == 'USER') {
var groupMember = groupMembers[i];
groupMember['groupName'] = currentGroup;
groupTreeUsers.push(groupMember)
}
else if (groupMembers[i].type == 'GROUP') {
groups.push(groupMembers[i].email)
}
}
}
Logger.log('There are %s user members in %s (including sub groups)', groupTreeUsers.length, rootGroup);
return groupTreeUsers;
}
function getAllMembers_(group) {
var memberPageToken, memberPage;
var members = [];
do {
memberPage = AdminDirectory.Members.list(group, {
maxResults: 200,
pageToken: memberPageToken
});
var pageMembers = memberPage.members;
if (pageMembers) {
for (var j =0; j < pageMembers.length; j++) {
members.push(pageMembers[j]);
}
}
memberPageToken = memberPage.nextPageToken;
} while (memberPageToken);
return members;
}
function delsheets(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var numSheets = ss.getNumSheets();// check how many sheets in the spreadsheet
for (var pa=numSheets-1;pa>0;pa--){
if(ss.getSheets()[pa].getSheetName()!='GroupAddress'){
ss.deleteSheet(ss.getSheets()[pa]); // delete sheets begining with the last one
Utilities.sleep(100);
}
}
SpreadsheetApp.flush();
}
AdminDirectory MembersCollection might be what you are looking for.
Example snippet:
var members = AdminDirectory.Members.list('testgroup#example.com').members;
for (var i = 0; i < members.length; i++) {
var member = members[i];
switch (member.type) {
case 'USER':
Logger.log('%s is a user', member.email);
break;
case 'GROUP':
Logger.log('%s is a group', member.email);
break;
default:
Logger.log('This will never happen');
}
Some of my customers have groups containing groups contaning groups... forming a group tree with arbitrary depth. I use this code to get all user members in such a group tree:
function walkTreeStack() {
var rootGroup = 'testgroup#example.com';
var groupTreeUsers = [];
var groups = [];
groups.push(rootGroup);
while (groups.length > 0) {
var currentGroup = groups.pop();
var groupName = AdminDirectory.Groups.get(currentGroup).name;
var groupMembers = getAllMembers_(currentGroup);
for (var i in groupMembers) {
if (groupMembers[i].type == 'USER') {
groupTreeUsers.push([groupName, groupMembers[i].email])
}
else if (groupMembers[i].type == 'GROUP') {
groups.push(groupMembers[i].email)
}
}
}
Logger.log('There are %s user members in %s (including sub groups)', groupTreeUsers.length, groupName);
}
function getAllMembers_(group) {
var memberPageToken, memberPage;
var members = [];
do {
memberPage = AdminDirectory.Members.list(group, {
maxResults: 200,
pageToken: memberPageToken
});
var pageMembers = memberPage.members;
if (pageMembers) {
for (var j =0; j < pageMembers.length; j++) {
members.push(pageMembers[j]);
}
}
memberPageToken = memberPage.nextPageToken;
} while (memberPageToken);
return members;
}
It's slightly faster if it's done recursively but I suspect the script might crash due to memory constraints if there are a large number of sub groups and user members:
function getGroupTreeMembers() {
var rootGroup = 'testgroup#example.com';
var groupTreeUsers = [];
walkTreeRecursive_(rootGroup, groupTreeUsers);
var groupName = AdminDirectory.Groups.get(rootGroup).name;
Logger.log('There are %s user members in %s (including sub groups)', groupTreeUsers.length, groupName);
}
function walkTreeRecursive_(rootGroup, groupTreeUsers) {
var groupName = AdminDirectory.Groups.get(rootGroup).name;
var groupMembers = getAllMembers_(rootGroup);
var groups = [];
for (var i in groupMembers) {
if (groupMembers[i].type == 'USER') {
groupTreeUsers.push([groupName, groupMembers[i].email])
}
else if (groupMembers[i].type == 'GROUP') {
groups.push(groupMembers[i].email)
}
}
for (var i in groups) {
walkTreeRecursive_(groups[i], groupTreeUsers);
}
}
For more information please visit:
https://developers.google.com/admin-sdk/directory/v1/reference/members#resource