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 .
×
Advertisement Create a free account

Trying to write my first API...

1622904209
Ok so I am trying to write my own API code.  What I am trying to do is respond to messages like this: /emas "Darryn" is: Unconscious Where "Darryn" is the character name and the condition is after the ":" These are posted from the Beyond20 browser addin when the player sets their condition on their character sheet.  What I am trying to do ultimately is capture when a player sets a condition, then call an API script (CombatMaster 2.0 in this case) to set the condition status for the token.  I wanted to start out by making sure I can capture the chat message, but this is not seeming to work.  I am sure I just missed something easy, but I can't figure it out right now :( on('ready', function() {     on('chat:message', function(msg) {        if (msg.content.indexOf('emas') !== -1) {            sendChat('', 'Here');            //var charname = msg.content.split(' ')[1];            //var c = getObj('character',charname);            //if(c) {            //   sendChat('', "Found: " + charname);            //}        };     }); });
1622906829
timmaugh
Pro
API Scripter
Couple of things... first, I'd suggest sticking to logging for test output -- especially when you're looking to establish what messages this script will pick up versus those it will not. One wrong move on your parameters and the chat message that you send as a confirmation could, itself, trigger your script to send another confirmation, and another... and another... infinite loop style. So instead of sendChat, try  log(...) ... generally. That said, although the API *does* get every message (not just those intended to bypass the chat interface by means of a leading exclamation point), it doesn't always get what you think... because the Roll20 parsers have at the message before the API gets it. In this case, if you drop a: log(msg.content); ...in the chat handler, you'll see that the /emas "Darryn" is eaten, and you only receive the rest of the message. So you'll need another way to detect that you want to do something with this message... and if it is auto-generated from the sheet, you might not have a lot of leeway in what you can change/add. If you can think about a way to control what the command line looks like, this discussion might help with ways to break down your command line once your API receives it.
1622911431
Thanks.  That has been helpful.  Another question.  I have found that I can use msg.Who to figure out who sent the message (since /emas "Darryn" is stripped off).  And I have found how to find the token based on the "Who"  var c = findObjs({ type: 'character', name: charname})[0]; Is there a way to "select" that token from the API?
1622913263
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
Unfortunately, the API cannot act as a user in the GUI. It cannot select objects, only reference or edit them.
1622917281
timmaugh
Pro
API Scripter
Keith is absolutely right that the API can't change the GUI, but MetaScripts can plug some of the holes in that situation. So let's talk about why you want a token selected. If you want the token selected so that you would get the buttons that are set up for token actions, then you're out of luck. The limitation that Keith mentioned is going to stop you. But if you just need the token to be selected for a message, then you're really operating in one of two messages. You're either in the message that began with the EMAS, or in another message that you would like to trigger. Now, MetaScripts only operate on an API message, so those messages that begin with an exclamation point, which means that they wouldn't work on that initial message coming off of the character sheet. But by this point you have determined the token ID already, so you should be able to reference anything on it you need to. You're building that script, so you make it do what you want it to do. If, on the other hand, you have another script or another command line that you want to trigger and you need that particular token to be selected so that when the command line comes up and expects a selected token it would find that one token to be selected, a meta script can help with that. If you can drop a select manager syntax token into your downstream command line, you can virtually select the token. In this case, " virtually " means that the token will only seem to be selected to that message that is generated by the downstream script call. But that might be enough. As an example, if you want to trigger an alterbar call when you see the EMAS message come off of the character sheet, then you could determine and trap that token ID as you are already doing, and then put a select manager syntax token in the alterbar command line; !alterbar ... {& select -M1234567890abcdef} If you can't figure out how to insert that token ID in the select manager syntax structure within the alter-bar command line that might be on a character sheet in an ability, or in a macro, then you could always write it to an attribute on a character sheet and use that in the select manager syntax structure since the roll 20 parser will return the value of that attribute before select manager processes the syntax structure to virtually select the token that matches that ID. If that makes sense.
1622925485
Yeah, basically I want to capture the /emas command, parse it to know what condition is being set (got that part figured out) and then call a CombatMaster script like this.  But CM requires a selected token !cmaster --add,condition=poisoned,duration=?{Duration|1},direction=?{Direction|-1}
1622926150
Ok, I have found a workaround for the selection part (though I will look into select manager).  Last question, how do you call an API call from within an API? I tried this sendChat('', '!cmaster --add,condition=unconscious,duration=?{Select token then enter duration|1},direction=?{Direction|-1}') and got this :(
1622926771
The Aaron
Forum Champion
API Scripter
You can't use ?{query} or @{selected} or @{target} references in an api to api call. You need to expand those to a value before sending. 
1622930607
Can you call a macro from the API? sendChat('', '#Condition-unconscious')
1622934803
Well I did find a way around it that works, but is nit quiet where I want to get to. on('ready', function() { on('chat:message', function(msg) { if (msg.type !== "api" && msg.content.indexOf(" is: ") !== -1) { var condition = msg.content.split(':')[1]; var charname = msg.who; condition = condition.trim(); var c = findObjs({ type: 'character', name: charname})[0]; if(c) { log("Found " + charname) log('#Condition-unconscious') var msg = '/w ' + charname + '&{template:desc} {{desc=**' + charname + ' is ' + condition + '** \r\n\r\nYou have become [' + condition + '](!
#Condition-' + condition + '). \r\n\r\nPlease select your token and click the link to assign youself the condition\r\n}}' sendChat('GM', msg) } }; }); }); So it builds a template, whispers to that player, and then provides a link to click to run the appropriate macro (obviously it has to exist).  It works, but boy I would like this to automatically do this instead of requiring another click... I'm a .NET windows developer, so I know enough javascript to successfully crash the sandbox at least 3 times everytime I make a change but hey, I have a partial solution for what I want to do :) I'm still open to ideas on how to just have this macro or api call just run, but at least I have a starting point.
1622945852
The Aaron
Forum Champion
API Scripter
If you look at my OnMyTurn script, I've got code in there that modifies an ability to run from the api. You could adapt that to macros. 
1622951348

Edited 1622997848
timmaugh
Pro
API Scripter
If the only reason for the button is that you need the token selected, then SelectManager  can help you out. It looks like you probably have a few macros (differentiated by condition). If you are going to pull the macro text and edit it before sending, anyway, I doubt whether you need so many. You could have 1 that had a replaceable hook for the condition to apply, and a replaceable hook for the token to virtually select. But once you've gone as far as getting the macros reduced down to a single command line (over which you handle replacements), you might as well just put the command line in your script. const getCommand = (condition, token) => {   return `!cmaster --add,condition=${condition}{& select ${token}}`; }; Then you can call it and feed the condition (which you're already determining), and the character name (which you're also determining). SelectManager will look for a token named for that character. So long as there is a token on the page named for that character, it should be found and fed to the CombatMaster message *before* CombatMaster gets it. If you're not convinced that the token will be named for the character, then you could get all the tokens and filter them for where their pageid is the current page and their "represents" property is the character you are dealing with. Either way, you can supply that to the function to build the CombatMaster call. That doesn't account for the direction or duration arguments you were asking for, so if you really need them and you don't have them, you're back to a button. However, if you have them, you can modify the function to include them in the command line. Either way, since you should be able to tell which token you want to affect (and therefore virtually select), you don't have to put it on your players to select their token and worry about them mis-typing the duration. Send the button to you, pre-populated with the {& select... } statement, and you take care it.
1623127649
Hmm, I am going to have to look into Select Manager, but I wanted to share what I have so far (still using the button method).  It will handle all of these messages now: Darryn has no active conditions Outputs this Darryn is: Blinded, Charmed, Deafened Outputs this on('ready', function() { on('chat:message', function(msg) { if (msg.type === "emote") { log('--------------------------------------------------------') log(msg.type) log(msg.content); var post = '' var index = 0 var charname = msg.who; if (msg.content.indexOf("is: ") !== -1) { var conditions = msg.content.split(':')[1]; conditions = conditions.trim(); log('All ConditionS:' + conditions) var condition = conditions.split(',') log('Split Condition:' + condition) log(condition.length) log(charname + ' ' + msg.content); log("Get the Character"); var c = findObjs({ type: 'character', name: charname})[0]; log(condition[0]); if(c) { log("Found " + charname) post = '&{template:desc} {{desc=**' + charname + ' is ' + conditions + '**' for(index = 0; index < condition.length; ++index) ( post = post + '\r\n\r\nYou have become [' + condition[index].trim() + '](!
#Condition-' + condition[index].trim() + ').' ) post = post + '\r\n\r\nPlease select your token and click the link to assign youself the condition. If there is more than one, only apply the new ones to your character.\r\n}}' } } else if (msg.content.indexOf == ("has no active conditions") !== -1) { post = '&{template:desc} {{desc=**' + charname + ' ' + msg.content + '** \r\n\r\nYou are free from conditions. [Clear Conditions](!
#Clear-Conditions) \r\n\r\nPlease select your token and click the link to clear all your condition\r\n}}' } if (post.length> 0) sendChat('GM', post) }; }); }); So it is a start.  Thanks for the help (I am sure I will need more later)
1623208856

Edited 1623208883
Just a suggestion as I wander past drinking my morning coffee... Combat Master looks like it has a public interface return section at the end. You should be able to call these functions directly without having to go via the Roll20 chat parser. return { CheckInstall: checkInstall, RegisterEventHandlers: registerEventHandlers, ObserveTokenChange: observeTokenChange, addConditionToToken, removeConditionFromToken, addTargetsToCondition, getConditions, getConditionByKey, sendConditionToChat, getDefaultIcon }; I don't have any experience with it, but if you figure out the parameters the script is expecting, you should be able to call directly like: CombatMaster.addConditionToToken(tokenObj,key,duration,direction,message);
1623254152
You know, I never even thought about doing that.  Hmmm.   Even more options to explore.  Thanks.  That's actually a great idea.  I did browse the code briefly, but perhaps I need to read the code in a real editor instead of the little window Roll20 gives us (mind you the logging window is AMAZING for debugging the code).  Thanks @Oosh
1623521313
Ok I am looking into how to call CombatMaster.addConditionToToken from my script.  Is there something I have to do to make this available or maybe I am misunderstanding the direct call. var c = findObjs({ type: 'character', name: charname})[0]; if(c) {     log("Found " + charname)     for(index = 0; index < condition.length; ++index)     (         log(condition[index].trim().toLowerCase()) CombatMaster.addConditionToToken(c,condition[index].trim().toLowerCase(),1,0,'');     ) } But I get this error unless I comment out the call to CombatMaster Oosh said: Just a suggestion as I wander past drinking my morning coffee... Combat Master looks like it has a public interface return section at the end. You should be able to call these functions directly without having to go via the Roll20 chat parser. return { CheckInstall: checkInstall, RegisterEventHandlers: registerEventHandlers, ObserveTokenChange: observeTokenChange, addConditionToToken, removeConditionFromToken, addTargetsToCondition, getConditions, getConditionByKey, sendConditionToChat, getDefaultIcon }; I don't have any experience with it, but if you figure out the parameters the script is expecting, you should be able to call directly like: CombatMaster.addConditionToToken(tokenObj,key,duration,direction,message);
1623526830
timmaugh
Pro
API Scripter
Don't think it's the CM call (at least not yet). An error there would throw a line number at you for where your call failed. This kind of error means that your code did not compile, and I think I can see why. Your for loop needs braces for the loop. Parentheses for the parameters, braces around the commands that run in the loop. for( ... ) {   dothis(...); }
1623602430
OMG, I can't believe I missed that lol.  It's what you get when you keep looking at something too long I guess.
1623622673
timmaugh
Pro
API Scripter
We've all done it! BTW, you might investigate the .forEach for this, if only for the concision of the lines: let c = findObjs({ type: 'character', name: charname})[0]; if(c) { log(`Found ${charname}`); condition.forEach(cond => { log(cond.trim().toLowerCase()); CombatMaster.addConditionToToken(c,cond.trim().toLowerCase(),1,0,''); } }