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

[Script] unWhisper - send a private GM message to chat

1642505476

Edited 1662944100
Oosh
Sheet Author
API Scripter
Script - unWhisper v0.1.0 Thanks to Covid, I have some time to kill, so I bashed this together after seeing a post about it earlier. It's pretty rough, and probably has some bugs. I also don't use Roll20 for games these days, so.... I may have done some stupid things. It seems to work though? This script allows the GM to unwhisper a recent self-whisper and send it public. The script only stores the GM's own whispers (ie private rolls made from sheets/macros), not private whispers from other players. Commands (not case sensitive): !unWhisper     -alias for !unWhisper --0, this will publicly post your most recent whisper !unWhisper --list     -list all stored messages in chat, 0-indexed, with 0 being the most recent. Each message has a small preview made up from the roll template name (if applicable) and either a {{*name=}} template property, or the start of the message text. !unWhisper --X     -where X is a number. Post the message with the index 'X'. Same as clicking on the buttons in --list. !unWhisper --maxWhispers     -set the number of whispers to store. Default is 5, max is 30 !unWhisper --maxPreview     -set the max length of the message preview in the --list view. Default is 20 characters, max is 50. IMPORTANT - This script relies on Tim & Aaron's libInline to restore inline rolls to roll templates. Install it from the one-click if you want to see original rolls (though you won't get original tooltips or Quantum roll icons). /* globals log on playerIsGM, state, sendChat, libInline */ const unWhisper = (() => { //eslint-disable-line no-unused-vars const scriptName = 'unWhisper'; const version = { M: 0, m: 1, p: 0, get: function() { return `${this.M}.${this.m}.${this.p}` }, getFloat: function() { return parseFloat(`${this.M}.${this.m}${this.p}`) } } const config = { maxWhispers: 5, maxPreviewSize: 20, } const init = () => { if (!state.unWhisper || !state.unWhisper.version) { state.unWhisper = { version: version.getFloat(), whispers: [], config: config, } } else if (state.unWhisper.version < version.getFloat) { // update version } refreshConfig(); on('chat:message', handleInput); log(`- Initialised ${scriptName} - v${version.get()} -`); setTimeout(() => { if (!/object/i.test(typeof(libInline))) return doChat(`/w gm <div style="color: red; font-weight: bold">libInline was not found: Please install from one-click library to enable inline rolls!</div>`, 'gm'), 250 }); } const refreshConfig = () => Object.assign(config, state.unWhisper.config); const filterPreview = (inputStr) => { let output = `${inputStr}`.replace(/[{}[\]@]/g, ''); return output; } const listWhispers = () => { const headStyle = `background-color: black; color: white; font-weight:bold; border:2px solid black; width: 100%; word-break: break-all; line-height: 2em;` const rowStyle = `background: white; color: black; font-weight: normal; padding: 5px 2px 5px 2px; line-height: 2rem; word-break: break-all` const buttonStyle = `background-color: darkblue; border: 1px darkblue solid; padding: 2px 5px 2px 5px; border-radius:3px; color: white; font-weight: bold; ` let previews = state.unWhisper.whispers.map(w => w.preview || 'no preivew'), templateHead = `<div style="${headStyle}">   unWhisper`, templateBody = [], templateFoot = `</div>` for (let i = previews.length - 1; i >= 0; i--) { templateBody.push(`<div style="${rowStyle}"><a href="!unWhisper --${i}" style="${buttonStyle}">Msg ${i}</a> ${previews[i]}</div>`); } //(`{{[Msg ${i}](\`!unWhisper --${i}" style="${buttonStyle})=${previews[i]}}}`); } doChat(`${templateHead}${templateBody.join(' ')}${templateFoot}`, 'gm'); } const storeWhisper = (whisper) => { let store = state.unWhisper.whispers, header = whisper.rolltemplate ? `\*\*${whisper.rolltemplate}\*\*/` : `\*\*-\*\*/`, // eslint-disable-line no-useless-escape nameMatch = whisper.content.match(/name=([^}]+?)}/); header += nameMatch ? nameMatch[1] : whisper.content.slice(0, config.maxPreviewSize); store.unshift({ preview: filterPreview(header), msg: whisper }); while (store.length > config.maxWhispers) { store.pop(); } // log(`Stored whisper - "${header}" - ${store.length} in store.`); } const sendWhisper = (index = 0) => { const whisper = state.unWhisper.whispers[index]; if (!whisper || !whisper.msg) return log(`unWhisper: bad index "${index}"`); const templatePrefix = whisper.msg.rolltemplate ? `&{template:${whisper.msg.rolltemplate}} ` : ''; let newContent = `${templatePrefix}${whisper.msg.content}`; if (whisper.msg.inlinerolls && whisper.msg.inlinerolls.length) { const rolls = libInline.getRollData(whisper.msg.inlinerolls); newContent = newContent.replace(/\$\[\[(\d+)]]/g, ((m, p1) => rolls[p1].getRollTip())); } doChat(newContent); } const doChat = (msg, who) => { const whisper = who ? `/w "${who}" ` : ``; sendChat(scriptName, `${whisper}${msg}`, null, { noarchive: true }); } const handleInput = (msg) => { if (!playerIsGM(msg.playerid)) return; if (msg.type === 'api' && /^!unwhisper/i.test(msg.content)) { let line = (msg.content.match(/^!unwhisper\s+(.+)/i) || [])[1]; if (!line) sendWhisper(0); else { let params = line.split(/\s*--\s*/g); params.shift(); params.forEach(param => { let cmd = (param.match(/^([^\s]+?)(\s|$)/) || [])[1], args = (param.match(/\s+(.+)/) || [])[1], change; if (!cmd) return; if (/maxwhisp/i.test(cmd)) { let newMax = args.replace(/\D/g, ''); if (newMax > 0 && newMax < 30) { state.unWhisper.config.maxWhispers = newMax; change = `new maxWhispers: ${newMax}`; } } else if (/maxprev/i.test(cmd)) { let newMax = args.replace(/\D/g, ''); if (newMax > 0 && newMax < 50) { state.unWhisper.config.maxPreviewSize = newMax; change = `new maxPreview: ${newMax}`; } } else if (/list/i.test(cmd)) { listWhispers(); } else if (/^\d+/.test(cmd)) { let index = cmd.replace(/\D/g, ''); sendWhisper(index); } else doChat(`Unrecognised command: "${cmd}"`, 'gm'); if (change) { doChat(`Setting change: ${change}`, 'gm'); refreshConfig(); } }); } } else if (msg.target && (/^gm$/i.test(msg.target) || playerIsGM(msg.target)) && playerIsGM(msg.playerid)) { storeWhisper(msg); } }; on('ready', () => init()); })();
1642508170
David M.
Pro
API Scripter
Neat, can't wait to try this out!
1642519374
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
That's awesome! I usually whisper my GM rolls, not because I fudge, but because I game for three fellow GMs who would gain meta-knowledge and constantly second guess results. It's just smoother. But occasionally, I have wished for something like this. Will try this out next time I am in the Big Chair.
keithcurtis said: That's awesome! I usually whisper my GM rolls, not because I fudge, but because I game for three fellow GMs who would gain meta-knowledge and constantly second guess results. It's just smoother. But occasionally, I have wished for something like this. Will try this out next time I am in the Big Chair. My single favorite whisper roll is the death save and EVERY nat 1 and nat 20 gets snipped and then shared in discord, this will speed things up so much for me personally I love this script.  I know that they say not to share things behind the screen with your players but part of the joy is proving the roll happened especially for those.  
1642531016
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
My favorite implementation of this is on the Shaped sheet, which gives you the option of hiding nearly everything about a roll independently. The GM then uses a Stylus style to display the entire roll template only to them. So for example, you can decide to hide damage rolls, or descriptions, or spell names, or name of attacker, but leave hit and damage rolls visible.
1642540800
vÍnce
Pro
Sheet Author
Much better than a screen shot posted to chat as "proof" of the TPK.  lol
vÍnce said: Much better than a screen shot posted to chat as "proof" of the TPK.  lol I prefer to record the slow dread creeping over the table in discord.  
I am unable to get this script to work with 5e OGL npc sheets properly.  
1642619700
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
In what way are you generating the roll? It works for me when rolling directly from the sheet:
Its behaving as expected now, I dunno what was causing it before, out of pure curiosity I just now tested it with CRL disabled and it behaved normal, enabled CRL and its still working so I dunno whats up maybe the api had needed a fresh restart before. 
1642637677
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
Sometimes it's just gremlins.
1642639148
Oosh
Sheet Author
API Scripter
I did have a boo-boo in the init() function, so something may have gone wrong on install and needed a restart. I think I've fixed it up.