Copy 2FA code from received iMessage/Text on MacOS

Hi everyone, Is anyone aware of a script/app that can automatically copy the code from a 2FA text message on MacOS? On iOS, if you receive a code via text/iMessage, a horizontal menu appears with the code, and with an easy finger click, it will automatically populate the code field on the given app. Is there something similar on MacOS? Thanks!

… will automatically populate the code field on the given app

Maybe I’m missing something, but doesn’t this already automatically happen on MacOS too?

You probably need to have Text Message Forwarding enabled on your iPhone (Settings → Messages → Text Message Forwarding), and your Mac will need to be signed into the same AppleID account, but then you’re set.

My Mac is set up with the forwarding and I can see the message. Does it only work in Safari?

Following up on this discussion, I would like Applescript to capture the code from iMessage, copy it to the clipboard and insert the result into Claris’ Sign in for Claris ID to access FileMaker Pro. Currently, the Claris ID although sent to my cell is never captured by the Claris Sign In verification code field.

I would like to replace my current practice of switching applications to iMessages in OSX, finding the last message, copying the six digit verification code to clipboard and then pasting the code into Claris’ Sign In on its “Sign in With Claris ID” page.

I do this by capturing the macOS system notification. I have the script assigned to Cmd-Opt-P. It has worked very reliably for me for about ~5 years.

The current version runs on Sonoma (and Ventura, I believe). I believe Monterey required a slightly different specifier for the notification UI element.

-- For security purposes, execute all potentially-sensitive code within a main handler.
return main()

-- Define the main handler.
to main()
	
	set {notification_message:s} to getUserNotificationInfo()
	
	set s_words to extractAllIntegers(s)
	set s_match to ""
	repeat with i from 1 to length of s_words
		set s_word to item i of s_words
		if length of s_word > 4 then
			set s_match to s_word
			exit repeat
		end if
	end repeat
	
	if s_match is "" then
		display dialog "Failed to find SMS OTP in string:" with title (my name as string) with icon caution buttons {"OK"} default button 1
	else
		setClipboard(s_match)
	end if
	
	return
	
end main



### HANDLERS ###
to getUserNotificationInfo()

	tell application "System Events"
		tell process "NotificationCenter"
			
			try
				set notification_window to first window
			on error
				return missing value
			end try
			
			set static_texts to value of static texts of group 1 of UI element 1 of scroll area 1 of group 1 of notification_window
			set static_texts_length to length of static_texts
			
			set application_name to item 1 of static_texts
			set notification_message to last item of static_texts
			if static_texts_length > 3 then
				set notification_title to item 3 of static_texts
			else
				set notification_title to missing value
			end if
			if static_texts_length > 4 then
				set notification_subtitle to item 4 of static_texts
			else
				set notification_subtitle to missing value
			end if
			
		end tell
	end tell
	
	return {application_name:application_name, notification_title:notification_title, notification_subtitle:notification_subtitle, notification_message:notification_message}
	
end getUserNotificationInfo


to extractAllIntegers(s)
	
	set L to {}
	set previous_character_is_digit to none
	repeat with char in s
		set char to contents of char
		set current_character_is_digit to char is in "0123456789"
		if current_character_is_digit then
			if current_character_is_digit is previous_character_is_digit then
				-- Add to current substring.
				set last item of L to last item of L & char
			else
				-- Start new substring.
				set the end of L to char
			end if
		end if
		set previous_character_is_digit to current_character_is_digit
	end repeat
	
	return L
	
end extractAllIntegers


to setClipboard(s)
	
	do shell script "printf -- %s " & quoted form of s & " | LANG=$(defaults read NSGlobalDomain AppleLocale).UTF-8 pbcopy" without altering line endings
	
	return
	
end setClipboard

Do note the following:

  • You must run the script while the notification is still visible
  • It relies on GUI scripting, so has the habit of breaking with major macOS updates
  • It does not mark the message as read

tree_frog, thanks for your reply.
The script you submitted however errs on my mac running OS 14.2.1 .

	set static_texts to value of static texts of group 1 of UI element 1 of scroll area 1 of group 1 of notification_window
--> System Events got an error: Can’t get scroll area 1 of group 1 of window 1 of application process "NotificationCenter". Invalid index.

Is there another way to script System Notifications to gather the data contained in that application’s most recent window?

It works for me on macOS 14.2.1.

You can check the following:

  • The script must be run while the notification is displayed
  • Since it’s UI scripting, whatever’s running the script must have Accessibility access granted through System Settings

For reasons that are unclear to me, I cannot get the script to work.

Notifications show temporarily on my Mac, but close as soon as I attempt to run the script in Script Debugger.

Script Debugger in System Settings, has accessibility to control both the computer and the full disk.

What else can I change or what script do I need to write to allow Applescript the ability to capture notifications, such as six digit codes sent by web sources?

You can add the following 2 lines to the start of the main() handler definition for easy debugging:

	display notification "Testing OTP code with 123456."
	delay 1

You can also try toggling the System Settings > Privacy & Security > Accessibilty > Script Debugger permission, and then re-launching Script Debugger in case it’s a permissions bug.


The script doesn’t do anything to close the notification. The OTP should be in your clipboard after the script is run.

Have you previously done anything that affects how long notifications are displayed by the system? You can check the following key (by default, this won’t exist).

defaults read com.apple.notificationcenterui bannerTime

Do you have updated version for 14.4?

When I try your script I get : error “The variable none is not defined.” number -2753 from “none”

I did set the accessibility and I am running while notification is present.

Thanks in advance!

macOS 14.4 hasn’t been released yet. The script is working normally on 14.3.1.

It’s possible there’s some weird kind of terminology clash (because of 14.4, or because of a scripting addition you have installed) with the following line:

set previous_character_is_digit to none

You could try changing this to:

set previous_character_is_digit to missing value

This is exactly why we built flowtext.io.

We got super frustrated with only being able to use iMessage + Safari for this on MacOS so this will automatically copy 2fa codes from iMessage to your clipboard for use in any browser/place you see fit!

Disclosure: It’s $5 for a lifetime license and I’m one of the developers.

2 Likes

Looks like Sequoia has some changes which prevent the AppleScript from working:

error "System Events got an error: Can’t get scroll area 1 of group 1 of window \"Notification Center\" of application process \"NotificationCenter\". Invalid index." number -1719 from scroll area 1 of group 1 of window "Notification Center" of application process "NotificationCenter"

I’ve been trying a few things but I’m just completely guessing as to what I need to change. I have Script Editor set to allowed for controlling my computer through accessibility settings which is what I understand is needed to allow it to work. I also tried enabling Screen and System Audio Recording permissions for Script Editor but that didn’t change anything.

All of my machines are still on Sonoma, so unfortunately I can’t test it at present. GUI scripting frequently breaks with major macOS updates. The only way to fix this is to dig through the view hierarchy of the notification using Script Debugger (or “caveman” style in Script Editor) or Accessibility Inspector.

I’ll be updating to Sequoia around version 15.2. If no one has posted the updated code for Sonoma by the end of the year, please let me know.

I’ve updated to macOS 15.1 Sequoia, and unfortunately it looks like the script is unfixable.

It doesn’t look like the contents of the notifcations are exposed through the Accessibility API anymore. Some of the UI elements are not created until you hover over the notification with the mouse, but even then I can’t find the strings anywhere in the hierachy.

Very disappointing news. I relied on parsing notifications in several scripts. Apple giveth and Apple taketh away.

I’ve been fixing my Notification Center automation as well, and it seems as if the hierarchy of notification windows (buttons) has gotten one deeper. This path works for me:
scroll area 1 of group 1 of group 1 of window "Notification Center".

However I no longer seem to be able to access the text contents of the notifications.

These two examples work:

-- Clicks the first notification or group in Sequoia
tell application "System Events" to tell application process "NotificationCenter"
	-- clicks the first notification
	click button 1 of scroll area 1 of group 1 of group 1 of window "Notification Center"
end tell
-- Clears the top notification in Sequoia
tell application "System Events"
  tell process "NotificationCenter"
    set SA to scroll area 1 of group 1 of group 1 of window "Notification Center"
    if (count (actions of button 1 of SA whose name starts with "Name:Clear All")) is 1 then
      -- expand group
      click button 1 of SA
      delay 0.5
      -- close top notification in group
      perform first action of (actions of button 3 of SA whose name starts with "Name:Close")
    else if description of UI element 1 of SA is "heading" then
      -- close top notification in group
      perform first action of (actions of button 3 of SA whose name starts with "Name:Close")
    else
      -- close single top notification
      perform first action of (actions of button 1 of SA whose name starts with "Name:Close")
    end if
  end tell
end tell

But trying to read the description doesn’t work:

-- Can't get any description attribute info in Sequoia
tell application "System Events" to tell application process "NotificationCenter"
	-- grab the description of the notification
	set desc to attribute "AXAttributedDescription" of button 1 of scroll area 1 of group 1 of group 1 of window "Notification Center"
	log (class of desc as text)
	
	-- Both attempts to access the value of the AXAttributedDescription attribute fail
	-- with the error: "System Events got an error: AppleEvent handler failed"
	log (get value of desc)
	log (get value of desc as text)
end tell

Any suggestions would be much appreciated.

(Fully featured notification center control scripts can be found on Github: scripting/applescript at main · seren/scripting · GitHub)

1 Like

No chance. Apple uses here (possibly deliberately) an NSConcreteAttributedString (private subclass of the NSAttributedString class). There is no equivalent for this in AppleScript. With ASObj-C and the PFAssistive Framework you can at least get to the text, but with comma separation, which can lead to problems if the message itself contains one.

See my Post in the Script Debugger Forum:
Parsing Notifications in macOS Sequoia

Maybe you can do something with it.

Note: You will need the Script Debugger to run the script bundle, as the Script Editor does not support third-party frameworks. A compiled applet runs as normal.

3 Likes

We now have a partial solution that requires an external compiled binary (still under development, but with the basics working) or using ASObjC with the PFAssistive Framework.

Further developments & updates on parsing notifications in macOS 15 Sequoia will be posted on this Late Night Software forum post.

1 Like

With regards to the original problem, which I appreciate is from a year ago but perhaps still actively being tackled, I would be tempted to retrieve the latest message receivesd within the Messages.app by executing an SQL query on the chat.db file, e.g.

sqlite3 ~/Library/Messages/chat.db '
    SELECT HEX(attributedBody)
    FROM message
    WHERE is_from_me = 0
    ORDER BY date DESC
    LIMIT 1'

which returns a string of hexbytes that represent serialised data, which wraps an NSAttributedString object with an NSString containing the plain text body of the message.

2 Likes

Nice idea!

It took me a while to get to the text, but it seems to work:

use AppleScript version "2.4"
use framework "Foundation"
use scripting additions

property NSData : a reference to current application's NSData
property NSArray : a reference to current application's NSArray
property NSString : a reference to current application's NSString
property NSUnarchiver : a reference to current application's NSUnarchiver

set hexString to do shell script "sqlite3 ~/Library/Messages/chat.db 'SELECT HEX(attributedBody) FROM message WHERE is_from_me = 0 ORDER BY date DESC LIMIT 1'"
set theData to (NSArray's arrayWithObject:(run script "«data rdat" & hexString & "»"))'s firstObject()'s |data|()
set {theMessage, theError} to (NSUnarchiver's alloc's initForReadingWithData:theData)'s decodeTopLevelObjectAndReturnError:(reference)
if theError is missing value then
	return theMessage's |string| as text
end if

--> This is a test message.
2 Likes