Automation & Scripting

RSS for tag

Learn about scripting languages and automation frameworks available on the platform to automate repetitive tasks.

Automation & Scripting Documentation

Posts under Automation & Scripting subtopic

Post

Replies

Boosts

Views

Activity

How do I flatten a PDF using Shortcuts or Automator?
Looking for any method to quickly flatten a PDF without opening Preview and without installing 3 party software. Any ideas? Save as PDF in Preview works, but I don't want to have to open Preview each time I need to do this. The Create PDF action which appears in Finder when you select 2 or more PDFs flattens PDFs, but it requires me to select 2 or more files, and I generally don't want to combine PDFs--I simply wish to flatten a PDF. Most Automator and Shortcuts options I am aware of do not flatten PDFs, and in some cases, strip out form field data from PDFs.
7
0
544
Oct ’25
How to properly localise AppShortcutPhrase?
Hi, I have AppShortcutsProvider in my app target(not as a separate extension) to support Siri command. It is working perfectly in English, but I would also like to add a localisation on it as my app supports multiple languages. struct MyShortcuts: AppShortcutsProvider { static let shortcutTileColor: ShortcutTileColor = .grape static var appShortcuts: [AppShortcut] { AppShortcut( intent: NextClassAppIntents(), phrases: [ "What is my next class in \(.applicationName)?", "What's the next class in \(.applicationName)?", "Next class in \(.applicationName)." ], shortTitle: "Next Class", systemImageName: "calendar.badge.clock" ) } } Xcode String Catalog was doing great jobs, It also detected shortTitle automatically and added that to the Catalog. However, I don't see localisation for those phrases anywhere in the String Catalog and when I try to use String(localized: ), compiler gives me an error. How can I properly localise AppShortcutPhrase?
1
0
229
Oct ’25
Press return
Here is the appleScript part of my script: tell application "Finder" activate open application file "Messages.app" of folder "Applications" of folder "System" of startup disk end tell What I need is a step to send the message already inside the Mesage.app. The message is ready to send. I just would like a step that will act as "Return" to send the message. Again I have already prepared message to send but just need a step to send the message. Is there a possible step to perform the" Return" command to iMessage?
1
0
305
Nov ’25
Is calling different SBApplication objects from different threads bad?
Not quite but maybe sorta related to the errOSAInternalTableOverflow problem I asked about in a different thread, this one deals with crashes our app gets (and much more frequently lately after recent OS updates (15.7.3) are OK'd by our IT department). Our app can run multiple jobs concurrently, each in their own NSOperation. Each op creates its own SBApplication instance that controls unique instances of InDesignServer. What I'm seeing recently is lots of crashes happening while multiple ops are calling into ScriptingBridge. Shown at the bottom is one of the stack crawls from one of the threads. I've trimmed all but the last of our code. Other threads have a similar stack crawl. In searching for answers, Google's AI overview mentions "If you must use multiple threads, ensure that each thread creates its own SBApplication instance…" Which is what we do. No thread can reach another thread's SBApplication instance. Is that statement a lie? Do I need to lock around every ScriptingBridge call (which is going to severely slow things down)? 0 AE 0x1a7dba8d4 0x1a7d80000 + 239828 1 AE 0x1a7d826d8 AEProcessMessage + 3496 2 AE 0x1a7d8f210 0x1a7d80000 + 61968 3 AE 0x1a7d91978 0x1a7d80000 + 72056 4 AE 0x1a7d91764 0x1a7d80000 + 71524 5 CoreFoundation 0x1a0396a64 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 28 6 CoreFoundation 0x1a03969f8 __CFRunLoopDoSource0 + 172 7 CoreFoundation 0x1a0396764 __CFRunLoopDoSources0 + 232 8 CoreFoundation 0x1a03953b8 __CFRunLoopRun + 840 9 CoreFoundation 0x1a03949e8 CFRunLoopRunSpecific + 572 10 AE 0x1a7dbc108 0x1a7d80000 + 246024 11 AE 0x1a7d988fc AESendMessage + 4724 12 ScriptingBridge 0x1ecb652ac -[SBAppContext sendEvent:error:] + 80 13 ScriptingBridge 0x1ecb5eb4c -[SBObject sendEvent:id:keys:values:count:] + 216 14 ScriptingBridge 0x1ecb6890c -[SBCommandThunk invoke:] + 376 15 CoreFoundation 0x1a037594c ___forwarding___ + 956 16 CoreFoundation 0x1a03754d0 _CF_forwarding_prep_0 + 96 17 RRD 0x1027fca18 -[AppleScriptHelper runAppleScript:withSubstitutionValues:usingSBApp:] + 1036
18
0
295
1h
AppIntents EntityPropertyQuery, how does "Filter Entity where" work?
When you correctly implement EntityPropertyQuery on an AppEntity, Shortcuts will expose a "Find Entity" action that calls into entities(matching:mode:sortedBy:limit:). This is demoed in the "Dive into App Intents" session and works as expected. However, with this action, you can change the "All Entity" input to a list variable which changes the action text from "Find All Entity" to "Filter Entity where" still giving you the same filter, sort and limit options. This appears to work as expected too. But, what's unexpected is that this filter action does not appear to call any method on my AppEntity code. It doesn't call entities(matching:mode:sortedBy:limit:). One would think there would need to be a filter(entities:matching:mode:sortedBy:limit:) to implement this functionality. But Shortcut just seems to do it all on it's own. I'm mostly wondering, how is this even working? Here's some example code: import AppIntents let books = [ BookEntity(id: 0, title: "A Family Affair"), BookEntity(id: 1, title: "Atlas of the Heart"), BookEntity(id: 2, title: "Atomic Habits"), BookEntity(id: 3, title: "Memphis"), BookEntity(id: 4, title: "Run Rose Run"), BookEntity(id: 5, title: "The Maid"), BookEntity(id: 6, title: "The Match"), BookEntity(id: 7, title: "Where the Crawdads Sing"), ] struct BookEntity: AppEntity, Identifiable { static var typeDisplayRepresentation: TypeDisplayRepresentation = "Book" var displayRepresentation: DisplayRepresentation { DisplayRepresentation(title: "\(title)") } static var defaultQuery = BookQuery() var id: Int @Property(title: "Title") var title: String init(id: Int, title: String) { self.id = id self.title = title } } struct BookQuery: EntityQuery { func entities(for identifiers: [Int]) async throws -> [BookEntity] { return identifiers.map { id in books[id] } } } extension BookQuery: EntityPropertyQuery { static var properties = QueryProperties { Property(\BookEntity.$title) { EqualToComparator { str in { book in book.title == str } } ContainsComparator { str in { book in book.title.contains(str) } } } } static var sortingOptions = SortingOptions { SortableBy(\BookEntity.$title) } func entities( matching comparators: [(BookEntity) -> Bool], mode: ComparatorMode, sortedBy: [Sort<BookEntity>], limit: Int? ) async throws -> [BookEntity] { books.filter { book in comparators.allSatisfy { comparator in comparator(book) } } } } The example Shortcut first invokes entities(matching:mode:sortedBy:limit:) with comparators=[], sortedBy=[], limit=nil to fetch all Book entities. Next the filter step correctly applies the title contains filter but never calls entities(matching:mode:sortedBy:limit:) or even the body of the ContainsComparator. But the output is correctly filtered.
1
0
1.7k
Jul ’25
A script to open files from Finder in Vim
Here is an AppleScript script to make it possible double-click files in Finder so that they will be opened in Vim, by Normen Hansen: https://github.com/normen/vim-macos-scripts/blob/master/open-file-in-vim.applescript (it must be used from Automator). I try to simplify it and also to make it possible to use it without Automator. To use my own version, you only need to save it as application instead, like this: osacompile -o open-with-vim.app open-with-vim.applescript and then copy it to /Applications and set your .txt files to be opened using this app. Here it is, it alsmost works: -- https://github.com/normen/vim-macos-scripts/blob/master/open-file-in-vim.applescript -- opens Vim with file or file list -- sets current folder to parent folder of first file on open theFiles set command to {} if input is null or input is {} or ((item 1 in input) as string) is "" then -- no files, open vim without parameters set end of command to "vim;exit" else set firstFile to (item 1 of theFiles) as string tell application "Finder" to set pathFolderParent to quoted form of (POSIX path of ((folder of item firstFile) as string)) set end of command to "cd" &amp; space &amp; (pathFolderParent as string) set end of command to ";hx" -- e.g. vi or any other command-line text editor set fileList to {} repeat with i from 1 to (theFiles count) set end of fileList to space &amp; quoted form of (POSIX path of (item i of theFiles as string)) end repeat set end of command to (fileList as string) set end of command to ";exit" -- if Terminal &gt; Settings &gt; Profiles &gt; Shell &gt; When the shell exits != Don't close the window end if set command to command as string set myTab to null tell application "Terminal" if it is not running then set myTab to (do script command in window 1) else set myTab to (do script command) end if activate end tell return end open The only problem is the if block in the very beginning: if input is null or input is {} or ((item 1 in input) as string) is "" then What is wrong here? How to make it work? (Assuming it already works fine if AppleScript is used using Automator.)
0
0
303
Mar ’25
WidgetInfo - intent is nil until edit face
We have a watchOS app that provides many configurable widgets. Those widgets are configured and installed with help of AppIntent: public struct RectComplAppIntent: AppIntent, WidgetConfigurationIntent, CustomIntentMigratedAppIntent { @Parameter(title: "Style") var style: String? .... } However when I print WidgetInfos with getCurrentConfigurations(), I sometimes got nil for configuration. At the same time widgets are not loaded. Exact steps: User installs the pre-cofnigured .watchface. Complications are not loaded since configuration is missing. I print getCurrentConfigurations() and get entries like this: WidgetInfo: - configuration: nil - widgetConfigurationIntent: nil - family: accessoryRectangular - kind: Rectangle Then user force-touches a face and opens editing mode. Returns to watch app, prints infos: WidgetInfo: - configuration: <INIntent: 0x780d290> { style = vol1Logo; } - widgetConfigurationIntent: nil - family: accessoryRectangular - kind: Rectangle – Suddenly intent appears with the correct style and complications start to show up. How do you think, why it happens? Why after .watchface install all the WidgetInfo has nil intent (configuration)? What helps them to load later? You can try this face yourself: https://cdn.watchfaces.co/watchfaces/glance-minimalist.watchface
1
0
364
Mar ’25
Start multiple Live Activities in the perform() of App Intents
In my case, when two functions that start each Live Activity(not connected each other) are performed in LiveActivityIntent's perform(), it seems that only one will start. (It's the same to start independently with two Task{}) And, set one to 'opensIntent' and separate it by opening another LiveActivityIntent, the result is same. Also, every time I tap the Intent directly in the shortcut app, one activity will end within a matter of seconds, even if there are two for a while. But, If openAppWhenRun to true, it seem to works without any problems. I would appreciate it if you could give me a tip to fix this problem.
0
0
223
Mar ’25
Using Automator
Hi Here is my problem. I have a large number of portrait pictures in my adobe lightroom catalogue which I need to email to the subjects of the portraits. These peoples names and email addresses are stored in my pictures metadata and I am able to export the pictures with accompanying text or CSV files containing the names &amp; addresses. What I want to do is export the pictures in bulk and automagically create and send individual emails for each picture, preferably with the persons name in the salutation at the start of the email. I have done some googling trying to find a solution to this. I think that I need to use automator. (The lightroom option of mailing directly from within the programme isn't going to do the job, each mail would need to be addressed and written by hand each time). I have been playing around with it, but can see no way to populate the email address field or add the attachment. Can anyone help me out, with even just the basic outline, of how this would be achieved please?
0
0
188
Mar ’25
Siri Shortcut Phrases iOS -17
I made a set of Siri Shortcuts in my app with the AppShortcutsProvider, and they each have a set of phrases. I can activate the shortcuts via Siri phrases or Spotlight search on iOS 18+, but not on iOS -17. I've checked the documentation and see that AppShortcutsProvider is supported from iOS 16+, so I don't understand why I can't view the shortcuts in Spotlight or activate them with Siri unless it's at least iOS 18. Any thoughts?
4
0
460
Mar ’25
Displaying an email in mail preview pane by message ID
Hi, I want to open an email message with AppleScript. Everything is working correctly, but in the Mail app, instead of focusing on targetMessage, it highlights the email after the target message. When I use: tell targetMessage to open the correct email opens in new window but the wrong email is highlighted in the Mail app list. tell application "Mail" activate set targetAccount to missing value repeat with anAccount in every account if name of anAccount is "AccountName" then set targetAccount to anAccount exit repeat end if end repeat if targetAccount is not missing value then set targetBox to missing value repeat with aBox in mailboxes of targetAccount if name of aBox is "MailboxName" then set targetBox to aBox exit repeat end if end repeat if targetBox is not missing value then set targetMessage to missing value set oneWeekAgo to (current date) - (7 * days) set filteredMessages to (every message of targetBox whose date received ≥ oneWeekAgo) repeat with aMessage in filteredMessages try if message id of aMessage is "MessageID" then set targetMessage to aMessage exit repeat end if end try end repeat if targetMessage is not missing value then if (count of message viewers) > 0 then set mailViewer to message viewer 1 else set mailViewer to make new message viewer end if tell mailViewer set selected mailboxes to {targetBox} delay 0.2 set selected messages to {targetMessage} end tell return "Found" else return "Message Not found" end if else return "Folder Not found" end if else return "Account Not found" end if end tell Why is this behavior happening?
1
0
269
Mar ’25
AppIntent, should I delete previous donations to the system and how?
Hi, In my application I am donating AppIntent instances that I have to the system using the donate() API. I recently came across this article that talks about deleting donations but it does not mention how to handle AppIntent instances. I am wondering when working with dynamic AppIntents (with different properties that can change in the future), should I be worried about "outdated" donated AppIntent instances? And if yes how can I delete previously donated AppIntent instances.
0
0
237
Mar ’25
Difficulties with Get Contents of URL
I’ve created several shortcuts that tell me the stock price of a given company. The shortcut queries Yahoo Finance using Get Contents of URL, with the URL https://finance.yahoo.com/quote/TICKER SYMBOL/, for example https://finance.yahoo.com/quote/PLTR/ for Palantir or https://finance.yahoo.com/quote/AAPL/ for Apple, etc. Then it uses RegEx to parse out the numbers which it then formats and displays in a notification. Simple. It works great for several stocks, but for some reason, it does not work correctly for Palantir. It shows an older “previous close” price. Oddly, when I go to the website myself, it shows me the current stock price. So for today Mar 21 https://finance.yahoo.com/quote/PLTR/ shows me $90.96 (correct) but the shortcut, via Get Contents of URL, shows $87.39 (incorrect). This $87.39 price is listed further down in the page as a "previous close” price. I don’t get it. Here is a link to my Palantir shortcut: https://www.icloud.com/shortcuts/edea6ee0261245f49b078efc74d632dd Here is a link to my Apple shortcut: https://www.icloud.com/shortcuts/54a416393203432aa356fe76373e3f8b So the question is, why does Get Contents of URL show an old stock price but when I go to the site myself, it shows the correct stock price … and only for Palantir? I have about six shortcuts running correctly. Palantir is the only one that does not work. Been banging my head on this one for weeks. Any advice would be much appreciated. Thank you, Rob
0
0
88
Mar ’25
NSUserActivity in application(_:continue:restorationHandler:) not recognized as INStartCallIntent
Hello, experts! I'm working on a VOIP application that handles audio calls and integrates with CallKit. The problem occurs when attempting to redial a previously made audio call from the system's call history. When I try to handle the NSUserActivity in the application(_:continue:restorationHandler:) method, it intercepts the INStartAudioCallIntent instead of the expected INStartCallIntent. Background Deprecation Warnings: I'm encountering deprecation warnings when using INStartAudioCallIntent and INStartVideoCallIntent: 'INStartAudioCallIntent' was deprecated in iOS 13.0: INStartAudioCallIntent is deprecated. Please adopt INStartCallIntent instead. 'INStartVideoCallIntent' was deprecated in iOS 13.0: INStartVideoCallIntent is deprecated. Please adopt INStartCallIntent instead. As a result, I need to migrate to INStartCallIntent instead, but the issue is that when trying to redial a call from the system’s call history, INStartAudioCallIntent is still being triggered. Working with Deprecated Intents: If I use INStartAudioCallIntent or INStartVideoCallIntent, everything works as expected, but I want to adopt INStartCallIntent to align with the current iOS recommendations. Configuration: CXProvider Configuration: The CXProvider is configured as follows: let configuration = CXProviderConfiguration() configuration.supportsVideo = true configuration.maximumCallsPerCallGroup = 1 configuration.maximumCallGroups = 1 configuration.supportedHandleTypes = [.generic] configuration.iconTemplateImageData = UIImage(asset: .callKitLogo)?.pngData() let provider = CXProvider(configuration: configuration) Outgoing Call Handle: When making an outgoing call, the CXHandle is created like this: let handle = CXHandle(type: .generic, value: callId) Info.plist Configuration: In the info.plist, the following key is defined: &lt;key&gt;NSUserActivityTypes&lt;/key&gt; &lt;array&gt; &lt;string&gt;INStartCallIntent&lt;/string&gt; &lt;/array&gt; Problem: When trying to redial the audio call from the system's call history, the NSUserActivity received in the application(_:continue:restorationHandler:) method is an instance of INStartAudioCallIntent instead of INStartCallIntent. This happens even though INStartCallIntent is listed in NSUserActivityTypes in the info.plist and I want to migrate to the newer intent as recommended in iOS 13+. Device: iPhone 13 mini iOS version 17.6.1
0
0
244
Mar ’25
Improving And Scaling App Intent Support
Platform and Version iOS Development Environment: Xcode 16.2.0, macOS 15.3 Run-time Configuration: iOS 18.3, 17.x Description of Problem We have started migrating some of the app’s core functionality over to App Intents. Our first release of App Intent support focused on two settings a user can modify on their Bose products, Audio Modes and Immersive Audio, giving users the ability to modify these settings via Siri and shortcuts. The implementation uses two separate shortcuts for each setting type, with each shortcut supporting a single phrase for Siri each: “Change my Bose mode to ” and “Change my Bose immersive audio to ”. Each shortcut uses their own App Intent, and each App Intent has support for optionally providing both a product and a setting when performing the intent. Failing to provide a device, which happens when the intent is performed via Siri, simply auto selects a currently connected Bose product. Failing to provide a setting, like in cases where a user says “Change my Bose ” without providing a setting will simply have Siri confirm the setting the user wants to change before changing the setting. We are using AppEntity to identify a Bose product for both App Intents. Because the App Intent for the Audio Modes setting has a larger number of supported values (up to 15 maximum), we are also using AppEntity to identify these settings. We are using AppEnum to identify available settings for the Immersive Audio App Intent, as only 3 static values are supported. Our original implementation of App Intent support had quite a few phrases supported for each shortcut. We had explicit support for direct synonyms of the verb “Change” in other phrases, supporting words like “Switch” and “Set”. We also had support for words that are like the word “Change”, but not directly related, like the word “Toggle” for instance. We also had support for phrases with or without the setting in each phrase. However, early on we had a lot of trouble with phrase detection with Siri. Siri had a hard time identifying what shortcut was being requested, as well as not being able to identify what settings the user was providing for the setting parameter of each App Intent. While researching potential fixes for this issue, we found a response to a thread in the Apple forums (https://developer.apple.com/forums/thread/759909) that seemed to indicate that Siri phrase recognition was very much an aggregate process. With the total number of phrases supported combined with the available settings for each phrase further compounding the total number of phrases Siri needs to learn to recognize for each shortcut. So, to hopefully improve Siri phrase detection, we added logic to limit the amount of Audio Mode settings supported based on what Audio Modes the user had setup on their Bose products. But, more importantly, we limited the number of explicit phrases supported for each shortcut to just a single phrase. In our testing, not only did this improve phrase recognition, but support for synonyms like “Set” or “Switch” seemed to implicitly still be recognized by Siri. The issues we ran into with Siri phrase detection above has us a bit concerned about scaling App Intent support to other settings and features for our products in the future. Our app supports the ability to modify a large number of settings on their Bose products, with support constantly expanding to new products as they are released. Our roadmap for App Intent support was initially very ambitious, supporting much more than just the two settings mentioned above. But our initial experience with App Intents has us tapering our expectations a little bit as far as how much can be supported in total for App Intents. One thing we also noticed is less than optimal display of default shortcuts in the Shortcuts app. The default shortcuts appeared like so, with shortcuts displayed based on available settings fro each shortcut: However, we could not find a way to indicate to users that one particular section pertained specifically to the Audio Mode setting and the other to the Immersive Audio setting. The only information the user has to make this determination for themselves is the available settings (or shortcuts) for each. This may not be immediately clear to a new customer who might be using one of our products for the first time. This display of default shortcuts in the Shortcuts app has us wondering if our shortcuts implementation is what is intended as far as support for the Shortcuts app is concerned. We did survey default shortcuts displayed by other third-party applications and they mostly dealt with navigation with a single section containing default options clearly indicating where the user can navigate with a shortcut. We couldn’t find an example of an application supporting the ability to change different setting types, with each setting type having their own available values for each. So, to summarize the questions we have concerning App Intent support: What can we do with our App Intents and Shortcuts implementation to guarantee optimal performance with Siri? What is an ideal number of phrases to support for each Shortcut. What limitations should we be placing as far as the total number of available settings for each Shortcut. Are there phrases that might work better than others for what we’re trying to achieve with App Intent support? i.e. Is “Change my Bose mode” or “Change my Bose immersive audio” a good phrase to use for this kind of functionality? Or should we be using different verbs or wording? Assuming optimal support of each Shortcut above. What is a reasonable expectation as far as how many different supported shortcuts we can scale to support at the same time. One issue we ran into early on was Siri confusing one shortcut with the other and triggering the wrong App Intent at times. While this was ultimately resolved, this outcome seems much more likely the greater the number of individual shortcuts supported. Are there any recommendations on how to display these App Intents to customers as far as default shortcuts in the Shortcuts app is concerned? Is what we currently display for default shortcuts in the Shortcuts app what was initially intended for third party support for App Intents? If what we are currently displaying is expected, would it be possible to support the ability to provide additional context to each section of default shortcuts displayed? We would like to indicate to the user that one set of shortcuts pertains to the Audio Modes settings, and the other to Immersive Audio. Something along the lines of a section header like some of the first-party apps use. Are there any recommendations or tips for supporting App Intents, particularly phrases for Siri, in other languages?
0
0
185
Apr ’25