Using AppleScript with Apple Events in macOS - Script not working
Asked Answered
L

1

4

We need to use a AppleScript to create an outgoing email message in macOS. The script works fine in the Script Editor. Using the code recommended by DTS https://forums.developer.apple.com/message/301006#301006 no results, warnings or errors. Same result with sample script from the forum. Need Swift and Apple Events expertise here. Thanks!

import Foundation
import Carbon
class  EmailDoc: NSObject {

    var script: NSAppleScript = { 
        let script = NSAppleScript(source: """


            set theSubject to "Some Subject"
            set theContent to "Some content of the email"
            set recipientName to "Some Name"
            set recipientAddress to "[email protected]"

            tell application "Mail"

                # Create an email
                set outgoingMessage to make new outgoing message with properties {subject:theSubject, content:theContent, visible:true}

                # Set the recipient
                tell outgoingMessage
                    make new to recipient with properties {name:recipientName, address:recipientAddress}

                    # Send the message
                    send

                end tell
            end tell
            """  
            )!  
        let success = script.compileAndReturnError(nil)  
        assert(success)  
        return script  
    }() 

    // Script that is referenced by DTS at https://forums.developer.apple.com/message/301006#301006
    // that goes with runScript method below  -- runs with no results

    /*var script: NSAppleScript = {  
     let script = NSAppleScript(source: """

     on displayMessage(message)  
     tell application "Finder"  
     activate  
     display dialog message buttons {"OK"} default button "OK"  
     end tell  
     end displayMessage  
     """  
     )!  
     let success = script.compileAndReturnError(nil)  
     assert(success)  
     return script  
     }() */

    @objc
    func runScript() {

        let parameters = NSAppleEventDescriptor.list()  
        parameters.insert(NSAppleEventDescriptor(string: "Hello Cruel World!"), at: 0)  

        let event = NSAppleEventDescriptor(  
            eventClass: AEEventClass(kASAppleScriptSuite),  
            eventID: AEEventID(kASSubroutineEvent),  
            targetDescriptor: nil,  
            returnID: AEReturnID(kAutoGenerateReturnID),  
            transactionID: AETransactionID(kAnyTransactionID)  
        )  
        event.setDescriptor(NSAppleEventDescriptor(string: "displayMessage"), forKeyword: AEKeyword(keyASSubroutineName))  
        event.setDescriptor(parameters, forKeyword: AEKeyword(keyDirectObject))  

        var error: NSDictionary? = nil  
        _ = self.script.executeAppleEvent(event, error: &error) as NSAppleEventDescriptor?  

        print ("runScript",self.script)

        }
    }


Lei answered 1/9, 2019 at 14:25 Comment(1)
Avoid NSAppleScript—it’s a total PITA for anything non-trivial. Best way to access AppleScript from ObjC/Swift is via the AppleScript-ObjC bridge, which lets you invoke AppleScript handlers as if they’re native ObjC methods.Balakirev
A
5

The problem with this code — which is an incredibly un-obvious problem, mind you — is that you're using code meant to run a script handler (a method or subroutine) to try to run the full script. One of the oddnesses of Obj-C's AppleScript classes is that there is no easy way to run a script with parameters, so the workaround is to enclose the code to be executed within a script handler, and use an Apple Event that calls that handler. To make your code work, you'll do something like the following...

First, change the script so that the code is in a handler:

var script: NSAppleScript = { 
    let script = NSAppleScript(source: """

    -- This is our handler definition
    on sendMyEmail(theSubject, theContent, recipientName, recipientAddress, attachmentPath)
        tell application "Mail"

            -- Create an email
            set outgoingMessage to make new outgoing message ¬
            with properties {subject:theSubject, content:theContent, visible:true}

            -- Set the recipient
            tell outgoingMessage
                make new to recipient ¬
                with properties {name:recipientName, address:recipientAddress}

                make new attachment with properties {file name:POSIX file attachmentPath}

               -- Mail.app needs a moment to process the attachment, so...
               delay 1

               -- Send the message
               send 
            end tell
        end tell
    end sendMyEmail
    """  
    )!  

Then alter the Apple Event you construct so that it passes the parameters and calls the handler we just defined:

func runScript() {
    let parameters = NSAppleEventDescriptor.list()  
    parameters.insert(NSAppleEventDescriptor(string: "Some Subject"), at: 0)  
    parameters.insert(NSAppleEventDescriptor(string: "Some content of the email"), at: 0)  
    parameters.insert(NSAppleEventDescriptor(string: "Some Name"), at: 0)  
    parameters.insert(NSAppleEventDescriptor(string: "[email protected]"), at: 0)  
    parameters.insert(NSAppleEventDescriptor(string: attachmentFileURL.path), at: 0)  

    let event = NSAppleEventDescriptor(  
        eventClass: AEEventClass(kASAppleScriptSuite),  
        eventID: AEEventID(kASSubroutineEvent),  
        targetDescriptor: nil,  
        returnID: AEReturnID(kAutoGenerateReturnID),  
        transactionID: AETransactionID(kAnyTransactionID)  
    )  

    // this line sets the name of the target handler
    event.setDescriptor(NSAppleEventDescriptor(string: "sendMyEmail"), forKeyword: AEKeyword(keyASSubroutineName))

    // this line adds the parameter list we constructed above  
    event.setDescriptor(parameters, forKeyword: AEKeyword(keyDirectObject))  

    var error: NSDictionary? = nil  
    _ = self.script.executeAppleEvent(event, error: &error) as NSAppleEventDescriptor?  

    print ("runScript",self.script)

    }
}

If you don't need to pass parameters, you could run the script directly using script.executeAndReturnError(&error), but if you need to pass parameters, you'll need to use this 'handler' approach.

Anachronous answered 1/9, 2019 at 16:8 Comment(10)
OMG! Not only does this work, it solves our next question which was how to pass data to the script. The real subject, recipient, etc. Interesting how it works with inserting all the parameters at: 0 ??? You solved in minutes what took DTS over 2 weeks to come back with the incomplete forum post. One last question, we need to attach a PDF document which is already exists in the class. Using: make new attachment with properties {file name:theAttachment} at after last paragraph. It runs in the script editor ok but doesn't show on the email. Any ideas? Again, thanks for your help!Lei
There's only two reasons the attachments wouldn't show: 1. you're having some security/permissions issue accessing the file, or 2. you're sending the wrong kind of path specifier. Mail attachments in script editor seem to work with posix paths and alias file specifiers, so add a fifth parameter (theAttachment) to the handler definition, and make sure you send it a text posix path (e.g., if you have an NSURL for the PDF in swift, add theUrl.path as a string descriptor to the parameter list).Anachronous
I've updated the example code in the main post. Incidentally, the '0' index seems to be shorthand for "add it at the end of the list". Obscure, perhaps, but not too mysterious...Anachronous
Didn't want to trouble you again. However, the attachment did not work. Did find a script that works fine in the Script Editor but can't get the attachment to work in the handler. There is an additional tell command and an activate. Been trying all the combinations of send and activate but can't get the attachment to work. Here are the details <#57778048> Thanks for any insight.Lei
No worries. Is there anything in the error dictionary, or is it nil after the script.executeAppleEvent call?Anachronous
No, it runs fine and creates the email with everything but the attachment. Have tried both send and activate between all of the end tell lines.Lei
I hate things that fail silently... try it without the posix file keyword (i.e. file name:attachmentPath) and see if that works (also, try it without the send command, to see if there's a race condition). Unfortunately, I'm stuck on testing this; I let my developer account expire, and I can't seem to get past the new security restrictions without it. Irritating...Anachronous
Ok, thanks. Actually spent all morning trying different things with the POSIX, alaises, separating the path and file, etc. Simply entering it as string is only thing that worked in the editor. I've tested with kinds of files. No race, it creates the email immediately. Is there any doc on all these commands - send-activate?Lei
The fact that it works in the script editor and not from NSAppleScript is perplexing, and keeps pulling me back to the idea that it's a system security issue. I'm almost tempted to tell you to switch to Scripting Bridge and see it that makes a difference, but there's overhead to that... P.s. there's not real documentation, just the Mail Scripting Dictionary.Anachronous
Let us continue this discussion in chat.Lei

© 2022 - 2024 — McMap. All rights reserved.