├── .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 |
--------------------------------------------------------------------------------