Debug/Fix "Exceeded maximum execution time" error Google Addon - google-apps-script
I've created my first Google Workspace Addon which I have deployed to the associated domain successfully although still working on it due to this error.
Without errors, functionally it's (was) working well, however, I now keep getting "Exceeded maximum execution time" errors.
This error is frustratingly intermittent, but it recently started occuring around 90%+ of the time despite not occurring previously for weeks. Reverting to previous versions of my code hasn't fixed the issue.
Most if not all existing answers regarding this error assume the script is genuinely exceeding the time limit for scripts (either 6 minutes or 30 minutes).
To be clear - this is not what's happening here. The script doesn't run for anywhere close to the maximum time allowed (as far as I'm aware). It actually runs for around 45 seconds before giving the error.
It may somehow be running in the background and causing a legitimate Timeout error but I need to figure out if this is the case and the cause of the error and then fix it.
I've contacted Google but their relevant support who are knowledgeable about this and who could help are unfortunately very difficult to get hold of.
In Cloud Logging for this error, I'm seeing (obfuscated id's):
{
"insertId": "28z8v2anf1xq6",
"jsonPayload": {
"context": {
"reportLocation": {
"filePath": "[unknown file]",
"functionName": "[unknown function]"
}
},
"message": "Exceeded maximum execution time",
"serviceContext": {
"service": "AKfycbx-SO5mXBNoe2leEH4wrj02t9fZZwkq5BQlJPuBaNM"
}
},
"resource": {
"type": "app_script_function",
"labels": {
"function_name": "fileMakerPro",
"invocation_type": "unknown",
"project_id": "order-management-713317"
}
},
"timestamp": "2022-03-10T10:09:58.827Z",
"severity": "ERROR",
"labels": {
"script.googleapis.com/process_id": "fIxbX5X5IjrIqLLK-XQgJiJV0U9vQMEAEA1GOxTqw-XzqsaVlKTNK2UcymM7feuXp-qZLshvBUg61TS6u28l_v6szoAq8QBBOvGudISVj0yceQXPKpIHO6HJ2G-uxuqy4xcnv-NzDfBTMbJH7VmK_AjZd6a5KVA-LnhtOE_28mCn_zTpI5AC3-BhX_lcCC1p-3QsMX6blhMZSYgVTTo1T_Z9SovufUWinpJieIbio-L8wzQPkjLYM2l9s5RHGuKMcT3LbvUO7fFS4DV1z_xfaR_nU1LqeayAk1aouGSXc",
"script.googleapis.com/user_key": "wPo7ZL4DSIqpPrZfeqo7E7yAArA430YCNnzbVYhyuVlR6nKybMkISeaDZoigRDx0gAJXIjF49EoL",
"script.googleapis.com/deployment_id": "wkq5BQlJPcbx-SO5mEHAKfywrj02t9fZZ4uBXBNoe2leaNM",
"script.googleapis.com/project_key": "Xf9t90MIwJJbMnu2Z9hQ48TM6DPTcW9w3"
},
"logName": "projects/order-management-317713/logs/script.googleapis.com%2Fconsole_logs",
"receiveTimestamp": "2022-03-10T10:09:59.313440894Z"
}
Which is obscure and isn't helpful at all with the "unknowns".
The function it references, for reference:
async function fileMakerPro(e) {
console.time("fileMakerPro");
//Order Management Named Ranges
let OM_NR = await getNamedRangesAsObject_(ss);
let orderNumber, orderFolder, orderFolderName;
let loadingDate, loadingDateFormatted, dueDate, dueDateFormatted;
let sheetGoodsTable = OM_NR.goodsTable.ranges.filter(function (x) { /* here, x is an array, not an object */
return !(x.every(element => element === (undefined || null || '')))
});
//Get order number if exists or show warning and return
if (orderNumber = OM_NR.OrderNo.range.toString()) {
} else {
ui.alert(`⚠️ ORDER NUMBER \(${OM_NR.OrderNo.rangeA1.toString()}\) appears missing, please correct and try again.`);
return;
}
//Get loading date if exists or show warning and return
if (loadingDate = OM_NR.transportLoadingDate.range) {
loadingDateFormatted = `${loadingDate.getDate()}-${loadingDate.getMonth() + 1}-${loadingDate.getFullYear()}`;
monthName = monthNames[loadingDate.getMonth()];
//Set Due Date 30 days after Loading Date
dueDate = new Date(loadingDate.getFullYear(), loadingDate.getMonth(), loadingDate.getDate() + 30);
dueDateFormatted = `${dueDate.getDate()}-${dueDate.getMonth() + 1}-${dueDate.getFullYear()}`;
} else {
ui.alert(`⚠️ LOADING DATE \(${OM_NR.transportLoadingDate.rangeA1.toString()}\) appears to be missing, please correct and try again.`);
return;
}
let rootFolder = DriveApp.getFileById(sheetId).getParents().next();
let tempFolder = DriveApp.getFolderById("1f4Ll-KODzmvBIuunaCS_7anMCX-w86S6");
let monthFolders = rootFolder.getFolders();
let tickBoxes = e.formInputs.generateDocsCheckboxes;
let packingListTemplateId = '1cZUB4U59Z56456gdgdghhka2QQ9sUNIlQSHYy1M';
let tickCMR = false, tickInvoice = false;
//Find existing order folder
let orderSearch = await searchFolders(monthFolders, monthName, orderNumber);
//Set Order Folder Name if both values present
orderFolderName = `${loadingDate.getDate()}-${loadingDate.getMonth() + 1}-${loadingDate.getFullYear()} \(${orderNumber}\)`;
if (tickBoxes !== undefined) {
if (tickBoxes.indexOf('tickBoxCMRnote') > -1) {
tickCMR = true;
}
if (tickBoxes.indexOf('tickBoxInvoice') > -1) {
tickInvoice = true;
}
} else {
let confirm = ui.alert(`⚠️ No documents selected. Update Order ${orderNumber} Snapshot and Packing List only?`, Browser.Buttons.YES_NO);
if (confirm === ui.Button.YES) {
fileMakerPro();
} else {
ss.toast(`⚠️ Update cancelled.`);
return;
}
}
if (!orderSearch.foundMonthFolder) {
ss.toast(`⚙️ Creating Month Folder...`);
monthFolder = rootFolder.createFolder(monthName);
} else {
monthFolder = orderSearch.foundMonthFolder;
}
if (!orderSearch.foundOrderFolder) {
ss.toast(`⚙️ Creating Order Folder...`);
orderFolder = monthFolder.createFolder(orderFolderName);
} else {
if (!orderSearch.foundOrderFolder.getName().includes(loadingDateFormatted)) {
ui.alert(`⚠️ Duplicate Order Number ${orderNumber} found: ${orderSearch.foundMonthFolder.getName()} / ${orderSearch.foundOrderFolder.getName()}.\n\n Please change Order Number or remove duplicate Order Folder.`);
return;
}
orderFolder = orderSearch.foundOrderFolder;
}
if (tickInvoice && tickCMR) {
assembleCMR(orderFolder, orderNumber, tempFolder, loadingDateFormatted, sheetGoodsTable, OM_NR);
assembleInvoice(orderFolder, orderNumber, tempFolder, loadingDateFormatted, dueDateFormatted, sheetGoodsTable, OM_NR);
ss.toast(`✅ Documents Saved`);
} else {
if (tickCMR) {
assembleCMR(orderFolder, orderNumber, tempFolder, loadingDateFormatted, sheetGoodsTable, OM_NR);
ss.toast(`✅ CMR Document Saved`);
}
if (tickInvoice) {
assembleInvoice(orderFolder, orderNumber, tempFolder, loadingDateFormatted, dueDateFormatted, sheetGoodsTable, OM_NR);
ss.toast(`✅ Invoice Document Saved`);
}
}
let existingOrderSnapshot = orderFolder.getFilesByName(`${orderNumber} - ORDER SNAPSHOT - DO NOT EDIT`);
let existingPackingList = orderFolder.getFilesByName(`${orderNumber} - PACKING LIST`);
let packingListId = await createPackingList(existingPackingList, orderFolder, orderNumber, packingListTemplateId, sheetId, OM_NR);
createOrderSnapshot(existingOrderSnapshot, orderFolder, orderNumber, sheetId, packingListId);
syncOrderNumbers(orderSearch.orderNumbers);
console.timeEnd("fileMakerPro");
return;
}
I suspect maybe the following function could be causing the Timeout since the loop may be malfunctioning but not sure how to check or fix:
async function writePackingList(OM_NR, packingList, packingListId){
let PL_NR = await getNamedRangesAsObject_(packingList);
for (let nr in PL_NR) {
Logger.log(nr);
if (nr == "loadingAddress") {
packingList.getRangeByName(`${nr}`).setValue(OM_NR[`${nr}`].range);
} else if (nr) {
packingList.getRangeByName(`${nr}`).setValue(OM_NR[`${nr}`].range);
} else {
break;
}
}
ss.getRangeByName("transportTotalNetWeight").setFormula(`=IMPORTRANGE("${packingListId}", "PL!I15")`)
ss.getRangeByName("transportTotalGrossWeight").setFormula(`=IMPORTRANGE("${packingListId}", "PL!H15")`)
}
Any help would be appreciated as I've hit a brick wall with this. I need to figure out how to debug this error, understand why it's happening and fix it so it doesn't happen when fully deployed.
Thanks
EDIT to include additional functions:
//Make copy of Invoice template doc, get body, replace all placeholders, return
async function assembleInvoice(orderFolder, orderNumber, tempFolder, loadingDateFormatted, dueDateFormatted, sheetGoodsTable, OM_NR) {
console.time("assembleInvoice");
ss.toast(`⚙️ Assembling Invoice Document...`);
let prefix = orderNumber + " - ";
let fileName = "INVOICE";
let templateTempCopy;
//Blue Invoice Template
let invoiceTemplate1 = DriveApp.getFileById("C2j_q3nUP4UTUR1KiCt07r1lATPKSjzEm-EeY109T8B4");
//Red Invoice Template
let invoiceTemplate2 = DriveApp.getFileById("1uczJT-F-JzKzaBwh_CdhJNcFkgDHfGjypJYom4vqGIo");
//Choose Invoice template based on Movement Type
let staraMovementType = OM_NR.staraMovementType.range;
if (staraMovementType.toString().includes('GB SHIPPING TO')) {
templateTempCopy = invoiceTemplate1.makeCopy(tempFolder);
} else {
templateTempCopy = invoiceTemplate1.makeCopy(tempFolder);
}
//Open working copy
let workingCopy = DocumentApp.openById(templateTempCopy.getId());
//Get body from working copy
let documentBody = workingCopy.getBody();
//Populate table
let allTables = documentBody.getTables();
let invoiceGoodsTable = allTables[3];
let itemRowTemplate = invoiceGoodsTable.getRow(invoiceGoodsTable.getNumRows() - 2);
let totalsRowTemplate = invoiceGoodsTable.getRow(invoiceGoodsTable.getNumRows() - 1);
for (let n in sheetGoodsTable) {
let tableRow = invoiceGoodsTable.appendTableRow(itemRowTemplate.copy());
tableRow.getCell(0).replaceText("{itemNo}", sheetGoodsTable[n][1]);
tableRow.getCell(1).replaceText("{itemDesc}", sheetGoodsTable[n][0]);
tableRow.getCell(2).replaceText("{commodityCode}", sheetGoodsTable[n][2]);
tableRow.getCell(3).replaceText("{cartons}", sheetGoodsTable[n][4]);
tableRow.getCell(4).replaceText("{qtyKgs}", sheetGoodsTable[n][5]);
tableRow.getCell(5).replaceText("{price}", sheetGoodsTable[n][3]);
tableRow.getCell(6).replaceText("{total}", sheetGoodsTable[n][5] * sheetGoodsTable[n][3]);
}
let totalsRow = invoiceGoodsTable.appendTableRow(totalsRowTemplate.copy());
totalsRow.getCell(3).replaceText("{cartons}", OM_NR.transportCasesCartons.range);
totalsRow.getCell(4).replaceText("{qtyKgs}", OM_NR.transportTotalNetWeight.range);
totalsRow.getCell(6).replaceText("{total}", OM_NR.transportInvoiceTotal.range);
itemRowTemplate.removeFromParent();
totalsRowTemplate.removeFromParent();
let importerAddress = `${OM_NR.importerConsigneeIntoEU.range}, ${OM_NR.importerAddress.range}`;
documentBody.replaceText("{currency}", OM_NR.staraInvoiceCurrency.range);
documentBody.replaceText("{shipToAddress}", importerAddress);
documentBody.replaceText("{billToAddress}", importerAddress);
documentBody.replaceText("{exporterCustomsInvoiceNo}", OM_NR.exporterCustomsInvoiceNo.range);
documentBody.replaceText("{exporterVAT}", OM_NR.exporterVAT.range);
documentBody.replaceText("{exporterEORI}", OM_NR.exporterEORI.range);
documentBody.replaceText("{staraOrderNo}", OM_NR.staraOrderNo.range);
documentBody.replaceText("{staraCustomerOrderNo}", OM_NR.staraCustomerOrderNo.range);
documentBody.replaceText("{transportLoadingDate}", loadingDateFormatted);
documentBody.replaceText("{dueDate}", dueDateFormatted);
documentBody.replaceText("{paymentTerms}", "30 DAYS");
documentBody.replaceText("{staraInvoiceNo}", OM_NR.staraInvoiceNo.range);
documentBody.replaceText("{incoTerms}", `${OM_NR.orderIncoterm.range} ${OM_NR.orderCountryOfDestination.range}`);
documentBody.replaceText("{transportTotalGrossWeight}", OM_NR.transportTotalGrossWeight.range);
documentBody.replaceText("{transportTotalNetWeight}", OM_NR.transportTotalNetWeight.range);
documentBody.replaceText("{transportTruckRef}", OM_NR.transportTruckRef.range);
documentBody.replaceText("{transportSeal1}", OM_NR.transportSeal1.range);
documentBody.replaceText("{transportSeal2}", OM_NR.transportSeal2.range);
documentBody.replaceText("{transportInvoiceTotal}", OM_NR.transportInvoiceTotal.range);
documentBody.replaceText("{staraInvoiceCurrency}", OM_NR.staraInvoiceCurrency.range);
workingCopy.saveAndClose();
//Gets a 'blob' of the completed document
let completedDoc = templateTempCopy.getAs('application/pdf');
//Check for existing PDF and move to trash (not fully delete)
let foundFile = orderFolder.getFilesByName(prefix + fileName);
if (foundFile.hasNext()) {
foundFile.next().setTrashed(true);
}
try {
//Create PDF
ss.toast(`📝 Saving ${fileName} PDF...`);
orderFolder.createFile(completedDoc).setName(prefix + fileName);
deleteTempFiles(templateTempCopy.getId());
} catch (err) {
showError(err);
} finally {
ss.toast(`✅ Invoice Document Saved`);
console.timeEnd("assembleInvoice");
}
return;
}
async function createPackingList(existingPackingList, orderFolder, orderNumber, packingListTemplateId, sheetId, OM_NR) {
console.time("createPackingList");
let packingListId;
//Get current sheet container objects and data
if (existingPackingList.hasNext()) {
packingListId = existingPackingList.next().getId();
} else {
ss.toast(`📃 Creating Packing List...`);
let newPackingList = DriveApp.getFileById(packingListTemplateId);
let newPackingListCopy = newPackingList.makeCopy(`${orderNumber} - PACKING LIST`, orderFolder)
packingListId = newPackingListCopy.getId();
}
let packingList = SpreadsheetApp.openById(packingListId);
addImportrangePermission(packingListId, sheetId);
writePackingList(OM_NR, packingList, packingListId);
ss.toast(`✅ Packing List Created/Updated`);
console.timeEnd("createPackingList");
return packingListId;
}
async function createOrderSnapshot(existingOrderSnapshot, orderFolder, orderNumber, sheetId, packingListId) {
console.time("createOrderSnapshot");
let snapshotExists = existingOrderSnapshot.hasNext();
//If snapshot exists, delete.
if (snapshotExists) {
existingOrderSnapshot.next().setTrashed(true);
ss.toast(`📷 Updating Order Snapshot...`);
} else {
ss.toast(`📷 Creating Order Snapshot...`);
}
//Make copy of sheet in order folder
let currentSheet = DriveApp.getFileById(sheetId);
let sheetCopy = currentSheet.makeCopy(`${orderNumber} - ORDER SNAPSHOT - DO NOT EDIT`, orderFolder);
let sheetCopyId = sheetCopy.getId();
let targetSpreadsheet = SpreadsheetApp.openById(sheetCopyId);
//let targetSpreadsheet = SpreadsheetApp.openById(DriveApp.getFileById(sheetId).makeCopy(`${orderNumber} - ORDER SNAPSHOT - DO NOT EDIT`, orderFolder).getId());
addImportrangePermission(targetSpreadsheet.getRangeByName("CONTROL").getValue(), sheetCopyId);
addImportrangePermission(packingListId, sheetCopyId);
targetSpreadsheet.getRangeByName("transportTotalNetWeight").setFormula(`=IMPORTRANGE("${packingListId}", "PL!I15")`)
targetSpreadsheet.getRangeByName("transportTotalGrossWeight").setFormula(`=IMPORTRANGE("${packingListId}", "PL!H15")`)
targetSpreadsheet.getRangeByName("topRow").clearDataValidations()
targetSpreadsheet.getRangeByName("sheetBody").clearDataValidations()
//DELETE UNNEEDED TABS FROM COPY
targetSpreadsheet.deleteSheet(targetSpreadsheet.getSheetByName("PC"));
targetSpreadsheet.deleteSheet(targetSpreadsheet.getSheetByName("C1"));
targetSpreadsheet.deleteSheet(targetSpreadsheet.getSheetByName("C2"));
targetSpreadsheet.deleteSheet(targetSpreadsheet.getSheetByName("SFC"));
if (snapshotExists) {
ss.toast(`✅ Order Snapshot Updated`);
} else {
ss.toast(`✅ Order Snapshot Created`);
}
console.timeEnd("createOrderSnapshot");
return;
}
async function addImportrangePermission(sourceSheetId, authSheetId) {
console.time("addImportrangePermission");
if (sourceSheetId) {
// donor or source spreadsheet id
// adding permission by fetching this url
let url = `https://docs.google.com/spreadsheets/d/${authSheetId}/externaldata/addimportrangepermissions?donorDocId=${sourceSheetId}`;
let token = ScriptApp.getOAuthToken();
let params = {
method: 'post',
headers: {
Authorization: 'Bearer ' + token,
},
muteHttpExceptions: true
};
UrlFetchApp.fetch(url, params);
}
console.timeEnd("addImportrangePermission");
return;
}
Issue:
In Workspace add-ons, callback functions executed when an Action triggers are limited to 30 seconds of execution time:
The Apps Script Card service limits callback functions to a maximum of 30 seconds of execution time. If the execution takes longer than that, your add-on UI may not update its card display properly in response to the Action.
Note:
Since you didn't provide the code related to the functions which are taking most time (e.g. assembleCMR, assembleInvoice, createPackingList) I cannot make any suggestion on that, but in any case you should try to improve the efficiency of those functions, or split it all into several Actions.
Reference:
Callback functions
Related
Insert page breaks in Google Docs with App script
I want to create a Google Docs file, provided that the number of pages must be even. I used the following code: function myFunction(){ var data = doc.getAs("application/pdf").getDataAsString(); var pages = data.match(/\/Contents/g).length; Logger.log(pages); if(pages % 2 !== 0) { var searchText = '----- End -----'; var res = Docs.Documents.get(docId); let offset = 0; const requests = res.body.content.reduce((ar, e) => { if (e.paragraph) { e.paragraph.elements.forEach(f => { if (f.textRun) { const re = new RegExp(searchText, "g"); let p = null; while (p = re.exec(f.textRun.content)) { ar.push({insertPageBreak: {location: {index: p.index + offset}}}); } } }) } offset = e.endIndex; return ar; }, []).reverse(); Docs.Documents.batchUpdate({requests: requests}, docId); } When running, I get the error: Preventing GoogleJsonResponseException: API call to sheets.spreadsheets.batchUpdate failed with error: Must specify at least one request. After some research, I fixed the line Docs.Documents.batchUpdate({requests: requests}, docId); to if (requests.length > 0) { Docs.Documents.batchUpdate({requests: requests}, docId);} } Now the code is running normally but in the generated Google Docs file, there are still no page breaks. I need help. Thank you.
From your showing script, I thought that you might my this answer https://stackoverflow.com/a/65745933 . If my understanding is correct, I thought that the reason for your current issue of Preventing GoogleJsonResponseException: API call to sheets.spreadsheets.batchUpdate failed with error: Must specify at least one request. is due to that in your Google Document has no value of var searchText = '----- End -----'. For example, when you put the value of ----- End ----- to the last page of your Google Document, this script can be used. But, from your error message, I'm worried that the value might not be included in your Google Document. So, in your situation, I thought that you might want to insert the page break to the last page. In this case, how about the following modifications? At the following modifications, the page break is inserted at the last of the document. Pattern 1: In this pattern, Docs API is used. From: var searchText = '----- End -----'; var res = Docs.Documents.get(docId); let offset = 0; const requests = res.body.content.reduce((ar, e) => { if (e.paragraph) { e.paragraph.elements.forEach(f => { if (f.textRun) { const re = new RegExp(searchText, "g"); let p = null; while (p = re.exec(f.textRun.content)) { ar.push({insertPageBreak: {location: {index: p.index + offset}}}); } } }) } offset = e.endIndex; return ar; }, []).reverse(); Docs.Documents.batchUpdate({requests: requests}, docId); To: var res = Docs.Documents.get(docId); var requests = [{ insertPageBreak: { location: { index: res.body.content.pop().endIndex - 1 } } }]; Docs.Documents.batchUpdate({ requests }, docId); Pattern 2: In this pattern, Docs API is not used. From: var searchText = '----- End -----'; var res = Docs.Documents.get(docId); let offset = 0; const requests = res.body.content.reduce((ar, e) => { if (e.paragraph) { e.paragraph.elements.forEach(f => { if (f.textRun) { const re = new RegExp(searchText, "g"); let p = null; while (p = re.exec(f.textRun.content)) { ar.push({insertPageBreak: {location: {index: p.index + offset}}}); } } }) } offset = e.endIndex; return ar; }, []).reverse(); Docs.Documents.batchUpdate({requests: requests}, docId); To: var body = doc.getBody(); doc.getBody().insertPageBreak(body.getChildIndex(body.appendParagraph(""))); From your script, unfortunately, I cannot know your doc. So, I guessed doc as Class Document object. Please be careful about this. Reference: insertPageBreak(childIndex)
Retrieve all my subscriptions in Google Sheets
I'm using YouTube Data API v3 and Google Apps Script for retrieve all my subscriptions. The problem I'm facing is that - using the following code, the response brings duplicated channels: do { const mySubsResponse = YouTube.Subscriptions.list('snippet', { mine: true, //channelId: "<MY_CHANNEL_ID>", maxResults: 50, fields: "pageInfo(totalResults),nextPageToken,items(snippet(title,resourceId(channelId)))" }); if (!mySubsResponse || mySubsResponse == undefined) { Logger.log('No subscriptions found.'); SpreadsheetApp.getUi().alert("No subscriptions found."); break; } // Loop all my subscriptions found in the response: for (let j = 0; j < mySubsResponse.items.length; j++) { const mySubItem = mySubsResponse.items[j]; sheet.getRange("H" + incrSub).setValue(mySubItem.snippet.title); sheet.getRange("I" + incrSub).setValue(mySubItem.snippet.resourceId.channelId); incrSub++; } nextPageToken = mySubsResponse.nextPageToken; } while (nextPageToken); I believe this is due each item in the response is actually the video uploaded by the channel I'm subscribed to - I don't think it's a problem with the page token. In the code above, I've commented the channelId parameter and I've testted with both: mine:true and channelId:<MY_CHANNEL_ID> and, the totalResults shows me I have 479 subscriptions, but, when I'm looping the results, For example, I'm subscribed to the channel called "Channel_1"; this channel had uploaded three videos today. The response of the code above brings me "Channel_1" three times, when it should be only 1 - because I'm subscribed to "Channel_1" once. What I want to get is a list of all channels I'm subscribed to. I've checked the subscriptions:list documentation, but, it's not clear how I can get my subscriptions only. If the subscriptions:list endopint is not the correct one for this task, which endpoint enables me to bring the desired results?1 1a list of all channels I'm subscribed to.
After checking more closely (and, I admit, after a little break I have), I finally found the problem and the solution: The problem is: I wasn't using the nextPageToken in every loop, so, basically, I was requesting the same page without actually making any pagination. In this section: const mySubsResponse = YouTube.Subscriptions.list('snippet', { mine: true, //channelId: "<MY_CHANNEL_ID>", maxResults: 50, fields: "pageInfo(totalResults),nextPageToken,items(snippet(title,resourceId(channelId)))" }); Can be seen that the pageToken: nextPageToken is not defined. Then, the solution is: Modify the code for sending the nextPageToken obtained. This is the modified code: // Call my subscriptions: /** Token pagination. */ var nextPageToken = ""; /** Row position where to start writing the results. */ var incrSub = 6; /** * Get all my subscriptions. */ do { const mySubsResponse = YouTube.Subscriptions.list('snippet', { channelId: "<MY_CHANNEL_ID>", // also works with "mine: true". maxResults: 50, // Here, the first time the call is made, the "nextPageToken" value // is empty. In every iteration (if "nextPageToken" is retrieved), // the "nextPageToken" is used - in order to get the next page. pageToken: nextPageToken, fields: "nextPageToken,items(snippet(title,resourceId(channelId)))" }); if (!mySubsResponse || mySubsResponse == undefined) { Logger.log('No subscriptions found.'); SpreadsheetApp.getUi().alert("No subscriptions found."); break; } // Write the subscriptions returned in the response: for (let j = 0; j < mySubsResponse.items.length; j++) { const mySubItem = mySubsResponse.items[j]; sheet.getRange("H" + incrSub).setValue(mySubItem.snippet.title); sheet.getRange("I" + incrSub).setValue(mySubItem.snippet.resourceId.channelId); incrSub++; } // Check the token: try { if (mySubsResponse.nextPageToken != null || mySubsResponse.nextPageToken != undefined) { nextPageToken = mySubsResponse.nextPageToken; } else { nextPageToken = undefined; break; } } catch (ex_page) { // An error occurred. Check closely the code. } } while (nextPageToken != undefined); With this modified code, all of my subscriptions are returned successfully.
Getting past permissions for a file through the API/Apps Script
I'm trying to create a list of files stored in my Google Drive and also a list of their current and previous permissions. Specifically, I want to create a list of files in my Google Drive which at any point in the past have had the 'Anyone with a link can view/edit (etc)' permission set. I have created a Google Apps Script to do this and I can iterate through all the files OK and I can get files which currently have that permission set, but I can't see a way to get the history of the file's permissions. I have found and activated the revisions list API: https://developers.google.com/drive/api/v2/reference/revisions/list This gets revisions but I can't see anywhere that it lists the sharing history of a revision. Is what I'm attempting to do possible?
It's definitely possible using the Drive Activity API. You can use the Quickstart for Google Apps Script to view all the activity of an item (file or folder) or done by a User. In this case I modified the Quickstart to show the Permissions changes of a given Drive Id. function listDriveActivity() { var request = { itemName: "items/1bFQvSJ8pMdss4jInrrg7bxdae3dKgu-tJqC1A2TktMs", //Id of the file pageSize: 10}; var response = DriveActivity.Activity.query(request); var activities = response.activities; if (activities && activities.length > 0) { Logger.log('Recent activity:'); for (var i = 0; i < activities.length; i++) { var activity = activities[i]; var time = getTimeInfo(activity); var action = getActionInfo(activity.primaryActionDetail); var actors = activity.actors.map(getActorInfo); var targets = activity.targets.map(getTargetInfo); if (action == "permissionChange"){ //Only show permissionChange activity Logger.log( '%s: %s, %s, %s', time, truncated(actors), action, truncated(targets)); } } } else { Logger.log('No activity.'); } } /** Returns a string representation of the first elements in a list. */ function truncated(array, opt_limit) { var limit = opt_limit || 2; var contents = array.slice(0, limit).join(', '); var more = array.length > limit ? ', ...' : ''; return '[' + contents + more + ']'; } /** Returns the name of a set property in an object, or else "unknown". */ function getOneOf(object) { for (var key in object) { return key; } return 'unknown'; } /** Returns a time associated with an activity. */ function getTimeInfo(activity) { if ('timestamp' in activity) { return activity.timestamp; } if ('timeRange' in activity) { return activity.timeRange.endTime; } return 'unknown'; } /** Returns the type of action. */ function getActionInfo(actionDetail) { return getOneOf(actionDetail); } /** Returns user information, or the type of user if not a known user. */ function getUserInfo(user) { if ('knownUser' in user) { var knownUser = user.knownUser; var isMe = knownUser.isCurrentUser || false; return isMe ? 'people/me' : knownUser.personName; } return getOneOf(user); } /** Returns actor information, or the type of actor if not a user. */ function getActorInfo(actor) { if ('user' in actor) { return getUserInfo(actor.user) } return getOneOf(actor); } /** Returns the type of a target and an associated title. */ function getTargetInfo(target) { if ('driveItem' in target) { var title = target.driveItem.title || 'unknown'; return 'driveItem:"' + title + '"'; } if ('drive' in target) { var title = target.drive.title || 'unknown'; return 'drive:"' + title + '"'; } if ('fileComment' in target) { var parent = target.fileComment.parent || {}; var title = parent.title || 'unknown'; return 'fileComment:"' + title + '"'; } return getOneOf(target) + ':unknown'; } Remember to enable the Drive Activity API in Resources > Advanced Google Services In my example this returns the logs: You can also look deeper into the Permissions by using the permissionChange Parameters in the query.
If you have a business/enterprise/edu account the admin audit logs will tell you this for 6 months of data. Or it will at least tell you when a permission was changed from x to y. Can't think of a method for personal.
Updating JSON file NodeJS
This is my code: const Discord = require('discord.js'); const client = new Discord.Client(); const TOKEN = "***********"; const PREFIX = "!"; client.on("ready", function () { console.log("Ready!"); }); client.on("message", function (message) { if (message.author.equals(client.user)) return; if (!message.content.startsWith(PREFIX)) return; var args = message.content.substring(PREFIX.length).split(" "); switch (args[0]) { case "rules": var _embed = new Discord.RichEmbed() .setTitle("Ruleset") .addField("Where is my order?", "Theres only one proper way to recive an order and help. Its a command .ticket") .addField("Why AZATEJ is such a bitch?", "If my status is 'dont disturb' and hue is way more red than green it means I have a reason to do so, im not a dick, but i recive a shitload of messages on daily route with stupid quiestions.") .addField("Dont ask stupid questions", "Stupid doesnt mean basic, we are up to help you but before you'll contact anyone read twice explanation documents and use a ticket.") .setColor(0x00FFFF) .setFooter("This message is coool !") .setThumbnail(message.author.avatarURL); message.channel.send(_embed); break; case "spotify": var uID = message.author.id; for (let i = 0; i < ftpr.buyers.length; i++) { if (uID === ftpr.buyers[i].id) { var _embed = new Discord.RichEmbed() .setTitle("Spotify") .addField("Username", "testsda#yahoo.com") .addField("Password", "ithastobe8") .setColor(0x00FFFF) .setFooter("Sincerely, LajgaardMoneyService") .setThumbnail(message.author.avatarURL); message.author.send(_embed); console.log(message.author.username + "(" + JSON.stringify(ftpr.buyers[i].id) + ") Just used the command !spotify"); break; } else { message.channel.send(message.author + "You haven't got a valid subscription. This command is locked until a new one is obtained!"); break; } } break; } }); client.on('guildMemberAdd', function(member) { console.log("User " + member.id + " has joined the server!"); //var role = member.guild.roles.find("name", "Google!"); var myRole = member.guild.roles.find("name", "Google!"); member.addRole(myRole); }); client.login(TOKEN); This is the JSON file: { "buyers": [ { "id": "1331499609509724162" }, { "id": "181336616164392960" }, { "id": "266389854122672128" } ] } When the bot is running and im changing one of the ID's the check function in case "spotify": is still using the old id. I do not want to restart the program every time the json file updates as it should be running 24/7. I have tried const fs = require("fs"); method but it gave me this error: TypeError: Cannot read property 'buyers' of undefined json Sincerely, Oscar
const fs = require("fs"); just loads the module. Put that at the top of your file. To read the json file each time you need to check user IDs (inefficient, but should get things working), put this at the top of your spotify case: case "spotify": var yourjsonfile = fs.readFileSync("yourjsonfile.json"); var ftpr = JSON.parse(yourjsonfile); var uID = message.author.id; for (let i = 0; i < ftpr.buyers.length; i++) { if (uID === ftpr.buyers[i].id) { Again, this is very inefficient - you are reloading the file every time you need to check it, and it uses readFileSync(), which blocks until the file is read (it is better to utilize node's asynchronous features). So as the JSON file grows larger, this will run slower. But at that point you probably need a database or some other mechanism for persisting and querying your data.
In-App purchases not working in chrome extension
The In-App purchases screen is returning: Screenshot of In-App screen The error is: An unexpected error has occurred. Please try again later. Dismiss And as you can see, half of it is also black. Buy.js: /*GOOGLE's CODE ... */ /*CODE FOR IN-APP PURCHASES*/ var prodButPrefix = "btnProdID-"; var statusDiv; function init() { console.log("App Init"); statusDiv = $("#status"); // getProductList(); } $("#div1").click(function() { checkIfBought("package2016"); }); /***************************************************************************** * Get the list of purchased products from the Chrome Web Store *****************************************************************************/ var purchased = false; function getLicenses() { console.log("google.payments.inapp.getPurchases"); console.log("Retreiving list of purchased products..."); google.payments.inapp.getPurchases({ 'parameters': {env: "prod"}, 'success': onLicenseUpdate, 'failure': onLicenseUpdateFailed }); } function checkIfBought(sku_init){ getLicenses(); function getLicenses() { console.log("google.payments.inapp.getPurchases"); console.log("Retreiving list of purchased products..."); google.payments.inapp.getPurchases({ 'parameters': {env: "prod"}, 'success': onLicenseUpdate, 'failure': onLicenseUpdateFailed }); } function onLicenseUpdate(response) { console.log("onLicenseUpdate", response); var licenses = response.response.details; var count = licenses.length; for (var i = 0; i < count; i++) { var license = licenses[i]; if(sku_init == licenses[i].response.details.sku){ //if purchased purchased = true; } addLicenseDataToProduct(license); } console.log(""); } function onLicenseUpdateFailed(response) { console.log("onLicenseUpdateFailed", response); console.log("Error retreiving list of purchased products."); } if(purchased == false){ buyProduct("package2016"); } } /***************************************************************************** * Purchase an item *****************************************************************************/ function buyProduct(sku) { console.log("google.payments.inapp.buy", sku); //var sku = ""; google.payments.inapp.buy({ 'parameters': {'env': 'prod'}, 'sku': sku, 'success': onPurchase, 'failure': onPurchaseFailed }); } function onPurchase(purchase) { console.log("onPurchase", purchase); var jwt = purchase.jwt; var cartId = purchase.request.cardId; var orderId = purchase.response.orderId; console.log("Purchase completed. Order ID: " + orderId); getLicenses(); } function onPurchaseFailed(purchase) { console.log("onPurchaseFailed", purchase); var reason = purchase.response.errorType; console.log("Purchase failed. " + reason); } This is what is being printed in the console: google.payments.inapp.buy package2016 When I close the In-App payment screen this is printed: onPurchaseFailed Object {request: Object, response: Object} Purchase failed. PURCHASE_CANCELED
I emailed Chrome Dev support, they said: In the Chrome Web Store, You are not allowed to purchase your own Apps/Extensions. In order to test your item, you need to access it from different account you have. (Not from your developer account).
You may have to make sure that you do not change/update the extension-id(nmmhkkegccagdldgiimedpiccmgmieda) in buy.js that they provide. Also, please stringify the onPurchaseFailed Object and share it so I can help confirm the issue.