We use Cookies to help personalize and improve Roll20. For more information on our use of non-essential Cookies, visit our Privacy Policy here.
Accept
Advertisement Create a free account

Are these bugs, or expected behavior (chat input)?

1593204821

Edited 1593205245
timmaugh
API Scripter
I've been developing a script recently that has some i/o with the chat. Because it is under development, it is also not yet fully user-proofed. During my testing, I just encountered two very strange situations where the chat was "leaky" in terms of letting the api run. I'm wondering if these are bugs (I will open a bug report), or if this is exactly the expected behavior. And if it is expected, I'm hoping someone can explain the rules of why it's happening so I understand better. Case 1 Input that I manually send through the chat using the tick is still invoking the api. Action : manually type into the chat input; use the tick character (`) to prepend an api call to my script                         ` !script_name --chat --t:@{target|Target|token_id} Expected Behavior : all of my input will get output to the chat window, nothing will be processed Actual Behavior : my input does go to the chat log (exactly as I typed it, minus the tick), but it breaks my sandbox Error : TypeError: Cannot read property 'get' of undefined                         at Object.getrepeating (apiscript.js:5328:82)                         at apiscript.js:5516:47... The script is expecting to process a get() statement for a token, if you include the t parameter, so it seems that the api is getting invoked but nothing is being passed to the t parameter, since the @{target} call never got processed. My token object remains undefined, and the get() fails. But it never should have been running in the first place! Case 2 This one is a little harder to explain. If in my script I read the contents of an attribute from a repeating section, and the current value of that attribute contains a roll template, and I encode the "&" character leading a roll template (replacing it with the html entity: &) and output that from the api to the chat window, the roll template itself is skipped, but an associated attribute from the set of related attributes from the repeating section is executed (the current value of that attribute is a script call). I know. Confusing. Here are a couple of visual aids... The Hero6e character sheet has a repeating section ("Powers"). repeating_ powers _{repeating element id}_... The Powers section has, among many other associated attributes, the attributes: repeating_powers_{repeating element id}_ use_power_formula repeating_powers_{repeating element id}_ extra_action An example element in this repeating section (a "power") has a roll template in the use_power_formula attribute: &{template:hero6template} {{charname=character_name}} {{power=Mountain (Strike/Shove)}} ...and a script call in the extra_action attribute: !heroll --d:check --pn:Way of the Mountain --n:does 9m of shove --t:ha --col:4b688b --tgt:__tgt__ --l:__loc__ In game, clicking on the sheet button for the power would first run the roll template (use_power_formula), then run the script call (extra_action). However, in the script I am currently developing, if I read the contents of use_power_formula into a string, then replace the leading "&" with "&" and include that in output to the chat, it doesn't process the roll template. It gives me the text as it is entered, above. It just then goes on to run the script call, too! So where I expect an output like this: I am getting an output like this: ...(which includes the resolution of the !heroll script call). It seems like there's obviously something on the character sheet that is firing the extra_action attribute if the attribute roll is used, but how is it even getting that far? If encoding the ampersand stopped the chat from processing the roll template, how does it know to fire the other attribute?
1593205377
The Aaron
Forum Champion
API Scripter
Case 1 sounds like you're doing something like: if(msg.content.indexOf('!foo') != -1){ // do stuff } That's a common way people tend to start out with argument parsing in the API, unfortunately.  At a minimum, you should check the message type: if('api' === msg.type && msg.content.indexOf('!foo') != -1){ // do stuff } And better is to check that !foo is at the beginning.  I prefer a regular expression, but indexOf() === 0 is also reasonably suitable: if('api' === msg.type && 0 === msg.content.indexOf('!foo')){ // do stuff }
1593205741
The Aaron
Forum Champion
API Scripter
I'm not completely clear on what you say is going on in Case 2.  If you're saying that you get the contents of an attribute, check it begins with a roll template, then escape the leading & as & and output it, which then shows the text of the template but also runs the API command, then probably the attribute contains both the template and API command, which is on a new line in the string.  Doing the escaping and outputting sends two messages to chat, the Roll Template and the API command. Using log to send the contents of attributes to the API Console Log is a good way to see what's going on.
1593205775

Edited 1593233609
timmaugh
API Scripter
OK, that makes sense... I am using a regex to catch it, but I'm not testing the type: if('api' !== msg_orig.type) return; // <-- new line, based on Aaron's feedback let apicatch = ""; if (new RegExp(/^!xray\s/).exec(msg_orig.content.toLowerCase()) !== null) apicatch = "xr"; else if (new RegExp(/^!xread\s/).exec(msg_orig.content.toLowerCase()) !== null) apicatch = "vw"; else if (new RegExp(/^!insertarg(\s|s\s)/).exec(msg_orig.content.toLowerCase()) !== null) apicatch = "ia"; if (apicatch === "") return; That's an easy fix... thank you! Any thoughts on the second problem? EDIT: I see your next post addressing that issue... let me run some tests and report back.
1593205967
GiGs
Pro
Sheet Author
For the second issue, it's hard to tell what the issue is, because it involves a chat message which we cant see in full, and a rolltemplate which is likely pretty complex.  It would be better as a start if you posted the full text that gets sent to chat. Not the output, but the actual text sent from the character sheet before it is processed by roll20's parser, and roll template. Also seeing what it looks like after its processed by the rolltemplate, too: remove the ! from the script call, so it doesnt get triggered, click the button, and then paste the result here. 
1593236658

Edited 1593264996
timmaugh
API Scripter
OK, the sheet does have multiple lines in that attribute field... &{template:hero6template} {{charname=@{character_name}}} {{power=Wind (Throw)}}  %{Heretic|WayOfTheWind} (That's a similar power to the one listed above, just because I didn't have time to hunt up the exact same one). So that tells me that the API call I was seeing, before, is because the ability had already been parsed out and replaced with its contents (the ability "WayOfTheWind" contains an API call). So I can get that text and output it to the log, no problem. What I'm trying to output to the chat is HTML that wraps that text: <div style="width:100%;">     <div style="border-radius:10px;border:2px solid #000000;background-color:#ffffff; margin-right:16px; overflow:hidden;">         <table style="width:100%; margin: 0 auto; border-collapse:collapse;font-size:12px;">             <tr style="border-bottom:1px solid #000000;font-weight:bold;text-align:center; background-color:#dedede; line-height: 22px;">                 <td>TEXT</td>             </tr>             <tr style="background-color:#ffffff;">                 <td style="padding:4px;">&{template:hero6template} {{charname=@{character_name}}} {{power=Wind (Throw)}} %{Heretic|WayOfTheWind}</td>             </tr>         </table>     </div> </div> The line break in the contents was a line break in the logged output... Everything after %{Heretic... was a second line... which makes sense from what Aaron said -- that it would be sent as it's own chat input. I also tried a small table of translation for the characters that seem important to attributes, macros, abilities, queries, and the like:     const htmlTable = {         "&": "&",         "{": "{",         "}": "}",         "|": "|",         ",": ",",         "%": "%",         "?": "?",         "[": "[",         "]": "]",         "@": "@",         "~": "~",         "(": "(",         ")": ")",     };     const htmlCoding = (s="", encode = true) => {         if (typeof s !== "string") return undefined;         let searchfor = "";         encode ? searchfor = Object.keys(htmlTable) : searchfor = Object.keys(_invert(htmlTable));         s = s.replace((new RegExp(escapeRegExp(searchfor.join("|")), 'gmi'), (r) => { return searchfor[r]; }));         return s;     }; But I'm not sure that's working... the code seemed to take a step backward, then. When I was ONLY detecting and replacing the leading "&" for the roll template, I was at least getting that roll template text into the message box (see the image in the posts above). Once I encoded everything, my little message box was nowhere to be seen... the chat was (badly) executing the roll template followed by text. By "badly" I mean this is what it's supposed to look like if I *wanted* to run it: But this is what it looks like when it gets inadvertently tripped by my code, above: Like the character_name never gets processed. It also throws a non-sandbox-breaking error to the log that I tried to use a roll template without specifying a character name. I'm not sure that I'm getting proper HTML entity replacement... even when I log it, I see the actual characters rather than the html entities, so I'm not sure if my browser is immediately decoding the string before it shows it to me. After encoding, the string logs as: &{template:hero6template} {{charname=@{character_name}}} {{power=Wind (Throw)}}  %{Heretic|WayOfTheWind} Any idea for a better approach?
1593445386

Edited 1593447106
timmaugh
API Scripter
OK, there's a change from the above, but I do still have a question (below). My code was pretty messed up, before... I was escaping the pipe that should have joined the Regex search expression. The keys of the object should have been joined with a pipe to create an OR case in the Regex... but because the join was added before the keys were getting escaped, the regex string was never matched (it was looking for the full set of keys, in order). I had to change that to escape each key individually, then join those escaped strings with the pipe. Here's the code that does that now: const rowbg = ["#ffffff", "#dedede"]; const msgtable = '<div style="width:100%;"><div style="border-radius:10px;border:2px solid #000000;background-color:__bg__; margin-right:16px; overflow:hidden;"><table style="width:100%; margin: 0 auto; border-collapse:collapse;font-size:12px;">__TABLE-ROWS__</table></div></div>'; const msg1header = '<tr style="border-bottom:1px solid #000000;font-weight:bold;text-align:center; background-color:__bg__; line-height: 22px;"><td>__cell1__</td></tr>'; const msg2header = '<tr style="border-bottom:1px solid #000000;font-weight:bold;text-align:center; background-color:__bg__; line-height: 22px;"><td>__cell1__</td><td style="border-left:1px solid #000000;">__cell2__</td></tr>'; const msg1row = '<tr style="background-color:__bg__;"><td style="padding:4px;">__cell1__</td></tr>'; const msg2row = '<tr style="background-color:__bg__;font-weight:bold;"><td style="padding:1px 4px;">__cell1__</td><td style="border-left:1px solid #000000;text-align:center;padding:1px 4px;font-weight:normal;">__cell2__</td></tr>';     const htmlTable = {         "&": "&",         "{": "{",         "}": "}",         "|": "|",         ",": ",",         "%": "%",         "?": "?",         "[": "[",         "]": "]",         "@": "@",         "~": "~",         "(": "(",         ")": ")",     };     const escapeRegExp = (string) => { return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); };     const htmlCoding = (s = "", encode = true) => {         if (typeof s !== "string") return undefined;         let searchfor = encode ? htmlTable : _invert(htmlTable);         s = s.replace(new RegExp(Object.keys(searchfor)                 .map((k) => { return escapeRegExp(k); })                 .join("|"), 'gmi'),             (r) => { return searchfor[r]; });         return s;     }; const msgOutput = ({ c: c="chat message", t: t = "title" } = {}) => { let tbl = copystr(msgtable).replace("__bg__", rowbg[0]); let hdr = copystr(msg1header).replace("__bg__", rowbg[1]).replace("__cell1__", t); let row = copystr(msg1row).replace("__bg__", rowbg[0]).replace("__cell1__", c); return tbl.replace("__TABLE-ROWS__", hdr + row); }; When I want to see the text, I use the msgOutput function to construct the chat text. That works great for giving me nearly all the output I'm looking for... meaning this entry in the attribute field: &{template:hero6template} {{charname=@{character_name}}} {{power=Wind (Throw)}}  %{Heretic|WayOfTheWind} ...(including the line break) produces the output of: Cool. That's what I'm looking for *except* that the second line isn't there. My regex driving the replace is set with the flags of 'gmi'... which I thought should do the multi-line replace (and the fact that the ability call in the second line isn't firing tells me that the leading "%" was properly encoded  probably missing). But when I log the html-encoded line now, I only have the first line: &{template:hero6template} {{charname=@{character_name}}}  {{action=@{skill_name}}}  {{roll=[[3d6]]}}  {{target=17}} {{base=9}} {{stat=4}} {{inc=4}} So my question is, why is the regex replace dropping the second line? Is there a way to test for the line break, and will it work with my current method (an object of key:value pairs)?
1593449088
timmaugh
API Scripter
Nevermind. I'm an idiot. The particular attribute I was testing with after I found that problem with the regex didn't actually have the extra line. That's why it wasn't outputting it. When I test with the PROPER attribute (multiline), it works.
1593449556
The Aaron
Forum Champion
API Scripter
It's always nice when you can solve your own problems, despite how it might make you feel.  And really, those feelings help you remember the solution more clearly than if it just worked the first time. =D
1593449597
timmaugh
API Scripter
For those who come upon this later looking for a solution to the same problem, the final step is to search for a line break and replace with <br> (or 2 of them, depending on taste), to prevent the second line being sent independently to the chat:     const htmlCoding = (s = "", encode = true) => {         if (typeof s !== "string") return undefined;         let searchfor = encode ? htmlTable : _invert(htmlTable);         s = s.replace(new RegExp(Object.keys(searchfor)                 .map((k) => { return escapeRegExp(k); })                 .join("|"), 'gmi'),             (r) => { return searchfor[r]; })             .replace(new RegExp(/\n/,'gmi'),'<br><br>'); // <== this is the new line         return s;     };