Need Help With AppleScript & JXA to Copy the Trigger From One Macro to Another

@ccstone, @JMichaelTX, @ComplexPoint, @peternlewis or whoever.

NOTE: All the code is stripped-down including removing error checking, to not clutter up the question.

PURPOSE:

I want to copy a trigger from one macro to another. This AppleScript works:

tell application "Keyboard Maestro"
	set sourceUUID to "A9904899-5B76-4E29-996C-E6D180A54C79"
	set destUUID to "82B0D2C9-C220-4863-BCB3-E15D92DD82A0"
	
	set sourceMacro to first macro whose id is sourceUUID
	set sourceTriggerXML to xml of trigger 1 of sourceMacro
	
	set destMacro to first macro whose id is destUUID
	tell destMacro
		make new trigger with properties {xml:sourceTriggerXML}
	end tell
end tell

I have everything working in JXA, except for the make new trigger line at the end:

(function() {
	'use strict';
	ObjC.import('AppKit');

	let km = Application("Keyboard Maestro");

	function execute() {
		const sourceUUID = "A9904899-5B76-4E29-996C-E6D180A54C79";
		const destUUID = "82B0D2C9-C220-4863-BCB3-E15D92DD82A0";

		// AS: set sourceMacro to first macro whose id is sourceUUID
		let sourceMacro = km.macros.whose({id: {"=": sourceUUID}})[0];
		console.log("sourceMacro: " + sourceMacro.name());

		// AS: set sourceTriggerXML to xml of trigger 1 of sourceMacro
		let sourceTriggerXML = sourceMacro.triggers[0].xml();
		console.log("sourceTriggerXML: " + sourceTriggerXML);

		// AS: set macro2 to first macro whose id is destUUID
		let destMacro = km.macros.whose({id: {"=": destUUID}})[0];
		console.log("destMacro: " + destMacro.name());

		// HERE'S WHERE I'M STUCK.
		// AS:
		// tell destMacro
		//     make new trigger with properties {xml:sourceTriggerXML}
		// end tell
		destMacro.trigger.make({xml: sourceTriggerXML});

		console.log("Did it work?");
		return "OK";
	}

	try {
		return execute();
	} catch (e) {
		return "Error on line: " + e.line + ": " + e.message;
	}

})();

I've tried lots of different syntax, but this one looks the closest, because here's the error message:

destMacro.trigger.make({xml: sourceTriggerXML});

Error on line: 28: Parameter is missing.

TO TEST THE CODE:

You need two macros. One with a Trigger, and one without.

Replace the sourceUUID and destUUID in the example code, and you can run it.

HELP!

I know I can modify the triggers collection in JXA, because I have working code that deletes all the triggers. So I know it's not read-only, or something like that.

I just can't get the syntax for adding the trigger!

Help, please!!!???!!!

Hi Dan,
It looks like you are making some amazing macros again!

I know almost nothing about JXA. But here are some macros I've downloaded and used in the past. (You may have seen this post by @gglick, but I just post the link here in case.)

1 Like

Thanks, Martin. There was a good chance that could've helped me. In the end it didn't, because it's AppleScript, not JXA, but still, thanks for the effort!

1 Like

Have you tried:

  1. Creating a new trigger object using the Trigger constructor, which is a method of the KM Application, and
  2. 'pushing' that new Trigger object into the triggers collection of the receiving macro

?

OS X 10.10 Release Notes

1 Like

Mmm ... I experimented with this:

(() => {
    "use strict";

    const main = () => {
        const
            km = Application("Keyboard Maestro"),
            selectedMacros = km.selectedMacros(),
            firstSelectedMacroUUID = 0 < selectedMacros.length ? (
                selectedMacros[0].id()
            ) : null;

        return null !== firstSelectedMacroUUID ? (() => {
            const addedTrigger = new km.Trigger({
                description: "The Hot Key ⌥⌘P is pressed",
                xml: `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>FireType</key>
    <string>Pressed</string>
    <key>KeyCode</key>
    <integer>35</integer>
    <key>MacroTriggerType</key>
    <string>HotKey</string>
    <key>Modifiers</key>
    <integer>2304</integer>
</dict>
</plist>`
            });

            const
                targetMacro = km.macros.byId(
                    firstSelectedMacroUUID
                );

            return (
                targetMacro.triggers.push(addedTrigger),
                addedTrigger.description()
            );

        })() : "No macros selected";
    };

    return main();
})();

but saw an Apple Event error. These object creation interfaces are not always easy to implement in a way that is compatible with the full requirements of the JXA Automation interface.

I sometimes resort to using an AppleScript snippet from inside JS.

(lots of ways of doing that – things like this, for example:

// evalASLR :: String -> Either String a
const evalASLR = s => {
    const
        error = $(),
        result = $.NSAppleScript.alloc.initWithSource(s)
        .executeAndReturnError(error),
        e = ObjC.deepUnwrap(error);
    return e ? (
        Left(e.NSAppleScriptErrorBriefMessage)
    ) : Right(ObjC.unwrap(result.stringValue));
};

)

Thanks, and I was already considering something like this, although I think I like your implementation better than what I have. But there's two things I don't understand in your code:

  1. What's the "Left" and "Right" functions (highlighted in yellow)?

  2. Isn't there an unnecessary set of parens directly after the "e ?" (highlighted in green). The only reason I mention this is to make sure I understand what's going on in this code - I'm not criticising your coding abilities. :slightly_smiling_face:

Thanks.

Left and Right are just constructors which I use for an Either option type – i.e. for composing two parallel channels (Left to hold messages when computation can't continue, Right to hold values) as below.

(You may well prefer to strip them out and handle the two cases – either message string, or successfully computed value – in some other way)

    // Left :: a -> Either a b
    const Left = x => ({
        type: "Either",
        Left: x
    });

    // Right :: b -> Either a b
    const Right = x => ({
        type: "Either",
        Right: x
    });

    // bindLR (>>=) :: Either a ->
    // (a -> Either b) -> Either b
    const bindLR = m =>
        mf => m.Left ? (
            m
        ) : mf(m.Right);

    // either :: (a -> c) -> (b -> c) -> Either a b -> c
    const either = fl =>
    // Application of the function fl to the
    // contents of any Left value in e, or
    // the application of fr to its Right value.
        fr => e => e.Left ? (
            fl(e.Left)
        ) : fr(e.Right);

(The same pattern as Optional Chaining — The Swift Programming Language (Swift 5.5) or an Either type in languages like Haskell)

With conditional expressions, I use those additional brackets to get a layout (with JS code formatters) in which the two alternatives are directly above each other, for ease of legibility at a glance.

Again, all sorts of other approaches are available : -)

1 Like

That totally makes sense now. I had an inkling it was something like that, but I just couldn't "get there".

And as for the additional set of parens, what an ingenious way of getting the formatting you want! I've been in that situation plenty of times, and I love this solution!

One more thing...

Obviously, using this "evalAS" type of function requires building the complete AS script string, which is fine. And since the AS script will be using a quoted string of XML, I'll need to escape newlines and double-quotes.

But it's possible for the XML to already contain escaped double-quotes, right? So can they be "double-escaped"?

Certainly – it'll take a bit of experimentation and fiddling to write a function to define the mapping you need – on the JS side back-ticks yield very flexible handling of code strings, and on the AS side you can, as you say, multiply the levels of escape.

on run
    set strXML to "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<array>\n\t<dict>\n\t\t<key>MacroActionType</key>\n\t\t<string>SetVariableToText</string>\n\t\t<key>Text</key>\n\t\t<string>~/curried-python/menuCurriedPython.json</string>\n\t\t<key>Variable</key>\n\t\t<string>preludeJSON</string>\n\t</dict>\n</array>\n</plist>"
    
end run

Thanks. Honestly, for what I have to do, I'll probably just create a sub-macro and pass the XML in as a KM variable. Much easier. But I definitely want to use this "evalAS" code in other places, so thanks for this great piece of code!

Now that I've said that, I don't suppose it's possible to alter your code to pass variables, kind of like KM does? I doubt it, but it's worth asking.

And here's another version which isn't as cool as yours, but since it uses "doShellScript" it might support environment variables?

const evalAS = s => {
	const
		a = Application.currentApplication(),
		sa = (a.includeStandardAdditions = true, a);
	return sa.doShellScript(
		['osascript -l AppleScript <<OSA_END 2>/dev/null']
		.concat([s])
		.concat('OSA_END')
		.join('\n')
	);
};

Sounds good.

Here FWIW, is the broad pattern that I had in mind (an AS snippet, called from a JS template string, which launches a KM play sound action):

(() => {
    "use strict";


    const main = () => {
        const actionXML = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
    <dict>
        <key>DeviceID</key>
        <string>SOUNDEFFECTS</string>
        <key>MacroActionType</key>
        <string>PlaySound</string>
        <key>Path</key>
        <string>/System/Library/Sounds/Glass.aiff</string>
        <key>TimeOutAbortsMacro</key>
        <true/>
        <key>Volume</key>
        <integer>50</integer>
    </dict>
</array>
</plist>
`;

        return either(
            // Message with header if something could
            // not be computed.
            alert("AS snippet from JS")
        )(
            // Any computed value returned directly.
            x => x
        )(
            evalASLR(`
            tell application "Keyboard Maestro Engine"
                do script "${actionXML.replace(/"/gu, '\\"')}"
            end tell
        `)
        );
    };

    // ----------------------- JXA -----------------------

    // alert :: String => String -> IO String
    const alert = title =>
        s => {
            const sa = Object.assign(
                Application("System Events"), {
                    includeStandardAdditions: true
                });

            return (
                sa.activate(),
                sa.displayDialog(s, {
                    withTitle: title,
                    buttons: ["OK"],
                    defaultButton: "OK"
                }),
                s
            );
        };

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

    // Left :: a -> Either a b
    const Left = x => ({
        type: "Either",
        Left: x
    });


    // Right :: b -> Either a b
    const Right = x => ({
        type: "Either",
        Right: x
    });


    // bindLR (>>=) :: Either a ->
    // (a -> Either b) -> Either b
    const bindLR = m =>
        mf => m.Left ? (
            m
        ) : mf(m.Right);


    // either :: (a -> c) -> (b -> c) -> Either a b -> c
    const either = fl =>
        // Application of the function fl to the
        // contents of any Left value in e, or
        // the application of fr to its Right value.
        fr => e => e.Left ? (
            fl(e.Left)
        ) : fr(e.Right);


    // evalASLR :: String -> Either String a
    const evalASLR = s => {
        const
            error = $(),
            result = $.NSAppleScript.alloc.initWithSource(s)
            .executeAndReturnError(error),
            e = ObjC.deepUnwrap(error);

        return e ? (
            Left(e.NSAppleScriptErrorBriefMessage)
        ) : Right(ObjC.unwrap(result.stringValue));
    };

    // MAIN
    return main();
})();

That looks awesome, and well thought out (as if I expected anything less). That "either" code is a true mind f*ck (in a good way). I think I understand it, believe it or not. ;p Thanks - it's now in my toolbox.

I love JS template strings. So much easier to use, you can embed line breaks and both single- and double-quotes easily, and it's more readable than other methods (IMO). A great addition to the JS standard.

Thanks again for your help!

1 Like

A minimal example:

Expand disclosure triangle to view JS Source
(() => {
    "use strict";

    // main :: IO ()
    const main = () =>
        either(
            // Message with title, if no value defined.
            alert("Left channel")
        )(
            // Any computed value returned directly.
            n => n
        )(
            // Attempt to compute a non-complex square root
            // for a negative number.
            sqrtLR(-5)
        );

    // ----------------------- JXA -----------------------

    // alert :: String => String -> IO String
    const alert = title =>
        s => {
            const sa = Object.assign(
                Application("System Events"), {
                    includeStandardAdditions: true
                });

            return (
                sa.activate(),
                sa.displayDialog(s, {
                    withTitle: title,
                    buttons: ["OK"],
                    defaultButton: "OK"
                }),
                s
            );
        };


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


    // Left :: a -> Either a b
    const Left = x => ({
        type: "Either",
        Left: x
    });

    // Right :: b -> Either a b
    const Right = x => ({
        type: "Either",
        Right: x
    });

    // bindLR (>>=) :: Either a ->
    // (a -> Either b) -> Either b
    const bindLR = m =>
        mf => m.Left ? (
            m
        ) : mf(m.Right);

    // either :: (a -> c) -> (b -> c) -> Either a b -> c
    const either = fl =>
        // Application of the function fl to the
        // contents of any Left value in e, or
        // the application of fr to its Right value.
        fr => e => e.Left ? (
            fl(e.Left)
        ) : fr(e.Right);

    // sqrtLR :: Num -> Either String Num
    const sqrtLR = n =>
        0 > n ? (
            Left(`Square root undefined for negative number: ${n}`)
        ) : Right(Math.sqrt(n));

    // MAIN ---
    return main();
})();

and a slight expansion:

Expand disclosure triangle to view JS Source
(() => {
    "use strict";

    // Obtaining phi, the 'golden mean'.

    // main :: IO ()
    const main = () =>
        either(
            // Message with title, if no value defined.
            alert("Left channel")
        )(
            // Any computed value returned directly.
            phi => phi
        )(
            // Attempt to compute a non-complex square root
            // for a negative number.
            bindLR(
                sqrtLR(5)
            )(
                n => Right(
                    1 + (
                        (n - 1) / 2
                    )
                )
            )
        );

    // ----------------------- JXA -----------------------

    // alert :: String => String -> IO String
    const alert = title =>
        s => {
            const sa = Object.assign(
                Application("System Events"), {
                    includeStandardAdditions: true
                });

            return (
                sa.activate(),
                sa.displayDialog(s, {
                    withTitle: title,
                    buttons: ["OK"],
                    defaultButton: "OK"
                }),
                s
            );
        };


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


    // Left :: a -> Either a b
    const Left = x => ({
        type: "Either",
        Left: x
    });

    // Right :: b -> Either a b
    const Right = x => ({
        type: "Either",
        Right: x
    });

    // bindLR (>>=) :: Either a ->
    // (a -> Either b) -> Either b
    const bindLR = m =>
        mf => m.Left ? (
            m
        ) : mf(m.Right);

    // either :: (a -> c) -> (b -> c) -> Either a b -> c
    const either = fl =>
        // Application of the function fl to the
        // contents of any Left value in e, or
        // the application of fr to its Right value.
        fr => e => e.Left ? (
            fl(e.Left)
        ) : fr(e.Right);

    // sqrtLR :: Num -> Either String Num
    const sqrtLR = n =>
        0 > n ? (
            Left(`Square root undefined for negative number: ${n}`)
        ) : Right(Math.sqrt(n));

    // MAIN ---
    return main();
})();

(see also Examples of Either in Haskell )

Thanks for the examples. I'm not sure I'll ever really feel comfortable with code like this, but being able to read it is a step in the right direction. :slight_smile: