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 data from a macro into a script.

I am very new to writing API scripts. Is there a way to get a numeric value entered (by user) in/with a macro and assign that to an API variable? (e.g.  macro:  [[?{Att%}]] ). It seems that the Script is only reading the macro code rather than the result. I appreciate any help.
Please post your Mod (API) script code.  Here's an example snippet of code that can take an input from a user and use it in a variable:       if('api'===msg.type && /^!self(\b\s|$)/i.test(msg.content)){         let content = args.slice(1).join(' ');           if(_.has(msg,'rolltemplate') && _.isString(msg.rolltemplate) && msg.rolltemplate.length){               content = content.replace(/\{\{/,'&{template:'+msg.rolltemplate+'} {{');           }           if(1 === args.length) {               return;           } else {               sendChat(`${who} (self whisper)`,`/w "${who}" ${content} `);           }           return;       } 
Thanks. I have no code, yet. Do you have the input or macro that this script is reading? I do not see how msg.content is being read by  args.slice(1).join(' ');
Ah whoops yeah there is an earlier section of the code (pulling from my Statblock Macromule Mod script ):    on("chat:message",function(msg){         let args = msg.content.split(/\s+/);         let who=(getObj('player',msg.playerid)||{get:()=>'API'}).get('_displayname');       if('api'===msg.type && /^!self(\b\s|$)/i.test(msg.content)){         let content = args.slice(1).join(' ');           if(_.has(msg,'rolltemplate') && _.isString(msg.rolltemplate) && msg.rolltemplate.length){               content = content.replace(/\{\{/,'&{template:'+msg.rolltemplate+'} {{');           }           if(1 === args.length) {               return;           } else {               sendChat(`${who} (self whisper)`,`/w "${who}" ${content} `);           }           return;       }    })
1733926968
timmaugh
Pro
API Scripter
Welcome to scripting, Kenneth! If you are using "API variable" to mean "JavaScript variable," then the typical way you do that is by parsing the command line. You would need to determine how your users would need to structure their commands so that your parser can properly split and identify each component part. Although Jarren's example shows splitting the command line on spaces: let args = msg.content.split(/\s+/); ...often scripters elect to use a space followed by double hyphens: !somehandle --arg1 --arg2 let args = msg.content.split(/\s+--/); Then, you might want to allow your user to construct their line in any order they wish (i.e., you'd allow them to specify arg2 before arg1, or maybe to not supply arg1 at all, etc.). To do that, you might want a recognizable name for each argument followed by the data for the argument after a demarcation. A hash and/or vertical pipe is often used as the demarcation character: !somehandle --arg1name#arg1data --arg2name#arg2data That way, when you get your array of arguments from splitting the line, you can iterate over them, split each at the first demarcation character, and test the name to see if it is something  you need to process (using the data supplied). Operating in this way, you can allow your user to supply them in any order; you can prioritize processing as necessary, if one is dependent on another. This is the link I typically share to get new scripters up to speed on some of the ins/outs of coding for the Roll20 platform. Just to point out a couple of items to start with off that list... I would look at the "Command Line Examination" conversation for more information about learning to construct your command line. Also, if you want to  understand the timing of certain things around chat messages, click on the "Scripts and Metascripts" link near the top of the list to watch a video that walks through the information (the first ~15 minutes is foundational to being a scripter; the next bit is specific to the idea of metascripts and how they interact with "standard" scripts, so you could skip that for now). Finally, I would not skip out on the links to the Inspector and ScriptInfo scripts; they can provide valuable info as you poke around at game elements and/or try to sort a code error. One other quick note about your example: [[ ?{Att%}]] Inline rolls are going to be in a different part of the message object (the data, at least). If you use the above syntax, what the user enters in the query prompt will be stored in the inline roll you have wrapped it in. What will be left in that place in the command line will be a roll marker (i.e., $[[0]] ), that serves as a pointer to the data you're looking for in the array of inline rolls (in this case, the "0th" roll in the array). There are handy ways to unpack rolls (from the fairly simple to the more robust, depending on what you want to do), so if you need to use rolls but can't crack it, post back and we can share more links/methods.
1733929448

Edited 1733929484
timmaugh
Pro
API Scripter
Also, since I don't think I've shared this anywhere, yet, I'll post the engine I've taken to using for command line parsing. This is customizable in a few ways: Choose what characters, following one or more spaces, mark s an argument (default: '--') Choose an array of characters that will split the argument data, i.e., splitting name and value (default: '|' or "#") Choose an array of ticks that can enclose component parts of an argument's value (default: single quotation mark, quotation mark, or tick) Choose whether to allow leading spaces before a tick, or if they need to be the first character immediately following whatever came before, i.e., a split character or a previous tick character (default: true) Those can all be set in the signature line of the tickSplit() function (in the code, below), or they can be overwritten for an individual call to the function. ( Usage and Example follow the Code) Code const tickSplit = (cmd, ticks = ["'", "`", '"'], split = ['|', '#'], mark = '--', leadingSpace = true) => {     const escapeRegExp = (string) => { return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); };     let index = 0;     let tokens = [];     let markrx,         openrx,         splitrx,         markbase;     class ArgToken {         constructor(type = '') {             this.type = type;             this.results = [];         }     }     const validate = () => {         if (             split && Array.isArray(split) && split.length &&             ticks && Array.isArray(ticks) && ticks.length &&             mark && typeof mark === 'string' && mark.length &&             cmd && typeof cmd === 'string' && cmd.length         ) {             markbase = `\\s+${escapeRegExp(mark).replace(/\s/g, '\\s')}`;             markrx = new RegExp(`${markbase}(.+?)(?:${split.map(s => escapeRegExp(s)).join('|')}|(?=${markbase})|$)`, 'g');             openrx = new RegExp(`^${markbase}`);             splitrx = new RegExp(`^($|${split.map(s => escapeRegExp(s)).join('|')})`);             return true;         }     };     const getTick = () => {         let tick = '';         ticks.some(t => {             let res;             let rx = new RegExp(`^${leadingSpace ? '\\s*' : ''}${escapeRegExp(t)}`);             if ((res = rx.exec(cmd.slice(index))) !== null) {                 tick = t;                 index += res[0].length;                 return true;             }         });         return tick;     };     const transition = (tick) => {         let res;         if (tick) {             let tickrx = new RegExp(`^${escapeRegExp(tick)}`);             if ((res = tickrx.exec(cmd.slice(index))) !== null) {                 index += res[0].length;             }         }         if (index < cmd.length) {             if ((res = splitrx.exec(cmd.slice(index))) !== null) {                 index += res[0].length;             }         }     };     const getPart = (token) => {         let tick = getTick();         let rx;         if (tick) {             rx = new RegExp(`^.*?(?=$|${escapeRegExp(tick)})`);         } else {             rx = new RegExp(`^.+?(?=$|${split.map(s => escapeRegExp(s)).join('|')}|${markbase})`);         }         let res = rx.exec(cmd.slice(index));         token.results.push(res[0]);         index += res[0].length;         if (index < cmd.length) {             transition(tick);         }     };     const getArg = () => {         let res;         markrx.lastIndex = 0;         if ((res = markrx.exec(cmd.slice(index))) === null) {             index = cmd.length;             return;         }         let token = new ArgToken(res[1]);         index += markrx.lastIndex;         while (index < cmd.length && !openrx.test(cmd.slice(index))) {             getPart(token);         }         tokens.push(token);     };     if (validate()) {         while (index < cmd.length) {             getArg();         }         return tokens;     } }; Usage Simply feed it a command line, and it will give you an array of objects with two properties each:  type - contains the "name" of the argument results - an array containing the data in that argument; multiple parts (designated by ticks) result in multiple items in the array let args = tickSplit(msg.content); Example Given a command line like: !somehandle --foo|Data for foo --bar| Play 'YaYa Ding Dong' --taco#'Volcano Man' "Actual Cannibal Shia LeBouf" 'Bob the Hirsute' ...the value of the return from tickSplit() would be: [{   results: ["Data for foo"],   type: "foo" }, {   results: ["Play 'YaYa Ding Dong'"],   type: "bar" }, {   results: ["Volcano Man", "Actual Cannibal Shia LeBouf", "Bob the Hirsute"],   type: "taco" }]
1733940475
GiGs
Pro
Sheet Author
API Scripter
Fascinating. But if ticksplit always gives exactly 2 items in the object, wouldn't it be better to have a result like this? I don't know the answer, which is why I'm asking. What is the advantaege of formatting the output as an array of objects, rather than a simple object like this: {   foo: ["Data for foo"], }, {   bar: ["Play 'YaYa Ding Dong'"], }, {   taco: ["Volcano Man", "Actual Cannibal Shia LeBouf", "Bob the Hirsute"], }
Thanks, this is all been very helpful. However, I cannot figure out how to use inlinerolls and/or rollresult to extract the values. I am looking for the simple way to unpack the rolls/values. This is the what appears in the macro generated chat: Att:  20  Def:  40  Roll:  91 This is what is being read by the script: zAtt: $[[0]] Def: $[[1]] Roll: $[[2]] This is my stab at what I thought might work. I cannot find any examples of inlinerolls or rollresult usage. on ( "chat:message" , function ( msg ) {     if ( msg . content [ 0 ] != "z" ) return ; {       let args = msg . content . split ( ' ' );       let values = args . inlinerolls . rollresult       let attack = values [ 1 ]       sendChat ( msg . who , '' + attack )     }   });  
1733972016
timmaugh
Pro
API Scripter
@GiGs... I gave the return this way o nly because I wanted to give maximum flexibility to the output... For most scripts you could imagine a single action/data point attached to each argument name even if the name were supplied a number of times. For something like that, what you suggest would be better. However, other scripts might chain outcomes one after the other.  For instance, when I initially wrote this, it was for the "replace" function of Plugger. In that one, you might have operations where you need them to happen one after another (like a cyclic permutation where you displace all A to C before B becomes A, then C becomes A). For that one, *all* of the find/replace arguments are called "--find," and the parts of them say what should be searched for, and what should be used to replace. It would be easy enough to throw a reduce() at the array that tickSplit returns in order to convert it to an object with unique properties... or to map the objects in the tickSplit return to be their "type" property and turn that into a Set (to get unique entries for each), then use another map operation to get the correct values out of the original array (maybe the last entry for that type, or maybe the aggregate of all, etc.). Lots of options if tickSplit is agnostic to what the data means -- just leave it all in there -- and leave it to you to massage it further. @Kenneth R.... There are a couple of options for getting the roll data into your command line. The simplest is to use a function like this:   const processInlineRolls = (msg) => {     if(msg.hasOwnProperty('inlinerolls')){       return msg.inlinerolls         .reduce((m,v,k) => {           let ti=v.results.rolls.reduce((m2,v2) => {             if(v2.hasOwnProperty('table')){               m2.push(v2.results.reduce((m3,v3) => [...m3,(v3.tableItem||{}).name],[]).join(", "));             }             return m2;           },[]).join(', ');           return [...m,{k:`$[[${k}]]`, v:(ti.length && ti) || v.results.total || 0}];         },[])         .reduce((m,o) => m.replaceAll(o.k,o.v), msg.content);     } else {       return msg.content;     }   }; (this is taken from The Aaron's TokenMod script, with one change for formatting, and one change from a conversation he and I had, but which he hasn't gotten around to updating/implementing -- long story) The above function is basically looking at the message's inlinerolls property and extracting the correct value for the roll (either the total or the table item if we're reading from a table). Since the index of the roll in the array in the inlinerolls property is the same as the roll marker (ie, the 0th roll in the array referenced by the $[[0]] roll marker, the index 1 roll by the $[[1]] roll marker, etc.), we can get the correct value for the correct roll, then perform a replaceAll() on the command line. To use the above function, you'd want to do something like:  let newContent = processInlineRolls(msg); Pretty straightforward. If you can't figure out what the processInlineRolls function is doing,  post back and I can break it down. Another Option: libInline libInline is a helper/library script I wrote with The Aaron that can simplify the work of getting other information from an inline roll. The upside is that all of the code for working with the rolls is off-loaded from your script; the downside (and it's not that big of a downside) is that you have to make sure libInline is installed in your game (or create a dependency if you are going to release your script to the one-click repository). There are a number of examples in that linked thread, so if you want to go this route, give them a try and let us know if you have any issue. Want to See What an Inline Roll Looks Like? If you want to see what an inline roll looks like (other than the examples available in that linked thread or in the wiki, you can use the Inspector script. With Inspector installed, run the command line: !inspect --inline [[2d20]] And replace the roll with whatever equation you want to use. You'll get an output of what the inlinerolls property looks like.