├── .editorconfig ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── bq-preview.png └── src ├── app-filter.js ├── icon.png ├── info.plist ├── quiet-allowed-list.js └── quiet.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | max_line_length = 80 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | logs 3 | *.log 4 | .vscode 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "lf", 3 | "singleQuote": false, 4 | "tabWidth": 2, 5 | "trailingComma": "all" 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Emmanuel Pilande 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

Alfred Be Quiet 🤫

3 |
4 | 5 |

6 | Workflow to automatically pause audio/video playing 7 | 8 |

9 | 10 | ## Why? 11 | 12 | If you have troubles finding a tab with audio playing, run this workflow and it will automatically pause the audio/video playing. 13 | 14 | ## Installation 15 | 16 | 1. Download the Alfred Workflow ([Be-Quiet.alfredworkflow](https://github.com/epilande/alfred-be-quiet/releases/latest/download/Be-Quiet.alfredworkflow)). 17 | 1. Double-click to import into Alfred (requires Powerpack). 18 | 1. For Chrome/Brave enable JavaScript from Apple Events: View -> Developer -> Allow JavaScript from Apple Events. 19 | 20 | ## Usage 21 | 22 | - `bq {app}` - activate alfred workflow, select app to pause audio/video. 23 | 24 | ## More workflows 25 | 26 | - 🔍 [alfred-browser-tabs](https://github.com/epilande/alfred-browser-tabs) - Search browser tabs from Chrome, Brave, & Safari. 27 | - 🔐 [alfred-wifi-password](https://github.com/epilande/alfred-wifi-password) - Get Wi-Fi password from Keychain. 28 | - 🗝 [alfred-password-generator](https://github.com/epilande/alfred-password-generator) - Workflow to generate passwords. 29 | - 🎨 [alfred-prettier-clipboard](https://github.com/epilande/alfred-prettier-clipboard) - Format code in your clipboard with Prettier. 30 | 31 | ## License 32 | 33 | [MIT License](https://oss.ninja/mit/epilande/) 34 | -------------------------------------------------------------------------------- /bq-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epilande/alfred-be-quiet/2b2774385225bb0d2d6e0ea74a4e6d8dc5503056/bq-preview.png -------------------------------------------------------------------------------- /src/app-filter.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env osascript -l JavaScript 2 | 3 | function run() { 4 | let apps = []; 5 | 6 | if (Application("Google Chrome").running()) { 7 | apps.push("Google Chrome"); 8 | } 9 | if (Application("Brave Browser").running()) { 10 | apps.push("Brave Browser"); 11 | } 12 | if (Application("Spotify").running()) { 13 | apps.push("Spotify"); 14 | } 15 | if (Application("Music").running()) { 16 | apps.push("Music"); 17 | } 18 | 19 | let items = apps.reduce((acc, app) => { 20 | acc.push({ 21 | title: `Be Quiet - ${app}`, 22 | subtitle: `Pause audio/video playing in ${app}`, 23 | arg: app, 24 | }); 25 | return acc; 26 | }, []); 27 | 28 | const allItem = { 29 | title: "Be Quiet - All", 30 | subtitle: `Pause all (${apps.join(", ")})`, 31 | arg: apps, 32 | }; 33 | 34 | return JSON.stringify({ items: [allItem, ...items] }); 35 | } 36 | -------------------------------------------------------------------------------- /src/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epilande/alfred-be-quiet/2b2774385225bb0d2d6e0ea74a4e6d8dc5503056/src/icon.png -------------------------------------------------------------------------------- /src/info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | bundleid 6 | com.epilande.be-quiet 7 | category 8 | Tools 9 | connections 10 | 11 | 14C39DFC-3D98-43F1-95AA-680B86383399 12 | 13 | 14 | destinationuid 15 | 7BBADF8E-762A-4EE6-B537-F4CFC0DD7B97 16 | modifiers 17 | 0 18 | modifiersubtext 19 | 20 | vitoclose 21 | 22 | 23 | 24 | 25 | createdby 26 | Emmanuel Pilande 27 | description 28 | Workflow to automatically pause audio/video playing 29 | disabled 30 | 31 | name 32 | Be Quiet 33 | objects 34 | 35 | 36 | config 37 | 38 | alfredfiltersresults 39 | 40 | alfredfiltersresultsmatchmode 41 | 0 42 | argumenttreatemptyqueryasnil 43 | 44 | argumenttrimmode 45 | 0 46 | argumenttype 47 | 1 48 | escaping 49 | 68 50 | keyword 51 | bq 52 | queuedelaycustom 53 | 3 54 | queuedelayimmediatelyinitially 55 | 56 | queuedelaymode 57 | 0 58 | queuemode 59 | 1 60 | runningsubtext 61 | 62 | script 63 | #!/usr/bin/env osascript -l JavaScript 64 | 65 | function run(args) { 66 | let apps = []; 67 | 68 | if (Application("Google Chrome").running()) { 69 | apps.push("Google Chrome"); 70 | } 71 | if (Application("Brave Browser").running()) { 72 | apps.push("Brave Browser"); 73 | } 74 | if (Application("Spotify").running()) { 75 | apps.push("Spotify"); 76 | } 77 | if (Application("Music").running()) { 78 | apps.push("Music"); 79 | } 80 | 81 | let items = apps.reduce((acc, app) => { 82 | acc.push({ 83 | title: `Be Quiet - ${app}`, 84 | subtitle: `Pause video/audio playing in ${app}`, 85 | arg: app, 86 | }); 87 | return acc; 88 | }, []); 89 | 90 | const allItem = { 91 | title: "Be Quiet - All", 92 | subtitle: `Pause all (${apps.join(", ")})`, 93 | arg: apps, 94 | }; 95 | 96 | return JSON.stringify({ items: [allItem, ...items] }); 97 | } 98 | 99 | scriptargtype 100 | 1 101 | scriptfile 102 | 103 | subtext 104 | 105 | title 106 | 107 | type 108 | 7 109 | withspace 110 | 111 | 112 | type 113 | alfred.workflow.input.scriptfilter 114 | uid 115 | 14C39DFC-3D98-43F1-95AA-680B86383399 116 | version 117 | 3 118 | 119 | 120 | config 121 | 122 | concurrently 123 | 124 | escaping 125 | 68 126 | script 127 | #!/usr/bin/env osascript -l JavaScript 128 | ObjC.import("stdlib"); 129 | 130 | function pause(app) { 131 | if (["Google Chrome", "Brave Browser"].includes(app)) { 132 | let urlRegex = /^(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&\/\/=]*)/; 133 | let browser = Application(app); 134 | browser.includeStandardAdditions = true; 135 | let windowCount = browser.windows.length; 136 | let tabsUrl = browser.windows.tabs.url(); 137 | 138 | for (let w = 0; w < windowCount; w++) { 139 | let window = browser.windows[w](); 140 | if (window) { 141 | for (let t = 0; t < tabsUrl[w].length; t++) { 142 | let tab = window.tabs[t](); 143 | let url = tabsUrl[w][t]; 144 | 145 | if (urlRegex.test(url)) { 146 | tab.url = ` 147 | javascript:(function () { 148 | let videoElements = document.getElementsByTagName("video"); 149 | let audioElements = document.getElementsByTagName("audio"); 150 | let pauseElements = document.querySelectorAll('button[title^="Pause"]'); 151 | let iframeElements = document.querySelectorAll('iframe'); 152 | 153 | if (videoElements.length > 0) { 154 | for (const element of videoElements) { 155 | element.pause(); 156 | } 157 | } 158 | 159 | if (audioElements.length > 0) { 160 | for (const element of audioElements) { 161 | element.pause(); 162 | } 163 | } 164 | 165 | if (pauseElements.length > 0) { 166 | for (const element of pauseElements) { 167 | element.click(); 168 | } 169 | } 170 | 171 | if (iframeElements.length > 0) { 172 | for (const element of iframeElements) { 173 | element.contentWindow.postMessage('{"event":"command","func":"pauseVideo","args":""}', '*'); 174 | element.contentWindow.postMessage('{"method":"pause"}', '*'); 175 | } 176 | } 177 | })(); 178 | `; 179 | } 180 | } 181 | } 182 | } 183 | } 184 | 185 | if (app === "Spotify") { 186 | let spotify = Application("Spotify"); 187 | spotify.pause(); 188 | } 189 | 190 | if (app === "Music") { 191 | let music = Application("Music"); 192 | music.pause(); 193 | } 194 | } 195 | 196 | function run(args) { 197 | let apps = args; 198 | 199 | for (const app of apps) { 200 | pause(app); 201 | } 202 | } 203 | scriptargtype 204 | 1 205 | scriptfile 206 | 207 | type 208 | 7 209 | 210 | type 211 | alfred.workflow.action.script 212 | uid 213 | 7BBADF8E-762A-4EE6-B537-F4CFC0DD7B97 214 | version 215 | 2 216 | 217 | 218 | readme 219 | Workflow to automatically pause audio/video playing. 220 | 221 | In order to pause audio/video in Google Chrome or Brave Browser, you must enable JavaScript from Apple Events: In the top menu click "View" -> "Developer" -> "Allow JavaScript from Apple Events". 222 | uidata 223 | 224 | 14C39DFC-3D98-43F1-95AA-680B86383399 225 | 226 | xpos 227 | 70 228 | ypos 229 | 60 230 | 231 | 7BBADF8E-762A-4EE6-B537-F4CFC0DD7B97 232 | 233 | xpos 234 | 345 235 | ypos 236 | 60 237 | 238 | 239 | variablesdontexport 240 | 241 | version 242 | 1.0.1 243 | webaddress 244 | https://github.com/epilande/alfred-be-quiet 245 | 246 | 247 | -------------------------------------------------------------------------------- /src/quiet-allowed-list.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env osascript -l JavaScript 2 | ObjC.import("stdlib"); 3 | 4 | let ALLOWED_URLS = [ 5 | "youtube", 6 | "soundcloud", 7 | "reddit", 8 | ...$.getenv("urls").split(/,\s?/).filter(Boolean), 9 | ]; 10 | 11 | function pause(app) { 12 | if (["Google Chrome", "Brave Browser"].includes(app)) { 13 | let browser = Application(app); 14 | browser.includeStandardAdditions = true; 15 | let windowCount = browser.windows.length; 16 | let tabsUrl = browser.windows.tabs.url(); 17 | 18 | for (let w = 0; w < windowCount; w++) { 19 | for (let t = 0; t < tabsUrl[w].length; t++) { 20 | let tab = browser.windows[w].tabs[t]; 21 | let url = tabsUrl[w][t]; 22 | 23 | if (ALLOWED_URLS.some((site) => url.includes(site))) { 24 | tab.execute({ 25 | javascript: ` 26 | (function () { 27 | let videoElements = document.getElementsByTagName("video"); 28 | let audioElements = document.getElementsByTagName("audio"); 29 | let soundcloudPause = document.querySelector('[title="Pause current"]'); 30 | 31 | if (videoElements.length > 0) { 32 | for (const element of videoElements) { 33 | element.pause(); 34 | } 35 | } 36 | if (audioElements.length > 0) { 37 | for (const element of audioElements) { 38 | element.pause(); 39 | } 40 | } 41 | if (soundcloudPause) { 42 | soundcloudPause.click(); 43 | } 44 | })(); 45 | `, 46 | }); 47 | } 48 | } 49 | } 50 | } 51 | 52 | if (app === "Spotify") { 53 | let spotify = Application("Spotify"); 54 | spotify.pause(); 55 | } 56 | 57 | if (app === "Music") { 58 | let music = Application("Music"); 59 | music.pause(); 60 | } 61 | } 62 | 63 | function run(args) { 64 | let apps = args; 65 | 66 | for (const app of apps) { 67 | pause(app); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/quiet.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env osascript -l JavaScript 2 | ObjC.import("stdlib"); 3 | 4 | function pause(app) { 5 | if (["Google Chrome", "Brave Browser"].includes(app)) { 6 | let urlRegex = /^(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&\/\/=]*)/; 7 | let browser = Application(app); 8 | browser.includeStandardAdditions = true; 9 | let windowCount = browser.windows.length; 10 | let tabsUrl = browser.windows.tabs.url(); 11 | 12 | for (let w = 0; w < windowCount; w++) { 13 | let window = browser.windows[w](); 14 | if (window) { 15 | for (let t = 0; t < tabsUrl[w].length; t++) { 16 | let tab = window.tabs[t](); 17 | let url = tabsUrl[w][t]; 18 | 19 | if (urlRegex.test(url)) { 20 | tab.url = ` 21 | javascript:(function () { 22 | let videoElements = document.getElementsByTagName("video"); 23 | let audioElements = document.getElementsByTagName("audio"); 24 | let pauseElements = document.querySelectorAll('button[title^="Pause"]'); 25 | let iframeElements = document.querySelectorAll('iframe'); 26 | 27 | if (videoElements.length > 0) { 28 | for (const element of videoElements) { 29 | element.pause(); 30 | } 31 | } 32 | 33 | if (audioElements.length > 0) { 34 | for (const element of audioElements) { 35 | element.pause(); 36 | } 37 | } 38 | 39 | if (pauseElements.length > 0) { 40 | for (const element of pauseElements) { 41 | element.click(); 42 | } 43 | } 44 | 45 | if (iframeElements.length > 0) { 46 | for (const element of iframeElements) { 47 | element.contentWindow.postMessage('{"event":"command","func":"pauseVideo","args":""}', '*'); 48 | element.contentWindow.postMessage('{"method":"pause"}', '*'); 49 | } 50 | } 51 | })(); 52 | `; 53 | } 54 | } 55 | } 56 | } 57 | } 58 | 59 | if (app === "Spotify") { 60 | let spotify = Application("Spotify"); 61 | spotify.pause(); 62 | } 63 | 64 | if (app === "Music") { 65 | let music = Application("Music"); 66 | music.pause(); 67 | } 68 | } 69 | 70 | function run(args) { 71 | let apps = args; 72 | 73 | for (const app of apps) { 74 | pause(app); 75 | } 76 | } 77 | --------------------------------------------------------------------------------