Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×
Create a free account

Getting Frustrated With API Scripts That Work for a Few Days and Then Don't Despite No Changes. :(

1597200684

Edited 1597204966
EDITED After Victor's comments. :) The following error has occurred today after I wrote the following code which is based on another, much more involved script.  The item that is causing the error is the for statement. I ran into this error today, but the more involved script I referred to was working perfectly until today and it also throws the same error. Both the code below and the more complex one run fine when one or three tokens are selected.  However, when I select all tokens, the error raises its head. A screen shot of the error message: Here is my code that I am wanting to develop into something a little more complex (I want to copy selected tokens to another page in the same location and change their tint colour or add one of the coloured token status).  The error is occurring in the  For loop2nd or 3rd line.  It did not do this until now. What I did do is "Let" the same variable names, except for the two tint colours (tintfriendly and tintenemy).  I figured because they were in different scripts and declared following different If statement that there would not be any confusion.  Could this be what is messing things up? on ('chat:message', function(msg) { let chatcommand = msg.content.trim().toLowerCase(); let sender = 'player|' + msg.playerid; if(msg.type == 'api' && chatcommand.startsWith('!copyunitstogm')) { let selected = msg.selected; let tintfriendly = "#cc0000"; let tintenemy = "#0000ff"; let ctr; //There will definitely be one selected! let selectedcount = 0; //log("---Start---"); if (selected === undefined) { sendChat(sender,"**=============**<br>**Copy to GM Map Process**<br>" + "**Error:** *No units are selected!*<br>Select a unit " + "and try again.", null, {noarchive:true}); return; } for (ctr=0; selected[ctr] !== undefined; ctr++) { selectedcount++; let token = getObj("graphic",selected[ctr]._id); //Assigns the token object to token //log("Name: " + token.get("name")); sendChat(sender, selectedcount + " " + token.get("name"), null, {noarchive:true}); let charid = token.get("represents"); //Determins if there is a character ID associated //log("charid: [" + charid + "]"); let unit = getObj("character", charid); } //log(selectedcount); sendChat(sender, "**Selected tokens: " + selectedcount +"**", null, {noarchive:true}); } }); As I mention in my title, this has worked beautifully before in the more complex script.  Suddenly, tonight both are not. I don't believe I've changed anything at all. Can anyone please help?  -- Tim
1597200913
Victor B.
Pro
Sheet Author
API Scripter
You've got a lot of code.  Log everything until you figure it out because there are others more nicer than me who will figure it out for you.  But if you want to go down this direction, log(whatever) will help you figure it out.  
Victor B. said: You've got a lot of code.  Log everything until you figure it out because there are others more nicer than me who will figure it out for you.  But if you want to go down this direction, log(whatever) will help you figure it out.   Thanks, victor.  What I should do is boil it down to where it is occuring.  The issue began when I started another script with a similar for.  I'll do that now.
1597204081
Ada L.
Marketplace Creator
Sheet Author
API Scripter
Try putting your event handler's body in a try-catch block and log any errors that it gets and their stack traces. It won't immediately fix your problem, but it'll give you a better visibility into where the issue is happening.
1597204494
The Aaron
Roll20 Production Team
API Scripter
You likely have a path object selected in addition to the tokens.  let token = getObj("graphic",selected[ctr]._id); //Assigns the token object to token //log("Name: " + token.get("name")); let charid = token.get("represents"); //Determins if there is a character ID associated //log("charid: [" + charid + "]"); Here token will be undefined if the selected item is a path or text object. Then the .get() will be on that undefined. 
Stephen L. said: Try putting your event handler's body in a try-catch block and log any errors that it gets and their stack traces. It won't immediately fix your problem, but it'll give you a better visibility into where the issue is happening. Thank you Stephan, but I'll have to look up what a try-catch block is.  I do everything in the roll20 console and find the error explanations to be helpful sometimes, but unlike some other development platforms I've used, it doesn't tell me where something is hiccoughing.  :) -- Tim
1597206674

Edited 1597207885
The Aaron said: You likely have a path object selected in addition to the tokens.  let token = getObj("graphic",selected[ctr]._id); //Assigns the token object to token //log("Name: " + token.get("name")); let charid = token.get("represents"); //Determins if there is a character ID associated //log("charid: [" + charid + "]"); Here token will be undefined if the selected item is a path or text object. Then the .get() will be on that undefined.  I think that must be what is happening, though for the life of me, I can't see a path, polygon, or text item on the tabletop.  However, I can select each token one at a time and run the code (which I have changed after Vicor's sensible suggestion) and for each token, it runs without difficulty.  I was experimenting with drawing paths for units proposed movement, so I am betting something was left behind, somehow.  That would be the difference from today compared to previous days. EDIT - selected everything on the objects & tokens layer and deleted.  Added new tokens, all worked fine when I did select all.  Added a single freehand path, did select all, and the error occurred. I will try to test to make sure the getObj("graphic",selected[ctr]._id) is defined.  Previously, while developing the originally posted script, I was only concerned with whether or not a token being evaluated in the for loop had a linked mook character or not. Once again, thank you for your help. -- Tim
1597206739

Edited 1597206792
try { // your code } catch (err) { // error handling code (like logging the error obj) } Above is a try-catch block in javascript.  try-catch is a standard exception (error) handling syntax most prominently used in OOP languages.  Javascript supports it. 
Victor B. said: TheAaaron is about the nicest guys you'll find.  Many many others on Roll20.  You want to learn about coding?  Log and log everything.  Everything until you figure out whats happening.  Then you'll figure it out.   I agree about Aaron!  And thanks on the logging tip.  I actually do log my butt off, based on previous experience in other systems and was about to follow your advice before Aaron spoiled me. :)  I had developed the code I originally posted (and replaced after your post) with considerable logging.  My problem was that I had not taken into account non-token graphics.  All the best, -- Tim
1597207066

Edited 1597207730
aisforanagrams said: try { // your code } catch (err) { // error handling code (like logging the error obj) } Above is a try-catch block in javascript.  try-catch is a standard exception (error) handling syntax most prominently used in OOP languages.  Javascript supports it.  That's pretty interesting.  So there is an error object in javascript after all.  TIL, thank you very much.  I will have to look into it, hopefully it has a line property, which would sure help immensely.  Gratefully yours (and, of course, to Stephen L. ) , -- Tim
1597208441

Edited 1597210424
Tim M said: I will have to look into it, hopefully it has a line property, which would sure help immensely.  though non-standard, almost all modern browsers will support stack property for error object.  i just got my pro account so i haven't played around with API script yet so i don't know how they consume the API script and whether their console will support the common non-standard properties of error. EDIT: Just tried a simple test code.  It does support stack, but you won't get much in the way of line information as your script isn't consumed like a file, but rather as a body of a http request.  Given this script, try { throw new Error("testing"); } catch (err) { log(err.stack) } I get this output "Error: testing at apiscript.js:8234:11 at Script.runInContext (vm.js:131:20) at Script.runInNewContext (vm.js:137:17) at Object.runInNewContext (vm.js:304:38) at Request._callback (/home/node/d20-api-server/api.js:3292:24) at Request.self.callback (/home/node/d20-api-server/node_modules/request/request.js:185:22) at Request.emit (events.js:310:20) at Request.<anonymous> (/home/node/d20-api-server/node_modules/request/request.js:1154:10) at Request.emit (events.js:310:20) at IncomingMessage.<anonymous> (/home/node/d20-api-server/node_modules/request/request.js:1076:12)" But, your error message in the original post should be a clue.  Put a debugging log statement before any calls to get method of an object.  Somewhere in your code is an "undefined" object that you are trying to access (which will raise an error).  In javascript an undefined is similar in concept to a null except javascript distinguishes unassigned variable and a variable that is explicitly assigned a null.  (If you want to get technical, undefined is both a primitive type and a primitive value with the type undefined - confusing I know - that indicates that the variable you're trying to access is not assigned any value, whereas null is a value that is intentionally assigned to a variable to indicate that the variable is not assigned any valid value.)
1597239611
Victor B.
Pro
Sheet Author
API Scripter
Tim, also look at Roll20 API Objects, specifically the find objects/get objects.  There's a few tricks when using the Roll20 objects.  Often, depending on what object you're going after, you need to specify the current player page, otherwise you pick up duplicate tokens from other unintended pages.  Find objects comes back as an array so you need to add a [0] at the end to get the first returned value.   Combat Master is on Roll20 and it has a ton of get tokens, characters, etc. Look at that code OR TheAarons Tokenmod or Group Init.  
1597239789
The Aaron
Roll20 Production Team
API Scripter
Tim M said: I think that must be what is happening, though for the life of me, I can't see a path, polygon, or text item on the tabletop.  However, I can select each token one at a time and run the code (which I have changed after Vicor's sensible suggestion) and for each token, it runs without difficulty.  I was experimenting with drawing paths for units proposed movement, so I am betting something was left behind, somehow.  That would be the difference from today compared to previous days. EDIT - selected everything on the objects & tokens layer and deleted.  Added new tokens, all worked fine when I did select all.  Added a single freehand path, did select all, and the error occurred. I will try to test to make sure the getObj("graphic",selected[ctr]._id) is defined.  Previously, while developing the originally posted script, I was only concerned with whether or not a token being evaluated in the for loop had a linked mook character or not. This is the construct I commonly employ for getting selected tokens: let tokens = (msg.selected || []) .map(o=>getObj('graphic',o._id)) .filter(g=>undefined !== g) ; That guarantees you an array of Token Objects. One thing that's bitten me in the past is text objects.  If you click to create a text object, then click off of it, it will leave an empty text object that is invisible hanging out there.  I'm 99% sure I developed the above explicitly because of that problem. 
1597240185

Edited 1597240213
Victor B.
Pro
Sheet Author
API Scripter
I had to add code, albeit much more verbose code than yours, to insure that we are dealing with graphic objects.  When selecting off a page, it's quite easy to pull in text objects and that does mess things up.  
1597240533
The Aaron
Roll20 Production Team
API Scripter
Tim M said: aisforanagrams said: try { // your code } catch (err) { // error handling code (like logging the error obj) } Above is a try-catch block in javascript.  try-catch is a standard exception (error) handling syntax most prominently used in OOP languages.  Javascript supports it.  That's pretty interesting.  So there is an error object in javascript after all.  TIL, thank you very much.  I will have to look into it, hopefully it has a line property, which would sure help immensely.  Gratefully yours (and, of course, to Stephen L. ) , -- Tim You can actually use the try-catch to decode to a line number in your script, which is pretty handy (proof of concept code): { let saveErrorLineOffset=0; try { throw new Error('get Line'); } catch(e3){ saveErrorLineOffset= parseInt(e3.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/,'$1'),10)-5; } try { (function(){ let b = function(ar){ ar.push('1'); return ar; }, f = (a) => b(a); f(3); }()); } catch( e2 ){ let scriptStack=_.chain(e2.stack.split('\n')) .rest() .map((l)=>{ let match=l.match(/([^ (]*):(\d+):\d+/), fileName=match[1].match(/[^/]*$/)[0], func=l.match(/^\s*at\s*([^(]*)/)[1], module = ({'apiscript.js':'SCRIPT','underscore.js':'UNDERSCORE'}[fileName]||'ROLL20'); return { //line: l, // file: match[1], // fileName: fileName, func: func, module: module, lineNum: match[2] }; }) .reject((o)=>o.module==='ROLL20') .value(); sendChat('',`Error happended in function ${scriptStack[0].func} on line ${scriptStack[0].lineNum-saveErrorLineOffset}.`); } } This works by throwing and catching from a known line, then using its line number as the offset to lines in your code.   Note that if you're using try-catch, you'll need to individually wrap each asynchronous entry point in order to catch errors.
1597240595
Victor B.
Pro
Sheet Author
API Scripter
where was this when I was coding combat master.  :(
1597241072

Edited 1597241098
The Aaron
Roll20 Production Team
API Scripter
In a file called "errorTest.js" a few subdirectories deep in my private development repo. =D You can tell how old it is since I'm using Underscore.js instead of modern JS. 
1597241364

Edited 1597241372
Victor B.
Pro
Sheet Author
API Scripter
I still use underscore everywhere, I feel so....dated.
1597241461
The Aaron
Roll20 Production Team
API Scripter
Only you can prevent forest fires!
1597241605
Victor B.
Pro
Sheet Author
API Scripter
ROFL...
Where does the -5 in the offset come from? Trial and error? Probably would want to store that as a constant at top level in case it ever changes. 
1597245524
The Aaron
Roll20 Production Team
API Scripter
It's the line number of the throw: throw new Error('get Line'); /* line 5 */ A constant is a possibility, but it's only used in that one place and tightly tied to the code in a non-introspectiony way, so I don't know that it buys you anything.
Ah okay. That makes sense. I was thinking it was some internal offset established via trial and error. 
Sorry I've been away from this for so long.  Got distracted and then my thalamus (I'm sure I grew up using the term "thalmus" for that part of the brain) decided to eff me up and the ambulance guys then had me twist an ankle really badly because of "operational health and safety".  Anyway, thanks for the response.  What I've been doing since is adding just small bits to my code at a time and then saving to see if all is well.  And logging the crap out of things, though I do use sendChat a fair bit too so I can see what I'm getting right on the tabletop.  With more practice, I am finding the error message helps, especially when errors happen in functions, so I am trying to break my main code up into component functions as much as possible as the message tends to identify the function in which it takes place.  Some errors don't appear until I run the code of course, but I'm finding things much better now. aisforanagrams said: Tim M said: I will have to look into it, hopefully it has a line property, which would sure help immensely.  though non-standard, almost all modern browsers will support stack property for error object.  i just got my pro account so i haven't played around with API script yet so i don't know how they consume the API script and whether their console will support the common non-standard properties of error. EDIT: Just tried a simple test code.  It does support stack, but you won't get much in the way of line information as your script isn't consumed like a file, but rather as a body of a http request.  Given this script, try { throw new Error("testing"); } catch (err) { log(err.stack) } I get this output "Error: testing at apiscript.js:8234:11 at Script.runInContext (vm.js:131:20) at Script.runInNewContext (vm.js:137:17) at Object.runInNewContext (vm.js:304:38) at Request._callback (/home/node/d20-api-server/api.js:3292:24) at Request.self.callback (/home/node/d20-api-server/node_modules/request/request.js:185:22) at Request.emit (events.js:310:20) at Request.<anonymous> (/home/node/d20-api-server/node_modules/request/request.js:1154:10) at Request.emit (events.js:310:20) at IncomingMessage.<anonymous> (/home/node/d20-api-server/node_modules/request/request.js:1076:12)" But, your error message in the original post should be a clue.  Put a debugging log statement before any calls to get method of an object.  Somewhere in your code is an "undefined" object that you are trying to access (which will raise an error).  In javascript an undefined is similar in concept to a null except javascript distinguishes unassigned variable and a variable that is explicitly assigned a null.  (If you want to get technical, undefined is both a primitive type and a primitive value with the type undefined - confusing I know - that indicates that the variable you're trying to access is not assigned any value, whereas null is a value that is intentionally assigned to a variable to indicate that the variable is not assigned any valid value.)