After a ton of work and a ton of help from the Aaron, here is the latest version of !character-list Syntax: !character-list (Get a full list of all PCs/NPCs on the map that aren't blacklisted. Clicking on the button pings the token and moves screen to them) !character-list blacklist (Shows you a full list of all PCs/NPCs that aren't blacklisted. Clicking on the button for the specified token/sheet will blacklist it and it won't show up when running "!character-list".) !delete-blacklist (Completely clear all blacklisted tokens/sheet and resets "!character-list" to the default setting of showing ALL NPC/PCs) !blacklist [character id] (Blacklist a specific character sheet. You can opt to run "!character-list blacklist" and blacklist sheets by pressing the buttons, or do it manually with this command if you know the sheet ID) !whitelist [character id] (Remove a certain sheet from the blacklist by specifying the sheet ID.) In order for this script to work, it requires the !ping script I wrote to use the functionality of chat buttons being able to call API functions. It also requires the APISelection script written by The Aaron (Thanks again!) to manage the blacklist. First, here is the updated !character-list script. // By: Kastion // Syntax: !character-list // Profile: <a href="https://app.roll20.net/users/3173313/kastion" rel="nofollow">https://app.roll20.net/users/3173313/kastion</a> const getPageForPlayer = (playerid) => { let player = getObj('player',playerid); if(playerIsGM(playerid)){ return player.get('lastpage'); } let psp = Campaign().get('playerspecificpages'); if(psp[playerid]){ return psp[playerid]; } return Campaign.get('playerpageid'); }; var Represents = Represents || (function(){ 'use strict'; const keyCharacterBlacklist = 'character-list:blacklist'; var handleInput = function(msg) { if ( "api" !== msg.type || !playerIsGM(msg.playerid) ) { return; } if (msg.content.indexOf("!character-list") === -1 && msg.content.indexOf("!blacklist") === -1 && msg.content.indexOf("!delete-blacklist") === -1 && msg.content.indexOf("!whitelist") === -1) return; let blacklist = APISelection.getList(keyCharacterBlacklist); var rep_id = "" ; rep_id = msg.content.split(" ")[1]; const playerPageID = getPageForPlayer(msg.playerid); const tokens = findObjs({ _pageid: playerPageID, _type: "graphic" }); var buttons = "", hide = ""; if (msg.content.indexOf("!delete-blacklist") !== -1) { APISelection.deleteList(keyCharacterBlacklist); sendChat("CharacterList","/w gm The blacklist has been deleted."); return; } if (msg.content.indexOf("!blacklist") !== -1 && rep_id !== "") { APISelection.addToList(keyCharacterBlacklist, [rep_id]); sendChat("CharacterList","/w gm The character sheet with the ID [**" + rep_id + "**] has been blacklisted and will not show up when !character-list is ran."); return; } if (msg.content.indexOf("!whitelist") !== -1 && rep_id !== "") { if (!blacklist.includes(rep_id)) { sendChat("CharacterList","/w gm There was no sheet found with that ID in the blacklist."); return; } else { APISelection.removeFromList(keyCharacterBlacklist, [rep_id]); sendChat("CharacterList","/w gm The character sheet with the ID [**" + rep_id + "**] has been removed from the blacklist."); return; } } if(tokens.length) { _.each(tokens, function(obj) { if (obj.get("represents") != "" && !blacklist.includes(obj.get('represents'))) { var thisToken = findObjs({ _pageid: playerPageID, _type: "graphic", _represents: obj.get("represents")}); if (thisToken) { var token_name = obj.get("_name").replace(/\(/g,'').replace(/\)/g,'').replace(/\[/g,'').replace(/\]/g,''); if (token_name === "") token_name = "NO NAME"; buttons = buttons + "[" + token_name + "](!ping " + obj.get("_id") + ")"; if (!blacklist.includes(obj.get('represents'))) hide = hide + "[BLACKLIST " + token_name.toUpperCase() + "](!blacklist " + obj.get("represents") + ")"; } } }); if (buttons) { if (msg.content.split(" ")[1] && msg.content.split(" ")[1] == "blacklist") sendChat("","/w gm " + hide); else sendChat("","/w gm " + buttons); } }; }, registerEventHandlers = function() { on('chat:message', handleInput); }; return { RegisterEventHandlers: registerEventHandlers }; }()); on("ready",function(){ 'use strict'; Represents.RegisterEventHandlers(); log("-=> Character List Script Loaded <=- [Last Edited July 16th 2018]"); }); on('ready',()=>{ on('chat:message',(msg)=>{ if('api' !== msg.type ) { return; } var cmdName = "!ping"; var msgTxt = msg.content; if (msg.type == "api" && msgTxt.indexOf(cmdName) !== -1 && playerIsGM(msg.playerid)) { let args = msg.content.split(/\s+/); switch(args.shift().toLowerCase()){ case '!ping': { var t = findObjs({ _pageid: Campaign().get("playerpageid"), _type: "graphic", _id: args[0] }); _.each(t, function(obj) { sendPing(obj.get("left"), obj.get("top"), Campaign().get('playerpageid'), msg.playerid, true); }); } break; } } }); log("-=> Ping command loaded (!ping) <=-") }); // Github: <a href="https://github.com/shdwjk/Roll20API/blob/master/APISelection/APISelection.js" rel="nofollow">https://github.com/shdwjk/Roll20API/blob/master/APISelection/APISelection.js</a>
// By: The Aaron, Arcane Scriptomancer
// Contact: <a href="https://app.roll20.net/users/104025/the-aaron" rel="nofollow">https://app.roll20.net/users/104025/the-aaron</a>
const APISelection = (() => { // eslint-disable-line no-unused-vars
const version = '0.1.0';
const lastUpdate = 1531793735;
const schemaVersion = 0.1;
const checkInstall = () => {
log('-=> APISelection v'+version+' <=- ['+(new Date(lastUpdate*1000))+']');
if( ! state.hasOwnProperty('APISelection') || state.APISelection.version !== schemaVersion) {
log(` > Updating Schema to v${schemaVersion} <`);
state.APISelection = {
version: schemaVersion,
lists: {}
};
}
};
const isString = (s)=>'string'===typeof s || s instanceof String;
const assureArray = (a) => Array.isArray(a) ? a : [a];
const getList = (id) => [...(state.APISelection.lists[id] || [])];
const setList = (id, list) => [ ...(state.APISelection.lists[id] = [...new Set(assureArray(list).filter(isString))])];
const deleteList = (id) => delete state.APISelection.lists[id] && [];
const removeFromList = (id, list) => [...(state.APISelection.lists[id] = (state.APISelection.lists[id]||[]).filter((s)=>!assureArray(list).includes(s)))];
const addToList = (id, list) => [...(state.APISelection.lists[id] = [...new Set([...state.APISelection.lists[id]||[],...assureArray(list).filter(isString)||[]])])];
const handleInput = (msg) => {
if (msg.type !== "api" || !playerIsGM(msg.playerid)) {
return;
}
let args = msg.content.split(/\s+/);
switch(args[0]) {
case '!api-selection': {
const who=(getObj('player',msg.playerid)||{get:()=>'API'}).get('_displayname');
sendChat('API Selection', `/w "${who}" <div><ul>${Object.keys(state.APISelection.lists).map((id)=>`<li><code>${id}</code><ul>${state.APISelection.lists[id].map((v)=>`<li>${v}</li>`).join('')}</ul></li>`).join('')}</ul></div>`);
}
break;
}
};
const registerEventHandlers = () => {
on('chat:message', handleInput);
};
on('ready', () => {
checkInstall();
registerEventHandlers();
});
return { getList, setList, deleteList, removeFromList, addToList };
})();
Thanks again The Aaron for your contributions to this script. If anyone has any issues with the script or if you have any suggestions on how to improve it please let me know.