JavaScript from Chrome console ok. Doesn't work from KM

I am trying to automate my online Dungeons & Dragons game. Critical. I know. :wink:

I am using Foundry VTT which is a NodeJS web app, and Google Chrome to connect to the server.

If I'm logged in to Foundry, I can open the console and type this:

game.macros.getName('Mark Defeated').execute()

It works as I'd expect. It runs a Foundry macro called "Mark Defeated".

If I put the same line of code into Keyboard Maestro "Execute Javascript in Google Chrome" and run it, I get the following error in the console:

Uncaught ReferenceError: game is not defined
at <anonymous>:4:1
at <anonymous>:5:4

I only have 1 tab open, the one running Foundry.

Any ideas would be welcome.

Jon

See: Allow JavaScript from Apple Events

https://wiki.keyboardmaestro.com/assistance/Web_Browser_Automation?redirect=1#Google_Chrome

Thank you for the suggestion. I already have Allow JavaScript from Apple Events turned on.

The issue is something else. Since it doesn't understand "game" it's like it's only talking to Chrome, and not the Foundry app running in Chrome.

Another thing to check is whether the JS evaluation space in which the game is running is that of:

  • the browser document model
  • a Chrome extension

The latter would be inaccessible to JS from Apple Events.


Reading again, I see that you did specify a NodeJS app – again, out of reach, I think.

jonmichaels,
It is because the code runs in a separate JavaScript context when executed by the KM's action "Execute Javascript in Google Chrome", which is different from both the browser’s global window object and the web page’s script context.

In Browser dev console, the code can access the window object and the page's script context hence no issue.

You might want to try Tampermonkey (https://www.tampermonkey.net/) which can access the game object if you configure it correctly, and then if you need to call KM from the script, use
window.location.href = "kmtrigger://macro=macroname" (see trigger:URL [Keyboard Maestro Wiki])

Where is game defined in this case?

If it is part of the window or document, then you may be able to get it that way (eg window.game.macros….

Thank you for the Tampermonkey suggestion. That looks very interesting, even apart from my current noodling around.

I think I might have found where game is defined in the client.mjs file:

/* ----------------------------------------- */
/*  Client-Side Globals                      */
/* ----------------------------------------- */

// Get the current View from the URL.
const gameView = new URL(window.location.href).pathname.split("/").at(-1);
const {CONST, applications, appv1, data, dice, documents, helpers, prosemirror, utils} = foundry;
const CONFIG = {...globalConfig};

Object.assign(globalThis, {
  foundry: {...foundry, CONFIG},
  CONST,
  CONFIG,
  /**
   * The singleton Game instance.
   * A simple object before the Game instance has been created.
   * @type {foundry.Game}
   */
  game: {view: gameView},
  ui: foundry.ui,

And of course there's a lot more after that. If the solution is obvious to someone and you can offer it up, amazing. Thank you very much. I would love to learn and understand more. I am also working on another way of tackling it.

globalThis will be window, self or frames apparently, so probably window.game might work.

In the web browser, see if game and window.game are the same object. Then try window.game. in the Keyboard Maestro action.

window.game.macros.getName('Mark Defeated').execute();

Works in the console but gives a different error when sent via KM:

Uncaught TypeError: Cannot read properties of undefined (reading 'macros')
at <anonymous>:4:13
at <anonymous>:5:4

So it's probably something complex to get it working.

I found a workaround by going at it more from the pretending to click and type method of automation. You can run Foundry macros in the game my typing "/macro MACRONAME" in the chat box. So I was able to automate it that way.

// Name of Macro to Run. CHANGE THIS
const macroname = "Mark Defeated";

// Simulate typing '/macro MACRONAME' into the chat message
const typemacro = "/macro"
const combined = `${typemacro} ${macroname}`;
const textarea = document.querySelector('#chat-message');
textarea.value = combined;

// Simulate pressing the Enter key
textarea.dispatchEvent(
  new KeyboardEvent('keydown', {
    bubbles: true,
    cancelable: true,
    key: 'Enter',
    code: 'Enter',
    keyCode: 13,   // legacy support
    which: 13      // legacy support
  })
);

This works when sent from KM, so I'll just do it this way. Thank you all for your help and ideas.

1 Like