Compare commits

...

685 Commits

Author SHA1 Message Date
TC
f44b6f0c33 Bump version to 1.15.0 2021-12-30 19:39:20 +01:00
TC
c45e4e50fc Bump electron to 16.0.5 2021-12-30 19:39:03 +01:00
TC
9839a973f7 nit: re-format package.json 2021-12-30 19:35:53 +01:00
9ea967f03b Merge pull request #531 from th-ch/fix-tests
Switch from spectron to playwright to fix tests
2021-12-30 19:35:00 +01:00
TC
9d6765125b Switch from spectron to playwright to fix tests 2021-12-30 19:26:01 +01:00
TC
8d66735585 Add presets to FFmpeg in menu 2021-12-30 18:46:43 +01:00
14b4c55ce7 Merge pull request #529 from th-ch/snyk-upgrade-27f67e987dd094f8f1db19ad7f90c292
[Snyk] Upgrade @cliqz/adblocker-electron from 1.23.0 to 1.23.1
2021-12-30 17:59:53 +01:00
1d1f4bbcc3 Merge branch 'master' into snyk-upgrade-27f67e987dd094f8f1db19ad7f90c292 2021-12-30 17:59:34 +01:00
bd520c7eff Merge pull request #525 from Araxeus/fix-precise-volume-options-sync
fix precise-volume options sync
2021-12-30 17:57:13 +01:00
73e201bb2c Merge pull request #524 from MulverineX/patch-1
Add album art/thumbnail to discord activity
2021-12-30 15:10:07 +01:00
81b08917ae Merge pull request #521 from Araxeus/fix-skip-silences
fix skip-silences plugin
2021-12-30 15:08:56 +01:00
81c2ab34d9 Merge pull request #520 from th-ch/snyk-upgrade-7535be87da222abdba60d9fa36da34b5
[Snyk] Upgrade electron-updater from 4.6.2 to 4.6.3
2021-12-30 15:06:06 +01:00
TC
33faa2deb3 nit: improve comment for shared Array Buffer 2021-12-30 14:59:11 +01:00
TC
4d4ac56486 Ensure NODE_OPTIONS are unset in dev mode to avoid warning 2021-12-30 14:58:21 +01:00
56ac2b3b06 Merge pull request #515 from Araxeus/fix-useragents
update electron & remote & user agents
2021-12-30 14:57:36 +01:00
c72ea4bad5 fix: upgrade @cliqz/adblocker-electron from 1.23.0 to 1.23.1
Snyk has created this PR to upgrade @cliqz/adblocker-electron from 1.23.0 to 1.23.1.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=referral&page=upgrade-pr
2021-12-27 20:52:46 +00:00
d60069555e Merge pull request #513 from markbaas/master
fixes mpris bug in snap
2021-12-27 16:12:40 +01:00
ed7025b4a2 fix precise-volume options sync 2021-12-24 02:13:21 +02:00
5fbc0f8122 Add album art/thumbnail to discord activity 2021-12-23 11:36:11 -07:00
02a989ca07 fix unnecessary skips 2021-12-18 21:03:32 +02:00
7c6fe6748e fix: upgrade electron-updater from 4.6.2 to 4.6.3
Snyk has created this PR to upgrade electron-updater from 4.6.2 to 4.6.3.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=referral&page=upgrade-pr
2021-12-18 11:02:24 +00:00
8f2ed3039a add comment to useragent fix 2021-12-16 19:29:09 +02:00
baeebd1959 downloader fixes
* --experimental-wasm-bulk-memory
* SharedArrayBuffer
* getFolder from front
* ytdl-core 4.9.2
2021-12-16 19:15:55 +02:00
49edbf723f fix custom-electron-prompt & remote 2021-12-16 18:28:49 +02:00
3764ce9a7c update custom-electron-titlebar 2021-12-16 18:01:11 +02:00
46943520bd Merge branch 'master' into fix-useragents 2021-12-15 19:32:14 +02:00
1048b3f99a Merge pull request #519 from th-ch/skip-silences-plugin
Add "Skip silences" plugin
2021-12-14 23:31:14 +01:00
TC
b1e40271e6 nit: re-order dependencies 2021-12-14 23:17:06 +01:00
TC
11429978c9 Add plugin to skip silences 2021-12-14 23:16:41 +01:00
47ca6e0b1f use session.webRequest.onBeforeSendHeaders for useragent 2021-12-14 19:22:02 +02:00
a273f6f73c fix video toggle button appearing when in song mode 2021-12-14 00:34:32 +02:00
c8ba85be76 use firefox as falllback useragent 2021-12-14 00:34:10 +02:00
6633243628 use @rozzzly/custom-electron-titlebar 2021-12-13 21:10:59 +02:00
2fb47933ac fix useragents 2021-12-13 21:06:52 +02:00
4fd683ed23 update electron & remote module 2021-12-13 20:10:36 +02:00
e1e9748002 always on useragent 2021-12-13 01:11:13 +02:00
dd122666c5 update useragents 2021-12-13 00:56:13 +02:00
5483f0ee36 Merge pull request #510 from MiepHD/master
Aligned lyric design
2021-12-10 21:15:36 +01:00
2c6c80d829 Merge pull request #509 from Araxeus/mpris-urgent-fix
Fix mpris bugs - follows #480
2021-12-10 21:14:31 +01:00
584d3e83c6 fixes mpris bug in snap 2021-12-10 20:52:16 +01:00
58d5256dd2 1vw would fit perfectly 2021-12-05 17:41:59 +01:00
920d61a1c6 Aligned lyric design
I aligned the lyric to the normal lyrics so that the lyric isn't that tiny
2021-12-04 20:53:19 +01:00
c5c2d5b74c Hide cast button which doesn't work 2021-12-03 16:56:51 +02:00
2daee01ff7 Fix bugs from bad merge conflict solving
-fix missing songControls
-use player.seeked directly
-fix 'seeked' event listener
-fix e.target instead of e.detail in apiLoaded event
-fix document.querySelector('video') before apiLoaded
-setup timeChange Listener if linux+shortcuts enabled
2021-12-03 15:55:40 +02:00
d13c9b7ca6 Merge pull request #476 from Araxeus/mix-fixes
Various small fixes (discord, video-toggle, precise-volume, playback-speed, shortcuts, lyrics)
2021-12-02 21:11:28 +01:00
5296a88525 Merge pull request #480 from Araxeus/mpris+tuna-fix
Mpris + obs-tuna fixes
2021-12-02 21:03:03 +01:00
8ce4b5b297 lint 2021-12-01 21:44:48 +02:00
2c39c0efed Merge branch 'master' into mpris+tuna-fix 2021-12-01 20:11:59 +02:00
e917abaec9 fix merge error 2021-12-01 00:08:55 +02:00
bdd0a2e8db Merge branch 'master' into mix-fixes 2021-12-01 00:06:15 +02:00
362003e10e Merge pull request #498 from th-ch/snyk-upgrade-9d2eea8c019b6593f1ef01f7fe8f404b
[Snyk] Upgrade node-fetch from 2.6.5 to 2.6.6
2021-11-30 00:16:34 +01:00
4e4b557413 Merge pull request #491 from Araxeus/fix-blur
fix interaction between blur navbar & in-app-menu
2021-11-30 00:14:49 +01:00
3a068af925 Merge pull request #475 from th-ch/snyk-upgrade-55c8a0f6d6911f431ebf75ad846e8f6c
[Snyk] Upgrade @cliqz/adblocker-electron from 1.22.7 to 1.23.0
2021-11-30 00:12:21 +01:00
44ca812330 Merge pull request #488 from Rubecks/exponential-volume-plugin
New Plugin: Exponential Volume
2021-11-30 00:10:48 +01:00
74a69e1c7a Merge pull request #474 from th-ch/snyk-upgrade-267eeda31c348d529d38d5a6413ef858
[Snyk] Upgrade electron-updater from 4.6.0 to 4.6.1
2021-11-30 00:06:42 +01:00
c3ef16c3dd Merge pull request #477 from Araxeus/fix-loadeddata/metdata-events-rarely-not-firing
Fix loadeddata/metadata video events rarely not firing (+other small fixes)
2021-11-29 23:52:23 +01:00
8c5ac17cdf fix multiple songInfo calls on start 2021-11-23 18:53:04 +02:00
c99b95e611 use config.plugins.isEnabled 2021-11-22 22:52:38 +02:00
4362101c0a lint 2021-11-22 22:08:24 +02:00
7ba205cc6c fix backquotes in keybind prompt 2021-11-22 18:59:29 +02:00
abc1712cf7 fix counter prompt 2021-11-22 18:33:32 +02:00
92452f804f fix song-info-request 2021-11-22 18:20:12 +02:00
c76df84ce3 fix: upgrade node-fetch from 2.6.5 to 2.6.6
Snyk has created this PR to upgrade node-fetch from 2.6.5 to 2.6.6.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=referral&page=upgrade-pr
2021-11-22 04:09:33 +00:00
185ebbf417 fix downloader playlist download 2021-11-21 19:44:01 +02:00
6726e2600b rework songInfo pause listener 2021-11-14 23:49:19 +02:00
93e0664f95 fix height & blur of search page item header 2021-11-14 22:02:37 +02:00
bf45ed10aa fix #490 2021-11-14 21:45:34 +02:00
8da78d50c4 Exponential Volume Plugin 2021-11-12 17:58:39 -03:00
b27a959c2b fix video-toggle&precise-volume interaction 2021-11-12 18:42:58 +02:00
cfe719b6bd use native thumbnail without modifiers 2021-11-12 17:46:40 +02:00
071799c435 fix some shortcuts 2021-11-12 16:39:43 +02:00
87ee7ed83d lint video-toggle 2021-11-10 22:35:49 +02:00
08fdd07969 speed up sponsorblock 2021-11-10 20:44:13 +02:00
02d5b78f55 add songInfo.album 2021-11-10 20:11:45 +02:00
5492afe5f6 add catch to fetch 2021-11-10 19:08:19 +02:00
9a7baeac23 fix tuna time update 2021-11-10 18:45:42 +02:00
ccfe7434bf fix mpris 2021-11-10 18:23:55 +02:00
6dbed73e6b fix disable autoplay 2021-11-09 18:52:03 +02:00
895136af0a used youtube's videodatachange event 2021-11-09 17:57:06 +02:00
72b4398024 lint&fix video-toggle plugin 2021-11-09 15:17:26 +02:00
65ce62adc1 use $('video') srcChanged event instead of loadeddata/metadata 2021-11-09 13:29:41 +02:00
eafdd5046d fix lyric text size 2021-11-09 10:42:32 +02:00
bbece751c0 lint playback speed 2021-11-09 10:03:06 +02:00
719c244e32 fix #472 2021-11-09 10:01:33 +02:00
e70b41b256 fix: upgrade @cliqz/adblocker-electron from 1.22.7 to 1.23.0
Snyk has created this PR to upgrade @cliqz/adblocker-electron from 1.22.7 to 1.23.0.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=referral&page=upgrade-pr
2021-11-09 05:45:20 +00:00
f4b6fd53f3 fix: upgrade electron-updater from 4.6.0 to 4.6.1
Snyk has created this PR to upgrade electron-updater from 4.6.0 to 4.6.1.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=referral&page=upgrade-pr
2021-11-09 05:45:17 +00:00
f40ed04899 Merge pull request #467 from th-ch/snyk-upgrade-cfd310bb818846c87b2239715bd6d1c1
[Snyk] Upgrade custom-electron-prompt from 1.1.0 to 1.2.0
2021-11-07 15:56:27 +01:00
c897323be0 fix: upgrade custom-electron-prompt from 1.1.0 to 1.2.0
Snyk has created this PR to upgrade custom-electron-prompt from 1.1.0 to 1.2.0.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=referral&page=upgrade-pr
2021-11-02 06:46:27 +00:00
TC
d7c4716a6e Re-order plugins in readme 2021-11-01 22:49:34 +01:00
TC
461cac741b nit: add new line at end of file 2021-11-01 22:42:28 +01:00
TC
cee2e066b9 Fix apiLoaded event listener in video-toggle plugin 2021-11-01 22:42:03 +01:00
TC
953d6fe3e4 Migrate old "hide-video-player" plugin to new one 2021-11-01 22:40:26 +01:00
c592a26e42 Merge pull request #448 from Araxeus/new-hide-video-player
Video Toggle Plugin
2021-11-01 22:11:38 +01:00
62bacf76d0 change file names to lower case 2021-11-01 23:10:15 +02:00
fc254db010 Merge pull request #462 from Araxeus/fix-playback-speed-plugin
fix playback speed plugin
2021-11-01 22:10:02 +01:00
0287b69424 fix missing thumbnails
+forced push fix woupsie
2021-11-01 23:04:39 +02:00
ceebd99927 Merge pull request #465 from Araxeus/fix-sponsorblock-v2
Fix sponsorblock skipping when not needed
2021-11-01 21:54:05 +01:00
41285ac9fc fix slider not working on tap 2021-11-01 22:23:26 +02:00
7b9415033f use loadedmetadata instead of loadeddata 2021-11-01 20:03:57 +02:00
b1ffd93bc2 lint in-app-menu 2021-11-01 19:57:28 +02:00
64637a6ac5 Reset segments on song end 2021-11-01 19:57:15 +02:00
b83afa22d9 Merge remote-tracking branch 'upstream/master' into fix-playback-speed-plugin 2021-11-01 18:07:54 +02:00
c68daabeab lint audio-compressor plugin 2021-11-01 18:07:21 +02:00
9ec0830a65 Merge remote-tracking branch 'upstream/master' into new-hide-video-player 2021-11-01 17:59:35 +02:00
48943ee74b lint 2021-11-01 16:06:55 +02:00
29d5b3c7db lint 2021-11-01 16:02:27 +02:00
TC
3de574a2e4 Remove debug log from genius plugin + add a dev log 2021-11-01 12:13:22 +01:00
e765d18ab0 Merge pull request #463 from Araxeus/use-apiLoaded-event-for-time-update
Sponsorblock fix + use new apiLoaded event
2021-11-01 12:03:47 +01:00
4629759eec Merge pull request #458 from Araxeus/use-apiReady-on-audio-compressor-plugin
use apiLoaded event in audio-compressor plugin
2021-11-01 11:47:11 +01:00
a14d27da70 Merge pull request #456 from Araxeus/hide-menu-initial-alert
alert on initial hide-menu enabled
2021-11-01 11:44:27 +01:00
5256ffcf77 Merge pull request #451 from Araxeus/fix-blur-plugin
Blur plugin tweaks and integration with in-app-menu
2021-11-01 11:34:54 +01:00
e2bbc6abbc Merge pull request #449 from Araxeus/fix-resume-on-start
set resume on start url to songInfo.url
2021-11-01 11:30:03 +01:00
6243e6fd48 set max playback speed to 16 ;) 2021-10-30 15:56:12 +03:00
1434849142 simplify playback rate steps 2021-10-30 15:40:45 +03:00
9bd089adb0 add check for lowest playback speed (0.07) 2021-10-30 14:57:16 +03:00
6c1a4c0ac2 playbackSpeed wheel listener 2021-10-30 14:49:03 +03:00
d35fef82fe fix sponsoblock spam updating time 2021-10-30 13:30:38 +03:00
bca22d8e24 update electron to v12.2.2 fixes fetch CERT_HAS_EXPIRED error 2021-10-30 13:17:47 +03:00
754eac6ee0 use apiLoaded 'once'
lint
2021-10-30 12:53:14 +03:00
762566cce6 lint 2021-10-30 12:52:04 +03:00
286bc0113e fix sponsorblock plugin 2021-10-30 12:43:51 +03:00
62e8e673eb use apiLoaded 'once' 2021-10-30 12:17:32 +03:00
68996809f0 add apiLoaded event to disable-autoplay plugin 2021-10-30 12:05:03 +03:00
48a2a13163 fix playback speed plugin 2021-10-30 12:00:29 +03:00
6e94422b15 use apiLoaded event in audio-compressor plugin 2021-10-26 20:57:56 +03:00
713e005aa8 Update readme.md 2021-10-26 20:45:43 +03:00
3f3ab766ce Update readme.md 2021-10-26 20:34:44 +03:00
00e1bbf994 Update readme.md 2021-10-26 20:31:27 +03:00
b45adac847 Update readme.md 2021-10-26 20:03:01 +03:00
3d9b495863 Update readme.md 2021-10-26 19:50:15 +03:00
a70364facf lint 2021-10-26 19:23:10 +03:00
02cb39602f alert on initial hide-menu enabled 2021-10-26 19:07:10 +03:00
12c31725fe fixes scrollbar position relative to navbar depending on player-page-open 2021-10-25 22:45:11 +03:00
e0841060df fix comment typo 2021-10-25 20:20:52 +03:00
b7b55b5c83 Add blur to (library)header + make in-app-menu compatible 2021-10-25 00:43:42 +03:00
43a9093eb7 set resume on start url to songInfo.url 2021-10-24 21:17:45 +03:00
67e43bc0e3 Video Toggle Plugin 2021-10-24 20:09:30 +03:00
TC
a47c906ab2 Add audio compressor plugin to readme 2021-10-24 17:55:37 +02:00
TC
41a01ba58a Bump dev deps 2021-10-24 14:32:35 +02:00
TC
ca2bd011e2 Document quality changer plugin in readme 2021-10-24 14:08:47 +02:00
2106914aff Merge pull request #446 from Araxeus/quality-changer-plugin
quality-changer-plugin
2021-10-24 14:06:08 +02:00
1c11ddbb7d Merge branch 'master' into quality-changer-plugin 2021-10-24 14:03:40 +02:00
TC
fc1211f7a1 Bump version to 1.14.0 2021-10-24 14:02:13 +02:00
TC
18f87c7b0d Add migration for precise-volume plugin (globalShortcuts key) 2021-10-24 14:02:01 +02:00
TC
f9a4bffa55 Fix styling of store migrations 2021-10-24 14:01:29 +02:00
TC
02f4aabead Fix condition on query selector 2021-10-24 14:01:00 +02:00
f97dade168 Merge pull request #443 from Araxeus/songInfo-straight-from-youtube-api
get songInfo from youtube API
2021-10-24 13:42:59 +02:00
8c3a6472f8 Merge branch 'master' into songInfo-straight-from-youtube-api 2021-10-24 13:40:48 +02:00
315f9783f5 Merge pull request #442 from cdaydreamer/master
New plugin: Blur navigation bar
2021-10-24 13:34:45 +02:00
ada78837ce Merge branch 'master' into master 2021-10-24 13:34:15 +02:00
58c6a12e53 Merge pull request #440 from cpiber/discord-clean
Discord plugin: Clean Up Export (follow-up #380)
2021-10-24 13:29:34 +02:00
005c930d58 Merge pull request #434 from Araxeus/remove-upgrade-button
remove upgrade button + makes images unselectable
2021-10-24 13:28:43 +02:00
7c2891b732 Merge branch 'master' into remove-upgrade-button 2021-10-24 13:28:15 +02:00
8b36139dab Merge pull request #433 from Araxeus/new-auto-confirm
new auto confirm when paused
2021-10-24 12:44:19 +02:00
a045d65e58 Merge pull request #431 from itzmanish/fix/mpris
fix: mpris instance not registering itself and media controls
2021-10-24 12:42:31 +02:00
7b064c1e6f Merge pull request #288 from thymue/compressor-plugin
Audio compressor plugin
2021-10-24 12:17:28 +02:00
6a2e3ab6c1 Merge pull request #275 from Araxeus/precise-volume-HUD
precise-volume plugin fixes & updates
2021-10-24 11:40:54 +02:00
362da8c308 Merge pull request #243 from Araxeus/custom-electron-prompt
Custom Prompt for changing options
2021-10-24 11:22:20 +02:00
5658765f54 quality-changer-plugin 2021-10-24 02:24:25 +03:00
38449f003a use apiLoad event 2021-10-23 18:28:38 +03:00
df75e480a6 use apiLoad event 2021-10-23 18:27:35 +03:00
79a95f133b use apiLoad event 2021-10-23 18:22:17 +03:00
9b1a5b8d26 clarify var names in cleanupName() 2021-10-23 17:18:58 +03:00
bb2e1bd616 use youtube native api to change volume 2021-10-23 16:57:44 +03:00
2d518abc19 remove leftover console.log 2021-10-23 16:26:45 +03:00
978aca1f9a use loadeddata instead of loadedmetadata
send event closer to actual initial start time of video
2021-10-23 16:21:42 +03:00
2224786478 lint 2021-10-23 16:19:45 +03:00
51364b63e7 get songInfo from youtube API 2021-10-23 16:13:06 +03:00
c897bedd90 New plugin: Blur navigation bar 2021-10-23 15:38:46 +03:00
831b1ea8e1 access window._lact directly 2021-10-23 15:21:49 +03:00
4d4dacbc71 discord: clean up export (follow-up #380) 2021-10-19 18:40:27 +02:00
1cd4f53657 Merge branch 'master' into custom-electron-prompt 2021-10-19 15:55:00 +03:00
2eda0e4948 Merge branch 'master' into new-auto-confirm 2021-10-19 15:19:58 +03:00
f2e04f9170 Merge pull request #412 from th-ch/snyk-upgrade-3ac551b4f96cf4c26f3157f42a916769
[Snyk] Upgrade async-mutex from 0.3.1 to 0.3.2
2021-10-18 23:34:29 +02:00
c92b3915d9 Merge pull request #414 from th-ch/dependabot/npm_and_yarn/tmpl-1.0.5
build(deps): bump tmpl from 1.0.4 to 1.0.5
2021-10-18 23:31:59 +02:00
6118a17b08 Merge pull request #416 from th-ch/snyk-upgrade-d31d43c15fe12eab06e73b6f8faeda29
[Snyk] Upgrade node-fetch from 2.6.1 to 2.6.2
2021-10-18 23:30:04 +02:00
1490c0f179 Merge pull request #429 from th-ch/snyk-upgrade-129504aaea5df4956b1cd910a6775c4a
[Snyk] Upgrade @cliqz/adblocker-electron from 1.22.5 to 1.22.6
2021-10-18 23:23:27 +02:00
fdf203e70a Merge pull request #430 from th-ch/dependabot/npm_and_yarn/electron-12.1.0
build(deps-dev): bump electron from 12.0.8 to 12.1.0
2021-10-18 23:18:29 +02:00
8114a28964 Merge pull request #380 from cpiber/discord
Fix discord clearActivity, menu, listen along option
2021-10-18 23:10:24 +02:00
663507b3f8 fix: player status when play at start 2021-10-17 11:06:26 +05:30
79d0c7b666 fix: typo 2021-10-17 10:54:05 +05:30
ce4580605d remove upgrade button + makes img unselectable 2021-10-15 15:40:34 +03:00
dda18a72af new auto confirm when paused 2021-10-15 14:38:09 +03:00
f7a1de05c8 defensive coding 2021-10-15 05:41:47 +03:00
361606427a fix: remove unused play pause functions 2021-10-14 10:18:05 +05:30
81fb5118aa fix: don't create play pause method unneccessarily 2021-10-14 10:16:36 +05:30
a76f12c01c feat: add play and pause seperate song controller. 2021-10-13 12:27:13 +05:30
88ee0fb989 fix: mpris was not registering itself before.
Sorry I missed that somehow, because playerctl controls were working.
That was because of chromium was also registering itself for mpris.
2021-10-13 12:26:38 +05:30
3ec49bca74 build(deps-dev): bump electron from 12.0.8 to 12.1.0
Bumps [electron](https://github.com/electron/electron) from 12.0.8 to 12.1.0.
- [Release notes](https://github.com/electron/electron/releases)
- [Changelog](https://github.com/electron/electron/blob/main/docs/breaking-changes.md)
- [Commits](https://github.com/electron/electron/compare/v12.0.8...v12.1.0)

---
updated-dependencies:
- dependency-name: electron
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-12 22:10:41 +00:00
1908921ae6 fix: upgrade @cliqz/adblocker-electron from 1.22.5 to 1.22.6
Snyk has created this PR to upgrade @cliqz/adblocker-electron from 1.22.5 to 1.22.6.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=referral&page=upgrade-pr
2021-10-11 21:23:58 +00:00
b9dbd8bd4d Allow disable listen along (#426) 2021-10-11 15:02:24 +02:00
587818b91e Add type, clear on close 2021-10-05 10:10:36 +02:00
157ae05f80 fix: upgrade node-fetch from 2.6.1 to 2.6.2
Snyk has created this PR to upgrade node-fetch from 2.6.1 to 2.6.2.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=referral&page=upgrade-pr
2021-09-27 19:13:33 +00:00
f2039e29e7 build(deps): bump tmpl from 1.0.4 to 1.0.5
Bumps [tmpl](https://github.com/daaku/nodejs-tmpl) from 1.0.4 to 1.0.5.
- [Release notes](https://github.com/daaku/nodejs-tmpl/releases)
- [Commits](https://github.com/daaku/nodejs-tmpl/commits/v1.0.5)

---
updated-dependencies:
- dependency-name: tmpl
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-21 21:12:33 +00:00
ea2d33c3cf fix: upgrade async-mutex from 0.3.1 to 0.3.2
Snyk has created this PR to upgrade async-mutex from 0.3.1 to 0.3.2.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=upgrade-pr
2021-09-21 03:24:11 +00:00
d775e3d588 Merge pull request #406 from th-ch/snyk-upgrade-3c48abddd3d31d59f08172a32b5dd378
[Snyk] Upgrade @cliqz/adblocker-electron from 1.22.4 to 1.22.5
2021-09-19 16:13:09 +02:00
e7ec15e90f fix: upgrade @cliqz/adblocker-electron from 1.22.4 to 1.22.5
Snyk has created this PR to upgrade @cliqz/adblocker-electron from 1.22.4 to 1.22.5.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=upgrade-pr
2021-09-16 00:27:20 +00:00
TC
403470be69 Bump version 2021-09-15 22:59:06 +02:00
6dc0ba74c4 Merge pull request #384 from konhi/useragent
Fix incorrect Google alert caused by changing user agent coresponding to current platform
2021-09-15 22:54:10 +02:00
TC
6dcfb336c2 Fix missing import in shortcuts plugin 2021-09-15 22:51:58 +02:00
84516b2ac1 Merge pull request #401 from th-ch/snyk-upgrade-4b273faf3969a8ede6309124c5ce3e75
[Snyk] Upgrade electron-updater from 4.4.3 to 4.4.6
2021-09-15 22:50:15 +02:00
57cf2a8cdd fix: upgrade electron-updater from 4.4.3 to 4.4.6
Snyk has created this PR to upgrade electron-updater from 4.4.3 to 4.4.6.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=upgrade-pr
2021-09-13 01:17:33 +00:00
e3ae97fec4 Merge pull request #370 from th-ch/snyk-upgrade-eeb9d7e7304322861f42850feedf7f74
[Snyk] Upgrade electron-updater from 4.4.0 to 4.4.1
2021-09-12 23:10:47 +02:00
TC
ee76e2cb45 Merge branch 'master' of github.com:th-ch/youtube-music into snyk-upgrade-eeb9d7e7304322861f42850feedf7f74
* 'master' of github.com:th-ch/youtube-music:
  Bump node to v14
  nit: fix code style for tuna obs (+ typo)
  add tuna plugin for obs
  Add mpris support
  Update menu buttons to new format
  Bump jszip from 3.5.0 to 3.7.1
  Add Genius lyrics plugin
  Apply clean up util to title + enrich prefixes
  Bump node to v14
  fix: upgrade @cliqz/adblocker-electron from 1.22.2 to 1.22.3
  Add "Listen Along" button
  sort alphabetically
  suggestions from @Araxeus and @cpiber
  Fix broken link
  update descriptions and add images
  List missing plugins
  Bump path-parse from 1.0.6 to 1.0.7
2021-09-12 23:03:23 +02:00
de01bb6e75 Merge pull request #375 from th-ch/dependabot/npm_and_yarn/path-parse-1.0.7
Bump path-parse from 1.0.6 to 1.0.7
2021-09-12 23:02:52 +02:00
TC
42668c3e99 Merge branch 'master' of github.com:th-ch/youtube-music into dependabot/npm_and_yarn/path-parse-1.0.7
* 'master' of github.com:th-ch/youtube-music:
  Bump node to v14
  nit: fix code style for tuna obs (+ typo)
  add tuna plugin for obs
  Add mpris support
  Update menu buttons to new format
  Bump jszip from 3.5.0 to 3.7.1
  Add Genius lyrics plugin
  Apply clean up util to title + enrich prefixes
  Bump node to v14
  fix: upgrade @cliqz/adblocker-electron from 1.22.2 to 1.22.3
  Add "Listen Along" button
  sort alphabetically
  suggestions from @Araxeus and @cpiber
  Fix broken link
  update descriptions and add images
  List missing plugins
2021-09-12 22:55:08 +02:00
05f3c56e47 Merge pull request #385 from th-ch/snyk-upgrade-f132bed3bdbd1c212a2d1580af9a445a
[Snyk] Upgrade @cliqz/adblocker-electron from 1.22.2 to 1.22.3
2021-09-12 22:53:39 +02:00
TC
d54977b9ee Bump node to v14 2021-09-12 22:42:31 +02:00
b89fb4dc2f Merge pull request #388 from th-ch/dependabot/npm_and_yarn/jszip-3.7.1
Bump jszip from 3.5.0 to 3.7.1
2021-09-12 22:33:50 +02:00
a0cf77edfb Merge pull request #382 from konhi/patch-1
List missing plugins
2021-09-12 22:20:53 +02:00
TC
069f9855d1 nit: fix code style for tuna obs (+ typo) 2021-09-12 22:17:54 +02:00
e3e0775401 Merge pull request #397 from mesmerx/master
add tuna plugin for obs
2021-09-12 22:16:22 +02:00
d255e5ffe1 Merge pull request #389 from th-ch/fix-menu-buttons
Update menu buttons to new format
2021-09-12 22:13:46 +02:00
fea460a374 Merge pull request #387 from th-ch/lyrics-genius-plugin
Plugin to fetch lyrics from Genius
2021-09-12 22:10:43 +02:00
302d3f693f add tuna plugin for obs 2021-09-08 22:45:49 -03:00
9cc320d74b Merge pull request #395 from itzmanish/feat/mpris-support
Add mpris support with cherry picked commit from previous PR https://github.com/th-ch/youtube-music/pull/394
2021-09-07 00:12:34 +02:00
e255777283 Add mpris support 2021-09-02 16:00:42 +05:30
ef66612cc8 Discord show error dialog on reconnect error 2021-08-27 17:07:23 +02:00
4bed835347 Merge branch 'master' of https://github.com/th-ch/youtube-music into discord 2021-08-27 16:37:50 +02:00
b5fd6b4969 Discord add reconnecting functionality
Clear rpc on disconnect
Add menu button to reconnect
2021-08-27 16:32:55 +02:00
fe0f213919 Merge pull request #383 from konhi/discord
Add "Listen Along" button, solve #353
2021-08-23 01:19:00 +02:00
TC
e888b5c896 Update menu buttons to new format 2021-08-23 01:17:29 +02:00
f27ff52689 Bump jszip from 3.5.0 to 3.7.1
Bumps [jszip](https://github.com/Stuk/jszip) from 3.5.0 to 3.7.1.
- [Release notes](https://github.com/Stuk/jszip/releases)
- [Changelog](https://github.com/Stuk/jszip/blob/master/CHANGES.md)
- [Commits](https://github.com/Stuk/jszip/compare/v3.5.0...v3.7.1)

---
updated-dependencies:
- dependency-name: jszip
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-22 23:16:24 +00:00
TC
acbe0ac25d Add Genius lyrics plugin 2021-08-23 01:12:53 +02:00
TC
c66ff2bf05 Apply clean up util to title + enrich prefixes 2021-08-23 01:12:53 +02:00
d089487aa8 Merge pull request #386 from th-ch/bump-node-version
Bump node to v14
2021-08-23 01:12:27 +02:00
TC
6bc1d1606f Bump node to v14 2021-08-23 01:05:45 +02:00
9df5d921c7 Chrome -> Firefox, simplified using electron-is
suggestions by @Araxeus
2021-08-20 12:33:46 +02:00
4b1dfa1173 fix: upgrade @cliqz/adblocker-electron from 1.22.2 to 1.22.3
Snyk has created this PR to upgrade @cliqz/adblocker-electron from 1.22.2 to 1.22.3.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=upgrade-pr
2021-08-19 21:20:28 +00:00
f98318e737 Add platform-based user-agent 2021-08-19 16:49:34 +02:00
7fa1278b31 Add "Listen Along" button 2021-08-19 16:16:49 +02:00
878ec1f6c1 sort alphabetically 2021-08-19 15:09:31 +02:00
086048780a suggestions from @Araxeus and @cpiber 2021-08-19 15:04:47 +02:00
65eaaecae5 Merge branch 'master' into custom-electron-prompt 2021-08-19 13:44:42 +03:00
aff0415816 Fix broken link
Oops.
2021-08-19 00:15:25 +02:00
6040fe1cbd update descriptions and add images 2021-08-19 00:08:17 +02:00
36bc9c62b0 Discord timeout 0 clear activity directly 2021-08-18 21:39:40 +02:00
3901457218 Discord add menu button for clearing activity 2021-08-17 10:09:47 +02:00
52f4e9d796 List missing plugins 2021-08-16 18:49:15 +02:00
183bad43f6 Fix discord clearActivity, menu
The callback sends multiple events, in particular two pause when going to the
next song, so the timeout wasn't properly cleared.
Add menu buttons for the two options
2021-08-15 12:25:00 +02:00
09fe80cae7 Bump path-parse from 1.0.6 to 1.0.7
Bumps [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/jbgutierrez/path-parse/releases)
- [Commits](https://github.com/jbgutierrez/path-parse/commits/v1.0.7)

---
updated-dependencies:
- dependency-name: path-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-12 03:24:48 +00:00
817b48dc9d fix: upgrade electron-updater from 4.4.0 to 4.4.1
Snyk has created this PR to upgrade electron-updater from 4.4.0 to 4.4.1.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=upgrade-pr
2021-08-06 21:42:35 +00:00
c6f8c42c45 Merge pull request #350 from th-ch/snyk-upgrade-4809caaf0847354b9b537aa0f8a0999d
[Snyk] Upgrade electron-updater from 4.3.9 to 4.3.10
2021-08-06 23:23:12 +02:00
TC
0535686129 Merge branch 'master' of github.com:th-ch/youtube-music into snyk-upgrade-4809caaf0847354b9b537aa0f8a0999d
# By TC (1) and snyk-bot (1)
# Via GitHub (1) and TC (1)
* 'master' of github.com:th-ch/youtube-music:
  Bump ytdl/ytpl
  fix: upgrade chokidar from 3.5.1 to 3.5.2
2021-08-06 23:22:44 +02:00
53a77255ca Merge pull request #354 from th-ch/snyk-upgrade-1a48119c8989bb533950b3384b3bce29
[Snyk] Upgrade chokidar from 3.5.1 to 3.5.2
2021-08-06 23:17:51 +02:00
TC
c01506dc44 Bump ytdl/ytpl 2021-08-06 23:12:29 +02:00
a49817fdc3 Merge branch 'master' into custom-electron-prompt 2021-07-20 11:06:08 +03:00
52a4608d76 create options.global if needed 2021-07-20 10:57:32 +03:00
6f5f9386ff fix: upgrade chokidar from 3.5.1 to 3.5.2
Snyk has created this PR to upgrade chokidar from 3.5.1 to 3.5.2.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=upgrade-pr
2021-07-07 05:50:15 +00:00
fddd0607e6 fix: upgrade electron-updater from 4.3.9 to 4.3.10
Snyk has created this PR to upgrade electron-updater from 4.3.9 to 4.3.10.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=upgrade-pr
2021-07-02 06:17:28 +00:00
TC
2cb6f56feb Bump version 2021-07-01 22:43:35 +02:00
TC
46285a5ed0 Bump glob-parent and add lint script (xo) 2021-06-27 21:19:33 +02:00
TC
496836b33b Bump dependencies to fix vulnerabilities 2021-06-27 21:12:28 +02:00
af127879a5 Merge pull request #339 from th-ch/fix-downloader-plugin
Fix downloader plugin
2021-06-27 21:10:35 +02:00
TC
38ef452801 Bump ffmpeg version 2021-06-27 20:46:48 +02:00
TC
a9a5d99676 Do not add network filters in adblocker cache to fix session enhancing 2021-06-27 20:46:48 +02:00
TC
e5ab50cebd Override content security policy to allow FFmpeg worker 2021-06-27 20:46:46 +02:00
TC
49194f8141 nit: re-order dependencies 2021-06-27 20:39:05 +02:00
TC
641ae27efd Update ytdl-core and ytpl 2021-06-27 20:38:08 +02:00
47a5dec465 Merge pull request #337 from th-ch/snyk-upgrade-fe663d0d7c5fc658f327e05ae5966f76
[Snyk] Upgrade @cliqz/adblocker-electron from 1.22.0 to 1.22.1
2021-06-25 23:11:25 +02:00
c93eabb400 Merge pull request #249 from Araxeus/update-in-app-menu
Update and simplify in-app-menu
2021-06-25 23:03:05 +02:00
664be51de2 Merge branch 'master' into custom-electron-prompt 2021-06-25 12:21:40 +03:00
492a47321d Merge branch 'master' into update-in-app-menu 2021-06-25 12:19:39 +03:00
c89f6af8c6 fix: upgrade @cliqz/adblocker-electron from 1.22.0 to 1.22.1
Snyk has created this PR to upgrade @cliqz/adblocker-electron from 1.22.0 to 1.22.1.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=upgrade-pr
2021-06-25 04:53:24 +00:00
9687c6c8e4 Merge pull request #331 from th-ch/dependabot/npm_and_yarn/hosted-git-info-2.8.9
Bump hosted-git-info from 2.8.8 to 2.8.9
2021-06-24 22:06:27 +02:00
ef0a89126a Merge pull request #330 from th-ch/dependabot/npm_and_yarn/lodash-4.17.21
Bump lodash from 4.17.20 to 4.17.21
2021-06-24 22:05:46 +02:00
8ce71d628d Bump lodash from 4.17.20 to 4.17.21
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.20 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.20...4.17.21)

---
updated-dependencies:
- dependency-name: lodash
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-24 20:04:35 +00:00
ca95d105c8 Merge pull request #328 from th-ch/snyk-upgrade-dacfb8f0b574367961e405e4912a4659
[Snyk] Upgrade ytdl-core from 4.8.0 to 4.8.2
2021-06-24 22:04:32 +02:00
12568c2b09 Merge pull request #324 from th-ch/snyk-upgrade-f650e2139b30a36951a4a00ed1b78d70
[Snyk] Upgrade electron-updater from 4.3.8 to 4.3.9
2021-06-24 22:03:15 +02:00
82abb4d4d3 Merge pull request #323 from th-ch/dependabot/npm_and_yarn/normalize-url-4.5.1
Bump normalize-url from 4.5.0 to 4.5.1
2021-06-24 22:02:52 +02:00
3c0a5dbbe5 Merge pull request #320 from th-ch/dependabot/npm_and_yarn/trim-newlines-3.0.1
Bump trim-newlines from 3.0.0 to 3.0.1
2021-06-24 22:02:33 +02:00
0b98eef06f Merge pull request #317 from th-ch/snyk-upgrade-a785f5d95c7765e2d47737e150a2263d
[Snyk] Upgrade @ffmpeg/core from 0.9.0 to 0.10.0
2021-06-24 22:02:05 +02:00
TC
18e69c9f2a Merge branch 'master' of github.com:th-ch/youtube-music into snyk-upgrade-a785f5d95c7765e2d47737e150a2263d
# By Araxeus (2) and snyk-bot (2)
# Via GitHub (3) and Araxeus (1)
* 'master' of github.com:th-ch/youtube-music:
  check if native image is empty before writing id tag
  fix unsupported hidden webp coverart
  fix: upgrade @ffmpeg/ffmpeg from 0.9.8 to 0.10.0
  fix: upgrade custom-electron-titlebar from 3.2.6 to 3.2.7
2021-06-24 22:01:16 +02:00
8f5d06d420 Merge pull request #316 from th-ch/snyk-upgrade-6e93904bf885d198521c6a5d8110bde3
[Snyk] Upgrade @ffmpeg/ffmpeg from 0.9.8 to 0.10.0
2021-06-24 22:00:07 +02:00
8a299461a0 Merge pull request #311 from th-ch/snyk-upgrade-44a5db26689d4091f6a2c3c3c69b869a
[Snyk] Upgrade custom-electron-titlebar from 3.2.6 to 3.2.7
2021-06-24 21:49:53 +02:00
0c58bec921 Bump hosted-git-info from 2.8.8 to 2.8.9
Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.8 to 2.8.9.
- [Release notes](https://github.com/npm/hosted-git-info/releases)
- [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md)
- [Commits](https://github.com/npm/hosted-git-info/compare/v2.8.8...v2.8.9)

---
updated-dependencies:
- dependency-name: hosted-git-info
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-19 20:29:37 +00:00
e0cb132686 Merge pull request #318 from Araxeus/fix-hidden-webp-cover-art
fix hidden webp thumbnail throwing MIME type error in downloader
2021-06-19 22:28:59 +02:00
2a192f39f9 fix: upgrade ytdl-core from 4.8.0 to 4.8.2
Snyk has created this PR to upgrade ytdl-core from 4.8.0 to 4.8.2.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=upgrade-pr
2021-06-19 05:30:04 +00:00
b7ebb7d499 fix: upgrade electron-updater from 4.3.8 to 4.3.9
Snyk has created this PR to upgrade electron-updater from 4.3.8 to 4.3.9.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=upgrade-pr
2021-06-12 04:23:30 +00:00
fffeac21b7 Bump normalize-url from 4.5.0 to 4.5.1
Bumps [normalize-url](https://github.com/sindresorhus/normalize-url) from 4.5.0 to 4.5.1.
- [Release notes](https://github.com/sindresorhus/normalize-url/releases)
- [Commits](https://github.com/sindresorhus/normalize-url/commits)

---
updated-dependencies:
- dependency-name: normalize-url
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-11 04:46:20 +00:00
4387cb485d Bump trim-newlines from 3.0.0 to 3.0.1
Bumps [trim-newlines](https://github.com/sindresorhus/trim-newlines) from 3.0.0 to 3.0.1.
- [Release notes](https://github.com/sindresorhus/trim-newlines/releases)
- [Commits](https://github.com/sindresorhus/trim-newlines/commits)

---
updated-dependencies:
- dependency-name: trim-newlines
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-09 18:57:55 +00:00
2a58dc823a check if native image is empty before writing id tag 2021-06-09 20:05:14 +03:00
8eb38271ff fix unsupported hidden webp coverart 2021-06-09 19:53:04 +03:00
1987ad1d4f fix: upgrade @ffmpeg/core from 0.9.0 to 0.10.0
Snyk has created this PR to upgrade @ffmpeg/core from 0.9.0 to 0.10.0.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=upgrade-pr
2021-06-08 04:08:44 +00:00
cc4dae60ef fix: upgrade @ffmpeg/ffmpeg from 0.9.8 to 0.10.0
Snyk has created this PR to upgrade @ffmpeg/ffmpeg from 0.9.8 to 0.10.0.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=upgrade-pr
2021-06-08 04:08:41 +00:00
1943116aa1 fix: upgrade custom-electron-titlebar from 3.2.6 to 3.2.7
Snyk has created this PR to upgrade custom-electron-titlebar from 3.2.6 to 3.2.7.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=upgrade-pr
2021-06-05 03:53:12 +00:00
3485d26b11 Merge pull request #308 from th-ch/sponsorblock-plugin
Add Sponsorblock plugin
2021-06-04 22:25:26 +02:00
TC
4a60aa9f20 Keep segments when skipping 2021-06-03 22:15:36 +02:00
TC
cda07c9675 Update adblocking 2021-06-03 22:04:49 +02:00
TC
ca64a77ed0 Add SponsorBlock plugin 2021-06-03 21:47:26 +02:00
TC
30e94d1d6f Refactor videoElement getter into a provider with callback 2021-06-03 21:45:28 +02:00
TC
b8c6ebfa53 Set test environment per test file 2021-06-03 21:43:07 +02:00
b26748ded8 Merge pull request #305 from th-ch/snyk-upgrade-15656c519a90f5bc0f5c8742a9fb04e9
[Snyk] Upgrade @ffmpeg/ffmpeg from 0.9.7 to 0.9.8
2021-05-31 22:06:17 +02:00
f186da0834 fix: upgrade @ffmpeg/ffmpeg from 0.9.7 to 0.9.8
Snyk has created this PR to upgrade @ffmpeg/ffmpeg from 0.9.7 to 0.9.8.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=upgrade-pr
2021-05-29 04:38:22 +00:00
c52c2d886a Merge pull request #303 from th-ch/dependabot/npm_and_yarn/ws-7.4.6
Bump ws from 7.4.3 to 7.4.6
2021-05-28 23:40:55 +02:00
e5dc1f8a58 Bump ws from 7.4.3 to 7.4.6
Bumps [ws](https://github.com/websockets/ws) from 7.4.3 to 7.4.6.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/7.4.3...7.4.6)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-28 21:39:25 +00:00
6dbf4134de Merge pull request #301 from th-ch/dependabot/npm_and_yarn/browserslist-4.16.6
Bump browserslist from 4.16.3 to 4.16.6
2021-05-28 23:38:48 +02:00
e1cc49a74d Merge pull request #300 from th-ch/snyk-upgrade-ce2777733d9dee231391c0a822c24a84
[Snyk] Upgrade @cliqz/adblocker-electron from 1.20.4 to 1.20.5
2021-05-28 23:35:11 +02:00
TC
4489a400b7 Merge branch 'master' of github.com:th-ch/youtube-music into snyk-upgrade-ce2777733d9dee231391c0a822c24a84
# By Araxeus (5) and others
# Via GitHub (5) and TC (2)
* 'master' of github.com:th-ch/youtube-music:
  Bump electron to 12.0.8
  fix: upgrade ytdl-core from 4.5.0 to 4.7.0
  fix: upgrade @ffmpeg/core from 0.8.5 to 0.9.0
  fix notificationOnUnpause option
  fix: upgrade filenamify from 4.2.0 to 4.3.0
  switch to `registerCallback()` on song info
  fix: upgrade ytpl from 2.1.1 to 2.2.0
  lint
  refactor notifications plugin
  setup SongInfo **once**
2021-05-28 23:33:20 +02:00
28aa1c0b22 Merge pull request #299 from th-ch/snyk-upgrade-37e43892f5a34f935e8486a98b3598fb
[Snyk] Upgrade ytdl-core from 4.5.0 to 4.7.0
2021-05-28 23:32:29 +02:00
TC
c540788d20 Merge branch 'master' of github.com:th-ch/youtube-music into snyk-upgrade-37e43892f5a34f935e8486a98b3598fb
# By Araxeus (5) and others
# Via GitHub (4) and TC (1)
* 'master' of github.com:th-ch/youtube-music:
  Bump electron to 12.0.8
  fix: upgrade @ffmpeg/core from 0.8.5 to 0.9.0
  fix notificationOnUnpause option
  fix: upgrade filenamify from 4.2.0 to 4.3.0
  switch to `registerCallback()` on song info
  fix: upgrade ytpl from 2.1.1 to 2.2.0
  lint
  refactor notifications plugin
  setup SongInfo **once**
2021-05-28 23:32:05 +02:00
4ab07dc875 Merge pull request #298 from th-ch/snyk-upgrade-d305cdce94133f6db9615e7b16007c87
[Snyk] Upgrade @ffmpeg/core from 0.8.5 to 0.9.0
2021-05-28 23:28:26 +02:00
5033de13ef Merge pull request #293 from th-ch/snyk-upgrade-335ec370c7caa5a759c54a46b2e27cf5
[Snyk] Upgrade filenamify from 4.2.0 to 4.3.0
2021-05-28 23:23:49 +02:00
55a8787a16 Merge pull request #285 from th-ch/snyk-upgrade-7b9c569f5bdd092f76adf7d412b0eea1
[Snyk] Upgrade ytpl from 2.1.1 to 2.2.0
2021-05-28 23:21:51 +02:00
3515bf364d Merge pull request #269 from Araxeus/fix-XHR-duplicate-callback
fix song-info callback duplication
2021-05-28 23:18:10 +02:00
TC
d8f3246e46 Bump electron to 12.0.8 2021-05-28 23:13:07 +02:00
cd613aaba2 Bump browserslist from 4.16.3 to 4.16.6
Bumps [browserslist](https://github.com/browserslist/browserslist) from 4.16.3 to 4.16.6.
- [Release notes](https://github.com/browserslist/browserslist/releases)
- [Changelog](https://github.com/browserslist/browserslist/blob/main/CHANGELOG.md)
- [Commits](https://github.com/browserslist/browserslist/compare/4.16.3...4.16.6)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-27 02:56:37 +00:00
c5f84b568b fix: upgrade @cliqz/adblocker-electron from 1.20.4 to 1.20.5
Snyk has created this PR to upgrade @cliqz/adblocker-electron from 1.20.4 to 1.20.5.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=upgrade-pr
2021-05-26 04:36:08 +00:00
14dc78984f fix: upgrade ytdl-core from 4.5.0 to 4.7.0
Snyk has created this PR to upgrade ytdl-core from 4.5.0 to 4.7.0.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=upgrade-pr
2021-05-26 04:36:06 +00:00
fb61dbfa6c fix: upgrade @ffmpeg/core from 0.8.5 to 0.9.0
Snyk has created this PR to upgrade @ffmpeg/core from 0.8.5 to 0.9.0.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=upgrade-pr
2021-05-26 04:36:03 +00:00
33855f17dd update refreshMenu() function 2021-05-23 17:57:54 +03:00
8124623142 fix notificationOnUnpause option 2021-05-22 18:38:36 +03:00
e99c91ce6e get rid of (hopefully) unnecessary watchDOMElement 2021-05-19 21:50:40 +02:00
177ce5721f fix: upgrade filenamify from 4.2.0 to 4.3.0
Snyk has created this PR to upgrade filenamify from 4.2.0 to 4.3.0.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=upgrade-pr
2021-05-19 05:31:34 +00:00
4fb0b1dd08 switch to registerCallback() on song info 2021-05-19 00:22:12 +03:00
177ad2ce7c minify firstRun() 2021-05-18 19:20:09 +03:00
9b88769585 remove leftover console.log 2021-05-18 19:02:56 +03:00
fd044072a1 use front load event instead of webcontents.did-finish-load 2021-05-18 18:53:00 +03:00
bae5155e19 use let and const instead of var 2021-05-17 13:42:27 +02:00
1e2085b990 compressor plugin 2021-05-17 12:33:43 +02:00
bbe5a7d50b fix: upgrade ytpl from 2.1.1 to 2.2.0
Snyk has created this PR to upgrade ytpl from 2.1.1 to 2.2.0.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=upgrade-pr
2021-05-17 04:44:57 +00:00
e5473cdfe4 lint 2021-05-17 00:07:28 +03:00
9c7a70e056 use .toFixed(2) on volume decimals 2021-05-16 23:50:59 +03:00
5bc8e86353 Merge pull request #270 from Araxeus/fix-normal-notification-showing-appID
fix notification showing appID instead of app name on windows
2021-05-16 22:46:54 +02:00
5b00465558 Merge pull request #273 from th-ch/upgrade-electron
Upgrade electron to v12
2021-05-16 22:31:07 +02:00
28b70f6459 added timeout when writing volume to config 2021-05-16 22:59:05 +03:00
6961cdee95 override youtube automatically changing the volume 2021-05-16 22:39:38 +03:00
58557505ae show mute icon when volume=0 2021-05-16 22:39:11 +03:00
65178b259f fix video muting when volume < 3 2021-05-16 21:32:40 +03:00
541c7f34b7 restore menuItems roles that were fixed 2021-05-15 17:24:01 +03:00
b2c209837c fix empty string input validation in setProxy 2021-05-14 03:12:27 +03:00
355f61188a use placeholder proxy example 2021-05-13 07:08:17 +03:00
ea672c2423 show volume hud in videoplayer if available 2021-05-13 03:18:24 +03:00
71ba6b8e55 lint 2021-05-12 20:58:43 +03:00
8a07fccf8f setup on page reload 2021-05-12 18:58:29 +03:00
7bc35f4cee add volume hud 2021-05-12 18:31:11 +03:00
002081bcb9 use store migration 2021-05-12 00:47:41 +03:00
TC
0e8e78362b Upgrade electron to v12 2021-05-11 22:56:07 +02:00
cb6a5a478e lint 2021-05-11 16:47:51 +03:00
d615030222 register appID on windows reguardless of shortcut 2021-05-11 00:34:40 +03:00
e43c01da64 lint 2021-05-11 00:20:00 +03:00
cb5ef1d6e5 check that app is installed / unpacked 2021-05-11 00:15:54 +03:00
78a7dcb7e8 lint 2021-05-10 23:46:56 +03:00
8cca9f3eeb create shortcut only if needed 2021-05-10 23:23:17 +03:00
93d4d3c976 writeShortcut on windows 2021-05-10 22:13:45 +03:00
8284b56075 set appID on windows 2021-05-10 20:40:25 +03:00
580caeffb9 destructure keybind output 2021-05-10 16:53:57 +03:00
b266037bb4 lint 2021-05-10 06:05:39 +03:00
cb743de7fd refactor notifications plugin 2021-05-10 05:00:58 +03:00
7cf78c6635 setup SongInfo **once** 2021-05-10 04:15:56 +03:00
0eca30367f lint 2021-05-10 01:56:41 +03:00
36317c953a globalize promptOptions 2021-05-10 01:43:50 +03:00
f910593fb6 use spread operator + async await 2021-05-10 00:40:02 +03:00
7942efa202 Merge pull request #267 from Araxeus/last-fm-config-check-fix
fix last-fm overwrite config on each start
2021-05-09 21:47:07 +02:00
792c2931b0 fix config overwrite on each start 2021-05-09 22:10:20 +03:00
TC
a3778af48a Downloader: rename UrlToJPG to urlToJPG 2021-05-08 22:48:18 +02:00
TC
163dc7e1d1 Downloader: catch error when fetching playlist 2021-05-08 22:41:56 +02:00
0a59122ac2 Merge pull request #265 from Araxeus/ensure-download-from-radio-button
Downloader tweaks + taskbar progress bar
2021-05-08 22:40:24 +02:00
d2a5110f3b querySelector optimization #2 2021-05-08 23:06:18 +03:00
cf4bbf94e4 update radioButton querySelector 2021-05-08 23:02:52 +03:00
d7e42471a4 lint 2021-05-08 23:01:57 +03:00
d634c41e75 Merge pull request #262 from Araxeus/remove-open-dependency
remove `open` dependency from last-fm plugin
2021-05-08 21:58:51 +02:00
6b88397f82 lint 2021-05-08 21:21:07 +03:00
da3c709ff0 remove videoDetails?.media query from XHR
(it never exists in the XHR responce)
2021-05-08 20:46:15 +03:00
ccd320d8ff minimize getArtist() 2021-05-08 20:40:44 +03:00
3831e61d10 differentiate names of different metadata sources 2021-05-08 19:45:33 +03:00
e46e7b74e2 fix sendError() 2021-05-08 19:34:57 +03:00
b3da77a6bc small refactor 2021-05-08 19:24:25 +03:00
96a74f8955 use original metadata only if not already captured from ytpl.getInfo() 2021-05-08 19:19:11 +03:00
3ea17e6f46 refactor 2021-05-08 08:05:38 +03:00
a8ac2c3af9 download progress bar on taskbar
+ Get the best possible artwork
2021-05-08 07:11:54 +03:00
2168cbca30 use image from imageSrc if transfered 2021-05-08 04:04:35 +03:00
cceb45319a debug videoUrl from start Radio button in menu 2021-05-08 04:03:34 +03:00
e985b78241 lint 2021-05-08 01:08:24 +03:00
090ca828c0 Merge branch 'master' into update-in-app-menu 2021-05-08 01:03:14 +03:00
3522925dec remove open dependency 2021-05-08 01:00:23 +03:00
5418ef7ae2 Merge branch 'master' into custom-electron-prompt 2021-05-08 00:21:16 +03:00
5faeddb99b Merge pull request #252 from Araxeus/fix-download-idtag-if-not-playing
Fix downloader metadata if not currently playing
2021-05-07 23:11:10 +02:00
1140c3e2e7 lint 2021-05-07 23:40:14 +03:00
TC
3fb08d27c7 Add start:debug command to enable dev with electron debug 2021-05-07 22:29:21 +02:00
250940d083 Merge pull request #259 from Araxeus/force-pause
fix playPause bugs by directly playPause video element
2021-05-07 22:24:03 +02:00
e00be8f010 lint 2021-05-07 05:43:26 +03:00
8b471c0772 create cleanupArtistName() in song-info 2021-05-07 04:47:24 +03:00
88e738c796 check if yns_pause exists 2021-05-07 04:17:17 +03:00
TC
c76d8c79d8 Bump version to 1.12.1 2021-05-06 22:29:04 +02:00
b396431a8b Merge pull request #260 from th-ch/dependabot/npm_and_yarn/ua-parser-js-0.7.28
Bump ua-parser-js from 0.7.23 to 0.7.28
2021-05-06 22:18:37 +02:00
d1795a82f7 Merge pull request #253 from Araxeus/fix-precise-volume-listener-override
Fix precise volume listener override
2021-05-06 21:45:09 +02:00
9b821a0dfe Bump ua-parser-js from 0.7.23 to 0.7.28
Bumps [ua-parser-js](https://github.com/faisalman/ua-parser-js) from 0.7.23 to 0.7.28.
- [Release notes](https://github.com/faisalman/ua-parser-js/releases)
- [Commits](https://github.com/faisalman/ua-parser-js/compare/0.7.23...0.7.28)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-06 19:41:13 +00:00
bf89842ee8 Merge pull request #255 from Araxeus/fix-css-on-reload
fix css not inserting on reload
2021-05-06 21:40:13 +02:00
d274e80f75 minify 2021-05-06 21:41:08 +03:00
a98b8945fb lint 2021-05-06 21:32:44 +03:00
6b72599f80 directly playPause video element 2021-05-06 21:20:42 +03:00
4b6fe78a1a fix css not inserting on reload 2021-05-06 18:40:56 +03:00
d000c03fca lint 2021-05-06 05:40:31 +03:00
c0a185ba68 refactor addEventListener 2021-05-06 05:18:06 +03:00
ef0813e638 fix preventDefault() being called on *all* keys 2021-05-06 04:58:05 +03:00
25e9f44260 fix listener override condition 2021-05-06 04:56:06 +03:00
2d6e858e8f lint 2021-05-06 04:43:31 +03:00
53bf7c5068 playlist download progressBar using chokidar 2021-05-06 04:41:58 +03:00
13fb686188 started playlist downlaod messageBox 2021-05-06 03:33:49 +03:00
61c5494588 custom metadata on playlist-download 2021-05-06 03:02:06 +03:00
d96fefbc24 fix error thrown when downloading playlist 2021-05-06 02:51:10 +03:00
e18b7c1013 allow unlimited playlist size 2021-05-06 02:28:35 +03:00
f190b51dcc lint 2021-05-06 02:17:48 +03:00
ca41c12f7c use media propery if exist in song-info 2021-05-06 02:05:48 +03:00
844edbe2f4 fix metadata when downloading unplayed song 2021-05-06 01:46:14 +03:00
78974c02e5 save in-app-menu activation state on launch 2021-05-05 21:12:52 +03:00
4508464fd1 update custom-electron-titlebar version 2021-05-05 20:39:08 +03:00
dd6455a559 update in-app-menu 2021-05-05 20:37:29 +03:00
6b147b098a fix prompt width 2021-05-05 03:48:07 +03:00
834f8674a3 massive prompt speed boost with v1.1.0 2021-05-05 03:27:47 +03:00
5cee331abe update prompt version and lint 2021-05-05 02:53:05 +03:00
98c00f7a60 format proxy example 2021-05-05 02:30:08 +03:00
db8d946178 fix electron dependency 2021-05-05 02:08:24 +03:00
b97a86f6dc Update yarn.lock 2021-05-05 01:55:25 +03:00
34a4e6be3d proxy url check 2021-05-05 01:47:53 +03:00
22c5ea5000 lint 2021-05-05 01:25:45 +03:00
79acf6c0ba Volume Steps Prompt
Precise-Volume Global Shortcuts Prompt
2021-05-05 00:42:42 +03:00
ebaa01896f Merge remote-tracking branch 'upstream/master' into custom-electron-prompt 2021-05-04 23:48:27 +03:00
TC
d4811b7901 Revert "Remove preload.js in plugin uses and use front plugin injection"
This reverts commit 4cb658daca.
2021-05-04 22:31:49 +02:00
TC
bf409967b2 Import front logger at top level 2021-05-04 21:30:10 +02:00
TC
4cb658daca Remove preload.js in plugin uses and use front plugin injection 2021-05-04 21:29:39 +02:00
8aeddcf8d8 Merge pull request #224 from Araxeus/menu-fixes
Menu tweaks
2021-05-04 21:24:29 +02:00
fb81e1bdd5 ignore did-fail-load error code -3
bug with in-app-menu
2021-05-04 02:04:17 +03:00
d5b9e3c960 stringify did-fail-load error
directly preload front-logger
simplify front-logger
2021-05-04 01:22:47 +03:00
TC
c7ff536ed5 Bump version to 1.12.0 2021-05-03 22:24:19 +02:00
TC
7dbb5fc86d Update electron to 11.4.4 2021-05-02 22:41:38 +02:00
8f766bcbaa resolve merge conflict 2021-05-02 23:41:16 +03:00
TC
f65c6c89ae Fix package.json indent, update yarn.lock 2021-05-02 22:30:24 +02:00
472462cdcb Merge pull request #228 from Araxeus/interactive-notifications
Interactive notifications for windows
2021-05-02 22:27:36 +02:00
8575996e46 Merge branch 'menu-fixes' of https://github.com/Araxeus/youtube-music into menu-fixes 2021-05-02 23:26:27 +03:00
02d16ca510 fix typo
Co-authored-by: th-ch <th-ch@users.noreply.github.com>
2021-05-02 23:25:46 +03:00
1f69048c86 minify switch case
Co-authored-by: th-ch <th-ch@users.noreply.github.com>
2021-05-02 23:25:34 +03:00
442aafd2c5 remove redundant dialog import 2021-05-02 23:25:20 +03:00
6082a6549a Merge branch 'master' into interactive-notifications 2021-05-02 22:25:01 +02:00
2567702b44 Merge pull request #236 from Araxeus/precise-volume
[Plugin] Precise volume control
2021-05-02 21:41:17 +02:00
ec981ac547 refactor registerAllShortcuts 2021-04-30 05:09:49 +03:00
d0d4ada7c2 restore original menu lint 2021-04-30 04:33:23 +03:00
54cbe3faa4 lint 2021-04-30 04:29:01 +03:00
49e51de274 update shortcuts config 2021-04-30 04:13:03 +03:00
e456035f29 fix typo 2021-04-30 03:22:36 +03:00
964974c142 add keybind changer v1 2021-04-30 03:04:38 +03:00
b63eb1c8b4 Merge pull request #244 from th-ch/snyk-upgrade-99aee86a0f7b96c0f871bcfabfb6e986
[Snyk] Upgrade electron-store from 7.0.2 to 7.0.3
2021-04-29 23:36:33 +02:00
603bcf7d9d Merge pull request #233 from th-ch/snyk-upgrade-db0717b422c1c3d1fcb50ff90a0b95cd
[Snyk] Upgrade @cliqz/adblocker-electron from 1.20.3 to 1.20.4
2021-04-29 23:34:51 +02:00
TC
0468a23c4f Fix getFolder util (main/renderer process) 2021-04-29 23:28:27 +02:00
f95e29df45 Merge pull request #231 from Araxeus/dependencies-update
Dependencies update
2021-04-29 23:27:21 +02:00
79d95d9477 Merge branch 'master' into dependencies-update 2021-04-29 23:10:52 +02:00
a406ba4ca0 Merge pull request #245 from th-ch/fix-downloader-metadata
Fix downloader metadata
2021-04-29 23:05:17 +02:00
TC
dfbda7c10b Set metadata in back (to have cover) 2021-04-29 22:57:47 +02:00
TC
c11ecd3323 Set max file length to 255 in downloader 2021-04-29 22:57:12 +02:00
TC
8a5c39ee53 Fix download URL 2021-04-29 22:56:33 +02:00
c38035188b fix: upgrade electron-store from 7.0.2 to 7.0.3
Snyk has created this PR to upgrade electron-store from 7.0.2 to 7.0.3.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=upgrade-pr
2021-04-29 02:19:11 +00:00
TC
ba02d372f7 Update yarn.lock 2021-04-28 22:23:31 +02:00
5312b3694b Merge pull request #196 from semvis123/lastfm
Last.fm support
2021-04-28 22:22:44 +02:00
d0800bb31c small refactor 2021-04-28 16:58:14 +03:00
e272d38ca5 fix typo 2021-04-28 16:54:05 +03:00
66517af81c fix typo 2021-04-28 16:33:12 +03:00
d2925ee3f9 make variable names clearer 2021-04-28 04:30:19 +03:00
729714375b update camelCase 2021-04-28 04:27:50 +03:00
b77643b928 remove proxy from menu 2021-04-28 03:54:59 +03:00
0491babe0a fix typo 2021-04-28 03:36:00 +03:00
0adb36cfb8 leave debug for next PR 2021-04-28 03:08:25 +03:00
8dc486f18f remove local prompt 2021-04-28 02:51:19 +03:00
a229ba9c15 disable reload of plugins on window created 2021-04-28 02:46:13 +03:00
e4eed2e519 add custom-electron-prompt
also use it to set proxy option
2021-04-28 02:41:44 +03:00
395eac26a3 switch function name to camelCase
Co-authored-by: th-ch <th-ch@users.noreply.github.com>
2021-04-27 23:52:08 +03:00
e9d7ddebb2 defensive code
Co-authored-by: th-ch <th-ch@users.noreply.github.com>
2021-04-27 23:51:29 +03:00
5dc1179d54 implement keybind prompt 2021-04-27 23:48:06 +03:00
8decdf4346 Merge pull request #239 from Araxeus/discord-fix
simple fix for discord plugin
2021-04-27 21:51:07 +02:00
98fd6240ba update inline doc 2021-04-26 21:09:42 +03:00
5a77528526 enable global volume shortcuts in advanced config 2021-04-26 21:01:19 +03:00
d4fdced538 addEventListener insead of .onwheel 2021-04-25 02:22:08 +03:00
8b1bbdf360 Add Video Player Mousewheel Volume Control
(if hide-video-player plugin is disabled)
2021-04-23 05:29:03 +03:00
eae4cca148 Update readme.md 2021-04-23 04:25:48 +03:00
a194046168 win.once() instead of win.on 2021-04-23 04:06:54 +03:00
7c6ed7bb31 once instead of on 2021-04-23 04:01:40 +03:00
20123d8245 revert to original lint format 2021-04-23 03:50:00 +03:00
650945418d simple fix 2021-04-23 03:42:00 +03:00
064facb048 remove slider on-hover after 3 seconds if !focused 2021-04-23 01:20:03 +03:00
0bc1b5e0d3 lint 2021-04-22 16:30:34 +03:00
65f6822199 Show volume slider on volume change 2021-04-22 14:44:37 +03:00
021d2a8a54 disable native volume-slider listeners 2021-04-22 05:34:54 +03:00
5fa8f3ef6f add option for plugin to have a preload.js 2021-04-22 05:34:43 +03:00
10dffdbde2 refactor + lint 2021-04-19 01:45:32 +03:00
72716afcd3 lint 2021-04-18 01:44:18 +03:00
00468c7d0e use timeout ID to stop callback 2021-04-18 00:44:22 +03:00
b7b1316e70 add rapidFire option to counter prompt 2021-04-17 22:55:25 +03:00
97a9e63231 xo --fix 2021-04-17 21:17:07 +03:00
3f50ab7cfc lint 2021-04-17 20:43:48 +03:00
341a06aae7 add prompt with number counter 2021-04-17 20:30:06 +03:00
c48260f10c add advanced option to change volume steps 2021-04-17 16:42:13 +03:00
12a2517697 xo --fix 2021-04-17 15:03:19 +03:00
49698ea669 fix changing settings when plugin is disabled 2021-04-17 05:08:07 +03:00
834202411d enable changing shortcut setting without restart 2021-04-17 04:38:23 +03:00
94e152bb57 add optional arrowkeys controls option 2021-04-17 04:14:54 +03:00
5adcc3efad fix set volume on first run after not using plugin 2021-04-17 02:38:45 +03:00
b65bc65d7c save volume to settings 2021-04-17 02:01:19 +03:00
06958c424c refactor 2021-04-16 23:30:43 +03:00
02896cac03 xo --fix 2021-04-16 23:02:16 +03:00
c0ec1bc5cf update inline doc 2021-04-16 22:37:34 +03:00
40968d573c add precise scrollwheel control + precise tooltip 2021-04-16 21:52:56 +03:00
9f848e3e76 fix: upgrade @cliqz/adblocker-electron from 1.20.3 to 1.20.4
Snyk has created this PR to upgrade @cliqz/adblocker-electron from 1.20.3 to 1.20.4.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=upgrade-pr
2021-04-16 02:19:07 +00:00
f765fb63f0 scrape artistName from playBar 2021-04-15 15:59:41 +03:00
ff6a486daf fix unresponsive dialog response 2021-04-15 15:35:22 +03:00
bb6ad14111 navbar background black fix visual bug 2021-04-15 12:40:09 +03:00
a2207a2cb3 remove " - Topic" from artist name 2021-04-14 13:44:32 +03:00
c764d657d7 cleanup unresponsiveDialog 2021-04-14 13:42:39 +03:00
2b6cecc441 Update ytpl 2021-04-12 20:56:52 +03:00
61d83be52e taskbar-mediacontrol no longer override win.hide
(electron 11.4.2 fixed the bug that required that)
2021-04-12 20:19:36 +03:00
9f2362d346 update dependecies 2021-04-12 20:19:35 +03:00
193c3823b6 remove downloads-folder and use electron instead 2021-04-12 20:19:35 +03:00
09d9f72db2 add unresponsive listener 2021-04-10 02:51:38 +03:00
3d41d04818 Revert "fix rare crash due to unfocus effect"
This reverts commit 80b1207640.
2021-04-09 22:28:55 +03:00
46ac0a1ed3 change notification priority to show only on linux 2021-04-09 19:10:20 +03:00
e6d77c165e Center Icon on ALL notifications
Delete notification on windowclosed
Refactor notifications.utils.setOption
2021-04-09 18:39:57 +03:00
18f041f1c6 clarify button purpose 2021-04-09 03:26:46 +03:00
9c0a633677 fix unPause option compatibility 2021-04-09 03:15:57 +03:00
095196785a add note to notifications.interactive 2021-04-09 02:32:14 +03:00
80b1207640 fix rare crash due to unfocus effect 2021-04-09 02:19:06 +03:00
ba6244780c minify 2021-04-09 02:14:11 +03:00
d8dc4656e4 stylecheck 2021-04-09 01:08:22 +03:00
30675e0567 remove appID because of bug:
Button would not transmit event
2021-04-08 18:56:13 +03:00
e6efddc639 add windows interactive notifications 2021-04-08 16:54:46 +03:00
47eace97bd Merge branch 'menu-fixes' of https://github.com/Araxeus/youtube-music into menu-fixes 2021-04-06 21:58:58 +03:00
980ffb45e9 Create readme.md
refactor and css fix

xo --fix

add inline doc

fix typo
2021-04-06 21:57:16 +03:00
17fd499420 fix typo 2021-04-06 01:51:11 +03:00
0e9b15722a add inline doc 2021-04-06 01:30:47 +03:00
724c213af1 xo --fix 2021-04-05 23:18:54 +03:00
a215035d07 refactor and css fix 2021-04-05 18:28:27 +03:00
25d0f50f3d Create readme.md 2021-04-05 04:52:22 +03:00
6d44a579a4 move prompt to provide 2021-04-05 04:21:52 +03:00
106e461beb fix typo 2021-04-05 04:08:55 +03:00
6472002f8a fix hide-menu timing + minify log function 2021-04-05 04:04:29 +03:00
11bd1adbd4 stylecheck 2021-04-05 03:25:45 +03:00
28d366ab19 add back-to-front logger
This somehow fix "did-fail-load" being called on song start, with in-app-plugin-activated
2021-04-05 03:04:06 +03:00
10e29090d8 stylecheck 2021-04-04 21:52:16 +03:00
73b0ddc2ce css tweaks 2021-04-04 21:25:41 +03:00
7c8e946871 implement custom prompt 2021-04-04 19:58:25 +03:00
d12d16348a custom dark skin for prompt 2021-04-04 15:06:14 +03:00
421fe67930 ignore proxy if equal to example 2021-04-04 02:46:12 +03:00
8291cdfc12 disable dev tools on electron-prompt 2021-04-04 02:37:19 +03:00
2e6fffc903 Revert "fix Connection Error when using in-app-menu"
This reverts commit 8b6c60bb17.
2021-04-03 23:46:16 +03:00
8b6c60bb17 fix Connection Error when using in-app-menu 2021-04-03 22:59:20 +03:00
1cae35a62b fix in-app-menu hideMenu on launch 2021-04-03 22:34:12 +03:00
70b03b71e4 refactor and stylecheck 2021-04-03 22:04:40 +03:00
317521bba6 fix memory leak +
in-app-menu updates menu only if needed
2021-04-03 21:53:39 +03:00
061e4a9e5f add 'electron-prompt' and use it for setting proxy 2021-04-03 21:51:35 +03:00
ec3adff706 fix bug when loading window with no connection 2021-04-03 20:15:48 +03:00
5524a14c87 move advanced options to dedicated submenu +
add "Use Proxy" checkbox
2021-04-03 19:20:56 +03:00
e1e8c943f3 Merge branch 'master' into lastfm 2021-04-03 17:21:56 +02:00
0c6630d2d9 additional cleanup/refactoring 2021-04-03 17:12:28 +02:00
80a7d2c255 remove redundant roles 2021-04-03 15:54:51 +03:00
2b3a20c5ff Minimalize tray menu
-doesnt include main menu template anymore
2021-04-03 15:47:32 +03:00
216205200c fix in-app-menu navbar opacity + scrollbar color 2021-04-03 15:30:33 +03:00
TC
61e7124516 Support proxy in advanced options 2021-04-03 12:20:48 +02:00
2ab216effc Merge pull request #215 from th-ch/menu-options
In-app-menu plugin - rename plugin & configure menu builder
2021-04-03 12:10:56 +02:00
TC
1e59301c0e Write metadata in front + pass it along (useful if the song is changed) 2021-04-03 12:04:49 +02:00
TC
d5a2c1cad6 Format front downloader 2021-04-03 12:01:31 +02:00
TC
b5c60ee6a9 Re-format front metadata lib 2021-04-03 11:35:31 +02:00
TC
533b8a8cb7 Metadata: call getImage in front 2021-04-03 11:34:55 +02:00
TC
fecb193ded Re-use default config to get base url in downloader 2021-04-03 11:34:23 +02:00
dc5d257082 Merge pull request #221 from Araxeus/download-song-that-isnt-playing
Allows downloading songs that aren't currently playing
2021-04-03 11:29:20 +02:00
c690473b2e Merge pull request #222 from Keyboardsheep/master
Updated download plugin icon color to match other icons
2021-04-03 10:58:37 +02:00
04fa5eb289 updated download.html svg 2021-04-03 00:10:09 -05:00
d6cd3a0ead updated download.html svg 2021-04-03 00:06:58 -05:00
a23f281734 updated download.html svg 2021-04-03 00:05:36 -05:00
135d58e1f5 updated download.html svg 2021-04-02 23:55:32 -05:00
832ff19e51 allows downloading song that aren't playing 2021-04-03 05:38:21 +03:00
c7bef5ac7b Merge pull request #216 from Araxeus/fix-duplicate-notification
[Notification Plugin] Fix duplicate notification
2021-04-02 23:10:19 +02:00
2254cac15b Merge pull request #213 from th-ch/song-info-front
Pass metadata to front + use metadata URL in downloader
2021-04-02 22:59:57 +02:00
TC
01d574a302 Merge branch 'master' of github.com:th-ch/youtube-music into menu-options
# By Araxeus
# Via GitHub (2) and Araxeus (1)
* 'master' of github.com:th-ch/youtube-music:
  remove 'shortcuts'+'discord' from default plugins
  cleanup styled-bars code
  only refresh if plugin has a menu.js
  update styled-bars to support all changes permantly
  Refresh menu on plugin enable/disable
2021-04-02 22:40:31 +02:00
d67d697847 Merge pull request #217 from Araxeus/Refresh-menu-on-plugin-enable/disable
Refresh menu on plugin enable/disable (show/hide submenu)
2021-04-02 21:43:12 +02:00
8c9e37f8a1 Merge pull request #218 from Araxeus/remove-shortcuts-from-default-plugins
remove 'shortcuts' from default plugins
2021-04-02 21:38:18 +02:00
TC
77393c5324 Replace click callback by override in custom menu 2021-04-02 21:20:50 +02:00
e66db051a9 remove 'shortcuts'+'discord' from default plugins 2021-04-01 21:24:11 +03:00
5670a6d1b4 cleanup styled-bars code 2021-04-01 21:09:31 +03:00
02d45bed74 only refresh if plugin has a menu.js 2021-04-01 19:21:21 +03:00
adc0d145c3 update styled-bars to support
all changes permantly
2021-04-01 19:02:39 +03:00
58481e3133 Refresh menu on plugin enable/disable 2021-04-01 18:10:08 +03:00
a3ec9b7b78 add notification on unpause option 2021-04-01 17:44:40 +03:00
2d534b0293 fix duplicate notification 2021-04-01 15:50:26 +03:00
d73d0cf8ce fix duplicate notification 2021-04-01 15:39:07 +03:00
fbe490c28d Merge remote-tracking branch 'upstream/master' into styled-bars-download-chooser 2021-04-01 02:56:11 +03:00
TC
c09de43154 Rename plugin to in-app-menu + hidden titleBarStyle 2021-03-31 22:37:12 +02:00
TC
be651ebb4b Parameter in menu builder for tray 2021-03-31 22:35:01 +02:00
TC
3fe793cd4d CSS: border radius compatibility 2021-03-31 21:18:31 +02:00
TC
a8cfe399b3 Rename bar var 2021-03-31 21:15:52 +02:00
TC
ff9e39e2ee Re-order dependencies 2021-03-31 21:15:16 +02:00
TC
0d2b61472c Use template builder in custom menu plugin 2021-03-31 21:14:59 +02:00
TC
f9f3482bf1 Pass callback in menu builder 2021-03-31 21:14:55 +02:00
TC
400a2a9bab Add option to menu to render without roles 2021-03-31 20:18:20 +02:00
TC
36864b4c2f Format custom menu plugin 2021-03-31 20:17:12 +02:00
TC
5671b99b7e Format new menu items 2021-03-31 20:15:55 +02:00
76f88686da Merge pull request #201 from Araxeus/styled-bars
[Plugin] styled-bars
2021-03-31 20:07:41 +02:00
ba42a8b269 increase font size for menu and menuItems 2021-03-30 02:18:02 +03:00
ae7def2313 Rename variables and function for clarity 2021-03-30 02:00:35 +03:00
e55176511f Merge pull request #212 from SapuSeven/master
Add configurable notification urgency
2021-03-29 22:42:17 +02:00
TC
640f146373 Use metadata URL in downloader (fallback to current URL) 2021-03-29 22:00:49 +02:00
TC
ebe8755613 Song provider: call callbacks when data is updated 2021-03-29 21:59:45 +02:00
TC
05eee7cb0f Store metadata in front 2021-03-29 21:33:33 +02:00
TC
7ac9fda1eb Send back metadata to front 2021-03-29 21:33:07 +02:00
TC
6987a0a585 Set isPaused default to undefined 2021-03-29 21:32:41 +02:00
1a338fb9e5 Add advanced configuration menu for notifications 2021-03-28 23:33:10 +02:00
8dc18bbe5e Add configurable notification urgency 2021-03-28 23:23:45 +02:00
TC
64c2b32b24 Add url to song info 2021-03-28 22:17:57 +02:00
TC
f01ef5d955 Ensure filename is not empty in downloader plugin 2021-03-28 21:54:21 +02:00
TC
221ee0be05 Order deps in menu file in downloader plugin (+ prettier) 2021-03-28 21:53:50 +02:00
2b8ba02c2a Merge pull request #207 from Araxeus/Download-Plugin-Directory-Chooser
add Download Folder Chooser
2021-03-28 21:51:02 +02:00
TC
b06583afce Bundle imports + prettier 2021-03-28 21:16:33 +02:00
b8b1ae7e88 Merge pull request #194 from semvis123/songprovider-update
Improved songinfo provider, by using the data from the '/player' request
2021-03-28 21:12:03 +02:00
b67b1bed13 refactor for clarity 2021-03-26 20:32:30 +03:00
33fa9f8f50 fix array input + use getFolder() 2021-03-26 17:52:23 +03:00
a7087aaa38 uses getFolder() now 2021-03-26 17:41:12 +03:00
a7170762d4 Merge pull request #10 from Araxeus/Download-Plugin-Directory-Chooser
Download plugin directory chooser
2021-03-26 05:59:45 +03:00
ea3d198723 in tray, delete quit button from submenu
also delete useless nonfunctional view submenu
2021-03-26 05:25:46 +03:00
fe8f048571 add Download Folder Chooser 2021-03-26 04:07:45 +03:00
09d2feb15b Add quit + restart button to main menu ) 2021-03-26 00:24:03 +02:00
33d4c1a60e Refactor into switchMenuVisibility 2021-03-26 00:23:28 +02:00
cbd7a13275 Merge pull request #180 from th-ch/snyk-upgrade-58542eeb4b38266ae86c95a43f335043
[Snyk] Upgrade @cliqz/adblocker-electron from 1.20.0 to 1.20.1
2021-03-25 22:22:23 +01:00
903b7f8718 lint 2021-03-25 23:16:00 +02:00
TC
88cd1651c3 Clean metadata + apply pngcrush on images in taskbar plugin 2021-03-25 22:15:39 +01:00
13ff5f26a2 Plugin doesn't create a new menu anymore
instead it modifies the original menu
2021-03-25 23:13:54 +02:00
57345a5fd2 Merge pull request #200 from Araxeus/master
[Plugin] taskbar-mediacontrol  (for Windows)
2021-03-25 22:11:49 +01:00
bd82bd2249 added extra comments and corrected existing ones 2021-03-25 21:27:33 +01:00
f253a69656 added comment for the added function 2021-03-25 21:11:55 +01:00
493a5835f8 Merge branch 'master' into lastfm 2021-03-25 20:34:40 +01:00
bffbcb229d Made the suffixes configurable and added VEVO suffix to remove 2021-03-25 20:15:43 +01:00
b5ec431b0a Merge remote-tracking branch 'upstream/master' into styled-bars 2021-03-25 18:39:06 +02:00
259706478f Merge remote-tracking branch 'upstream/master' 2021-03-25 18:33:48 +02:00
c9f172ef21 fix scrollbar clash with nav bar 2021-03-25 16:43:01 +02:00
eb32c98b76 fix tray option radio selection 2021-03-25 07:51:06 +02:00
95c3f04c10 more fixes for plugin options menu 2021-03-25 02:18:23 +02:00
d4daf7231c merge source (#3)
* Added Discord timeout

* Add getOptions in plugin util

* Mutex in ffmpeg conversion (only supports one command at a time)

* Add menu customization in plugin system

* Add ytpl package (playlist info)

* Handle ffmpeg metadata flags when metadata is not present

* Only use artist in file name if present

* Export sendError method

* Handle image not present in metadata util

* Add downloader utils (getFolder and default menu label)

* Pass (optional) existing metadata and subfolder in mp3 converter

* Add listener to download playlist

* Add custom menu in downloader plugin ("download playlist" item)

* nit: fix main CSS style

* Only set the "enable" item in menu if plugin not enabled

* Navigation plugin: inject HTML once CSS is loaded

Co-authored-by: Sem Visscher <semvisscher10@gmail.com>
Co-authored-by: TC <th-ch@users.noreply.github.com>
2021-03-25 01:36:22 +02:00
9a95f435ad merge source (#2)
* Added Discord timeout

* Add getOptions in plugin util

* Mutex in ffmpeg conversion (only supports one command at a time)

* Add menu customization in plugin system

* Add ytpl package (playlist info)

* Handle ffmpeg metadata flags when metadata is not present

* Only use artist in file name if present

* Export sendError method

* Handle image not present in metadata util

* Add downloader utils (getFolder and default menu label)

* Pass (optional) existing metadata and subfolder in mp3 converter

* Add listener to download playlist

* Add custom menu in downloader plugin ("download playlist" item)

* nit: fix main CSS style

* Only set the "enable" item in menu if plugin not enabled

* Navigation plugin: inject HTML once CSS is loaded

Co-authored-by: Sem Visscher <semvisscher10@gmail.com>
Co-authored-by: TC <th-ch@users.noreply.github.com>
2021-03-25 01:02:42 +02:00
5218b80cab preparation for plugin menu update 2021-03-25 01:00:06 +02:00
TC
b1665c880b Navigation plugin: inject HTML once CSS is loaded 2021-03-24 23:06:35 +01:00
6395dfe425 Merge pull request #203 from th-ch/downloader-plugin-playlist
Add playlist feature in downloader plugin + custom menus in plugin system
2021-03-24 22:39:50 +01:00
304ad6e767 Merge pull request #192 from semvis123/master
Added Discord timeout
2021-03-24 22:38:56 +01:00
TC
587a093aff Only set the "enable" item in menu if plugin not enabled 2021-03-24 21:55:53 +01:00
TC
91b49e4a16 nit: fix main CSS style 2021-03-24 21:52:33 +01:00
TC
e72915c5d0 Add custom menu in downloader plugin ("download playlist" item) 2021-03-24 21:52:12 +01:00
TC
bee2da567b Add listener to download playlist 2021-03-24 21:51:11 +01:00
TC
951689c5ea Pass (optional) existing metadata and subfolder in mp3 converter 2021-03-24 21:50:46 +01:00
TC
4146ae60bc Add downloader utils (getFolder and default menu label) 2021-03-24 21:50:31 +01:00
TC
6bcf5efb65 Handle image not present in metadata util 2021-03-24 21:48:12 +01:00
TC
ddf10f1052 Export sendError method 2021-03-24 21:45:59 +01:00
TC
7edca44ed8 Only use artist in file name if present 2021-03-24 21:44:54 +01:00
TC
ebc3f16597 Handle ffmpeg metadata flags when metadata is not present 2021-03-24 21:44:54 +01:00
TC
9dad31775c Add ytpl package (playlist info) 2021-03-24 21:44:54 +01:00
TC
b1089b66c3 Add menu customization in plugin system 2021-03-24 21:44:54 +01:00
TC
84142ab27e Mutex in ffmpeg conversion (only supports one command at a time) 2021-03-24 21:44:48 +01:00
TC
fd518e39ff Add getOptions in plugin util 2021-03-24 21:36:17 +01:00
2fb4cbcf02 move icons to 'assets' folder 2021-03-24 19:38:45 +02:00
d3337c7d3c titlebar dragArea fix + refactor 2021-03-24 00:13:38 +02:00
5fd11bf009 fixed search page header height 2021-03-23 22:15:16 +02:00
92603a1e7f fix search button alignment 2021-03-23 22:09:10 +02:00
7d8afe3476 style check 2021-03-23 22:03:48 +02:00
f9c66ead50 css color fix 2021-03-23 19:47:47 +02:00
a12d5a982f fix library UI bug 2021-03-23 19:29:07 +02:00
c1f176fa21 Fixed MenuItem roles + add restart+quit button 2021-03-23 16:17:50 +02:00
f1dbe4ab6c Allow hide menu options (uses Escape key) 2021-03-23 15:07:31 +02:00
459ebf7070 xo lint --fix 2021-03-23 03:58:19 +02:00
8cf0ec16bb Merge branch 'styled-bars' of https://github.com/Araxeus/youtube-music into styled-bars 2021-03-23 03:51:16 +02:00
87558c67c8 stylecheck 2021-03-23 03:50:59 +02:00
8727a2e299 separate plugins to different branches 2021-03-23 02:51:12 +02:00
b8c5c87cfa Styled Bars [Titlebar + Scrollbar] 2021-03-23 02:46:13 +02:00
ce2970eefa fixed line that xo-lint broke 2021-03-22 13:48:08 +02:00
24fea5a24a added back original yarn.lock
(was deleted by accident)
2021-03-22 04:22:23 +02:00
5285680eed xo lint --fix 2021-03-22 04:17:39 +02:00
17e63194ad fixed typo bug 2021-03-22 04:06:42 +02:00
6427b3406c Override hide(),show(),isVisible from inside plugin
instead of changing source code
2021-03-22 04:01:19 +02:00
df8c77cd3e Wait for song to start before setting thumbar 2021-03-22 03:01:46 +02:00
41796aec06 Tray Break Thumbar Fix
using  win.minimize()
and win.setSkipTaskbar(bool)
instead of win.hide() / win.show()
2021-03-22 01:26:20 +02:00
1355b692b9 Remove the - Topic for more matches 2021-03-21 22:28:05 +01:00
2c13ef40e2 taskbar-mediacontrol plugin
Add UI Control to windows taskbar preview
2021-03-21 20:08:07 +02:00
2bb67db888 Fixed backwards compatibility 2021-03-21 18:47:16 +01:00
204f384d01 fetch highest resolution image instead of lowest resolution 2021-03-21 14:14:26 +01:00
42e3d48caf Added now playing to last-fm 2021-03-19 22:00:00 +01:00
4747050b60 Added working lastfm support
currently only played songs are added
2021-03-19 21:32:33 +01:00
TC
c926db7f13 Increase top margin for MacOS 2021-03-18 22:12:18 +01:00
TC
8cce3f4503 Add view/navigation menu 2021-03-18 22:02:19 +01:00
3464b0383c small readability changes 2021-03-18 17:53:30 +01:00
d852029d25 Improved songinfo provider, by using the data from the '/player' request 2021-03-18 17:48:56 +01:00
ca8d62d4e2 Added Discord timeout 2021-03-17 10:22:19 +01:00
0632920a6f fix: upgrade @cliqz/adblocker-electron from 1.20.0 to 1.20.1
Snyk has created this PR to upgrade @cliqz/adblocker-electron from 1.20.0 to 1.20.1.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=upgrade-pr
2021-03-10 02:19:11 +00:00
84 changed files with 6209 additions and 4163 deletions

View File

@ -18,7 +18,7 @@ jobs:
- name: Setup NodeJS
uses: actions/setup-node@v1
with:
node-version: "12.x"
node-version: "14.x"
- name: Get yarn cache directory path
id: yarn-cache-dir-path

View File

@ -15,25 +15,72 @@ const defaultConfig = {
trayClickPlayPause: false,
autoResetAppCache: false,
resumeOnStart: true,
proxy: "",
},
plugins: {
// Enabled plugins
navigation: {
enabled: true,
},
shortcuts: {
enabled: true,
},
adblocker: {
enabled: true,
cache: true,
additionalBlockLists: [], // Additional list of filters, e.g "https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/filters.txt"
},
// Disabled plugins
shortcuts: {
enabled: false,
overrideMediaKeys: false,
},
downloader: {
enabled: false,
ffmpegArgs: [], // e.g. ["-b:a", "192k"] for an audio bitrate of 192kb/s
downloadFolder: undefined, // Custom download folder (absolute path)
preset: "mp3",
},
"last-fm": {
enabled: false,
api_root: "http://ws.audioscrobbler.com/2.0/",
api_key: "04d76faaac8726e60988e14c105d421a", // api key registered by @semvis123
secret: "a5d2a36fdf64819290f6982481eaffa2",
},
discord: {
enabled: false,
activityTimoutEnabled: true, // if enabled, the discord rich presence gets cleared when music paused after the time specified below
activityTimoutTime: 10 * 60 * 1000, // 10 minutes
listenAlong: true, // add a "listen along" button to rich presence
},
notifications: {
enabled: false,
unpauseNotification: false,
urgency: "normal", //has effect only on Linux
interactive: false //has effect only on Windows
},
"precise-volume": {
enabled: false,
steps: 1, //percentage of volume to change
arrowsShortcut: true, //enable ArrowUp + ArrowDown local shortcuts
globalShortcuts: {
volumeUp: "",
volumeDown: ""
},
savedVolume: undefined //plugin save volume between session here
},
sponsorblock: {
enabled: false,
apiURL: "https://sponsor.ajay.app",
categories: [
"sponsor",
"intro",
"outro",
"interaction",
"selfpromo",
"music_offtopic",
],
},
"video-toggle": {
enabled: false,
forceHide: false,
},
},
};

View File

@ -24,6 +24,10 @@ function setOptions(plugin, options) {
});
}
function getOptions(plugin) {
return store.get("plugins")[plugin];
}
function enable(plugin) {
setOptions(plugin, { enabled: true });
}
@ -38,4 +42,5 @@ module.exports = {
enable,
disable,
setOptions,
getOptions,
};

View File

@ -3,6 +3,44 @@ const Store = require("electron-store");
const defaults = require("./defaults");
const migrations = {
">=1.14.0": (store) => {
if (
typeof store.get("plugins.precise-volume.globalShortcuts") !== "object"
) {
store.set("plugins.precise-volume.globalShortcuts", {});
}
if (store.get("plugins.hide-video-player.enabled")) {
store.delete("plugins.hide-video-player");
store.set("plugins.video-toggle.enabled", true);
}
},
">=1.13.0": (store) => {
if (store.get("plugins.discord.listenAlong") === undefined) {
store.set("plugins.discord.listenAlong", true);
}
},
">=1.12.0": (store) => {
const options = store.get("plugins.shortcuts");
let updated = false;
for (const optionType of ["global", "local"]) {
if (Array.isArray(options[optionType])) {
const updatedOptions = {};
for (const optionObject of options[optionType]) {
if (optionObject.action && optionObject.shortcut) {
updatedOptions[optionObject.action] = optionObject.shortcut;
}
}
options[optionType] = updatedOptions;
updated = true;
}
}
if (updated) {
store.set("plugins.shortcuts", options);
}
},
">=1.11.0": (store) => {
if (store.get("options.resumeOnStart") === undefined) {
store.set("options.resumeOnStart", true);

227
index.js
View File

@ -2,6 +2,9 @@
const path = require("path");
const electron = require("electron");
const remote = require('@electron/remote/main');
remote.initialize();
const enhanceWebRequest = require("electron-better-web-request").default;
const is = require("electron-is");
const unhandled = require("electron-unhandled");
const { autoUpdater } = require("electron-updater");
@ -11,6 +14,7 @@ const { setApplicationMenu } = require("./menu");
const { fileExists, injectCSS } = require("./plugins/utils");
const { isTesting } = require("./utils/testing");
const { setUpTray } = require("./tray");
const { setupSongInfo } = require("./providers/song-info");
// Catch errors and log them
unhandled({
@ -22,8 +26,9 @@ const app = electron.app;
app.commandLine.appendSwitch(
"js-flags",
// WebAssembly flags
"--experimental-wasm-threads --experimental-wasm-bulk-memory"
"--experimental-wasm-threads"
);
app.commandLine.appendSwitch("enable-features", "SharedArrayBuffer"); // Required for downloader
app.allowRendererProcessReuse = true; // https://github.com/electron/electron/issues/18397
if (config.get("options.disableHardwareAcceleration")) {
if (is.dev()) {
@ -32,8 +37,14 @@ if (config.get("options.disableHardwareAcceleration")) {
app.disableHardwareAcceleration();
}
if (config.get("options.proxy")) {
app.commandLine.appendSwitch("proxy-server", config.get("options.proxy"));
}
// Adds debug features like hotkeys for triggering dev tools and reload
require("electron-debug")();
require("electron-debug")({
showDevTools: false //disable automatic devTools on new window
});
// Prevent window being garbage collected
let mainWindow;
@ -54,7 +65,7 @@ function onClosed() {
function loadPlugins(win) {
injectCSS(win.webContents, path.join(__dirname, "youtube-music.css"));
win.webContents.on("did-finish-load", () => {
win.webContents.once("did-finish-load", () => {
if (is.dev()) {
console.log("did finish load");
win.webContents.openDevTools();
@ -75,6 +86,7 @@ function createMainWindow() {
const windowSize = config.get("window-size");
const windowMaximized = config.get("window-maximized");
const windowPosition = config.get("window-position");
const useInlineMenu = config.plugins.isEnabled("in-app-menu");
const win = new electron.BrowserWindow({
icon: icon,
@ -89,20 +101,24 @@ function createMainWindow() {
preload: path.join(__dirname, "preload.js"),
nodeIntegrationInSubFrames: true,
nativeWindowOpen: true, // window.open return Window object(like in regular browsers), not BrowserWindowProxy
enableRemoteModule: true,
affinity: "main-window", // main window, and addition windows should work in one process
...(isTesting()
? {
// Only necessary when testing with Spectron
contextIsolation: false,
nodeIntegration: true,
}
// Only necessary when testing with Spectron
contextIsolation: false,
nodeIntegration: true,
}
: undefined),
},
frame: !is.macOS(),
titleBarStyle: is.macOS() ? "hiddenInset" : "default",
frame: !is.macOS() && !useInlineMenu,
titleBarStyle: useInlineMenu
? "hidden"
: is.macOS()
? "hiddenInset"
: "default",
autoHideMenuBar: config.get("options.hideMenu"),
});
remote.enable(win.webContents);
if (windowPosition) {
const { x, y } = windowPosition;
win.setPosition(x, y);
@ -134,51 +150,81 @@ function createMainWindow() {
}
});
win.webContents.on("render-process-gone", (event, webContents, details) => {
showUnresponsiveDialog(win, details);
});
win.once("ready-to-show", () => {
if (config.get("options.appVisible")) {
win.show();
}
});
removeContentSecurityPolicy();
return win;
}
app.on("browser-window-created", (event, win) => {
app.once("browser-window-created", (event, win) => {
// User agents are from https://developers.whatismybrowser.com/useragents/explore/
const originalUserAgent = win.webContents.userAgent;
const userAgents = {
mac: "Mozilla/5.0 (Macintosh; Intel Mac OS X 12.1; rv:95.0) Gecko/20100101 Firefox/95.0",
windows: "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:95.0) Gecko/20100101 Firefox/95.0",
linux: "Mozilla/5.0 (Linux x86_64; rv:95.0) Gecko/20100101 Firefox/95.0",
}
const updatedUserAgent =
is.macOS() ? userAgents.mac :
is.windows() ? userAgents.windows :
userAgents.linux;
win.webContents.userAgent = updatedUserAgent;
app.userAgentFallback = updatedUserAgent;
win.webContents.session.webRequest.onBeforeSendHeaders((details, cb) => {
// this will only happen if login failed, and "retry" was pressed
if (win.webContents.getURL().startsWith("https://accounts.google.com") && details.url.startsWith("https://accounts.google.com")){
details.requestHeaders["User-Agent"] = originalUserAgent;
}
cb({ requestHeaders: details.requestHeaders });
});
setupSongInfo(win);
loadPlugins(win);
win.webContents.on("did-fail-load", () => {
win.webContents.on("did-fail-load", (
_event,
errorCode,
errorDescription,
validatedURL,
isMainFrame,
frameProcessId,
frameRoutingId,
) => {
const log = JSON.stringify({
error: "did-fail-load",
errorCode,
errorDescription,
validatedURL,
isMainFrame,
frameProcessId,
frameRoutingId,
}, null, "\t");
if (is.dev()) {
console.log("did fail load");
console.log(log);
}
if( !(config.plugins.isEnabled("in-app-menu") && errorCode === -3)) { // -3 is a false positive with in-app-menu
win.webContents.send("log", log);
win.webContents.loadFile(path.join(__dirname, "error.html"));
}
win.webContents.loadFile(path.join(__dirname, "error.html"));
});
win.webContents.on("will-prevent-unload", (event) => {
event.preventDefault();
});
win.webContents.on("did-navigate-in-page", () => {
const url = win.webContents.getURL();
if (url.startsWith("https://music.youtube.com")) {
config.set("url", url);
}
});
win.webContents.on("will-navigate", (_, url) => {
if (url.startsWith("https://accounts.google.com")) {
// Force user-agent "Firefox Windows" for Google OAuth to work
// From https://github.com/firebase/firebase-js-sdk/issues/2478#issuecomment-571356751
// Only set on accounts.google.com, otherwise querySelectors in preload scripts fail (?)
const userAgent =
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:70.0) Gecko/20100101 Firefox/70.0";
win.webContents.session.webRequest.onBeforeSendHeaders((details, cb) => {
details.requestHeaders["User-Agent"] = userAgent;
cb({ requestHeaders: details.requestHeaders });
});
}
});
win.webContents.on(
"new-window",
(e, url, frameName, disposition, options) => {
@ -222,6 +268,35 @@ app.on("ready", () => {
}, 20000);
}
// Register appID on windows
if (is.windows()) {
const appID = "com.github.th-ch.youtube-music";
app.setAppUserModelId(appID);
const appLocation = process.execPath;
const appData = app.getPath("appData");
// check shortcut validity if not in dev mode / running portable app
if (!is.dev() && !appLocation.startsWith(path.join(appData, "..", "Local", "Temp"))) {
const shortcutPath = path.join(appData, "Microsoft", "Windows", "Start Menu", "Programs", "YouTube Music.lnk");
try { // check if shortcut is registered and valid
const shortcutDetails = electron.shell.readShortcutLink(shortcutPath); // throw error if doesn't exist yet
if (shortcutDetails.target !== appLocation || shortcutDetails.appUserModelId !== appID) {
throw "needUpdate";
}
} catch (error) { // if not valid -> Register shortcut
electron.shell.writeShortcutLink(
shortcutPath,
error === "needUpdate" ? "update" : "create",
{
target: appLocation,
cwd: appLocation.slice(0, appLocation.lastIndexOf(path.sep)),
description: "YouTube Music Desktop App - including custom plugins",
appUserModelId: appID
}
);
}
}
}
mainWindow = createMainWindow();
setApplicationMenu(mainWindow);
if (config.get("options.restartOnConfigChanges")) {
@ -269,14 +344,20 @@ app.on("ready", () => {
});
}
// Optimized for Mac OS X
if (is.macOS()) {
if (!config.get("options.appVisible")) {
app.dock.hide();
}
if (config.get("options.hideMenu") && !config.get("options.hideMenuWarned")) {
electron.dialog.showMessageBox(mainWindow, {
type: 'info', title: 'Hide Menu Enabled',
message: "Menu is hidden, use 'Alt' to show it (or 'Escape' if using in-app-menu)"
});
config.set("options.hideMenuWarned", true);
}
var forceQuit = false;
// Optimized for Mac OS X
if (is.macOS() && !config.get("options.appVisible")) {
app.dock.hide();
}
let forceQuit = false;
app.on("before-quit", () => {
forceQuit = true;
});
@ -291,3 +372,65 @@ app.on("ready", () => {
});
}
});
function showUnresponsiveDialog(win, details) {
if (!!details) {
console.log("Unresponsive Error!\n"+JSON.stringify(details, null, "\t"))
}
electron.dialog.showMessageBox(win, {
type: "error",
title: "Window Unresponsive",
message: "The Application is Unresponsive",
details: "We are sorry for the inconvenience! please choose what to do:",
buttons: ["Wait", "Relaunch", "Quit"],
cancelId: 0
}).then( result => {
switch (result.response) {
case 1: //if relaunch - relaunch+exit
app.relaunch();
case 2:
app.quit();
break;
default:
break;
}
});
}
function removeContentSecurityPolicy(
session = electron.session.defaultSession
) {
// Allows defining multiple "onHeadersReceived" listeners
// by enhancing the session.
// Some plugins (e.g. adblocker) also define a "onHeadersReceived" listener
enhanceWebRequest(session);
// Custom listener to tweak the content security policy
session.webRequest.onHeadersReceived(function (details, callback) {
if (
!details.responseHeaders["content-security-policy-report-only"] &&
!details.responseHeaders["content-security-policy"]
)
return callback({ cancel: false });
delete details.responseHeaders["content-security-policy-report-only"];
delete details.responseHeaders["content-security-policy"];
callback({ cancel: false, responseHeaders: details.responseHeaders });
});
// When multiple listeners are defined, apply them all
session.webRequest.setResolver("onHeadersReceived", (listeners) => {
const response = listeners.reduce(
async (accumulator, listener) => {
if (accumulator.cancel) {
return accumulator;
}
const result = await listener.apply();
return { ...accumulator, ...result };
},
{ cancel: false }
);
return response;
});
}

View File

@ -2,6 +2,5 @@ module.exports = {
globals: {
__APP__: undefined, // A different app will be launched in each test environment
},
testEnvironment: "./tests/environment",
testTimeout: 30000, // 30s
};

390
menu.js
View File

@ -1,95 +1,114 @@
const { app, Menu } = require("electron");
const { existsSync } = require("fs");
const path = require("path");
const { app, Menu, dialog } = require("electron");
const is = require("electron-is");
const { getAllPlugins } = require("./plugins/utils");
const config = require("./config");
const mainMenuTemplate = (win) => [
{
label: "Plugins",
submenu: [
...getAllPlugins().map((plugin) => {
return {
label: plugin,
type: "checkbox",
checked: config.plugins.isEnabled(plugin),
click: (item) => {
if (item.checked) {
config.plugins.enable(plugin);
} else {
config.plugins.disable(plugin);
}
},
};
}),
{ type: "separator" },
{
label: "Advanced options",
click: () => {
config.edit();
},
},
],
const prompt = require("custom-electron-prompt");
const promptOptions = require("./providers/prompt-options");
// true only if in-app-menu was loaded on launch
const inAppMenuActive = config.plugins.isEnabled("in-app-menu");
const pluginEnabledMenu = (plugin, label = "", hasSubmenu = false, refreshMenu = undefined) => ({
label: label || plugin,
type: "checkbox",
checked: config.plugins.isEnabled(plugin),
click: (item) => {
if (item.checked) {
config.plugins.enable(plugin);
} else {
config.plugins.disable(plugin);
}
if (hasSubmenu) {
refreshMenu();
}
},
{
label: "Options",
submenu: [
{
label: "Auto-update",
type: "checkbox",
checked: config.get("options.autoUpdates"),
click: (item) => {
config.set("options.autoUpdates", item.checked);
});
const mainMenuTemplate = (win) => {
const refreshMenu = () => {
this.setApplicationMenu(win);
if (inAppMenuActive) {
win.webContents.send("updateMenu", true);
}
}
return [
{
label: "Plugins",
submenu: [
...getAllPlugins().map((plugin) => {
const pluginPath = path.join(__dirname, "plugins", plugin, "menu.js")
if (existsSync(pluginPath)) {
if (!config.plugins.isEnabled(plugin)) {
return pluginEnabledMenu(plugin, "", true, refreshMenu);
}
const getPluginMenu = require(pluginPath);
return {
label: plugin,
submenu: [
pluginEnabledMenu(plugin, "Enabled", true, refreshMenu),
...getPluginMenu(win, config.plugins.getOptions(plugin), refreshMenu),
],
};
}
return pluginEnabledMenu(plugin);
}),
],
},
{
label: "Options",
submenu: [
{
label: "Auto-update",
type: "checkbox",
checked: config.get("options.autoUpdates"),
click: (item) => {
config.set("options.autoUpdates", item.checked);
},
},
},
{
label: "Disable hardware acceleration",
type: "checkbox",
checked: config.get("options.disableHardwareAcceleration"),
click: (item) => {
config.set("options.disableHardwareAcceleration", item.checked);
{
label: "Resume last song when app starts",
type: "checkbox",
checked: config.get("options.resumeOnStart"),
click: (item) => {
config.set("options.resumeOnStart", item.checked);
},
},
},
{
label: "Restart on config changes",
type: "checkbox",
checked: config.get("options.restartOnConfigChanges"),
click: (item) => {
config.set("options.restartOnConfigChanges", item.checked);
{
label: "Remove upgrade button",
type: "checkbox",
checked: config.get("options.removeUpgradeButton"),
click: (item) => {
config.set("options.removeUpgradeButton", item.checked);
},
},
},
{
label: "Reset App cache when app starts",
type: "checkbox",
checked: config.get("options.autoResetAppCache"),
click: (item) => {
config.set("options.autoResetAppCache", item.checked);
},
},
{
label: "Resume last song when app starts",
type: "checkbox",
checked: config.get("options.resumeOnStart"),
click: (item) => {
config.set("options.resumeOnStart", item.checked);
},
},
...(is.windows() || is.linux()
? [
...(is.windows() || is.linux()
? [
{
label: "Hide menu",
type: "checkbox",
checked: config.get("options.hideMenu"),
click: (item) => {
config.set("options.hideMenu", item.checked);
if (item.checked && !config.get("options.hideMenuWarned")) {
dialog.showMessageBox(win, {
type: 'info', title: 'Hide Menu Enabled',
message: "Menu will be hidden on next launch, use 'Alt' to show it (or 'Escape' if using in-app-menu)"
});
}
},
},
]
: []),
...(is.windows() || is.macOS()
? // Only works on Win/Mac
// https://www.electronjs.org/docs/api/app#appsetloginitemsettingssettings-macos-windows
[
]
: []),
...(is.windows() || is.macOS()
? // Only works on Win/Mac
// https://www.electronjs.org/docs/api/app#appsetloginitemsettingssettings-macos-windows
[
{
label: "Start at login",
type: "checkbox",
@ -98,74 +117,155 @@ const mainMenuTemplate = (win) => [
config.set("options.startAtLogin", item.checked);
},
},
]
: []),
{
label: "Tray",
submenu: [
{
label: "Disabled",
type: "radio",
checked: !config.get("options.tray"),
click: () => {
config.set("options.tray", false);
config.set("options.appVisible", true);
]
: []),
{
label: "Tray",
submenu: [
{
label: "Disabled",
type: "radio",
checked: !config.get("options.tray"),
click: () => {
config.set("options.tray", false);
config.set("options.appVisible", true);
},
},
},
{
label: "Enabled + app visible",
type: "radio",
checked:
config.get("options.tray") && config.get("options.appVisible"),
click: () => {
config.set("options.tray", true);
config.set("options.appVisible", true);
{
label: "Enabled + app visible",
type: "radio",
checked:
config.get("options.tray") && config.get("options.appVisible"),
click: () => {
config.set("options.tray", true);
config.set("options.appVisible", true);
},
},
},
{
label: "Enabled + app hidden",
type: "radio",
checked:
config.get("options.tray") && !config.get("options.appVisible"),
click: () => {
config.set("options.tray", true);
config.set("options.appVisible", false);
{
label: "Enabled + app hidden",
type: "radio",
checked:
config.get("options.tray") && !config.get("options.appVisible"),
click: () => {
config.set("options.tray", true);
config.set("options.appVisible", false);
},
},
},
{ type: "separator" },
{
label: "Play/Pause on click",
{ type: "separator" },
{
label: "Play/Pause on click",
type: "checkbox",
checked: config.get("options.trayClickPlayPause"),
click: (item) => {
config.set("options.trayClickPlayPause", item.checked);
},
},
],
},
{ type: "separator" },
{
label: "Advanced options",
submenu: [
{
label: "Proxy",
type: "checkbox",
checked: config.get("options.trayClickPlayPause"),
checked: !!config.get("options.proxy"),
click: (item) => {
config.set("options.trayClickPlayPause", item.checked);
setProxy(item, win);
},
},
{
label: "Disable hardware acceleration",
type: "checkbox",
checked: config.get("options.disableHardwareAcceleration"),
click: (item) => {
config.set("options.disableHardwareAcceleration", item.checked);
},
},
{
label: "Restart on config changes",
type: "checkbox",
checked: config.get("options.restartOnConfigChanges"),
click: (item) => {
config.set("options.restartOnConfigChanges", item.checked);
},
},
{
label: "Reset App cache when app starts",
type: "checkbox",
checked: config.get("options.autoResetAppCache"),
click: (item) => {
config.set("options.autoResetAppCache", item.checked);
},
},
{ type: "separator" },
is.macOS() ?
{
label: "Toggle DevTools",
// Cannot use "toggleDevTools" role in MacOS
click: () => {
const { webContents } = win;
if (webContents.isDevToolsOpened()) {
webContents.closeDevTools();
} else {
const devToolsOptions = {};
webContents.openDevTools(devToolsOptions);
}
},
} :
{ role: "toggleDevTools" },
{
label: "Edit config.json",
click: () => {
config.edit();
},
},
]
},
],
},
{
label: "View",
submenu: [
{ role: "reload" },
{ role: "forceReload" },
{ type: "separator" },
{ role: "zoomIn" },
{ role: "zoomOut" },
{ role: "resetZoom" },
],
},
{
label: "Navigation",
submenu: [
{
label: "Go back",
click: () => {
if (win.webContents.canGoBack()) {
win.webContents.goBack();
}
},
],
},
{ type: "separator" },
{
label: "Toggle DevTools",
// Cannot use "toggleDevTools" role in MacOS
click: () => {
const { webContents } = win;
if (webContents.isDevToolsOpened()) {
webContents.closeDevTools();
} else {
const devToolsOptions = {};
webContents.openDevTools(devToolsOptions);
}
},
},
{
label: "Advanced options",
click: () => {
config.edit();
{
label: "Go forward",
click: () => {
if (win.webContents.canGoForward()) {
win.webContents.goForward();
}
},
},
},
],
},
];
{
label: "Restart App",
click: () => {
app.relaunch();
app.quit();
},
},
{ role: "quit" },
],
},
];
}
module.exports.mainMenuTemplate = mainMenuTemplate;
module.exports.setApplicationMenu = (win) => {
@ -200,3 +300,25 @@ module.exports.setApplicationMenu = (win) => {
const menu = Menu.buildFromTemplate(menuTemplate);
Menu.setApplicationMenu(menu);
};
async function setProxy(item, win) {
const output = await prompt({
title: 'Set Proxy',
label: 'Enter Proxy Address: (leave empty to disable)',
value: config.get("options.proxy"),
type: 'input',
inputAttrs: {
type: 'url',
placeholder: "Example: 'socks5://127.0.0.1:9999"
},
width: 450,
...promptOptions()
}, win);
if (typeof output === "string") {
config.set("options.proxy", output);
item.checked = output !== "";
} else { //user pressed cancel
item.checked = !item.checked; //reset checkbox
}
}

View File

@ -1,7 +1,7 @@
{
"name": "youtube-music",
"productName": "YouTube Music",
"version": "1.11.0",
"version": "1.15.0",
"description": "YouTube Music Desktop App - including custom plugins",
"license": "MIT",
"repository": "th-ch/youtube-music",
@ -37,11 +37,21 @@
"deb",
"rpm"
]
},
"snap": {
"slots": [
{
"mpris": {
"interface": "mpris"
}
}
]
}
},
"scripts": {
"test": "jest",
"start": "electron .",
"start": "NODE_OPTIONS= electron .",
"start:debug": "ELECTRON_ENABLE_LOGGING=1 electron .",
"icon": "rimraf assets/generated && electron-icon-maker --input=assets/youtube-music.png --output=assets/generated",
"generate:package": "node utils/generate-package-json.js",
"postinstall": "yarn run icon && yarn run plugins",
@ -50,53 +60,73 @@
"build:linux": "yarn run clean && electron-builder --linux",
"build:mac": "yarn run clean && electron-builder --mac",
"build:win": "yarn run clean && electron-builder --win",
"plugins": "yarn run plugin:adblocker && yarn run plugin:autoconfirm",
"lint": "xo",
"plugins": "yarn run plugin:adblocker",
"plugin:adblocker": "rimraf plugins/adblocker/ad-blocker-engine.bin && node plugins/adblocker/blocker.js",
"plugin:autoconfirm": "yarn run generate:package YoutubeNonStop",
"release:linux": "yarn run clean && electron-builder --linux -p always -c.snap.publish=github",
"release:mac": "yarn run clean && electron-builder --mac -p always",
"release:win": "yarn run clean && electron-builder --win -p always"
},
"engines": {
"node": ">=12.16.1",
"node": ">=14.0.0",
"npm": "Please use yarn and not npm"
},
"dependencies": {
"@cliqz/adblocker-electron": "^1.20.0",
"@ffmpeg/core": "^0.8.5",
"@ffmpeg/ffmpeg": "^0.9.7",
"YoutubeNonStop": "git://github.com/lawfx/YoutubeNonStop.git#v0.8.1",
"@cliqz/adblocker-electron": "^1.23.1",
"@electron/remote": "^2.0.1",
"@ffmpeg/core": "^0.10.0",
"@ffmpeg/ffmpeg": "^0.10.0",
"async-mutex": "^0.3.2",
"browser-id3-writer": "^4.4.0",
"chokidar": "^3.5.2",
"custom-electron-prompt": "^1.4.0",
"custom-electron-titlebar": "^3.2.9",
"discord-rpc": "^3.2.0",
"downloads-folder": "^3.0.1",
"electron-better-web-request": "^1.0.1",
"electron-debug": "^3.2.0",
"electron-is": "^3.0.0",
"electron-localshortcut": "^3.2.1",
"electron-store": "^7.0.2",
"electron-store": "^7.0.3",
"electron-unhandled": "^3.0.2",
"electron-updater": "^4.3.6",
"filenamify": "^4.2.0",
"node-fetch": "^2.6.1",
"ytdl-core": "^4.4.5"
"electron-updater": "^4.6.3",
"filenamify": "^4.3.0",
"hark": "^1.2.3",
"md5": "^2.3.0",
"mpris-service": "^2.1.2",
"node-fetch": "^2.6.6",
"node-notifier": "^9.0.1",
"ytdl-core": "^4.9.2",
"ytpl": "^2.2.3"
},
"devDependencies": {
"electron": "^11.2.3",
"electron-builder": "^22.9.1",
"electron": "^16.0.5",
"electron-builder": "^22.10.5",
"electron-devtools-installer": "^3.1.1",
"electron-icon-maker": "0.0.5",
"get-port": "^5.1.1",
"jest": "^26.6.3",
"jest": "^27.3.1",
"playwright": "^1.17.1",
"rimraf": "^3.0.2",
"spectron": "^13.0.0",
"xo": "^0.37.1"
"xo": "^0.45.0"
},
"resolutions": {
"glob-parent": "5.1.2",
"minimist": "1.2.5",
"yargs-parser": "18.1.3"
},
"xo": {
"envs": [
"node",
"browser"
]
],
"rules": {
"quotes": [
"error",
"double",
{
"avoidEscape": true,
"allowTemplateLiterals": true
}
]
}
}
}

View File

@ -8,7 +8,9 @@ const SOURCES = [
"https://raw.githubusercontent.com/kbinani/adblock-youtube-ads/master/signed.txt",
// uBlock Origin
"https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/filters.txt",
"https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/filters-2020.txt",
"https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/filters-2021.txt",
// Fanboy Annoyances
"https://secure.fanboy.co.nz/fanboy-annoyance_ubo.txt",
];
const loadAdBlockerEngine = (
@ -31,7 +33,17 @@ const loadAdBlockerEngine = (
...additionalBlockLists,
];
ElectronBlocker.fromLists(fetch, lists, {}, cachingOptions)
ElectronBlocker.fromLists(
fetch,
lists,
{
// when generating the engine for caching, do not load network filters
// So that enhancing the session works as expected
// Allowing to define multiple webRequest listeners
loadNetworkFilters: session !== undefined,
},
cachingOptions
)
.then((blocker) => {
if (session) {
blocker.enableBlockingInSession(session);

View File

@ -0,0 +1,17 @@
const applyCompressor = () => {
const audioContext = new AudioContext();
const compressor = audioContext.createDynamicsCompressor();
compressor.threshold.value = -50;
compressor.ratio.value = 12;
compressor.knee.value = 40;
compressor.attack.value = 0;
compressor.release.value = 0.25;
const source = audioContext.createMediaElementSource(document.querySelector("video"));
source.connect(compressor);
compressor.connect(audioContext.destination);
};
module.exports = () => document.addEventListener('apiLoaded', applyCompressor, { once: true, passive: true });

View File

@ -1,12 +0,0 @@
// Define global chrome object to be compliant with the extension code
global.chrome = {
runtime: {
getManifest: () => ({
version: 1
})
}
};
module.exports = () => {
require("YoutubeNonStop/autoconfirm.js");
};

View File

@ -1,5 +1,5 @@
const { injectCSS } = require("../utils");
const path = require("path");
const { injectCSS } = require("../utils");
module.exports = win => {
injectCSS(win.webContents, path.join(__dirname, "style.css"));

View File

@ -0,0 +1,10 @@
#nav-bar-background,
#header.ytmusic-item-section-renderer,
ytmusic-tabs {
background: rgba(0, 0, 0, 0.3) !important;
backdrop-filter: blur(8px) !important;
}
#nav-bar-divider {
display: none !important;
}

View File

@ -1,25 +1,14 @@
let videoElement = null;
const observer = new MutationObserver((mutations, observer) => {
if (!videoElement) {
videoElement = document.querySelector("video");
}
if (videoElement) {
videoElement.ontimeupdate = () => {
if (videoElement.currentTime === 0 && videoElement.duration !== NaN) {
// auto-confirm-when-paused plugin can interfere here if not disabled!
videoElement.pause();
}
};
}
});
function observeVideoElement() {
observer.observe(document, {
childList: true,
subtree: true,
});
}
module.exports = observeVideoElement;
module.exports = () => {
document.addEventListener('apiLoaded', apiEvent => {
apiEvent.detail.addEventListener('videodatachange', name => {
if (name === 'dataloaded') {
apiEvent.detail.pauseVideo();
document.querySelector('video').ontimeupdate = e => {
e.target.pause();
}
} else {
document.querySelector('video').ontimeupdate = null;
}
})
}, { once: true, passive: true })
};

View File

@ -1,51 +1,148 @@
const Discord = require("discord-rpc");
const { dev } = require("electron-is");
const { dialog, app } = require("electron");
const getSongInfo = require("../../providers/song-info");
const rpc = new Discord.Client({
transport: "ipc",
});
const registerCallback = require("../../providers/song-info");
// Application ID registered by @semvis123
const clientId = "790655993809338398";
module.exports = (win) => {
const registerCallback = getSongInfo(win);
/**
* @typedef {Object} Info
* @property {import('discord-rpc').Client} rpc
* @property {boolean} ready
* @property {import('../../providers/song-info').SongInfo} lastSongInfo
*/
/**
* @type {Info}
*/
const info = {
rpc: null,
ready: false,
lastSongInfo: null,
};
/**
* @type {(() => void)[]}
*/
const refreshCallbacks = [];
const resetInfo = () => {
info.rpc = null;
info.ready = false;
clearTimeout(clearActivity);
if (dev()) console.log("discord disconnected");
refreshCallbacks.forEach(cb => cb());
};
// If the page is ready, register the callback
win.on("ready-to-show", () => {
rpc.on("ready", () => {
// Register the callback
registerCallback((songInfo) => {
// Song information changed, so lets update the rich presence
const activityInfo = {
details: songInfo.title,
state: songInfo.artist,
largeImageKey: "logo",
largeImageText: songInfo.views + " - " + songInfo.likes,
};
let window;
const connect = (showErr = false) => {
if (info.rpc) {
if (dev())
console.log('Attempted to connect with active RPC object');
return;
}
if (songInfo.isPaused) {
// Add an idle icon to show that the song is paused
activityInfo.smallImageKey = "idle";
activityInfo.smallImageText = "idle/paused";
} else {
// Add the start and end time of the song
const songStartTime = Date.now() - songInfo.elapsedSeconds * 1000;
activityInfo.startTimestamp = songStartTime;
activityInfo.endTimestamp =
songStartTime + songInfo.songDuration * 1000;
}
info.rpc = new Discord.Client({
transport: "ipc",
});
info.ready = false;
rpc.setActivity(activityInfo);
});
});
info.rpc.once("connected", () => {
if (dev()) console.log("discord connected");
refreshCallbacks.forEach(cb => cb());
});
info.rpc.once("ready", () => {
info.ready = true;
if (info.lastSongInfo) updateActivity(info.lastSongInfo)
});
info.rpc.once("disconnected", resetInfo);
// Startup the rpc client
rpc
.login({
clientId,
})
.catch(console.error);
// Startup the rpc client
info.rpc.login({ clientId }).catch(err => {
resetInfo();
if (dev()) console.error(err);
if (showErr) dialog.showMessageBox(window, { title: 'Connection failed', message: err.message || String(err), type: 'error' });
});
};
let clearActivity;
/**
* @type {import('../../providers/song-info').songInfoCallback}
*/
let updateActivity;
module.exports = (win, { activityTimoutEnabled, activityTimoutTime, listenAlong }) => {
window = win;
// We get multiple events
// Next song: PAUSE(n), PAUSE(n+1), PLAY(n+1)
// Skip time: PAUSE(N), PLAY(N)
updateActivity = songInfo => {
if (songInfo.title.length === 0 && songInfo.artist.length === 0) {
return;
}
info.lastSongInfo = songInfo;
// stop the clear activity timout
clearTimeout(clearActivity);
// stop early if discord connection is not ready
// do this after clearTimeout to avoid unexpected clears
if (!info.rpc || !info.ready) {
return;
}
// clear directly if timeout is 0
if (songInfo.isPaused && activityTimoutEnabled && activityTimoutTime === 0) {
info.rpc.clearActivity().catch(console.error);
return;
}
// Song information changed, so lets update the rich presence
// @see https://discord.com/developers/docs/topics/gateway#activity-object
// not all options are transfered through https://github.com/discordjs/RPC/blob/6f83d8d812c87cb7ae22064acd132600407d7d05/src/client.js#L518-530
const activityInfo = {
type: 2, // Listening, addressed in https://github.com/discordjs/RPC/pull/149
details: songInfo.title,
state: songInfo.artist,
largeImageKey: songInfo.imageSrc,
largeImageText: [
songInfo.uploadDate,
songInfo.views.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") + " views",
].join(' || '),
buttons: listenAlong ? [
{ label: "Listen Along", url: songInfo.url },
] : undefined,
};
if (songInfo.isPaused) {
// Add an idle icon to show that the song is paused
activityInfo.smallImageKey = "idle";
activityInfo.smallImageText = "idle/paused";
// Set start the timer so the activity gets cleared after a while if enabled
if (activityTimoutEnabled)
clearActivity = setTimeout(() => info.rpc.clearActivity().catch(console.error), activityTimoutTime ?? 10000);
} else {
// Add the start and end time of the song
const songStartTime = Date.now() - songInfo.elapsedSeconds * 1000;
activityInfo.startTimestamp = songStartTime;
activityInfo.endTimestamp =
songStartTime + songInfo.songDuration * 1000;
}
info.rpc.setActivity(activityInfo).catch(console.error);
};
// If the page is ready, register the callback
win.once("ready-to-show", () => {
registerCallback(updateActivity);
connect();
});
app.on('window-all-closed', module.exports.clear)
};
module.exports.clear = () => {
if (info.rpc) info.rpc.clearActivity();
clearTimeout(clearActivity);
};
module.exports.connect = connect;
module.exports.registerRefresh = (cb) => refreshCallbacks.push(cb);
module.exports.isConnected = () => info.rpc !== null;

47
plugins/discord/menu.js Normal file
View File

@ -0,0 +1,47 @@
const { setOptions } = require("../../config/plugins");
const { edit } = require("../../config");
const { clear, connect, registerRefresh, isConnected } = require("./back");
let hasRegisterred = false;
module.exports = (win, options, refreshMenu) => {
if (!hasRegisterred) {
registerRefresh(refreshMenu);
hasRegisterred = true;
}
return [
{
label: isConnected() ? "Connected" : "Reconnect",
enabled: !isConnected(),
click: connect,
},
{
label: "Clear activity",
click: clear,
},
{
label: "Clear activity after timeout",
type: "checkbox",
checked: options.activityTimoutEnabled,
click: (item) => {
options.activityTimoutEnabled = item.checked;
setOptions('discord', options);
},
},
{
label: "Listen Along",
type: "checkbox",
checked: options.listenAlong,
click: (item) => {
options.listenAlong = item.checked;
setOptions('discord', options);
},
},
{
label: "Set timeout time in config",
// open config.json
click: edit,
},
];
};

View File

@ -2,6 +2,7 @@ const CHANNEL = "downloader";
const ACTIONS = {
ERROR: "error",
METADATA: "metadata",
PROGRESS: "progress",
};
module.exports = {

View File

@ -4,59 +4,73 @@ const { join } = require("path");
const ID3Writer = require("browser-id3-writer");
const { dialog, ipcMain } = require("electron");
const getSongInfo = require("../../providers/song-info");
const registerCallback = require("../../providers/song-info");
const { injectCSS, listenAction } = require("../utils");
const { cropMaxWidth } = require("./utils");
const { ACTIONS, CHANNEL } = require("./actions.js");
const { getImage } = require("../../providers/song-info");
const sendError = (win, err) => {
const dialogOpts = {
const sendError = (win, error) => {
win.setProgressBar(-1); // close progress bar
dialog.showMessageBox({
type: "info",
buttons: ["OK"],
title: "Error in download!",
message: "Argh! Apologies, download failed…",
detail: err.toString(),
};
dialog.showMessageBox(dialogOpts);
detail: error.toString(),
});
};
let metadata = {};
let nowPlayingMetadata = {};
function handle(win) {
injectCSS(win.webContents, join(__dirname, "style.css"));
const registerCallback = getSongInfo(win);
registerCallback((info) => {
metadata = info;
nowPlayingMetadata = info;
});
listenAction(CHANNEL, (event, action, error) => {
listenAction(CHANNEL, (event, action, arg) => {
switch (action) {
case ACTIONS.ERROR:
sendError(win, error);
case ACTIONS.ERROR: // arg = error
sendError(win, arg);
break;
case ACTIONS.METADATA:
event.returnValue = JSON.stringify(metadata);
event.returnValue = JSON.stringify(nowPlayingMetadata);
break;
case ACTIONS.PROGRESS: // arg = progress
win.setProgressBar(arg);
break;
default:
console.log("Unknown action: " + action);
}
});
ipcMain.on("add-metadata", (event, filePath, songBuffer) => {
ipcMain.on("add-metadata", async (event, filePath, songBuffer, currentMetadata) => {
let fileBuffer = songBuffer;
const songMetadata = currentMetadata.imageSrcYTPL ? // This means metadata come from ytpl.getInfo();
{
...currentMetadata,
image: cropMaxWidth(await getImage(currentMetadata.imageSrcYTPL))
} :
{ ...nowPlayingMetadata, ...currentMetadata };
try {
const coverBuffer = metadata.image.toPNG();
const coverBuffer = songMetadata.image && !songMetadata.image.isEmpty() ?
songMetadata.image.toPNG() : null;
const writer = new ID3Writer(songBuffer);
// Create the metadata tags
writer
.setFrame("TIT2", metadata.title)
.setFrame("TPE1", [metadata.artist])
.setFrame("APIC", {
.setFrame("TIT2", songMetadata.title)
.setFrame("TPE1", [songMetadata.artist]);
if (coverBuffer) {
writer.setFrame("APIC", {
type: 3,
data: coverBuffer,
description: "",
description: ""
});
}
writer.addTag();
fileBuffer = Buffer.from(writer.arrayBuffer);
} catch (error) {
@ -70,3 +84,4 @@ function handle(win) {
}
module.exports = handle;
module.exports.sendError = sendError;

View File

@ -1,5 +1,6 @@
const { contextBridge } = require("electron");
const { defaultConfig } = require("../../config");
const { getSongMenu } = require("../../providers/dom-elements");
const { ElementFromFile, templatePath, triggerAction } = require("../utils");
const { ACTIONS, CHANNEL } = require("./actions.js");
@ -24,6 +25,7 @@ const observer = new MutationObserver((mutations, observer) => {
});
const reinit = () => {
triggerAction(CHANNEL, ACTIONS.PROGRESS, -1); // closes progress bar
if (!progress) {
console.warn("Cannot update progress");
} else {
@ -31,27 +33,45 @@ const reinit = () => {
}
};
const baseUrl = defaultConfig.url;
// TODO: re-enable once contextIsolation is set to true
// contextBridge.exposeInMainWorld("downloader", {
// download: () => {
global.download = () => {
const videoUrl = window.location.href;
triggerAction(CHANNEL, ACTIONS.PROGRESS, 2); // starts with indefinite progress bar
let metadata;
let videoUrl = getSongMenu()
// selector of first button which is always "Start Radio"
?.querySelector('ytmusic-menu-navigation-item-renderer.iron-selected[tabindex="0"] #navigation-endpoint')
?.getAttribute("href");
if (videoUrl) {
videoUrl = baseUrl + "/" + videoUrl;
metadata = null;
} else {
metadata = global.songInfo;
videoUrl = metadata.url || window.location.href;
}
downloadVideoToMP3(
videoUrl,
(feedback) => {
(feedback, ratio = undefined) => {
if (!progress) {
console.warn("Cannot update progress");
} else {
progress.innerHTML = feedback;
}
if (ratio) {
triggerAction(CHANNEL, ACTIONS.PROGRESS, ratio);
}
},
(error) => {
triggerAction(CHANNEL, ACTIONS.ERROR, error);
reinit();
},
reinit,
pluginOptions
pluginOptions,
metadata
);
};
// });

125
plugins/downloader/menu.js Normal file
View File

@ -0,0 +1,125 @@
const { existsSync, mkdirSync } = require("fs");
const { join } = require("path");
const { dialog, ipcMain } = require("electron");
const is = require("electron-is");
const ytpl = require("ytpl");
const chokidar = require('chokidar');
const { setOptions } = require("../../config/plugins");
const { sendError } = require("./back");
const { defaultMenuDownloadLabel, getFolder, presets } = require("./utils");
let downloadLabel = defaultMenuDownloadLabel;
let playingPlaylistId = undefined;
let callbackIsRegistered = false;
module.exports = (win, options) => {
if (!callbackIsRegistered) {
ipcMain.on("video-src-changed", async (_, data) => {
playingPlaylistId = JSON.parse(data)?.videoDetails?.playlistId;
});
callbackIsRegistered = true;
}
return [
{
label: downloadLabel,
click: async () => {
const currentPagePlaylistId = new URL(win.webContents.getURL()).searchParams.get("list");
const playlistId = currentPagePlaylistId || playingPlaylistId;
if (!playlistId) {
sendError(win, new Error("No playlist ID found"));
return;
}
console.log(`trying to get playlist ID: '${playlistId}'`);
let playlist;
try {
playlist = await ytpl(playlistId, {
limit: options.playlistMaxItems || Infinity,
});
} catch (e) {
sendError(win, e);
return;
}
const playlistTitle = playlist.title;
const folder = getFolder(options.downloadFolder);
const playlistFolder = join(folder, playlistTitle);
if (existsSync(playlistFolder)) {
sendError(
win,
new Error(`The folder ${playlistFolder} already exists`)
);
return;
}
mkdirSync(playlistFolder, { recursive: true });
dialog.showMessageBox({
type: "info",
buttons: ["OK"],
title: "Started Download",
message: `Downloading Playlist "${playlistTitle}"`,
detail: `(${playlist.items.length} songs)`,
});
if (is.dev()) {
console.log(
`Downloading playlist "${playlistTitle}" (${playlist.items.length} songs)`
);
}
const steps = 1 / playlist.items.length;
let progress = 0;
win.setProgressBar(2); // starts with indefinite bar
let dirWatcher = chokidar.watch(playlistFolder);
dirWatcher.on('add', () => {
progress += steps;
if (progress >= 0.9999) {
win.setProgressBar(-1); // close progress bar
dirWatcher.close().then(() => dirWatcher = null);
} else {
win.setProgressBar(progress);
}
});
playlist.items.forEach((song) => {
win.webContents.send(
"downloader-download-playlist",
song.url,
playlistTitle,
options
);
});
},
},
{
label: "Choose download folder",
click: () => {
let result = dialog.showOpenDialogSync({
properties: ["openDirectory", "createDirectory"],
defaultPath: getFolder(options.downloadFolder),
});
if (result) {
options.downloadFolder = result[0];
setOptions("downloader", options);
} // else = user pressed cancel
},
},
{
label: "Presets",
submenu: Object.keys(presets).map((preset) => ({
label: preset,
type: "radio",
click: () => {
options.preset = preset;
setOptions("downloader", options);
},
checked: options.preset === preset || presets[preset] === undefined,
})),
},
];
};

View File

@ -6,8 +6,16 @@
cursor: pointer;
}
.menu-item > .yt-simple-endpoint:hover {
background-color: var(--ytmusic-menu-item-hover-background-color);
}
.menu-icon {
flex: var(--ytmusic-menu-item-icon_-_flex);
margin: var(--ytmusic-menu-item-icon_-_margin);
fill: var(--ytmusic-menu-item-icon_-_fill);
stroke: var(--iron-icon-stroke-color, none);
width: var(--iron-icon-width, 24px);
height: var(--iron-icon-height, 24px);
animation: var(--iron-icon_-_animation);
}

View File

@ -1,5 +1,5 @@
<div
class="menu-item ytmusic-menu-popup-renderer"
class="style-scope menu-item ytmusic-menu-popup-renderer"
role="option"
tabindex="-1"
aria-disabled="false"
@ -7,31 +7,39 @@
onclick="download()"
>
<div
class="menu-icon yt-icon-container yt-icon ytmusic-toggle-menu-service-item-renderer"
id="navigation-endpoint"
class="yt-simple-endpoint style-scope ytmusic-menu-navigation-item-renderer"
tabindex="-1"
>
<svg
viewBox="0 0 24 24"
preserveAspectRatio="xMidYMid meet"
focusable="false"
class="style-scope yt-icon"
style="pointer-events: none; display: block; width: 100%; height: 100%;"
<div
class="icon menu-icon style-scope ytmusic-menu-navigation-item-renderer"
>
<g class="style-scope yt-icon">
<path
d="M25.462,19.105v6.848H4.515v-6.848H0.489v8.861c0,1.111,0.9,2.012,2.016,2.012h24.967c1.115,0,2.016-0.9,2.016-2.012v-8.861H25.462z"
class="style-scope yt-icon"
/>
<path
d="M14.62,18.426l-5.764-6.965c0,0-0.877-0.828,0.074-0.828s3.248,0,3.248,0s0-0.557,0-1.416c0-2.449,0-6.906,0-8.723c0,0-0.129-0.494,0.615-0.494c0.75,0,4.035,0,4.572,0c0.536,0,0.524,0.416,0.524,0.416c0,1.762,0,6.373,0,8.742c0,0.768,0,1.266,0,1.266s1.842,0,2.998,0c1.154,0,0.285,0.867,0.285,0.867s-4.904,6.51-5.588,7.193C15.092,18.979,14.62,18.426,14.62,18.426z"
class="style-scope yt-icon"
/>
</g>
</svg>
</div>
<div
class="text style-scope ytmusic-toggle-menu-service-item-renderer"
id="ytmcustom-download"
>
Download
<svg
viewBox="0 0 24 24"
preserveAspectRatio="xMidYMid meet"
focusable="false"
class="style-scope yt-icon"
style="pointer-events: none; display: block; width: 100%; height: 100%"
>
<g class="style-scope yt-icon">
<path
d="M25.462,19.105v6.848H4.515v-6.848H0.489v8.861c0,1.111,0.9,2.012,2.016,2.012h24.967c1.115,0,2.016-0.9,2.016-2.012v-8.861H25.462z"
class="style-scope yt-icon"
fill="#aaaaaa"
/>
<path
d="M14.62,18.426l-5.764-6.965c0,0-0.877-0.828,0.074-0.828s3.248,0,3.248,0s0-0.557,0-1.416c0-2.449,0-6.906,0-8.723c0,0-0.129-0.494,0.615-0.494c0.75,0,4.035,0,4.572,0c0.536,0,0.524,0.416,0.524,0.416c0,1.762,0,6.373,0,8.742c0,0.768,0,1.266,0,1.266s1.842,0,2.998,0c1.154,0,0.285,0.867,0.285,0.867s-4.904,6.51-5.588,7.193C15.092,18.979,14.62,18.426,14.62,18.426z"
class="style-scope yt-icon"
fill="#aaaaaa"
/>
</g>
</svg>
</div>
<div
class="text style-scope ytmusic-menu-navigation-item-renderer"
id="ytmcustom-download"
>
Download
</div>
</div>
</div>

View File

@ -0,0 +1,39 @@
const electron = require("electron");
module.exports.getFolder = customFolder => customFolder || electron.app.getPath("downloads");
module.exports.defaultMenuDownloadLabel = "Download playlist";
const orderedQualityList = ["maxresdefault", "hqdefault", "mqdefault", "sdddefault"];
module.exports.urlToJPG = (imgUrl, videoId) => {
if (!imgUrl || imgUrl.includes(".jpg")) return imgUrl;
//it will almost never get further than hqdefault
for (const quality of orderedQualityList) {
if (imgUrl.includes(quality)) {
return `https://img.youtube.com/vi/${videoId}/${quality}.jpg`;
}
}
return `https://img.youtube.com/vi/${videoId}/default.jpg`;
}
module.exports.cropMaxWidth = (image) => {
const imageSize = image.getSize();
// standart youtube artwork width with margins from both sides is 280 + 720 + 280
if (imageSize.width === 1280 && imageSize.height === 720) {
return image.crop({
x: 280,
y: 0,
width: 720,
height: 720
});
}
return image;
}
// Presets for FFmpeg
module.exports.presets = {
"None (defaults to mp3)": undefined,
opus: {
extension: "opus",
ffmpegArgs: ["-acodec", "libopus"],
},
};

View File

@ -1,8 +1,9 @@
const { randomBytes } = require("crypto");
const { join } = require("path");
const downloadsFolder = require("downloads-folder");
const Mutex = require("async-mutex").Mutex;
const { ipcRenderer } = require("electron");
const remote = require('@electron/remote');
const is = require("electron-is");
const filenamify = require("filenamify");
@ -12,8 +13,10 @@ const filenamify = require("filenamify");
const FFmpeg = require("@ffmpeg/ffmpeg/dist/ffmpeg.min");
const ytdl = require("ytdl-core");
const { triggerActionSync } = require("../utils");
const { triggerAction, triggerActionSync } = require("../utils");
const { ACTIONS, CHANNEL } = require("./actions.js");
const { presets, urlToJPG } = require("./utils");
const { cleanupName } = require("../../providers/song-info");
const { createFFmpeg } = FFmpeg;
const ffmpeg = createFFmpeg({
@ -21,16 +24,34 @@ const ffmpeg = createFFmpeg({
logger: () => {}, // console.log,
progress: () => {}, // console.log,
});
const ffmpegMutex = new Mutex();
const downloadVideoToMP3 = (
const downloadVideoToMP3 = async (
videoUrl,
sendFeedback,
sendError,
reinit,
options
options,
metadata = undefined,
subfolder = ""
) => {
sendFeedback("Downloading…");
if (metadata === null) {
const { videoDetails } = await ytdl.getInfo(videoUrl);
const thumbnails = videoDetails?.thumbnails;
metadata = {
artist:
videoDetails?.media?.artist ||
cleanupName(videoDetails?.author?.name) ||
"",
title: videoDetails?.media?.song || videoDetails?.title || "",
imageSrcYTPL: thumbnails ?
urlToJPG(thumbnails[thumbnails.length - 1].url, videoDetails?.videoId)
: ""
}
}
let videoName = "YouTube Music - Unknown title";
let videoReadableStream;
try {
@ -50,9 +71,10 @@ const downloadVideoToMP3 = (
.on("data", (chunk) => {
chunks.push(chunk);
})
.on("progress", (chunkLength, downloaded, total) => {
const progress = Math.floor((downloaded / total) * 100);
sendFeedback("Download: " + progress + "%");
.on("progress", (_chunkLength, downloaded, total) => {
const ratio = downloaded / total;
const progress = Math.floor(ratio * 100);
sendFeedback("Download: " + progress + "%", ratio);
})
.on("info", (info, format) => {
videoName = info.videoDetails.title.replace("|", "").toString("ascii");
@ -66,9 +88,18 @@ const downloadVideoToMP3 = (
}
})
.on("error", sendError)
.on("end", () => {
.on("end", async () => {
const buffer = Buffer.concat(chunks);
toMP3(videoName, buffer, sendFeedback, sendError, reinit, options);
await toMP3(
videoName,
buffer,
sendFeedback,
sendError,
reinit,
options,
metadata,
subfolder
);
});
};
@ -78,14 +109,18 @@ const toMP3 = async (
sendFeedback,
sendError,
reinit,
options
options,
existingMetadata = undefined,
subfolder = ""
) => {
const convertOptions = { ...presets[options.preset], ...options };
const safeVideoName = randomBytes(32).toString("hex");
const extension = options.extension || "mp3";
const extension = convertOptions.extension || "mp3";
const releaseFFmpegMutex = await ffmpegMutex.acquire();
try {
if (!ffmpeg.isLoaded()) {
sendFeedback("Loading…");
sendFeedback("Loading…", 2); // indefinite progress bar after download
await ffmpeg.load();
}
@ -93,33 +128,39 @@ const toMP3 = async (
ffmpeg.FS("writeFile", safeVideoName, buffer);
sendFeedback("Converting…");
const metadata = getMetadata();
const metadata = existingMetadata || getMetadata();
await ffmpeg.run(
"-i",
safeVideoName,
...getFFmpegMetadataArgs(metadata),
...(options.ffmpegArgs || []),
...(convertOptions.ffmpegArgs || []),
safeVideoName + "." + extension
);
const folder = options.downloadFolder || downloadsFolder();
const name = metadata
? `${metadata.artist} - ${metadata.title}`
const folder = options.downloadFolder || remote.app.getPath("downloads");
const name = metadata.title
? `${metadata.artist ? `${metadata.artist} - ` : ""}${metadata.title}`
: videoName;
const filename = filenamify(name + "." + extension, {
replacement: "_",
maxLength: 255,
});
const filePath = join(folder, subfolder, filename);
const fileBuffer = ffmpeg.FS("readFile", safeVideoName + "." + extension);
// Add the metadata
sendFeedback("Adding metadata…");
ipcRenderer.send(
"add-metadata",
join(folder, filename),
ffmpeg.FS("readFile", safeVideoName + "." + extension)
);
ipcRenderer.send("add-metadata", filePath, fileBuffer, {
artist: metadata.artist,
title: metadata.title,
imageSrcYTPL: metadata.imageSrcYTPL
});
ipcRenderer.once("add-metadata-done", reinit);
} catch (e) {
sendError(e);
} finally {
releaseFFmpegMutex();
}
};
@ -133,13 +174,28 @@ const getFFmpegMetadataArgs = (metadata) => {
}
return [
"-metadata",
`title=${metadata.title}`,
"-metadata",
`artist=${metadata.artist}`,
...(metadata.title ? ["-metadata", `title=${metadata.title}`] : []),
...(metadata.artist ? ["-metadata", `artist=${metadata.artist}`] : []),
];
};
module.exports = {
downloadVideoToMP3,
};
ipcRenderer.on(
"downloader-download-playlist",
(_, url, playlistFolder, options) => {
downloadVideoToMP3(
url,
() => {},
(error) => {
triggerAction(CHANNEL, ACTIONS.ERROR, error);
},
() => {},
options,
null,
playlistFolder
);
}
);

View File

@ -0,0 +1,47 @@
// "Youtube Music fix volume ratio 0.4" by Marco Pfeiffer
// https://greasyfork.org/en/scripts/397686-youtube-music-fix-volume-ratio/
const exponentialVolume = () => {
// manipulation exponent, higher value = lower volume
// 3 is the value used by pulseaudio, which Barteks2x figured out this gist here: https://gist.github.com/Barteks2x/a4e189a36a10c159bb1644ffca21c02a
// 0.05 (or 5%) is the lowest you can select in the UI which with an exponent of 3 becomes 0.000125 or 0.0125%
const EXPONENT = 3;
const storedOriginalVolumes = new WeakMap();
const { get, set } = Object.getOwnPropertyDescriptor(
HTMLMediaElement.prototype,
"volume"
);
Object.defineProperty(HTMLMediaElement.prototype, "volume", {
get() {
const lowVolume = get.call(this);
const calculatedOriginalVolume = lowVolume ** (1 / EXPONENT);
// The calculated value has some accuracy issues which can lead to problems for implementations that expect exact values.
// To avoid this, I'll store the unmodified volume to return it when read here.
// This mostly solves the issue, but the initial read has no stored value and the volume can also change though external influences.
// To avoid ill effects, I check if the stored volume is somewhere in the same range as the calculated volume.
const storedOriginalVolume = storedOriginalVolumes.get(this);
const storedDeviation = Math.abs(
storedOriginalVolume - calculatedOriginalVolume
);
const originalVolume =
storedDeviation < 0.01
? storedOriginalVolume
: calculatedOriginalVolume;
return originalVolume;
},
set(originalVolume) {
const lowVolume = originalVolume ** EXPONENT;
storedOriginalVolumes.set(this, originalVolume);
set.call(this, lowVolume);
},
});
};
module.exports = () =>
document.addEventListener("apiLoaded", exponentialVolume, {
once: true,
passive: true,
});

View File

@ -0,0 +1,33 @@
const path = require("path");
const electronLocalshortcut = require("electron-localshortcut");
const config = require("../../config");
const { injectCSS } = require("../utils");
//tracks menu visibility
let visible = true;
module.exports = (win) => {
// css for custom scrollbar + disable drag area(was causing bugs)
injectCSS(win.webContents, path.join(__dirname, "style.css"));
win.once("ready-to-show", () => {
//register keyboard shortcut && hide menu if hideMenu is enabled
if (config.get("options.hideMenu")) {
electronLocalshortcut.register(win, "Esc", () => {
setMenuVisibility(!visible);
});
}
});
win.webContents.once("did-finish-load", () => {
// fix bug with menu not applying on start when no internet connection available
setMenuVisibility(!config.get("options.hideMenu"));
});
function setMenuVisibility(value) {
visible = value;
win.webContents.send("updateMenu", visible);
}
};

View File

@ -0,0 +1,33 @@
const { ipcRenderer } = require("electron");
const { Menu } = require("@electron/remote");
const customTitlebar = require("custom-electron-titlebar");
function $(selector) { return document.querySelector(selector); }
module.exports = () => {
const bar = new customTitlebar.Titlebar({
backgroundColor: customTitlebar.Color.fromHex("#050505"),
itemBackgroundColor: customTitlebar.Color.fromHex("#121212"),
});
bar.updateTitle(" ");
document.title = "Youtube Music";
ipcRenderer.on("updateMenu", function (_event, showMenu) {
bar.updateMenu(showMenu ? Menu.getApplicationMenu() : null);
});
// Increases the right margin of Navbar background when the scrollbar is visible to avoid blocking it (z-index doesn't affect it)
document.addEventListener('apiLoaded', () => {
setNavbarMargin();
const playPageObserver = new MutationObserver(setNavbarMargin);
playPageObserver.observe($('ytmusic-app-layout'), { attributeFilter: ['player-page-open_', 'playerPageOpen_'] })
}, { once: true, passive: true })
};
function setNavbarMargin() {
$('#nav-bar-background').style.right =
$('ytmusic-app-layout').playerPageOpen_ ?
'0px' :
'12px';
}

View File

@ -0,0 +1,73 @@
/* increase font size for menu and menuItems */
.titlebar,
.menubar-menu-container .action-label {
font-size: 14px !important;
}
/* fixes nav-bar-background opacity bug, reposition it, and allows clicking scrollbar through it */
#nav-bar-background {
opacity: 1 !important;
pointer-events: none !important;
position: sticky !important;
top: 0 !important;
height: 75px !important;
}
/* remove window dragging for nav bar (conflict with titlebar drag) */
ytmusic-nav-bar,
.tab-titleiron-icon,
ytmusic-pivot-bar-item-renderer {
-webkit-app-region: unset !important;
}
/* move up item selection renderers */
ytmusic-item-section-renderer.stuck #header.ytmusic-item-section-renderer,
ytmusic-tabs.stuck {
top: calc(var(--ytmusic-nav-bar-height) - 15px) !important;
}
/* fix weird positioning in search screen*/
ytmusic-header-renderer.ytmusic-search-page {
position: unset !important;
}
/* Move navBar downwards */
ytmusic-nav-bar[slot="nav-bar"] {
top: 17px !important;
}
/* fix page progress bar position*/
yt-page-navigation-progress,
#progress.yt-page-navigation-progress {
top: 30px !important;
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 12px;
background-color: #030303;
border-radius: 100px;
-moz-border-radius: 100px;
-webkit-border-radius: 100px;
}
/* hover effect for both scrollbar area, and scrollbar 'thumb' */
::-webkit-scrollbar:hover {
background-color: rgba(15, 15, 15, 0.699);
}
/* The scrollbar 'thumb' ...that marque oval shape in a scrollbar */
::-webkit-scrollbar-thumb:vertical {
background-clip: padding-box;
border: 2px solid rgba(0, 0, 0, 0);
background: #3a3a3a;
border-radius: 100px;
-moz-border-radius: 100px;
-webkit-border-radius: 100px;
}
::-webkit-scrollbar-thumb:vertical:active {
background: #4d4c4c; /* Some darker color when you click it */
border-radius: 100px;
-moz-border-radius: 100px;
-webkit-border-radius: 100px;
}

160
plugins/last-fm/back.js Normal file
View File

@ -0,0 +1,160 @@
const fetch = require('node-fetch');
const md5 = require('md5');
const { shell } = require('electron');
const { setOptions } = require('../../config/plugins');
const registerCallback = require('../../providers/song-info');
const defaultConfig = require('../../config/defaults');
const createFormData = params => {
// creates the body for in the post request
const formData = new URLSearchParams();
for (const key in params) {
formData.append(key, params[key]);
}
return formData;
}
const createQueryString = (params, api_sig) => {
// creates a querystring
const queryData = [];
params.api_sig = api_sig;
for (const key in params) {
queryData.push(`${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`);
}
return '?'+queryData.join('&');
}
const createApiSig = (params, secret) => {
// this function creates the api signature, see: https://www.last.fm/api/authspec
const keys = [];
for (const key in params) {
keys.push(key);
}
keys.sort();
let sig = '';
for (const key of keys) {
if (String(key) === 'format')
continue
sig += `${key}${params[key]}`;
}
sig += secret;
sig = md5(sig);
return sig;
}
const createToken = async ({ api_key, api_root, secret }) => {
// creates and stores the auth token
const data = {
method: 'auth.gettoken',
api_key: api_key,
format: 'json'
};
const api_sig = createApiSig(data, secret);
let response = await fetch(`${api_root}${createQueryString(data, api_sig)}`);
response = await response.json();
return response?.token;
}
const authenticate = async config => {
// asks the user for authentication
config.token = await createToken(config);
setOptions('last-fm', config);
shell.openExternal(`https://www.last.fm/api/auth/?api_key=${config.api_key}&token=${config.token}`);
return config;
}
const getAndSetSessionKey = async config => {
// get and store the session key
const data = {
api_key: config.api_key,
format: 'json',
method: 'auth.getsession',
token: config.token,
};
const api_sig = createApiSig(data, config.secret);
let res = await fetch(`${config.api_root}${createQueryString(data, api_sig)}`);
res = await res.json();
if (res.error)
await authenticate(config);
config.session_key = res?.session?.key;
setOptions('last-fm', config);
return config;
}
const postSongDataToAPI = async (songInfo, config, data) => {
// this sends a post request to the api, and adds the common data
if (!config.session_key)
await getAndSetSessionKey(config);
const postData = {
track: songInfo.title,
duration: songInfo.songDuration,
artist: songInfo.artist,
api_key: config.api_key,
sk: config.session_key,
format: 'json',
...data,
};
postData.api_sig = createApiSig(postData, config.secret);
fetch('https://ws.audioscrobbler.com/2.0/', {method: 'POST', body: createFormData(postData)})
.catch(res => {
if (res.response.data.error == 9) {
// session key is invalid, so remove it from the config and reauthenticate
config.session_key = undefined;
setOptions('last-fm', config);
authenticate(config);
}
});
}
const addScrobble = (songInfo, config) => {
// this adds one scrobbled song to last.fm
const data = {
method: 'track.scrobble',
timestamp: ~~((Date.now() - songInfo.elapsedSeconds) / 1000),
};
postSongDataToAPI(songInfo, config, data);
}
const setNowPlaying = (songInfo, config) => {
// this sets the now playing status in last.fm
const data = {
method: 'track.updateNowPlaying',
};
postSongDataToAPI(songInfo, config, data);
}
// this will store the timeout that will trigger addScrobble
let scrobbleTimer = undefined;
const lastfm = async (_win, config) => {
if (!config.api_root) {
// settings are not present, creating them with the default values
config = defaultConfig.plugins['last-fm'];
config.enabled = true;
setOptions('last-fm', config);
}
if (!config.session_key) {
// not authenticated
config = await getAndSetSessionKey(config);
}
registerCallback( songInfo => {
// set remove the old scrobble timer
clearTimeout(scrobbleTimer);
if (!songInfo.isPaused) {
setNowPlaying(songInfo, config);
// scrobble when the song is half way through, or has passed the 4 minute mark
const scrobbleTime = Math.min(Math.ceil(songInfo.songDuration / 2), 4 * 60);
if (scrobbleTime > songInfo.elapsedSeconds) {
// scrobble still needs to happen
const timeToWait = (scrobbleTime - songInfo.elapsedSeconds) * 1000;
scrobbleTimer = setTimeout(addScrobble, timeToWait, songInfo, config);
}
}
});
}
module.exports = lastfm;

View File

@ -0,0 +1,52 @@
const { join } = require("path");
const { ipcMain } = require("electron");
const is = require("electron-is");
const fetch = require("node-fetch");
const { cleanupName } = require("../../providers/song-info");
const { injectCSS } = require("../utils");
module.exports = async (win) => {
injectCSS(win.webContents, join(__dirname, "style.css"));
ipcMain.on("search-genius-lyrics", async (event, extractedSongInfo) => {
const metadata = JSON.parse(extractedSongInfo);
const queryString = `${cleanupName(metadata.artist)} ${cleanupName(
metadata.title
)}`;
let response = await fetch(
`https://genius.com/api/search/multi?per_page=5&q=${encodeURI(
queryString
)}`
);
if (!response.ok) {
event.returnValue = null;
return;
}
const info = await response.json();
let url = "";
try {
url = info.response.sections.filter(
(section) => section.type === "song"
)[0].hits[0].result.url;
} catch {
event.returnValue = null;
return;
}
if (is.dev()) {
console.log("Fetching lyrics from Genius:", url);
}
response = await fetch(url);
if (!response.ok) {
event.returnValue = null;
return;
}
event.returnValue = await response.text();
});
};

View File

@ -0,0 +1,67 @@
const { ipcRenderer } = require("electron");
const is = require("electron-is");
module.exports = () => {
ipcRenderer.on("update-song-info", (_, extractedSongInfo) => {
const lyricsTab = document.querySelector('tp-yt-paper-tab[tabindex="-1"]');
// Check if disabled
if (!lyricsTab || !lyricsTab.hasAttribute("disabled")) {
return;
}
const html = ipcRenderer.sendSync(
"search-genius-lyrics",
extractedSongInfo
);
if (!html) {
return;
} else if (is.dev()) {
console.log("Fetched lyrics from Genius");
}
const wrapper = document.createElement("div");
wrapper.innerHTML = html;
const lyricsSelector1 = wrapper.querySelector(".lyrics");
const lyricsSelector2 = wrapper.querySelector(
'[class^="Lyrics__Container"]'
);
const lyrics = lyricsSelector1
? lyricsSelector1.innerHTML
: lyricsSelector2
? lyricsSelector2.innerHTML
: null;
if (!lyrics) {
return;
}
lyricsTab.removeAttribute("disabled");
lyricsTab.removeAttribute("aria-disabled");
document.querySelector("tp-yt-paper-tab").onclick = () => {
lyricsTab.removeAttribute("disabled");
lyricsTab.removeAttribute("aria-disabled");
};
lyricsTab.onclick = () => {
const tabContainer = document.querySelector("ytmusic-tab-renderer");
const observer = new MutationObserver((_, observer) => {
const lyricsContainer = document.querySelector(
'[page-type="MUSIC_PAGE_TYPE_TRACK_LYRICS"] > ytmusic-message-renderer'
);
if (lyricsContainer) {
lyricsContainer.innerHTML = `<div id="contents" class="style-scope ytmusic-section-list-renderer genius-lyrics">
${lyrics}
<yt-formatted-string class="footer style-scope ytmusic-description-shelf-renderer">Source&nbsp;: Genius</yt-formatted-string>
</div>`;
observer.disconnect();
}
});
observer.observe(tabContainer, {
attributes: true,
childList: true,
subtree: true,
});
};
});
};

View File

@ -0,0 +1,12 @@
/* Disable links in Genius lyrics */
.genius-lyrics a {
color: var(--ytmusic-text-primary);
display: inline-block;
pointer-events: none;
text-decoration: none;
}
#contents.genius-lyrics {
font-size: 1vw;
opacity: 0.9;
}

View File

@ -4,7 +4,10 @@ const { injectCSS, listenAction } = require("../utils");
const { ACTIONS, CHANNEL } = require("./actions.js");
function handle(win) {
injectCSS(win.webContents, path.join(__dirname, "style.css"));
injectCSS(win.webContents, path.join(__dirname, "style.css"), () => {
win.webContents.send("navigation-css-ready");
});
listenAction(CHANNEL, (event, action) => {
switch (action) {
case ACTIONS.NEXT:

View File

@ -1,15 +1,19 @@
const { ipcRenderer } = require("electron");
const { ElementFromFile, templatePath } = require("../utils");
function run() {
const forwardButton = ElementFromFile(
templatePath(__dirname, "forward.html")
);
const backButton = ElementFromFile(templatePath(__dirname, "back.html"));
const menu = document.querySelector("ytmusic-pivot-bar-renderer");
ipcRenderer.on("navigation-css-ready", () => {
const forwardButton = ElementFromFile(
templatePath(__dirname, "forward.html")
);
const backButton = ElementFromFile(templatePath(__dirname, "back.html"));
const menu = document.querySelector("ytmusic-pivot-bar-renderer");
if (menu) {
menu.prepend(backButton, forwardButton);
}
if (menu) {
menu.prepend(backButton, forwardButton);
}
});
}
module.exports = run;

View File

@ -20,7 +20,7 @@
preserveAspectRatio="xMidYMid meet"
focusable="false"
class="style-scope iron-icon"
style="pointer-events: none; display: block; width: 100%; height: 100%;"
style="pointer-events: none; display: block; width: 100%; height: 100%"
>
<g class="style-scope iron-icon">
<path

View File

@ -1,41 +1,46 @@
const { Notification } = require("electron");
const getSongInfo = require("../../providers/song-info");
const is = require("electron-is");
const registerCallback = require("../../providers/song-info");
const { notificationImage } = require("./utils");
const notify = info => {
let notificationImage = "assets/youtube-music.png";
const setupInteractive = require("./interactive")
if (info.image) {
notificationImage = info.image.resize({ height: 256, width: 256 });
}
const notify = (info, options) => {
// Fill the notification with content
const notification = {
title: info.title || "Playing",
body: info.artist,
icon: notificationImage,
icon: notificationImage(info),
silent: true,
urgency: options.urgency,
};
// Send the notification
currentNotification = new Notification(notification);
const currentNotification = new Notification(notification);
currentNotification.show()
return currentNotification;
};
module.exports = (win) => {
const registerCallback = getSongInfo(win);
const setup = (options) => {
let oldNotification;
win.on("ready-to-show", () => {
// Register the callback for new song information
registerCallback(songInfo => {
// If song is playing send notification
if (!songInfo.isPaused) {
// Close the old notification
oldNotification?.close();
// This fixes a weird bug that would cause the notification to be updated instead of showing
setTimeout(()=>{ oldNotification = notify(songInfo) }, 10);
}
});
let currentUrl;
registerCallback(songInfo => {
if (!songInfo.isPaused && (songInfo.url !== currentUrl || options.unpauseNotification)) {
// Close the old notification
oldNotification?.close();
currentUrl = songInfo.url;
// This fixes a weird bug that would cause the notification to be updated instead of showing
setTimeout(() => { oldNotification = notify(songInfo, options) }, 10);
}
});
}
module.exports = (win, options) => {
// Register the callback for new song information
is.windows() && options.interactive ?
setupInteractive(win, options.unpauseNotification) :
setup(options);
};

View File

@ -0,0 +1,103 @@
const { notificationImage, icons } = require("./utils");
const getSongControls = require('../../providers/song-controls');
const registerCallback = require("../../providers/song-info");
const notifier = require("node-notifier");
//store song controls reference on launch
let controls;
let notificationOnUnpause;
module.exports = (win, unpauseNotification) => {
//Save controls and onPause option
const { playPause, next, previous } = getSongControls(win);
controls = { playPause, next, previous };
notificationOnUnpause = unpauseNotification;
let currentUrl;
// Register songInfoCallback
registerCallback(songInfo => {
if (!songInfo.isPaused && (songInfo.url !== currentUrl || notificationOnUnpause)) {
currentUrl = songInfo.url;
sendToaster(songInfo);
}
});
win.webContents.once("closed", () => {
deleteNotification()
});
}
//delete old notification
let toDelete;
function deleteNotification() {
if (toDelete !== undefined) {
// To remove the notification it has to be done this way
const removeNotif = Object.assign(toDelete, {
remove: toDelete.id
})
notifier.notify(removeNotif)
toDelete = undefined;
}
}
//New notification
function sendToaster(songInfo) {
deleteNotification();
//download image and get path
let imgSrc = notificationImage(songInfo, true);
toDelete = {
//app id undefined - will break buttons
title: songInfo.title || "Playing",
message: songInfo.artist,
id: parseInt(Math.random() * 1000000, 10),
icon: imgSrc,
actions: [
icons.previous,
songInfo.isPaused ? icons.play : icons.pause,
icons.next
],
sound: false,
};
//send notification
notifier.notify(
toDelete,
(err, data) => {
// Will also wait until notification is closed.
if (err) {
console.log(`ERROR = ${err.toString()}\n DATA = ${data}`);
}
switch (data) {
//buttons
case icons.previous.normalize():
controls.previous();
return;
case icons.next.normalize():
controls.next();
return;
case icons.play.normalize():
controls.playPause();
// dont delete notification on play/pause
toDelete = undefined;
//manually send notification if not sending automatically
if (!notificationOnUnpause) {
songInfo.isPaused = false;
sendToaster(songInfo);
}
return;
case icons.pause.normalize():
controls.playPause();
songInfo.isPaused = true;
toDelete = undefined;
sendToaster(songInfo);
return;
//Native datatype
case "dismissed":
case "timeout":
deleteNotification();
}
}
);
}

View File

@ -0,0 +1,30 @@
const { urgencyLevels, setOption } = require("./utils");
const is = require("electron-is");
module.exports = (win, options) => [
...(is.linux() ?
[{
label: "Notification Priority",
submenu: urgencyLevels.map(level => ({
label: level.name,
type: "radio",
checked: options.urgency === level.value,
click: () => setOption(options, "urgency", level.value)
})),
}] :
[]),
...(is.windows() ?
[{
label: "Interactive Notifications",
type: "checkbox",
checked: options.interactive,
click: (item) => setOption(options, "interactive", item.checked)
}] :
[]),
{
label: "Show notification on unpause",
type: "checkbox",
checked: options.unpauseNotification,
click: (item) => setOption(options, "unpauseNotification", item.checked)
},
];

View File

@ -0,0 +1,56 @@
const { setOptions } = require("../../config/plugins");
const path = require("path");
const { app } = require("electron");
const fs = require("fs");
const icon = "assets/youtube-music.png";
const tempIcon = path.join(app.getPath("userData"), "tempIcon.png");
module.exports.icons = {
play: "\u{1405}", // ᐅ
pause: "\u{2016}", // ‖
next: "\u{1433}", //
previous: "\u{1438}" //
}
module.exports.setOption = (options, option, value) => {
options[option] = value;
setOptions("notifications", options)
}
module.exports.urgencyLevels = [
{ name: "Low", value: "low" },
{ name: "Normal", value: "normal" },
{ name: "High", value: "critical" },
];
module.exports.notificationImage = function (songInfo, saveIcon = false) {
//return local path to temp icon
if (saveIcon && !!songInfo.image) {
try {
fs.writeFileSync(tempIcon,
centerNativeImage(songInfo.image)
.toPNG()
);
} catch (err) {
console.log(`Error writing song icon to disk:\n${err.toString()}`)
return icon;
}
return tempIcon;
}
//else: return image
return songInfo.image
? centerNativeImage(songInfo.image)
: icon
};
function centerNativeImage(nativeImage) {
const tempImage = nativeImage.resize({ height: 256 });
const margin = Math.max((tempImage.getSize().width - 256), 0);
return tempImage.crop({
x: Math.round(margin / 2),
y: 0,
width: 256, height: 256
})
}

View File

@ -1,83 +1,93 @@
const {
getSongMenu,
watchDOMElement,
} = require("../../providers/dom-elements");
const { getSongMenu } = require("../../providers/dom-elements");
const { ElementFromFile, templatePath } = require("../utils");
function $(selector) { return document.querySelector(selector); }
const slider = ElementFromFile(templatePath(__dirname, "slider.html"));
const MIN_PLAYBACK_SPEED = 0.25;
const MAX_PLAYBACK_SPEED = 2;
const roundToTwo = n => Math.round(n * 1e2) / 1e2;
let videoElement;
let playbackSpeedPercentage = 50; // = Playback speed of 1
const MIN_PLAYBACK_SPEED = 0.07;
const MAX_PLAYBACK_SPEED = 16;
const computePlayBackSpeed = () => {
if (playbackSpeedPercentage <= 50) {
// Slow down video by setting a playback speed between MIN_PLAYBACK_SPEED and 1
return (
MIN_PLAYBACK_SPEED +
((1 - MIN_PLAYBACK_SPEED) / 50) * playbackSpeedPercentage
);
}
// Accelerate video by setting a playback speed between 1 and MAX_PLAYBACK_SPEED
return 1 + ((MAX_PLAYBACK_SPEED - 1) / 50) * (playbackSpeedPercentage - 50);
};
let playbackSpeed = 1;
const updatePlayBackSpeed = () => {
const playbackSpeed = Math.round(computePlayBackSpeed() * 100) / 100;
$('video').playbackRate = playbackSpeed;
if (!videoElement || videoElement.playbackRate === playbackSpeed) {
return;
}
videoElement.playbackRate = playbackSpeed;
const playbackSpeedElement = document.querySelector("#playback-speed-value");
const playbackSpeedElement = $("#playback-speed-value");
if (playbackSpeedElement) {
playbackSpeedElement.innerHTML = playbackSpeed;
}
};
module.exports = () => {
watchDOMElement(
"video",
(document) => document.querySelector("video"),
(element) => {
videoElement = element;
updatePlayBackSpeed();
}
);
let menu;
let observingSlider = false;
watchDOMElement(
"menu",
(document) => getSongMenu(document),
(menuElement) => {
if (!menuElement.contains(slider)) {
menuElement.prepend(slider);
const observePopupContainer = () => {
const observer = new MutationObserver(() => {
if (!menu) {
menu = getSongMenu();
}
if (menu && !menu.contains(slider)) {
menu.prepend(slider);
if (!observingSlider) {
setupSliderListener();
observingSlider = true;
}
const playbackSpeedElement = document.querySelector(
"#playback-speed-slider #sliderKnob .slider-knob-inner"
);
const playbackSpeedObserver = new MutationObserver((mutations) => {
mutations.forEach(function (mutation) {
if (mutation.type == "attributes") {
const value = playbackSpeedElement.getAttribute("value");
playbackSpeedPercentage = parseInt(value, 10);
if (isNaN(playbackSpeedPercentage)) {
playbackSpeedPercentage = 50;
}
updatePlayBackSpeed();
return;
}
});
});
playbackSpeedObserver.observe(playbackSpeedElement, {
attributes: true,
});
}
);
});
observer.observe($('ytmusic-popup-container'), {
childList: true,
subtree: true,
});
};
const observeVideo = () => {
$('video').addEventListener('ratechange', forcePlaybackRate)
$('video').addEventListener('srcChanged', forcePlaybackRate)
}
const setupWheelListener = () => {
slider.addEventListener('wheel', e => {
e.preventDefault();
if (isNaN(playbackSpeed)) {
playbackSpeed = 1;
}
// e.deltaY < 0 means wheel-up
playbackSpeed = roundToTwo(e.deltaY < 0 ?
Math.min(playbackSpeed + 0.01, MAX_PLAYBACK_SPEED) :
Math.max(playbackSpeed - 0.01, MIN_PLAYBACK_SPEED)
);
updatePlayBackSpeed();
// update slider position
$('#playback-speed-slider').value = playbackSpeed;
})
}
function setupSliderListener() {
$('#playback-speed-slider').addEventListener('immediate-value-changed', e => {
playbackSpeed = e.detail.value || MIN_PLAYBACK_SPEED;
if (isNaN(playbackSpeed)) {
playbackSpeed = 1;
}
updatePlayBackSpeed();
})
}
function forcePlaybackRate(e) {
if (e.target.playbackRate !== playbackSpeed) {
e.target.playbackRate = playbackSpeed
}
}
module.exports = () => {
document.addEventListener('apiLoaded', e => {
observePopupContainer();
observeVideo();
setupWheelListener();
}, { once: true, passive: true })
};

View File

@ -1,79 +1,88 @@
<div
class="menu-item ytmusic-menu-popup-renderer"
class="style-scope menu-item ytmusic-menu-popup-renderer"
role="option"
tabindex="-1"
aria-disabled="false"
aria-selected="false"
>
<tp-yt-paper-slider
id="playback-speed-slider"
class="volume-slider style-scope ytmusic-player-bar on-hover"
max="100"
min="0"
step="5"
dir="ltr"
title="Playback speed"
aria-label="Playback speed"
role="slider"
tabindex="0"
aria-valuemin="0"
aria-valuemax="100"
aria-valuenow="50"
aria-disabled="false"
value="50"
><!--css-build:shady-->
<div id="sliderContainer" class="style-scope tp-yt-paper-slider">
<div class="bar-container style-scope tp-yt-paper-slider">
<tp-yt-paper-progress
id="sliderBar"
aria-hidden="true"
class="style-scope tp-yt-paper-slider"
role="progressbar"
value="50"
aria-valuenow="50"
aria-valuemin="0"
aria-valuemax="100"
aria-disabled="false"
style="touch-action: none"
><!--css-build:shady-->
<div
id="navigation-endpoint"
class="yt-simple-endpoint style-scope ytmusic-menu-navigation-item-renderer"
tabindex="-1"
>
<tp-yt-paper-slider
id="playback-speed-slider"
class="volume-slider style-scope ytmusic-player-bar on-hover"
style="display: inherit !important"
max="2"
min="0"
step="0.125"
dir="ltr"
title="Playback speed"
aria-label="Playback speed"
role="slider"
tabindex="0"
aria-valuemin="0"
aria-valuemax="2"
aria-valuenow="1"
aria-disabled="false"
value="1"
><!--css-build:shady-->
<div id="sliderContainer" class="style-scope tp-yt-paper-slider">
<div class="bar-container style-scope tp-yt-paper-slider">
<tp-yt-paper-progress
id="sliderBar"
aria-hidden="true"
class="style-scope tp-yt-paper-slider"
role="progressbar"
value="1"
aria-valuenow="1"
aria-valuemin="0"
aria-valuemax="2"
aria-disabled="false"
style="touch-action: none"
><!--css-build:shady-->
<div id="progressContainer" class="style-scope tp-yt-paper-progress">
<div
id="secondaryProgress"
id="progressContainer"
class="style-scope tp-yt-paper-progress"
hidden="true"
style="transform: scaleX(0)"
></div>
<div
id="primaryProgress"
class="style-scope tp-yt-paper-progress"
style="transform: scaleX(0.5)"
></div>
</div>
</tp-yt-paper-progress>
>
<div
id="secondaryProgress"
class="style-scope tp-yt-paper-progress"
hidden="true"
style="transform: scaleX(0)"
></div>
<div
id="primaryProgress"
class="style-scope tp-yt-paper-progress"
style="transform: scaleX(0.5)"
></div>
</div>
</tp-yt-paper-progress>
</div>
<dom-if class="style-scope tp-yt-paper-slider"
><template is="dom-if"></template
></dom-if>
<div
id="sliderKnob"
class="slider-knob style-scope tp-yt-paper-slider"
style="left: 50%; touch-action: none"
>
<div
class="slider-knob-inner style-scope tp-yt-paper-slider"
value="1"
></div>
</div>
</div>
<dom-if class="style-scope tp-yt-paper-slider"
><template is="dom-if"></template
></dom-if>
<div
id="sliderKnob"
class="slider-knob style-scope tp-yt-paper-slider"
style="left: 50%; touch-action: none"
>
<div
class="slider-knob-inner style-scope tp-yt-paper-slider"
value="50"
></div>
</div>
><template is="dom-if"></template></dom-if
></tp-yt-paper-slider>
<div
class="text style-scope ytmusic-menu-navigation-item-renderer"
id="ytmcustom-playback-speed"
>
Speed (<span id="playback-speed-value">1</span>)
</div>
<dom-if class="style-scope tp-yt-paper-slider"
><template is="dom-if"></template></dom-if
></tp-yt-paper-slider>
<div
class="text style-scope ytmusic-toggle-menu-service-item-renderer"
id="ytmcustom-playback-speed"
>
Speed (<span id="playback-speed-value">1</span>)
</div>
</div>

View File

@ -0,0 +1,9 @@
/*
This is used to determine if plugin is actually active
(not if its only enabled in options)
*/
let enabled = false;
module.exports = () => enabled = true;
module.exports.enabled = () => enabled;

View File

@ -0,0 +1,236 @@
const { ipcRenderer } = require("electron");
const { globalShortcut } = require('@electron/remote');
const { setOptions } = require("../../config/plugins");
function $(selector) { return document.querySelector(selector); }
let api;
module.exports = (options) => {
document.addEventListener('apiLoaded', e => {
api = e.detail;
firstRun(options);
}, { once: true, passive: true })
};
/** Restore saved volume and setup tooltip */
function firstRun(options) {
if (typeof options.savedVolume === "number") {
// Set saved volume as tooltip
setTooltip(options.savedVolume);
if (api.getVolume() !== options.savedVolume) {
api.setVolume(options.savedVolume);
}
}
setupPlaybar(options);
setupLocalArrowShortcuts(options);
setupGlobalShortcuts(options);
const noVid = $("#main-panel")?.computedStyleMap().get("display").value === "none";
injectVolumeHud(noVid);
if (!noVid) {
setupVideoPlayerOnwheel(options);
}
// Change options from renderer to keep sync
ipcRenderer.on("setOptions", (_event, newOptions = {}) => {
for (option in newOptions) {
options[option] = newOptions[option];
}
setOptions("precise-volume", options);
});
}
function injectVolumeHud(noVid) {
if (noVid) {
const position = "top: 18px; right: 60px; z-index: 999; position: absolute;";
const mainStyle = "font-size: xx-large; padding: 10px; transition: opacity 1s; pointer-events: none;";
$(".center-content.ytmusic-nav-bar").insertAdjacentHTML("beforeend",
`<span id="volumeHud" style="${position + mainStyle}"></span>`)
} else {
const position = `top: 10px; left: 10px; z-index: 999; position: absolute;`;
const mainStyle = "font-size: xxx-large; padding: 10px; transition: opacity 0.6s; webkit-text-stroke: 1px black; font-weight: 600; pointer-events: none;";
$("#song-video").insertAdjacentHTML('afterend',
`<span id="volumeHud" style="${position + mainStyle}"></span>`)
}
}
let hudFadeTimeout;
function showVolumeHud(volume) {
let volumeHud = $("#volumeHud");
if (!volumeHud) return;
volumeHud.textContent = volume + '%';
volumeHud.style.opacity = 1;
if (hudFadeTimeout) {
clearTimeout(hudFadeTimeout);
}
hudFadeTimeout = setTimeout(() => {
volumeHud.style.opacity = 0;
hudFadeTimeout = null;
}, 2000);
}
/** Add onwheel event to video player */
function setupVideoPlayerOnwheel(options) {
$("#main-panel").addEventListener("wheel", event => {
event.preventDefault();
// Event.deltaY < 0 means wheel-up
changeVolume(event.deltaY < 0, options);
});
}
function saveVolume(volume, options) {
options.savedVolume = volume;
writeOptions(options);
}
//without this function it would rewrite config 20 time when volume change by 20
let writeTimeout;
function writeOptions(options) {
if (writeTimeout) clearTimeout(writeTimeout);
writeTimeout = setTimeout(() => {
setOptions("precise-volume", options);
writeTimeout = null;
}, 1000)
}
/** Add onwheel event to play bar and also track if play bar is hovered*/
function setupPlaybar(options) {
const playerbar = $("ytmusic-player-bar");
playerbar.addEventListener("wheel", event => {
event.preventDefault();
// Event.deltaY < 0 means wheel-up
changeVolume(event.deltaY < 0, options);
});
// Keep track of mouse position for showVolumeSlider()
playerbar.addEventListener("mouseenter", () => {
playerbar.classList.add("on-hover");
});
playerbar.addEventListener("mouseleave", () => {
playerbar.classList.remove("on-hover");
});
setupSliderObserver(options);
}
/** Save volume + Update the volume tooltip when volume-slider is manually changed */
function setupSliderObserver(options) {
const sliderObserver = new MutationObserver(mutations => {
for (const mutation of mutations) {
// This checks that volume-slider was manually set
if (mutation.oldValue !== mutation.target.value &&
(typeof options.savedVolume !== "number" || Math.abs(options.savedVolume - mutation.target.value) > 4)) {
// Diff>4 means it was manually set
setTooltip(mutation.target.value);
saveVolume(mutation.target.value, options);
}
}
});
// Observing only changes in 'value' of volume-slider
sliderObserver.observe($("#volume-slider"), {
attributeFilter: ["value"],
attributeOldValue: true
});
}
/** if (toIncrease = false) then volume decrease */
function changeVolume(toIncrease, options) {
// Apply volume change if valid
const steps = Number(options.steps || 1);
api.setVolume(toIncrease ?
Math.min(api.getVolume() + steps, 100) :
Math.max(api.getVolume() - steps, 0));
// Save the new volume
saveVolume(api.getVolume(), options);
// change slider position (important)
updateVolumeSlider(options);
// Change tooltips to new value
setTooltip(options.savedVolume);
// Show volume slider
showVolumeSlider();
// Show volume HUD
showVolumeHud(options.savedVolume);
}
function updateVolumeSlider(options) {
// Slider value automatically rounds to multiples of 5
$("#volume-slider").value = options.savedVolume > 0 && options.savedVolume < 5 ?
5 : options.savedVolume;
}
let volumeHoverTimeoutID;
function showVolumeSlider() {
const slider = $("#volume-slider");
// This class display the volume slider if not in minimized mode
slider.classList.add("on-hover");
// Reset timeout if previous one hasn't completed
if (volumeHoverTimeoutID) {
clearTimeout(volumeHoverTimeoutID);
}
// Timeout to remove volume preview after 3 seconds if playbar isn't hovered
volumeHoverTimeoutID = setTimeout(() => {
volumeHoverTimeoutID = null;
if (!$("ytmusic-player-bar").classList.contains("on-hover")) {
slider.classList.remove("on-hover");
}
}, 3000);
}
// Set new volume as tooltip for volume slider and icon + expanding slider (appears when window size is small)
const tooltipTargets = [
"#volume-slider",
"tp-yt-paper-icon-button.volume",
"#expand-volume-slider",
"#expand-volume"
];
function setTooltip(volume) {
for (target of tooltipTargets) {
$(target).title = `${volume}%`;
}
}
function setupGlobalShortcuts(options) {
if (options.globalShortcuts.volumeUp) {
globalShortcut.register((options.globalShortcuts.volumeUp), () => changeVolume(true, options));
}
if (options.globalShortcuts.volumeDown) {
globalShortcut.register((options.globalShortcuts.volumeDown), () => changeVolume(false, options));
}
}
function setupLocalArrowShortcuts(options) {
if (options.arrowsShortcut) {
window.addEventListener('keydown', (event) => {
switch (event.code) {
case "ArrowUp":
event.preventDefault();
changeVolume(true, options);
break;
case "ArrowDown":
event.preventDefault();
changeVolume(false, options);
break;
}
});
}
}

View File

@ -0,0 +1,82 @@
const { enabled } = require("./back");
const { setOptions } = require("../../config/plugins");
const prompt = require("custom-electron-prompt");
const promptOptions = require("../../providers/prompt-options");
function changeOptions(changedOptions, options, win) {
for (option in changedOptions) {
options[option] = changedOptions[option];
}
// Dynamically change setting if plugin is enabled
if (enabled()) {
win.webContents.send("setOptions", changedOptions);
} else { // Fallback to usual method if disabled
setOptions("precise-volume", options);
}
}
module.exports = (win, options) => [
{
label: "Local Arrowkeys Controls",
type: "checkbox",
checked: !!options.arrowsShortcut,
click: item => {
changeOptions({ arrowsShortcut: item.checked }, options, win);
}
},
{
label: "Global Hotkeys",
type: "checkbox",
checked: !!options.globalShortcuts.volumeUp || !!options.globalShortcuts.volumeDown,
click: item => promptGlobalShortcuts(win, options, item)
},
{
label: "Set Custom Volume Steps",
click: () => promptVolumeSteps(win, options)
}
];
// Helper function for globalShortcuts prompt
const kb = (label_, value_, default_) => { return { value: value_, label: label_, default: default_ || undefined }; };
async function promptVolumeSteps(win, options) {
const output = await prompt({
title: "Volume Steps",
label: "Choose Volume Increase/Decrease Steps",
value: options.steps || 1,
type: "counter",
counterOptions: { minimum: 0, maximum: 100, multiFire: true },
width: 380,
...promptOptions()
}, win)
if (output || output === 0) { // 0 is somewhat valid
changeOptions({ steps: output}, options, win);
}
}
async function promptGlobalShortcuts(win, options, item) {
const output = await prompt({
title: "Global Volume Keybinds",
label: "Choose Global Volume Keybinds:",
type: "keybind",
keybindOptions: [
kb("Increase Volume", "volumeUp", options.globalShortcuts?.volumeUp),
kb("Decrease Volume", "volumeDown", options.globalShortcuts?.volumeDown)
],
...promptOptions()
}, win)
if (output) {
let newGlobalShortcuts = {};
for (const { value, accelerator } of output) {
newGlobalShortcuts[value] = accelerator;
}
changeOptions({ globalShortcuts: newGlobalShortcuts }, options, win);
item.checked = !!options.globalShortcuts.volumeUp || !!options.globalShortcuts.volumeDown;
} else {
// Reset checkbox if prompt was canceled
item.checked = !item.checked;
}
}

View File

@ -0,0 +1,33 @@
const { ipcRenderer } = require("electron");
const is = require("electron-is");
let ignored = {
id: ["volume-slider", "expand-volume-slider"],
types: ["mousewheel", "keydown", "keyup"]
};
function overrideAddEventListener() {
// Save native addEventListener
Element.prototype._addEventListener = Element.prototype.addEventListener;
// Override addEventListener to Ignore specific events in volume-slider
Element.prototype.addEventListener = function (type, listener, useCapture = false) {
if (!(
ignored.id.includes(this.id) &&
ignored.types.includes(type)
)) {
this._addEventListener(type, listener, useCapture);
} else if (is.dev()) {
console.log(`Ignoring event: "${this.id}.${type}()"`);
}
};
}
module.exports = () => {
overrideAddEventListener();
// Restore original function after finished loading to avoid keeping Element.prototype altered
window.addEventListener('load', () => {
Element.prototype.addEventListener = Element.prototype._addEventListener;
Element.prototype._addEventListener = undefined;
ignored = undefined;
}, { once: true });
};

View File

@ -0,0 +1,41 @@
const { ElementFromFile, templatePath } = require("../utils");
const { dialog } = require('@electron/remote');
function $(selector) { return document.querySelector(selector); }
const qualitySettingsButton = ElementFromFile(
templatePath(__dirname, "qualitySettingsTemplate.html")
);
module.exports = () => {
document.addEventListener('apiLoaded', setup, { once: true, passive: true });
}
function setup(event) {
const api = event.detail;
$('.top-row-buttons.ytmusic-player').prepend(qualitySettingsButton);
qualitySettingsButton.onclick = function chooseQuality() {
if (api.getPlayerState() === 2) api.playVideo();
else if (api.getPlayerState() === 1) api.pauseVideo();
const currentIndex = api.getAvailableQualityLevels().indexOf(api.getPlaybackQuality())
dialog.showMessageBox({
type: "question",
buttons: api.getAvailableQualityLabels(),
defaultId: currentIndex,
title: "Choose Video Quality",
message: "Choose Video Quality:",
detail: `Current Quality: ${api.getAvailableQualityLabels()[currentIndex]}`,
cancelId: -1
}).then((promise) => {
if (promise.response === -1) return;
const newQuality = api.getAvailableQualityLevels()[promise.response];
api.setPlaybackQualityRange(newQuality);
api.setPlaybackQuality(newQuality)
})
}
}

View File

@ -0,0 +1,13 @@
<tp-yt-paper-icon-button class="player-quality-button style-scope ytmusic-player" icon="yt-icons:settings"
title="Open player quality changer" aria-label="Open player quality changer" role="button" tabindex="0" aria-disabled="false">
<tp-yt-iron-icon id="icon" class="style-scope tp-yt-paper-icon-button"><svg viewBox="0 0 24 24"
preserveAspectRatio="xMidYMid meet" focusable="false" class="style-scope yt-icon"
style="pointer-events: none; display: block; width: 100%; height: 100%;">
<g class="style-scope yt-icon">
<path
d="M19.43 12.98c.04-.32.07-.64.07-.98s-.03-.66-.07-.98l2.1-1.65c.2-.15.25-.42.13-.64l-2-3.46c-.12-.22-.4-.3-.6-.22l-2.5 1c-.52-.4-1.08-.73-1.7-.98l-.37-2.65c-.06-.24-.27-.42-.5-.42h-4c-.27 0-.48.18-.5.42l-.4 2.65c-.6.25-1.17.6-1.7.98l-2.48-1c-.23-.1-.5 0-.6.22l-2 3.46c-.14.22-.08.5.1.64l2.12 1.65c-.04.32-.07.65-.07.98s.02.66.06.98l-2.1 1.65c-.2.15-.25.42-.13.64l2 3.46c.12.22.4.3.6.22l2.5-1c.52.4 1.08.73 1.7.98l.37 2.65c.04.24.25.42.5.42h4c.25 0 .46-.18.5-.42l.37-2.65c.6-.25 1.17-.6 1.7-.98l2.48 1c.23.1.5 0 .6-.22l2-3.46c.13-.22.08-.5-.1-.64l-2.12-1.65zM12 15.5c-1.93 0-3.5-1.57-3.5-3.5s1.57-3.5 3.5-3.5 3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z"
class="style-scope yt-icon"></path>
</g>
</svg>
</tp-yt-iron-icon>
</tp-yt-paper-icon-button>

View File

@ -1,7 +1,8 @@
const { globalShortcut } = require("electron");
const is = require("electron-is");
const electronLocalshortcut = require("electron-localshortcut");
const getSongControls = require("../../providers/song-controls");
const registerMPRIS = require("./mpris");
function _registerGlobalShortcut(webContents, shortcut, action) {
globalShortcut.register(shortcut, () => {
@ -19,31 +20,43 @@ function registerShortcuts(win, options) {
const songControls = getSongControls(win);
const { playPause, next, previous, search } = songControls;
_registerGlobalShortcut(win.webContents, "MediaPlayPause", playPause);
_registerGlobalShortcut(win.webContents, "MediaNextTrack", next);
_registerGlobalShortcut(win.webContents, "MediaPreviousTrack", previous);
if (options.overrideMediaKeys) {
_registerGlobalShortcut(win.webContents, "MediaPlayPause", playPause);
_registerGlobalShortcut(win.webContents, "MediaNextTrack", next);
_registerGlobalShortcut(win.webContents, "MediaPreviousTrack", previous);
}
_registerLocalShortcut(win, "CommandOrControl+F", search);
_registerLocalShortcut(win, "CommandOrControl+L", search);
if (is.linux()) registerMPRIS(win);
const { global, local } = options;
(global || []).forEach(({ shortcut, action }) => {
console.debug("Registering global shortcut", shortcut, ":", action);
if (!action || !songControls[action]) {
console.warn("Invalid action", action);
return;
}
const shortcutOptions = { global, local };
_registerGlobalShortcut(win.webContents, shortcut, songControls[action]);
});
(local || []).forEach(({ shortcut, action }) => {
console.debug("Registering local shortcut", shortcut, ":", action);
if (!action || !songControls[action]) {
console.warn("Invalid action", action);
return;
}
for (const optionType in shortcutOptions) {
registerAllShortcuts(shortcutOptions[optionType], optionType);
}
_registerLocalShortcut(win, shortcut, songControls[action]);
});
function registerAllShortcuts(container, type) {
for (const action in container) {
if (!container[action]) {
continue; // Action accelerator is empty
}
console.debug(`Registering ${type} shortcut`, container[action], ":", action);
if (!songControls[action]) {
console.warn("Invalid action", action);
continue;
}
if (type === "global") {
_registerGlobalShortcut(win.webContents, container[action], songControls[action]);
} else { // type === "local"
_registerLocalShortcut(win, local[action], songControls[action]);
}
}
}
}
module.exports = registerShortcuts;

53
plugins/shortcuts/menu.js Normal file
View File

@ -0,0 +1,53 @@
const { setOptions } = require("../../config/plugins");
const prompt = require("custom-electron-prompt");
const promptOptions = require("../../providers/prompt-options");
module.exports = (win, options) => [
{
label: "Set Global Song Controls",
click: () => promptKeybind(options, win)
},
{
label: "Override MediaKeys",
type: "checkbox",
checked: options.overrideMediaKeys,
click: item => setOption(options, "overrideMediaKeys", item.checked)
}
];
function setOption(options, key = null, newValue = null) {
if (key && newValue !== null) {
options[key] = newValue;
}
setOptions("shortcuts", options);
}
// Helper function for keybind prompt
const kb = (label_, value_, default_) => { return { value: value_, label: label_, default: default_ }; };
async function promptKeybind(options, win) {
const output = await prompt({
title: "Global Keybinds",
label: "Choose Global Keybinds for Songs Control:",
type: "keybind",
keybindOptions: [ // If default=undefined then no default is used
kb("Previous", "previous", options.global?.previous),
kb("Play / Pause", "playPause", options.global?.playPause),
kb("Next", "next", options.global?.next)
],
height: 270,
...promptOptions()
}, win);
if (output) {
if (!options.global) {
options.global = {};
}
for (const { value, accelerator } of output) {
options.global[value] = accelerator;
}
setOption(options);
}
// else -> pressed cancel
}

View File

@ -0,0 +1,85 @@
const mpris = require("mpris-service");
const { ipcMain } = require("electron");
const registerCallback = require("../../providers/song-info");
const getSongControls = require("../../providers/song-controls");
function setupMPRIS() {
const player = mpris({
name: "youtube-music",
identity: "YouTube Music",
canRaise: true,
supportedUriSchemes: ["https"],
supportedMimeTypes: ["audio/mpeg"],
supportedInterfaces: ["player"],
desktopEntry: "youtube-music",
});
return player;
}
function registerMPRIS(win) {
const songControls = getSongControls(win);
const { playPause, next, previous } = songControls;
try {
const secToMicro = n => Math.round(Number(n) * 1e6);
const microToSec = n => Math.round(Number(n) / 1e6);
const seekTo = e => win.webContents.send("seekTo", microToSec(e.position));
const seekBy = o => win.webContents.send("seekBy", microToSec(o));
const player = setupMPRIS();
ipcMain.on('seeked', (_, t) => player.seeked(secToMicro(t)));
let currentSeconds = 0;
ipcMain.on('timeChanged', (_, t) => currentSeconds = t);
player.getPosition = () => secToMicro(currentSeconds)
player.on("raise", () => {
win.setSkipTaskbar(false);
win.show();
});
player.on("play", () => {
if (player.playbackStatus !== 'Playing') {
player.playbackStatus = 'Playing';
playPause()
}
});
player.on("pause", () => {
if (player.playbackStatus !== 'Paused') {
player.playbackStatus = 'Paused';
playPause()
}
});
player.on("playpause", playPause);
player.on("next", next);
player.on("previous", previous);
player.on('seek', seekBy);
player.on('position', seekTo);
registerCallback(songInfo => {
if (player) {
const data = {
'mpris:length': secToMicro(songInfo.songDuration),
'mpris:artUrl': songInfo.imageSrc,
'xesam:title': songInfo.title,
'xesam:artist': songInfo.artist,
'mpris:trackid': '/'
};
if (songInfo.album) data['xesam:album'] = songInfo.album;
player.metadata = data;
player.seeked(secToMicro(songInfo.elapsedSeconds))
player.playbackStatus = songInfo.isPaused ? "Paused" : "Playing"
}
})
} catch (e) {
console.warn("Error in MPRIS", e);
}
}
module.exports = registerMPRIS;

View File

@ -0,0 +1,37 @@
const hark = require("hark/hark.bundle.js");
module.exports = () => {
let isSilent = false;
document.addEventListener("apiLoaded", (apiEvent) => {
const video = document.querySelector("video");
const speechEvents = hark(video, {
threshold: -100, // dB (-100 = absolute silence, 0 = loudest)
interval: 2, // ms
});
const skipSilence = () => {
if (isSilent && !video.paused) {
video.currentTime += 0.2; // in s
}
};
speechEvents.on("speaking", function () {
isSilent = false;
});
speechEvents.on("stopped_speaking", function () {
if (!(video.paused || video.seeking || video.ended)) {
isSilent = true;
skipSilence();
}
});
video.addEventListener("play", function () {
skipSilence();
});
video.addEventListener("seeked", function () {
skipSilence();
});
});
};

View File

@ -0,0 +1,51 @@
const fetch = require("node-fetch");
const is = require("electron-is");
const { ipcMain } = require("electron");
const defaultConfig = require("../../config/defaults");
const { sortSegments } = require("./segments");
let videoID;
module.exports = (win, options) => {
const { apiURL, categories } = {
...defaultConfig.plugins.sponsorblock,
...options,
};
ipcMain.on("video-src-changed", async (_, data) => {
videoID = JSON.parse(data)?.videoDetails?.videoId;
const segments = await fetchSegments(apiURL, categories);
win.webContents.send("sponsorblock-skip", segments);
});
};
const fetchSegments = async (apiURL, categories) => {
const sponsorBlockURL = `${apiURL}/api/skipSegments?videoID=${videoID}&categories=${JSON.stringify(
categories
)}`;
try {
const resp = await fetch(sponsorBlockURL, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
redirect: "follow",
});
if (resp.status !== 200) {
return [];
}
const segments = await resp.json();
const sortedSegments = sortSegments(
segments.map((submission) => submission.segment)
);
return sortedSegments;
} catch (e) {
if (is.dev()) {
console.log('error on sponsorblock request:', e);
}
return [];
}
};

View File

@ -0,0 +1,31 @@
const { ipcRenderer } = require("electron");
const is = require("electron-is");
let currentSegments = [];
module.exports = () => {
ipcRenderer.on("sponsorblock-skip", (_, segments) => {
currentSegments = segments;
});
document.addEventListener('apiLoaded', () => {
const video = document.querySelector('video');
video.addEventListener('timeupdate', e => {
currentSegments.forEach((segment) => {
if (
e.target.currentTime >= segment[0] &&
e.target.currentTime < segment[1]
) {
e.target.currentTime = segment[1];
if (is.dev()) {
console.log("SponsorBlock: skipping segment", segment);
}
}
});
})
// Reset segments on song end
video.addEventListener('emptied', () => currentSegments = []);
}, { once: true, passive: true })
};

View File

@ -0,0 +1,29 @@
// Segments are an array [ [start, end], … ]
module.exports.sortSegments = (segments) => {
segments.sort((segment1, segment2) =>
segment1[0] === segment2[0]
? segment1[1] - segment2[1]
: segment1[0] - segment2[0]
);
const compiledSegments = [];
let currentSegment;
segments.forEach((segment) => {
if (!currentSegment) {
currentSegment = segment;
return;
}
if (currentSegment[1] < segment[0]) {
compiledSegments.push(currentSegment);
currentSegment = segment;
return;
}
currentSegment[1] = Math.max(currentSegment[1], segment[1]);
});
compiledSegments.push(currentSegment);
return compiledSegments;
};

View File

@ -0,0 +1,34 @@
const { sortSegments } = require("../segments");
test("Segment sorting", () => {
expect(
sortSegments([
[0, 3],
[7, 8],
[5, 6],
])
).toEqual([
[0, 3],
[5, 6],
[7, 8],
]);
expect(
sortSegments([
[0, 5],
[6, 8],
[4, 6],
])
).toEqual([[0, 8]]);
expect(
sortSegments([
[0, 6],
[7, 8],
[4, 6],
])
).toEqual([
[0, 6],
[7, 8],
]);
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 B

View File

@ -0,0 +1,53 @@
const getSongControls = require('../../providers/song-controls');
const registerCallback = require('../../providers/song-info');
const path = require('path');
let controls;
let currentSongInfo;
module.exports = win => {
const { playPause, next, previous } = getSongControls(win);
controls = { playPause, next, previous };
registerCallback(songInfo => {
//update currentsonginfo for win.on('show')
currentSongInfo = songInfo;
// update thumbar
setThumbar(win, songInfo);
});
// need to set thumbar again after win.show
win.on("show", () => {
setThumbar(win, currentSongInfo)
})
};
function setThumbar(win, songInfo) {
// Wait for song to start before setting thumbar
if (!songInfo?.title) {
return;
}
// Win32 require full rewrite of components
win.setThumbarButtons([
{
tooltip: 'Previous',
icon: get('backward.png'),
click() { controls.previous(win.webContents); }
}, {
tooltip: 'Play/Pause',
// Update icon based on play state
icon: songInfo.isPaused ? get('play.png') : get('pause.png'),
click() { controls.playPause(win.webContents); }
}, {
tooltip: 'Next',
icon: get('forward.png'),
click() { controls.next(win.webContents); }
}
]);
}
// Util
function get(file) {
return path.join(__dirname, "assets", file);
}

View File

@ -7,7 +7,7 @@ const {
TouchBarScrubber,
} = TouchBar;
const getSongInfo = require("../../providers/song-info");
const registerCallback = require("../../providers/song-info");
const getSongControls = require("../../providers/song-controls");
// Songtitle label
@ -59,11 +59,10 @@ const touchBar = new TouchBar({
});
module.exports = (win) => {
const registerCallback = getSongInfo(win);
const { playPause, next, previous, like, dislike } = getSongControls(win);
// If the page is ready, register the callback
win.on("ready-to-show", () => {
win.once("ready-to-show", () => {
controls = [previous, playPause, next, like, dislike];
// Register the callback

52
plugins/tuna-obs/back.js Normal file
View File

@ -0,0 +1,52 @@
const { ipcMain } = require("electron");
const fetch = require('node-fetch');
const registerCallback = require("../../providers/song-info");
const secToMilisec = t => Math.round(Number(t) * 1e3);
const data = {
cover_url: '',
title: '',
artists: [],
status: '',
progress: 0,
duration: 0,
album_url: '',
album: undefined
};
const post = async (data) => {
const port = 1608;
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Access-Control-Allow-Headers': '*',
'Access-Control-Allow-Origin': '*'
}
const url = `http://localhost:${port}/`;
fetch(url, { method: 'POST', headers, body: JSON.stringify({ data }) }).catch(e => console.log(`Error: '${e.code || e.errno}' - when trying to access obs-tuna webserver at port ${port}`));
}
module.exports = async (win) => {
ipcMain.on('timeChanged', async (_, t) => {
if (!data.title) return;
data.progress = secToMilisec(t);
post(data);
});
registerCallback((songInfo) => {
if (!songInfo.title && !songInfo.artist) {
return;
}
data.duration = secToMilisec(songInfo.songDuration)
data.progress = secToMilisec(songInfo.elapsedSeconds)
data.cover_url = songInfo.imageSrc;
data.album_url = songInfo.imageSrc;
data.title = songInfo.title;
data.artists = [songInfo.artist];
data.status = songInfo.isPaused ? 'stopped' : 'playing';
data.album = songInfo.album;
post(data);
})
}

View File

@ -42,9 +42,12 @@ module.exports.fileExists = (path, callbackIfExists) => {
});
};
module.exports.injectCSS = (webContents, filepath) => {
webContents.on("did-finish-load", () => {
webContents.insertCSS(fs.readFileSync(filepath, "utf8"));
module.exports.injectCSS = (webContents, filepath, cb = undefined) => {
webContents.on("did-finish-load", async () => {
await webContents.insertCSS(fs.readFileSync(filepath, "utf8"));
if (cb) {
cb();
}
});
};

View File

@ -0,0 +1,10 @@
const { injectCSS } = require("../utils");
const path = require("path");
module.exports = (win, options) => {
if (options.forceHide) {
injectCSS(win.webContents, path.join(__dirname, "force-hide.css"));
} else {
injectCSS(win.webContents, path.join(__dirname, "button-switcher.css"));
}
};

View File

@ -0,0 +1,77 @@
#main-panel.ytmusic-player-page {
align-items: unset !important;
}
.video-switch-button {
z-index: 999;
box-sizing: border-box;
padding: 0;
margin-top: 20px;
margin-left: 10px;
background: rgba(33, 33, 33, 0.4);
border-radius: 30px;
overflow: hidden;
width: 240px;
text-align: center;
font-size: 18px;
letter-spacing: 1px;
color: #fff;
padding-right: 120px;
position: absolute;
}
.video-switch-button:before {
content: "Video";
position: absolute;
top: 0;
bottom: 0;
right: 0;
width: 120px;
display: flex;
align-items: center;
justify-content: center;
z-index: 3;
pointer-events: none;
}
.video-switch-button-checkbox {
cursor: pointer;
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 100%;
height: 100%;
opacity: 0;
z-index: 2;
}
.video-switch-button-label-span {
position: relative;
}
.video-switch-button-checkbox:checked+.video-switch-button-label:before {
transform: translateX(120px);
transition: transform 300ms linear;
}
.video-switch-button-checkbox+.video-switch-button-label {
position: relative;
padding: 15px 0;
display: block;
user-select: none;
pointer-events: none;
}
.video-switch-button-checkbox+.video-switch-button-label:before {
content: "";
background: rgba(60, 60, 60, 0.4);
height: 100%;
width: 100%;
position: absolute;
left: 0;
top: 0;
border-radius: 30px;
transform: translateX(0);
transition: transform 300ms;
}

View File

@ -0,0 +1,116 @@
const { ElementFromFile, templatePath } = require("../utils");
const { setOptions } = require("../../config/plugins");
function $(selector) { return document.querySelector(selector); }
let options, player, video, api;
const switchButtonDiv = ElementFromFile(
templatePath(__dirname, "button_template.html")
);
module.exports = (_options) => {
if (_options.forceHide) return;
options = _options;
document.addEventListener('apiLoaded', setup, { once: true, passive: true });
}
function setup(e) {
api = e.detail;
player = $('ytmusic-player');
video = $('video');
$('ytmusic-player-page').prepend(switchButtonDiv);
$('#song-image.ytmusic-player').style.display = "block";
if (options.hideVideo) {
$('.video-switch-button-checkbox').checked = false;
changeDisplay(false);
forcePlaybackMode();
// fix black video
video.style.height = "auto";
}
// button checked = show video
switchButtonDiv.addEventListener('change', (e) => {
options.hideVideo = !e.target.checked;
changeDisplay(e.target.checked);
setOptions("video-toggle", options);
})
video.addEventListener('srcChanged', videoStarted);
observeThumbnail();
}
function changeDisplay(showVideo) {
player.style.margin = showVideo ? '' : 'auto 0px';
player.setAttribute('playback-mode', showVideo ? 'OMV_PREFERRED' : 'ATV_PREFERRED');
$('#song-video.ytmusic-player').style.display = showVideo ? 'unset' : 'none';
if (showVideo && !video.style.top) {
video.style.top = `${(player.clientHeight - video.clientHeight) / 2}px`;
}
moveVolumeHud(showVideo);
}
function videoStarted() {
if (api.getPlayerResponse().videoDetails.musicVideoType !== 'MUSIC_VIDEO_TYPE_ATV') {
// switch to high res thumbnail
forceThumbnail($('#song-image img'));
// show toggle button
switchButtonDiv.style.display = "initial";
// change display to video mode if video exist & video is hidden & option.hideVideo = false
if (!options.hideVideo && $('#song-video.ytmusic-player').style.display === "none") {
changeDisplay(true);
} else {
moveVolumeHud(!options.hideVideo);
}
} else {
// video doesn't exist -> switch to song mode
changeDisplay(false);
// hide toggle button
switchButtonDiv.style.display = "none";
}
}
// on load, after a delay, the page overrides the playback-mode to 'OMV_PREFERRED' which causes weird aspect ratio in the image container
// this function fix the problem by overriding that override :)
function forcePlaybackMode() {
const playbackModeObserver = new MutationObserver(mutations => {
mutations.forEach(mutation => {
if (mutation.target.getAttribute('playback-mode') !== "ATV_PREFERRED") {
playbackModeObserver.disconnect();
mutation.target.setAttribute('playback-mode', "ATV_PREFERRED");
}
});
});
playbackModeObserver.observe(player, { attributeFilter: ["playback-mode"] });
}
// if precise volume plugin is enabled, move its hud to be on top of the video
function moveVolumeHud(showVideo) {
const volumeHud = $('#volumeHud');
if (volumeHud)
volumeHud.style.top = showVideo ? `${(player.clientHeight - video.clientHeight) / 2}px` : 0;
}
function observeThumbnail() {
const playbackModeObserver = new MutationObserver(mutations => {
if (!player.videoMode_) return;
mutations.forEach(mutation => {
if (!mutation.target.src.startsWith('data:')) return;
forceThumbnail(mutation.target)
});
});
playbackModeObserver.observe($('#song-image img'), { attributeFilter: ["src"] })
}
function forceThumbnail(img) {
const thumbnails = $('#movie_player').getPlayerResponse()?.videoDetails?.thumbnail?.thumbnails;
if (thumbnails && thumbnails.length > 0) {
img.src = thumbnails[thumbnails.length - 1].url.split("?")[0];
}
}

View File

@ -0,0 +1,13 @@
const { setOptions } = require("../../config/plugins");
module.exports = (win, options) => [
{
label: "Force Remove Video Tab",
type: "checkbox",
checked: options.forceHide,
click: item => {
options.forceHide = item.checked;
setOptions("video-toggle", options);
}
}
];

View File

@ -0,0 +1,4 @@
<div class="video-switch-button">
<input class="video-switch-button-checkbox" type="checkbox" checked="true"></input>
<label class="video-switch-button-label" for=""><span class="video-switch-button-label-span">Song</span></label>
</div>

View File

@ -1,16 +1,27 @@
const path = require("path");
const { contextBridge, remote } = require("electron");
const remote = require('@electron/remote');
const config = require("./config");
const { fileExists } = require("./plugins/utils");
const setupFrontLogger = require("./providers/front-logger");
const setupSongInfo = require("./providers/song-info-front");
const { setupSongControls } = require("./providers/song-controls-front");
const plugins = config.plugins.getEnabled();
let api;
plugins.forEach(([plugin, options]) => {
const pluginPath = path.join(__dirname, "plugins", plugin, "actions.js");
fileExists(pluginPath, () => {
const actions = require(pluginPath).actions || {};
const preloadPath = path.join(__dirname, "plugins", plugin, "preload.js");
fileExists(preloadPath, () => {
const run = require(preloadPath);
run(options);
});
const actionPath = path.join(__dirname, "plugins", plugin, "actions.js");
fileExists(actionPath, () => {
const actions = require(actionPath).actions || {};
// TODO: re-enable once contextIsolation is set to true
// contextBridge.exposeInMainWorld(plugin + "Actions", actions);
@ -29,7 +40,52 @@ document.addEventListener("DOMContentLoaded", () => {
});
});
// wait for complete load of youtube api
listenForApiLoad();
// inject song-info provider
setupSongInfo();
// inject song-controls
setupSongControls();
// inject front logger
setupFrontLogger();
// Add action for reloading
global.reload = () =>
remote.getCurrentWindow().webContents.loadURL(config.get("url"));
// Blocks the "Are You Still There?" popup by setting the last active time to Date.now every 15min
setInterval(() => window._lact = Date.now(), 900000);
});
function listenForApiLoad() {
api = document.querySelector('#movie_player');
if (api) {
onApiLoaded();
return;
}
const observer = new MutationObserver(() => {
api = document.querySelector('#movie_player');
if (api) {
observer.disconnect();
onApiLoaded();
}
})
observer.observe(document.documentElement, { childList: true, subtree: true });
}
function onApiLoaded() {
document.dispatchEvent(new CustomEvent('apiLoaded', { detail: api }));
// Remove upgrade button
if (config.get("options.removeUpgradeButton")) {
const upgradeButtton = document.querySelector('ytmusic-pivot-bar-item-renderer[tab-id="SPunlimited"]')
if (upgradeButtton) {
upgradeButtton.style.display = "none";
}
}
}

View File

@ -1,23 +1,4 @@
let domElements = {};
const watchDOMElement = (name, selectorFn, cb) => {
const observer = new MutationObserver((mutations, observer) => {
if (!domElements[name]) {
domElements[name] = selectorFn(document);
}
if (domElements[name]) {
cb(domElements[name]);
}
});
observer.observe(document, {
childList: true,
subtree: true,
});
};
const getSongMenu = () =>
document.querySelector("ytmusic-menu-popup-renderer tp-yt-paper-listbox");
module.exports = { getSongMenu, watchDOMElement };
module.exports = { getSongMenu };

13
providers/front-logger.js Normal file
View File

@ -0,0 +1,13 @@
const { ipcRenderer } = require("electron");
function logToString(log) {
return (typeof log === "string") ?
log :
JSON.stringify(log, null, "\t");
}
module.exports = () => {
ipcRenderer.on("log", (_event, log) => {
console.log(logToString(log));
});
};

View File

@ -0,0 +1,14 @@
const customTitlebar = require("custom-electron-titlebar");
module.exports = () => {
new customTitlebar.Titlebar({
backgroundColor: customTitlebar.Color.fromHex("#050505"),
minimizable: false,
maximizable: false,
menu: null
});
const mainStyle = document.querySelector("#container").style;
mainStyle.width = "100%";
mainStyle.position = "fixed";
mainStyle.border = "unset";
};

View File

@ -0,0 +1,18 @@
const path = require("path");
const is = require("electron-is");
const iconPath = path.join(__dirname, "..", "assets", "youtube-music-tray.png");
const customTitlebarPath = path.join(__dirname, "prompt-custom-titlebar.js");
const promptOptions = is.macOS() ? {
customStylesheet: "dark",
icon: iconPath
} : {
customStylesheet: "dark",
// The following are used for custom titlebar
frame: false,
customScript: customTitlebarPath,
enableRemoteModule: true
};
module.exports = () => promptOptions;

View File

@ -0,0 +1,13 @@
const { ipcRenderer } = require("electron");
const config = require("../config");
const is = require("electron-is");
module.exports.setupSongControls = () => {
document.addEventListener('apiLoaded', e => {
ipcRenderer.on("seekTo", (_, t) => e.detail.seekTo(t));
ipcRenderer.on("seekBy", (_, t) => e.detail.seekBy(t));
if (is.linux() && config.plugins.isEnabled('shortcuts')) { // MPRIS Enabled
document.querySelector('video').addEventListener('seeked', v => ipcRenderer.send('seeked', v.target.currentTime));
}
}, { once: true, passive: true })
};

View File

@ -13,8 +13,8 @@ module.exports = (win) => {
previous: () => pressKey(win, "k"),
next: () => pressKey(win, "j"),
playPause: () => pressKey(win, "space"),
like: () => pressKey(win, "_"),
dislike: () => pressKey(win, "+"),
like: () => pressKey(win, "+"),
dislike: () => pressKey(win, "_"),
go10sBack: () => pressKey(win, "h"),
go10sForward: () => pressKey(win, "l"),
go1sBack: () => pressKey(win, "h", ["shift"]),
@ -24,8 +24,6 @@ module.exports = (win) => {
// General
volumeMinus10: () => pressKey(win, "-"),
volumePlus10: () => pressKey(win, "="),
dislikeAndNext: () => pressKey(win, "-", ["shift"]),
like: () => pressKey(win, "=", ["shift"]),
fullscreen: () => pressKey(win, "f"),
muteUnmute: () => pressKey(win, "m"),
maximizeMinimisePlayer: () => pressKey(win, "q"),
@ -38,14 +36,14 @@ module.exports = (win) => {
pressKey(win, "g");
pressKey(win, "l");
},
goToHotlist: () => {
pressKey(win, "g");
pressKey(win, "t");
},
goToSettings: () => {
pressKey(win, "g");
pressKey(win, ",");
},
goToExplore: () => {
pressKey(win, "g");
pressKey(win, "e");
},
search: () => pressKey(win, "/"),
showShortcuts: () => pressKey(win, "/", ["shift"]),
};

View File

@ -0,0 +1,60 @@
const { ipcRenderer } = require("electron");
const is = require('electron-is');
const { getImage } = require("./song-info");
const config = require("../config");
global.songInfo = {};
function $(selector) { return document.querySelector(selector); }
ipcRenderer.on("update-song-info", async (_, extractedSongInfo) => {
global.songInfo = JSON.parse(extractedSongInfo);
global.songInfo.image = await getImage(global.songInfo.imageSrc);
});
// used because 'loadeddata' or 'loadedmetadata' weren't firing on song start for some users (https://github.com/th-ch/youtube-music/issues/473)
const srcChangedEvent = new CustomEvent('srcChanged');
module.exports = () => {
document.addEventListener('apiLoaded', apiEvent => {
if (config.plugins.isEnabled('tuna-obs') ||
(is.linux() && config.plugins.isEnabled('shortcuts'))) {
setupTimeChangeListener();
}
const video = $('video');
// name = "dataloaded" and abit later "dataupdated"
apiEvent.detail.addEventListener('videodatachange', (name, _dataEvent) => {
if (name !== 'dataloaded') return;
video.dispatchEvent(srcChangedEvent);
sendSongInfo();
})
for (const status of ['playing', 'pause']) {
video.addEventListener(status, e => {
if (Math.round(e.target.currentTime) > 0) {
ipcRenderer.send("playPaused", {
isPaused: status === 'pause',
elapsedSeconds: Math.floor(e.target.currentTime)
});
}
});
}
function sendSongInfo() {
const data = apiEvent.detail.getPlayerResponse();
data.videoDetails.album = $('ytmusic-player-page')?.__data?.playerPageWatchMetadata?.albumName?.runs[0].text
data.videoDetails.elapsedSeconds = Math.floor(video.currentTime);
data.videoDetails.isPaused = false;
ipcRenderer.send("video-src-changed", JSON.stringify(data));
}
}, { once: true, passive: true });
};
function setupTimeChangeListener() {
const progressObserver = new MutationObserver(mutations => {
ipcRenderer.send('timeChanged', mutations[0].target.value);
global.songInfo.elapsedSeconds = mutations[0].target.value;
});
progressObserver.observe($('#progress-bar'), { attributeFilter: ["value"] })
}

View File

@ -1,137 +1,129 @@
const { nativeImage } = require("electron");
const { ipcMain, nativeImage } = require("electron");
const fetch = require("node-fetch");
// This selects the song title
const titleSelector = ".title.style-scope.ytmusic-player-bar";
const config = require("../config");
// This selects the song image
const imageSelector =
"#layout > ytmusic-player-bar > div.middle-controls.style-scope.ytmusic-player-bar > img";
// This selects the song subinfo, this includes artist, views, likes
const subInfoSelector =
"#layout > ytmusic-player-bar > div.middle-controls.style-scope.ytmusic-player-bar > div.content-info-wrapper.style-scope.ytmusic-player-bar > span";
// This selects the progress bar, used for songlength and current progress
const progressSelector = "#progress-bar";
// Grab the title using the selector
const getTitle = (win) => {
return win.webContents
.executeJavaScript(
"document.querySelector('" + titleSelector + "').innerText"
)
.catch((error) => {
console.log(error);
});
};
// Grab the image src using the selector
const getImageSrc = (win) => {
return win.webContents
.executeJavaScript("document.querySelector('" + imageSelector + "').src")
.catch((error) => {
console.log(error);
});
};
// Grab the subinfo using the selector
const getSubInfo = async (win) => {
// Get innerText of subinfo element
const subInfoString = await win.webContents.executeJavaScript(
'document.querySelector("' + subInfoSelector + '").innerText'
);
// Split and clean the string
const splittedSubInfo = subInfoString.replaceAll("\n", "").split(" • ");
// Make sure we always return 3 elements in the aray
const subInfo = [];
for (let i = 0; i < 3; i++) {
// Fill array with empty string if not defined
subInfo.push(splittedSubInfo[i] || "");
}
return subInfo;
};
// Grab the progress using the selector
const getProgress = async (win) => {
// Get max value of the progressbar element
const songDuration = await win.webContents.executeJavaScript(
'document.querySelector("' + progressSelector + '").max'
);
// Get current value of the progressbar element
const elapsedSeconds = await win.webContents.executeJavaScript(
'document.querySelector("' + progressSelector + '").value'
);
return { songDuration, elapsedSeconds };
// Fill songInfo with empty values
/**
* @typedef {songInfo} SongInfo
*/
const songInfo = {
title: "",
artist: "",
views: 0,
uploadDate: "",
imageSrc: "",
image: null,
isPaused: undefined,
songDuration: 0,
elapsedSeconds: 0,
url: "",
album: undefined,
videoId: "",
playlistId: "",
};
// Grab the native image using the src
const getImage = async (src) => {
const result = await fetch(src);
const buffer = await result.buffer();
return nativeImage.createFromBuffer(buffer);
const output = nativeImage.createFromBuffer(buffer);
if (output.isEmpty() && !src.endsWith(".jpg") && src.includes(".jpg")) { // fix hidden webp files (https://github.com/th-ch/youtube-music/issues/315)
return getImage(src.slice(0, src.lastIndexOf(".jpg") + 4));
} else {
return output;
}
};
const getPausedStatus = async (win) => {
const title = await win.webContents.executeJavaScript("document.title");
return !title.includes("-");
};
const handleData = async (responseText, win) => {
const data = JSON.parse(responseText);
if (!data) return;
// Fill songInfo with empty values
const songInfo = {
title: "",
artist: "",
views: "",
likes: "",
imageSrc: "",
image: null,
isPaused: true,
songDuration: 0,
elapsedSeconds: 0,
};
const microformat = data.microformat?.microformatDataRenderer;
if (microformat) {
songInfo.uploadDate = microformat.uploadDate;
songInfo.url = microformat.urlCanonical?.split("&")[0];
songInfo.playlistId = new URL(microformat.urlCanonical).searchParams.get("list");
// used for options.resumeOnStart
config.set("url", microformat.urlCanonical);
}
const registerProvider = (win) => {
// This variable will be filled with the callbacks once they register
const callbacks = [];
const videoDetails = data.videoDetails;
if (videoDetails) {
songInfo.title = cleanupName(videoDetails.title);
songInfo.artist = cleanupName(videoDetails.author);
songInfo.views = videoDetails.viewCount;
songInfo.songDuration = videoDetails.lengthSeconds;
songInfo.elapsedSeconds = videoDetails.elapsedSeconds;
songInfo.isPaused = videoDetails.isPaused;
songInfo.videoId = videoDetails.videoId;
songInfo.album = data?.videoDetails?.album; // Will be undefined if video exist
// This function will allow plugins to register callback that will be triggered when data changes
const registerCallback = (callback) => {
callbacks.push(callback);
};
win.on("page-title-updated", async () => {
// Save the old title temporarily
const oldTitle = songInfo.title;
// Get and set the new data
songInfo.title = await getTitle(win);
songInfo.isPaused = await getPausedStatus(win);
const { songDuration, elapsedSeconds } = await getProgress(win);
songInfo.songDuration = songDuration;
songInfo.elapsedSeconds = elapsedSeconds;
// If title changed then we do need to update other info
if (oldTitle !== songInfo.title) {
const subInfo = await getSubInfo(win);
songInfo.artist = subInfo[0];
songInfo.views = subInfo[1];
songInfo.likes = subInfo[2];
songInfo.imageSrc = await getImageSrc(win);
const oldUrl = songInfo.imageSrc;
songInfo.imageSrc = videoDetails.thumbnail?.thumbnails?.pop()?.url.split("?")[0];
if (oldUrl !== songInfo.imageSrc) {
songInfo.image = await getImage(songInfo.imageSrc);
}
// Trigger the callbacks
win.webContents.send("update-song-info", JSON.stringify(songInfo));
}
};
// This variable will be filled with the callbacks once they register
const callbacks = [];
// This function will allow plugins to register callback that will be triggered when data changes
/**
* @callback songInfoCallback
* @param {songInfo} songInfo
* @returns {void}
*/
/**
* @param {songInfoCallback} callback
*/
const registerCallback = (callback) => {
callbacks.push(callback);
};
const registerProvider = (win) => {
// This will be called when the song-info-front finds a new request with song data
ipcMain.on("video-src-changed", async (_, responseText) => {
await handleData(responseText, win);
callbacks.forEach((c) => {
c(songInfo);
});
});
return registerCallback;
ipcMain.on("playPaused", (_, { isPaused, elapsedSeconds }) => {
songInfo.isPaused = isPaused;
songInfo.elapsedSeconds = elapsedSeconds;
callbacks.forEach((c) => {
c(songInfo);
});
})
};
module.exports = registerProvider;
const suffixesToRemove = [
" - topic",
"vevo",
" (performance video)",
" (official music video)",
" (official video)",
" (clip officiel)",
];
function cleanupName(name) {
if (!name) return name;
const lowCaseName = name.toLowerCase();
for (const suffix of suffixesToRemove) {
if (lowCaseName.endsWith(suffix)) {
return name.slice(0, -suffix.length);
}
}
return name;
}
module.exports = registerCallback;
module.exports.setupSongInfo = registerProvider;
module.exports.getImage = getImage;
module.exports.cleanupName = cleanupName;

View File

@ -34,16 +34,32 @@ You can check out the [latest release](https://github.com/th-ch/youtube-music/re
Install the `youtube-music-bin` package from the AUR. For AUR installation instructions, take a look at this [wiki page](https://wiki.archlinux.org/index.php/Arch_User_Repository#Installing_packages).
## Available plugins:
- **Ad Blocker**: block all ads and tracking out of the box
- **Downloader**: download to MP3 directly from the interface (youtube-dl)
- **No Google Login**: remove Google login buttons and links from the interface
- **Shortcuts**: use your usual shortcuts (media keys, Ctrl/CMD + F…) to control YouTube Music
- **Audio compressor**: apply compression to audio (lowers the volume of the loudest parts of the signal and raises the volume of the softest parts)
- **Blur navigation bar**: makes navigation bar transparent and blurry
- **Disable autoplay**: makes every song start in "paused" mode
- [**Discord**](https://discord.com/): show your friends what you listen to with [Rich Presence](https://user-images.githubusercontent.com/28219076/104362104-a7a0b980-5513-11eb-9744-bb89eabe0016.png)
- **Downloader**: downloads MP3 [directly from the interface](https://user-images.githubusercontent.com/61631665/129977677-83a7d067-c192-45e1-98ae-b5a4927393be.png) [(youtube-dl)](https://github.com/ytdl-org/youtube-dl)
- **In-app menu**: [gives bars a fancy, dark look](https://user-images.githubusercontent.com/78568641/112215894-923dbf00-8c29-11eb-95c3-3ce15db27eca.png)
> (see [this post](https://github.com/th-ch/youtube-music/issues/410#issuecomment-952060709) if you have problem accessing the menu after enabling this plugin and hide-menu option)
- [**Last.fm**](https://www.last.fm/): scrobbles support
- **Navigation**: next/back navigation arrows directly integrated in the interface, like in your favorite browser
- **Auto confirm when paused**: when the "Continue Watching?" modal appears, automatically click "Yes"
- **Hide video player**: no video in the interface when playing music
- **Notifications**: display a notification when a song starts playing
- **No Google Login**: remove Google login buttons and links from the interface
- **Notifications**: display a notification when a song starts playing ([interactive notifications](https://user-images.githubusercontent.com/78568641/114102651-63ce0e00-98d0-11eb-9dfe-c5a02bb54f9c.png) are available on windows)
- **Playback speed**: listen fast, listen slow! [Adds a slider that controls song speed](https://user-images.githubusercontent.com/61631665/129976003-e55db5ba-bf42-448c-a059-26a009775e68.png)
- **Precise volume**: customizable volume steps for more comfort, allows controlling the volume precisely using mousewheel
- **Quality changer**: Allows changing the video quality with a [button](https://user-images.githubusercontent.com/78568641/138574366-70324a5e-2d64-4f6a-acdd-dc2a2b9cecc5.png) on the video overlay
- **Shortcuts**: Allows setting global hotkeys for playback (play/pause/next/previous) + disable [media osd](https://user-images.githubusercontent.com/84923831/128601225-afa38c1f-dea8-4209-9f72-0f84c1dd8b54.png) by overriding media keys + enable Ctrl/CMD + F to search + enable linux mpris support for mediakeys + [custom hotkeys](https://github.com/Araxeus/youtube-music/blob/1e591d6a3df98449bcda6e63baab249b28026148/providers/song-controls.js#L13-L50) for [advanced users](https://github.com/th-ch/youtube-music/issues/106#issuecomment-952156902)
- [**SponsorBlock**](https://github.com/ajayyy/SponsorBlock): skips non-music parts
- **Taskbar media control**: control playback from your [Windows taskbar](https://user-images.githubusercontent.com/78568641/111916130-24a35e80-8a82-11eb-80c8-5021c1aa27f4.png)
- **Touchbar**: custom TouchBar layout for macOS
- **Video Toggle**: Adds a button to switch between Video/Song mode. can also optionally remove the whole video tab
---
- **Auto confirm when paused** (Always Enabled): disable the ["Continue Watching?"](https://user-images.githubusercontent.com/61631665/129977894-01c60740-7ec6-4bf0-9a2c-25da24491b0e.png) popup that pause music after a certain time
> If using `Hide Menu` option - you can show the menu with the `alt` key (or `escape` if using the in-app-menu plugin)
## Dev

View File

@ -1,9 +1,7 @@
const path = require("path");
const getPort = require("get-port");
const NodeEnvironment = require("jest-environment-node");
const electronPath = require("electron");
const { Application } = require("spectron");
const { _electron: electron } = require("playwright");
class TestEnvironment extends NodeEnvironment {
constructor(config) {
@ -14,21 +12,12 @@ class TestEnvironment extends NodeEnvironment {
await super.setup();
const appPath = path.resolve(__dirname, "..");
const port = await getPort();
this.global.__APP__ = new Application({
path: electronPath,
args: [appPath],
port,
});
await this.global.__APP__.start();
const { client } = this.global.__APP__;
await client.waitUntilWindowLoaded();
this.global.__APP__ = await electron.launch({ args: [appPath] });
}
async teardown() {
if (this.global.__APP__.isRunning()) {
await this.global.__APP__.stop();
if (this.global.__APP__) {
await this.global.__APP__.close();
}
await super.teardown();
}

View File

@ -1,23 +1,16 @@
/**
* @jest-environment ./tests/environment
*/
describe("YouTube Music App", () => {
const app = global.__APP__;
test("With default settings, app is launched and visible", async () => {
expect(app.isRunning()).toBe(true);
const win = app.browserWindow;
const isMenuVisible = await win.isMenuBarVisible();
expect(isMenuVisible).toBe(true);
const isVisible = await win.isVisible();
expect(isVisible).toBe(true);
const { width, height } = await win.getBounds();
expect(width).toBeGreaterThan(0);
expect(height).toBeGreaterThan(0);
const { client } = app;
const title = await client.getTitle();
const window = await app.firstWindow();
const title = await window.title();
expect(title).toEqual("YouTube Music");
const url = window.url();
expect(url.startsWith("https://music.youtube.com")).toBe(true);
});
});

12
tray.js
View File

@ -3,7 +3,6 @@ const path = require("path");
const { Menu, nativeImage, Tray } = require("electron");
const config = require("./config");
const { mainMenuTemplate } = require("./menu");
const getSongControls = require("./providers/song-controls");
// Prevent tray being garbage collected
@ -32,7 +31,7 @@ module.exports.setUpTray = (app, win) => {
}
});
const trayMenu = Menu.buildFromTemplate([
let template = [
{
label: "Play/Pause",
click: () => {
@ -57,13 +56,16 @@ module.exports.setUpTray = (app, win) => {
win.show();
},
},
...mainMenuTemplate(win),
{
label: "Quit",
label: "Restart App",
click: () => {
app.relaunch();
app.quit();
},
},
]);
{ role: "quit" }
];
const trayMenu = Menu.buildFromTemplate(template);
tray.setContextMenu(trayMenu);
};

6159
yarn.lock

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@
/* Allow window dragging */
ytmusic-nav-bar {
-webkit-user-select: none;
-webkit-app-region : drag;
-webkit-app-region: drag;
}
iron-icon,
@ -17,14 +17,25 @@ a {
/* custom style for navbar */
ytmusic-app-layout {
--ytmusic-nav-bar-height: 85px;
--ytmusic-nav-bar-height: 90px;
}
ytmusic-search-box.ytmusic-nav-bar {
margin-top: 12px;
margin-top: 15px;
}
/* Blocking annoying elements */
ytmusic-mealbar-promo-renderer {
display: none !important;
}
/* Disable Image Selection */
img {
-webkit-user-select: none;
user-select: none;
}
/* Hide cast button which doesn't work */
ytmusic-cast-button {
display: none !important;
}