How to Get the Selected Action's Information (Name, Id, Macro) in KM Editor Using AppleScript/JavaScript

The idea of thinking of it as a database query helps, thanks.

1 Like

Tiffle, Dan,

Thank for the sample.


Tiffle,

I have used your original script to get the actionName and action Id successfully. Just that I now need to get actionId of the actions (even if they are not selected) to verify that action exist in KM (because it may get deleted)

Dan,

that 's javascipt of gettiing action name is cool. Just that I need to find a way to verify if action is valid even if they are not selected.


What I have done so far, is that I capture selected Actions and save them into a json file.

But I need a way to verify that actions in the json file are still valid so I think that a general js/applescript code to check if the action is valid is useful.

Something along like that maybe


for Applescript

tell application "Keyboard Maestro" -- or should it be "Keyboard Maestro Engine" if it just to retrieve by actionID ?

get name of action "11119" -- from actionId or actionName

end tell

or for JS


const selectedMacros = kmEditor.macros.whose({actionId: {"=": "78823"}});

You can probably speed it up a bit, if that seems relevant, by batch-fetching both names and ids:

Expand disclosure triangle to view AppleScript
use AppleScript version "2.4"
use framework "Foundation"
use scripting additions

-- name and id of selected KM macros
on run
	tell application "Keyboard Maestro"
		tell (macros where selected is true)
			set actionIDs to id
			set macroNames to name
		end tell
	end tell
	
	set xs to {}
	repeat with i from 1 to length of actionIDs
		set end of xs to item i of macroNames & "@@##" & item i of actionIDs
	end repeat
	
	unlines(xs)
end run


-- unlines :: [String] -> String
on unlines(xs)
	-- A single string formed by the intercalation
	-- of a list of strings with the newline character.
	set {dlm, my text item delimiters} to ¬
		{my text item delimiters, linefeed}
	set s to xs as text
	set my text item delimiters to dlm
	s
end unlines





1 Like

In JS we could simplify Dan's query a little, and use a generic zipWith:

(() => {
    "use strict";

    const main = () => {
        const
            selectedMacros = Application("Keyboard Maestro")
            .macros.where({
                selected: true
            });

        return 0 < selectedMacros.length ? (
            zipWith(
                k => uuid => `${k}@@##${uuid}`
            )(
                selectedMacros.name()
            )(
                selectedMacros.id()
            ).join("\n")
        ) : "No macros selected";
    };

    // --------------------- GENERIC ---------------------

    // zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
    const zipWith = f =>
        // A list constructed by zipping with a
        // custom function, rather than with the
        // default tuple constructor.
        xs => ys => xs.map(
            (x, i) => f(x)(ys[i])
        ).slice(
            0, Math.min(xs.length, ys.length)
        );

    return main();
})();
1 Like

I summarize the steps:

let says I have this macro [ TEST Macro ] that contains some actions


Given the following input
Macro Name: TEST Macro
Macro ID: 32344-2342343-234324234432
Action Name: Execute Script
Action ID: 38847

How do I use Applescript/Javascript to validate that action with ActionID 38847 exists ?

Steps:

  1. Check that macro exists by its macroID
  2. If exists, retrieve all actions belonging to that macro. otherwise if macro is invalid, then action is confirm Invalid.
  3. Do Repeat loop to compare each macro's actionID with given Action ID
  4. If exist mean valid, otherwise the action is invalid.

The problem I have is how to translate the above into applescript/javascript.

The context here is:

  • differences between KM versions ?
  • custom actions that may not be locally installed ?

ComplexPoint,

  • to keep it simple. only KM10 is used. Because KM10 has added selectAction which accept actionID to navigate to, so i be using this in KM10.
  • I have verified that custom action has its own unique ActionID (eg I have used custom Action Activate Action By Name and it has a unique ID: 74711 ). So if it is not install, then it is treated as invalid actionID.

Thank to Tiffle, Dan, and ComplexPoint for helping

I have found the solution and modified, on the original code by Tiffle.
The main problem I have initially is getting the action by its ActionID. It seems that there is no direct way to retrieve it, but only from macro object.

So here it is the solution for those who are interested.

tell application "Keyboard Maestro"
	
	set macroId to "334C61D4-4689-4B1E-B432-6D1B88D5867B"
	set actionId to "69217"
	set actionFound to false
	
	set macroList to {}
	set macroList to every macro whose id is macroId -- Any way to retrieve macro without list ?
	
	repeat with macroItem in macroList
		set macroName to name of macroItem
		set actionList to actions of macroItem
		
		repeat with actionItem in actionList
			set curActionId to id of actionItem
			
			if curActionId is actionId then -- pass as lo
				set actionFound to true
				exit repeat
			end if
			
		end repeat
		
	end repeat
	
	display dialog actionFound -- Result
	
end tell

FYI: Don't expect the action's ID to be the same on another computer, even if you exported your macro to a .kmmacros file and loaded it into the other machine.

Yes, it will be different for each machine since the macro import will generate a unique actionID again to prevent conflict with existing actionID .

By the way, did you try the KM10's Menu feature. It is really cool, I using for tracking task time

image

1 Like

I just wanted to make sure you knew that. In general, you can rely on macro and group UUIDs to be the same, unless a macro or group was duplicated.

By the way, did you try the KM10's Menu feature. It is really cool, I using for tracking task time

Not yet, but I will. :slightly_smiling_face:

You're welcome.

I should remind you, however, that if the action you're looking for is embedded in, for example, an action of one of the following MacroActionTypes: IfThenElse, PauseUntil, Switch (there may be others) then your script will not see it as you have to recursively examine each of those types.

In order to determine that, you need to examine the XML of the action (your actionItem in your script) and see if there's a MacroActionType property defined like this for example:

	<key>MacroActionType</key>
	<string>IfThenElse</string>

That's the only consistently reliable way I've found to do it, but then I am not an AppleScript expert so there may be alternatives.

I do have code to do all this but unfortunately it's protected by an IPA so I can't share it. However, you can see it in action in the Prettify facility implemented as part of my KM debugging aid, Checkpoint, which is available to all for free - just so you know it is possible to do.

tiffle,
yes, that did not actually solve the problem until I have to use recursion. Unfortunately I not familiar with Applescript to do recursion. Dan suggest converting the macro plist into jxa to perform operation as it is easier to use Javascript, so I pending for that solution.

So sorry - I got sidetracked by VSCode's ability to use JSDoc comments. See this topic for something that might be helpful for your issue. Then again it might not, but it's still cool: Get JXA Plist for a Macro UUID, With VSCode "Intellisense" Documentation Examples

1 Like

Dan,
Np, that's great. Thank for the hard work. I take a look at that.
thank

1 Like

The issue is still how to find a specific action. I forget - what will you do with it when you get it? Is it only to verify that it exists in the macro? And if so, you could find it easily by searching the XML for that macro. It's stored like this:

<key>ActionUID</key>
<integer>13025</integer>

And where did you get the action's ID to begin with?

Dan,

I use it to go to selected action. Yes, I just need to check if the ActionID exists in the macro, and from what I see the simple string search will do fine given that your code generates standard json. No need for recursion.

The json return has this string:

{
   "MacroAction": "Unmark",
   "MacroUID": "FE0CA6DC-2359-4FA9-A236-E2D1BBE5160E",
   "ActionColor": "Teal",
   "MacroActionType": "MarkMacro",
   "ActionUID": 18241
}

So I just need to search for "ActionUID": actionId , and that will check for existence of action. Recursion could be slower since action can be nested several deep down.

I modified Tiffle's original code to return the currently selected actionName and actionID

tell application "System Events"
   if visible of application process "Keyboard Maestro" then
      -- display dialog "Visible"
   else
      -- display dialog "Not Visible"
      return 0
   end if
end tell

tell application "Keyboard Maestro"
   try
      set selMacros to selected macros
      if ((count of selMacros) = 1) then
         set selActionList to get selection
         --- Loop Through All Actions ---
         repeat with selAction in selActionList
            if the class of selAction is action then
               set aID to id of selAction
               tell selAction
                  set actionName to its name -- Grammar Police
                  set actionId to its id -- Grammar Police
                  return actionName & "@@##" & actionId
                  -- display dialog actionName & actionId
               end tell
            end if
         end repeat
      end if
   end try
end tell

The original data is xml, and that's what I would search - no need to convert it to json if you're going to do a string match. Just use a regex like /<key>ActionUID<\/key>\s*<integer>(\d*)<\/integer>/

But you could convert it to json if you wanted to, as I show in the other topic

Dan,

I get my macro working now with your code, and your code works fast.

I'm using JSON as I want to reuse your existing sample as much as possible since I need the fastest way to leverage. Your amazing piece of work is the last missing piece I need to finish up my macro ...

Look like another late night for me :}

thanks

Cool - I'm glad it helps. I prefer to work in json also, and in fact, I have my Macro Repository backup all my macros in json.

One thing you might need to know about plists in JXA: They don't store <data> elements, like icons. At least I couldn't figure out a way to get it to work. And that's not an issue, unless you want to convert the plist back to XML, for instance to rewrite a .kmmacros file.

By the way, if anyone knows the proper way to handle <data> elements, let me know.

Personally, I sometimes need the elements, because I edit .kmmacros files. In one case, I strip out Triggers. In another case, I transfer the triggers from existing macros to new macros before I import them.

So I've developed a workaround so I don't lose the <data> elements. It's an integral of my Plist library, so most of the time I forget it's even there. I won't go into the details here unless you want to know. But suffice it to say that I have a workaround that's worked perfectly for 5 or 6 years.

1 Like