Resources: Browser Scripts

From 5etools Community Wiki
Jump to: navigation, search

These are scripts that are pasted into the web browser's console to perform a specific action or actions, but are not included within BetteR20 or VTTes

You can open the console by hitting Ctrl+Shift+J (or Cmd+Shift+J on a Mac) or F12

Console-panel.png

Handouts mass set view by all

loops over each handout and ask if it should be visible to all players
By Stormy
(function () {
   Campaign.handouts.models.forEach(h => {
       if(confirm(`Make ${h.attributes.name} visible by all?`)) {
           h.save({inplayerjournals: "all"});
       }
   })
})();


Cards

minify playing card icons next to user avatars (lasts that session)
By Stormy
#playerzone .deckhands {
   min-height: unset;
}
#playerzone .deckhands .hand .cardback img {
   max-height: 20px;
   max-width: 20px;
}
#playerzone .deckhands .hand .cardback span {
   font-size: 12px;
}


Character Sheets

create an arbitrary amount of blank characters that can be viewed and edited by everyone:
By Stormy
(function() {
   var numCharacters = parseInt(prompt("How many blank characters should be created?", "1"));
   for (var i = 0; i < numCharacters; i++) {
       window.Campaign.characters.create({
           name: `Blank Sheet ${i}`,
           inplayerjournals: "all",
           controlledby: "all"
       });
   }
})();


Music Tracks

for each, track prompt whether it should be deleted or not
By Stormy
(function() {
   let idx = Jukebox.playlist.models.length;
   while(idx --> 0) {
       const track =  Jukebox.playlist.models[idx];
       if(confirm(`Delete track "${track.attributes.title}"?`)) {
           track.destroy();
       }
   }
})();



Bookmarklet for LFG

to filter out Roll 20 LFG games with too many players

(Adjust the 50 at the end to tweak the cutoff; default, unsurprisingly, is 50. Remove the leading javascript: and paste into the console for non-bookmarklet use)

By Giddy
javascript:((thresh) => {$(`.lfglisting`).each((i, e) => {const $e = $(e);const cur = Number($e.find(`strong:contains('Current Player')`).text().replace(/^([\d,.]+) C.*$/g, "$1"));const open = Number($e.find(`span:contains('Open Slot')`).text().replace(/[()]/g, "").replace(/^([\d,.]+) O.*$/g,"$1"));const total = cur + open;if (total > thresh) $e.remove();});})(50);


Jukebox

clear jukebox
By Stormy
(function(){
   while(Jukebox.playlist.length) {
       Jukebox.playlist.models[0].destroy();
   }
   d20.Campaign.save({jukeboxfolder: ""});
})();


Jukebox keybind

bind playlists/tracks to keys
By Stormy
(function() {
   const startPlaylist = (playlistName) => { 
       $(`#jukeboxfolderroot div.folder-title:contains("${playlistName}")`).closest(".dd-content").find(".playlistcontrols .play").click();
   };

  const startTrack = (trackName) => {
       $(`#jukeboxfolderroot .jukeboxitem .title:contains("${trackName}")`).closest(".dd-content").find(".play").click()
   };

   Mousetrap.unpause();

   Mousetrap.bind("g", () => startPlaylist("my playlist name"));
   Mousetrap.bind("j", () => startPlaylist("my other playlist"));
   Mousetrap.bind("n", () => startPlaylist("asdf"));
   Mousetrap.bind("a", () => startTrack("ye"));
})()


Character to Handout

Migrate character bio/gm notes/avatar into handouts with the character names
By Stormy
(function(){
Campaign.characters.models.forEach(m => {
   if(!confirm(m.attributes.name)) return;

   let handout = Campaign.handouts.create({
       name: m.attributes.name,
       avatar: m.attributes.avatar
   });

   const migrateBlob = (sourceName, destName) => {
       m._getLatestBlob(sourceName, data => {

           handout.updateBlobs({
               [destName]: data
           });

           handout.save({
               [destName]: Date.now()
           });
       });
   };

   migrateBlob("bio", "notes");
   migrateBlob("gmnotes", "gmnotes");
});
})()


Handout Purge

delete all handouts (you will be asked if you want to delete this handout)
By Stormy
(function(){Campaign.handouts.forEach(h => {if(confirm(h.attributes.name)) h.destroy()})})()


Dump my Art library to a zip

Take the users Art library and then take the
By stormy
(async () => {
   const unknown_objects = [];
   const root = await (await fetch("https://app.roll20.net/image_library/fetchroot")).json();
   const mkReq=url=>{
       const r = new XMLHttpRequest();
       return new Promise((resolve, reject)=>{
           r.open("GET", url, true);
           r.responseType="arraybuffer";
           r.onload =()=>resolve({buff: r.response});
           r.onerror =e=>reject(new Error(`${e}`));
           r.send();
       });
   };
   const appendFile = async (url, title, zip) => {
       let data = null;
       try {
           data = await mkReq(url);
       } catch (e) {
           try {
               data = await mkReq(`https://cors-anywhere.herokuapp.com/${url}`);
           } catch (e) {
               d20plus.ut.error(`err"${title}" (${url}): ${e.message}.`);
           }
       }
       zip.file(title, data.buff, {binary: true});
       return data;
   };
   const p = [];
   const zip = new JSZip();
   const visitFolder = async (folder, zipFolder) => {
       for (const data of folder) {
           if (data.t == "item") {
               if (data.fullsize_url) {
                   const name = data.n || "file";
                   d20plus.ut.log(name);
                   p.push(appendFile(data.fullsize_url, name, zipFolder));
               }
               else {
                   unknown_objects.push(data);
               }
           }
           else if (data.t == "folder") {
               const child = await (await fetch(`https://app.roll20.net/image_library/fetchlibraryfolder/${data.id}`)).json();
               await visitFolder(child, zipFolder.folder(data.n));
           }
       }
   };

   await visitFolder(root, zip);
   await Promise.all(p);
   zip.generateAsync({type: "blob"}).then((content) => d20plus.ut.saveAs(content, `roll20_assets.zip`));
})();


Jukebox

adds jukebox playlist keyboard shortcuts
By Rich
$(document).keyup((e) => {
   if (e.altKey && e.keyCode > 48 && e.keyCode < 58) {
       const numberKey = e.keyCode - 48;
       const playlistId = d20plus.jukebox.getJukeboxFileStructure()[numberKey-1].id;
       $(document)
           .find(`#jukeboxfolderroot .dd-folder[data-globalfolderid="${playlistId}"]`)
           .find("> .dd-content .play[data-isplaying=false]")
           .trigger("click")
   }
});


DM Screen

Hide the DM screen's settings tab/widget
By Dusk
$(".sidemenu__toggle").hide();



Roll20 Character F'd up?

https://app.roll20.net/forum/post/5662016/
how-to-find-character-id (that's probably the easiest way)
If you select a token that represents that character you could use this chat command: @{selected|character_id} and once you have that, ctrl-shift-J to open the console and here's a slightly friendlier version of that code, so you only have to change one thing; switch MY_ID_HERE for the character ID
By Giddy
((charId) => { d20.Campaign.characters.get(charId).view.$el.find(`[accept]`).val(null).each(function(){d20.Campaign.characters.get(charId).view.saveSheetValues(this)}) 
})("MY_ID_HERE")


Roll20 Screen Capture

takes the current map and generates images of such
By Stormy
(function(){
   window.imgDownloadIncrement = window.imgDownloadIncrement || 0;
   const a = document.createElement("a");
   a.download = String(window.imgDownloadIncrement++);
   a.href =document.getElementById("maincanvas").toDataURL("image/png", 1.0);
   document.body.appendChild(a);
   a.click();
   a.remove();
})()