Hi @sm1ng,
If you’re still exploring solutions for triggering actions when an Apple Message (iMessage or SMS) is received, I wanted to share a sample script that might help. This script monitors your Messages database for new incoming messages by comparing the current count of messages with a stored count from the previous run. When it detects a new message, it displays a desktop notification. You can easily customize the notification or add your own custom logic.
Below is the JavaScript code that you can run with BetterTouchTool’s Run Real JavaScript action:
(async () => {
// Retrieve the stored count of received messages from BetterTouchTool's persistent storage.
// This value keeps track of how many messages (that are not sent by me) have been previously received.
let previousNumberOfMessages = await get_number_variable({
variable_name: "number_of_messages",
});
// If the persisted message count is not defined (e.g., on the first run),
// initialize it to 0 to establish a starting point.
if (previousNumberOfMessages === undefined) {
previousNumberOfMessages = 0;
await set_persistent_number_variable({
variable_name: "number_of_messages",
to: previousNumberOfMessages,
});
}
// Define a shell command to count the number of received messages (messages where is_from_me equals 0)
// from the chat database. This command uses sqlite3 to execute the query on the Messages database.
let countRowsShellScript =
'sqlite3 "$HOME/Library/Messages/chat.db" "SELECT COUNT(*) FROM message WHERE is_from_me = 0;"';
// Execute the shell script and capture the current total number of incoming messages.
let numberOfMessages = await runShellScript({
script: countRowsShellScript,
});
// Check if a new message has been received by comparing the current count with the stored count.
if (numberOfMessages > previousNumberOfMessages) {
// Update the persistent storage with the new message count.
await set_persistent_number_variable({
variable_name: "number_of_messages",
to: numberOfMessages,
});
// Construct a shell command to fetch the most recent incoming message.
// This command selects the latest message where is_from_me is 0, outputs it as JSON,
// and uses jq to neatly extract the first (and only) object from the returned JSON array.
let getLastMessageShellScript = `sqlite3 -json $HOME/Library/Messages/chat.db "SELECT * FROM message WHERE is_from_me = 0 ORDER BY date DESC LIMIT 1;" | /opt/homebrew/bin/jq '.[0]'`;
// Run the shell command to retrieve the last received message.
let lastMessage = await runShellScript({
script: getLastMessageShellScript,
});
// Convert the JSON string received from the shell command into a JavaScript object.
let lastMessageObject = JSON.parse(lastMessage);
// Extract the text from the latest message.
// Note: Some messages (like audio messages or reactions) may not contain textual content.
let text = lastMessageObject.text || "No text";
// Display a desktop notification to inform the user about the new incoming message.
// If the message has no text, a default fallback ("No text") is shown.
await display_notification({
title: "New message!",
subTitle: `${text}`,
});
}
// Return the current message count back to BetterTouchTool,
// useful for debugging and testing.
returnToBTT(numberOfMessages);
})();
And here’s the corresponding BetterTouchTool configuration JSON. This setup uses a Repeating or Time Based Trigger (set to every 5 seconds) to run the above JavaScript code:
[
{
"BTTActionCategory" : 0,
"BTTLastUpdatedAt" : 1740813597.1263862,
"BTTTriggerType" : 678,
"BTTTriggerTypeDescriptionReadOnly" : "Repeating or Time Based Trigger",
"BTTTriggerClass" : "BTTTriggerTypeOtherTriggers",
"BTTUUID" : "B395EECB-0330-4BC8-8E35-2EC839B3BCEB",
"BTTPredefinedActionType" : 366,
"BTTPredefinedActionName" : "Empty Placeholder",
"BTTAdditionalConfiguration" : "{\"BTTTimedRepeatEveryXSeconds\":\"5\",\"BTTTimedWhenToTrigger\":0}",
"BTTEnabled" : 1,
"BTTEnabled2" : 1,
"BTTOrder" : 11,
"BTTAdditionalActions" : [
{
"BTTActionCategory" : 0,
"BTTLastUpdatedAt" : 1740813588.787029,
"BTTTriggerParentUUID" : "B395EECB-0330-4BC8-8E35-2EC839B3BCEB",
"BTTIsPureAction" : true,
"BTTTriggerClass" : "BTTTriggerTypeOtherTriggers",
"BTTUUID" : "13FEDBDA-6255-4098-B312-BB1BC6186DF1",
"BTTPredefinedActionType" : 281,
"BTTPredefinedActionName" : "Run Real JavaScript",
"BTTAdditionalActionData" : {
"BTTScriptFunctionToCall" : "someJavaScriptFunction",
"BTTJavaScriptUseIsolatedContext" : false,
"BTTAppleScriptRunInBackground" : false,
"BTTScriptType" : 3,
"BTTAppleScriptString" : "(async () => {\n\t\/\/ Retrieve the stored count of received messages from BetterTouchTool's persistent storage.\n\t\/\/ This value keeps track of how many messages (that are not sent by me) have been previously received.\n\tlet previousNumberOfMessages = await get_number_variable({\n\t\tvariable_name: \"number_of_messages\",\n\t});\n\n\t\/\/ If the persisted message count is not defined (e.g., on the first run),\n\t\/\/ initialize it to 0 to establish a starting point.\n\tif (previousNumberOfMessages === undefined) {\n\t\tpreviousNumberOfMessages = 0;\n\t\tawait set_persistent_number_variable({\n\t\t\tvariable_name: \"number_of_messages\",\n\t\t\tto: previousNumberOfMessages,\n\t\t});\n\t}\n\n\t\/\/ Define a shell command to count the number of received messages (messages where is_from_me equals 0)\n\t\/\/ from the chat database. This command uses sqlite3 to execute the query on the Messages database.\n\tlet countRowsShellScript =\n\t\t'sqlite3 \"$HOME\/Library\/Messages\/chat.db\" \"SELECT COUNT(*) FROM message WHERE is_from_me = 0;\"';\n\n\t\/\/ Execute the shell script and capture the current total number of incoming messages.\n\tlet numberOfMessages = await runShellScript({\n\t\tscript: countRowsShellScript,\n\t});\n\n\t\/\/ Check if a new message has been received by comparing the current count with the stored count.\n\tif (numberOfMessages > previousNumberOfMessages) {\n\t\t\/\/ Update the persistent storage with the new message count.\n\t\tawait set_persistent_number_variable({\n\t\t\tvariable_name: \"number_of_messages\",\n\t\t\tto: numberOfMessages,\n\t\t});\n\n\t\t\/\/ Construct a shell command to fetch the most recent incoming message.\n\t\t\/\/ This command selects the latest message where is_from_me is 0, outputs it as JSON,\n\t\t\/\/ and uses jq to neatly extract the first (and only) object from the returned JSON array.\n\t\tlet getLastMessageShellScript = `sqlite3 -json $HOME\/Library\/Messages\/chat.db \"SELECT * FROM message WHERE is_from_me = 0 ORDER BY date DESC LIMIT 1;\" | \/opt\/homebrew\/bin\/jq '.[0]'`;\n\n\t\t\/\/ Run the shell command to retrieve the last received message.\n\t\tlet lastMessage = await runShellScript({\n\t\t\tscript: getLastMessageShellScript,\n\t\t});\n\n\t\t\/\/ Convert the JSON string received from the shell command into a JavaScript object.\n\t\tlet lastMessageObject = JSON.parse(lastMessage);\n\n\t\t\/\/ Extract the text from the latest message.\n\t\t\/\/ Note: Some messages (like audio messages or reactions) may not contain textual content.\n\t\tlet text = lastMessageObject.text || \"No text\";\n\n\t\t\/\/ Display a desktop notification to inform the user about the new incoming message.\n\t\t\/\/ If the message has no text, a default fallback (\"No text\") is shown.\n\t\tawait display_notification({\n\t\t\ttitle: \"New message!\",\n\t\t\tsubTitle: `${text}`,\n\t\t});\n\t}\n\n\t\/\/ Return the current message count back to BetterTouchTool,\n\t\/\/ useful for debugging and testing.\n\treturnToBTT(numberOfMessages);\n})();\n",
"BTTActionJSRunInSeparateContext" : false,
"BTTAppleScriptUsePath" : false,
"BTTScriptLocation" : 0
},
"BTTRealJavaScriptString" : "(async () => {\n\t\/\/ Retrieve the stored count of received messages from BetterTouchTool's persistent storage.\n\t\/\/ This value keeps track of how many messages (that are not sent by me) have been previously received.\n\tlet previousNumberOfMessages = await get_number_variable({\n\t\tvariable_name: \"number_of_messages\",\n\t});\n\n\t\/\/ If the persisted message count is not defined (e.g., on the first run),\n\t\/\/ initialize it to 0 to establish a starting point.\n\tif (previousNumberOfMessages === undefined) {\n\t\tpreviousNumberOfMessages = 0;\n\t\tawait set_persistent_number_variable({\n\t\t\tvariable_name: \"number_of_messages\",\n\t\t\tto: previousNumberOfMessages,\n\t\t});\n\t}\n\n\t\/\/ Define a shell command to count the number of received messages (messages where is_from_me equals 0)\n\t\/\/ from the chat database. This command uses sqlite3 to execute the query on the Messages database.\n\tlet countRowsShellScript =\n\t\t'sqlite3 \"$HOME\/Library\/Messages\/chat.db\" \"SELECT COUNT(*) FROM message WHERE is_from_me = 0;\"';\n\n\t\/\/ Execute the shell script and capture the current total number of incoming messages.\n\tlet numberOfMessages = await runShellScript({\n\t\tscript: countRowsShellScript,\n\t});\n\n\t\/\/ Check if a new message has been received by comparing the current count with the stored count.\n\tif (numberOfMessages > previousNumberOfMessages) {\n\t\t\/\/ Update the persistent storage with the new message count.\n\t\tawait set_persistent_number_variable({\n\t\t\tvariable_name: \"number_of_messages\",\n\t\t\tto: numberOfMessages,\n\t\t});\n\n\t\t\/\/ Construct a shell command to fetch the most recent incoming message.\n\t\t\/\/ This command selects the latest message where is_from_me is 0, outputs it as JSON,\n\t\t\/\/ and uses jq to neatly extract the first (and only) object from the returned JSON array.\n\t\tlet getLastMessageShellScript = `sqlite3 -json $HOME\/Library\/Messages\/chat.db \"SELECT * FROM message WHERE is_from_me = 0 ORDER BY date DESC LIMIT 1;\" | \/opt\/homebrew\/bin\/jq '.[0]'`;\n\n\t\t\/\/ Run the shell command to retrieve the last received message.\n\t\tlet lastMessage = await runShellScript({\n\t\t\tscript: getLastMessageShellScript,\n\t\t});\n\n\t\t\/\/ Convert the JSON string received from the shell command into a JavaScript object.\n\t\tlet lastMessageObject = JSON.parse(lastMessage);\n\n\t\t\/\/ Extract the text from the latest message.\n\t\t\/\/ Note: Some messages (like audio messages or reactions) may not contain textual content.\n\t\tlet text = lastMessageObject.text || \"No text\";\n\n\t\t\/\/ Display a desktop notification to inform the user about the new incoming message.\n\t\t\/\/ If the message has no text, a default fallback (\"No text\") is shown.\n\t\tawait display_notification({\n\t\t\ttitle: \"New message!\",\n\t\t\tsubTitle: `${text}`,\n\t\t});\n\t}\n\n\t\/\/ Return the current message count back to BetterTouchTool,\n\t\/\/ useful for debugging and testing.\n\treturnToBTT(numberOfMessages);\n})();\n",
"BTTEnabled" : 1,
"BTTEnabled2" : 1,
"BTTOrder" : 726
}
]
}
]
This configuration continuously checks for new messages every 5 seconds and triggers the JavaScript, making it a flexible starting point for further customization.
Hope this helps!