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

Reading text from GMNotes via API

1513022334

Edited 1513022364
First, thanks for the help on the previous issue. I've gotten the following api to work...sort of on('ready',() => {     on('chat:message',(msg) => {         if('api'===msg.type && /^!mapinfo/i.test(msg.content) && playerIsGM(msg.playerid)){             _.chain(msg.selected)                 .map((o)=>getObj('graphic',o._id))                 .reject(_.isUndefined)                 .each((t)=>{                     sendChat("Map Info", `/w gm &{template:desc} {{desc=${t.get('gmnotes')}}}`                     );                 });         }     }); }); However, as you can see I'm reading the gmnotes from the selected token. These are tokens I have on the GM layer that contain information about the encounters. When I print them to chat however, everything is being converted to html characters, as in the output looks like this: %3Ch3%3E11.%20Elevator%2C%20Upper%20Level%3C/h3%3EA%20ring-shaped%20gantry%20is%20bolted%20to%20the%20wall%20of%20the%20elevator%20shaft%2C%2050%20feet%20above%20the%20floor%20of%20the%20lower%20level%20%28area%2024%29.%20When%20the%20elevator%20stops%20here%2C%20the%20platform%20is%20level%20with%20the%20gantry.%20The%20circular%20space%20that%20the%20gantry%20surrounds%20is%20just%20wide%20enough%20for%20the%20platform%20to%20pass%20through%20it.%3Cbr%3E%3Cbr%3ENo%20guards%20are%20stationed%20here.%20Characters%20can%20hear%20the%20rattling%20of%20chains%20all%20around%20them. Is there a way to get it to keep the spaces and carriage returns?
1513022841

Edited 1513023072
Jakob
Sheet Author
API Scripter
Use decodeURIComponent(t.get('gmnotes')) instead. Better, use The Aaron's solution, I forgot that you need special handling for line breaks.
1513022966
The Aaron
Pro
API Scripter
Here's a Snippet I made that does that: on('ready',function(){     'use strict';     on('chat:message',function(msg){         if('api' === msg.type && msg.content.match(/^!gmnote/) && playerIsGM(msg.playerid) ){             let match=msg.content.match(/^!gmnote-(.*)$/),                 regex;             if(match && match[1]){                 regex = new RegExp(`^${match[1]}`,'i');             }                                              _.chain(msg.selected)                 .map( s => getObj('graphic',s._id))                 .reject(_.isUndefined)                 .reject((o)=>o.get('gmnotes').length===0)                 .each( o => {                     if(regex){                         let lines=_.filter(decodeURIComponent(o.get('gmnotes')).split(/(?:[\n\r]+|<br\/?>)/),(l)=>regex.test(l)).join('\r');                         sendChat(o.get('name'),lines);                     } else {                         sendChat(o.get('name'),decodeURIComponent(o.get('gmnotes')));                     }                 });         }     }); });
Actually, I think for simplicity, I'm just going to create blank NPCs and link them to the tokens. That way, all I have to do is shift + double click on them to bring up the bio page.
But I'll try this as well! I didn't see it appear until after I tried a few other things.
That works really well by the way!
1513033257
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
The advantage to using the Aaron's script is that it reads from the token's GM notes, meaning you only have to have one Notes character . Every token and its notes is unique. BTW Aaron, I modified your script to output into a role template for formatting. It crashes the API every time it encounters certain characters, such as a quote mark. I generally have to clean up text to prevent that, rather than paste from a word processor. Is this likely from what I have done, or does the vanilla script do that as well? on('ready',function(){ 'use strict'; on('chat:message',function(msg){ if('api' === msg.type && msg.content.match(/^!gmnote/) && playerIsGM(msg.playerid) ){ _.chain(msg.selected) .map( s => getObj('graphic',s._id)) .reject(_.isUndefined) .each( o => { sendChat(o.get('name'),'/w gm &{template:5e-shaped}{{title=' + decodeURIComponent(o.get('name')) +'}} {{text='+ decodeURIComponent(o.get('gmnotes'))+'}}'); }); } }); });
1513033748
The Aaron
Pro
API Scripter
Hmm.  You mean whenever there are quotes in the notes field? Probably it could happen on mine as well.  decodeURIComponent can throw exceptions but I don't know what would cause that to happen.  If you have an example of a token where it happens, I'd be happy to come figure out the precise issue.  BTW, You don't need the decodeURIComponents() on the name, just the gmnotes.
1513034026
Spren
Sheet Author
There's an old script I really like called tokenotes that may be something you would like. It basically makes the GM notes show up on the board in text. So all you have to do is single click to see any notes. It's one of those things that should be a roll20 default feature but for some reason isn't. <a href="https://app.roll20.net/forum/post/1760741/script-tokenotes" rel="nofollow">https://app.roll20.net/forum/post/1760741/script-tokenotes</a>
1513034809
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
The Aaron said: Hmm.&nbsp; You mean whenever there are quotes in the notes field? Probably it could happen on mine as well.&nbsp; decodeURIComponent can throw exceptions but I don't know what would cause that to happen.&nbsp; If you have an example of a token where it happens, I'd be happy to come figure out the precise issue.&nbsp; BTW, You don't need the decodeURIComponents() on the name, just the gmnotes. I'll come up with some samples tonight. I have to pretend to be at work right now...
1513034936

Edited 1513035046
Is this the error you were refering to? By the way, I am going to go this route. URIError: URI malformed URIError: URI malformed at decodeURIComponent (&lt;anonymous&gt;) at _.chain.map.reject.reject.each.o (apiscript.js:419:59) at Function._.each._.forEach (/home/node/d20-api-server/node_modules/underscore/underscore.js:153:9) at _.(anonymous function) [as each] (/home/node/d20-api-server/node_modules/underscore/underscore.js:1496:34) at apiscript.js:414:18 at eval (eval at &lt;anonymous&gt; (/home/node/d20-api-server/api.js:146:1), &lt;anonymous&gt;:65:16) at Object.publish (eval at &lt;anonymous&gt; (/home/node/d20-api-server/api.js:146:1), &lt;anonymous&gt;:70:8) at /home/node/d20-api-server/api.js:1510:12 at /home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:93:560 at hc (/home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:39:147) For reference, this is the text it's trying to read 16B. Northeast Room This room holds an iron-framed bed, two cabinets (one containing manacles, the other containing instruments of torture), a barrel of water, a barrel of dwarven ale, two empty ironbound wooden crates, and an iron trunk with a lock built into it. The trunk is 7 feet tall, 13 feet long, and 8 feet wide, and it weighs 1,000 pounds. The fire giant in area 12 carries the key to the lock, which is too big to be picked with thieves’ tools. A Small or Medium character can reach into it and open it with a successful DC 20 Dexterity check. Treasure. The trunk contains 15,000 cp, 6,200 sp, 700 gp, a drinking horn made from a gorgon’s horn and bearing flame-like patterns (worth 2,500 gp and weighing 50 pounds), and a sack containing 2d4 mundane items, determined by rolling on the Items in a Giant's Bag table in the introduction.
1513041024
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
Yes. In order to avoid the problem, the apostrophes need to be removed.
Does the method you provide handle this?
1513067367
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
No, I manually remove them or avoid typing them in the first place. If you type the apostrophe directly in the GM note's field, you get a safe character (the foot mark, or, ' ), if you paste it in from some source that gives typographer's quotes or real apostrophes (’) it crashes. So I am careful to replace them before using any pasted content. The section you provided as a sample had both kinds. The "Giant's Bag" used a straight apostrophe (the foot mark), the "gorgon’s horn" (and others) had a real apostrophe and would cause an API crash.
1513083896
The Aaron
Pro
API Scripter
Ah... Unicode... my old nemesis...
1513085297

Edited 1513086043
Jakob
Sheet Author
API Scripter
Don't you want to join the lines by a HTML line break (&lt;br&gt;) instead of a carriage return (\r)? Anyway, getting a completely safe string is probably hard, but at least you can get rid of quotes and emdashes: o.get('gmnotes').replace(/(?:%u2018|%u2019)/g, "'").replace(/(?:%u201C|%u201D)/g, '"').replace(/(?:%u2013|%u2014)/g, '-') EDIT: Edited to adapt to how Roll20 actually stores these characters.
Yeah, it's because I'm copy pasting it. This is just another example of why they need to handle mapnotes way better in roll20.
Jeremy R. said: Yeah, it's because I'm copy pasting it. This is just another example of why they need to handle mapnotes way better in roll20. Can't agree more... even allowing a token to be linked to a handout so that I can shift+double click to bring up the handout would be so much better.
Spren, thanks for passing along the link to that older thread.&nbsp; TokeNotes is perfect for what I need.
1513087236
The Aaron
Pro
API Scripter
Try this function: const fixUnicode = (str) =&gt; { &nbsp; &nbsp; const replacers = { &nbsp; &nbsp; &nbsp; &nbsp; "[\u2018\u2019]"&nbsp; &nbsp;: "'", &nbsp; &nbsp; &nbsp; &nbsp; "[\u201C\u201D]"&nbsp; &nbsp;: '"', &nbsp; &nbsp; &nbsp; &nbsp; "[\u2013\u2014]"&nbsp; &nbsp;: '-', &nbsp; &nbsp; &nbsp; &nbsp; "[\u2026]"&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;: '...', &nbsp; &nbsp; &nbsp; &nbsp; "[^\u0000-\u007f]" : '' &nbsp; &nbsp; }; &nbsp; &nbsp; return Object.keys(replacers).reduce( (s,r)=&gt;s.replace(RegExp(r,'g'),replacers[r]),str); }; /* ... */ /* ... */ decodeURIComponent( fixUnicode (o.get('gmnotes')) /* ... */ The replacers object contains the regular expressions for specific unicode first (I just took what Jakob had) and their ASCII equivalents, and the final line matches anything left and gets rid of it.
1513087484
Jakob
Sheet Author
API Scripter
Aaron, you probably didn't see my edited version - you have to replace the string "%u2018", not the unicode character "\u2018".
1513088514
The Aaron
Pro
API Scripter
Ah, good point.&nbsp; I was dealing with the resultant text rather than the stored text.&nbsp; That does complicate things slightly. =D&nbsp; I suppose I'll have to test this out some.
1513091795
The Aaron
Pro
API Scripter
Ok, thanks for that info Jakob, that actually leads to an even better solution!&nbsp; The decodeURIComponent() function in the API sees the % and tries to turn the next 2 characters into ascii, but doesn't know how to decode a 'u'.&nbsp; The solution is to handle all the unicode decoding first: &nbsp; &nbsp; const decodeUnicode = (str) =&gt; str.replace(/%u[0-9a-fA-F]{2,4}/g,(m)=&gt;String.fromCharCode(parseInt(m.slice(2),16))); Then decodeURIComponent() can take care of the rest and you don't have to care about the unicode because it displays fine: /* ... */ decodeURIComponent(decodeUnicode(o.get('gmnotes')) /* ... */
1513092585

Edited 1513093240
Edit to my original question. I've put the whole thing back together and so far it seems to be working quite well. I've listed it all in one piece here for reference. const decodeUnicode = (str) =&gt; str.replace(/%u[0-9a-fA-F]{2,4}/g,(m)=&gt;String.fromCharCode(parseInt(m.slice(2),16))); on('ready',function(){ &nbsp; &nbsp; 'use strict'; &nbsp; &nbsp; on('chat:message',function(msg){ &nbsp; &nbsp; &nbsp; &nbsp; if('api' === msg.type && msg.content.match(/^!gmnote/) && playerIsGM(msg.playerid) ){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let match=msg.content.match(/^!gmnote-(.*)$/), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; regex; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(match && match[1]){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; regex = new RegExp(`^${match[1]}`,'i'); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _.chain(msg.selected) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .map( s =&gt; getObj('graphic',s._id)) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .reject(_.isUndefined) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .reject((o)=&gt;o.get('gmnotes').length===0) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .each( o =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(regex){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let lines=_.filter(decodeURIComponent(decodeUnicode(o.get('gmnotes'))).split(/(?:[\n\r]+|&lt;br\/?&gt;)/),(l)=&gt;regex.test(l)).join('\r'); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sendChat(o.get('name'),`/w gm ` + lines); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sendChat(o.get('name'),`/w gm ` + decodeURIComponent(decodeUnicode(o.get('gmnotes')))); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; }); });
1513092766

Edited 1513093036
Also, I'm just curious as to why this if statement is here, as in what task it's checking for or performing. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .each( o =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(regex){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let lines=_.filter(decodeURIComponent(o.get('gmnotes')).split(/(?:[\n\r]+|&lt;br\/?&gt;)/),(l)=&gt;regex.test(l)).join('\r'); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sendChat(o.get('name'),`/w gm &{template:desc} {{desc=` + lines + `}}`); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sendChat(o.get('name'),`/w gm &{template:desc} {{desc=` + decodeURIComponent(o.get('gmnotes')) + `}}`); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); I ask, because I noticed that Keith's code does not contain this check. Also sorry for a lot of the questions. I'm used to just going to stack exchange for c# :P.
1513093975
The Aaron
Pro
API Scripter
This block: &nbsp; &nbsp; &nbsp; &nbsp; if('api' === msg.type && msg.content.match(/^!gmnote/) && playerIsGM(msg.playerid) ){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let match=msg.content.match(/^!gmnote-(.*)$/), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; regex; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(match && match[1]){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; regex = new RegExp(`^${match[1]}`,'i'); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } checks if the chat command begins with !gmnote, then grabs anything after !gmnote- and makes a regular expression to match lines starting with it.&nbsp; If that regex has been created, it will instead find all the lines matching it and output only those.&nbsp; So, you could do: !gmnotes-gold and it would find all the lines that start with gold (case insensitive) and output them.
1513094049
The Aaron
Pro
API Scripter
I think that was an addition I did for someone, probably Ziechael. =D
1513096134
Ziechael
Forum Champion
Sheet Author
API Scripter
Sounds like something I'd ask for... maybe as part of the Token GM-note chat editor I pestered you about :)
So, one last thing, and I'm quite sure this isn't possible because we can't use the api for interface actions. Is there anyway to intercept a different style click on a token (ie. ctrl + click or a hotkey). Then, maybe a separate thing, read a link from the GM notes or a bar, and then try to open that link. I'm thinking with the API maybe we get around this link to handouts issue another way. Put the link in the token somewhere and just automatically open it. I know you can print the link from the sheet to chat, and then click on it there. I'm looking to bypass a step here.
1513099766
The Aaron
Pro
API Scripter
Nope.&nbsp; I suggested that about 3-4 years ago in the old suggestions forum, but it hasn't come up yet...
1513106413
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
The Aaron said: This block: &nbsp; &nbsp; &nbsp; &nbsp; if('api' === msg.type && msg.content.match(/^!gmnote/) && playerIsGM(msg.playerid) ){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let match=msg.content.match(/^!gmnote-(.*)$/), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; regex; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(match && match[1]){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; regex = new RegExp(`^${match[1]}`,'i'); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } checks if the chat command begins with !gmnote, then grabs anything after !gmnote- and makes a regular expression to match lines starting with it.&nbsp; If that regex has been created, it will instead find all the lines matching it and output only those.&nbsp; So, you could do: !gmnotes-gold and it would find all the lines that start with gold (case insensitive) and output them. I'll edit that into my copy. It looks like a useful addition. Cool!
1513174868
GiGs
Pro
Sheet Author
API Scripter
I've been trying the completed script Jeremy R posted above, using !gmnotes-gold and it is just outputting the complete text, even though i have a line starting with gold. To be clear, I am seeing identical behaviour when using&nbsp; !gmnotes-gold and !gmnotes Both just output the full contents of the GMnotes. What am I missing?
1513176335
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
I'm having the opposite problem. The filter works fine, but I'm still crashing on an apostrophe.
1513179072
The Aaron
Pro
API Scripter
G G: Looks like I mistyped it above. it's !gmnote-gold, not gmnotes-gold.&nbsp; Making sure it works now... Keithcurtis: hmm.... might have to look at that in-game.
1513181406
The Aaron
Pro
API Scripter
So, one problem is that if you've got HTML in there, it likely doesn't split well.&nbsp; I'm throwing together a simple block level element splitter to reflow the HTML before breaking into "lines", then I'll make the matching ignore html and it should output lines fine.&nbsp; Initial testing did a pretty good job, but It wasn't accounting for inline elements like &lt;b&gt; that you might want to retain in the same "line".
1513185369
GiGs
Pro
Sheet Author
API Scripter
Thanks, Aaron, the gmnote-gold works.&nbsp;
1513265831
The Aaron
Pro
API Scripter
Ok. Here's a version that will at least take a stab at block level elements of HTML.&nbsp; I also expanded the command slightly to provide a whispered and open version: !gmnote !wgmnote !gmnote-&lt;some text&gt; !wgmnote-&lt;some text&gt; It will do it's best to preserve the formatting, but short of a full HTML parser, it can't be perfect.&nbsp; YMMV. on('ready',function(){ &nbsp; &nbsp; 'use strict'; &nbsp; &nbsp; const decodeUnicode = (str) =&gt; str.replace(/%u[0-9a-fA-F]{2,4}/g,(m)=&gt;String.fromCharCode(parseInt(m.slice(2),16))); &nbsp; &nbsp; const blockElements = [ &nbsp; &nbsp; &nbsp; &nbsp; 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'ul', 'pre', 'address', &nbsp; &nbsp; &nbsp; &nbsp; 'blockquote', 'dl', 'div', 'fieldset', 'form', 'hr', 'noscript', 'table','br' &nbsp; &nbsp; ]; &nbsp; &nbsp; const rStart=new RegExp(`&lt;\\s*(?:${blockElements.join('|')})\\b[^&gt;]*&gt;`,'ig'); &nbsp; &nbsp; const rEnd=new RegExp(`&lt;\\s*\\/\\s*(?:${blockElements.join('|')})\\b[^&gt;]*&gt;`,'ig'); &nbsp; &nbsp; const getLines = (str) =&gt;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (rStart.test(str) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ? str &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .replace(/[\n\r]+/g,' ') &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .replace(rStart,"\r$&") &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .replace(rEnd,"$&\r") &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .split(/[\n\r]+/) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; : str &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .split(/(?:[\n\r]+|&lt;br\/?&gt;)/) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .map((s)=&gt;s.trim()) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .filter((s)=&gt;s.length) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ; &nbsp; &nbsp; const cmdRegex = /^!(w?)gmnote(?:-(.*))?$/i; &nbsp; &nbsp; on('chat:message',function(msg){ &nbsp; &nbsp; &nbsp; &nbsp; if('api' === msg.type && cmdRegex.test(msg.content) && playerIsGM(msg.playerid) ){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let match=msg.content.match(cmdRegex), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; output = match[1].length ? '/w gm ' : '', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; regex; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(match[2]){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; regex = new RegExp(`^${match[2]}`,'i'); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _.chain(msg.selected) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .map( s =&gt; getObj('graphic',s._id)) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .reject(_.isUndefined) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .reject((o)=&gt;o.get('gmnotes').length===0) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .each( o =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(regex){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let lines = _.filter( &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; getLines(decodeURIComponent(decodeUnicode(o.get('gmnotes')))), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (l) =&gt; regex.test(l.replace(/&lt;[^&gt;]*&gt;/g,'')) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ).join('&lt;br&gt;'); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sendChat(o.get('name'), `${output}${lines}`); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sendChat(o.get('name'), `${output}${decodeURIComponent(decodeUnicode(o.get('gmnotes')))}`); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; }); });
1513270477
GiGs
Pro
Sheet Author
API Scripter
Cool, I was thinking about asking for an open chat version, too.
1513277797
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
Sweet!