From 92d8ff3bc6938bab02c63995673f9ded8699ed73 Mon Sep 17 00:00:00 2001 From: manveti Date: Tue, 16 Jun 2015 17:25:33 -0700 Subject: [PATCH 1/2] Add wrapper around sendChat to execute CommandShell-aware API commands --- CommandShell/Help.txt | 3 +++ CommandShell/package.json | 2 +- CommandShell/shell.js | 26 +++++++++++++++++++++++--- ExtendedExpressions/extend.js | 11 +++++++++-- ExtendedExpressions/package.json | 2 +- cron/cron.js | 9 ++++++++- cron/package.json | 2 +- 7 files changed, 46 insertions(+), 9 deletions(-) diff --git a/CommandShell/Help.txt b/CommandShell/Help.txt index cb5172c025..c0fa5a76a2 100644 --- a/CommandShell/Help.txt +++ b/CommandShell/Help.txt @@ -79,6 +79,9 @@ The following utility functions are provided for convenience: Calls write(s, to) and logs each line of s to the API console. writeErr(s): Shorthand for writeAndLog(s, "gm"). + sendChat(speakingAs, input): + Wrapper around built-in sendChat, which will execute CommandShell-aware + API commands. tokenize(s): Splits the string s into an array based on whitespace. Quotes preserve spaces, and adjacent quotes are merged (as with POSIX shell command diff --git a/CommandShell/package.json b/CommandShell/package.json index b0cb4bde73..107299e4f2 100644 --- a/CommandShell/package.json +++ b/CommandShell/package.json @@ -1,6 +1,6 @@ { "name": "CommandShell", - "version": "1.1", + "version": "1.2", "description": "Framework for chat commands", "authors": "manveti", "roll20userid": "503018", diff --git a/CommandShell/shell.js b/CommandShell/shell.js index 5b885f081f..abb7a4f86f 100644 --- a/CommandShell/shell.js +++ b/CommandShell/shell.js @@ -25,6 +25,22 @@ var Shell = Shell || { Shell.writeAndLog(s, "gm"); }, + sendChat: function(speakingAs, input){ + if ((input.length <= 0) || (input.charAt(0) != '!')){ + return sendChat(speakingAs, input); + } + function processCommand(msgs){ + var doSend = true; + for (var i = 0; i < msgs.length; i++){ + if (Shell.handleApiMessage(msgs[i])){ + doSend = false; + } + } + if (doSend){ sendChat(speakingAs, input); } + } + sendChat(speakingAs, input, processCommand); + }, + // command registration @@ -271,9 +287,7 @@ var Shell = Shell || { return false; }, - handleChatMessage: function(msg){ - if (msg.type != "api"){ return; } - + handleApiMessage: function(msg){ // tokenize command string var tokens = Shell.tokenize(msg.content); if (typeof(tokens) == typeof("")){ @@ -294,6 +308,12 @@ var Shell = Shell || { // execute command callback Shell.commands[tokens[0]].callback(tokens, _.clone(msg)); + return true; + }, + + handleChatMessage: function(msg){ + if (msg.type != "api"){ return; } + Shell.handleApiMessage(msg); }, init: function(){ diff --git a/ExtendedExpressions/extend.js b/ExtendedExpressions/extend.js index 70c4b72796..21056e65bf 100644 --- a/ExtendedExpressions/extend.js +++ b/ExtendedExpressions/extend.js @@ -217,7 +217,7 @@ var ExExp = ExExp || { if (err){ return err; } return parseHelper(); } - return "Error: Unrecognized token: " + s.tok.text; + return "Error: Unrecognized token: " + s.tok.text + (s.tok.type == "raw" ? s.s.split(" ", 1)[0] : ""); } // if we were given a string, construct a state object @@ -299,6 +299,10 @@ var ExExp = ExExp || { sendChat(from, who + s.replace(//g, ">").replace(/\n/g, "
")); }, + sendChat: function(speakingAs, input){ + return sendChat(speakingAs, input); + }, + sendCommand: function(chunks, asts, evalResults, inline, from, labels){ // constants var FUNCTION_FUNCTIONS = { @@ -680,7 +684,7 @@ var ExExp = ExExp || { return ExExp.sendCommand(chunks, asts, [], inline, from, labels) } // if we got here, we're done evaluating everything; submit results via sendChat - sendChat(from, chunks.join("")); + ExExp.sendChat(from, chunks.join("")); }, showHelp: function(who){ @@ -793,6 +797,9 @@ var ExExp = ExExp || { if (Shell.write){ ExExp.write = Shell.write; } + if (Shell.sendChat){ + ExExp.sendChat = Shell.sendChat; + } } else{ on("chat:message", ExExp.handleChatMessage); diff --git a/ExtendedExpressions/package.json b/ExtendedExpressions/package.json index e778245662..ccb9365864 100644 --- a/ExtendedExpressions/package.json +++ b/ExtendedExpressions/package.json @@ -1,6 +1,6 @@ { "name": "ExtendedExpressions", - "version": "0.4", + "version": "0.5", "description": "Extended roll expression syntax, supporting conditionals, variable references, bitwise operators, and more.", "authors": "manveti", "roll20userid": "503018", diff --git a/cron/cron.js b/cron/cron.js index e27f7013fd..0feefe624b 100644 --- a/cron/cron.js +++ b/cron/cron.js @@ -38,8 +38,12 @@ var cron = cron || { cron.nextJob = 1; // we'll check for ID uniqueness when creating job, so initializing to 1 will give us the lowest available ID }, + sendChat: function(speakingAs, input){ + return sendChat(speakingAs, input); + }, + handleJob: function(job){ - sendChat(job['from'] || "CronD", job['command']); + cron.sendChat(job['from'] || "CronD", job['command']); }, handleTimedJob: function(jobId){ @@ -448,6 +452,9 @@ var cron = cron || { if (Shell.write){ cron.write = Shell.write; } + if (Shell.sendChat){ + cron.sendChat = Shell.sendChat; + } } else{ on("chat:message", cron.handleChatMessage); diff --git a/cron/package.json b/cron/package.json index 4ef756bd1e..aa966f1c16 100644 --- a/cron/package.json +++ b/cron/package.json @@ -1,6 +1,6 @@ { "name": "cron", - "version": "0.5", + "version": "0.6", "description": "Schedule (possibly recurring) commands to run at some point in the future.", "authors": "manveti", "roll20userid": "503018", From 5d55621ec56bab7c6408ae7b96e543de72628364 Mon Sep 17 00:00:00 2001 From: manveti Date: Thu, 11 Jun 2015 20:20:42 -0700 Subject: [PATCH 2/2] Add script to get and set object properties --- ObjectProperties/Help.txt | 82 ++++++++++++ ObjectProperties/package.json | 10 ++ ObjectProperties/properties.js | 226 +++++++++++++++++++++++++++++++++ 3 files changed, 318 insertions(+) create mode 100644 ObjectProperties/Help.txt create mode 100644 ObjectProperties/package.json create mode 100644 ObjectProperties/properties.js diff --git a/ObjectProperties/Help.txt b/ObjectProperties/Help.txt new file mode 100644 index 0000000000..5fe5de093d --- /dev/null +++ b/ObjectProperties/Help.txt @@ -0,0 +1,82 @@ +ObjectProperties + +ObjectProperties allows the user to get and set properties of specified objects. +This provides quick access to token IDs, image URLs, etc., as well as the +ability to set token bar values, move tokens, and various other tricks which +would otherwise require (very small) specialized scripts. + +It is recommended that this script be used in conjunction with the CommandShell +module, which will improve output formatting and command discovery. + + +Commands: + + !getprop [options] [property1] [property2] ... + Gets specified properties (or all properties, if none specified) of + specified (or selected) object(s). + + !setprop [options] property1 value1 [property2 value2] ... + Sets specified properties of specified (or selected) object(s) to the + specified values. + +Valid properties can be found in the API documentation. ObjectProperties only +accesses directly-accessible properties, so BLOB properties like "bio", "notes", +and "gmnotes" are inaccessible, as are properties the API can't access (like +"_defaulttoken"). + +The above commands accept the following options: + + -h, --help Displays a help message and exits. + + -t T, --type T Specifies the type (e.g. graphic) of objects + specified after this argument. Can be passed + multiple times to get properties of objects of + different types (see examples below). Does not + directly specify any objects (use -i, -n, etc., + described below). + + -i ID, --id ID Specifies the ID of an object. This should come + after a -t option to specify the type for more + efficient lookup. Can be passed multiple times + to specify multiple objects. + + -n NAME, --name NAME Specifies the name of an object, for types which + have a "name" property. This should come after + a -t option in order to be unique (without a + type, this will affect all objects with the + specified name). + + -r, --relative When setting properties, the new value will be + added to the existing value, rather than + overwriting it. + + +Examples: + +!getprop + Will display all properties of each selected object. + +!getprop -t graphic -i -Jp3illTx5IjO2NMoxEC name + Will display the name of the specified graphic object (token). + +!getprop -n Fighter _id _type + Will display the ID and type of all objects named "Fighter" (e.g. a + character and its associated tokens). + +!getprop _pageid + Will display the ID of the page containing the selected object (an easy way + for a GM to get the ID of the page currently being viewed). + +!setprop -r top 70 left -70 + Will move the selected object(s) one square down and one square left. + +!setprop top +70 left -70 + CAUTION: Unlike the previous command, this will move the selected object(s) + to the coordinates (-70,70), or off the left edge of the map along the first + horizontal grid line. + +!setprop -r bar1_value -3 + Will subtract 3 from bar 1 of the selected token(s). + +!setprop bar1_value -3 + CAUTION: Will set bar 1 of the selected token(s) to "-3". diff --git a/ObjectProperties/package.json b/ObjectProperties/package.json new file mode 100644 index 0000000000..3b9468f6a7 --- /dev/null +++ b/ObjectProperties/package.json @@ -0,0 +1,10 @@ +{ + "name": "ObjectProperties", + "version": "0.1", + "description": "Get and set properties of selected/specified Roll20 objects.", + "authors": "manveti", + "roll20userid": "503018", + "dependencies": {}, + "modifies": {}, + "conflicts": [] +} diff --git a/ObjectProperties/properties.js b/ObjectProperties/properties.js new file mode 100644 index 0000000000..de4e39b212 --- /dev/null +++ b/ObjectProperties/properties.js @@ -0,0 +1,226 @@ +var ObjectProperties = ObjectProperties || { + EXCLUDE_KEYS: {'bio': true, 'notes': true, 'gmnotes': true, '_defaulttoken': true}, + SPACES: " ", + + rawWrite: function(s, who, style, from){ + if (who){ + who = "/w " + who.split(" ", 1)[0] + " "; + } + sendChat(from, who + s.replace(/\n/g, "
")); + }, + + write: function(s, who, style, from){ + return ObjectProperties.rawWrite(s.replace(//g, ">"), who, style, from); + }, + + showHelp: function(who){ + var helpMsg = ""; + helpMsg += "Usage: !getprop [options] [property]\n"; + helpMsg += " or: !setprop [options] property value\n"; + helpMsg += "!getprop gets specified property (or all properties) of specified (or selected) object(s).\n"; + helpMsg += "!setprop sets specified property of specified (or selected) object(s) to specified value.\n"; + ObjectProperties.write(helpMsg, who, "", "OP"); + helpMsg = "Options:\n"; + helpMsg += " -h, --help: display this help message\n"; + helpMsg += " -t T, --type T: specify type (e.g. graphic) of all objects referenced after this argument\n"; + helpMsg += " -i ID, --id ID: specify object ID\n"; + helpMsg += " -n N, --name N: specify object name, for types which have a \"name\" property\n"; + helpMsg += " -r, --relative: add value to existing property value rather than overwriting it\n"; + ObjectProperties.write(helpMsg, who, "font-size: small; font-family: monospace", "OP"); + }, + + spaces: function(n){ + while (ObjectProperties.SPACES.length < n){ + ObjectProperties.SPACES += ObjectProperties.SPACES; + } + return ObjectProperties.SPACES.substring(0, n); + }, + + displayObjectProperties: function(who, obj, properties){ + var keys = [], values = {}, maxPropLen = 0; + if (properties.length > 0){ + for (var i = 0; i < properties.length; i++){ + if ((!properties[i]) || (ObjectProperties.EXCLUDE_KEYS[properties[i]])){ continue; } + if (properties[i].length > maxPropLen){ + maxPropLen = properties[i].length; + } + keys.push(properties[i]); + values[properties[i]] = obj.get(properties[i]); + } + } + else{ + for (var k in obj.attributes){ + if ((ObjectProperties.EXCLUDE_KEYS[k]) || (!obj.attributes.hasOwnProperty(k))){ continue; } + if (k.length > maxPropLen){ + maxPropLen = k.length; + } + keys.push(k); + values[k] = obj.get(k); + } + keys.sort(); + } + var output = ""; + for (var i = 0; i < keys.length; i++){ + output += keys[i] + ": " + ObjectProperties.spaces(maxPropLen - keys[i].length) + values[keys[i]] + "\n"; + } + ObjectProperties.write(output, who, "font-size: small; font-family: monospace", "OP"); + }, + + setObjectProperties: function(who, obj, properties, values, relative){ + function numify(x){ + var xNum = x; + if (typeof(x) == typeof("")){ + if (x.charAt(0) == "+"){ x = x.substring(1); } + xNum = parseFloat(x); + } + if ("" + xNum == "" + x){ return xNum; } + return x; + } + var output = "", updateAttrs = {}; + for (var i = 0; i < properties.length; i++){ + if ((!properties[i]) || (ObjectProperties.EXCLUDE_KEYS[properties[i]])){ continue; } + var newVal = numify(values[i]), curVal = numify(obj.get(properties[i])); + if (relative){ + newVal = curVal + newVal; + } + output += properties[i] + ":\n"; + output += " old: " + curVal + "\n"; + output += " new: " + newVal + "\n"; + updateAttrs[properties[i]] = newVal; + } + obj.set(updateAttrs); + ObjectProperties.write(output, who, "font-size: small; font-family: monospace", "OP"); + }, + + handleObjectPropertiesMessage: function(tokens, msg){ + var objects = []; + var objArgs = {}; + var getObjArg = null, objArgFinal = false; + var properties = [], values = []; + var relative = false; + for (var i = 1; i < tokens.length; i++){ + if (getObjArg){ + objArgs[getObjArg] = tokens[i]; + getObjArg = null; + if (objArgFinal){ + if ((objArgs['_type']) && (objArgs['_id'])){ + var obj = getObj(objArgs['_type'], objArgs['_id']); + if (obj){ objects.push(obj); } + } + else{ + var objs = findObjs(objArgs); + while (objs.length > 0){ + objects.push(objs.shift()); + } + } + for (var k in objArgs){ + if ((objArgs.hasOwnProperty(k)) && (k != '_type')){ + delete objArgs[k]; + } + } + objArgFinal = false; + } + continue; + } + switch (tokens[i]){ + case "-t": + case "--type": + getObjArg = '_type'; + break; + case "-i": + case "--id": + getObjArg = '_id'; + objArgFinal = true; + break; + case "-n": + case "--name": + getObjArg = 'name'; + objArgFinal = true; + break; + case "-r": + case "--relative": + relative = true; + break; + default: + if ((tokens[0] == "!getprop") || (properties.length == values.length)){ + if (ObjectProperties.EXCLUDE_KEYS[tokens[i]]){ + ObjectProperties.write("Warning: Skipping special property " + tokens[i], msg.who, "", "OP"); + properties.push(null); + } + else{ + properties.push(tokens[i]); + } + } + else{ + values.push(tokens[i]); + } + } + } + if (getObjArg){ + ObjectProperties.write("Expected argument for " + getObjArg, msg.who, "", "OP"); + return ObjectProperties.showHelp(msg.who); + } + + // if no objects specified, look for selected objects + if ((objects.length <= 0) && (msg.selected)){ + for (var i = 0; i < msg.selected.length; i++){ + var obj = getObj(msg.selected[i]._type, msg.selected[i]._id); + if (obj){ objects.push(obj); } + } + } + + // if still no objects specified, error + if (objects.length <= 0){ + ObjectProperties.write("No objects specified or selected", msg.who, "", "OP"); + return ObjectProperties.showHelp(msg.who); + } + + if (tokens[0] == "!getprop"){ + for (var i = 0; i < objects.length; i++){ + ObjectProperties.rawWrite("
", msg.who, "", "OP"); + ObjectProperties.displayObjectProperties(msg.who, objects[i], properties); + } + ObjectProperties.rawWrite("
", msg.who, "", "OP"); + } + else{ + // !setprop; verify command has at least one property and same number of properties and values + if (properties.length <= 0){ + ObjectProperties.write("Must specify at least one property to set", msg.who, "", "OP"); + return ObjectProperties.showHelp(msg.who); + } + if (properties.length != values.length){ + ObjectProperties.write("Must specify a value for each property to set", msg.who, "", "OP"); + return ObjectProperties.showHelp(msg.who); + } + for (var i = 0; i < objects.length; i++){ + ObjectProperties.rawWrite("
", msg.who, "", "OP"); + ObjectProperties.setObjectProperties(msg.who, objects[i], properties, values, relative); + } + ObjectProperties.rawWrite("
", msg.who, "", "OP"); + } + }, + + handleChatMessage: function(msg){ + if ((msg.type != "api") || ((msg.content.indexOf("!getprop") != 0) && (msg.content.indexOf("!setprop") != 0))){ return; } + + return ObjectProperties.handleObjectPropertiesMessage(msg.content.split(" "), msg); + }, + + registerObjectProperties: function(){ + if ((typeof(Shell) != "undefined") && (Shell) && (Shell.registerCommand)){ + Shell.registerCommand("!getprop", "!getprop [options] [property]", "Get object properties", ObjectProperties.handleObjectPropertiesMessage); + Shell.registerCommand("!setprop", "!setprop [options] property value", "Set object property", ObjectProperties.handleObjectPropertiesMessage); + if (Shell.rawWrite){ + ObjectProperties.rawWrite = Shell.rawWrite; + } + if (Shell.write){ + ObjectProperties.write = Shell.write; + } + } + else{ + on("chat:message", ObjectProperties.handleChatMessage); + } + } +}; + +on("ready", function(){ ObjectProperties.registerObjectProperties(); });