Compare commits

...

342 Commits

Author SHA1 Message Date
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
8124623142 fix notificationOnUnpause option 2021-05-22 18:38:36 +03: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
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
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
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
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
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
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
3522925dec remove open dependency 2021-05-08 01:00:23 +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
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
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
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
44 changed files with 2602 additions and 897 deletions

View File

@ -15,26 +15,55 @@ 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,
},
downloader: {
enabled: false,
ffmpegArgs: [], // e.g. ["-b:a", "192k"] for an audio bitrate of 192kb/s
downloadFolder: undefined, // Custom download folder (absolute path)
},
"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
},
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: {
enabled: false, // enable global shortcuts
volumeUp: "Shift+PageUp", // Keybind default can be changed
volumeDown: "Shift+PageDown"
},
savedVolume: undefined //plugin save volume between session here
}
},
};

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,
};

118
index.js
View File

@ -11,6 +11,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({
@ -32,6 +33,10 @@ 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")();
@ -75,6 +80,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,
@ -93,14 +99,18 @@ function createMainWindow() {
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"),
});
if (windowPosition) {
@ -133,7 +143,11 @@ 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();
@ -143,14 +157,35 @@ function createMainWindow() {
return win;
}
app.on("browser-window-created", (event, win) => {
app.once("browser-window-created", (event, win) => {
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) => {
@ -222,6 +257,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")) {
@ -270,13 +334,11 @@ app.on("ready", () => {
}
// Optimized for Mac OS X
if (is.macOS()) {
if (!config.get("options.appVisible")) {
app.dock.hide();
}
if (is.macOS() && !config.get("options.appVisible")) {
app.dock.hide();
}
var forceQuit = false;
let forceQuit = false;
app.on("before-quit", () => {
forceQuit = true;
});
@ -291,3 +353,27 @@ 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;
}
});
}

242
menu.js
View File

@ -1,34 +1,54 @@
const { existsSync } = require("fs");
const path = require("path");
const { app, Menu } = require("electron");
const is = require("electron-is");
const { getAllPlugins } = require("./plugins/utils");
const config = require("./config");
const pluginEnabledMenu = (win, plugin, label = "", hasSubmenu = false) => ({
label: label || plugin,
type: "checkbox",
checked: config.plugins.isEnabled(plugin),
//Submenu check used in in-app-menu
hasSubmenu: hasSubmenu || undefined,
click: (item) => {
if (item.checked) {
config.plugins.enable(plugin);
} else {
config.plugins.disable(plugin);
}
if (hasSubmenu) {
this.setApplicationMenu(win);
}
},
});
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);
}
},
};
const pluginPath = path.join(__dirname, "plugins", plugin, "menu.js")
if (existsSync(pluginPath)) {
if (!config.plugins.isEnabled(plugin)) {
return pluginEnabledMenu(win, plugin, "", true);
}
const getPluginMenu = require(pluginPath);
return {
label: plugin,
submenu: [
pluginEnabledMenu(win, plugin, "Enabled", true),
...getPluginMenu(win, config.plugins.getOptions(plugin), () =>
module.exports.setApplicationMenu(win)
),
],
};
}
return pluginEnabledMenu(win, plugin);
}),
{ type: "separator" },
{
label: "Advanced options",
click: () => {
config.edit();
},
},
],
},
{
@ -42,30 +62,6 @@ const mainMenuTemplate = (win) => [
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: "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);
},
},
{
label: "Resume last song when app starts",
type: "checkbox",
@ -76,29 +72,29 @@ const mainMenuTemplate = (win) => [
},
...(is.windows() || is.linux()
? [
{
label: "Hide menu",
type: "checkbox",
checked: config.get("options.hideMenu"),
click: (item) => {
config.set("options.hideMenu", item.checked);
},
{
label: "Hide menu",
type: "checkbox",
checked: config.get("options.hideMenu"),
click: (item) => {
config.set("options.hideMenu", item.checked);
},
]
},
]
: []),
...(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",
checked: config.get("options.startAtLogin"),
click: (item) => {
config.set("options.startAtLogin", item.checked);
},
// https://www.electronjs.org/docs/api/app#appsetloginitemsettingssettings-macos-windows
[
{
label: "Start at login",
type: "checkbox",
checked: config.get("options.startAtLogin"),
click: (item) => {
config.set("options.startAtLogin", item.checked);
},
]
},
]
: []),
{
label: "Tray",
@ -145,22 +141,126 @@ const mainMenuTemplate = (win) => [
},
{ type: "separator" },
{
label: "Toggle DevTools",
// Cannot use "toggleDevTools" role in MacOS
label: "Advanced options",
submenu: [
{
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" },
{
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: "Edit config.json",
click: () => {
config.edit();
},
},
]
},
],
},
{
label: "View",
submenu: [
{
label: "Reload",
click: () => {
const { webContents } = win;
if (webContents.isDevToolsOpened()) {
webContents.closeDevTools();
} else {
const devToolsOptions = {};
webContents.openDevTools(devToolsOptions);
win.webContents.reload();
},
},
{
label: "Force Reload",
click: () => {
win.webContents.reloadIgnoringCache();
},
},
{ type: "separator" },
{
label: "Zoom In",
click: () => {
win.webContents.setZoomLevel(
win.webContents.getZoomLevel() + 1
);
},
},
{
label: "Zoom Out",
click: () => {
win.webContents.setZoomLevel(
win.webContents.getZoomLevel() - 1
);
},
},
{
label: "Reset Zoom",
click: () => {
win.webContents.setZoomLevel(0);
},
},
],
},
{
label: "Navigation",
submenu: [
{
label: "Go back",
click: () => {
if (win.webContents.canGoBack()) {
win.webContents.goBack();
}
},
},
{
label: "Advanced options",
label: "Go forward",
click: () => {
config.edit();
if (win.webContents.canGoForward()) {
win.webContents.goForward();
}
},
},
{
label: "Restart App",
click: () => {
app.relaunch();
app.quit();
},
},
{
label: "Quit App",
click: () => {
app.quit();
},
},
],

View File

@ -1,7 +1,7 @@
{
"name": "youtube-music",
"productName": "YouTube Music",
"version": "1.11.0",
"version": "1.12.1",
"description": "YouTube Music Desktop App - including custom plugins",
"license": "MIT",
"repository": "th-ch/youtube-music",
@ -42,6 +42,7 @@
"scripts": {
"test": "jest",
"start": "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",
@ -62,33 +63,38 @@
"npm": "Please use yarn and not npm"
},
"dependencies": {
"@cliqz/adblocker-electron": "^1.20.0",
"@ffmpeg/core": "^0.8.5",
"@cliqz/adblocker-electron": "^1.20.5",
"@ffmpeg/core": "^0.9.0",
"@ffmpeg/ffmpeg": "^0.9.7",
"YoutubeNonStop": "git://github.com/lawfx/YoutubeNonStop.git#v0.8.1",
"YoutubeNonStop": "git://github.com/lawfx/YoutubeNonStop.git#v0.9.0",
"async-mutex": "^0.3.1",
"browser-id3-writer": "^4.4.0",
"chokidar": "^3.5.1",
"custom-electron-titlebar": "^3.2.6",
"discord-rpc": "^3.2.0",
"downloads-folder": "^3.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",
"electron-updater": "^4.3.8",
"filenamify": "^4.3.0",
"md5": "^2.3.0",
"node-fetch": "^2.6.1",
"ytdl-core": "^4.4.5"
"node-notifier": "^9.0.1",
"ytdl-core": "^4.7.0",
"ytpl": "^2.2.0"
},
"devDependencies": {
"electron": "^11.2.3",
"electron-builder": "^22.9.1",
"electron": "^12.0.8",
"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",
"rimraf": "^3.0.2",
"spectron": "^13.0.0",
"xo": "^0.37.1"
"spectron": "^14.0.0",
"xo": "^0.38.2"
},
"resolutions": {
"yargs-parser": "18.1.3"
@ -97,6 +103,16 @@
"envs": [
"node",
"browser"
]
],
"rules": {
"quotes": [
"error",
"double",
{
"avoidEscape": true,
"allowTemplateLiterals": true
}
]
}
}
}

View File

@ -1,6 +1,6 @@
const Discord = require("discord-rpc");
const getSongInfo = require("../../providers/song-info");
const registerCallback = require("../../providers/song-info");
const rpc = new Discord.Client({
transport: "ipc",
@ -9,27 +9,38 @@ const rpc = new Discord.Client({
// Application ID registered by @semvis123
const clientId = "790655993809338398";
module.exports = (win) => {
const registerCallback = getSongInfo(win);
let clearActivity;
module.exports = (win, {activityTimoutEnabled, activityTimoutTime}) => {
// If the page is ready, register the callback
win.on("ready-to-show", () => {
rpc.on("ready", () => {
win.once("ready-to-show", () => {
rpc.once("ready", () => {
// Register the callback
registerCallback((songInfo) => {
if (songInfo.title.length === 0 && songInfo.artist.length === 0) {
return;
}
// Song information changed, so lets update the rich presence
const activityInfo = {
details: songInfo.title,
state: songInfo.artist,
largeImageKey: "logo",
largeImageText: songInfo.views + " - " + songInfo.likes,
largeImageText: [
songInfo.uploadDate,
songInfo.views.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") + " views"
].join(' || '),
};
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(()=>rpc.clearActivity(), activityTimoutTime||10000);
} else {
// stop the clear activity timout
clearTimeout(clearActivity);
// Add the start and end time of the song
const songStartTime = Date.now() - songInfo.elapsedSeconds * 1000;
activityInfo.startTimestamp = songStartTime;
@ -42,10 +53,6 @@ module.exports = (win) => {
});
// Startup the rpc client
rpc
.login({
clientId,
})
.catch(console.error);
rpc.login({ clientId }).catch(console.error);
});
};

View File

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

View File

@ -4,59 +4,71 @@ 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.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 +82,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
);
};
// });

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

@ -0,0 +1,115 @@
const { existsSync, mkdirSync } = require("fs");
const { join } = require("path");
const { URL } = require("url");
const { dialog } = require("electron");
const is = require("electron-is");
const ytpl = require("ytpl");
const chokidar = require('chokidar');
const { setOptions } = require("../../config/plugins");
const registerCallback = require("../../providers/song-info");
const { sendError } = require("./back");
const { defaultMenuDownloadLabel, getFolder } = require("./utils");
let downloadLabel = defaultMenuDownloadLabel;
let metadataURL = undefined;
let callbackIsRegistered = false;
module.exports = (win, options) => {
if (!callbackIsRegistered) {
registerCallback((info) => {
metadataURL = info.url;
});
callbackIsRegistered = true;
}
return [
{
label: downloadLabel,
click: async () => {
const currentURL = metadataURL || win.webContents.getURL();
const playlistID = new URL(currentURL).searchParams.get("list");
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
},
},
];
};

View File

@ -19,11 +19,11 @@
<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"
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"
class="style-scope yt-icon" fill="#aaaaaa"
/>
</g>
</svg>

View File

@ -0,0 +1,31 @@
const electron = require("electron");
module.exports.getFolder = (customFolder) =>
customFolder || (electron.app || electron.remote.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;
}

View File

@ -1,7 +1,7 @@
const { randomBytes } = require("crypto");
const { join } = require("path");
const downloadsFolder = require("downloads-folder");
const Mutex = require("async-mutex").Mutex;
const { ipcRenderer } = require("electron");
const is = require("electron-is");
const filenamify = require("filenamify");
@ -12,8 +12,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 { getFolder, urlToJPG } = require("./utils");
const { cleanupArtistName } = require("../../providers/song-info");
const { createFFmpeg } = FFmpeg;
const ffmpeg = createFFmpeg({
@ -21,16 +23,31 @@ 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 || cleanupArtistName(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 +67,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 +84,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 +105,17 @@ const toMP3 = async (
sendFeedback,
sendError,
reinit,
options
options,
existingMetadata = undefined,
subfolder = ""
) => {
const safeVideoName = randomBytes(32).toString("hex");
const extension = options.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,7 +123,7 @@ const toMP3 = async (
ffmpeg.FS("writeFile", safeVideoName, buffer);
sendFeedback("Converting…");
const metadata = getMetadata();
const metadata = existingMetadata || getMetadata();
await ffmpeg.run(
"-i",
safeVideoName,
@ -102,24 +132,30 @@ const toMP3 = async (
safeVideoName + "." + extension
);
const folder = options.downloadFolder || downloadsFolder();
const name = metadata
? `${metadata.artist} - ${metadata.title}`
const folder = getFolder(options.downloadFolder);
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 +169,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,83 @@
const path = require("path");
const { Menu } = require("electron");
const electronLocalshortcut = require("electron-localshortcut");
const config = require("../../config");
const { setApplicationMenu } = require("../../menu");
const { injectCSS } = require("../utils");
//tracks menu visibility
let visible = true;
// win hook for fixing menu
let win;
const originalBuildMenu = Menu.buildFromTemplate;
// This function natively gets called on all submenu so no more reason to use recursion
Menu.buildFromTemplate = (template) => {
// Fix checkboxes and radio buttons
updateTemplate(template);
// return as normal
return originalBuildMenu(template);
};
module.exports = (winImport) => {
win = winImport;
// css for custom scrollbar + disable drag area(was causing bugs)
injectCSS(win.webContents, path.join(__dirname, "style.css"));
win.once("ready-to-show", () => {
setApplicationMenu(win);
//register keyboard shortcut && hide menu if hideMenu is enabled
if (config.get("options.hideMenu")) {
electronLocalshortcut.register(win, "Esc", () => {
switchMenuVisibility();
});
}
});
//set menu visibility on load
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 switchMenuVisibility() {
setMenuVisibility(!visible);
}
function setMenuVisibility(value) {
visible = value;
win.webContents.send("updateMenu", visible);
}
function updateCheckboxesAndRadioButtons(item, isRadio, hasSubmenu) {
if (!isRadio) {
//fix checkbox
item.checked = !item.checked;
}
//update menu if radio / hasSubmenu
if (isRadio || hasSubmenu) {
win.webContents.send("updateMenu", true);
}
}
// Update checkboxes/radio buttons
function updateTemplate(template) {
for (let item of template) {
// Change onClick of checkbox+radio
if ((item.type === "checkbox" || item.type === "radio") && !item.fixed) {
const originalOnclick = item.click;
item.click = (itemClicked) => {
originalOnclick(itemClicked);
updateCheckboxesAndRadioButtons(itemClicked, item.type === "radio", item.hasSubmenu);
};
item.fixed = true;
}
}
}

View File

@ -0,0 +1,24 @@
const { remote, ipcRenderer } = require("electron");
const customTitlebar = require("custom-electron-titlebar");
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, menu) {
if (menu) {
bar.updateMenu(remote.Menu.getApplicationMenu());
} else {
try {
bar.updateMenu(null);
} catch (e) {
//will always throw type error - null isn't menu, but it works
}
}
});
};

View File

@ -0,0 +1,79 @@
/* increase font size for menu and menuItems */
.titlebar,
.menubar-menu-container .action-label {
font-size: 14px !important;
}
/* allow submenu's to show correctly */
.menubar-menu-container {
overflow-y: visible !important;
}
/* fixes scrollbar positioning relative to nav bar */
#nav-bar-background.ytmusic-app-layout {
right: 15px !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;
}
/* navbar background black */
.center-content.ytmusic-nav-bar {
background: #030303;
}
/* move up item selectrion renderer by 15 px */
ytmusic-item-section-renderer[has-item-section-tabbed-header-renderer_] #header.ytmusic-item-section-renderer {
top: 75 !important;
}
/* fix weird positioning in search screen*/
ytmusic-header-renderer.ytmusic-search-page {
position: unset !important;
}
/* Move navBar downwards */
ytmusic-app-layout > [slot="nav-bar"],
#nav-bar-background.ytmusic-app-layout {
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

@ -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

@ -0,0 +1,23 @@
const { isEnabled } = require("../../config/plugins");
/*
This is used to determine if plugin is actually active
(not if its only enabled in options)
*/
let enabled = false;
module.exports = (win) => {
enabled = true;
// youtube-music register some of the target listeners after DOMContentLoaded
// did-finish-load is called after all elements finished loading, including said listeners
// Thats the reason the timing is controlled from main
win.webContents.once("did-finish-load", () => {
win.webContents.send("restoreAddEventListener");
win.webContents.send("setupVideoPlayerVolumeMousewheel", !isEnabled("hide-video-player"));
});
};
module.exports.enabled = () => {
return enabled;
};

View File

@ -0,0 +1,208 @@
const { ipcRenderer, remote } = require("electron");
const { setOptions } = require("../../config/plugins");
function $(selector) { return document.querySelector(selector); }
module.exports = (options) => {
setupPlaybar(options);
setupSliderObserver(options);
setupLocalArrowShortcuts(options);
if (options.globalShortcuts?.enabled) {
setupGlobalShortcuts(options);
}
firstRun(options);
// This way the ipc listener gets cleared either way
ipcRenderer.once("setupVideoPlayerVolumeMousewheel", (_event, toEnable) => {
if (toEnable)
setupVideoPlayerOnwheel(options);
});
};
/** 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 toPercent(volume) {
return Math.round(Number.parseFloat(volume) * 100);
}
function saveVolume(volume, options) {
options.savedVolume = volume;
setOptions("precise-volume", options);
}
/** Restore saved volume and setup tooltip */
function firstRun(options) {
const videoStream = $(".video-stream");
const slider = $("#volume-slider");
// Those elements load abit after DOMContentLoaded
if (videoStream && slider) {
// Set saved volume IF it pass checks
if (options.savedVolume
&& options.savedVolume >= 0 && options.savedVolume <= 100
&& Math.abs(slider.value - options.savedVolume) < 5
// If plugin was disabled and volume changed then diff>4
) {
videoStream.volume = options.savedVolume / 100;
slider.value = options.savedVolume;
}
// Set current volume as tooltip
setTooltip(toPercent(videoStream.volume));
} else {
setTimeout(firstRun, 500, options); // Try again in 500 milliseconds
}
}
/** 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");
});
}
/** if (toIncrease = false) then volume decrease */
function changeVolume(toIncrease, options) {
// Need to change both the actual volume and the slider
const videoStream = $(".video-stream");
const slider = $("#volume-slider");
// Apply volume change if valid
const steps = (options.steps || 1) / 100;
videoStream.volume = toIncrease ?
Math.min(videoStream.volume + steps, 1) :
Math.max(videoStream.volume - steps, 0);
// Save the new volume
saveVolume(toPercent(videoStream.volume), options);
// Slider value automatically rounds to multiples of 5
slider.value = options.savedVolume;
// Change tooltips to new value
setTooltip(options.savedVolume);
// Show volume slider on volume change
showVolumeSlider(slider);
}
let volumeHoverTimeoutID;
function showVolumeSlider(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);
}
/** 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 &&
(!options.savedVolume || 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
});
}
// 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) {
remote.globalShortcut.register((options.globalShortcuts.volumeUp), () => changeVolume(true, options));
}
if (options.globalShortcuts.volumeDown) {
remote.globalShortcut.register((options.globalShortcuts.volumeDown), () => changeVolume(false, options));
}
}
function setupLocalArrowShortcuts(options) {
if (options.arrowsShortcut) {
addListener();
}
// Change options from renderer to keep sync
ipcRenderer.on("setArrowsShortcut", (_event, isEnabled) => {
options.arrowsShortcut = isEnabled;
setOptions("precise-volume", options);
// This allows changing this setting without restarting app
if (isEnabled) {
addListener();
} else {
removeListener();
}
});
function addListener() {
window.addEventListener('keydown', callback);
}
function removeListener() {
window.removeEventListener("keydown", callback);
}
function callback(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,19 @@
const { enabled } = require("./back");
const { setOptions } = require("../../config/plugins");
module.exports = (win, options) => [
{
label: "Arrowkeys controls",
type: "checkbox",
checked: !!options.arrowsShortcut,
click: (item) => {
// Dynamically change setting if plugin enabled
if (enabled()) {
win.webContents.send("setArrowsShortcut", item.checked);
} else { // Fallback to usual method if disabled
options.arrowsShortcut = item.checked;
setOptions("precise-volume", options);
}
}
}
];

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 did-finish-load to avoid keeping Element.prototype altered
ipcRenderer.once("restoreAddEventListener", () => { // Called from main to make sure page is completly loaded
Element.prototype.addEventListener = Element.prototype._addEventListener;
Element.prototype._addEventListener = undefined;
ignored = undefined;
});
};

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

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

@ -1,16 +1,25 @@
const path = require("path");
const { contextBridge, remote } = require("electron");
const { remote } = require("electron");
const config = require("./config");
const { fileExists } = require("./plugins/utils");
const setupFrontLogger = require("./providers/front-logger");
const setupSongControl = require("./providers/song-controls-front");
const setupSongInfo = require("./providers/song-info-front");
const plugins = config.plugins.getEnabled();
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,6 +38,15 @@ document.addEventListener("DOMContentLoaded", () => {
});
});
// inject song-info provider
setupSongInfo();
// inject song-control provider
setupSongControl();
// inject front logger
setupFrontLogger();
// Add action for reloading
global.reload = () =>
remote.getCurrentWindow().webContents.loadURL(config.get("url"));

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,18 @@
const { ipcRenderer } = require("electron");
let videoStream = document.querySelector(".video-stream");
module.exports = () => {
ipcRenderer.on("playPause", () => {
if (!videoStream) {
videoStream = document.querySelector(".video-stream");
}
if (videoStream.paused) {
videoStream.play();
} else {
videoStream.yns_pause ?
videoStream.yns_pause() :
videoStream.pause();
}
});
};

View File

@ -12,7 +12,7 @@ module.exports = (win) => {
// Playback
previous: () => pressKey(win, "k"),
next: () => pressKey(win, "j"),
playPause: () => pressKey(win, "space"),
playPause: () => win.webContents.send("playPause"),
like: () => pressKey(win, "_"),
dislike: () => pressKey(win, "+"),
go10sBack: () => pressKey(win, "h"),

View File

@ -0,0 +1,31 @@
const { ipcRenderer } = require("electron");
const { getImage } = require("./song-info");
global.songInfo = {};
ipcRenderer.on("update-song-info", async (_, extractedSongInfo) => {
global.songInfo = JSON.parse(extractedSongInfo);
global.songInfo.image = await getImage(global.songInfo.imageSrc);
});
const injectListener = () => {
const oldXHR = window.XMLHttpRequest;
function newXHR() {
const realXHR = new oldXHR();
realXHR.addEventListener(
"readystatechange",
() => {
if (realXHR.readyState === 4 && realXHR.status === 200
&& realXHR.responseURL.includes("/player")) {
// if the request contains the song info, send the response to ipcMain
ipcRenderer.send("song-info-request", realXHR.responseText);
}
},
false
);
return realXHR;
}
window.XMLHttpRequest = newXHR;
};
module.exports = injectListener;

View File

@ -1,73 +1,19 @@
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";
// 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
// This selects the progress bar, used for 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 };
return elapsedSeconds;
};
// Grab the native image using the src
@ -77,61 +23,93 @@ const getImage = async (src) => {
return nativeImage.createFromBuffer(buffer);
};
// To find the paused status, we check if the title contains `-`
const getPausedStatus = async (win) => {
const title = await win.webContents.executeJavaScript("document.title");
return !title.includes("-");
};
const getArtist = async (win) => {
return await win.webContents.executeJavaScript(`
document.querySelector(".subtitle.ytmusic-player-bar .yt-formatted-string")
?.textContent
`);
}
// Fill songInfo with empty values
const songInfo = {
title: "",
artist: "",
views: "",
likes: "",
views: 0,
uploadDate: "",
imageSrc: "",
image: null,
isPaused: true,
isPaused: undefined,
songDuration: 0,
elapsedSeconds: 0,
url: "",
};
const handleData = async (responseText, win) => {
let data = JSON.parse(responseText);
songInfo.title = data?.videoDetails?.title;
songInfo.artist = await getArtist(win) || cleanupArtistName(data?.videoDetails?.author);
songInfo.views = data?.videoDetails?.viewCount;
songInfo.imageSrc = data?.videoDetails?.thumbnail?.thumbnails?.pop()?.url;
songInfo.songDuration = data?.videoDetails?.lengthSeconds;
songInfo.image = await getImage(songInfo.imageSrc);
songInfo.uploadDate = data?.microformat?.microformatDataRenderer?.uploadDate;
songInfo.url = data?.microformat?.microformatDataRenderer?.urlCanonical;
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
const registerCallback = (callback) => {
callbacks.push(callback);
};
const registerProvider = (win) => {
// 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
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;
const elapsedSeconds = await getProgress(win);
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);
songInfo.image = await getImage(songInfo.imageSrc);
}
// Trigger the callbacks
callbacks.forEach((c) => {
c(songInfo);
});
});
return registerCallback;
// This will be called when the song-info-front finds a new request with song data
ipcMain.on("song-info-request", async (_, responseText) => {
await handleData(responseText, win);
callbacks.forEach((c) => {
c(songInfo);
});
});
};
module.exports = registerProvider;
const suffixesToRemove = [' - Topic', 'VEVO'];
function cleanupArtistName(artist) {
if (!artist) {
return artist;
}
for (const suffix of suffixesToRemove) {
if (artist.endsWith(suffix)) {
return artist.slice(0, -suffix.length);
}
}
return artist;
}
module.exports = registerCallback;
module.exports.setupSongInfo = registerProvider;
module.exports.getImage = getImage;
module.exports.cleanupArtistName = cleanupArtistName;

15
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,21 @@ module.exports.setUpTray = (app, win) => {
win.show();
},
},
...mainMenuTemplate(win),
{
label: "Restart App",
click: () => {
app.relaunch();
app.quit();
},
},
{
label: "Quit",
click: () => {
app.quit();
},
},
]);
];
const trayMenu = Menu.buildFromTemplate(template);
tray.setContextMenu(trayMenu);
};

1477
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,11 +17,11 @@ 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 */