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

Maybe I'm just tired - Getting Token Id from Character Name

Working on an API, and I'm struggling to get the token id given the character's name.  Here's what I have on('ready', function()  {     on('chat:message', function(msg)      { var charname = msg.who; log ("Found [" + charname + "]"); var casterTok = findObjs({ type: 'token', name: charname})[0] log (casterTok)     }); }); But the log always shows undefined.  So what am I missing....
1657437140
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
Is "Darryn" the character name or the player name?
1657453529
David M.
Pro
API Scripter
Yeah, msg.who definitely returns the player name like Keith is suggesting. A description of the msg object can be found  here . If you are trying to return an array of tokens controlled by the player that sent the api msg, then you'll need additional filters in your findObjs (like _pageid, and controlledby, perhaps others). This will always return an array, even if only one token is controlled on the page. Depending on what you are ultimately trying to accomplish, this method can be somewhat unreliable, as it is not uncommon for players to control more than one token on a page (npc followers, pets, spell effect tokens, etc), and they won't necessarily be returned in the order that you want. This is why so many scripts require a token to be selected (grabbing msg.selected) or require passing in additional information. What is the idea behind the script? Or are you just using it to get a feel for the api?
1657456020

Edited 1657457666
keithcurtis said: Is "Darryn" the character name or the player name? In this case it was actually both the character and player name (I use that token to check UDL), but your correct that I am trying to get a token from a player id. So I changed it to this where I parse the message for the character name from the templated message, but it still comes down to the same thing.   I figure out what I was missing to find the token, now how do I get the ID?  This line give me an error  log (singleTok[0].id) on('ready', function()      {         on('chat:message', function(msg)              {                 log (msg)                 var start = msg.content.indexOf("{{charname=")                 var end = msg.content.indexOf("}}", start)         var charname = msg.content.substring(start + ("{{charname=").length, end-1).trim()         log ("Found [" + charname + "]");         var casterTok = findObjs({name: charname})         log (casterTok)                 var singleTok = findObjs({type: "graphic", subtype: "token", name: charname})         log (singleTok)         log (singleTok[0].id)             }         );     } );
1657461671
David M.
Pro
API Scripter
Try including the underscore singleTok[0]._id Normally Roll20 objects are different than basic js objects in that to obtain their properites you need to use the roll20 "get" method, but IIRC the _id is an exception to this? If that doesn't work then try singleTok[0].get("_id") Sry I'm on mobile sitting in a waiting room (and I forgot my glasses haha) so hard to look things up. Just going from memory 
1657467944

Edited 1657468337
The Aaron
Roll20 Production Team
API Scripter
Here's how I'd write what you have so far: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 on ( 'ready' ,() => { // extracts roll template fields from message contents as a map const parseRollTemplate = ( t ) => [... t . matchAll ( /{{([^=]*)=(.*?)}}/g )]. reduce (( m , p ) => ({... m ,[ p [ 1 ]] : p [ 2 ]}),{}); // finds the page for the given playerid const getPageForPlayer = ( playerid ) => { let player = getObj ( 'player' , playerid ); if ( playerIsGM ( playerid )){ return player . get ( 'lastpage' ) || Campaign (). get ( 'playerpageid' ); } let psp = Campaign (). get ( 'playerspecificpages' ); if ( psp [ playerid ]){ return psp [ playerid ]; } return Campaign (). get ( 'playerpageid' ); }; on ( 'chat:message' , msg => { if ([ 'general' , 'whisper' ]. includes ( msg . type ) && 'API' !== msg . playerid ){ let who = ( getObj ( 'player' , msg . playerid ) || { get : () => 'API' }). get ( '_displayname' ); // find what page the player is currently on const pageid = getPageForPlayer ( msg . playerid ); // extract all roll template fields const rt = parseRollTemplate ( msg . content ); // grab the charname field from the rolltemplate const charname = rt . charname ; if ( charname ){ // find the character object that has this name and is controlled by this player (take first one) const character = findObjs ({ type : 'character' , name : charname }) . filter ( c => c . get ( 'controlledby' ). split ( /\w*,\w*/ ). includes ( msg . playerid ))[ 0 ]; if ( character ){ // get the token which represents that character on this page const token = findObjs ({ type : 'graphic' , represents : character . id , pageid })[ 0 ]; if ( token ){ sendChat ( '' , ` < div >< h2 >< code > $ { charname } < /code>'s token found!</h2><img style="max-width:10em;" src="${token.get('imgsrc')}"/></div>`); } else { log ( "No Token" ); } } else { log ( "Missing Character" ); } } else { log ( "Missing charname template" ); } } }); }); I left comments in the code, but to hit the highlights: On line 4, I wrote a general purpose roll template parser that extracts all the fields as properties on an object, with their values as the value.  It doesn't handle empty field names very well, but it's a good enough starting point for a general solution. On line 7, there's a function to find what page a player is on.  As you play, tokens for your player's characters will likely get spread across multiple maps, so this will help drill down to just the one on the current page for that player. On lines 34, 39, and 42, I'm checking that I got something back (and logging failure on lines 45, 48, and 51).  Error checking, even in trivial scripts, will save you lots of headache while developing and using the script. On line 36, I'm finding the character based on the name in the roll template, but I'm also filtering out characters the executing player can't control.  For players, this makes sense, but it will eliminate the behavior for a GM as they control all characters but are not in the controlledby field for them.  That means you might want to put yourself controlling your major NPCs if you want this behavior to trigger, or you might want to cause that filtering to be skipped if the player is a GM.  I'm also taking only the first returned character from this list. On line 41, I'm finding the graphic on the current page for the player that represents the character whose name was in the roll template, and I'm only taking the first one.  In the case that there are many tokens representing the character, you'll have to figure out what you'd like to do to further refine the choice (maybe keep a cache of the last moved token on a page for a given character id, or look at the tokens in the turn order). I don't know precisely what you're trying to do with your API, but there are many ways to skin a cat.  You might find that it makes sense to start with the Turn Order and just pull a list of tokens from that, then filter it by tokens that represent characters and are controlled by players, then find the one in that list that you mean to use.  You could also find the page the player is on, then find all the tokens on that page that represent characters which are controlled by players, and find the right one there.  It might even be that you can just use the token at the top of the turn order directly with the assumption that players only do something on their turn. Hope that helps!
1657468121
The Aaron
Roll20 Production Team
API Scripter
David M. said: Yeah, msg.who definitely returns the player name like Keith is suggesting.  Minor point of clarity: msg.who returns the speaking as name.  If no other character has been selected, it will be the player name (plus a "(GM)" decorator for GMs).
1657468419
David M.
Pro
API Scripter
Huh, I usually never change my "speaking as", so never noticed that. Thanks for clarifying!
1657468596
The Aaron
Roll20 Production Team
API Scripter
Yeah, I actually don't know anyone that does. =D  The only thing I've ever used it for was back before there was a playerIsGM() function in the API and I had a script called isGM that would build a cache of who had that little (GM) decorator appended to their name in chat messages.   That's also when I learned that the same msg object is passed to all scripts because HoneyBadger was stripping that tag off in Power Cards, which caused some problems for me in the design and implementation phase. =D  That also lead to the creation of Meta scripts, so it was good and bad. =D
1657469676

Edited 1657470876
The Aaron
Roll20 Production Team
API Scripter
Here's a version of that Roll Template parser that works for duplicate fields: const parseRollTemplateArray = (t) => [...t.matchAll(/{{([^=]*?)(?:=(.*?))?}}/g)].reduce((m,p)=>({...m,[p[1]]:[...(m[p[1]]||[]),...(undefined===p[2]?[""]:[p[2]])]}),{}); Each property is an array of the values supplied for the given field: > JSON.stringify(parseRollTemplateArray("{{=foo}}{{=bar}}{{s=c}}")); '{"":["foo","bar"],"s":["c"]}'
1657476341
timmaugh
Pro
API Scripter
The Aaron said: That also lead to the creation of Meta scripts, so it was good and bad. =D I trust you meant "good and better." =D
1657483169
The Aaron
Roll20 Production Team
API Scripter
Lol!
I read these when Aaron, Tim and David get deep into a subject and play a little game where I try to spot the exact point where they seem to switch to a language that I do not know...
1657516952
The Aaron
Roll20 Production Team
API Scripter
Hahaha!
1657541212
timmaugh
Pro
API Scripter
LOL. Also, sorry. Really. Sorry... about... all of that.
Ok TheAaron, love the script, but I can't seem to get it to work and I'm not sure what I am messing up. I created a new game, clean, only one API Created a character and token Whispered a message to Darryn But all that happens is Missing charname Template
The Aaron said: Here's a version of that Roll Template parser that works for duplicate fields: const parseRollTemplateArray = (t) => [...t.matchAll(/{{([^=]*?)(?:=(.*?))?}}/g)].reduce((m,p)=>({...m,[p[1]]:[...(m[p[1]]||[]),...(undefined===p[2]?[""]:[p[2]])]}),{}); Each property is an array of the values supplied for the given field: > JSON.stringify(parseRollTemplateArray("{{=foo}}{{=bar}}{{s=c}}")); '{"":["foo","bar"],"s":["c"]}' This gave me an error when I tried it so I wend back to the 1st one.
1657545478
timmaugh
Pro
API Scripter
What are you feeding to that parsing function, Darryn? It works for me just in a quick off-site test: const parseRollTemplateArray = (t) => [...t.matchAll(/{{([^=]*?)(?:=(.*?))?}}/g)].reduce((m,p)=>({...m,[p[1]]:[...(m[p[1]]||[]),...(undefined===p[2]?[""]:[p[2]])]}),{}); let msg = {content: `&{template:default}{{foo=bar}}{{Volcano Man=Not iceberg man}}{{Actual Cannibal}}{{=Normal Tuesday night}}`}; console.log(JSON.stringify(parseRollTemplateArray(msg.content),undefined,2)); I'm passing in the content of a message object and getting back an object populated with properties that have array values: '{   "foo": [     "bar"   ],   "Volcano Man": [     "Not iceberg man"   ],   "Actual Cannibal": [     ""   ],   "": [     "Normal Tuesday night"   ] }'
1657552417
The Aaron
Roll20 Production Team
API Scripter
The script inspects Roll Templates for a character name field, so if you aren't sending a roll template, or your roll template doesn't have a charname field, it won't find anything.  Here's the template I sent for testing: &{template:default}{{name=Test Script}}{{charname=Some Char}} I just based that requirement off of how your script was looking for a character name in a roll template: var start = msg.content.indexOf("{{charname=") var end = msg.content.indexOf("}}", start) var charname = msg.content.substring(start + ("{{charname=").length, end-1).trim()
1657555008

Edited 1657586448
SO  I figured it out and it worked great.  I'm going to study it some more to up my API skillz.
First question.  I am taking it from reading this line that we are looking for character objects controlled by the player that posted the message.  Now, how do I make this work (in a way that is better than brute force remove the filter) to have this also respond when the GM (me) posts a message?  I tried that and it didn't work (I had to post AS the player not the GM).  const character = findObjs({type:'character',name: charname}).filter(c=>c.get('controlledby').split(/\w*,\w*/).includes(msg.playerid))[0];
1657592876
timmaugh
Pro
API Scripter
You can add the test for if the player is a GM to your filter. Logical OR tests process until the first true case, so putting the playerIsGM() test in will mean that is the player is NOT a GM, your filter continues processing the record, looking to see if it qualifies by some other test. Your filter would look like... .filter(c=>playerIsGM() || c.get('controlledby').split...
You know, C# has the => function also, and I don't really get it there either LOL.  Been programming too long to learn too many new tricks
Ok, more testing.  This one is for Tim.  I added the filter change, but it is not working.  It is saying I am not the GM when the message is posted.  Added a log message to check
1657632233
timmaugh
Pro
API Scripter
That's what I get for air-coding. Sorry... you have to supply an ID to that function. So you'd want to feed the playerid from the message object (probably): log(`playerIsGM: ${playerIsGM(msg.playerid)}`); And your filter would be: .filter(c=>playerIsGM(msg.playerid) || (c.get('controlledby').split(/\w*,\w*/).includes(msg.playerid)))[0];
I air code all the time, so I get it.  Thanks.  This worked!  I need to up my JS skills it seems, a lot :) timmaugh said: That's what I get for air-coding. Sorry... you have to supply an ID to that function. So you'd want to feed the playerid from the message object (probably): log(`playerIsGM: ${playerIsGM(msg.playerid)}`); And your filter would be: .filter(c=>playerIsGM(msg.playerid) || (c.get('controlledby').split(/\w*,\w*/).includes(msg.playerid)))[0];
1657673695
The Aaron
Roll20 Production Team
API Scripter
=> is the "Fat Arrow" syntax for defining functions in Javascript. This: const foo = (bar) => `[${bar}]`; Is basically the same as: function foo(bar){ return `[${bar}]`; } It's mostly just a shorthand notation for creating a function, particularly an anonymous function.  There are some minor differences between the created function, but for all practical purposes, they're the same.
Yeah C# has this same construct, and I get the basics of the Lambda expression, but it still confuses me at times.  It reminds me of the time when OLE/COM/ActiveX was confusing to me and then one day I just "got it" :)  I'm sure that will happen here as well.. some day :)  Lambda's are used a lot more in JS that in C# (and they are done poorly in C# in my experience). Thanks Aaron for all the help.  I have really appreciated it.
1657717316
timmaugh
Pro
API Scripter
Generation ActiveX
1657722749
The Aaron
Roll20 Production Team
API Scripter
No problem!  I wouldn't get too hung up on the idea that => is the Javascript Lambda, all function in Javascript are objects (actually, everything is an object, including literals) and you could always make anonymous functions with the function keyword.  => is just a shorter notation that forces a cleaner syntax (contents are always under "use strict" edict) and has a this object bound to the lexical scope of creation (meaning you can do short little operations inside an object's function without having to save the object's this inside the closure before creating the sub function).