Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
AppleScript to close all notifications on macOS Big Sur
function run(input, parameters) {
const appName = "";
const verbose = true;
const scriptName = "close_notifications_applescript";
const CLEAR_ALL_ACTION = "Clear All";
const CLEAR_ALL_ACTION_TOP = "Clear";
const CLOSE_ACTION = "Close";
const notNull = (val) => {
return val !== null && val !== undefined;
}
const isNull = (val) => {
return !notNull(val);
}
const isError = (maybeErr) => {
return notNull(maybeErr) && (maybeErr instanceof Error || maybeErr.message);
}
const systemVersion = () => {
return Application("Finder").version().split(".").map(val => parseInt(val));
}
const systemVersionGreaterThanOrEqualTo = (vers) => {
return systemVersion()[0] >= vers;
}
const isBigSurOrGreater = () => {
return systemVersionGreaterThanOrEqualTo(11);
}
const V11_OR_GREATER = isBigSurOrGreater();
const APP_NAME_MATCHER_ROLE = V11_OR_GREATER ? "AXStaticText" : "AXImage";
const hasAppName = notNull(appName) && appName !== "";
const appNameForLog = hasAppName ? ` [${appName}]` : "";
const logs = [];
const log = (message, ...optionalParams) => {
let message_with_prefix = `${new Date().toISOString().replace("Z", "").replace("T", " ")} [${scriptName}]${appNameForLog} ${message}`;
console.log(message_with_prefix, optionalParams);
logs.push(message_with_prefix);
}
const logError = (message, ...optionalParams) => {
if (isError(message)) {
let err = message;
message = `${err}${err.stack ? (' ' + err.stack) : ''}`;
}
log(`ERROR ${message}`, optionalParams);
}
const logErrorVerbose = (message, ...optionalParams) => {
if (verbose) {
logError(message, optionalParams);
}
}
const logVerbose = (message) => {
if (verbose) {
log(message);
}
}
const getLogLines = () => {
return logs.join("\n");
}
const getSystemEvents = () => {
let systemEvents = Application("System Events");
systemEvents.includeStandardAdditions = true;
return systemEvents;
}
const getNotificationCenter = () => {
try {
return getSystemEvents().processes.byName("NotificationCenter");
} catch (err) {
logError("Could not get NotificationCenter");
throw err;
}
}
const getNotificationCenterGroups = (retryOnError = false) => {
try {
let notificationCenter = getNotificationCenter();
if (notificationCenter.windows.length <= 0) {
return [];
}
if (!V11_OR_GREATER) {
return notificationCenter.windows();
}
return notificationCenter.windows[0].uiElements[0].uiElements[0].uiElements();
} catch (err) {
logError("Could not get NotificationCenter groups");
if (retryOnError) {
logError(err);
return getNotificationCenterGroups(false);
} else {
throw err;
}
}
}
const isClearButton = (description, name) => {
return description === "button" && name === CLEAR_ALL_ACTION_TOP;
}
const matchesAppName = (role, value) => {
return role === APP_NAME_MATCHER_ROLE && value.toLowerCase() === appName.toLowerCase();
}
const notificationGroupMatches = (group) => {
try {
let description = group.description();
if (V11_OR_GREATER && isClearButton(description, group.name())) {
return true;
}
if (V11_OR_GREATER && description !== "group") {
return false;
}
if (!V11_OR_GREATER) {
let matchedAppName = !hasAppName;
if (!matchedAppName) {
for (let elem of group.uiElements()) {
if (matchesAppName(elem.role(), elem.description())) {
matchedAppName = true;
break;
}
}
}
if (matchedAppName) {
return notNull(findCloseActionV10(group, -1));
}
return false;
}
if (!hasAppName) {
return true;
}
let firstElem = group.uiElements[0];
return matchesAppName(firstElem.role(), firstElem.value());
} catch (err) {
logErrorVerbose(`Caught error while checking window, window is probably closed: ${err}`);
logErrorVerbose(err);
}
return false;
}
const findCloseActionV10 = (group, closedCount) => {
try {
for (let elem of group.uiElements()) {
if (elem.role() === "AXButton" && elem.title() === CLOSE_ACTION) {
return elem.actions["AXPress"];
}
}
} catch (err) {
logErrorVerbose(`(group_${closedCount}) Caught error while searching for close action, window is probably closed: ${err}`);
logErrorVerbose(err);
return null;
}
log("No close action found for notification");
return null;
}
const findCloseAction = (group, closedCount) => {
try {
if (!V11_OR_GREATER) {
return findCloseActionV10(group, closedCount);
}
let checkForPress = isClearButton(group.description(), group.name());
let clearAllAction;
let closeAction;
for (let action of group.actions()) {
let description = action.description();
if (description === CLEAR_ALL_ACTION) {
clearAllAction = action;
break;
} else if (description === CLOSE_ACTION) {
closeAction = action;
} else if (checkForPress && description === "press") {
clearAllAction = action;
break;
}
}
if (notNull(clearAllAction)) {
return clearAllAction;
} else if (notNull(closeAction)) {
return closeAction;
}
} catch (err) {
logErrorVerbose(`(group_${closedCount}) Caught error while searching for close action, window is probably closed: ${err}`);
logErrorVerbose(err);
return null;
}
log("No close action found for notification");
return null;
}
const closeNextGroup = (groups, closedCount) => {
try {
for (let group of groups) {
if (notificationGroupMatches(group)) {
let closeAction = findCloseAction(group, closedCount);
if (notNull(closeAction)) {
try {
closeAction.perform();
return [true, 1];
} catch (err) {
logErrorVerbose(`(group_${closedCount}) Caught error while performing close action, window is probably closed: ${err}`);
logErrorVerbose(err);
}
}
return [true, 0];
}
}
return false;
} catch (err) {
logError("Could not run closeNextGroup");
throw err;
}
}
try {
let groupsCount = getNotificationCenterGroups(true).filter(group => notificationGroupMatches(group)).length;
if (groupsCount > 0) {
logVerbose(`Closing ${groupsCount}${appNameForLog} notification group${(groupsCount > 1 ? "s" : "")}`);
let startTime = new Date().getTime();
let closedCount = 0;
let maybeMore = true;
let maxAttempts = 2;
let attempts = 1;
while (maybeMore && ((new Date().getTime() - startTime) <= (1000 * 30))) {
try {
let closeResult = closeNextGroup(getNotificationCenterGroups(), closedCount);
maybeMore = closeResult[0];
if (maybeMore) {
closedCount = closedCount + closeResult[1];
}
} catch (innerErr) {
if (maybeMore && closedCount === 0 && attempts < maxAttempts) {
log(`Caught an error before anything closed, trying ${maxAttempts - attempts} more time(s).`)
attempts++;
} else {
throw innerErr;
}
}
}
} else {
throw Error(`No${appNameForLog} notifications found...`);
}
} catch (err) {
logError(err);
logError(err.message);
getLogLines();
throw err;
}
return getLogLines();
}
@babramczyk

This comment has been minimized.

Copy link

@babramczyk babramczyk commented Feb 10, 2021

So awesome, @lancethomps! Thanks for sharing

Do you know of a way to leverage this updated Big Sur logic to do any of the following? I'm having a tough time with it ever since I switched to Big Sur.

  • Clicking on the last received notification (instead of dismissing it)
  • Click Accept, Decline, End buttons for the FaceTime windows in the top left when receiving/on a call
@duhd1993

This comment has been minimized.

Copy link

@duhd1993 duhd1993 commented Feb 13, 2021

Could you explain how do you find out those system APIs and UI elements? To my knowledge, they are not documented by Apple.

@capaldo

This comment has been minimized.

Copy link

@capaldo capaldo commented Apr 9, 2021

Could you explain how do you find out those system APIs and UI elements? To my knowledge, they are not documented by Apple.

Not the poster but you can use UI Browser

@rayshan

This comment has been minimized.

Copy link

@rayshan rayshan commented Apr 15, 2021

Any idea how to overcome this Accessibility permission error for all apps? Adding AppleScript Utility, Automator and Script Editor to Settings > Security & Privacy > Privacy > Accessibility leads to this error. Adding the app that's in focus when running this script fixes this error, but does that mean that we need to add every app to Accessibility permissions?

Screen Shot 2021-04-15 at 10 40 13 AM

@Albaniamd

This comment has been minimized.

Copy link

@Albaniamd Albaniamd commented Apr 18, 2021

First off, @lancethomps thank you for posting this.

@rayshan I had the same problem and after searching through many forums I found a workaround. HT to jefinthejam

"I had the exact same issue with a Service that I had written using Automator. After reading the reply from red_menace, I converted my AppleScript to an Application, saved it using the Application file format in my "Applications" folder and added it to the list of applications allowed to control my computer in the Accessibility section of "Security & Privacy" in System Preferences. After that, I simply created a new Service that launch the application."

https://discussions.apple.com/thread/6874514?answerId=28722368022#28722368022

@rayshan

This comment has been minimized.

Copy link

@rayshan rayshan commented Apr 18, 2021

@Albaniamd thank you so much that works!

@L-Killerman

This comment has been minimized.

Copy link

@L-Killerman L-Killerman commented May 21, 2021

This is AMAZING. Have been searching for such a script for a long while, as the Big Sur changes its way and the script I used on old MacOS no longer works, which is suffering. This code works so well! Genius, briefly scanned the code. Must coming from a coding-OCD person, so well organized and covering different kinds of corner situations. OMG, love you so much!

@bawanyadam

This comment has been minimized.

Copy link

@bawanyadam bawanyadam commented May 24, 2021

Been looking for something like this forever! Thanks @lancethomps!

Not sure if I'm totally missing something, but when running the Automator App (saved to Applications dir) I keep getting the error "The action “Run JavaScript” encountered an error: “Error: Error: No notifications found...” despite there being notifications present. Not sure if this is due a to change in Big Sur 11.3.1? Any insight would be appreciated!

Screen Shot 2021-05-24 at 11 45 42 AM

@lancethomps

This comment has been minimized.

Copy link
Owner Author

@lancethomps lancethomps commented May 24, 2021

Thanks all for the feedback - I've been quite busy with my real job so I haven't had much time to spend on responding/updating this.

@bawanyadam I am not seeing that issue, but I do see that 11.4 is now available so maybe you're actually on that? I will try to upgrade to that today and see if this still works. It looks like you have the control center/widgets open so maybe thats causing the issue... I will check that out too.

@bawanyadam

This comment has been minimized.

Copy link

@bawanyadam bawanyadam commented May 24, 2021

@lancethomps Appreciate the response! No worries or priority on getting back to this, real job comes first hah.

For what it's worth even with the widgets removed, the error still persists on 11.3.1. I'm updating to 11.4 now and will report back if it changes. Screenshot attached for reference if it even matters hah.

Screen Shot 2021-05-24 at 12 43 53 PM

Edit: Just realized this script pertains to actually closing incoming notifications. My mistake - I was looking for a way to clear all notifications at once in NC and mistook this for that!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment