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

API Script Help Request

Smarter people than me, I have been learning how to do this API thing with the help of historical posts but I have come to problem that I can't seem to find anywhere on the forums, so I decided to start my own post. In general it is the first part of a script I am working on, partly just to teach myself a new skill and partly to make combat a bit quicker.  The script generates a profile that it stores as a character attribute so I can retrieve it later (when I get around to writing the next script).  For most of the characters and weapons it works fine, but for some reason on two characters it throws an error, see below.  I'm not sure what all information would be beneficial, so I'll throw in the code, error message and a sample of a message that works and one that fails.  I have disabled all other scripts, this is the only one running.  Your help is pre-appreciated! Code: function HMInit (cmds,playerID) {          function getWeapon (startElement) {                  let i = 0         if (Number.isInteger(Number(cmds[startElement+1])) == false) {             Loop1:             while (i<20) {                 cmds[startElement] += " " + cmds[startElement+1];                 cmds.splice(startElement+1, 1);                 if (Number.isInteger(Number(cmds[startElement+1])) == true) {                     break Loop1;                 }                 i++;             }         }         let returnArray =[];         returnArray.push(cmds[startElement]); //4= primary weapon name         //CP.push(cmds[7]); //Don't see the need to push the magic modifier to the CP at this time         returnArray.push(eval(cmds[startElement+2])); //5= primary weapon Attack Mods,         returnArray.push(eval(cmds[startElement+3])+Math.floor(Number(cmds[2]/2))); //6= primary weapon speed         returnArray.push(eval(cmds[startElement+4])+Number(cmds[2])); //7=primary weapon Defense         returnArray.push(cmds[startElement+5]+"+"+eval(cmds[startElement+6])); //8=primary weapon damage roll         return returnArray;     }     function getShield (startElement) {         let i = 0         if (Number.isInteger(Number(cmds[startElement+1])) == false) {             Loop2:             while (i<20) {                 cmds[startElement] += " " + cmds[startElement+1];                 cmds.splice(startElement+1, 1);                 if (Number.isInteger(Number(cmds[startElement+1])) == true) {                     break Loop2;                 }                 i++;             }         }         let returnArray =[];         returnArray.push(cmds[startElement]); //= primary weapon name         returnArray.push(Number(cmds[startElement+1]));         returnArray.push(Number(cmds[startElement+2]));         //log(returnArray);         return returnArray;     }          playerID = playerID.toString();     const initMod=eval(cmds[1])+Math.floor(Number(cmds[2]/2));     let initRoll = randomInteger(cmds[3]);     let init=initRoll + initMod;     if(init<1) init=1;     const fs = cmds[4]; //set fighting style see below for tags     //Two Handed Weapon,2HW| Weapon and Shield,WAS| One handed weapon only,1HW| One handed weapon used Two Handed,1HW2|     //Two one handed weapons attacking with both,21HWA| Two one handed weapons defending with secondary,21HWD|     //Shield Only,SO|Two Shields,2S|Ranged,RA     const charID = cmds[5];     const dr = Number(cmds[7]);     const top = Number(cmds[8]);     const topSave = Number(cmds[9]);     const charName = getAttrByName(charID, 'character_name');     var CP = [init,fs,charID,charName,dr,top,topSave]; //0=init 1=fighting style 2=character code 3=character name 4=dr 5=top 6=topsave     //Get weapon info based on fighting style     var wpnArray = [];     var shldArray =[];     switch(fs){         case "2HW":             wpnArray = getWeapon(11);             CP = CP.concat(wpnArray); //7=Weapon name, 8=Attack Mods, 9= weapon speed, 10=weapon Defense, 11=weapon damage roll             break;         case "WAS":             wpnArray = getWeapon(11);             CP = CP.concat(wpnArray); //7=Weapon name, 8=Attack Mods, 9= weapon speed, 10=weapon Defense, 11=weapon damage roll             shldArray = getShield(18);             CP.push(shldArray[0]); //12=shield name             CP.push(shldArray[2]); //13=shield dr bonus             CP[10] = CP[10]+shldArray[1]; //add shield defense             break;         case "1HW":             wpnArray = getWeapon(11);             CP = CP.concat(wpnArray); //7=Weapon name, 8=Attack Mods, 9= weapon speed, 10=weapon Defense, 11=weapon damage roll             CP[9] = CP[9]-1;             CP[10] = CP[10]-4; //add defense die             break;         case "1HW2":             wpnArray = getWeapon(11);             CP = CP.concat(wpnArray); //7=Weapon name, 8=Attack Mods, 9= weapon speed, 10=weapon Defense, 11=weapon damage roll             CP[9] = CP[9]+2;             CP[11] = CP[11]+"+3";             break;         case "21HWA":             wpnArray = getWeapon(11);             CP = CP.concat(wpnArray); //7=Weapon name, 8=Attack Mods, 9= weapon speed, 10=weapon Defense, 11=weapon damage roll             wpnArray = getWeapon(18);             CP = CP.concat(wpnArray);             CP.splice(15, 1);  //12=Weapon name, 13=Attack Mods, 14= weapon speed, 15=weapon damage roll             CP[9] = CP[9]+2;             CP[14] = CP[14]+2;             break;         case "21HWD":             wpnArray = getWeapon(11);             CP = CP.concat(wpnArray); //7=Weapon name, 8=Attack Mods, 9= weapon speed, 10=weapon Defense, 11=weapon damage roll             wpnArray = getWeapon(18);             CP = CP.concat(wpnArray);             CP[10] = CP[10]+CP[15]; //add secondary weapon's defense before removing it from the array             CP.splice(15, 1);  //12=Weapon name, 13=Attack Mods, 14= weapon speed, 15=weapon damage roll             log(CP);             break;         case "RA":             wpnArray = getWeapon(11);             CP = CP.concat(wpnArray); //7=Weapon name, 8=Attack Mods, 9= weapon speed, 10=weapon Defense, 11=weapon damage roll             break;         case "SO":             shldArray = getShield(11);             CP = CP.concat(shldArray); //7=shield name, 8=shield defense, 9= shield dr bonus             CP[8] = CP[8]+1             log(CP);             break;         case "2S":             shldArray = getShield(11);             CP = CP.concat(shldArray); //7=shield name, 8=shield defense, 9= shield dr bonus             log(CP);             break;     }     //declare current turn order and download.  add new person to end of pile, then upload.  ordering turns is done by CountUp.js     var turnOrder;     if(Campaign().get("turnorder") == "") turnOrder = []; //NOTE: We check to make sure that the turnorder isn't just an empty string first. If it is treat it like an empty array.     else turnOrder = JSON.parse(Campaign().get("turnorder"));     //does character already exist in array?     let position = turnOrderSearch (turnOrder,playerID);     if (position > -1) {         turnOrder[position] = ({id:playerID,pr:init,custom:""});     } else {         turnOrder.push({id:playerID,pr:init,custom:""});     }     Campaign().set('turnorder', JSON.stringify(turnOrder));     //tell user what initative result is     sendChat('','/em '+charName+' initiative is '+init+"<br>(Roll="+initRoll+", Mod="+initMod+")");     //Set Attribute function handles creating or editing attributes.     setAttribute("CombatProfile", charID, CP);      } function turnOrderSearch (workArray, value) {         let k=-1;         let j = workArray.length;         let i=0;         while (i<j) {             if (workArray[i].id == value) {                 k=i                 break;             }             i++;         }         return k;     }     function setAttribute (attribName, charID, attribValue) {         let setAttrib1 = findObjs({_characterid: charID,name:attribName});  //changed from var to let         if (setAttrib1.length == 0) {createObj('attribute', {name:attribName,current:attribValue, max:"",characterid: charID})};         let setAttrib2 = findObjs({_type: 'attribute', _characterid: charID, name: attribName})[0];  //changed from var to let         setAttrib2.set({current: attribValue});         return;     } on("chat:message", function(msg) {     if (msg.type == 'api' && msg.content.indexOf("!HM") !== -1) {         //Split out variables into commands         var cmds=msg.content.split(/\s+/);         var playerID = _.pluck(msg.selected,'_id');         if (cmds[0] == '!HM_Init') HMInit(cmds,playerID);     } }); Error message: Your scripts are currently disabled due to an error that was detected. Please make appropriate changes to your scripts and click the "Save Script" button and we'll attempt to start running them again. More info... For reference, the error message generated was: TypeError: a.split is not a function TypeError: a.split is not a function at K.t.G (/home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:18:285) at Vd (/home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:98:448) at Ud (/home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:97:126) at Vd (/home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:98:462) at Ud (/home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:97:126) at Td (/home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:96:62) at W (/home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:111:179) at ne.t.update (/home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:124:58) at J.update (/home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:146:469) at TrackedObj._doSave (/home/node/d20-api-server/api.js:850:27) Message that works right: {"content":"!HM_Init 1+0+3+0 1 4 2HW -KsZGwjopEAlP2lkgnD9 Reserved 3 10 8 Reserved Greataxe 0 0+0+0+0+1+0 14+0+0+-1+0 2+-1+1+0+-2 4d4!p 2+1+0+0 Greataxe 0 0+0+0+0+1+0 14+0+0+-1+0 2+-1+1+0+-2 4d4!p 2+1+0+0","playerid":"-KoPeGNLQhEIz5idhqDD","selected":[{"_id":"-L3yR2R374frRluEEC7B","_type":"graphic"}],"type":"api","who":"Eric J. (GM)"} Message that blows up: {"content":"!HM_Init -1+3+-1+0 0 4 2HW -KsZH-d40a0fdLwFj7OS Reserved 2 5 4 Reserved Short Bow 0 0+-1+2+0+0+1 12+0+0+-3+0 -1+3+0+0+-2 2d6!p 0+0+0+0 Nice Dagger 1 0+-1+2+1+0+1 7+0+0+0+-1 -1+3+1+0+-2 2d4!p 0+0+0+1","playerid":"-KoPeGNLQhEIz5idhqDD","selected":[{"_id":"-L3xfJWaItkexqzd217J","_type":"graphic"}],"type":"api","who":"Eric J. (GM)"}
1528083683

Edited 1528083897
The Aaron
Pro
API Scripter
So, you've got only the one call to .split() in there, but it shouldn't be failing unless msg.content is not a string.&nbsp; I don't think that's what the issue is, but just to rule it out, try doing: on("chat:message", function(msg) { &nbsp; &nbsp; if (msg.type == 'api' && msg.content.indexOf("!HM") !== -1) { &nbsp; &nbsp; &nbsp; &nbsp; //Split out variables into commands &nbsp; &nbsp; &nbsp; &nbsp; if(Object.isString(msg.content)){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var cmds=msg.content.split(/\s+/); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var playerID = _.pluck(msg.selected,'_id'); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (cmds[0] == '!HM_Init') HMInit(cmds,playerID); &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; } }); Based on the call stack, I suspect the crash is actually happening inside Roll20 and is the result of data being expected to be a string.&nbsp; Try changing your setAttribute() function to: &nbsp; &nbsp; function setAttribute (attribName, charID, attribValue) { &nbsp; &nbsp; &nbsp; &nbsp; let setAttrib1 = findObjs({_characterid: charID,name:attribName});&nbsp; //changed from var to let &nbsp; &nbsp; &nbsp; &nbsp; if (setAttrib1.length == 0) {createObj('attribute', {name:attribName,current:attribValue, max:"",characterid: charID})}; &nbsp; &nbsp; &nbsp; &nbsp; let setAttrib2 = findObjs({_type: 'attribute', _characterid: charID, name: attribName})[0];&nbsp; //changed from var to let &nbsp; &nbsp; &nbsp; &nbsp; setAttrib2.set({current: `${attribValue}` }); &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; } For the record, using eval() is generally considered a very bad idea.&nbsp; It looks like you're using it to do simple math, you might consider wrapping those in inline rolls instead and sub'ing in the result (Here's a function that does the sub'ing for you:&nbsp; <a href="https://wiki.roll20.net/API:Cookbook#processInline" rel="nofollow">https://wiki.roll20.net/API:Cookbook#processInline</a>... ). Good luck!
Fantastic, Thanks!&nbsp; The setAttribute() fix did it.&nbsp; If you have another moment, why?&nbsp; It looks like '${ x }' turns x into a string?&nbsp; And isn't the .set looking for an object (I guess not)? I'll take a look at those eval statements and see what I can do about them.&nbsp; Thanks again for your really quick help.&nbsp;
1528087214
The Aaron
Pro
API Scripter
The `${x}` (back ticks) does turn it into a string. The .set() function doesn’t check the type of the data it’s setting, but some of the Roll20 code has implicit expectations that the data will be a string. I’ve run into similar call stacks in the past when attributes got set to NaN as a result of a bad math operation. Any time an attribute is set from a character sheet input or from the attributes and abilities&nbsp;panel, it’s set as a string, so best just to mimic that. (Also take note if you need to do math on the value at some point, parseInt() and parseFloat() are your friends. ) No worries! =D
1528121502
The Aaron
Pro
API Scripter
Just an example of the problem with eval: !HM_Init state={} 1 4 2HW -KsZGwjopEAlP2lkgnD9 Reserved 3 10 8 Reserved Greataxe 0 0+0+0+0+1+0 14+0+0+-1+0 2+-1+1+0+-2 4d4!p 2+1+0+0 Greataxe 0 0+0+0+0+1+0 14+0+0+-1+0 2+-1+1+0+-2 4d4!p 2+1+0+0 Would likely delete your entire state.
Thanks - now I'm lost.&nbsp; In the script I'm using eval to turn the string value "2+-1+1+0+-2" into a number of value 0. Where did state come into it and what is it doing?
1528123032

Edited 1528123065
The Aaron
Pro
API Scripter
Because I passed "state={}"&nbsp; instead of "2+-1+1+0+-2" and eval executes code.&nbsp; "state={}" would set the state to {}, the empty object. Just saying that eval(), particularly executed on arbitrary arguments, permits for code injection exploits and weird bugs. =D
1528131176
Jakob
Sheet Author
API Scripter
The Aaron said: Because I passed "state={}"&nbsp; instead of "2+-1+1+0+-2" and eval executes code.&nbsp; "state={}" would set the state to {}, the empty object. Just saying that eval(), particularly executed on arbitrary arguments, permits for code injection exploits and weird bugs. =D Fun with eval()!
Got it, Thanks again!
1528134077
The Aaron
Pro
API Scripter
No problem! =D