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

How to build a query into an api command line

Hello I am planning an API to roll spell tables for dungeon crawl classics, i want to have a query for a modifier and then feed that modifier into the API command string, i searched the forums and i did see that order is very important when dealing with macros and API. but i was unclear if i could use a query inside an api string and if it would pass the value on. I'm still theory crafting the script so if the api string looked like: !spellRoll @{selected|token_id} [[1d20]] ?{Modifier|0} just a rough draft but would an idea like that work?
1675138394

Edited 1675140092
timmaugh
Pro
API Scripter
Yes, that will work. The Script Moderator receives the message object after Roll20 parsers have done their thing. That is covered in the Order of Operations ... so all inline rolls, queries, attributes, abilities, and macros are handled in this phase. When your script gets the message, then, it will have a command line more like: !spellRoll -M1234567890abcdef $[[0]] 2 A couple of things to think about... 1) Setup an easy way to parse your command line, and setup an easy way for users to remember how to use it. Right now it looks like you're going to split on white space, and your arguments will be hard-coded to come in order (which can be difficult for the user to remember). The most user-friendly scripts, in my opinion, use some sort of argument format like: --arg=value --arg|value --arg#value --arg sub=value --arg sub|value --arg sub#value ...or some permissible combination of those. That lets you split on white-space-double-hyphen (regex: /\s+--/g), and it lets your user put the arguments in any order, since once you've split the command line you can examine each arg/value pair to determine the name of the setting the user is passing. 2) Inline rolls get replaced with roll markers like I showed ($[[0]]), starting with the 0th roll. You can extract the values with a simple function like this: if (_.has(msg, 'inlinerolls')) {   msg.content = _.chain(msg.inlinerolls)     .reduce(function (m, v, k) {       m['$[[' + k + ']]'] = v.results.total || 0;       return m;   }, {})   .reduce(function (m, v, k) {     return m.replace(k, v);   }, msg.content)   .value(); } That's an older version of that function which doesn't take into account tables. I think Aaron proposed a more updated one. The other alternative with inline rolls, especially if you need more than a simple value from the roll, is to use libInline and have it handle your inline roll parsing for you. If you're interesting, here's a post discussing parsing of the command line...
Thank you Tim :)
I am still plugging away at this, i'm not a pro scripter but i am learning a ton from taking this on. Where do i find a guide on making fancy HTML outputs in my chat return or can I use a Chat template to build a spell card output to chat?
1678071020
timmaugh
Pro
API Scripter
Good to hear you're making progress, Isaac. Having a good looking HTML output is probably as important as the underlying code, in my opinion. I've seen many a helpful script struggle under a clunky output or interface. There's hardly a quicker way to have your script leave a user unsatisfied with what you've done. w3schools.com has a lot of lessons and do-it-yourself opportunities to learn, I would start there. A few things to bear in mind, as you get started: Your panels won't have header and body sections. You're working within a self-contained tag embedded in the larger page. That may not make sense now, but as you learn HTML and CSS, you'll be looking at lessons that are aimed at teaching to build a *page*... which you're not really doing. Your CSS will need to be inline. Again, file this one away as you are learning HTML & CSS... but there are different ways to apply a style to an element: from an external file, from a style block in the header, or individually in each tag that requires formatting. Roll20 will be in that last category... so when lessons tell you "don't mix style and content", you have a reason to tell them to kindly go sit in the corner. You can code html anywhere...  you can write it in notepad, save the file as html, and open it with your browser. jsfiddle.net is another great tool for that. It will let you keep your css and html separate (I know, I just told you it was going to have to be together... bear with me a minute: it's tough to make 2 points at once). Using JSFiddle, you can mockup what you want your interface/output to look like in HTML, how you need to style it with your CSS, and then once you like what you have, you can figure out how to break it down with javascript so that you can template it up and replace the parts that you know you have to replace. Because it lets you keep your HTML and CSS separate, you can more quickly fashion the look you want... instead of making the same change in each of 12 items that you need to look the same way, you give them the same CSS class and you adjust your CSS rules to style those elements. Then, when you're done, you can use a CSS-inliner to re-render your HTML so that your CSS rules are written to the tags that rely on them for their formatting. Some tags are disallowed by the Roll20 parser. Sucks, but that's the way it is. As for using chat templates, the answer to your question is yes and no. You can use chat templates for sure: you can send a message from  your script that will trigger and utilize a chat template. However, most of the time, you have more dynamic content you want to display... for example, an inline roll with the hoverable roll tip. You can't use a template in your code and look for ways to insert your data into the html... you can only send a message that instructs the Roll20 parsers to use the template, and you can put your data in the various parts (that is, in the command line) as necessary. The trouble comes when things might resolve IN that message. For instance, an inline roll. If you script relies on that roll to do something, you're typically going to need to catch the message, parse the roll, and figure out what you need to do. Listening for and taking action on normal messages (like roll templates) can be done, but it takes a different approach... and can't stop the original message from hitting the chat. You're getting into some wide waters, with too many maybe-s, what-ifs, and watch-out-fors to proactively name. I would start with the w3 schools and jsfiddle links and start playing, then come back with specific questions. Good luck!
Hi Tim, i am getting a strange error in my listener that i wasn't getting before i switched my handler to follow along the lines of what you were saying about sanitation ect i'll post the Code: // macro !spellroll --@{selected||token_id} --[[1d20+x]] --?{Spellburn|0} --spellNameCamelCase //begin script var spellRoll = spellRoll || {};   const registerEventHandlers = () => {         on ( 'chat:message' , handleInput );     }; function handleInput ( msg ) {     if ( msg . type !== "api" ) { // <== tests the msg type to be 'api'         return ;     }         let args = msg . content . split ( " --" );         if ( args . shift () === '!spellRoll' ) spellRoll ( msg , args );         log ( args );         let character = getObj ( "graphic" , args . shift ());         let characterId = character . get ( "represents" );         let dieRoll = args . shift ();         let spellBurn = args . shift ();         let spellName = args . shift ();         let totalRoll = dieRoll + spellBurn ;         log ( characterId , dieRoll , spellBurn , spellName , totalRoll );                 } and here is a pic of the error in the chat box: What do you think is causing this? can i not have -- before --@{selected||token_id}/?
1678073332
timmaugh
Pro
API Scripter
You're getting that error because you have 2 vertical pipes in a row... so it doesn't recognize that there's an attribute name it should be returning. Also, 2 quick notes... you're testing the message to see if it is an api message, but you are not testing it to see if it belongs to this script.  You'll want something like that in there: if (msg.type !== 'api' || !/^!spellroll\b/.test(msg.content)) return; That says that if the command line does not begin with your chosen script handle (spellroll), this script should do nothing. That's what you want if a message comes through for TokenMod, ChatSetAttr, Reporter, etc. Also, you're splitting on a space followed by hyphens, which will work... but what if the user inputs more than one space? I would suggest this regex as the splitter: /\s+--/ That takes one-or-more whitespace characters followed by a double hyphen. It's a little more fool-proof.
timmaugh said: You're getting that error because you have 2 vertical pipes in a row... so it doesn't recognize that there's an attribute name it should be returning. Also, 2 quick notes... you're testing the message to see if it is an api message, but you are not testing it to see if it belongs to this script.  You'll want something like that in there: if (msg.type !== 'api' || !/^!spellroll\b/.test(msg.content)) return; Oh i see that now thank you, also what is that concatenation your using in this conditional i will have to go back into my js book and read about it! Also, you're splitting on a space followed by hyphens, which will work... but what if the user inputs more than one space? I would suggest this regex as the splitter: /\s+--/ That takes one-or-more whitespace characters followed by a double hyphen. It's a little more fool-proof. I started using that but i could not get it to function in vanilla js I wrote a script to learn to split strings to test if i had it right. But i can use it for the mod script. Also it's likely i was not concatenating it correctly because i skipped some section in the book lol. Thank you! Issac
1678135218
timmaugh
Pro
API Scripter
I think what you're asking about in that conditional is the Logical OR operator... so that statement is asking if this OR that is truthy. The Logical AND operator tests if both conditions are truthy. Incidentally, a good thing to know is that both actually return the result of one of the operands... for the OR it returns the first truthy result; and for the AND it returns the first falsey result, or the last operand result if they are all truthy... this can be useful when assigning values to variables: const funky = (userOption) => {     let foo = userOption || 'bar';     console.log(foo); }; funky(); funky('baz'); As for an example of splitting the command line, here is a write-up I just did for someone else... or here is another example of a different parsing of the command line.
1678228749

Edited 1678230605
if this is my handler: function handleInput ( msg ) {     if ( msg . type !== 'api' || ! / ^ !spellroll \b / . test ( msg . content )) return ;   // <== tests the msg type to be 'api'           var args = msg . content       . split ( /\s + --/ )       . map ( a => a . split ( /=/ ))       . map ( a => [ a [ 0 ]. toLowerCase (), a [ 1 ]])       . filter ( a => [ "level" , "roll1" , "roll2" ]. includes ( a [ 0 ])); I know they say there are no stupid questions yet i have the haunting suspicion this is one, here map is going to produce an array for me is that array named a[0, 1, ect] or is it named args? also so i need to process inline rolls with the standard method after the filter or does the filter cover all of that and leave me with a number value for my die roll?
1678250012
timmaugh
Pro
API Scripter
No worries. Everybody starts in the same place. =D See this declaration: var args = msg.content.... ? That is the assignment that will happen after everything on the right side of the equals sign is evaluated (included the subsequent split, maps, and filter). It will all be assigned to the variable args . (Also, I'd suggest staying away from the var declaration; use const or let... here is a good discussion as to why...) The 'a' that you see is a product of how arrow function syntax works. You are giving the map operation a function that will be applied to every iterable item in the parent object (obviously the parent must be iterable this way for map() to work). The function you supply will be applied to every item in turn, and while the item is being evaluated, you need a way to reference it. That's what the 'a' is... the argument under examination in the supplied function. After the initial split, we have an array of items. ['foo=bar','baz=leaky'] Then the map runs, which looks at each argument (as 'a'), and splits it again... this time on the equals sign. Now your array looks like: [ ['foo','bar'], ['baz','leaky'] ] ...so future map or filter operations, looking at each iterable item ('a'), will need to refer to a[0] for the 0th item, and a[1] for the 1st item. Those references are scoped to only the arrow function you supply, and only are meaningful in the context of having been filled with the data from one of the items. Once all of that is done, the result will be assigned to your args variable. Regarding your inline rolls, the method I described in the linked discussion was for detecting the roll index... not the value. When you see $[[0]] in your command line, you know that an inline roll was found in that location. The number you see there is the roll index (referring to the roll's position in the message object's inlinerolls property. The value of that roll is completely different. You can get it from the inlinerolls property (from the roll at the correct index), or if you want to replace the $[[0]] from your command line with the value of the roll, you can use something like this: msg.content = msg.inlinerolls .reduce((m,v,k) => { m['$[['+k+']]']=v.results.total || 0; return m; },{}) .reduce((m,v,k) => { return m.replace(k,v); },msg.content); That reduce() function is another function like map(), split(), and filter() that let you tranform a dataset. These are super powerful functions that are really essential to doing anything on Roll20. If you want an explanation of how that reduce() operation works, you can read about that here . The version in that link is slighlty older (and uses the underscore library), but that change really amounts to a cosmetic alteration... nothing functional is changing.
Ahh thank you again Tim for the clarifications! I need to use the variables i will extract from args outside the block scope but i could keep args block scope and just assign the variables i need as local. Thank you again for showing me how to filter, split, map, and reduce it took me a few reads but i understand the syntax now :)
I wrote a script do i could "see" the outputs and also so i could type all the syntax out myself so i would get it. My script runs without errors and also produces no output? So i am obviously missing something in the syntax. I will keep working it. Code: var inlineTest = inlineTest || {} const registerEventHandlers = () => {     on ( 'chat:message' , handleInput ); function handleInput ( msg ){     if ( msg . type !== 'api' || ! / ^ !inlineTest \b / . test ( msg . content )) return ;           let args = msg . content       . split ( /\s + --/ )       . map ( a => a . split ( /=/ ))       . map ( a => [ a [ 0 ]. toLowerCase (), a [ 1 ]])       . filter ( a => [ "level" , "roll1" , "roll2" ]. includes ( a [ 0 ]));       }     msg . content = msg . inlinerolls         . reduce (( previous , current , index ) => {             previous [ '$[[' + index + ']]' ] = current . results . total || 0 ;             return previous ;         },{})         . reduce (( previous , current , index ) => {             return previous . replace ( index , current );         }, msg . content );     function chatLog (){         sendChat ( "GM" , `chat test  args array ${ args } + msg.content array ${ msg . content } ` );     } chatLog ();       }
1678371776
timmaugh
Pro
API Scripter
You're getting there, Isaac... let's clean up a few things. First, it looks like you're trying to follow a namespace pattern: var inlineTest = inlineTest || {}; ...except that after that, you don't assign your functions to that namespace. handleInput() is in the same global namespace as where you declared inlineTest, instead of attached to your inlineTest object like this: inlineTest.handleInput = function(msg) { ... } or inlineTest.handleInput = msg => { ... } With unattached functions, you're beginning to clutter the global space. Many scripts might want a function named "handleInput", and if they all register to the global namespace, only the last one to load will win (provided they're all declared with var or let; const would throw an error; more on that in a minute). That's why when people use the namespace pattern like this, they typically write it: var inlineTest = inlineTest || (function () { // ... code goes here })(); That creates an IIFE (immediately invoked function expression), which does a number of things... importantly, it carves out a corner of the namespace for your objects so your script doesn't step on (or get stepped on by) other scripts. Within that object, you're going to want individual functions. Right now, you're missing the right hand brace closing your functions, so they are running together. I'm not even sure you have the right number of closures. A good IDE should be able to alert you to that fact, or you can always use the closure compiler to check. So, within your script closure, you're going to want your handleInput(); you're going to want your registerEventHandlers() (which registers handleInput() to answer a chat event); you're going to want your chatLog() function to output/report the status of things, and you're going to want a function that will actually run the registerEventHandlers() function when everything is ready. Lucky for us, there is a 'ready' event that we can execute when the sandbox is loaded. Altogether, that would look more like this: var inlineTest = inlineTest || (function () {   'use strict'   const handleInput = function(msg){     if (msg.type !== 'api' || !/^!inlineTest\b/.test(msg.content)) return;           function chatLog(){       sendChat("GM", `chat test  args array ${args} + msg.content array ${msg.content}`);     };      let args = msg.content       .split(/\s+--/)       .map(a => a.split(/=/))       .map(a => [a[0].toLowerCase(),a[1]])       .filter(a => ["level", "roll1", "roll2"].includes(a[0]));       };     msg.content = msg.inlinerolls       .reduce((memo, current, index) => {         memo['$[['+index+']]']=current.results.total || 0;         return memo;       },{})       .reduce((memo, current, index) => {         return memo.replace(index, current);       },msg.content);     chatLog();          };   const registerEventHandlers = () => {     on('chat:message', handleInput);   }; on('ready',() => { registerEventHandlers(); }); })(); I would point out, too, that I renamed the "previous" argument in your reduce() statements... in the context of "current" and "previous" that might mean something a little different from what it means in a reduce() function. I named it "memo", because that argument refers to the single object that is being passed through every iteration... ie, the object you are building. The reduce() function will take the first item in the array as the memo if you don't supply a starting value for the memo, but that means you're working with the "previous" item only during the first pass. After that, you're working with whatever you have returned as the memo. Then... ...once all of that makes sense, I would point out that this namespace pattern has a few weaknesses, still. For instance, the entire structure of it (built with a "var" declaration that can be redeclared) means that you don't know if your script has been stepped on by something that comes later with the same name. You'd probably much rather know immediately if you had a name collision, like that, so you can figure out a workaround and get both scripts functional again. Most larger scripts people write these days follow something called the Revealing Module Pattern. Aaron shared a good  starting template for that. You should be able to map the component parts of your script over the top of what he provided. Also, if you look at that template, you'll see an instance where he used the var namespace pattern (with API_Meta ). This is a good example (in fact, the only example I've encountered on Roll20) where this pattern is necessary. The idea is that many scripts might need to access the API_Meta object, so they might all try to declare it. If another script has already declared it (and stashed information, there), we don't want to lose that when the next script tries to make sure it exists and stash its own information there, too. The API_Meta object provides many benefits, which Aaron discusses in that link. The benefits are also leveraged in the ScriptInfo script (and the ways to implement it in your script are discussed in that thread, too). In Aaron's template, it is already fully implemented, so you don't have to go through the steps, but reading through the pieces can help you understand what they do. As always, post back with more questions!
Oh thank you, I'm going to pick apart the template not that i can do better but the only way to learn is to do it :) Thank you Tim! Issac