Ok, back at my computer. Here's a script I threw together using several functions I already had in various places. The extractRoll function is entirely stolen from Aaron's various scripts, as is my general script design. About half of the script below is just my error handling technique and basic script scaffold. The actual work is done in the handleInput and extractRoll functions. var hideRolls= hideRolls || (function(){let scriptStart = new Error;//Generates an error to localize the start of the script //converts the line number in the error to be line 1 scriptStart = scriptStart.stack.match(/apiscript\.js:(\d+)/)[1]*1; var version = 0.1, lastUpdate = 1574706278, //Error reporting function sendError = function(err){ var stackMatch = err.stack.match(/apiscript\.js:\d+/g); _.each(stackMatch,(s)=>{ let sMatch = s.match(/\d+/)[0]*1; err.stack = err.stack.replace(new RegExp('apiscript\.js:'+sMatch),'apiscript.js:'+(sMatch-scriptStart+ 1)); }); var stackToSend = err.stack ? (err.stack.match(/([^\n]+\n[^\n]+)/) ? err.stack.match(/([^\n]+\n[^\n]+)/)[1].replace(/\n/g,'<br>') : 'Unable to parse error') : 'Unable to parse error'; sendChat('','/w gm <div style="border: 1px solid black; background-color: white; padding: 3px 3px;">'//overall div for nice formatting of control panel +'<div style="font-weight: bold; border-bottom: 1px solid black;font-size: 130%;">'//Control Panel Header div +'Hide Rolls v'+version+'<b> Error Handling</b></div>' +'<div style="border-top: 1px solid #000000; border-radius: .2em; background-color: white;">' +'The following error occurred:<br><pre><div style="color:red"><b>'+err.message+'<br>'+stackToSend+'</b></div></pre>Please post this error report to the <b><u>[Script forum thread](<a href="https://app.roll20.net/forum/post/7698809/script-door-knocker/?pageforid=7698809#post-7698809)</u></b" rel="nofollow">https://app.roll20.net/forum/post/7698809/script-door-knocker/?pageforid=7698809#post-7698809)</u></b</a>>.' +'</div>' +'</div>',null,{noarchive:true}); }, boot = function(){ checkInstall(); }, /*Checks the API environment to make sure everything is prepped for the script*/ checkInstall = function() { log('=> Hide Rolls v'+version+' <=- ['+(new Date(lastUpdate*1000))+']'); if( ! _.has(state,'hide') || state.hide.version !== version) { state.hide = state.hide || {}; log(`==> Updating Hide Rolls to v${version} <`); state.hide.version = version; } }, handleInput = function(msg_orig) { try{ var msg = _.clone(msg_orig), validTemplates = [//add the name of the roll templates you want the script to respond to here. 'pf_attack', 'pf_spell', ]; if (!/^gm$/i.test(msg.target)||!new RegExp(`^${validTemplates.join('|')}$`).test(msg.rolltemplate)){//if the message isn't a whisper to the gm or doesn't have a valid roll template declaration, do nothing return; } if(_.has(msg,'inlinerolls')){//if there are inline rolls in the message, convert them to their raw numbers, and send the converted text to chat as a public message. msg.content = extractRoll(msg); sendChat(msg.who,`&{template:${msg.rolltemplate}} ${msg.content}`); } }catch(err){ sendError(err); } }, extractRoll = function(msg){ return _.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);//converts [[1d20+5]] = 6 to be just 6 (normal text, no hover) //return m.replace(k,`[[${v}]]`); //switch to this return value if you want the output to look like it would if you did [[6]] in the above example. Maintaining natural 1 and crit highlighting is a little more involved. },msg.content) .value(); }, RegisterEventHandlers = function() { on('chat:message',handleInput) }; return { Bootup: boot, RegisterEventHandlers: RegisterEventHandlers }; }()); on("ready",function(){ 'use strict'; hideRolls.Bootup(); hideRolls.RegisterEventHandlers(); });