Unexpected exception upon serializing continuation Google Apps Script - google-apps-script

I recently started getting the error "Unexpected exception upon serializing continuation" on a spreadsheet Google Apps Script when trying to debug. The error seem to start after I created a connection to the Google CloudSQL api. This error still occurs even after commenting out the jdbc object constructor. It appears that others have had this issue and needed a Google Tech to resolve the issue.
I have searched all of the discussion boards for a solution to this issue with no luck. Any chance there is a Google tech out there who could take a look under the hood for me? I would post code if I could determine what line was actually triggering the error.
EDIT:
Ok, I think I have discovered where the error is occuring. Seems to be the
var response = UrlFetchApp.fetch(url + nextPage,oauth_options);
in the while loop. Here is the entire function code.
function retrieveEvents(endTimeMinimum, updatedAfter, orderBy){
//var url = 'https://www.googleapis.com/calendar/v3/calendars/' + source_cal + '/events?key=' + api_key + "&futureevents=true&orderBy=updated&sortOrder=descending&updatedMin=" + last_sync_date_formated;
//var url = 'https://www.googleapis.com/calendar/v3/calendars/' + source_cal + '/events?key=' + api_key + "&orderBy=updated&sortOrder=descending&updatedMin=" + last_sync_date_formated;
var url = 'https://www.googleapis.com/calendar/v3/calendars/' + source_cal + '/events?key=' + api_key + "&singleEvents=true";
if ((orderBy != null) && (orderBy != "")){
url += "&orderBy=" + orderBy;
}
else url += "&orderBy=updated";
if ((updatedAfter != null) && (updatedAfter != "")){
url += "&updatedMin=" + updatedAfter;
}
else url += "&updatedMin=" + last_sync_dateTime;
//if no endTimeMinimum is specified, the current time will be used.
if (endTimeMinimum == null || endTimeMinimum == ""){
endTimeMinimum = date_rfc339("Today");
}
url += "&timeMin=" + endTimeMinimum;
Logger.log("Request URL:" + url);
var largeString = "";
var events = new Array();
var nextPage = "";
var jsonObj
while(true){
var response = UrlFetchApp.fetch(url + nextPage,oauth_options);
largeString = response.getContentText();
if ((largeString != null) && (largeString != "")) {
jsonObj = JSON.parse(largeString);
}
if ('items' in jsonObj) events = events.concat(jsonObj.items);
if ('nextPageToken' in jsonObj){
nextPage = "&pageToken=" + jsonObj.nextPageToken;
continue;
}
break;
}
if (events.length == 0)return null;
return events;
}

OK, so I was able to make the problem go away by removing the try catch block inside a function that was called from inside a try catch block in the main function. I no longer am seeing the "Unexpected exception upon serializing continuation" when running the program from the debugger.
I wish I had a more solid answer on what causes this error and how to correct it.

In my experience, this is not an error caused by that line (or any other) specifically, but because an error is triggered within a loop. I haven't pinned down the exact replicable cause, but GAS seems to lose the loop pointer certain errors.
The best I can suggest is that any line that you suspect to causing an error within the while loop wrap with a try-catch that logs the error to the logger and proceeds. The loop pointer is then not lost and will debug as expected.

Related

Workaround for 403 error when using URLFETCH with Google Apps Script (external website)

I've used sof for many years (I almost always found all my answers!) but I'm quite stuck for the current project so this is the first time I post here. :)
I want to get the product price from www.hermes.com using either the URL or the product ref.
ex: https://www.hermes.com/fr/fr/product/portefeuille-dogon-duo-H050896CK5E/
ref = H050896CK5E
The URLs and Refs are stored in a Spreadsheet.
As I called UrlFetchApp.fetch function in my script, I got 403 error.
If my understanding is correct, that means the hermes.com server is blocking me out.
I also tried =IMPORTXML and it says that the spreadsheet cannot access the URL.
Here are the workaround I found: use Google Custom Search API to search the URL and iterate until the result URL matches the query.
[Current issues]
If the object is out of stock or if the URL is not found, I am unable to get the price.
ex:
when I search https://www.hermes.com/it/it/product/cappello-alla-pescatora-eden-H221007NvA259/
it returns me nothing.
I know it can return
https://www.hermes.com/it/it/product/cappello-alla-pescatora-eden-H221007Nv0156/
but not the same colour (and sometimes the price does change between colours)
So my question was:
How would you do to bypass the 403 error ? (not bypass security of course but if you have any ideas how to retrieve the hermes.com prices, please let me know!)
I will paste the scripts below.
Thank you in advance.
→ What I used for hermes.com.
With the muteHttpExceptions = true, I get the captcha html
var response = UrlFetchApp.fetch("http://www.hermes.com/",
{
method: "get",
contentType: "application/json",
muteHttpExceptions: true,
});
→ Result of above (a captcha html, I think hermes.com knows I'm a bot)
<html><head><title>hermes.com</title><style>#cmsg{animation: A 1.5s;}#keyframes A{0%{opacity:0;}99%{opacity:0;}100%{opacity:1;}}</style></head><body style="margin:0"><p id="cmsg">Please enable JS and disable any ad blocker</p><script>var dd={'cid':'AHrlqAAAAAMAs2XwactPh88AInQWTw==','hsh':'2211F522B61E269B869FA6EAFFB5E1','t':'fe','s':13461,'host':'geo.captcha-delivery.com'}</script><script src="https://ct.captcha-delivery.com/c.js"></script></body></html>
→ What I'm using now (Google Custom Search)
for (var i = 0; i < 5; i++) {
var start = (i * 10) + 1;
var apiUrl = "https://www.googleapis.com/customsearch/v1?key=" + apiKey + "&cx=" + searchId + "&q=search " + query + "&start=" + start;
var apiOptions = {
method: 'get'
};
var responseApi = UrlFetchApp.fetch(apiUrl, apiOptions);
var responseJson = JSON.parse(responseApi.getContentText());
var checkDomain = "";
for (var v = 0; v < 10; v++) {
if (responseJson["items"] != null && responseJson["items"][v] != null) {
checkDomain = responseJson["items"][v]["link"];
if (checkDomain != null && checkDomain == query) {
productPrice = responseJson["items"][v]["pagemap"]["metatags"][0]["product:price:amount"];
currency = responseJson["items"][v]["pagemap"]["metatags"][0]["product:price:currency"];
break;
}
}
}
if (productPrice > 0) { break; }
}

Google App Script: LockService doesn't work

I'm trying to implement a chat-bot using GAS (google app script). And faced some troubles with LockService. It seems not to work properly or at all.
When i get new message from appropriate chat, i saved delay-value (8 seconds) for ignoring subsequent messages from this chat. I do this using lockService to avoid skipping concurrent messages.
However it doesn't work, and some messages leaved out.
My code:
function get_message_safely(chat_id, message) {
var lock = LockService.getScriptLock();
const delay = 8*1000; // 8 seconds
// save time of getting message
var get_time = (new Date()).getTime();
// lock
// set lock before accessing data
try {
if (lock.hasLock()) { // ==always false (for unknown reasons)
sendLog('get_message_safely/hasLock', 'lock = true' + '\n' + message);
}
lock.waitLock(10000); // wait 10 seconds for others' use of the code section and lock to stop and then proceed
} catch (error) {
sendLog('get_message_safely/lock_service', 'ОШИБКА: ' + error);
send_message(chat_id, "Наши сервера сейчас загружены. Отправьте сообщение повторно или попробуйте позже.");
lock.releaseLock();
return;
}
// get data from PropertiesService
var scriptProperties = PropertiesService.getScriptProperties();
var chat_access_flag = scriptProperties.getProperty(chat_id);
if (chat_access_flag != undefined) { // if chat already exist
if (chat_access_flag == 'false') { // access denied
sendLog('get_message_safely/lock_service', 'LockService: доступ запрещен.\n' + message);
lock.releaseLock();
return;
}
var lock_time = Number(chat_access_flag);
if (get_time <= lock_time) { // access denied: lock_time is still actual
sendLog('get_message_safely/lock_service', 'LockService: доступ запрещен т.к. время не прошло:\n' + lock_time + ' - ' + get_time + ' = ' + (lock_time - get_time) + '\n' + message);
lock.releaseLock();
return;
}
sendLog('get_message_safely/lock_service', 'LockService: доступ разрешен:\n' + lock_time + ' - ' + get_time + ' = ' + (lock_time - get_time) + '\nnew_lock_time: ' + (get_time + delay) + '\n' + message);
}
scriptProperties.setProperty(chat_id, get_time + delay); // if access is allowed, set new delay
// lock release
lock.releaseLock(); // release the lock
get_message(chat_id, message); // handling message
}
So, sometimes i have the case (when i send a lot messages at the same time), when lockService should handle it but it doesn't:
Case (example):
// lock_time == a;
// get 1 message: access allowed. So set new value to lock_time (=b)
// lock_time == b;
// get 2 message: access denied. get_time < lock_time (=b)
// lock_time still == b;
// get 3 message: access allowed: get_time > lock_time, where lock_time == a (!???). So set new value to lock_time (=c);
// lock_time == с (??)
// get 4 message: access denied: get_time < lock_time (=b)
Handling messages due to lockService should be done sequentially. But some messages for some reason access to PropertiesService and get same date at same time.
It's blowing my mind. Have you any ideas what's happening here?
And how can i reach desirable result?
After studying your code I see that .hasLock() always returns false. Please, forgive me if I am mistaken. In your scenario this could mean that neither .waitLock() nor .tryLock() was called before. To fix the issue you have to use one of the mentioned methods. You can read on the links some example usages. Please, ask me any question if you need further help.

Getting response with NodeJS request module

I just started using the twitch kraken api and I have a few questions.
Whenever I attempt to get a JSON object there is no response. I am attempting to run this function through Amazon AWS Lambda, and don't have access to a console.
In the code below my callback function will always print out "SUCCESS got streamers ERROR". I am pretty certain right now the "ERROR" comes from my initial setting of result.
How come result does not get changed into the proper JSON?
I have used postman and it returns the proper thing with the query and param, and headers:
function getJSON(callback){
var result = "ERROR";
request.get(url(games[0]),function(error,response,body){
console.log("requested for url: " + url(games[0]));
var d = JSON.parse(body);
result = d.streams[0];//.channel.display_name;
// for(var i = 0; i < limit; i++){
// streamers.push(d.streams[i].channel.display_name)
// }
streamers.push(result);
});
if (streamers.length < 0){
callback("ERROR");
}else{
callback("SUCCESS got streamers " + result);
}
}
function url(game){
return {
url: "https://api.twitch.tv/kraken/streams/",//twitchlimit,
qs : {
'game' : 'overwatch',
'limit' : 2
},
headers: {
'Client-ID': clientID,
'Accept': 'application/json',
'Accept-Charset': 'utf-8',
}
};
}
I think your streamers code
if (streamers.length < 0){
callback("ERROR");
}else{
callback("SUCCESS got streamers " + result);
}
should be included in the request callback because currently it's not waiting for the request to finish, it's just carrying on so therefore the value of result will not change. Also the array length cannot be less than 0 so it will always go to the else and say "SUCCESS got streamers ERROR"
Thank you guys for the suggestions. I did have a few oversights and attempted to fix them.
I have implemented you suggestions and it seems to have worked a bit. I ended up putting the json.parse into a try/catch block, and moved the if/else statements inside the getJSON method. However, now I don't get any output.
This is how I am invoking the getJSON method:
function handleGameResponse(intent,session,callback){
//gets the game
var game = intent.slots.game.value;
if (!games.includes(game)){
var speechOutput = "You asked for: " + intent.slots.game.value;
//var speechOutput = "You asked for: " + games[game] + " That game is currently not an option. These are your current options: " + arrayToString(games)
var repromptText = "Please ask one from the current options.";
var header = "Invalid Game";
}else {
getJSON(function(data){
if(data !== "ERROR"){
var speechOutput = data; //capitalizeFirst(game) + " top three streamers are: " + arrayToString(streamers) + '.';
var repromptText = "Do you want to hear more about games?";
var header = capitalizeFirst(game);
}else{
var speechOutput = "I'm sorry, something went wrong and I could not get the streamers.";
}
//speechOutput = data;
});
//speechOutput = games[0] + " games[0], game= " + game; //this executes so the getJSON isn't executing
}
var shouldEndSession = false;
callback(session.attributes,buildSpeechletResponse(header,speechOutput,repromptText,shouldEndSession));
}
Does the above execute the same way? As in the shouldEndSession and callback execute before the getJSON has time to give a response?
For ref, this is the getJSON method now:
function getJSON(callback){
var result = "ERROR";
request.get(url(games[0]),function(error,response,body){
try{
var d = JSON.parse(body);
} catch (err){
callback("Sorry, something seems to have malfunctioned while getting the streamers");
}
result = d.streams[0].channel.display_name;
// for(var i = 0; i < limit; i++){
// streamers.push(d.streams[i].channel.display_name)
// }
streamers.push(result);
if (streamers.length <= 0){
callback("ERROR");
}else{
callback("SUCCESS got streamers " + result);
}
});
}

Intermittant DriveWriteVolume rateMax exception

I have been struggling with this error since about a week before DriveApp was released. I have a section of code that fails intermittantly with the error:
Service invoked too many times in a short time: driveWriteVolume rateMax. Try Utilities.sleep(1000) between calls.
Here is the code in question:
for(var a = 0; a<attachments.length; a++){
if(a > 0){
child = "." + (a + 1) + " ";
}
else{
child = ".1 ";
}
var parent = (m + 1);
Utilities.sleep(5000);
var file = attachmentFolder.createFile(attachments[a]);//This is the line that causes the error.
Utilities.sleep(1000);
file.rename(parent + child + attachments[a].getName());
}
I started with 1000ms, then gradually worked up to 5000 and it still throws the error every ~200 iterations. This is using DocsList.
I have had a similar problem in a script I'm working on.
The problem arises when I loop through the attachments to the message and attempt to save them too. If I skip the attachments, I don't have a problem, even with the same number of total file creations and the same sleep time between them. This is the code I'm using. newFolder.createFile(pdfBlob) does not cause a problem. newFolder.createFile(attachmentBlob) does.
// Create the message PDF inside the new folder
var htmlBodyFile = newFolder.createFile('body.html', messageBody, "text/html");
var pdfBlob = htmlBodyFile.getAs('application/pdf');
pdfBlob.setName(newFolderName + ".pdf");
newFolder.createFile(pdfBlob);
Utilities.sleep(sleepTime); // wait after creating something on the drive.
htmlBodyFile.setTrashed(true);
// Save attachments
Logger.log("Saving Attachments");
for(var i = 0; i < messageAttachments.length; i++) {
Logger.log("Saving Attachment " + i);
var attachmentBlob = messageAttachments[i].copyBlob();
newFolder.createFile(attachmentBlob);
Utilities.sleep(sleepTime); // wait after creating something on the drive.
} // each attachment

typeof fails on trigger object property

I am trying to add an dumpObject function to a Spreadsheet Container bound Script.
Ideally, it is for visibility into variables passed through triggers.
I can run it all day long from within the Script Editor, but when setup as either an onEdit event or onEdit Installible trigger, it dies with no error.
I did some trial and error toast messages and confirmed the code in dumpObject is being executed from the Trigger.
If you take this code below, setup onEdit2 as an installable trigger, you might see it.
To see it work as a Trigger, uncommment the first line //e of onEdit2.
Best I can figure, is something in the e object coming from the trigger that is not quite what is expected of an object?
This test should be limiting the maxDepth to 5, so I don't think I'm hitting the 1000 depth limit.
UPDATE: The problem is calling typeof on the trigger object properties. For example, "typeof e.user" reports the following error: Invalid JavaScript value of type
Thanks,
Jim
function onEdit2(e) {
//e = {fish:{a:"1",b:"2"},range:SpreadsheetApp.getActiveSpreadsheet().getActiveRange(),B:"2"};
Browser.msgBox(typeof e);
Browser.msgBox("U:" + Utilities.jsonStringify(e));
e.range.setComment("Edited at: " + new Date().toTimeString());
Browser.msgBox("ShowOBJ:"+dumpObject(e, 5));
}
function dumpObject(obj, maxDepth) {
var dump = function(obj, name, depth, tab){
if (depth > maxDepth) {
return name + ' - Max depth\n';
}
if (typeof obj === 'object') {
var child = null;
var output = tab + name + '\n';
tab += '\t';
for(var item in obj){
child = obj[item];
if (typeof child === 'object') {
output += dump(child, item, depth + 1, tab);
} else {
output += tab + item + ': ' + child + '\n';
}
}
}
return output;
};
return dump(obj, '', 0, '');
}
You're not getting quite what you expect from the event object. If you throw in:
for(var q in e) {
Logger.log(q + " = " + e[q])
}
and then check the View->Logs menu item in the script editor you get
source = Spreadsheet
user = <your user>
So, checking the docs, you can come up with this as an alternative to your e.range.setComment("Edited at: " + new Date().toTimeString());:
e.source.getActiveSheet().getActiveCell().setComment("Edited at: " + new Date().toTimeString());
note: you can debug an error like you were (secretly) getting by wrapping your statement in a try catch like so:
try {
e.range.setComment("Edited at: " + new Date().toTimeString());
} catch (ex) {
Logger.log(ex);
}
and then checking the logs as mentioned above (or dumping to Browser.msgBox(), if you prefer).
This might not be a great "answer" but it works.
I found that replacing typeof with Object.prototype.toString.call(obj) I got something usable.
Of note, the e object returns [object Object] but the properties (e.user) return [object JavaObject]
if (Object.prototype.toString.call(obj).indexOf("object") != -1) {
var child = null;
var output = tab + name + '\n';
tab += '\t';
for(var item in obj){
child = obj[item];
if (Object.prototype.toString.call(child).indexOf("object") != -1) {
output += dump(child, item, depth + 1, tab);