-
-
Save lancethomps/a5ac103f334b171f70ce2ff983220b4f to your computer and use it in GitHub Desktop.
function run(input, parameters) { | |
const appNames = []; | |
const skipAppNames = []; | |
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 notNullOrEmpty = (val) => { | |
return notNull(val) && val.length > 0; | |
}; | |
const isNullOrEmpty = (val) => { | |
return !notNullOrEmpty(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 SYS_VERSION = systemVersion(); | |
const V11_OR_GREATER = isBigSurOrGreater(); | |
const V10_OR_LESS = !V11_OR_GREATER; | |
const V12 = SYS_VERSION[0] === 12; | |
const V15_OR_GREATER = SYS_VERSION[0] >= 15; | |
const V15_2_OR_GREATER = SYS_VERSION[0] >= 15 && SYS_VERSION[1] >= 2; | |
const APP_NAME_MATCHER_ROLE = V11_OR_GREATER ? 'AXStaticText' : 'AXImage'; | |
const NOTIFICATION_SUB_ROLES = ['AXNotificationCenterAlert', 'AXNotificationCenterAlertStack']; | |
const hasAppNames = notNullOrEmpty(appNames); | |
const hasSkipAppNames = notNullOrEmpty(skipAppNames); | |
const hasAppNameFilters = hasAppNames || hasSkipAppNames; | |
const appNameForLog = hasAppNames ? ` [${appNames.join(',')}]` : ''; | |
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 (V10_OR_LESS) { | |
return notificationCenter.windows(); | |
} | |
if (V12) { | |
return notificationCenter.windows[0].uiElements[0].uiElements[0].uiElements(); | |
} | |
if (V15_2_OR_GREATER) { | |
return findNotificationCenterAlerts([], notificationCenter.windows[0].uiElements[0].uiElements[0].uiElements()); | |
} | |
return notificationCenter.windows[0].uiElements[0].uiElements[0].uiElements[0].uiElements(); | |
} catch (err) { | |
logError('Could not get NotificationCenter groups'); | |
if (retryOnError) { | |
logError(err); | |
log('Retrying getNotificationCenterGroups...'); | |
return getNotificationCenterGroups(false); | |
} else { | |
throw err; | |
} | |
} | |
}; | |
const findNotificationCenterAlerts = (alerts, elements) => { | |
for (let elem of elements) { | |
let subrole = elem.subrole(); | |
if (NOTIFICATION_SUB_ROLES.indexOf(subrole) > -1) { | |
alerts.push(elem); | |
} else if (elem.uiElements.length > 0) { | |
findNotificationCenterAlerts(alerts, elem.uiElements()); | |
} | |
} | |
return alerts; | |
}; | |
const isClearButton = (description, name) => { | |
return description === 'button' && name === CLEAR_ALL_ACTION_TOP; | |
}; | |
const matchesAnyAppNames = (value, checkValues) => { | |
if (isNullOrEmpty(checkValues)) { | |
return false; | |
} | |
let lowerAppName = value.toLowerCase(); | |
for (let checkValue of checkValues) { | |
if (lowerAppName === checkValue.toLowerCase()) { | |
return true; | |
} | |
} | |
return false; | |
}; | |
const matchesAppName = (value) => { | |
if (hasAppNames) { | |
return matchesAnyAppNames(value, appNames); | |
} | |
return !matchesAnyAppNames(value, skipAppNames); | |
}; | |
const getAppName = (group) => { | |
if (V15_OR_GREATER) { | |
for (let action of group.actions()) { | |
if (action.description() === 'Remind Me Tomorrow') { | |
return 'reminders'; | |
} | |
} | |
return ''; | |
} | |
if (V10_OR_LESS) { | |
if (group.role() !== APP_NAME_MATCHER_ROLE) { | |
return ''; | |
} | |
return group.description(); | |
} | |
let checkElem = group.uiElements[0]; | |
if (checkElem.value().toLowerCase() === 'time sensitive') { | |
checkElem = group.uiElements[1]; | |
} | |
if (checkElem.role() !== APP_NAME_MATCHER_ROLE) { | |
return ''; | |
} | |
return checkElem.value(); | |
}; | |
const notificationGroupMatches = (group) => { | |
try { | |
let description = group.description(); | |
if (V11_OR_GREATER && isClearButton(description, group.name())) { | |
return true; | |
} | |
if (V15_OR_GREATER) { | |
let subrole = group.subrole(); | |
if (NOTIFICATION_SUB_ROLES.indexOf(subrole) === -1) { | |
return false; | |
} | |
} else if (V11_OR_GREATER && description !== 'group') { | |
return false; | |
} | |
if (V10_OR_LESS) { | |
let matchedAppName = !hasAppNameFilters; | |
if (!matchedAppName) { | |
for (let elem of group.uiElements()) { | |
if (matchesAppName(getAppName(elem))) { | |
matchedAppName = true; | |
break; | |
} | |
} | |
} | |
if (matchedAppName) { | |
return notNull(findCloseActionV10(group, -1)); | |
} | |
return false; | |
} | |
if (!hasAppNameFilters) { | |
return true; | |
} | |
return matchesAppName(getAppName(group)); | |
} 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 (V10_OR_LESS) { | |
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(); | |
} |
I wish I could understand what's going on here. I'm on Monterey but not a single script on this page works. Most do absolutely nothing at all.
The OP's gist, saved as a javascript AS application and granted the accessibility permissions, is incredibly slow, waits a long time before very slowly closing a small fraction of my notifications, not all of them, and then simply quits. I've run it three times now and I still haven't gotten rid of all my notifications.
The rest of the code samples on this page do nothing at all on my machine, except for @Ptujec's, which aborts with
error "The variable _descriptions is not defined." number -2753 from "_descriptions"
even though I do have notifications showing. The rest of them, I run them, they run, and I still have notifications showing, nothing at all happened.
Scripts that involve GUI scripting are prone to break with new OS versions. Not sure to which version of my script you are referring but this one should work under Monterey
Has anyone been able to get this to work within BetterTouchTool (BTT) and if so, what did you do? I am having trouble getting it to fire and unsure if I'm tripping up on something due to BTT or something else... thanks in advance!
FYI: BetterTouchTool offers this feature built-in: Close All Notification Alerts
, for those who would prefer a paid solution: https://community.folivora.ai/t/clear-notifications-with-a-keyboard-shortcut/30335
(I already use BetterTouchTool for dozens of other use cases, so I already had it.)
(and if you're wondering how I came across this thread: I didn't realize BTT already offered this feature until today...)
Does this script still work with Sonoma?
I've been trying this script out as an Alfred workflow (courtesy of https://github.com/bpetrynski/alfred-notification-dismisser), and it doesn't seem like the script is doing anything.
I created an Alfred Workflow based on your script. Works perfectly, tested with macOS Monterey 12.6: https://github.com/bpetrynski/alfred-notification-dismisser
OMG Thank YOU!!!! Been needing this for a long time. :-)
Just FYI, to me, separating and doing "Cleare All" first then "Close" works better.
tell application "System Events" try repeat set _groups to groups of UI element 1 of scroll area 1 of group 1 of window "Notification Center" of application process "NotificationCenter" set numGroups to number of _groups if numGroups = 0 then exit repeat end if repeat with _group in _groups set _actions to actions of _group set actionPerformed to false repeat with _action in _actions if description of _action is in {"Clear All"} then perform _action set actionPerformed to true exit repeat end if end repeat repeat with _action in _actions if description of _action is in {"Close"} then perform _action set actionPerformed to true exit repeat end if end repeat if actionPerformed then exit repeat end if end repeat end repeat end try end tell
I'm getting the following error on macOS Sonoma 14.3.
My old script for closing notifications broke with the upgrade to Sequoia, but after looking at the UI hierarchy I've got a new one working:
tell application "System Events" to tell application process "NotificationCenter"
try
perform (actions of UI elements of UI element 1 of scroll area 1 of group 1 of group 1 of window "Notification Center" of application process "NotificationCenter" of application "System Events" whose name starts with "Name:Close" or name starts with "Name:Clear All")
end try
end tell
I have not tested it extensively at all, but it is working for my simple case of using a hotkey (via Hammerspoon) to dismiss the occasional notifications that pop up. It is of course highly prone to breaking with macOS updates.
edit: I've updated it to work with groups of notifications as well (which requires the "Clear All" action instead of the "Close" action)
@evannjohnson
Your Applescript works on my machine – so far so good.
OS: macOS Sequoia 15.0 arm64
@evannjohnson hmm, when I try to run that on Sequoia 15.0 (M2 Air), I get:
System Events got an error: Can’t get window "Notification Center" of application process "NotificationCenter".
Actually I think that error only occurs when the Notification Center is not visible. When it is visible, I get the error missing label
which I'm guessing has something to do with retrieving the button Clear All
. 🤔
@evannjohnson you are a life saver man, thanks, my notifications dismissed script got messed up when I upgraded to Sequoia.
bur now no notifications messing with me again.
When it is visible, I get the error
missing label
which I'm guessing has something to do with retrieving the buttonClear All
. 🤔
Updated script to account for Notifications found in a Stack (i.e. grouped):
tell application "System Events" to tell application process "NotificationCenter"
try
perform (actions of UI elements of UI element 1 of scroll area 1 of group 1 of group 1 of window "Notification Center" of application process "NotificationCenter" of application "System Events" whose name starts with "Name:Close" or name starts with "Name:Clear All")
end try
end tell
@nicksergeant I added or name starts with "Name:Clear All"
to the whose
query.
Note
This script may not work if your localization isn't English.
@MrJarnould strange. Still getting the missing value
error. Localization is English. Maybe I have some strange conflict between Karabiner, Keyboard Maestro, or something.
I think it is unlikely that is a better approach, as since this UI scripting, to click that button programmatically requires the full Notification Center to be open, showing the button that we want to click. Meaning, you'll need to have either opened the full notification center, or have the script open it just so it can click the button (unless there is something I'm missing).
Clicking the dismiss buttons of just the notifications that are visible on screen is likely what most people will want.
Can you please make this a real repository as everyone keeps getting unnecessary notifications. We only need the bug fix updates to be tracked.
Also, how may we code sign this applescript to avoid tampering?
@evannjohnson ahh, that explains why this isn't working for me 😅. The original gist here works when the Notification Center is open. It may also work when it's closed - I'm not sure - but I only ever use this after opening the Notification Center, seeing the notifications, and then clearing all of them.
Here you have tested, multi-language Apple Script for macOS 15.0 Sequoia: https://github.com/bpetrynski/alfred-notification-dismisser/raw/refs/heads/main/notification-dismisser-macos15.scpt
@bpetrynski thanks! worked for me.
@bpetrynski awesome! That's definitely in the right direction. It only will clear one notification grouping at a time but I can probably add in some looping here. Thanks!
I updated my version for macOS 15. If you have a group of notifications expanded it will collapse the group first which may speed things up a little in some cases.
@Ptujec Which version of macOS 15 are you using? I tried your script and couldn't get it to work.
@Ptujec Which version of macOS 15 are you using? I tried your script and couldn't get it to work.
15.0.1
I just updated the script so it should now should give you an error message.
@Ptujec Which version of macOS 15 are you using? I tried your script and couldn't get it to work.
@MrJarnould I just updated to 15.1 and adjusted the script(s). Apple changed things again from 15.0.1 to 15.1.
Has anyone been able to get this to work within BetterTouchTool (BTT) and if so, what did you do? I am having trouble getting it to fire and unsure if I'm tripping up on something due to BTT or something else... thanks in advance!
FYI: BetterTouchTool offers this feature built-in:
Close All Notification Alerts
, for those who would prefer a paid solution: https://community.folivora.ai/t/clear-notifications-with-a-keyboard-shortcut/30335(I already use BetterTouchTool for dozens of other use cases, so I already had it.)
(and if you're wondering how I came across this thread: I didn't realize BTT already offered this feature until today...)
The BTT action was working for me until I updated to Sequoia (Version 15.1). Has anyone found a solution that works well in BTT?
Happy "This is broken again with macOS 15.2" Day! I think I fixed it though.
sorry all - I know I haven't been active in responding to issues / helping people out on this, but I didn't really plan to find a way to make this support multiple macOS versions and languages. perhaps I should be a better citizen here and maybe even make this a repo instead of a gist.
I just upgraded to macOS 15.1.1 and fixed any issues (at least for my laptop using BTT via the custom application). let me know if you are still having issues. @Ptujec I will see if I can upgrade to 15.2
and check for any issues, but this is a company laptop so I might not be able to.
Happy "This is broken again with macOS 15.2" Day! I think I fixed it though.
I just pushed a fix for 15.2
Has anyone been able to get this to work within BetterTouchTool (BTT) and if so, what did you do? I am having trouble getting it to fire and unsure if I'm tripping up on something due to BTT or something else... thanks in advance!
FYI: BetterTouchTool offers this feature built-in:
Close All Notification Alerts
, for those who would prefer a paid solution: https://community.folivora.ai/t/clear-notifications-with-a-keyboard-shortcut/30335
(I already use BetterTouchTool for dozens of other use cases, so I already had it.)
(and if you're wondering how I came across this thread: I didn't realize BTT already offered this feature until today...)The BTT action was working for me until I updated to Sequoia (Version 15.1). Has anyone found a solution that works well in BTT?
I wonder what @fifafu (dev of BTT) is using for the feature. But I guess the reasons why things break after updates are similar.
I wish I could understand what's going on here. I'm on Monterey but not a single script on this page works. Most do absolutely nothing at all.
The OP's gist, saved as a javascript AS application and granted the accessibility permissions, is incredibly slow, waits a long time before very slowly closing a small fraction of my notifications, not all of them, and then simply quits. I've run it three times now and I still haven't gotten rid of all my notifications.
The rest of the code samples on this page do nothing at all on my machine, except for @Ptujec's, which aborts with
error "The variable _descriptions is not defined." number -2753 from "_descriptions"
even though I do have notifications showing. The rest of them, I run them, they run, and I still have notifications showing, nothing at all happened.