Compare commits

...

1038 Commits

Author SHA1 Message Date
22b74113b6 init webnowplaying 2024-03-28 23:05:55 +09:00
9da3ad2fb7 chore(deps): update dependency electron to v29.1.6 (#1898)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-28 17:30:29 +09:00
d45d597136 chore(i18n): Translated using Weblate (Portuguese)
Currently translated at 100.0% (342 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pt/
2024-03-28 07:47:08 +01:00
2495d5da99 chore(i18n): Translated using Weblate (Dutch)
Currently translated at 5.5% (19 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/nl/
2024-03-27 21:31:00 +01:00
33aeafd19c chore(i18n): Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (342 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2024-03-27 21:31:00 +01:00
374d0ce5e7 chore(i18n): Translated using Weblate (Russian)
Currently translated at 100.0% (342 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ru/
2024-03-27 21:31:00 +01:00
371805334b Improve video title filters (#1667) 2024-03-28 02:54:16 +09:00
47dbeff0d0 fix: fix switch-repeat
fix #1810
2024-03-28 02:40:29 +09:00
17652b5b77 chore(deps): update dependency rollup to v4.13.1 (#1896)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-28 01:12:07 +09:00
9608c2a7fc chore(i18n): Added translation using Weblate (Hebrew) 2024-03-27 08:26:39 +01:00
8abe2823d7 chore(deps): update dependency node-gyp to v10.1.0 (#1890)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-27 01:18:08 +09:00
dbc7f23ab8 chore(deps): update dependency node-gyp to v10.1.0 (#1889)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-26 20:31:13 +09:00
357bd935e4 Update changelog for v3.3.5 2024-03-26 10:58:49 +00:00
f99ca53a6d Bump version to 3.3.5 2024-03-26 11:51:38 +01:00
8700c1a110 chore(deps): update dependency node-gyp to v10.1.0 (#1885)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-26 19:51:34 +09:00
c5e37b791c chore(deps): update dependency @typescript-eslint/eslint-plugin to v7.4.0 (#1886)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-26 19:51:29 +09:00
307f6387ab fix(style): resolve #1887 2024-03-26 19:09:51 +09:00
652a150a0a chore(i18n): Translated using Weblate (Swedish)
Currently translated at 8.4% (29 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/sv/
2024-03-26 10:45:23 +01:00
2c59badb46 chore(i18n): Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/
2024-03-26 10:41:31 +01:00
69087bbf1f chore(i18n): Translated using Weblate (Swedish)
Currently translated at 7.6% (26 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/sv/
2024-03-26 10:41:31 +01:00
af78f1596a chore(i18n): Translated using Weblate (French)
Currently translated at 98.8% (338 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/fr/
2024-03-26 10:41:30 +01:00
fca936a698 chore(i18n): Added translation using Weblate (Swedish) 2024-03-25 19:06:56 +01:00
54b70f6b3e chore(deps): update dependency vite to v5.2.6 (#1883)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-25 05:08:49 +09:00
62f7d440fa Update changelog for v3.3.4 2024-03-23 18:21:29 +00:00
752ccbf482 Bump version to 3.3.4 2024-03-24 03:14:20 +09:00
a8bc53912d fix(style): fix miniplayer style 2024-03-24 02:07:36 +09:00
ed700c2916 fix(style): fix fullscreen style and in-app-menu 2024-03-24 01:28:34 +09:00
97695444af Update changelog for v3.3.3 2024-03-23 15:44:08 +00:00
85e5e1814a Bump version to 3.3.3 2024-03-24 00:37:13 +09:00
88c84a50d0 chore(deps): update dependency electron to v29.1.5 (#1876)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-24 00:24:06 +09:00
0004fb3fc8 chore(deps): update dependency typescript to v5.4.3 (#1877)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-24 00:22:34 +09:00
9cbaf5797b chore(deps): update dependency discord-api-types to v0.37.76 (#1878)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-24 00:22:28 +09:00
1df75ae82f chore(deps): update dependency vite to v5.2.4 (#1881)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-24 00:22:22 +09:00
4d86af5437 Ambient Plugin cleanup (#1880) 2024-03-24 00:22:15 +09:00
ba0876fd8b chore(i18n): Translated using Weblate (Thai)
Currently translated at 69.5% (238 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/th/
2024-03-21 19:01:58 +01:00
a3a3fca694 chore(i18n): Translated using Weblate (Thai)
Currently translated at 69.2% (237 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/th/
2024-03-20 18:50:45 +01:00
de4396936d chore(deps): update dependency vite to v5.2.2 (#1875)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-21 00:58:59 +09:00
164575296f fix(deps): update dependency solid-js to v1.8.16 (#1873)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-20 17:36:36 +09:00
7e8cbfc4c0 chore(i18n): Translated using Weblate (Portuguese)
Currently translated at 97.3% (333 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pt/
2024-03-19 16:01:50 +01:00
679938ccf7 fix: add support for Wayland
fix #1864
2024-03-19 21:11:10 +09:00
5a6d681bf4 chore(deps): update dependency @typescript-eslint/eslint-plugin to v7.3.1 (#1868)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-19 18:10:26 +09:00
62304b723e chore(deps): update dependency discord-api-types to v0.37.75 (#1867)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-19 18:10:04 +09:00
c89bb4606f chore(i18n): Translated using Weblate (Hungarian)
Currently translated at 12.8% (44 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/hu/
2024-03-18 12:01:57 +01:00
14c50e0d57 chore(deps): update pnpm to v8.15.5 (#1865)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-18 13:37:14 +09:00
f7e9cf9a29 fix: Fix Miniplayer image size (#1863) 2024-03-18 02:46:44 +09:00
4bb3f41828 fix(style): fixed image/video alignment when toggle is active (#1862) 2024-03-18 02:46:23 +09:00
e4759ebe25 chore(i18n): Translated using Weblate (Lithuanian)
Currently translated at 84.7% (290 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/lt/
2024-03-17 11:13:36 +01:00
ce33a92f02 chore(i18n): Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (342 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2024-03-17 11:13:36 +01:00
dd44c07450 chore(i18n): Translated using Weblate (Russian)
Currently translated at 100.0% (342 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ru/
2024-03-17 11:13:36 +01:00
15a5b7a820 chore(i18n): Translated using Weblate (German)
Currently translated at 100.0% (342 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2024-03-17 11:13:36 +01:00
41b7e095eb chore: Update README-is.md (#1858) 2024-03-17 11:53:15 +09:00
f34a297fcf chore(deps): update dependency vite-plugin-solid to v2.10.2 (#1859)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-17 11:52:59 +09:00
9b2c1a320b fix: Ambient Mode intialization improvement (#1857) 2024-03-17 11:52:46 +09:00
70ed6f8e6c chore(i18n): Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/
2024-03-17 02:01:51 +01:00
5a2489f0bf chore(i18n): Translated using Weblate (Dutch)
Currently translated at 4.9% (17 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/nl/
2024-03-17 02:01:50 +01:00
a7d035022a chore(i18n): Translated using Weblate (Thai)
Currently translated at 69.0% (236 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/th/
2024-03-17 02:01:50 +01:00
b879a70b24 chore(i18n): Added translation using Weblate (Dutch) 2024-03-16 14:48:17 +01:00
ec4e9a1d47 chore(deps): bump follow-redirects from 1.15.5 to 1.15.6 (#1856)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-15 14:45:05 +09:00
d9c52c0a7f chore(README): Nicer Readme 2.0 (#1833) 2024-03-15 14:43:57 +09:00
81ecf18231 chore(deps): update dependency discord-api-types to v0.37.74 (#1854)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-15 14:40:22 +09:00
b0b12a075d chore(deps): update dependency esbuild to v0.20.2 (#1855)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-15 14:40:16 +09:00
54c428083c Improve ambient mode (#1853) 2024-03-15 14:39:31 +09:00
60228a387a chore(deps): update dependency electron to v29.1.4 (#1852)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-14 18:32:17 +09:00
3e04baef00 chore(deps): update dependency electron to v29.1.3 (#1851)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-14 13:34:42 +09:00
573bcba1a0 chore(i18n): Translated using Weblate (Bulgarian)
Currently translated at 25.7% (88 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/bg/
2024-03-13 18:01:58 +01:00
ae2fad5db3 chore(i18n): Translated using Weblate (French)
Currently translated at 95.6% (327 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/fr/
2024-03-13 18:01:56 +01:00
c8fc12569c chore(deps): update dependency rollup to v4.13.0 (#1850)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-13 08:00:29 +09:00
df8efe3fa4 chore(i18n): Translated using Weblate (Bulgarian)
Currently translated at 11.1% (38 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/bg/
2024-03-12 17:01:53 +01:00
HKi
251131b9b5 chore(i18n): Translated using Weblate (Vietnamese)
Currently translated at 99.4% (340 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/vi/
2024-03-12 17:01:53 +01:00
35fb61087a fix(deps): update dependency electron-store to v8.2.0 (#1843)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-12 07:45:40 +09:00
cbec88ff47 chore(deps): update dependency electron to v29.1.1 (#1841)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-12 07:43:13 +09:00
f4319616a6 fix(deps): update dependency i18next to v23.10.1 (#1842)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-12 07:43:04 +09:00
1534b7a67f chore(deps): update dependency @typescript-eslint/eslint-plugin to v7.2.0 (#1848)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-12 07:05:33 +09:00
cbfcc9d140 chore(deps): update dependency vite to v5.1.6 (#1847)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-12 07:05:27 +09:00
60d10a9222 fix(deps): update dependency async-mutex to v0.5.0 (#1849)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-12 07:05:18 +09:00
17e090d5c5 chore(i18n): Translated using Weblate (Hungarian)
Currently translated at 8.4% (29 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/hu/
2024-03-11 07:01:55 +01:00
fa6b4fa83b chore(i18n): Translated using Weblate (Japanese)
Currently translated at 100.0% (342 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ja/
2024-03-11 07:01:54 +01:00
e4b5244d95 fix(deps): update dependency ts-morph to v22 (#1846)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-11 08:27:01 +09:00
04dc5d6314 chore(i18n): Added translation using Weblate (Hungarian) 2024-03-10 16:54:07 +01:00
33ccb03f90 chore(i18n): Translated using Weblate (Italian)
Currently translated at 100.0% (342 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2024-03-08 23:01:58 +01:00
80011ed3aa chore(i18n): Translated using Weblate (Polish)
Currently translated at 100.0% (342 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pl/
2024-03-07 20:01:57 +01:00
3e43cf5959 chore(deps): update dependency discord-api-types to v0.37.73 (#1840)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-08 04:00:53 +09:00
bbd590dde8 chore(deps): update dependency rollup to v4.12.1 (#1837)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-07 19:47:53 +09:00
0975a951e4 chore: Changed a single word (README-is.md) (#1836) 2024-03-07 05:27:39 +09:00
8b78f227a7 chore(deps): update dependency typescript to v5.4.2 (#1838)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-07 05:27:06 +09:00
5a93a04b61 chore(deps): update dependency electron-vite to v2.1.0 (#1823)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-05 15:47:51 +09:00
b971eb4191 chore(deps): update dependency @typescript-eslint/eslint-plugin to v7.1.1 (#1829)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-05 15:41:23 +09:00
403e825b8d chore(deps): update dependency vite to v5.1.5 (#1831)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-05 15:41:15 +09:00
9164eba88c Revert "chore(deps): update dependency electron-builder to v24.13.3" (#1818) 2024-03-03 04:56:49 +09:00
bb83bbac38 chore(deps): update dependency electron-builder to v24.13.3 (#1774)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-03 04:50:28 +09:00
651a641b22 chore: Update bug_report.yml 2024-03-02 23:56:23 +09:00
441b5fc8dd fix(style): fix navigation bar items are not working
resolve #1381
resolve #1396
resolve #1649
2024-03-02 23:51:18 +09:00
4fba9445d1 chore(i18n): Translated using Weblate (Norwegian Bokmål)
Currently translated at 66.6% (228 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/nb_NO/
2024-03-02 08:07:24 +01:00
3fb5e01ca5 chore(i18n): Translated using Weblate (Spanish)
Currently translated at 100.0% (342 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2024-03-02 08:07:24 +01:00
09b2b0d507 chore(deps): update playwright monorepo to v1.42.1 (#1816)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-02 15:31:55 +09:00
a9be35481a chore(i18n): Translated using Weblate (Romanian)
Currently translated at 100.0% (342 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ro/
2024-03-01 15:00:24 +01:00
c871506a69 chore(i18n): Translated using Weblate (Romanian)
Currently translated at 100.0% (342 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ro/
2024-03-01 15:00:24 +01:00
8e6790d366 chore(i18n): Translated using Weblate (Norwegian Bokmål)
Currently translated at 66.0% (226 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/nb_NO/
2024-03-01 15:00:22 +01:00
46620c5ec9 fix(tray): fix tray icon ratio in macOS 2024-03-01 03:20:37 +09:00
5f090169da fix: Add scale ratio for tray icons (#1811) 2024-03-01 03:14:51 +09:00
2205150b86 chore(i18n): Translated using Weblate (Romanian)
Currently translated at 19.5% (67 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ro/
2024-02-29 14:48:03 +01:00
3c4bb8a8fc chore(i18n): Translated using Weblate (Ukrainian)
Currently translated at 100.0% (342 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/uk/
2024-02-29 14:48:03 +01:00
77d0e71529 chore(i18n): Added translation using Weblate (Romanian) 2024-02-29 13:18:59 +01:00
cf974e2d62 chore(i18n): Translated using Weblate (Icelandic)
Currently translated at 98.8% (338 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/is/
2024-02-29 01:02:08 +01:00
ee03db4745 fix(README-is): image path 2024-02-28 14:59:36 +09:00
efd2061058 Icelandic translation of the readme file (#1806) 2024-02-28 14:58:32 +09:00
e7ed20f62f chore(deps): update dependency electron to v29.1.0 (#1808)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-28 14:58:25 +09:00
36d4c08a56 chore(deps): update playwright monorepo to v1.42.0 (#1805)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-28 14:57:10 +09:00
2a939e615c chore(deps): update dependency eslint to v8.57.0 (#1793)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-27 04:39:40 +09:00
9825165286 chore(deps): update dependency @typescript-eslint/eslint-plugin to v7.1.0 (#1800)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-27 04:30:11 +09:00
55c934ac7c fix: remove possible memory leak 2024-02-27 02:42:55 +09:00
c7715115ee chore(i18n): Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (342 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hans/
2024-02-26 18:01:55 +01:00
e93b5e8135 chore(deps): update dependency discord-api-types to v0.37.71 (#1799)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-27 01:20:07 +09:00
fd6ba1eda1 fix(downloader): fix memory leak reported in #1791 2024-02-26 19:14:13 +09:00
34f5411aec chore(i18n): Translated using Weblate (Indonesian)
Currently translated at 100.0% (342 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/id/
2024-02-25 13:02:04 +01:00
2b74ec2ef8 chore(i18n): Translated using Weblate (German)
Currently translated at 99.7% (341 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2024-02-25 13:02:03 +01:00
14dd0e8e03 chore(deps): update pnpm to v8.15.4 (#1795)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-25 17:40:19 +09:00
b0156261b7 chore(deps): update dependency @types/semver to v7.5.8 (#1797)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-25 17:40:12 +09:00
05d520f1c1 chore(i18n): Translated using Weblate (Icelandic)
Currently translated at 98.8% (338 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/is/
2024-02-23 15:02:00 +01:00
ccd44c79e8 fix: center the pause icon (#1786) 2024-02-23 22:56:40 +09:00
7be48ab05e fix(deps): update dependency @cliqz/adblocker-electron to v1.26.16 (#1788)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-23 22:56:19 +09:00
2d40b410b5 fix(deps): update dependency @cliqz/adblocker-electron-preload to v1.26.16 (#1789)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-23 22:47:34 +09:00
f54df86eec fix(deps): update dependency youtubei.js to v9.1.0 (#1790)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-23 20:55:24 +09:00
1be476de54 fix(deps): update dependency i18next to v23.10.0 (#1785)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-22 22:17:30 +09:00
82fa8719a9 chore(i18n): Translated using Weblate (Icelandic)
Currently translated at 97.0% (332 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/is/
2024-02-22 11:02:05 +01:00
7600620c4a chore(i18n): Translated using Weblate (Turkish)
Currently translated at 100.0% (342 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/tr/
2024-02-22 11:02:03 +01:00
c5217f3e1e chore(i18n): Translated using Weblate (Polish)
Currently translated at 100.0% (342 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pl/
2024-02-22 11:02:03 +01:00
706279852a chore(i18n): Translated using Weblate (Spanish)
Currently translated at 100.0% (342 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2024-02-22 11:02:03 +01:00
3a6274504f fix(ytm-bugs): fixed a scrollbar-color bug that affected Chromium 121 and later
fix #1737
2024-02-22 12:53:34 +09:00
3aa9398481 chore(deps): update dependency electron to v29 (#1773)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-22 11:58:40 +09:00
7cca435b1d chore(deps): update dependency vite to v5.1.4 (#1778)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-22 00:10:41 +09:00
241b5800d1 chore(deps): bump ip from 2.0.0 to 2.0.1 (#1777)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-21 11:02:47 +09:00
8abb8acdd3 chore(i18n): Translated using Weblate (Icelandic)
Currently translated at 19.0% (65 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/is/
2024-02-21 02:54:14 +01:00
ef8226c091 chore(i18n): Translated using Weblate (Spanish)
Currently translated at 100.0% (342 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2024-02-21 02:54:13 +01:00
7484e1bf9a chore(i18n): Added translation using Weblate (Icelandic) 2024-02-20 23:55:29 +01:00
2919fd54b7 Update changelog for v3.3.2 2024-02-20 12:07:35 +00:00
9eeb1c986a release 3.3.2 2024-02-20 20:59:48 +09:00
d37cd2418c fix: fix bugs in MPRIS, and improve MPRIS (#1760)
Co-authored-by: JellyBrick <shlee1503@naver.com>
Co-authored-by: Totto <32566573+Totto16@users.noreply.github.com>
2024-02-20 20:50:55 +09:00
8bd05f525d chore(deps): rollback dependency electron-builder to v24.9.1 2024-02-20 15:24:27 +09:00
47b23b414c chore(deps): update dependency electron-builder to v24.13.1 2024-02-20 14:43:04 +09:00
6f70d179c7 fix: fixed an issue that caused infinite loops when using Music Together
fix #1752
2024-02-20 12:57:49 +09:00
62a86e9267 fix(deps): update dependency electron-updater to v6.1.8 (#1770)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-20 12:55:26 +09:00
6358a2d0b1 chore(deps): update dependency electron-builder to v24.12.0 (#1771)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-20 12:55:17 +09:00
273633c2ce chore(i18n): Translated using Weblate (Korean)
Currently translated at 100.0% (342 of 342 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2024-02-20 04:15:04 +01:00
8b1209ef73 chore(i18n): Translated using Weblate (Italian)
Currently translated at 100.0% (340 of 340 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2024-02-20 03:55:03 +01:00
47505e9748 chore(i18n): Translated using Weblate (German)
Currently translated at 100.0% (340 of 340 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2024-02-20 03:55:03 +01:00
5178cc6bd8 feat(scrobblers): use BrowserWindow instead of shell.openExternal (#1758) 2024-02-20 11:54:57 +09:00
d9a27fff42 chore(deps): update dependency @typescript-eslint/eslint-plugin to v7.0.2 (#1763)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-20 11:54:37 +09:00
9e6560b814 chore(i18n): Translated using Weblate (Italian)
Currently translated at 99.7% (339 of 340 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2024-02-19 19:01:59 +01:00
afdb19a742 chore(deps): update dependency esbuild to v0.20.1 (#1759)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-19 18:23:04 +09:00
0ae5b668f5 fix(in-app-menu): default config 2024-02-19 17:06:01 +09:00
10533e28fa fix: error.html path 2024-02-19 16:53:32 +09:00
6189e67819 fix(deps): update dependency i18next to v23.9.0 (#1754)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-19 14:29:22 +09:00
f9ad505e40 fix: scrobbler migration 2024-02-19 14:27:24 +09:00
9b011101ed Update changelog for v3.3.1 2024-02-18 12:43:37 +00:00
a6ed8bf3aa release 3.3.1 (HOTFIX) 2024-02-18 21:36:04 +09:00
87acf4cf04 hotfix: in-app-menu position issue 2024-02-18 21:35:41 +09:00
b6fe2afd75 chore: Update README.md 2024-02-18 20:57:38 +09:00
6d9bb8eb1c Update changelog for v3.3.0 2024-02-18 11:40:17 +00:00
192fd0620f release 3.3.0 2024-02-18 20:32:45 +09:00
00d0b31980 fix(scrobbler): remove snake_case 2024-02-18 20:21:19 +09:00
5edb2131d2 fix(in-app-menu): implement auto close 2024-02-18 20:00:52 +09:00
4657aeca45 fix: apply fix from eslint 2024-02-18 11:58:26 +09:00
9f5651a8ba fix: fix upgrade button
fix #1199
2024-02-18 10:50:36 +09:00
570dcfee29 fix: revert fbc02a494a 2024-02-18 10:50:15 +09:00
2c130ce80d chore(mpris): use override keyword 2024-02-18 10:34:29 +09:00
8457115105 fix(mpris): fix mpris invalid position
- fix #1726
2024-02-18 10:26:45 +09:00
fbc02a494a fix(in-app-menu): simplified if-statement 2024-02-18 09:50:20 +09:00
7e2c254ecf fix(in-app-menu): should be disabled by default in linux env 2024-02-18 09:47:05 +09:00
11936a889e fix(deps): update dependency i18next to v23.8.3 (#1751)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-18 07:33:35 +09:00
f33970addd import fixed ./constants (#1748) 2024-02-18 07:33:11 +09:00
2c02b7193d chore(deps): update dependency rollup to v4.12.0 (#1743)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-17 10:08:33 +09:00
3f80b598ae chore(deps): bump undici from 5.28.2 to 5.28.3 (#1747)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-17 10:07:19 +09:00
477068ed49 chore(deps): update dependency vite to v5.1.3 (#1742)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-16 08:44:31 +09:00
b4083874ac fix: invalid podcast artist name
🤦
2024-02-16 08:41:00 +09:00
c5d0673b2f chore(deps): update dependency vite-plugin-solid to v2.10.1 (#1734)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-15 22:32:18 +09:00
9ccc44474b chore(deps): update dependency discord-api-types to v0.37.70 (#1740)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-15 22:31:50 +09:00
98e341f122 chore(deps): update dependency electron to v28.2.3 (#1736)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-15 22:29:11 +09:00
b23eba51dd chore(deps): update pnpm to v8.15.3 (#1739)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-15 22:28:47 +09:00
980005a58c chore(deps): update dependency rollup to v4.11.0 (#1738)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-15 22:28:41 +09:00
aba58b1d34 fix(deps): update dependency solid-js to v1.8.15 (#1735)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-15 22:28:17 +09:00
bb6a127d22 chore(deps): update dependency vite to v5.1.2 (#1733)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-15 20:32:18 +09:00
ac6e30a6b6 chore(i18n): Translated using Weblate (Polish)
Currently translated at 98.2% (334 of 340 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pl/
2024-02-14 19:01:58 +01:00
3f23282eed chore(deps): update dependency vite-plugin-solid to v2.10.0 (#1732)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-14 20:36:09 +09:00
199a77819c chore(deps): update pnpm to v8.15.2 (#1729)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-14 01:16:33 +09:00
89a53b2854 Update Copyright - 2024 (#1730) 2024-02-14 01:06:26 +09:00
c57bf79b08 chore(i18n): Translated using Weblate (Indonesian)
Currently translated at 100.0% (340 of 340 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/id/
2024-02-12 23:42:44 +01:00
eabb3392b4 fix: discord RPC (fix #1664) 2024-02-13 07:41:32 +09:00
c78cc3a38d chore(deps): update dependency @typescript-eslint/eslint-plugin to v7 (#1728)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-13 07:10:13 +09:00
011ffd538b fix(deps): update dependency @floating-ui/dom to v1.6.3 (#1727)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-13 06:41:43 +09:00
f386576196 chore(deps): update dependency electron to v28.2.2 (#1717)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-12 05:07:22 +09:00
bab3526f0f chore(deps): update dependency vite to v5.1.1 (#1718)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-12 05:07:14 +09:00
115923b422 chore(deps): update dependency @types/semver to v7.5.7 (#1724)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-12 05:07:07 +09:00
fefa8c8750 fix(deps): update dependency @floating-ui/dom to v1.6.2 (#1725)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-12 05:07:01 +09:00
3aa80c0f01 chore(i18n): Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (340 of 340 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2024-02-11 04:02:12 +01:00
aea219994a chore(deps): update dependency rollup to v4.10.0 (#1719)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-11 00:36:55 +09:00
74e22ccecd chore(i18n): Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (340 of 340 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hans/
2024-02-09 17:44:03 +01:00
db09cf5ffb chore(i18n): Translated using Weblate (Indonesian)
Currently translated at 58.8% (200 of 340 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/id/
2024-02-07 14:01:55 +01:00
cc1d13f203 chore(i18n): Translated using Weblate (Spanish)
Currently translated at 100.0% (340 of 340 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2024-02-07 14:01:54 +01:00
0cd1ce6a79 fix(deps): update dependency solid-js to v1.8.14 (#1713)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-06 18:00:05 +09:00
95254fc2ff chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.21.0 (#1711) 2024-02-06 05:20:13 +09:00
205376aadf fix(deps): update dependency semver to v7.6.0 (#1712) 2024-02-06 05:20:04 +09:00
6f057bfbfc fix(in-app-menu): remove unused style 2024-02-06 03:09:16 +09:00
bb39481666 fix(plugins): fix many bugs (in-app-menu, album-color-theme, blur-nav-bar) 2024-02-06 02:02:08 +09:00
ddb9968195 fix: workarounds for region restrict 2024-02-06 01:30:36 +09:00
a309729fa4 fix: revert electron version to v28 2024-02-06 01:30:09 +09:00
ba7e065ba6 fix: remove sign-in button (fix #1199) 2024-02-06 00:34:43 +09:00
ee05893d4c feat(album-color-theme): change nav-bar style 2024-02-06 00:00:32 +09:00
febc63edef fix(in-app-menu): fix app crash in production 2024-02-05 23:14:00 +09:00
b3c05c8647 refactor(in-app-menu): refactor in-app-menu plugin (#1710)
Co-authored-by: JellyBrick <shlee1503@naver.com>
2024-02-05 22:26:25 +09:00
cd8701d0e5 chore(i18n): Translated using Weblate (Ukrainian)
Currently translated at 100.0% (340 of 340 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/uk/
2024-02-05 02:02:48 +01:00
3b41edb62d chore(i18n): Translated using Weblate (Japanese)
Currently translated at 100.0% (340 of 340 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ja/
2024-02-05 02:02:38 +01:00
ab3b8495df chore(i18n): Translated using Weblate (Czech)
Currently translated at 89.7% (305 of 340 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2024-02-05 02:02:37 +01:00
b04cd79bcf chore(i18n): Translated using Weblate (Indonesian)
Currently translated at 52.0% (177 of 340 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/id/
2024-02-02 15:01:47 +01:00
c864e5764a chore(i18n): Translated using Weblate (Turkish)
Currently translated at 100.0% (340 of 340 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/tr/
2024-02-02 15:01:47 +01:00
1f71df6c41 chore(i18n): Translated using Weblate (Russian)
Currently translated at 100.0% (340 of 340 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ru/
2024-02-02 15:01:47 +01:00
0decda57ab chore(deps): update playwright monorepo to v1.41.2 (#1706)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-02 17:47:46 +09:00
5bfd3a4562 chore(deps): update dependency electron to v29.0.0-beta.5 (#1707)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-02 17:47:36 +09:00
d4af820ae8 chore(i18n): Translated using Weblate (Korean)
Currently translated at 100.0% (340 of 340 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2024-02-01 14:50:56 +01:00
3ec25b7779 chore(i18n): Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (338 of 338 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hans/
2024-02-01 14:12:03 +01:00
a9ee12b05e chore(i18n): Translated using Weblate (Turkish)
Currently translated at 100.0% (338 of 338 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/tr/
2024-02-01 14:12:03 +01:00
a612d1c1fd feat(album-color-theme): support album color theme in all pages (#1685) 2024-02-01 22:11:57 +09:00
fb48d24e0d fix(deps): update dependency youtubei.js to v9.0.2 (#1704)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-01 15:55:16 +09:00
38d19d9ea7 fix(deps): update dependency i18next to v23.8.2 (#1702)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-01 01:55:17 +09:00
4950abc399 fix(PiP): fix multiple handler 2024-01-31 18:40:36 +09:00
e3ad804dc4 feat: Support disabling scrobbling for non-music content (#1665)
Co-authored-by: JellyBrick <shlee1503@naver.com>
2024-01-31 17:41:55 +09:00
A L
f2f15bc3cc chore(i18n): Translated using Weblate (Bulgarian)
Currently translated at 5.0% (17 of 337 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/bg/
2024-01-30 23:01:21 +01:00
4624a1022a fix(deps): update dependency youtubei.js to v9 (#1682)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-30 19:04:03 +09:00
A L
cc169da6d4 chore(i18n): Added translation using Weblate (Bulgarian) 2024-01-30 10:10:26 +01:00
c58ed21661 chore(deps): update dependency electron to v29.0.0-beta.4 (#1698)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-30 09:16:36 +09:00
c483733bc3 fix(deps): update dependency i18next to v23.8.1 (#1694)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-30 09:13:41 +09:00
9738f2f6ae chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.20.0 (#1700)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-30 09:10:18 +09:00
ecdd8eb9f6 chore(deps): update pnpm to v8.15.1 (#1699)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-30 09:10:06 +09:00
47e2052ce0 chore(i18n): Translated using Weblate (Russian)
Currently translated at 100.0% (337 of 337 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ru/
2024-01-29 19:07:33 +01:00
a34eb31d9c chore(i18n): Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (337 of 337 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2024-01-29 18:47:29 +01:00
c03cab179e chore(i18n): Translated using Weblate (Russian)
Currently translated at 88.7% (299 of 337 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ru/
2024-01-29 18:47:29 +01:00
0d61cf906d Fix #1617
Remove duplicated call of `titleBar.appendChild(logo);`.
2024-01-29 14:50:41 +02:00
b3c1aa6b4b chore(deps): update dependency esbuild to v0.20.0 (#1691)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-28 07:07:22 +09:00
4904620ce6 chore(deps): update pnpm to v8.15.0 (#1692)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-28 07:07:09 +09:00
feaccb593d fix(deps): update dependency i18next to v23.7.20 (#1684)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-27 18:29:12 +09:00
9ad2baea59 chore(deps): update dependency electron to v29.0.0-beta.3 (#1683)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-27 18:29:01 +09:00
df77086039 chore(deps): update dependency electron to v29.0.0-beta.2 (#1681)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-26 09:45:16 +09:00
ceb844473a chore(deps): update dependency rollup to v4.9.6 (#1663)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-26 09:44:12 +09:00
a0932b0dc4 chore(i18n): Translated using Weblate (Italian)
Currently translated at 100.0% (337 of 337 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2024-01-25 23:01:50 +01:00
0be37716ef chore(deps): update dependency electron to v29.0.0-beta.1 (#1670) 2024-01-26 00:17:06 +09:00
c07b05a7be fix(deps): update dependency i18next to v23.7.19 (#1680) 2024-01-26 00:16:55 +09:00
94b1da9db0 chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.19.1 (#1669) 2024-01-26 00:16:28 +09:00
5fa7a1273f chore(deps): update pnpm to v8.14.3 (#1668) 2024-01-26 00:16:18 +09:00
cf9088785b chore(deps): update dependency vite-plugin-inspect to v0.8.3 (#1672) 2024-01-26 00:15:44 +09:00
adf3e3150e chore(deps): update dependency esbuild to v0.19.12 (#1673) 2024-01-26 00:13:11 +09:00
dbeb63018e fix(deps): update dependency @electron/remote to v2.1.2 (#1676) 2024-01-26 00:12:55 +09:00
51a39d240c chore: Update issue templates (#1661)
* Update bug_report.yml

* Update feature_request.yml

* Update bug_report.yml
2024-01-26 00:08:39 +09:00
4061f8e0e6 chore(i18n): Translated using Weblate (Japanese)
Currently translated at 100.0% (337 of 337 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ja/
2024-01-24 14:01:53 +01:00
d32e60249a chore(i18n): Translated using Weblate (Indonesian)
Currently translated at 52.5% (177 of 337 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/id/
2024-01-23 08:01:49 +01:00
b84a1e43af chore(i18n): Translated using Weblate (Thai)
Currently translated at 18.3% (62 of 337 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/th/
2024-01-23 08:01:48 +01:00
f5c22b63aa chore(i18n): Translated using Weblate (French)
Currently translated at 83.6% (282 of 337 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/fr/
2024-01-23 08:01:47 +01:00
41700799c7 chore(i18n): Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (337 of 337 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hans/
2024-01-22 07:01:47 +01:00
b15b421975 chore(i18n): Translated using Weblate (Japanese)
Currently translated at 92.5% (312 of 337 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ja/
2024-01-22 07:01:47 +01:00
cca4de8684 chore(deps): update playwright monorepo to v1.41.1 (#1660)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-21 12:46:32 +09:00
946c2790c4 fix(deps): update dependency i18next to v23.7.18 (#1662)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-21 12:46:15 +09:00
3a033f1bab chore(deps): update actions/dependency-review-action action to v4 (#1654)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-21 12:44:10 +09:00
67a04a2840 chore(deps): update dependency electron to v29.0.0-alpha.11 (#1656)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-21 12:43:57 +09:00
dd48e46854 chore(deps): update dependency vite to v5.0.12 [security] (#1659)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-21 12:43:17 +09:00
99edb15c77 chore(i18n): Translated using Weblate (Indonesian)
Currently translated at 32.6% (110 of 337 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/id/
2024-01-21 04:29:51 +01:00
8503afeac7 chore(i18n): Translated using Weblate (Japanese)
Currently translated at 85.7% (289 of 337 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ja/
2024-01-21 04:29:50 +01:00
0528637135 chore(i18n): Translated using Weblate (Vietnamese)
Currently translated at 99.4% (335 of 337 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/vi/
2024-01-19 09:00:21 +01:00
bf20a2b3be chore(i18n): Translated using Weblate (German)
Currently translated at 99.4% (335 of 337 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2024-01-19 09:00:19 +01:00
0db55ce4f3 fix(deps): update dependency async-mutex to v0.4.1 (#1653)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-18 17:40:11 +09:00
21347e9d0a chore(i18n): Translated using Weblate (Vietnamese)
Currently translated at 43.3% (146 of 337 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/vi/
2024-01-17 16:06:28 +01:00
4333a25c31 chore(i18n): Translated using Weblate (Ukrainian)
Currently translated at 100.0% (337 of 337 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/uk/
2024-01-17 16:06:26 +01:00
5b20e491bd chore(i18n): Translated using Weblate (Turkish)
Currently translated at 100.0% (337 of 337 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/tr/
2024-01-17 16:06:25 +01:00
bc05f5849d chore(i18n): Translated using Weblate (Spanish)
Currently translated at 100.0% (337 of 337 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2024-01-17 16:06:25 +01:00
b1046bc28d chore(deps): update playwright monorepo to v1.41.0 (#1651)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-17 11:31:10 +09:00
d5ab36f42e fix(crossfade): fix #1633 2024-01-16 22:38:05 +09:00
922d78dcee fix: notification close 2024-01-16 21:14:37 +09:00
de6506e6b4 fix: notification closing 2024-01-16 21:13:07 +09:00
9d136c8dd5 fix: apply fix from eslint 2024-01-16 21:02:55 +09:00
26de7f940e fix: fix #1621 2024-01-16 20:34:00 +09:00
7c404ba2ea chore(song-info): fix typo 2024-01-16 19:14:16 +09:00
96d2a72bfa chore(song-info): change from array to Set
change from `array` to `Set` to prevent the duplicate callback from being stored
2024-01-16 19:13:35 +09:00
10f41bddad fix(i18n): use dash (-) instead of under-bar (_) 2024-01-16 17:36:48 +09:00
39d2f3ec80 chore(i18n): Translated using Weblate (Korean)
Currently translated at 100.0% (337 of 337 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2024-01-16 09:33:15 +01:00
512b446a3d chore(i18n): Translated using Weblate (English)
Currently translated at 100.0% (337 of 337 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/en/
2024-01-16 09:33:15 +01:00
c9b96f0488 fix(tuna-obs): partially fix #1596 2024-01-16 17:27:27 +09:00
c84ea257d5 fix: os-specific plugin 2024-01-16 17:14:02 +09:00
6512f5ad2a fix(discord): fix hide duration button
fix #1644

Thanks to @DronovasP
2024-01-16 16:41:23 +09:00
e5d0eced5d fix(store): remove duplicated if-statement 2024-01-16 16:40:06 +09:00
f424ee5170 feat: Better Scrobbler Plugin (#1640)
Co-authored-by: JellyBrick <shlee1503@naver.com>
2024-01-16 16:36:27 +09:00
ea0f6c401d chore(deps): update dependency electron to v29.0.0-alpha.10 (#1645)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-16 14:34:05 +09:00
5c5f51b3de chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.19.0 (#1643)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-16 12:52:28 +09:00
7caf02ebba chore(i18n): Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (331 of 331 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hans/
2024-01-16 04:06:12 +01:00
f6a444b970 chore(i18n): Translated using Weblate (Russian)
Currently translated at 73.1% (242 of 331 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ru/
2024-01-15 02:26:24 +01:00
d19a36b44f chore(i18n): Translated using Weblate (Polish)
Currently translated at 100.0% (331 of 331 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pl/
2024-01-15 02:26:23 +01:00
aacb126fb5 chore(i18n): Translated using Weblate (Korean)
Currently translated at 100.0% (331 of 331 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2024-01-15 02:26:23 +01:00
5adf45cde2 chore(i18n): Translated using Weblate (Italian)
Currently translated at 100.0% (331 of 331 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2024-01-15 02:26:23 +01:00
0980aad060 chore(i18n): Translated using Weblate (Spanish)
Currently translated at 100.0% (331 of 331 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2024-01-15 02:26:23 +01:00
069e5ac8b8 chore(README): Fix plugins names and add plugins in/to Readme (in menu too) (#1624) 2024-01-14 14:50:40 +09:00
b5f6762997 chore(i18n): Translated using Weblate (Polish)
Currently translated at 100.0% (331 of 331 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pl/
2024-01-14 06:50:01 +01:00
c2abfe4b41 chore(i18n): Translated using Weblate (Polish)
Currently translated at 91.2% (302 of 331 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pl/
2024-01-13 13:43:07 +01:00
3964d03a3b fix: apply fix for album-actions 2024-01-13 17:28:12 +09:00
a82c4ce499 fix(album-actions): Fixed album actions (#1639) 2024-01-13 17:08:51 +09:00
2f54fa19e6 fix(yt-music bugs): fix margin in video mode 2024-01-13 12:35:54 +09:00
aacb4b3147 fix(deps): update dependency @xhayper/discord-rpc to v1.1.2 2024-01-13 10:10:32 +09:00
60980251f2 chore: update pnpm-lock 2024-01-13 10:07:55 +09:00
f8e55f95df fix(yt-music bugs): fixed a weird margin-bottom issue when using YouTube Premium 2024-01-13 10:02:18 +09:00
bebd232af0 chore(deps): update playwright monorepo to v1.41.0-beta-1705101589000 (#1638)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-13 09:08:02 +09:00
1a89fbe612 fix(#1543): fix song control doesn't work (#1637)
Co-authored-by: Su-Yong <simssy2205@gmail.com>
2024-01-13 09:06:41 +09:00
e73584c2aa feat(issue-template): add checklist 2024-01-13 07:34:00 +09:00
96a713074f chore(deps): update playwright monorepo to v1.41.0-beta-1705092460000 (#1635)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-13 07:20:59 +09:00
aedfa4ca06 chore(deps): update dependency rollup to v4.9.5 (#1629)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-13 07:17:58 +09:00
ac187f722b chore(i18n): Translated using Weblate (Vietnamese)
Currently translated at 29.6% (98 of 331 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/vi/
2024-01-12 23:16:48 +01:00
cd87642384 chore(i18n): Translated using Weblate (Indonesian)
Currently translated at 4.5% (15 of 331 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/id/
2024-01-12 23:16:48 +01:00
4a736d3211 chore(i18n): Translated using Weblate (Thai)
Currently translated at 1.2% (4 of 331 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/th/
2024-01-12 23:16:48 +01:00
e586940c57 chore(i18n): Translated using Weblate (Portuguese)
Currently translated at 100.0% (331 of 331 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pt/
2024-01-12 23:16:47 +01:00
ab36a21583 chore(i18n): Translated using Weblate (French)
Currently translated at 86.4% (286 of 331 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/fr/
2024-01-12 23:16:47 +01:00
c0c1c3b626 chore(i18n): Translated using Weblate (German)
Currently translated at 91.5% (303 of 331 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2024-01-12 23:16:47 +01:00
918bd7fdb1 chore(deps): update dependency electron to v29.0.0-alpha.9 (#1627)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-12 11:57:43 +09:00
971d1a5776 chore(i18n): Translated using Weblate (Vietnamese)
Currently translated at 16.9% (56 of 331 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/vi/
2024-01-11 18:06:13 +01:00
1235d46e73 chore(i18n): Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (331 of 331 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hans/
2024-01-11 18:06:13 +01:00
451b98833b chore(i18n): Translated using Weblate (Portuguese)
Currently translated at 99.3% (329 of 331 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pt/
2024-01-11 18:06:12 +01:00
48f9be9712 chore(i18n): Translated using Weblate (Italian)
Currently translated at 100.0% (331 of 331 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2024-01-11 18:06:12 +01:00
fd8d59bada chore: update pnpm-lock 2024-01-11 08:47:46 +09:00
99ce0b7f9c chore(deps): update dependency electron to v29.0.0-alpha.8 (#1608)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-11 08:08:37 +09:00
88ace9ab35 fix(deps): update dependency @cliqz/adblocker-electron to v1.26.15 (#1615)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-11 08:06:41 +09:00
63b0ea60e4 chore(deps): update dependency rollup to v4.9.4 (#1591)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-11 08:02:31 +09:00
b4ecf0f935 fix(deps): update dependency @cliqz/adblocker-electron-preload to v1.26.15 (#1616)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-11 08:02:10 +09:00
c846f18086 chore(deps): update pnpm to v8.14.1 (#1619)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-11 08:02:03 +09:00
8a851b06f9 chore(deps): update dependency eslint-plugin-prettier to v5.1.3 (#1618)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-10 14:36:19 +09:00
5484dc8bd1 chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.18.1 (#1612)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-09 19:36:07 +09:00
6c47ac36e3 fix(deps): update dependency youtubei.js to v8.2.0 (#1614)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-09 19:31:02 +09:00
3554496803 chore(deps): update dependency electron-vite to v2.0.0 (#1609)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-09 19:30:50 +09:00
b8a197615e chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.18.0 (#1603)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-08 23:22:16 +09:00
2ed949920f chore(i18n): Translated using Weblate (Ukrainian)
Currently translated at 100.0% (331 of 331 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/uk/
2024-01-08 14:06:18 +00:00
d76f4dade3 chore(i18n): Translated using Weblate (Ukrainian)
Currently translated at 100.0% (331 of 331 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/uk/
2024-01-08 14:06:17 +00:00
aacd01ce7c chore(i18n): Translated using Weblate (Ukrainian)
Currently translated at 100.0% (331 of 331 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/uk/
2024-01-08 14:06:16 +00:00
d501b016fc chore(i18n): Translated using Weblate (Turkish)
Currently translated at 100.0% (331 of 331 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/tr/
2024-01-08 14:06:14 +00:00
b1503cfb87 chore(deps): update dependency electron-vite to v2.0.0-beta.4 (#1602)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-07 04:19:01 +09:00
8a004ae9dd chore(i18n): Translated using Weblate (Spanish)
Currently translated at 100.0% (331 of 331 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2024-01-06 18:06:15 +01:00
de709cc7c9 fix: download/pip button for podcast video 2024-01-06 11:05:15 +09:00
6b7c43925a feat: rename IPC 2024-01-06 10:17:40 +09:00
5d5cc58f59 chore(tuna-obs): set keepAlive to true for reuse TCP socket 2024-01-06 09:37:47 +09:00
3e6bab7f15 fix(in-app-menu): fix invalid margin-top
fix #1597
2024-01-06 09:15:57 +09:00
7e17a8b73b fix(README): fix plugins path
fix #1598
2024-01-06 09:03:42 +09:00
129b798d8f chore(README): add demo image 2024-01-06 06:00:07 +09:00
a3a411e197 fix(config): fix duplicated array value 2024-01-06 01:07:57 +09:00
6e6acd6f19 Update changelog for v3.2.2 2024-01-05 14:44:33 +00:00
34cb79eeaf Bump version to 3.2.2 2024-01-05 23:33:42 +09:00
1c30a07031 chore(deps): update dependency playwright to v1.41.0-alpha-jan-5-2024 2024-01-05 23:30:16 +09:00
5dd5f41ef5 chore(deps): update dependency electron to v29.0.0-alpha.7
Change to electron v29 to use node.js v20.
2024-01-05 23:28:24 +09:00
45a3c11d51 chore(i18n): Translated using Weblate (Korean)
Currently translated at 100.0% (331 of 331 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2024-01-05 14:24:44 +00:00
8bd3b4d3f0 fix(visualizer): fixed an issue with audio getting unusually loud 2024-01-05 23:23:39 +09:00
4e3cb5806d fix(music-together): modernize code 2024-01-05 23:11:26 +09:00
6563eb4ddd chore(i18n): Translated using Weblate (Spanish)
Currently translated at 100.0% (330 of 330 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2024-01-05 14:02:12 +00:00
895386f6f8 fix(music-together): typing 2024-01-05 23:01:55 +09:00
3810955e56 fix(skip-silences): fix audio distorted
fix #1141
2024-01-05 21:58:36 +09:00
59c521e53f fix: download button not working 2024-01-05 21:04:52 +09:00
25d266f8f9 feat(tray): Add song info and paused icon (#1592) 2024-01-05 20:56:47 +09:00
0c3c380591 chore(deps): update dependency rollup to v4.9.3 2024-01-05 20:56:17 +09:00
a20cfa30a1 chore(deps): update dependency vite to v5.0.11 2024-01-05 20:43:13 +09:00
fefe899393 chore(i18n): Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (328 of 328 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hans/
2024-01-05 11:12:50 +01:00
55759e8d7a chore(i18n): Translated using Weblate (Italian)
Currently translated at 100.0% (328 of 328 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2024-01-05 11:12:50 +01:00
ddb561937f fix(deps): update dependency i18next to v23.7.16 2024-01-04 22:41:16 +09:00
198cb71a4c chore(deps): update dependency electron to v28.1.1 2024-01-04 22:38:45 +09:00
c34b880752 chore(deps): update dependency electron-vite to v2.0.0-beta.3 2024-01-04 22:38:30 +09:00
76944e3e41 fix(deps): update dependency i18next to v23.7.14 2024-01-03 17:35:30 +09:00
68cd76f2af chore(deps): update pnpm to v8.14.0 2024-01-03 17:35:23 +09:00
81145b52b7 fix(#1580): fix NEW badge doesn't show 2024-01-03 15:02:29 +09:00
2a19dab061 chore(i18n): Translated using Weblate (Vietnamese)
Currently translated at 3.3% (11 of 328 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/vi/
2024-01-02 10:52:22 +01:00
6958d59d4f chore(i18n): Translated using Weblate (Lithuanian)
Currently translated at 89.6% (294 of 328 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/lt/
2024-01-02 09:49:04 +01:00
8a51dfad87 chore(i18n): Translated using Weblate (Chinese (Simplified))
Currently translated at 89.6% (294 of 328 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hans/
2024-01-02 09:49:04 +01:00
5bb4d9efbe chore(i18n): Translated using Weblate (Turkish)
Currently translated at 100.0% (328 of 328 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/tr/
2024-01-02 09:49:04 +01:00
927aa5f24b chore(i18n): Translated using Weblate (Portuguese)
Currently translated at 89.6% (294 of 328 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pt/
2024-01-02 09:49:03 +01:00
d695bc93a1 chore(i18n): Translated using Weblate (Polish)
Currently translated at 89.3% (293 of 328 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pl/
2024-01-02 09:49:03 +01:00
b05fb4ccbe chore(i18n): Translated using Weblate (Norwegian Bokmål)
Currently translated at 67.9% (223 of 328 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/nb_NO/
2024-01-02 09:49:03 +01:00
299eb7e7d6 chore(i18n): Translated using Weblate (Italian)
Currently translated at 89.3% (293 of 328 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2024-01-02 09:49:03 +01:00
ae26333224 chore(i18n): Translated using Weblate (French)
Currently translated at 85.9% (282 of 328 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/fr/
2024-01-02 09:49:03 +01:00
35176469b0 chore(i18n): Translated using Weblate (Spanish)
Currently translated at 100.0% (328 of 328 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2024-01-02 09:49:03 +01:00
4e74f9cbc5 chore(i18n): Translated using Weblate (Czech)
Currently translated at 94.8% (311 of 328 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2024-01-02 09:49:02 +01:00
4091b36f36 chore(i18n): Translated using Weblate (English)
Currently translated at 100.0% (328 of 328 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/en/
2024-01-02 09:49:02 +01:00
b3f805fce6 chore(i18n): Translated using Weblate (Vietnamese)
Currently translated at 2.4% (8 of 328 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/vi/
2024-01-02 09:43:14 +01:00
b129a3e8d8 chore(i18n): Translated using Weblate (Chinese (Simplified))
Currently translated at 89.9% (295 of 328 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hans/
2024-01-02 09:43:13 +01:00
64ea1fdb58 chore(i18n): Translated using Weblate (Turkish)
Currently translated at 100.0% (328 of 328 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/tr/
2024-01-02 09:43:13 +01:00
8fcf59ed0a chore(i18n): Translated using Weblate (Spanish)
Currently translated at 100.0% (328 of 328 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2024-01-02 09:43:12 +01:00
9811ca63de chore(i18n): Translated using Weblate (Czech)
Currently translated at 94.8% (311 of 328 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2024-01-02 09:43:12 +01:00
9028f88299 chore(i18n): Translated using Weblate (English)
Currently translated at 100.0% (328 of 328 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/en/
2024-01-02 09:43:12 +01:00
fd47766d93 chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.17.0 2024-01-02 17:34:04 +09:00
26b12c7208 chore(i18n): Added translation using Weblate (Vietnamese) 2024-01-02 07:19:07 +01:00
8da9b3454d chore(deps): update dependency esbuild to v0.19.11 2024-01-01 20:32:13 +09:00
205cbefc83 Update changelog for v3.2.1 2024-01-01 00:32:24 +00:00
0e94c72eef Bump version to 3.2.1 2024-01-01 09:22:46 +09:00
c055641351 chore(i18n): Translated using Weblate (Korean)
Currently translated at 100.0% (328 of 328 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2024-01-01 01:19:52 +01:00
c0a3aa99de chore(i18n): Translated using Weblate (English)
Currently translated at 100.0% (328 of 328 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/en/
2024-01-01 01:19:52 +01:00
8a8976acef chore(i18n): Translated using Weblate (Czech)
Currently translated at 94.8% (311 of 328 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2024-01-01 01:06:02 +01:00
e409165e1b chore(i18n): Translated using Weblate (English)
Currently translated at 100.0% (328 of 328 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/en/
2024-01-01 01:06:02 +01:00
b278140796 chore(i18n): Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/
2024-01-01 01:01:00 +01:00
397056a54d chore(i18n): Translated using Weblate (Turkish)
Currently translated at 26.5% (87 of 328 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/tr/
2024-01-01 01:01:00 +01:00
edecd65419 chore(i18n): Translated using Weblate (Korean)
Currently translated at 100.0% (328 of 328 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2024-01-01 01:01:00 +01:00
4d2d0b7bd6 chore(i18n): Translated using Weblate (Czech)
Currently translated at 85.6% (281 of 328 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2024-01-01 01:01:00 +01:00
0ca4e34efd chore(i18n): Translated using Weblate (Czech)
Currently translated at 85.6% (281 of 328 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2024-01-01 01:00:59 +01:00
43f3226c3a fix: fix #1574 2024-01-01 08:36:48 +09:00
0a6dbecc05 fix: fix #1575 2024-01-01 08:36:22 +09:00
f5aa179cd6 chore(i18n): Translated using Weblate
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: inson1 <vaclav.svarc01@seznam.cz>
Co-authored-by: Anonymous <noreply@weblate.org>
2024-01-01 04:53:59 +09:00
3140e91dda Update changelog for v3.2.0 2023-12-31 16:31:59 +00:00
022f8ff65c Merge branch 'master' of https://github.com/th-ch/youtube-music 2024-01-01 01:23:01 +09:00
5e63cc2e89 Bump version to 3.2.0 2024-01-01 01:22:48 +09:00
880ed99846 Revert "fix(deps): update dependency @xhayper/discord-rpc to v1.1.2"
This reverts commit 050d55c736.
2024-01-01 00:34:49 +09:00
222e78c85b fix(in-app-menu): fix in-app-menu tooltip position 2023-12-31 23:48:01 +09:00
050d55c736 fix(deps): update dependency @xhayper/discord-rpc to v1.1.2 2023-12-31 23:40:26 +09:00
13ef8560ff fix: pnpm build error 2023-12-31 23:40:09 +09:00
78d990c079 feat(album-color-theme): improve Album Color Theme style (#1571) 2023-12-31 23:04:44 +09:00
4d3e2c09da feat(menu): add more detail in Menu (#1570) 2023-12-31 20:56:24 +09:00
aa899d247a chore(plugins): change default config album-actions, music-together 2023-12-31 13:56:42 +09:00
ee0c512529 feat(music-together): Add new plugin Music Together (#1562)
* feat(music-together): test `peerjs`

* feat(music-together): replace `prompt` to `custom-electron-prompt`

* fix(music-together): fix

* test fix

* wow

* test

* feat(music-together): improve `onStart`

* fix: adblocker

* fix(adblock): fix crash with `peerjs`

* feat(music-together): add host UI

* feat(music-together): implement addSong, removeSong, syncQueue

* feat(music-together): inject panel

* feat(music-together): redesign music together panel

* feat(music-together): sync queue, profile

* feat(music-together): sync progress, song, state

* fix(music-together): fix some bug

* fix(music-together): fix sync queue

* feat(music-together): support i18n

* feat(music-together): improve sync queue

* feat(music-together): add profile in music item

* refactor(music-together): refactor structure

* feat(music-together): add permission

* fix(music-together): fix queue sync bug

* fix(music-together): fix some bugs

* fix(music-together): fix permission not working on guest mode

* fix(music-together): fix queue sync relate bugs

* fix(music-together): fix automix items not append using music together

* fix(music-together): fix

* feat(music-together): improve video injection

* fix(music-together): fix injection code

* fix(music-together): fix broadcast guest

* feat(music-together): add more permission

* fix(music-together): fix injector

* fix(music-together): fix guest add song logic

* feat(music-together): add popup close listener

* fix(music-together): fix connection issue

* fix(music-together): fix connection issue 2

* feat(music-together): reserve playlist

* fix(music-together): exclude automix songs

* fix(music-together): fix playlist index sync bug

* fix(music-together): fix connection failed error and sync index

* fix(music-together): fix host set index bug

* fix: apply fix from eslint

* feat(util): add `ImageElementFromSrc`

* chore(util): update jsdoc

* feat(music-together): add owner name

* chore(music-together): add translation

* feat(music-together): add progress sync

* chore(music-together): remove `console.log`

---------

Co-authored-by: JellyBrick <shlee1503@naver.com>
2023-12-31 13:52:15 +09:00
5f9b522307 chore(deps): update dependency rollup to v4.9.2 (#1567)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-31 04:05:49 +09:00
c207e29980 fix(deps): update dependency i18next to v23.7.13 (#1569)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-31 04:05:44 +09:00
df4d2d6b72 chore(ambient-mode): remove console.log 2023-12-31 02:28:31 +09:00
c3dd20cabd chore(i18n): Translated using Weblate (Korean)
Currently translated at 100.0% (296 of 296 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2023-12-30 17:09:06 +00:00
7a6db95d1a chore(i18n): Translated using Weblate (Czech)
Currently translated at 80.4% (238 of 296 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-30 17:09:05 +00:00
bc6825d63b chore(i18n): Translated using Weblate (Czech)
Currently translated at 76.8% (226 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-29 16:14:02 +01:00
5e79e9e0f2 feat: Add new plugin Album actions (#1515)
Co-authored-by: JellyBrick <shlee1503@naver.com>
2023-12-30 00:13:56 +09:00
5e303c2ba8 fix(deps): update dependency i18next to v23.7.12 (#1564)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-29 22:26:41 +09:00
0bd9c16356 fix: Only apply scale factor on Windows (#1565) 2023-12-29 22:26:24 +09:00
f0f5d9da2f feat(ambient-mode): support ambient mode on Song section
resolve #1555
2023-12-29 21:46:27 +09:00
TC
f46c431f4c Move cask definition to separate repo 2023-12-29 11:26:55 +01:00
62410e9ee2 feat(in-app-menu): add show on hover 2023-12-29 17:52:31 +09:00
46f76f1408 chore(i18n): Translated using Weblate (Indonesian)
Currently translated at 3.7% (11 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/id/
2023-12-27 18:20:59 +01:00
5e071e16d8 chore(i18n): Translated using Weblate (Turkish)
Currently translated at 29.2% (86 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/tr/
2023-12-27 18:20:58 +01:00
c0238588bd chore(i18n): Translated using Weblate (Russian)
Currently translated at 78.5% (231 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ru/
2023-12-27 18:20:58 +01:00
Nik
30002d660a chore(i18n): Translated using Weblate (Russian)
Currently translated at 78.5% (231 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ru/
2023-12-27 18:20:58 +01:00
48eeb6bca3 fix: picture-in-picture icon 2023-12-28 02:17:42 +09:00
e67699fed5 fix: fixed an issue with the download button disappearing
- resolve #1551
2023-12-28 02:08:03 +09:00
8aeae45965 chore(i18n): Translated using Weblate (Ukrainian)
Currently translated at 77.2% (227 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/uk/
2023-12-27 13:09:36 +01:00
ce7491941b chore(i18n): Translated using Weblate (Russian)
Currently translated at 74.8% (220 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ru/
2023-12-27 13:09:36 +01:00
1dce03c4f2 chore(i18n): Translated using Weblate (Polish)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pl/
2023-12-27 13:09:36 +01:00
62eae6d5d0 chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.16.0 (#1556)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-27 18:37:06 +09:00
15b2b26b84 chore(deps): update pnpm to v8.13.1 (#1557)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-27 18:36:57 +09:00
9664c17c47 chore(deps): update dependency ws to v8.16.0 (#1559)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-27 18:36:51 +09:00
8067dad2fa fix(deps): update dependency youtubei.js to v8.1.0 (#1560)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-27 18:36:42 +09:00
4dcaa510d9 chore(i18n): Translated using Weblate (Indonesian)
Currently translated at 3.7% (11 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/id/
2023-12-25 12:42:54 +01:00
b6e918089d chore(i18n): Translated using Weblate (Turkish)
Currently translated at 29.2% (86 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/tr/
2023-12-25 12:42:54 +01:00
1c9e6b1bb8 chore(i18n): Translated using Weblate (Polish)
Currently translated at 97.9% (288 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pl/
2023-12-25 12:42:54 +01:00
ebd304c252 fix(deps): update dependency node-html-parser to v6.1.12 (#1554)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-25 20:37:05 +09:00
36083c4173 Revert "fix(deps): update dependency @xhayper/discord-rpc to v1.1.2" (#1552) 2023-12-25 03:48:15 +09:00
a084b060d8 chore(deps): update dependency electron to v28.1.0 2023-12-25 03:32:28 +09:00
432c79b606 fix(deps): update dependency @xhayper/discord-rpc to v1.1.2 2023-12-25 03:32:07 +09:00
0f1f0ee933 chore(deps): update dependency eslint-plugin-prettier to v5.1.2 2023-12-25 03:31:59 +09:00
9b1a4b8d88 chore(i18n): Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2023-12-23 21:07:03 +01:00
1a7a665915 chore(i18n): Translated using Weblate (Czech)
Currently translated at 76.5% (225 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-22 03:06:46 +01:00
623ecf7fb8 chore(i18n): Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/
2023-12-20 17:44:47 +01:00
0dc9c6a1a9 chore(i18n): Translated using Weblate (Czech)
Currently translated at 72.7% (214 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-20 17:44:47 +01:00
72c5eaa5ff chore(i18n): Translated using Weblate (Czech)
Currently translated at 82.3% (242 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-20 13:54:56 +01:00
0f47b94b7d chore(i18n): Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/
2023-12-20 13:53:36 +01:00
9abe15f1ad chore(i18n): Translated using Weblate (Czech)
Currently translated at 82.3% (242 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-20 13:53:35 +01:00
96afda92c8 chore(i18n): Translated using Weblate (Czech)
Currently translated at 80.9% (238 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-20 13:33:50 +01:00
5c6fd4a739 Update README-ko.md 2023-12-20 14:11:53 +02:00
23b87a876d chore(deps): update dependency eslint-plugin-prettier to v5.1.0 2023-12-20 13:59:40 +09:00
737fd05369 chore(deps): update dependency electron-vite to v2.0.0-beta.2 2023-12-20 13:59:28 +09:00
c5bcd89f16 chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.15.0 2023-12-19 16:32:46 +09:00
377e1be0b2 fix(deps): update dependency @foobar404/wave to v2.0.5 2023-12-19 16:32:31 +09:00
a92049c0c9 chore(i18n): Translated using Weblate (Lithuanian)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/lt/
2023-12-19 07:21:28 +01:00
27a2955bba fix: fix homebrew cask
- resolve #1514
2023-12-18 22:02:38 +09:00
cc940e2020 Update changelog for v3.1.1 2023-12-18 12:57:28 +00:00
203c80767d Bump version to 3.1.1 2023-12-18 21:49:31 +09:00
f564039438 chore: fix the example plugins in the README 2023-12-18 14:47:18 +02:00
d6566fb870 chore(i18n): Translated using Weblate (Lithuanian)
Currently translated at 84.3% (248 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/lt/
2023-12-18 13:46:47 +01:00
55ab17e789 chore(i18n): Translated using Weblate (Portuguese)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pt/
2023-12-18 13:43:08 +01:00
e0eeb720cd feat: upgrade to Vite v5 2023-12-18 21:10:47 +09:00
53ac7ff257 fix: electron-vite dev --watch 2023-12-18 20:00:00 +09:00
a4e2f10afa chore(deps): update dependency playwright to v1.41.0-alpha-dec-18-2023 2023-12-18 19:58:33 +09:00
c9c9d766e6 chore(deps): update dependency rollup to v4.9.1 2023-12-18 19:56:14 +09:00
b82d161180 chore: Update bug_report.yml 2023-12-18 19:47:51 +09:00
7cadacd8cf fix: vite dev mode
fix an issue caused by da3bc5aeb7
2023-12-18 19:07:43 +09:00
fc1a7cda62 chore(i18n): Translated using Weblate (Lithuanian)
Currently translated at 84.3% (248 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/lt/
2023-12-18 10:56:31 +01:00
da3bc5aeb7 fix: renderer plugin load timing
MAGIC OF JAVASCRIPT
2023-12-18 18:42:01 +09:00
ee98344064 chore(i18n): Added translation using Weblate (Lithuanian) 2023-12-17 21:43:08 +01:00
da10eabf99 chore(i18n): Translated using Weblate (Ukrainian)
Currently translated at 69.3% (204 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/uk/
2023-12-17 14:55:42 +01:00
ce3c63c386 chore(i18n): Translated using Weblate (Portuguese)
Currently translated at 73.8% (217 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pt/
2023-12-17 14:55:42 +01:00
f8c3cff5a9 chore(i18n): Translated using Weblate (Korean)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2023-12-17 14:55:41 +01:00
08f369f8bc chore(i18n): Translated using Weblate (Portuguese)
Currently translated at 43.5% (128 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pt/
2023-12-17 08:28:44 +01:00
1517230ede chore(i18n): Translated using Weblate (German)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2023-12-17 08:28:44 +01:00
45aeadef86 fix(song-info-front): fix type 2023-12-17 16:05:26 +09:00
5ec7c346ca fix(song-info-front): fix parser to parse album name 2023-12-17 15:51:35 +09:00
4b68de1606 fix: update types 2023-12-16 22:39:57 +09:00
7710bcdacc chore(i18n): Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2023-12-16 11:58:32 +01:00
4c19bcc983 chore(i18n): Translated using Weblate (Turkish)
Currently translated at 26.1% (77 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/tr/
2023-12-16 03:33:06 +01:00
2f50b1423a chore(i18n): Translated using Weblate (French)
Currently translated at 95.9% (282 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/fr/
2023-12-16 03:33:06 +01:00
15768c9691 chore(deps): update dependency eslint to v8.56.0 2023-12-16 11:28:58 +09:00
442eb75b8d chore(i18n): Translated using Weblate (Portuguese)
Currently translated at 43.1% (127 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pt/
2023-12-16 03:09:12 +01:00
8facd27125 chore(i18n): Translated using Weblate (Italian)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-16 03:09:11 +01:00
a302cb6ebf chore(i18n): Translated using Weblate (Spanish)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-16 03:09:11 +01:00
32422c7ba9 fix(deps): update dependency i18next to v23.7.11 2023-12-15 13:42:50 +09:00
ca131cb156 chore(deps): update dependency eslint-plugin-import to v2.29.1 2023-12-15 13:42:44 +09:00
5f8262ede1 chore(i18n): Translated using Weblate (Ukrainian)
Currently translated at 65.3% (192 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/uk/
2023-12-14 02:11:40 +01:00
88db9d3693 chore(i18n): Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2023-12-13 21:33:32 +01:00
0fa6f344f9 chore(i18n): Translated using Weblate (Polish)
Currently translated at 97.6% (287 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pl/
2023-12-13 21:33:31 +01:00
d1642b3dfb chore(i18n): Translated using Weblate (Polish)
Currently translated at 97.6% (287 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pl/
2023-12-13 21:33:31 +01:00
7599fce5f5 chore(i18n): Translated using Weblate (Spanish)
Currently translated at 97.6% (287 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-13 21:33:31 +01:00
9bd0dc9a7b chore(i18n): Translated using Weblate (Greek)
Currently translated at 23.8% (70 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/el/
2023-12-13 21:33:31 +01:00
dad7cafebc chore(i18n): Translated using Weblate (Russian)
Currently translated at 72.7% (214 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ru/
2023-12-13 16:20:54 +01:00
4febb28201 chore(deps): update dependency rollup to v4.9.0 2023-12-13 21:13:41 +09:00
ced943bad3 chore(deps): update pnpm to v8.12.1 2023-12-13 21:12:07 +09:00
202b8717a2 chore(i18n): Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2023-12-13 13:10:46 +01:00
eba7026b89 chore(i18n): Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hans/
2023-12-13 13:10:46 +01:00
6b2adca1f4 fix(downloader): fix download button i18n 2023-12-13 18:02:18 +09:00
0c9d3cc057 chore(i18n): Translated using Weblate (Ukrainian)
Currently translated at 42.8% (126 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/uk/
2023-12-13 05:07:02 +01:00
45ec11aaed chore(deps): update pnpm-lock.yaml 2023-12-13 13:06:10 +09:00
4d47fa3fac fix(deps): update dependency @electron/remote to v2.1.1 2023-12-13 12:24:55 +09:00
6322d5bb09 chore(i18n): Translated using Weblate (Indonesian)
Currently translated at 3.0% (9 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/id/
2023-12-12 21:56:36 +01:00
2e8a579049 chore(i18n): Translated using Weblate (Thai)
Currently translated at 1.0% (3 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/th/
2023-12-12 21:56:36 +01:00
9db7e99d7f chore(i18n): Translated using Weblate (Korean)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2023-12-12 21:56:36 +01:00
ef73989995 chore(i18n): Translated using Weblate (Czech)
Currently translated at 79.9% (235 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-12 21:56:36 +01:00
61024084e9 chore(deps): update dependency ws to v8.15.1 2023-12-13 05:46:56 +09:00
1c71b2020f fix(deps): update dependency i18next to v23.7.9 2023-12-13 05:45:52 +09:00
3b2b545388 chore(i18n): Translated using Weblate (Russian)
Currently translated at 67.6% (199 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ru/
2023-12-12 21:44:51 +01:00
fafc7f94fd chore(i18n): Translated using Weblate (Italian)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-12 21:44:50 +01:00
5fa88b5ce9 fix: fix renderer plugin load timing
- fix #1522
2023-12-12 12:53:41 +09:00
a8fe4167db fix(pip): fix invalid position config 2023-12-12 11:11:41 +09:00
0e6b0ddc26 chore(i18n): Translated using Weblate (Korean)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2023-12-12 02:24:41 +01:00
af88db3865 chore(i18n): Translated using Weblate (Japanese)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ja/
2023-12-12 02:24:41 +01:00
0a1bef556c chore(i18n): Translated using Weblate (English)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/en/
2023-12-12 02:24:41 +01:00
7030181dc1 chore(i18n): Translated using Weblate (English)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/en/
2023-12-12 02:24:41 +01:00
98cb2f61ab chore(deps): update dependency playwright to v1.41.0-alpha-dec-12-2023 2023-12-12 10:23:59 +09:00
a601d0b3d2 chore(deps): update dependency rollup to v4.8.0 2023-12-12 10:22:23 +09:00
8d18923065 chore(i18n): Translated using Weblate (Norwegian Bokmål)
Currently translated at 75.1% (221 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/nb_NO/
2023-12-12 01:08:34 +01:00
4b6a7e0bd7 chore(i18n): Translated using Weblate (English)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/en/
2023-12-12 01:08:34 +01:00
44f87853d6 chore(i18n): Translated using Weblate (Norwegian Bokmål)
Currently translated at 74.8% (220 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/nb_NO/
2023-12-12 01:02:37 +01:00
4ff47bee03 chore(i18n): Translated using Weblate (Norwegian Bokmål)
Currently translated at 74.1% (218 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/nb_NO/
2023-12-12 00:54:03 +01:00
7a2c28df21 chore(i18n): Translated using Weblate (English)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/en/
2023-12-12 00:54:02 +01:00
f7241d1e4a chore(i18n): Added translation using Weblate (Indonesian) 2023-12-12 00:33:55 +01:00
2b5daf3a75 chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.14.0 2023-12-12 06:43:20 +09:00
d7184cf75d chore(i18n): Translated using Weblate (Russian)
Currently translated at 54.0% (159 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ru/
2023-12-11 19:59:59 +01:00
1313d18d44 chore(i18n): Translated using Weblate (Czech)
Currently translated at 80.9% (238 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-11 19:59:58 +01:00
605e9481d9 chore(i18n): Translated using Weblate (English)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/en/
2023-12-11 19:59:58 +01:00
cf4c89f825 chore(i18n): Added translation using Weblate (Thai) 2023-12-11 10:17:18 +01:00
f4f1d4d373 chore(i18n): Translated using Weblate (Norwegian Bokmål)
Currently translated at 46.2% (136 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/nb_NO/
2023-12-11 09:00:43 +01:00
6557b40924 Translated using Weblate (Spanish)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-11 00:20:44 +01:00
e01a29c5d1 Translated using Weblate (Spanish)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-11 00:20:44 +01:00
d9a7e16d63 Translated using Weblate (Italian)
Currently translated at 99.3% (292 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-11 00:20:44 +01:00
d170199d85 Update changelog for v3.1.0 2023-12-10 23:10:52 +00:00
e650aae491 Bump version to 3.1.0 2023-12-11 08:00:42 +09:00
49058dbeab chore: update pnpm-lock.yaml 2023-12-11 07:59:42 +09:00
31d2d91566 Translated using Weblate (Japanese)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ja/
2023-12-10 23:57:22 +01:00
abf2a52b46 fix: in-player adblocker inject timing issue
- fix #1478
2023-12-11 07:46:01 +09:00
d0ca10e1a1 Translated using Weblate (Chinese (Simplified))
Currently translated at 1.0% (3 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hans/
2023-12-10 22:57:39 +01:00
fe99a04174 Translated using Weblate (Portuguese)
Currently translated at 31.6% (93 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pt/
2023-12-10 22:57:39 +01:00
916900ae6a Translated using Weblate (Spanish)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-10 22:57:39 +01:00
826c9ba48e fix(package.json): fix RPM version libuuid issue
- fix #1508
2023-12-11 06:57:32 +09:00
7783cc5f30 Translated using Weblate (Chinese (Simplified))
Currently translated at 0.3% (1 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hans/
2023-12-10 22:49:58 +01:00
035d7f19ea Translated using Weblate (Spanish)
Currently translated at 99.3% (292 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-10 22:49:58 +01:00
294cf15d58 Translated using Weblate (Spanish)
Currently translated at 99.3% (292 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-10 22:49:58 +01:00
11fa0dcbc6 Translated using Weblate (Spanish)
Currently translated at 99.3% (292 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-10 22:49:58 +01:00
85b53db439 Translated using Weblate (Korean)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2023-12-10 22:49:57 +01:00
200df100f4 Translated using Weblate (English)
Currently translated at 100.0% (294 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/en/
2023-12-10 22:49:57 +01:00
ba202a8572 chore(deps): update dependency electron to v28 (#1498)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: JellyBrick <shlee1503@naver.com>
2023-12-11 06:44:59 +09:00
daf05239a1 Enable/Disable Navigation without restart (#1507) 2023-12-11 04:21:17 +09:00
b73b5735ec Translated using Weblate (Spanish)
Currently translated at 90.8% (267 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-10 04:08:22 +01:00
292248dd35 Translated using Weblate (Spanish)
Currently translated at 90.8% (267 of 294 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-10 04:08:22 +01:00
2e240541c8 Turkish(tr)_lang_file (#1513) 2023-12-10 08:41:46 +09:00
ab62ae3682 chore(deps): update dependency ws to v8.15.0 2023-12-10 04:28:02 +09:00
5c3f5d05d3 chore(deps): update pnpm to v8.12.0 2023-12-09 12:38:32 +09:00
c81af537b6 Translated using Weblate (Spanish)
Currently translated at 83.5% (244 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-09 01:37:30 +00:00
7295c73371 Translated using Weblate (Spanish)
Currently translated at 83.5% (244 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-09 01:37:29 +00:00
5a65e08a94 Translated using Weblate (Spanish)
Currently translated at 83.5% (244 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-09 01:37:28 +00:00
45e5bc7df5 Skip Disliked Songs (#1505)
* Added skip-disliked-songs

* Changed it to activate and deactivate without restart

* Added waiter for Element

* MutationObserver can be null

Co-authored-by: JellyBrick <shlee1503@naver.com>

* MutationObserver could not exist

Co-authored-by: JellyBrick <shlee1503@naver.com>

* Update src/plugins/skip-disliked-songs/index.ts

Co-authored-by: JellyBrick <shlee1503@naver.com>

* Replaced double quotes with single quotes

---------

Co-authored-by: JellyBrick <shlee1503@naver.com>
2023-12-09 10:37:17 +09:00
a81cb9515c Translated using Weblate (Spanish)
Currently translated at 39.7% (116 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-08 21:55:06 +01:00
88e5cc2728 Translated using Weblate (Spanish)
Currently translated at 39.7% (116 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-08 21:55:06 +01:00
1685648328 Translated using Weblate (Spanish)
Currently translated at 39.0% (114 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-08 21:54:40 +01:00
9334adacf6 Translated using Weblate (Spanish)
Currently translated at 39.0% (114 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-08 21:54:39 +01:00
efbe557dce Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/
2023-12-08 20:37:43 +01:00
52b2625486 Translated using Weblate (Spanish)
Currently translated at 10.6% (31 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-08 20:37:43 +01:00
aa0424db08 Translated using Weblate (Spanish)
Currently translated at 10.6% (31 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-08 20:37:43 +01:00
157619aa4f chore(deps): update dependency rollup to v4.7.0 2023-12-09 03:23:17 +09:00
ce637968b2 Translated using Weblate (Italian)
Currently translated at 100.0% (292 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-08 11:32:34 +01:00
b2a3ed7428 Translated using Weblate (Spanish)
Currently translated at 4.1% (12 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-08 11:08:18 +01:00
ca856e4d88 Translated using Weblate (Spanish)
Currently translated at 4.1% (12 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-08 11:08:17 +01:00
2ef2536766 Translated using Weblate (Italian)
Currently translated at 100.0% (292 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-08 11:08:17 +01:00
1b22633388 Translated using Weblate (Italian)
Currently translated at 100.0% (292 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-08 11:08:17 +01:00
db306ad4e0 Translated using Weblate (Italian)
Currently translated at 65.4% (191 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-07 19:20:03 +01:00
34a44cb9c6 Translated using Weblate (Italian)
Currently translated at 65.4% (191 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-07 19:20:03 +01:00
8c499a6e20 Translated using Weblate (Italian)
Currently translated at 51.3% (150 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-07 18:58:33 +01:00
3b816a6fd9 Translated using Weblate (Italian)
Currently translated at 51.3% (150 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-07 18:58:33 +01:00
35aba8e25a Translated using Weblate (Italian)
Currently translated at 46.9% (137 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-07 18:52:44 +01:00
0eb6723f27 Translated using Weblate (Italian)
Currently translated at 46.9% (137 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-07 18:52:44 +01:00
2250ec9372 Translated using Weblate (Spanish)
Currently translated at 1.0% (3 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2023-12-07 13:48:08 +00:00
ef02b53284 Translated using Weblate (Italian)
Currently translated at 42.1% (123 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-07 13:48:08 +00:00
57df7cf3f9 Translated using Weblate (Italian)
Currently translated at 42.1% (123 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-07 13:48:07 +00:00
f0cd540726 Translated using Weblate (Ukrainian)
Currently translated at 1.0% (3 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/uk/
2023-12-07 13:48:06 +00:00
7e919395eb chore(deps): update dependency typescript to v5.3.3 2023-12-07 10:03:26 +09:00
facc7252c2 fix(deps): update dependency i18next to v23.7.8 2023-12-07 10:03:06 +09:00
09b8943f69 Translated using Weblate (Italian)
Currently translated at 32.1% (94 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-07 01:45:31 +01:00
aa84ffc7e1 Translated using Weblate (French)
Currently translated at 100.0% (292 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/fr/
2023-12-07 01:45:31 +01:00
10162c948b chore: Create zh-CN.json 2023-12-07 09:45:08 +09:00
e8c1cbdd94 BC-BREAK: bump node engines version 2023-12-07 02:52:14 +09:00
5459f623ec Translated using Weblate (Italian)
Currently translated at 32.1% (94 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-05 18:59:01 +01:00
2a2cba3539 Translated using Weblate (Italian)
Currently translated at 32.1% (94 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-05 18:59:01 +01:00
1ce84607e6 Translated using Weblate (Portuguese)
Currently translated at 30.8% (90 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pt/
2023-12-05 17:59:26 +01:00
075c54d39f Translated using Weblate (Portuguese)
Currently translated at 30.8% (90 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pt/
2023-12-05 17:59:26 +01:00
aabb826cb8 Translated using Weblate (Italian)
Currently translated at 13.0% (38 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-05 17:59:26 +01:00
3791b1ae1c Translated using Weblate (Italian)
Currently translated at 13.0% (38 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-05 17:59:26 +01:00
d4b87d098b Added translation using Weblate (Portuguese) 2023-12-05 17:39:07 +01:00
57e9c13d13 Translated using Weblate (Italian)
Currently translated at 6.8% (20 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-05 05:59:05 +01:00
e48e570e69 Translated using Weblate (Italian)
Currently translated at 6.8% (20 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-05 05:59:05 +01:00
b7d4d5b022 Translated using Weblate (Czech)
Currently translated at 61.3% (179 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-05 05:59:05 +01:00
dc8b0173e9 Added translation using Weblate (Spanish) 2023-12-05 05:59:05 +01:00
840729126e fix(macos): fix app crash in macOS 2023-12-05 13:36:23 +09:00
ed90b97a92 chore(deps): update dependency vite to v4.5.1 2023-12-05 13:29:45 +09:00
bbe4f07651 chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.13.2 (#1452)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-05 13:29:23 +09:00
66df83a86d fix: Homebrew latest release url parsing (#1496)
Co-authored-by: Gian Marco Cinalli <gianmarco.cinalli@immobiliare.it>
2023-12-05 10:56:59 +09:00
dfedd8f091 Translated using Weblate (Italian)
Currently translated at 4.1% (12 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2023-12-04 19:15:58 +01:00
58ec6eebfb Translated using Weblate (Czech)
Currently translated at 58.5% (171 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-04 19:15:58 +01:00
3ba4130bc6 Translated using Weblate (Czech)
Currently translated at 58.5% (171 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-04 19:15:58 +01:00
7dbbf38e78 Added translation using Weblate (Italian) 2023-12-04 17:31:24 +01:00
51e04162a7 Translated using Weblate (Czech)
Currently translated at 57.1% (167 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-04 09:58:03 +01:00
d3325e2490 Translated using Weblate (Czech)
Currently translated at 56.1% (164 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-04 05:05:31 +01:00
d2708ee32f Translated using Weblate (Czech)
Currently translated at 56.1% (164 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-04 05:05:31 +01:00
781a09c717 Translated using Weblate (Czech)
Currently translated at 55.4% (162 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-04 04:55:52 +01:00
11605293dd Translated using Weblate (Czech)
Currently translated at 55.4% (162 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-04 04:55:52 +01:00
3f0b946190 Translated using Weblate (Czech)
Currently translated at 52.7% (154 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-03 23:49:11 +01:00
74972f053a Translated using Weblate (Czech)
Currently translated at 52.7% (154 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-03 23:49:11 +01:00
d85190ace1 Translated using Weblate (Czech)
Currently translated at 51.7% (151 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-03 23:39:39 +01:00
5755b4ac7f Translated using Weblate (Czech)
Currently translated at 51.7% (151 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-03 23:39:38 +01:00
3c24f2edcd Translated using Weblate (Czech)
Currently translated at 51.3% (150 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-03 23:26:36 +01:00
6828520853 Translated using Weblate (Czech)
Currently translated at 51.3% (150 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-03 23:26:36 +01:00
2ba7dddb95 Translated using Weblate (Czech)
Currently translated at 50.6% (148 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-03 22:17:53 +00:00
57a8922d04 Translated using Weblate (Czech)
Currently translated at 51.0% (149 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-03 22:17:53 +00:00
640098860a Translated using Weblate (Czech)
Currently translated at 51.0% (149 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-03 22:17:52 +00:00
53384d9f3b Translated using Weblate (Russian)
Currently translated at 13.6% (40 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ru/
2023-12-03 22:17:51 +00:00
89d8d98a35 fix: remove import 'process' 2023-12-04 03:53:24 +09:00
7b78ba6761 Translated using Weblate (Polish)
Currently translated at 100.0% (292 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pl/
2023-12-03 17:43:49 +01:00
3926a9a0c0 Translated using Weblate (French)
Currently translated at 99.6% (291 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/fr/
2023-12-03 17:43:48 +01:00
0708cd5a38 Added translation using Weblate (Polish) 2023-12-03 11:35:51 +01:00
e20e9ca771 Added translation using Weblate (Ukrainian) 2023-12-03 10:26:51 +01:00
be1038bafd Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (292 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2023-12-03 01:50:48 +01:00
ebc087963b Translated using Weblate (French)
Currently translated at 85.2% (249 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/fr/
2023-12-03 01:50:48 +01:00
020bdc0811 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (292 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2023-12-02 23:23:55 +00:00
a0d1ad6a47 Translated using Weblate (French)
Currently translated at 19.1% (56 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/fr/
2023-12-02 23:23:54 +00:00
62e5679791 fix(deps): update dependency ts-morph to v21 2023-12-03 07:50:56 +09:00
35568bd299 Translated using Weblate (Chinese (Traditional))
Currently translated at 46.5% (136 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2023-12-02 21:13:54 +01:00
1e40b377af Translated using Weblate (Chinese (Traditional))
Currently translated at 46.5% (136 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2023-12-02 21:13:54 +01:00
1073de1b45 Translated using Weblate (Czech)
Currently translated at 46.5% (136 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-02 21:13:45 +01:00
1d5788acaf Translated using Weblate (Chinese (Traditional))
Currently translated at 35.6% (104 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2023-12-02 20:27:58 +01:00
8fb446588d Translated using Weblate (Chinese (Traditional))
Currently translated at 35.6% (104 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2023-12-02 20:27:58 +01:00
6cec34b2ac Translated using Weblate (Chinese (Traditional))
Currently translated at 35.6% (104 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2023-12-02 20:27:58 +01:00
763d3e8f74 Translated using Weblate (German)
Currently translated at 100.0% (292 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2023-12-02 20:27:58 +01:00
7da0a913f1 Update changelog for v3.0.2 2023-12-02 18:39:16 +00:00
ca04c4561b Bump version to 3.0.2 2023-12-03 03:30:08 +09:00
7d8fbf49a8 Translated using Weblate (Chinese (Traditional))
Currently translated at 23.6% (69 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2023-12-02 19:29:34 +01:00
75e15b948d Translated using Weblate (Chinese (Traditional))
Currently translated at 23.6% (69 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2023-12-02 19:29:28 +01:00
125b69fd75 Translated using Weblate (Chinese (Traditional))
Currently translated at 23.6% (69 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2023-12-02 19:29:28 +01:00
a68d6b64dd Translated using Weblate (French)
Currently translated at 8.9% (26 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/fr/
2023-12-02 19:29:28 +01:00
a60d4264dc fix: pnpm install doesn't refresh lock file 2023-12-03 03:16:07 +09:00
9e2c6b1afa fix: update pnpm-lock.yaml 2023-12-03 03:07:11 +09:00
14965a93e9 fix: simple-youtube-age-restriction-bypass 2023-12-03 02:49:17 +09:00
f62664b6a5 fix(adblocker): fix In-Player adblocker
- resolves #1478
2023-12-03 02:06:49 +09:00
60cb7f32f1 fix(in-app-menu/i18n): fix hide-dom-window-controls 2023-12-03 01:00:33 +09:00
008b3ad710 Translated using Weblate (Chinese (Traditional))
Currently translated at 1.0% (3 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2023-12-02 16:59:02 +01:00
8cae64f496 fix(menu): crash on linux
- resolves #1477
2023-12-03 00:54:36 +09:00
5cdc1bc762 Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/
2023-12-02 16:29:40 +01:00
15c455105b Translated using Weblate (French)
Currently translated at 8.5% (25 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/fr/
2023-12-02 16:29:40 +01:00
14407a98c9 Translated using Weblate (English)
Currently translated at 100.0% (292 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/en/
2023-12-02 16:29:40 +01:00
0d004d5caf Added translation using Weblate (Chinese (Traditional)) 2023-12-02 16:29:40 +01:00
4b75a2405c Update changelog for v3.0.1 2023-12-02 14:32:40 +00:00
1f7e28b6fb Bump version to 3.0.1 (hotfix) 2023-12-02 23:23:26 +09:00
c41b2ce861 fix: if locale is not present, set to unspecified 2023-12-02 23:23:13 +09:00
7f02afc5a6 Translated using Weblate (French)
Currently translated at 3.4% (10 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/fr/
2023-12-02 14:14:53 +00:00
496b3ffc1b fix(adblocker): remove unused statement 2023-12-02 23:14:04 +09:00
e9a395f67a hotfix(adblocker): fix #1475 2023-12-02 23:13:21 +09:00
0660f0b7ce Translated using Weblate (German)
Currently translated at 100.0% (292 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2023-12-02 14:41:50 +01:00
3ac09b9dc1 Added translation using Weblate (French) 2023-12-02 14:41:50 +01:00
fe4904a4af fix(index): i18n translate 2023-12-02 22:35:49 +09:00
d8c8bd17ec Update changelog for v3.0.0 2023-12-02 13:21:02 +00:00
e9d4d5ba14 Bump version to 3.0.0 2023-12-02 22:12:51 +09:00
5b2e69588f Translated using Weblate (Korean)
Currently translated at 100.0% (292 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2023-12-02 14:11:43 +01:00
c1591402a0 Translated using Weblate (Korean)
Currently translated at 100.0% (292 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2023-12-02 13:11:07 +00:00
e2e9c03895 chore(deps): update dependency vite-plugin-inspect to v0.8.1 2023-12-02 22:06:08 +09:00
deac4ef56b Translated using Weblate (German)
Currently translated at 99.6% (291 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2023-12-02 14:03:09 +01:00
7c39e658ce Translated using Weblate (German)
Currently translated at 99.6% (291 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2023-12-02 13:49:31 +01:00
6b026f57bc Translated using Weblate (Japanese)
Currently translated at 100.0% (292 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ja/
2023-12-02 13:27:29 +01:00
dc07cbda6f Translated using Weblate (German)
Currently translated at 99.6% (291 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2023-12-02 13:27:29 +01:00
1cf43fcd42 Translated using Weblate (German)
Currently translated at 99.6% (291 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2023-12-02 13:27:29 +01:00
e2cf550bed Translated using Weblate (Japanese)
Currently translated at 100.0% (292 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ja/
2023-12-02 13:18:24 +01:00
2917da1138 Translated using Weblate (Korean)
Currently translated at 100.0% (292 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2023-12-02 13:18:24 +01:00
b74eeb5688 Translated using Weblate (Japanese)
Currently translated at 100.0% (292 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ja/
2023-12-02 13:16:20 +01:00
0b084a6441 Translated using Weblate (German)
Currently translated at 99.3% (290 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2023-12-02 13:16:20 +01:00
865efa1b12 Translated using Weblate (Japanese)
Currently translated at 100.0% (292 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ja/
2023-12-02 13:13:10 +01:00
6a248e5336 Translated using Weblate (Czech)
Currently translated at 46.2% (135 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-02 13:13:10 +01:00
eb9c256a5d Translated using Weblate (German)
Currently translated at 99.3% (290 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2023-12-02 13:13:10 +01:00
4bd54dcb2d Translated using Weblate (Japanese)
Currently translated at 40.0% (117 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ja/
2023-12-02 10:58:23 +01:00
17b035d317 Translated using Weblate (Japanese)
Currently translated at 38.6% (113 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ja/
2023-12-02 10:49:31 +01:00
28bcd1fefc Translated using Weblate (Korean)
Currently translated at 100.0% (292 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2023-12-02 02:16:18 +00:00
59bb1d9124 feat(README): add i18n README
- resolves https://github.com/th-ch/youtube-music/discussions/719
2023-12-02 10:06:55 +09:00
d9255c1cec Translated using Weblate (Korean)
Currently translated at 100.0% (292 of 292 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2023-12-02 00:27:27 +01:00
4ab4bb4cb3 feat(menu): add 'to-help-translate' button 2023-12-02 08:25:07 +09:00
a6c8b887e3 fix(in-app-menu): hide 'invisible' item from menu 2023-12-02 08:06:15 +09:00
1db0abf32d Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/
2023-12-02 00:02:39 +01:00
ff899b8720 Translated using Weblate (German)
Currently translated at 99.6% (290 of 291 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2023-12-02 00:02:39 +01:00
18004c4441 Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/
2023-12-01 23:56:30 +01:00
ce1cde72bd Translated using Weblate (Norwegian Bokmål)
Currently translated at 20.2% (59 of 291 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/nb_NO/
2023-12-01 23:56:30 +01:00
453f4d92c9 Translated using Weblate (Czech)
Currently translated at 37.8% (110 of 291 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-01 23:56:30 +01:00
37740e78b4 Translated using Weblate (Russian)
Currently translated at 11.6% (34 of 291 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ru/
2023-12-01 23:56:30 +01:00
8ace123179 Translated using Weblate (Greek)
Currently translated at 9.6% (28 of 291 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/el/
2023-12-01 23:56:30 +01:00
bcdb9de41a chore(deps): update dependency eslint to v8.55.0 2023-12-02 07:12:19 +09:00
9fdb6eb7e5 fix(security): fix vulnerability detected by snyk 2023-12-02 07:06:39 +09:00
88cd1d2390 chore(README): replace snyk badge 2023-12-02 07:00:19 +09:00
943bcd322d Translated using Weblate (German)
Currently translated at 99.6% (290 of 291 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2023-12-01 22:42:08 +01:00
7774128d7e Translated using Weblate (Korean)
Currently translated at 100.0% (291 of 291 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2023-12-01 22:42:08 +01:00
b9b9e2ba00 Translated using Weblate (English)
Currently translated at 100.0% (291 of 291 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/en/
2023-12-01 22:42:08 +01:00
0ce4f20ec5 fix(downloader): call submenu 2023-12-02 06:39:33 +09:00
51b87312c4 feat: changed Zoom shortcuts to standard
- resolves #1458
2023-12-02 06:16:38 +09:00
9ffd7af8a7 Translated using Weblate (Japanese)
Currently translated at 36.5% (106 of 290 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ja/
2023-12-01 21:48:39 +01:00
4a453a4f3d Translated using Weblate (German)
Currently translated at 99.6% (289 of 290 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2023-12-01 21:48:39 +01:00
dc8a472cdb Translated using Weblate (Korean)
Currently translated at 100.0% (290 of 290 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2023-12-01 21:48:39 +01:00
d2eabaa4bb Translated using Weblate (English)
Currently translated at 100.0% (290 of 290 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/en/
2023-12-01 21:48:39 +01:00
39c8ca66d1 fix: add (Language) to Language setting 2023-12-02 05:14:22 +09:00
806098a5ef feat(tray): add separator 2023-12-02 05:11:27 +09:00
5f6cfd9558 Translated using Weblate (Japanese)
Currently translated at 34.1% (99 of 290 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ja/
2023-12-01 21:03:01 +01:00
b4b7ad824b Translated using Weblate (Korean)
Currently translated at 100.0% (290 of 290 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2023-12-01 21:03:01 +01:00
7b5d602f63 Translated using Weblate (Japanese)
Currently translated at 31.3% (91 of 290 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ja/
2023-12-01 21:03:01 +01:00
7eeeb89457 Translated using Weblate (Japanese)
Currently translated at 31.3% (91 of 290 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ja/
2023-12-01 21:03:01 +01:00
6e8447b5d1 feat: run prettier 2023-12-02 05:01:29 +09:00
a6445bacf0 Translated using Weblate (Japanese)
Currently translated at 27.9% (81 of 290 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ja/
2023-12-01 20:36:58 +01:00
bd9b4f1b1a Translated using Weblate (Korean)
Currently translated at 100.0% (290 of 290 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2023-12-01 20:36:58 +01:00
9a816b3f07 fix(song-info-front): fix eslint warning 2023-12-02 04:29:05 +09:00
4dcac23688 feat: async video event dispatch 2023-12-02 04:28:38 +09:00
97ef6ff997 fix(quality-changer): i18n 2023-12-02 04:24:02 +09:00
244a656671 Translated using Weblate (Japanese)
Currently translated at 19.4% (54 of 277 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ja/
2023-12-01 19:50:33 +01:00
f8a2829adb Translated using Weblate (Japanese)
Currently translated at 19.4% (54 of 277 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ja/
2023-12-01 19:50:33 +01:00
24daadbef8 Translated using Weblate (Czech)
Currently translated at 39.7% (110 of 277 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-01 19:50:33 +01:00
bf2ac88847 Translated using Weblate (German)
Currently translated at 87.7% (243 of 277 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2023-12-01 19:50:33 +01:00
e42423b100 Translated using Weblate (Korean)
Currently translated at 100.0% (277 of 277 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2023-12-01 19:50:33 +01:00
de0c02efaf Translated using Weblate (Korean)
Currently translated at 100.0% (277 of 277 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2023-12-01 19:50:33 +01:00
b8290417f8 fix(en): fix 'quit' string 2023-12-02 03:50:23 +09:00
b92205a228 fix: i18n for default menu 2023-12-02 03:49:25 +09:00
5f642007ba feat(executed-at-ms): displaying a float to 2 decimal places
e.g. 58.399999998509884ms -> 58.39ms
2023-12-02 03:18:30 +09:00
ee40d278d4 Translated using Weblate (Korean)
Currently translated at 100.0% (277 of 277 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2023-12-01 18:35:12 +01:00
02d2e8ea92 Translated using Weblate (English)
Currently translated at 100.0% (277 of 277 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/en/
2023-12-01 18:35:12 +01:00
70715e5e8a fix(discord): connected, disconnected i18n 2023-12-02 02:33:54 +09:00
5ae4f564b7 fix(i18n): plugin name, description i18n 2023-12-02 02:33:40 +09:00
123eabd77a Translated using Weblate (Czech)
Currently translated at 39.6% (109 of 275 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-01 17:14:58 +00:00
fd3438a20d feat(i18n): i18n auto-importer 2023-12-02 02:13:49 +09:00
c8554a12f6 fix(i18n): fix locale name (Norwegian (Bokmal)) 2023-12-02 01:54:32 +09:00
4a687ade9c Translated using Weblate (German)
Currently translated at 86.1% (237 of 275 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2023-12-01 17:43:01 +01:00
f77aa372cc Translated using Weblate (Japanese)
Currently translated at 5.0% (14 of 275 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ja/
2023-12-01 17:37:32 +01:00
0b9eef94c4 Translated using Weblate (German)
Currently translated at 85.8% (236 of 275 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2023-12-01 17:37:32 +01:00
71b2f69f98 Translated using Weblate (German)
Currently translated at 85.8% (236 of 275 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2023-12-01 17:37:31 +01:00
4b61c5307e Translated using Weblate (German)
Currently translated at 75.6% (208 of 275 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2023-12-01 17:06:02 +01:00
a617b91263 Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/
2023-12-01 17:06:02 +01:00
fc79bdd0f3 Translated using Weblate (Norwegian Bokmål)
Currently translated at 21.0% (58 of 275 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/nb_NO/
2023-12-01 17:06:02 +01:00
e5e1e547d5 Translated using Weblate (Japanese)
Currently translated at 4.0% (11 of 275 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ja/
2023-12-01 17:06:02 +01:00
edac9b0c20 Translated using Weblate (German)
Currently translated at 75.2% (207 of 275 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2023-12-01 17:06:02 +01:00
dfaf3cf95a Translated using Weblate (Russian)
Currently translated at 11.2% (31 of 275 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ru/
2023-12-01 17:06:02 +01:00
bae90ce8f3 Translated using Weblate (Russian)
Currently translated at 11.2% (31 of 275 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ru/
2023-12-01 17:06:02 +01:00
188e56ce30 Translated using Weblate (Czech)
Currently translated at 39.6% (109 of 275 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-01 17:06:02 +01:00
19b48b123f Translated using Weblate (German)
Currently translated at 51.6% (142 of 275 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2023-12-01 17:06:02 +01:00
549961f297 Translated using Weblate (Korean)
Currently translated at 100.0% (275 of 275 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2023-12-01 17:06:02 +01:00
ba7bc68ac3 Translated using Weblate (Norwegian Bokmål)
Currently translated at 10.5% (29 of 275 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/nb_NO/
2023-12-01 17:06:02 +01:00
bbfe272d41 Translated using Weblate (Japanese)
Currently translated at 2.5% (7 of 275 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ja/
2023-12-01 17:06:02 +01:00
8a3e0a31ca Translated using Weblate (Czech)
Currently translated at 39.6% (109 of 275 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-01 17:06:02 +01:00
8fbda97885 Translated using Weblate (Czech)
Currently translated at 39.6% (109 of 275 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-01 17:06:02 +01:00
1856deb0f5 Translated using Weblate (German)
Currently translated at 30.5% (84 of 275 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2023-12-01 17:06:02 +01:00
fec7c5c130 Translated using Weblate (Korean)
Currently translated at 60.0% (165 of 275 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2023-12-01 17:06:02 +01:00
936b4b28bb Added translation using Weblate (Norwegian Bokmål) 2023-12-01 17:06:02 +01:00
f3092d0778 Added translation using Weblate (Japanese) 2023-12-01 17:06:02 +01:00
fc1adfae6c Translated using Weblate (Czech)
Currently translated at 32.7% (90 of 275 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-01 17:06:02 +01:00
e279aaed64 Translated using Weblate (Czech)
Currently translated at 100.0% (0 of 0 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2023-12-01 17:06:02 +01:00
4d346a9471 Translated using Weblate (German)
Currently translated at 29.8% (82 of 275 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2023-12-01 17:06:02 +01:00
cfc504da34 Added translation using Weblate (Czech) 2023-12-01 17:06:02 +01:00
0919a4b9b7 Translated using Weblate (German)
Currently translated at 100.0% (0 of 0 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2023-12-01 17:06:02 +01:00
f46ad2ea0e Added translation using Weblate (German) 2023-12-01 17:06:02 +01:00
252719bc71 Translated using Weblate (Russian)
Currently translated at 100.0% (0 of 0 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ru/
2023-12-01 17:06:02 +01:00
45f49361ea Translated using Weblate (Greek)
Currently translated at 6.9% (19 of 275 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/el/
2023-12-01 17:06:02 +01:00
c4a74c6c7e Translated using Weblate (Korean)
Currently translated at 44.0% (121 of 275 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2023-12-01 17:06:02 +01:00
05f197948d Added translation using Weblate (Russian) 2023-12-01 17:06:02 +01:00
5a1d230538 Translated using Weblate (Greek)
Currently translated at 100.0% (0 of 0 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/el/
2023-12-01 17:06:02 +01:00
a7ad260a00 Translated using Weblate (Korean)
Currently translated at 18.1% (50 of 275 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2023-12-01 17:06:02 +01:00
ef068cccd9 Added translation using Weblate (Greek) 2023-12-01 17:06:02 +01:00
166067920d Translated using Weblate (Korean)
Currently translated at 1.0% (3 of 275 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2023-12-01 17:06:02 +01:00
8227853cf9 fix: load i18n 2023-12-01 23:50:15 +09:00
324a539b89 fix(in-app-menu): resize
- Window controls: 36px -> 32px
- Font size: 14px -> 12px
2023-12-01 23:04:49 +09:00
ce7557353c Add text to Translation section (#1470) 2023-12-01 21:41:13 +09:00
7b7923fe9b fix(downloader): fix i18n 2023-12-01 21:40:03 +09:00
105d5c78e7 chore(deps): update dependency rollup to v4.6.1 2023-12-01 21:31:13 +09:00
b25183a8f5 fix(deps): update dependency youtubei.js to v8 (#1473)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-01 21:20:15 +09:00
adde33d1f5 chore(deps): update dependency electron to v27.1.3 (#1471)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-01 21:18:54 +09:00
ad325ccb10 fix(deps): update dependency @xhayper/discord-rpc to v1.1.1 (#1472)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-01 21:17:28 +09:00
2e7ea6969c chore(README): add translation info 2023-12-01 02:24:23 +09:00
7401cf69ad feat: add support i18n (#1468) 2023-12-01 01:30:46 +09:00
7f71c36dc0 doc: Add warning in index.html 2023-11-30 11:07:32 +02:00
a3104fda4b feat: run prettier 2023-11-30 11:59:27 +09:00
44c42310f1 feat(.eslintrc.js): add prettier 2023-11-30 11:48:03 +09:00
a22a8ac5c9 feat: add description for plugins 2023-11-30 11:42:34 +09:00
aa5c3bac4e chore(deps): bump deps 2023-11-30 10:27:11 +09:00
30b3beee18 Merge pull request #1401 from organization/feat/refactor-plugin-system 2023-11-30 10:07:43 +09:00
b059e43fb1 fix(discord): rename from 'timout' to 'timeout' 2023-11-30 10:00:39 +09:00
3b04d0ba19 feat(discord): apply config as dynamic 2023-11-30 09:54:10 +09:00
959f99beae fix(blur-nav-bar): set restartNeeded to true 2023-11-30 09:41:05 +09:00
ed402933d3 fix(in-app-menu): fix #1436 2023-11-30 09:38:51 +09:00
ef8bb95884 chore(deps): update playwright monorepo to v1.40.1 2023-11-30 03:13:09 +09:00
1b79d2e429 fix(song-info-front): add type 2023-11-30 03:03:04 +09:00
ec786748be fix: dispatch event 2023-11-30 03:02:20 +09:00
06f1c7effe fix(ambient-mode): fix unload 2023-11-30 02:45:44 +09:00
d78da237fc fix: fix an issue with videodatachangefired timing 2023-11-30 00:45:15 +09:00
4c0cce89ee fix(video-toggle): remove unnecessary querySelector call 2023-11-29 19:23:58 +09:00
888ced8fd1 fix(loadAllPreloadPlugins): remove await 2023-11-29 18:27:45 +09:00
e1690720b3 fix: disable auto-play 2023-11-29 18:05:32 +09:00
bbff0a6bc2 feat: plugin load await 2023-11-29 18:04:37 +09:00
5db759150c feat: use '.call' instead of '.bind' 2023-11-29 01:27:51 +09:00
ae239f6700 fix(precise-volume): fix precise-volume plugin 2023-11-28 12:37:31 +09:00
1d26d10e57 fix: menu await 2023-11-28 12:32:07 +09:00
da70a4ce7e fix: fix audioCanPlay event
- e.g. Visualizer Plugin
2023-11-28 12:11:48 +09:00
75ae9f4fad fix: fix unloader 2023-11-28 11:48:09 +09:00
8f7933c111 fix: fix restartNeeded 2023-11-28 11:29:43 +09:00
29a0dedcce chore(action/build): fix version tag 2023-11-28 11:10:41 +09:00
4d62993177 chore(action/build): add notes for Windows
resolve https://github.com/th-ch/youtube-music/discussions/1343
2023-11-28 11:07:44 +09:00
8714f33fa2 feat: use app.whenReady() instead of app.on('ready', ...) 2023-11-28 10:55:33 +09:00
5dacd50ff6 feat: use LoggerPrefix const instead of hardcoded string 2023-11-28 10:51:19 +09:00
8d06dcc7b6 fix(utils/index): little fix (lint) 2023-11-28 10:44:48 +09:00
b8f6dd2584 chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.13.0 2023-11-28 10:41:39 +09:00
0650205b86 chore: update README 2023-11-28 10:28:39 +09:00
3e8a0ec49a fix: plugin config default value 2023-11-28 10:21:08 +09:00
04d7b32d3f feat: createBackend, createPreload, createRenderer
Introduced the mentioned helper methods in order to help split big plugins into manageable chunks.
2023-11-28 00:06:59 +02:00
eaaf170cc8 make log prefix consistent 2023-11-27 23:31:05 +02:00
09450fb8c7 fix(store): fix TypeError: Cannot convert undefined or null to object 2023-11-28 05:51:47 +09:00
ac0b78eefb fix(store): fix Cannot read properties of undefined 'global' 2023-11-28 05:46:49 +09:00
90103d9853 fix: lifecycle check
edge case:
 - There may be plugins that don't have a start or stop function
2023-11-28 05:23:35 +09:00
bf27c73f1d fix(album-color-theme): more simple assign 2023-11-28 04:50:24 +09:00
845c9365be fix(album-color-theme): sidebar color 2023-11-28 04:44:17 +09:00
91cf5f5c25 fix: fix Cannot access 'Ba' before initialization on first run 2023-11-28 04:21:58 +09:00
783a892e26 bump rollup version 2023-11-28 01:59:15 +09:00
41d8f86962 Merge branch 'master' into feat/refactor-plugin-system 2023-11-28 01:51:31 +09:00
252349579e chore(deps): update dependency rollup to v4.6.0 2023-11-28 01:51:10 +09:00
99b1cfbde4 fix: fix pnpm-lock 2023-11-28 01:50:46 +09:00
3f70d912d7 Merge branch 'master' into feat/refactor-plugin-system 2023-11-28 01:48:38 +09:00
bf33c4e7b4 Merge branch 'feat/new-plugin-system' into feat/refactor-plugin-system 2023-11-28 01:43:51 +09:00
3152842a30 fix: this binding
Co-authored-by: Su-Yong <simssy2205@gmail.com>
2023-11-28 01:39:11 +09:00
d84416b27c fix: crash 2023-11-28 01:02:05 +09:00
cc38978bd3 feat: label sort 2023-11-28 00:51:16 +09:00
7a76079ff4 fix: load plugins
Co-authored-by: Su-Yong <simssy2205@gmail.com>
2023-11-28 00:34:36 +09:00
2fe28cf126 clean code 2023-11-27 19:26:45 +09:00
3ffbfbe0e3 convert plugins 2023-11-27 18:41:50 +09:00
4fad456619 fix eslint warning 2023-11-27 05:10:35 +09:00
7591f13505 in-app-menu 2023-11-27 05:09:33 +09:00
11d06c50a5 WIP 2 2023-11-27 04:59:20 +09:00
e0a3489640 fix: remove PluginBaseConfig 2023-11-27 00:57:05 +09:00
e55a1d3076 fix: fix onConfigChange 2023-11-27 00:44:46 +09:00
563d431c00 WIP
Co-authored-by: Su-Yong <simssy2205@gmail.com>
2023-11-27 00:18:34 +09:00
3a1b77ebd8 fix: electron-vite setting
Co-authored-by: Su-Yong <simssy2205@gmail.com>
2023-11-26 23:25:57 +09:00
3f8030a9c5 fix: inject multiple stylesheet 2023-11-26 23:25:38 +09:00
e12e67af0e feat: reimplement inject css, fix types 2023-11-26 23:12:19 +09:00
3ab4cd5d05 change plugin system 2023-11-26 01:17:24 +02:00
738adbed98 chore(deps): update dependency electron-builder to v24.9.1 2023-11-25 20:44:32 +09:00
365a078600 fix(deps): update dependency electron-updater to v6.1.7 2023-11-25 20:41:44 +09:00
04fc43e18b chore(deps): update pnpm to v8.11.0 2023-11-25 20:41:33 +09:00
54273baec7 chore(deps): update dependency rollup to v4.5.2 2023-11-24 20:56:29 +09:00
51e62ef47b fix(discord): update application client-id
fix #1431
2023-11-23 12:12:08 +09:00
a330ebcda7 chore(deps): update dependency electron to v27.1.2 (#1441)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-23 09:21:08 +09:00
a023fff2d0 Nicer Readme (#1439) 2023-11-22 15:33:49 +09:00
abb25ea6fb chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.12.0 2023-11-22 15:29:51 +09:00
ef49bcdb5f chore(deps): update dependency typescript to v5.3.2 2023-11-22 15:25:37 +09:00
b4f1b112d6 chore(deps): update dependency rollup to v4.5.1 2023-11-22 15:23:11 +09:00
f24ec0ae9d chore(deps): update dependency rollup to v4.5.0 2023-11-20 15:44:36 +09:00
ebb51fe37b chore(deps): update dependency eslint to v8.54.0 2023-11-20 15:44:28 +09:00
e8ee18f903 chore(deps): update dependency electron-vite to v1.0.29 2023-11-20 15:42:36 +09:00
a593de705c chore(deps): update playwright monorepo to v1.40.0 2023-11-20 15:42:25 +09:00
03dd024704 Windows Zoom, ScaleFactor (#1402) 2023-11-20 15:42:04 +09:00
528c3535dd fix(deps): update dependency @xhayper/discord-rpc to v1.1.0 2023-11-19 21:01:47 +09:00
0e0f80a2d0 chore(deps): update dependency electron to v27.1.0 2023-11-16 09:26:50 +09:00
6b67fb136a fix(deps): update dependency conf to v12 2023-11-15 21:52:58 +09:00
9fe1c14869 chore(deps): update dependency @electron/universal to v2 2023-11-15 18:09:00 +09:00
8a96dddf54 pnpm dedupe 2023-11-15 18:08:25 +09:00
230422c98b pnpm dedupe 2023-11-15 18:02:51 +09:00
d16ffc531f fix: renovate.json 2023-11-15 18:02:05 +09:00
f614199ea5 feat: remove support for npm
- remove yarpm
2023-11-15 17:56:52 +09:00
55a1c2e9e3 fix(package.json): fix spaces in overrides 2023-11-15 17:53:32 +09:00
bee1f77812 chore(deps): bump rollup & pnpm 2023-11-15 17:51:53 +09:00
fdf982ada5 chore(deps): bump deps 2023-11-15 17:50:07 +09:00
ff02fc7855 chore(deps): bump axios from 1.5.1 to 1.6.1 (#1400)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-15 17:43:16 +09:00
01ed289400 feat(renovate): add labels for dependency update 2023-11-13 01:40:33 +09:00
TC
aedb2db655 Add renovate config file 2023-11-12 17:35:02 +01:00
10a54b9de0 feat: update README 2023-11-12 02:56:02 +09:00
ccd029c040 fix: update types 2023-11-12 02:55:44 +09:00
3a431841b7 fix(plugin): fix always show restart dialog 2023-11-12 02:10:50 +09:00
deceae8354 chore(plugin): clean import 2023-11-12 02:10:11 +09:00
c8628670cf fix(ambient-plugin): fix plugin definition 2023-11-12 02:05:12 +09:00
ffe53d5596 feat(plugin): show dialog need to restart 2023-11-12 02:02:54 +09:00
a4f4ecb569 feat(plugin): add onPlayerApiReady hook
Co-authored-by: JellyBrick <shlee1503@naver.com>
2023-11-12 01:51:26 +09:00
2097f42efb feat(plugin): support dynamic plugin load / unload 2023-11-12 01:16:34 +09:00
9c59f56aac refactor(plugin): apply new plugin loader at all type of plugin 2023-11-12 00:50:09 +09:00
dfcc4107b7 feat(menu): sort plugin name 2023-11-12 00:31:48 +09:00
ef71abfff1 refactor(plugin): add renderer-plugin-loader 2023-11-12 00:21:34 +09:00
bc916f3a6e refactor(plugin): refactor plugin loader and add dynamic loading 2023-11-12 00:09:56 +09:00
c7ff0dcbf6 fix: remove defaults
Co-authored-by: Su-Yong <simssy2205@gmail.com>
2023-11-11 22:43:51 +09:00
7242f9bfd0 fix(plugin): fix onChangeConfig hook 2023-11-11 22:08:44 +09:00
bb2e865880 wip: trying to fix electron-store issue
Co-authored-by: JellyBrick <shlee1503@naver.com>
2023-11-11 21:48:51 +09:00
6ab3cf9ac9 fix(frontend): fix cannot apply style 2023-11-11 20:58:16 +09:00
b77f5c9ecc fix: fix insert 2023-11-11 20:05:38 +09:00
b470dbd6b9 fix: add default config 2023-11-11 19:32:05 +09:00
1f96b6b44d fix: plugin load 2023-11-11 19:23:17 +09:00
de0b228ae8 feat: electron-vite hot-reload 2023-11-11 18:12:20 +09:00
f35d192650 fix(downloader): fix downloader plugin 2023-11-11 18:11:24 +09:00
794d00ce9e feat: migrate to new plugin api
Co-authored-by: Su-Yong <simssy2205@gmail.com>
2023-11-11 18:02:22 +09:00
739e7a448b remove unused Promise.resolve 2023-11-11 11:51:06 +09:00
7fa8a454b6 fix: fix setPartial 2023-11-11 11:50:02 +09:00
5cd1d9abe8 feat(plugin): migrate some plugin (WIP)
Co-authored-by: JellyBrick <shlee1503@naver.com>
2023-11-11 00:03:26 +09:00
e0e17cac99 feat(plugin): migrating plugins to new plugin system (WIP)
Co-authored-by: JellyBrick <shlee1503@naver.com>
2023-11-10 21:32:05 +09:00
840039330f fix: Update bug_report.yml 2023-11-10 20:09:23 +09:00
734409dc3f chore: Update bug_report.yml 2023-11-10 20:09:04 +09:00
34564c8c55 Updated mac icon to better reflect the Mac styling (#1395) 2023-11-10 12:33:17 +09:00
afe6accab8 refactor(plugin): new plugin system poc 2023-11-09 22:54:58 +09:00
b6e7e75ae8 plugin system poc 2023-11-09 12:43:41 +09:00
06dc0e80f0 feat: rename plugins to clarify context (#1392) 2023-11-09 11:35:43 +09:00
47cccbce7c feat: refactor plugin utils (#1391) 2023-11-09 11:06:06 +09:00
269352af97 chore(deps): update dependency electron to v27.0.4
- fix #1324
2023-11-09 10:39:43 +09:00
fa62f79dce chore(deps): bump deps 2023-11-09 10:31:30 +09:00
9f88b37f41 fix(in-app-menu): panel should close with the window when it is closed
- fix #1389
2023-11-09 10:27:07 +09:00
55ae9eac1e fix: fixed issues identified in eslint 2023-11-09 10:06:03 +09:00
05564d4a58 feat: add auto-importer for menu 2023-11-09 09:57:32 +09:00
59426c56db feat: plugin auto-importer with vite-plugin-resolve (#1385) 2023-11-09 09:22:23 +09:00
18cd4c0c9a chore(navigation): remove unused css attribute 2023-11-08 18:15:24 +09:00
a0e2a33e28 fix: change titleBarOverlay height based on zoomFactor
- fix #1375
2023-11-08 17:48:32 +09:00
7bdb46e161 fix: fixed an issue if "Always on top" is enabled, the dialog is displayed below the window
- fix #1379
2023-11-08 16:35:19 +09:00
f560b62de0 fix(precise-volume): fix precise-volume plugin 2023-11-08 02:59:20 +09:00
adc1f6822b fix: image path 2023-11-07 23:43:16 +09:00
2da29fcfa7 feat: migrate from rollup to electron-vite (#1364)
* feat: electron-vite PoC

* fix: fix preload path

* remove rollup deps and config

* fix: debug mode

* fix: build mode, asset path

* fix: remove unused dependencies

* feat: use `executeJavaScriptInIsolatedWorld` instead of `executeJavaScript`

* feat: enable `minify`

* fix(actions): update task name

* fix: fix dev mode check

* fix: remove unused variable
2023-11-07 19:49:28 +09:00
c5d0314db6 fix(winget): fix env name 2023-11-07 08:27:20 +09:00
8c052faedd fix: fix winget version (fix #1363) 2023-11-06 17:38:57 +09:00
37067ff950 fix: update jsdoc 2023-11-06 17:22:46 +09:00
6366dc026e feat: enable context-isolation (#1361) 2023-11-06 17:21:29 +09:00
6e52178074 fix: fix README 2023-11-06 17:13:47 +09:00
47f38cc690 fix: add workaround for podcast type video (#1362) 2023-11-04 16:56:41 +09:00
fdd6d9929f fix: temporary workaround for #1356 2023-11-04 10:58:32 +09:00
1707261f49 chore(deps): bump deps 2023-11-04 10:58:14 +09:00
6712fced6d fix: fix broken menu-layout (#1360) 2023-11-04 10:46:49 +09:00
6dabfaa9ba fix: use window instead of (global as any) 2023-11-04 10:30:39 +09:00
a41db79c35 fix(deps): fix pnpm.overrides 2023-11-03 10:09:04 +09:00
87786d9aef chore(deps): update dependency node-gyp to v10.0.1 2023-11-03 09:57:24 +09:00
22f5866050 chore(deps): update dependency @electron/universal to v1.4.4 2023-11-03 09:57:12 +09:00
04894fbcf5 chore(deps): update dependency node-gyp to v10 2023-11-02 12:51:52 +09:00
c17c624ba4 chore(deps): bump deps 2023-11-02 12:48:44 +09:00
bfe7249df8 Add Homebrew cask install option for MacOS. (#1357) 2023-11-02 09:42:13 +09:00
13c570efe9 fix: use node-fetch v3 instead of v2
- auto-change: moved from devDependencies to npx
2023-10-29 06:17:33 +09:00
b299846f0f fix: fix node-gyp version 2023-10-29 06:09:47 +09:00
59e9289d27 remove: remove patch-package
- see https://github.com/LuanRT/YouTube.js/pull/509
2023-10-29 06:05:15 +09:00
8dc29caa1b chore(actions): update setup-node v3 to v4 2023-10-29 06:02:03 +09:00
7fedf88654 chore(deps): bump deps version
- rollup: 4.1.4 to 4.1.5
- node-gyp: 9.4.0 to 9.4.1
- @cliqz/adblocker: 1.26.8 to 1.26.9
- youtubei.js: 6.4.1 to 7.0.0
- pnpm: 8.9.2 to 8.10.0
2023-10-29 05:54:43 +09:00
5da0202425 Update changelog for v2.2.0 2023-10-26 17:16:26 +00:00
6288d0b171 Bump version to 2.2.0 2023-10-27 02:01:44 +09:00
4248d20e8e bump deps 2023-10-25 16:46:59 +09:00
0b413492ad feat(ambient-mode): add config for ambient-mode plugin (#1349) 2023-10-25 15:37:51 +09:00
dc73561c8a Update changelog for v2.1.3 2023-10-22 15:50:12 +00:00
949a2f6428 Bump version to 2.1.3 2023-10-23 00:35:10 +09:00
bceaa05197 fix(store): fix listenAlong statement 2023-10-23 00:29:59 +09:00
776cdac30d feat(discord): rename Listen Along to Play on YTM
resolve #1341
2023-10-23 00:27:43 +09:00
4333891cca chore(deps): bump deps 2023-10-23 00:19:42 +09:00
8a89bbccf7 fix: fixed bugs in downloader (#1342) 2023-10-23 00:19:01 +09:00
fa4c69d228 Update changelog for v2.1.2 2023-10-19 13:55:04 +00:00
c25def8901 Bump version to 2.1.2 2023-10-19 22:39:14 +09:00
284a59b721 fix: fix unresponsive (fix #1325) 2023-10-19 22:35:32 +09:00
5fcba8619a feat(in-app-menu): add an option to hide the window controls (#1335) 2023-10-19 22:34:18 +09:00
f3cd759276 fix: fixed an issue where the album name was missing (#1334) 2023-10-19 21:45:46 +09:00
9d3981e361 chore: Update build.yml 2023-10-19 21:33:54 +09:00
787326948b chore: making actions more efficient 2023-10-19 09:51:31 +09:00
779251933c chore(deps): update dependency electron to v27.0.1 (#1331) 2023-10-19 09:42:41 +09:00
1efe835c69 fix: fixed an issue where only the first 100 songs in a playlist were downloaded (#1329) 2023-10-19 05:40:05 +09:00
5702978227 chore(deps): update dependencies 2023-10-18 18:18:28 +09:00
fa3d742838 chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.8.0 2023-10-18 02:21:25 +09:00
c460cc2296 Updated readme plugins list (#1326) 2023-10-17 20:04:23 +09:00
4e4af5e830 fix(actions): fix if statement 2023-10-16 22:55:04 +09:00
9a4e98063b chore(actions): disable pnpm cache for macOS 2023-10-16 22:54:11 +09:00
8bfe04bb50 chore: update issue template
Thanks to Alipoodle for writing this question.
2023-10-16 22:42:02 +09:00
6774d54f5e chore(deps): update dependency rollup to v4.1.4 2023-10-16 16:41:36 +09:00
9705f8489d chore(deps): Bump @rollup/plugin-commonjs, pnpm version, Remove ytpl 2023-10-16 16:24:16 +09:00
a7229cbe14 Bump @rollup/plugin-commonjs, pnpm version 2023-10-16 03:08:04 +09:00
7577aba45e feat: use test:debug for CI 2023-10-16 02:08:25 +09:00
d78fbe476e hotfix: fix Cannot read properties of undefined (reading 'removeChild') 2023-10-16 01:09:24 +09:00
bfe4b2bba7 fix(actions): use GabrielBB/xvfb-action instead of coactions/setup-xvfb 2023-10-16 00:58:02 +09:00
7625a3aa52 QOL: Move source code under the src directory. (#1318) 2023-10-15 21:52:48 +09:00
30c8dcf730 fix: release action 2023-10-15 18:54:25 +09:00
00a3e8d35e chore(deps): Bump rollup, @xhayper/discord-rpc version 2023-10-15 18:35:57 +09:00
4d01cdfa6c fix(blocker): remove the app.isPackaged check (fix #1315) 2023-10-15 18:33:14 +09:00
f924b6c8e3 fix(actions): install pnpm before call setup-node 2023-10-15 18:28:09 +09:00
926d98174c fix: fix build actions 2023-10-15 18:22:19 +09:00
41b3972f54 chore(README): add pnpm install guide 2023-10-15 18:20:49 +09:00
467f29e363 feat: migrate from npm to pnpm (#1316) 2023-10-15 18:18:20 +09:00
9cc13c3757 Merge pull request #1317 from foonathan/fix-loop-status 2023-10-15 04:23:33 +09:00
f8ccb86156 Fix mpris player.loopStatus 2023-10-14 21:03:06 +02:00
b316aa2301 chore(deps): update dependency rollup to v4.1.0 2023-10-14 22:33:09 +09:00
5c49b28664 fix(discord): Discord RPC fails if a song's title is only one character (fix #1314) 2023-10-14 20:27:58 +09:00
dedf96afd3 Update changelog for v2.1.1 2023-10-14 05:49:56 +00:00
3bb5bc2ca1 Bump version to 2.1.1 2023-10-14 14:34:39 +09:00
c79fdd9887 fix: empty title playlist directory path 2023-10-14 14:03:47 +09:00
d7b821727d hotfix(downloader): can't get an album title (fix #1313) 2023-10-14 13:55:57 +09:00
TC
21c45faf20 Add "about" menu to show app version 2023-10-13 22:04:50 +02:00
92cab89d17 Update changelog for v2.1.0 2023-10-13 19:27:20 +00:00
fa160b2e90 Bump version to 2.1.0 2023-10-14 04:09:52 +09:00
308ac38e6b feat(downloader): Added support for audio format auto-detection (#1310) 2023-10-14 03:42:10 +09:00
a62cafb601 feat(in-app-menu): enable in-app-menu by default (in Windows) (#1311) 2023-10-14 03:07:06 +09:00
bf9e3b5f48 hotfix(downloader): fix invalid query selector (fix #1308) 2023-10-13 22:06:27 +09:00
3c6b3aeff0 chore(deps): bump dependencies 2023-10-12 13:39:14 +09:00
37181a7b5e chore(actions): create winget-cla.yml 2023-10-12 12:51:17 +09:00
0b363d6487 fix: winget publish (#1307)
* chore(actions): Update build.yml

* fix: installer regex
2023-10-12 08:01:10 +09:00
e9398adac3 Update changelog for v2.0.4 2023-10-11 16:02:44 +00:00
6901713036 Bump version to 2.0.4 2023-10-12 00:46:04 +09:00
1d5b2997bd fix(downloader): private playlist download 2023-10-12 00:41:58 +09:00
572a023aaa fix: fixed an issue with the initial launch in certain regions, such as South Korea 2023-10-11 23:09:05 +09:00
9187f1e240 Revert "fix: set default adblocker as InPlayer"
This reverts commit 85228fd7d2.
2023-10-11 22:47:56 +09:00
df13d7d0f3 Merge pull request #1304 from th-ch/fix/deps 2023-10-11 22:37:16 +09:00
85228fd7d2 fix: set default adblocker as InPlayer
Fixed an issue with the initial launch in certain regions, such as South Korea.
2023-10-11 22:12:54 +09:00
17ba071057 fix: crash before window loaded 2023-10-11 21:59:03 +09:00
d7df4d7d10 fix: fix It Just Works
Fixed an issue that caused inconsistent execution results.
2023-10-11 19:28:01 +09:00
7aa970cebc fix: bump dependencies 2023-10-11 18:24:11 +09:00
f08f003cf4 Merge pull request #1301 from th-ch/fix/1300
hotfix(adblocker): fix `ipcRenderer.sendSync() with ...`
2023-10-11 08:53:22 +09:00
9f99eded9e chore(readme): update build instruction 2023-10-11 08:48:36 +09:00
c512f13009 hotfix(adblocker): fix ipcRenderer.sendSync() with ...
This issue is caused by the renderer's adblocker being loaded before the main process's adblocker.
2023-10-11 02:01:44 +09:00
b475f780ff Merge pull request #1296 from Lucasamiel0406/master 2023-10-11 00:23:11 +09:00
2294102006 Merge pull request #1297 from nnnlog/master
fix(downloader): Korean filename is broken on non-macOS devices
2023-10-10 16:48:43 +09:00
d69a07d025 fix(downloader): normalize filename depending on OS 2023-10-10 16:05:09 +09:00
4f4995c20c fix: typo in readme.md 2023-10-10 15:54:55 +09:00
b6894dca29 chore(deps): bump deps 2023-10-10 14:10:33 +09:00
73f14e581d Fix Library removed for Premium users
As by now, the code removes the last child of the YT's buttons sidebar. It's good for non-premium users but affects premium users, as it removes the "Library" button.

This small fix targets the 4th child (usually the Upgrade button location) instead of last child.

A bad move/practice, but does its job and remove the Upgrade button while not removing the Library one.
2023-10-09 20:56:08 -03:00
2f2e64af4a Update changelog for v2.0.3 2023-10-09 16:06:41 +00:00
5710307ddc Bump version to 2.0.3 2023-10-10 00:51:44 +09:00
52ba2dc9ff remove: migration scripts 2023-10-10 00:51:16 +09:00
926b9fb5e6 feat: add migration script 2023-10-10 00:42:26 +09:00
a6c9b3381a fix(discord): apply hideGitHubButton 2023-10-10 00:42:10 +09:00
5dc13a4698 feat(discord): add Hide GitHub link Button (#1293) 2023-10-10 00:15:15 +09:00
a69085c591 fix: chore(deps): update dependency @jellybrick/mpris-service to 2.1.4 (fix #971) 2023-10-09 21:55:23 +09:00
a22f7fed21 feat(deps): bundle youtubei.js (temporary solution) (#1292) 2023-10-09 21:30:53 +09:00
8b7045fb1b chore(deps): update dependency @rollup/plugin-node-resolve to v15.2.3 2023-10-09 20:16:40 +09:00
efd1b92514 feat(defaults): change the default value of blocker back to WithBlocklists 2023-10-09 19:52:02 +09:00
969f6d7bba chore(deps): Bump @cliqz/adblocker-electron to 1.26.8 (fix #1269) 2023-10-09 19:50:27 +09:00
4f7c92d6a0 feat(plugins/utils): mediaIcons as const 2023-10-09 19:49:40 +09:00
24d4a50574 fix(mpris): fixed an issue where MPRIS information was incorrect (#1291) 2023-10-09 19:36:17 +09:00
7693a3ba4a fix(discord): fixed an issue where timeChanged was not being applied to Discord activities (#1290) 2023-10-09 19:36:06 +09:00
7ca4dc5c85 Fix: typo in README (#1286) 2023-10-08 23:54:58 +09:00
21ff09b605 Merge pull request #1283 from th-ch/fix/missing-taskbar-mediacontrol-icons 2023-10-08 19:57:50 +09:00
fbf4b3b8b5 fix: missing icons taskbar-mediacontrol 2023-10-08 19:41:39 +09:00
5812eb0147 Update changelog for v2.0.2 2023-10-08 08:51:28 +00:00
369 changed files with 42588 additions and 19249 deletions

View File

@ -1,3 +1 @@
.eslintrc.js
rollup.main.config.ts
rollup.preload.config.ts

View File

@ -7,7 +7,7 @@ module.exports = {
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
],
plugins: ['@typescript-eslint', 'import'],
plugins: ['prettier', '@typescript-eslint', 'import'],
parser: '@typescript-eslint/parser',
parserOptions: {
project: './tsconfig.json',
@ -26,6 +26,7 @@ module.exports = {
'import/newline-after-import': 'error',
'import/no-default-export': 'off',
'import/no-duplicates': 'error',
'import/no-unresolved': ['error', { ignore: ['^virtual:', '\\?inline$', '\\?raw$', '\\?asset&asarUnpack'] }],
'import/order': [
'error',
{
@ -66,4 +67,14 @@ module.exports = {
es6: true,
},
ignorePatterns: ['dist', 'node_modules'],
root: true,
settings: {
'import/parsers': {
'@typescript-eslint/parser': ['.ts']
},
'import/resolver': {
typescript: {},
exports: {},
},
},
};

View File

@ -12,16 +12,24 @@ body:
required: true
- label: I have searched the [issue tracker](https://github.com/th-ch/youtube-music/issues) for a bug report that matches the one I want to file, without success.
required: true
- label: I understand that **th-ch/youtube-music has NO affiliation with Google or YouTube**
required: true
- type: input
attributes:
label: YouTube Music (Application) Version
description: |
What version of YouTube Music Application are you using?
What version of the YouTube Music Application are you using?
Note: Please check if this issue is reproducible with the latest stable release.
placeholder: 2.0.0
validations:
required: true
- type: checkboxes
attributes:
label: Checklists
options:
- label: I use the portable version of the YouTube Music Application.
- label: I can reproduce this issue in the [official version of (WEB) YTM](https://music.youtube.com).
- type: dropdown
attributes:
label: What operating system are you using?
@ -36,13 +44,13 @@ body:
- type: input
attributes:
label: Operating System Version
description: What operating system version are you using? On Windows, click Start button > Settings > System > About. On macOS, click the Apple Menu > About This Mac. On Linux, use lsb_release or uname -a.
description: What operating system version are you using? On Windows, click the Start button > Settings > System > About. On macOS, click the Apple Menu > About This Mac. On Linux, use lsb_release or uname -a.
placeholder: "e.g. Windows 10 version 1909, macOS Catalina 10.15.7, or Ubuntu 20.04"
validations:
required: true
- type: dropdown
attributes:
label: What arch are you using?
label: What CPU architecture are you using?
options:
- x64
- ia32
@ -55,10 +63,17 @@ body:
label: Last Known Working YouTube Music (Application) version
description: (If applicable) What is the last version of YouTube Music this worked in?
placeholder: 1.20.0
- type: textarea
attributes:
label: Reproduction steps
description: Provide steps to reproduce the issue.
placeholder: 1. Enable the X plugin.
validations:
required: true
- type: textarea
attributes:
label: Expected Behavior
description: A clear and concise description of what you expected to happen. (Add a replication step if applicable)
description: A clear and concise description of what you expected to happen.
validations:
required: true
- type: textarea
@ -67,6 +82,13 @@ body:
description: A clear description of what actually happens.
validations:
required: true
- type: textarea
attributes:
label: Enabled plugins
description: Provide the list of plugins you enabled.
placeholder: 1. Album Color Theme
validations:
required: true
- type: textarea
attributes:
label: Additional Information

View File

@ -15,7 +15,7 @@ body:
- type: textarea
attributes:
label: Problem Description
description: Please add a clear and concise description of the problem you are seeking to solve with this feature request.
description: A clear and concise description of the problem you are seeking to solve with this feature request.
validations:
required: true
- type: textarea
@ -33,6 +33,6 @@ body:
- type: textarea
attributes:
label: Additional Information
description: Add any other context about the problem here.
description: Any other context about the problem.
validations:
required: false

View File

@ -20,59 +20,63 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 8
run_install: false
- name: Setup NodeJS
uses: actions/setup-node@v3
if: startsWith(matrix.os, 'macOS') != true
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'
- name: Setup NodeJS for macOS
if: startsWith(matrix.os, 'macOS')
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
run: pnpm install --frozen-lockfile
# Only vite build without release if it is a fork, or it is a pull-request
- name: Vite Build
if: github.repository == 'th-ch/youtube-music' && github.event_name == 'pull_request'
run: |
pnpm build
# Build and release if it's the main repository and is not pull-request
- name: Build and release on Mac
if: startsWith(matrix.os, 'macOS') && (github.repository == 'th-ch/youtube-music' && github.event_name != 'pull_request')
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
pnpm release:mac
- name: Build and release on Linux
if: startsWith(matrix.os, 'ubuntu') && (github.repository == 'th-ch/youtube-music' && github.event_name != 'pull_request')
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
pnpm release:linux
- name: Build and release on Windows
if: startsWith(matrix.os, 'windows') && (github.repository == 'th-ch/youtube-music' && github.event_name != 'pull_request')
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
pnpm release:win
- name: Test
uses: GabrielBB/xvfb-action@v1
uses: coactions/setup-xvfb@v1
env:
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
with:
run: npm run test
run: pnpm test:debug
# Build and release if it's the main repository
- name: Build and release on Mac
if: startsWith(matrix.os, 'macOS') && github.repository == 'th-ch/youtube-music'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
npm run release:mac
- name: Build and release on Linux
if: startsWith(matrix.os, 'ubuntu') && github.repository == 'th-ch/youtube-music'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
npm run release:linux
- name: Build and release on Windows
if: startsWith(matrix.os, 'windows') && github.repository == 'th-ch/youtube-music'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
npm run release:win
# Only build without release if it is a fork
- name: Build on Mac
if: startsWith(matrix.os, 'macOS') && github.repository != 'th-ch/youtube-music'
run: |
npm run build:mac
- name: Build on Linux
if: startsWith(matrix.os, 'ubuntu') && github.repository != 'th-ch/youtube-music'
run: |
npm run build:linux
- name: Build on Windows
if: startsWith(matrix.os, 'windows') && github.repository != 'th-ch/youtube-music'
run: |
npm run build:win
release:
runs-on: ubuntu-latest
@ -84,14 +88,27 @@ jobs:
with:
fetch-depth: 0
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 8
run_install: false
- name: Setup NodeJS
uses: actions/setup-node@v3
if: startsWith(matrix.os, 'macOS') != true
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'
- name: Setup NodeJS for macOS
if: startsWith(matrix.os, 'macOS')
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
run: pnpm install --frozen-lockfile
- name: Get version
run: |
@ -117,7 +134,7 @@ jobs:
if: ${{ env.VERSION_HASH == '' }}
uses: irongut/EditRelease@v1.2.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
token: ${{ secrets.GH_TOKEN }}
id: ${{ steps.get_draft_release.outputs.id }}
draft: false
prerelease: false
@ -129,10 +146,12 @@ jobs:
Thanks to all contributors! 🏅
(Note for Windows: `YouTube-Music-Web-Setup-${{ env.VERSION_TAG }}.exe` is an installer, and `YouTube-Music-${{ env.VERSION_TAG }}.exe` is a portable version)
- name: Update changelog
if: ${{ env.VERSION_HASH == '' }}
run: |
npm run changelog
pnpm changelog
- name: Commit changelog
if: ${{ env.VERSION_HASH == '' }}

View File

@ -17,4 +17,4 @@ jobs:
- name: "Checkout Repository"
uses: actions/checkout@v4
- name: "Dependency Review"
uses: actions/dependency-review-action@v3
uses: actions/dependency-review-action@v4

20
.github/workflows/winget-cla.yml vendored Normal file
View File

@ -0,0 +1,20 @@
name: Submit CLA to Winget PR
on:
workflow_dispatch:
inputs:
pr_url:
description: "Specific PR URL"
required: true
type: string
jobs:
comment:
name: Comment to PR
runs-on: ubuntu-latest
steps:
- name: Submit CLA to Windows Package Manager Community Repository Pull Request
run: gh pr comment $PR_URL --body "@microsoft-github-policy-service agree"
env:
GITHUB_TOKEN: ${{ secrets.WINGET_ACC_TOKEN }}
PR_URL: ${{ inputs.pr_url }}

View File

@ -15,12 +15,16 @@ jobs:
name: Publish winget package
runs-on: ubuntu-latest
steps:
- name: Set winget version env
env:
TAG_NAME: ${{ inputs.tag_name || github.event.release.tag_name }}
run: echo "WINGET_TAG_NAME=$(echo ${TAG_NAME#v})" >> $GITHUB_ENV
- name: Submit package to Windows Package Manager Community Repository
uses: vedantmgoyal2009/winget-releaser@v2
with:
identifier: th-ch.YouTubeMusic
installers-regex: '^YouTube-Music-Setup-[\d\.]+\.exe$'
version: ${{ inputs.tag_name || github.event.release.tag_name }}
installers-regex: '^YouTube-Music-Web-Setup-[\d\.]+\.exe$'
version: ${{ env.WINGET_TAG_NAME }}
release-tag: ${{ inputs.tag_name || github.event.release.tag_name }}
token: ${{ secrets.WINGET_ACC_TOKEN }}
fork-user: youtube-music-winget

1
.gitignore vendored
View File

@ -12,3 +12,4 @@ electron-builder.yml
!.yarn/releases
!.yarn/sdks
!.yarn/versions
.vite-inspect

5
.prettierrc Normal file
View File

@ -0,0 +1,5 @@
{
"tabWidth": 2,
"useTabs": false,
"singleQuote": true
}

View File

@ -1,31 +1,173 @@
# YouTube Music
<div align="center">
# YouTube Music
[![GitHub release](https://img.shields.io/github/release/th-ch/youtube-music.svg?style=for-the-badge&logo=youtube-music)](https://github.com/th-ch/youtube-music/releases/)
[![GitHub license](https://img.shields.io/github/license/th-ch/youtube-music.svg?style=for-the-badge)](https://github.com/th-ch/youtube-music/blob/master/LICENSE)
[![eslint code style](https://img.shields.io/badge/code_style-eslint-5ed9c7.svg?style=for-the-badge)](https://github.com/th-ch/youtube-music/blob/master/.eslintrc.js)
[![Build status](https://img.shields.io/github/actions/workflow/status/th-ch/youtube-music/build.yml?branch=master&style=for-the-badge&logo=youtube-music)](https://GitHub.com/th-ch/youtube-music/releases/)
[![Known Vulnerabilities](https://img.shields.io/snyk/vulnerabilities/github/th-ch/youtube-music?style=for-the-badge)](https://snyk.io/test/github/th-ch/youtube-music)
[![GitHub All Releases](https://img.shields.io/github/downloads/th-ch/youtube-music/total?style=for-the-badge&logo=youtube-music)](https://GitHub.com/th-ch/youtube-music/releases/)
[![AUR](https://img.shields.io/aur/version/youtube-music-bin?color=blueviolet&style=for-the-badge&logo=youtube-music)](https://aur.archlinux.org/packages/youtube-music-bin)
[![Known Vulnerabilities](https://snyk.io/test/github/th-ch/youtube-music/badge.svg)](https://snyk.io/test/github/th-ch/youtube-music)
</div>
![Screenshot](web/screenshot.jpg "Screenshot")
<div align="center">
<a href="https://github.com/th-ch/youtube-music/releases/latest">
<img src="web/youtube-music.svg" width="400" height="100">
<img src="web/youtube-music.svg" width="400" height="100" alt="YouTube Music SVG">
</a>
</div>
Read this in other languages: [🇰🇷](./docs/readme/README-ko.md), [🇮🇸](./docs/readme/README-is.md)
**Electron wrapper around YouTube Music featuring:**
- Native look & feel, aims at keeping the original interface
- Framework for custom plugins: change YouTube Music to your needs (style, content, features), enable/disable plugins in
one click
## Demo Image
| Player Screen (album color theme & ambient light) |
|:---------------------------------------------------------------------------------------------------------:|
|![Screenshot1](https://github.com/th-ch/youtube-music/assets/16558115/53efdf73-b8fa-4d7b-a235-b96b91ea77fc)|
## Content
- [Features](#features)
- [Available plugins](#available-plugins)
- [Translation](#translation)
- [Download](#download)
- [Arch Linux](#arch-linux)
- [MacOS](#macos)
- [Windows](#windows)
- [How to install without a network connection? (in Windows)](#how-to-install-without-a-network-connection-in-windows)
- [Themes](#themes)
- [Dev](#dev)
- [Build your own plugins](#build-your-own-plugins)
- [Creating a plugin](#creating-a-plugin)
- [Common use cases](#common-use-cases)
- [Build](#build)
- [Production Preview](#production-preview)
- [Tests](#tests)
- [License](#license)
- [FAQ](#faq)
## Features:
- **Auto confirm when paused** (Always Enabled): disable
the ["Continue Watching?"](https://user-images.githubusercontent.com/61631665/129977894-01c60740-7ec6-4bf0-9a2c-25da24491b0e.png)
popup that pause music after a certain time
- And more ...
## Available plugins:
- **Ad Blocker**: Block all ads and tracking out of the box
- **Album Actions**: Adds Undislike, Dislike, Like, and Unlike buttons to apply this to all songs in a playlist or album
- **Album Color Theme**: Applies a dynamic theme and visual effects based on the album color palette
- **Ambient Mode**: Applies a lighting effect by casting gentle colors from the video, into your screens background
- **Audio Compressor**: Apply compression to audio (lowers the volume of the loudest parts of the signal and raises the
volume of the softest parts)
- **Blur Navigation Bar**: makes navigation bar transparent and blurry
- **Bypass Age Restrictions**: bypass YouTube's age verification
- **Captions Selector**: Enable captions
- **Compact Sidebar**: Always set the sidebar in compact mode
- **Crossfade**: Crossfade between songs
- **Disable Autoplay**: Makes every song start in "paused" mode
- **[Discord](https://discord.com/) Rich Presence**: Show your friends what you listen to
with [Rich Presence](https://user-images.githubusercontent.com/28219076/104362104-a7a0b980-5513-11eb-9744-bb89eabe0016.png)
- **Downloader**: downloads
MP3 [directly from the interface](https://user-images.githubusercontent.com/61631665/129977677-83a7d067-c192-45e1-98ae-b5a4927393be.png) [(youtube-dl)](https://github.com/ytdl-org/youtube-dl)
- **Exponential Volume**: Makes the volume
slider [exponential](https://greasyfork.org/en/scripts/397686-youtube-music-fix-volume-ratio/) so it's easier to
select lower volumes
- **In-App Menu**: [gives bars a fancy, dark look](https://user-images.githubusercontent.com/78568641/112215894-923dbf00-8c29-11eb-95c3-3ce15db27eca.png)
> (see [this post](https://github.com/th-ch/youtube-music/issues/410#issuecomment-952060709) if you have problem
accessing the menu after enabling this plugin and hide-menu option)
- **Scrobbler**: Adds scrobbling support for [Last.fm](https://www.last.fm/) and [ListenBrainz](https://listenbrainz.org/)
- **Lumia Stream**: Adds [Lumia Stream](https://lumiastream.com/) support
- **Lyrics Genius**: Adds lyrics support for most songs
- **Music Together**: Share a playlist with others. When the host plays a song, everyone else will hear the same song
- **Navigation**: Next/Back navigation arrows directly integrated in the interface, like in your favorite browser
- **No Google Login**: Remove Google login buttons and links from the interface
- **Notifications**: Display a notification when a song starts
playing ([interactive notifications](https://user-images.githubusercontent.com/78568641/114102651-63ce0e00-98d0-11eb-9dfe-c5a02bb54f9c.png)
are available on windows)
- **Picture-in-picture**: allows to switch the app to picture-in-picture mode
- **Playback Speed**: Listen fast, listen
slow! [Adds a slider that controls song speed](https://user-images.githubusercontent.com/61631665/129976003-e55db5ba-bf42-448c-a059-26a009775e68.png)
- **Precise Volume**: Control the volume precisely using mousewheel/hotkeys, with a custom hud and customizable volume
steps
- **Shortcuts (& MPRIS)**: Allows setting global hotkeys for playback (play/pause/next/previous) +
disable [media osd](https://user-images.githubusercontent.com/84923831/128601225-afa38c1f-dea8-4209-9f72-0f84c1dd8b54.png)
by overriding media keys + enable Ctrl/CMD + F to search + enable linux mpris support for
mediakeys + [custom hotkeys](https://github.com/Araxeus/youtube-music/blob/1e591d6a3df98449bcda6e63baab249b28026148/providers/song-controls.js#L13-L50)
for [advanced users](https://github.com/th-ch/youtube-music/issues/106#issuecomment-952156902)
- **Skip Disliked Song**: Skips disliked songs
- **Skip Silences**: Automatically skip silenced sections
- [**SponsorBlock**](https://github.com/ajayyy/SponsorBlock): Automatically Skips non-music parts like intro/outro or
parts of music videos where the song isn't playing
- **Taskbar Media Control**: Control playback from
your [Windows taskbar](https://user-images.githubusercontent.com/78568641/111916130-24a35e80-8a82-11eb-80c8-5021c1aa27f4.png)
- **TouchBar**: Custom TouchBar layout for macOS
- **Tuna OBS**: Integration with [OBS](https://obsproject.com/)'s
plugin [Tuna](https://obsproject.com/forum/resources/tuna.843/)
- **Video Quality Changer**: Allows changing the video quality with
a [button](https://user-images.githubusercontent.com/78568641/138574366-70324a5e-2d64-4f6a-acdd-dc2a2b9cecc5.png) on
the video overlay
- **Video Toggle**: Adds
a [button](https://user-images.githubusercontent.com/28893833/173663950-63e6610e-a532-49b7-9afa-54cb57ddfc15.png) to
switch between Video/Song mode. can also optionally remove the whole video tab
- **Visualizer**: Different music visualizers
## Translation
You can help with translation on [Hosted Weblate](https://hosted.weblate.org/projects/youtube-music/).
<a href="https://hosted.weblate.org/engage/youtube-music/">
<img src="https://hosted.weblate.org/widget/youtube-music/i18n/multi-auto.svg" alt="translation status" />
<img src="https://hosted.weblate.org/widget/youtube-music/i18n/287x66-black.png" alt="translation status 2" />
</a>
## Download
You can check out the [latest release](https://github.com/th-ch/youtube-music/releases/latest) to quickly find the
@ -33,12 +175,18 @@ latest version.
### Arch Linux
Install the `youtube-music-bin` package from the AUR. For AUR installation instructions, take a look at
Install the [`youtube-music-bin`](https://aur.archlinux.org/packages/youtube-music-bin) package from the AUR. For AUR installation instructions, take a look at
this [wiki page](https://wiki.archlinux.org/index.php/Arch_User_Repository#Installing_packages).
### MacOS
If you get an error "is damaged and cant be opened." when launching the app, run the following in the Terminal:
You can install the app using Homebrew (see the [cask definition](https://github.com/th-ch/homebrew-youtube-music)):
```bash
brew install th-ch/youtube-music/youtube-music
```
If you install the app manually and get an error "is damaged and cant be opened." when launching the app, run the following in the Terminal:
```bash
xattr -cr /Applications/YouTube\ Music.app
@ -75,99 +223,6 @@ winget install th-ch.YouTubeMusic
- Place them in the **same directory**.
- Run the installer.
## Available plugins:
- **Ad Blocker**: Block all ads and tracking out of the box
- **Audio Compressor**: Apply compression to audio (lowers the volume of the loudest parts of the signal and raises the
volume of the softest parts)
- **Blur Nav Bar**: makes navigation bar transparent and blurry
- **Bypass age restrictions**: bypass YouTube's age verification
- **Captions selector**: Enable captions
- **Compact sidebar**: Always set the sidebar in compact mode
- **Crossfade**: Crossfade between songs
- **Disable Autoplay**: Makes every song start in "paused" mode
- [**Discord**](https://discord.com/): Show your friends what you listen to
with [Rich Presence](https://user-images.githubusercontent.com/28219076/104362104-a7a0b980-5513-11eb-9744-bb89eabe0016.png)
- **Downloader**: downloads
MP3 [directly from the interface](https://user-images.githubusercontent.com/61631665/129977677-83a7d067-c192-45e1-98ae-b5a4927393be.png) [(youtube-dl)](https://github.com/ytdl-org/youtube-dl)
- **Exponential Volume**: Makes the volume
slider [exponential](https://greasyfork.org/en/scripts/397686-youtube-music-fix-volume-ratio/) so it's easier to
select lower volumes.
- **In-App Menu
**: [gives bars a fancy, dark look](https://user-images.githubusercontent.com/78568641/112215894-923dbf00-8c29-11eb-95c3-3ce15db27eca.png)
> (see [this post](https://github.com/th-ch/youtube-music/issues/410#issuecomment-952060709) if you have problem
accessing the menu after enabling this plugin and hide-menu option)
- [**Last.fm**](https://www.last.fm/): Scrobbles support
- **Lyrics Genius**: Adds lyrics support for most songs
- **Navigation**: Next/Back navigation arrows directly integrated in the interface, like in your favorite browser
- **No Google Login**: Remove Google login buttons and links from the interface
- **Notifications**: Display a notification when a song starts
playing ([interactive notifications](https://user-images.githubusercontent.com/78568641/114102651-63ce0e00-98d0-11eb-9dfe-c5a02bb54f9c.png)
are available on windows)
- **Picture in picture**: allows to switch the app to picture-in-picture mode
- **Playback Speed**: Listen fast, listen
slow! [Adds a slider that controls song speed](https://user-images.githubusercontent.com/61631665/129976003-e55db5ba-bf42-448c-a059-26a009775e68.png)
- **Precise Volume**: Control the volume precisely using mousewheel/hotkeys, with a custom hud and customizable volume
steps
- **Quality Changer**: Allows changing the video quality with
a [button](https://user-images.githubusercontent.com/78568641/138574366-70324a5e-2d64-4f6a-acdd-dc2a2b9cecc5.png) on
the video overlay
- **Shortcuts**: Allows setting global hotkeys for playback (play/pause/next/previous) +
disable [media osd](https://user-images.githubusercontent.com/84923831/128601225-afa38c1f-dea8-4209-9f72-0f84c1dd8b54.png)
by overriding media keys + enable Ctrl/CMD + F to search + enable linux mpris support for
mediakeys + [custom hotkeys](https://github.com/Araxeus/youtube-music/blob/1e591d6a3df98449bcda6e63baab249b28026148/providers/song-controls.js#L13-L50)
for [advanced users](https://github.com/th-ch/youtube-music/issues/106#issuecomment-952156902)
- **Skip-Silences** - Automatically skip silenced sections
- [**SponsorBlock**](https://github.com/ajayyy/SponsorBlock): Automatically Skips non-music parts like intro/outro or
parts of music videos where the song isn't playing
- **Taskbar Media Control**: Control playback from
your [Windows taskbar](https://user-images.githubusercontent.com/78568641/111916130-24a35e80-8a82-11eb-80c8-5021c1aa27f4.png)
- **Touchbar**: Custom TouchBar layout for macOS
- **Tuna-OBS**: Integration with [OBS](https://obsproject.com/)'s
plugin [Tuna](https://obsproject.com/forum/resources/tuna.843/)
- **Video Toggle**: Adds
a [button](https://user-images.githubusercontent.com/28893833/173663950-63e6610e-a532-49b7-9afa-54cb57ddfc15.png) to
switch between Video/Song mode. can also optionally remove the whole video tab
- **Visualizer**: Different music visualizers
---
- **Auto confirm when paused** (Always Enabled): disable
the ["Continue Watching?"](https://user-images.githubusercontent.com/61631665/129977894-01c60740-7ec6-4bf0-9a2c-25da24491b0e.png)
popup that pause music after a certain time
> If `Hide Menu` option is on - you can show the menu with the <kbd>alt</kbd> key (or <kbd>\`</kbd> [backtick] if using
> the in-app-menu plugin)
## Themes
You can load CSS files to change the look of the application (Options > Visual Tweaks > Themes).
@ -179,8 +234,8 @@ Some predefined themes are available in https://github.com/kerichdev/themes-for-
```bash
git clone https://github.com/th-ch/youtube-music
cd youtube-music
npm
npm run start
pnpm install --frozen-lockfile
pnpm dev
```
## Build your own plugins
@ -192,49 +247,72 @@ Using plugins, you can:
### Creating a plugin
Create a folder in `plugins/YOUR-PLUGIN-NAME`:
- if you need to manipulate the BrowserWindow, create a file with the following template:
Create a folder in `src/plugins/YOUR-PLUGIN-NAME`:
- `index.ts`: the main file of the plugin
```typescript
// file: back.ts
export default (win: Electron.BrowserWindow, config: ConfigType<'YOUR-PLUGIN-NAME'>) => {
// something
};
```
import style from './style.css?inline'; // import style as inline
then, register the plugin in `index.ts`:
import { createPlugin } from '@/utils';
```typescript
import yourPlugin from './plugins/YOUR-PLUGIN-NAME/back';
export default createPlugin({
name: 'Plugin Label',
restartNeeded: true, // if value is true, ytmusic show restart dialog
config: {
enabled: false,
}, // your custom config
stylesheets: [style], // your custom style,
menu: async ({ getConfig, setConfig }) => {
// All *Config methods are wrapped Promise<T>
const config = await getConfig();
return [
{
label: 'menu',
submenu: [1, 2, 3].map((value) => ({
label: `value ${value}`,
type: 'radio',
checked: config.value === value,
click() {
setConfig({ value });
},
})),
},
];
},
backend: {
start({ window, ipc }) {
window.maximize();
// ...
const mainPlugins = {
// ...
'YOUR-PLUGIN-NAME': yourPlugin,
};
```
- if you need to change the front, create a file with the following template:
```typescript
// file: front.ts
export default (config: ConfigType<'YOUR-PLUGIN-NAME'>) => {
// This function will be called as a preload script
// So you can use front features like `document.querySelector`
};
```
then, register the plugin in `preload.ts`:
```typescript
import yourPlugin from './plugins/YOUR-PLUGIN-NAME/front';
const rendererPlugins: PluginMapper<'renderer'> = {
// ...
'YOUR-PLUGIN-NAME': yourPlugin,
};
// you can communicate with renderer plugin
ipc.handle('some-event', () => {
return 'hello';
});
},
// it fired when config changed
onConfigChange(newConfig) { /* ... */ },
// it fired when plugin disabled
stop(context) { /* ... */ },
},
renderer: {
async start(context) {
console.log(await context.ipc.invoke('some-event'));
},
// Only renderer available hook
onPlayerApiReady(api: YoutubePlayer, context: RendererContext) {
// set plugin config easily
context.setConfig({ myConfig: api.getVolume() });
},
onConfigChange(newConfig) { /* ... */ },
stop(_context) { /* ... */ },
},
preload: {
async start({ getConfig }) {
const config = await getConfig();
},
onConfigChange(newConfig) {},
stop(_context) {},
},
});
```
### Common use cases
@ -242,45 +320,67 @@ const rendererPlugins: PluginMapper<'renderer'> = {
- injecting custom CSS: create a `style.css` file in the same folder then:
```typescript
import path from 'node:path';
import { injectCSS } from '../utils';
// index.ts
import style from './style.css?inline'; // import style as inline
// back.ts
export default (win: Electron.BrowserWindow) => {
injectCSS(win.webContents, path.join(__dirname, 'style.css'));
};
import { createPlugin } from '@/utils';
export default createPlugin({
name: 'Plugin Label',
restartNeeded: true, // if value is true, ytmusic will show a restart dialog
config: {
enabled: false,
}, // your custom config
stylesheets: [style], // your custom style
renderer() {} // define renderer hook
});
```
- changing the HTML:
- If you want to change the HTML:
```typescript
// front.ts
export default () => {
// Remove the login button
document.querySelector(".sign-in-link.ytmusic-nav-bar").remove();
};
import { createPlugin } from '@/utils';
export default createPlugin({
name: 'Plugin Label',
restartNeeded: true, // if value is true, ytmusic will show the restart dialog
config: {
enabled: false,
}, // your custom config
renderer() {
// Remove the login button
document.querySelector(".sign-in-link.ytmusic-nav-bar").remove();
} // define renderer hook
});
```
- communicating between the front and back: can be done using the ipcMain module from electron. See `utils.js` file and
example in `navigation` plugin.
- communicating between the front and back: can be done using the ipcMain module from electron. See `index.ts` file and
example in `sponsorblock` plugin.
## Build
1. Clone the repo
2. Run `npm i` to install dependencies
3. Run `npm run build:OS`
2. Follow [this guide](https://pnpm.io/installation) to install `pnpm`
3. Run `pnpm install --frozen-lockfile` to install dependencies
4. Run `pnpm build:OS`
- `npm run build:win` - Windows
- `npm run build:linux` - Linux
- `npm run build:mac` - MacOS
- `pnpm dist:win` - Windows
- `pnpm dist:linux` - Linux
- `pnpm dist:mac` - MacOS
Builds the app for macOS, Linux, and Windows,
using [electron-builder](https://github.com/electron-userland/electron-builder).
## Production Preview
```bash
pnpm start
```
## Tests
```bash
npm run test
pnpm test
```
Uses [Playwright](https://playwright.dev/) to test the app.
@ -288,3 +388,10 @@ Uses [Playwright](https://playwright.dev/) to test the app.
## License
MIT © [th-ch](https://github.com/th-ch/youtube-music)
## FAQ
### Why apps menu isn't showing up?
If `Hide Menu` option is on - you can show the menu with the <kbd>alt</kbd> key (or <kbd>\`</kbd> [backtick] if using
the in-app-menu plugin)

50
assets/error.html Normal file
View File

@ -0,0 +1,50 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Cannot load YouTube Music</title>
<style>
body {
background: #000;
}
.container {
margin: 0;
font-family: Roboto, Arial, sans-serif;
font-size: 20px;
font-weight: 500;
color: rgba(255, 255, 255, 0.5);
position: absolute;
top: 50%;
left: 50%;
margin-right: -50%;
transform: translate(-50%, -50%);
text-align: center;
}
.button {
background: #065fd4;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: white;
font: inherit;
text-transform: uppercase;
text-decoration: none;
border-radius: 2px;
font-size: 16px;
font-weight: normal;
text-align: center;
padding: 8px 22px;
display: inline-block;
}
</style>
</head>
<body>
<div class="container">
<p>Cannot load YouTube Music… Internet disconnected?</p>
<a class="button" href="#" onclick="reload()">Retry</a>
</div>
</body>
</html>

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -2,8 +2,433 @@
All notable changes to this project will be documented in this file. Dates are displayed in UTC.
#### [v3.3.5](https://github.com/th-ch/youtube-music/compare/v3.3.4...v3.3.5)
- chore(deps): update dependency node-gyp to v10.1.0 [`#1885`](https://github.com/th-ch/youtube-music/pull/1885)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v7.4.0 [`#1886`](https://github.com/th-ch/youtube-music/pull/1886)
- chore(deps): update dependency vite to v5.2.6 [`#1883`](https://github.com/th-ch/youtube-music/pull/1883)
- fix(style): resolve #1887 [`#1887`](https://github.com/th-ch/youtube-music/issues/1887)
- chore(i18n): Translated using Weblate (Swedish) [`69087bb`](https://github.com/th-ch/youtube-music/commit/69087bbf1fac1ba58e992146deb1d6f1706b1e3c)
- chore(i18n): Translated using Weblate (French) [`af78f15`](https://github.com/th-ch/youtube-music/commit/af78f1596ab8db2fa7069fdb1c4f078099ce4446)
- Update changelog for v3.3.4 [`62f7d44`](https://github.com/th-ch/youtube-music/commit/62f7d440fab5bdbe9f49a3a5f8c32e7aaf2f28f6)
#### [v3.3.4](https://github.com/th-ch/youtube-music/compare/v3.3.3...v3.3.4)
> 24 March 2024
- Update changelog for v3.3.3 [`9769544`](https://github.com/th-ch/youtube-music/commit/97695444affbacb71dd73ae7107d4c987e285a37)
- fix(style): fix fullscreen style and in-app-menu [`ed700c2`](https://github.com/th-ch/youtube-music/commit/ed700c2916cc7e6ccd2010d0c552364af116eb4f)
- fix(style): fix miniplayer style [`a8bc539`](https://github.com/th-ch/youtube-music/commit/a8bc53912d1f4137008ecb2d9d5d9d9eb06ee2a8)
#### [v3.3.3](https://github.com/th-ch/youtube-music/compare/v3.3.2...v3.3.3)
> 24 March 2024
- chore(deps): update dependency electron to v29.1.5 [`#1876`](https://github.com/th-ch/youtube-music/pull/1876)
- chore(deps): update dependency typescript to v5.4.3 [`#1877`](https://github.com/th-ch/youtube-music/pull/1877)
- chore(deps): update dependency discord-api-types to v0.37.76 [`#1878`](https://github.com/th-ch/youtube-music/pull/1878)
- chore(deps): update dependency vite to v5.2.4 [`#1881`](https://github.com/th-ch/youtube-music/pull/1881)
- Ambient Plugin cleanup [`#1880`](https://github.com/th-ch/youtube-music/pull/1880)
- chore(deps): update dependency vite to v5.2.2 [`#1875`](https://github.com/th-ch/youtube-music/pull/1875)
- fix(deps): update dependency solid-js to v1.8.16 [`#1873`](https://github.com/th-ch/youtube-music/pull/1873)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v7.3.1 [`#1868`](https://github.com/th-ch/youtube-music/pull/1868)
- chore(deps): update dependency discord-api-types to v0.37.75 [`#1867`](https://github.com/th-ch/youtube-music/pull/1867)
- chore(deps): update pnpm to v8.15.5 [`#1865`](https://github.com/th-ch/youtube-music/pull/1865)
- fix: Fix Miniplayer image size [`#1863`](https://github.com/th-ch/youtube-music/pull/1863)
- fix(style): fixed image/video alignment when toggle is active [`#1862`](https://github.com/th-ch/youtube-music/pull/1862)
- chore: Update README-is.md [`#1858`](https://github.com/th-ch/youtube-music/pull/1858)
- chore(deps): update dependency vite-plugin-solid to v2.10.2 [`#1859`](https://github.com/th-ch/youtube-music/pull/1859)
- fix: Ambient Mode intialization improvement [`#1857`](https://github.com/th-ch/youtube-music/pull/1857)
- chore(deps): bump follow-redirects from 1.15.5 to 1.15.6 [`#1856`](https://github.com/th-ch/youtube-music/pull/1856)
- chore(README): Nicer Readme 2.0 [`#1833`](https://github.com/th-ch/youtube-music/pull/1833)
- chore(deps): update dependency discord-api-types to v0.37.74 [`#1854`](https://github.com/th-ch/youtube-music/pull/1854)
- chore(deps): update dependency esbuild to v0.20.2 [`#1855`](https://github.com/th-ch/youtube-music/pull/1855)
- Improve ambient mode [`#1853`](https://github.com/th-ch/youtube-music/pull/1853)
- chore(deps): update dependency electron to v29.1.4 [`#1852`](https://github.com/th-ch/youtube-music/pull/1852)
- chore(deps): update dependency electron to v29.1.3 [`#1851`](https://github.com/th-ch/youtube-music/pull/1851)
- chore(deps): update dependency rollup to v4.13.0 [`#1850`](https://github.com/th-ch/youtube-music/pull/1850)
- fix(deps): update dependency electron-store to v8.2.0 [`#1843`](https://github.com/th-ch/youtube-music/pull/1843)
- chore(deps): update dependency electron to v29.1.1 [`#1841`](https://github.com/th-ch/youtube-music/pull/1841)
- fix(deps): update dependency i18next to v23.10.1 [`#1842`](https://github.com/th-ch/youtube-music/pull/1842)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v7.2.0 [`#1848`](https://github.com/th-ch/youtube-music/pull/1848)
- chore(deps): update dependency vite to v5.1.6 [`#1847`](https://github.com/th-ch/youtube-music/pull/1847)
- fix(deps): update dependency async-mutex to v0.5.0 [`#1849`](https://github.com/th-ch/youtube-music/pull/1849)
- fix(deps): update dependency ts-morph to v22 [`#1846`](https://github.com/th-ch/youtube-music/pull/1846)
- chore(deps): update dependency discord-api-types to v0.37.73 [`#1840`](https://github.com/th-ch/youtube-music/pull/1840)
- chore(deps): update dependency rollup to v4.12.1 [`#1837`](https://github.com/th-ch/youtube-music/pull/1837)
- chore: Changed a single word (README-is.md) [`#1836`](https://github.com/th-ch/youtube-music/pull/1836)
- chore(deps): update dependency typescript to v5.4.2 [`#1838`](https://github.com/th-ch/youtube-music/pull/1838)
- chore(deps): update dependency electron-vite to v2.1.0 [`#1823`](https://github.com/th-ch/youtube-music/pull/1823)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v7.1.1 [`#1829`](https://github.com/th-ch/youtube-music/pull/1829)
- chore(deps): update dependency vite to v5.1.5 [`#1831`](https://github.com/th-ch/youtube-music/pull/1831)
- Revert "chore(deps): update dependency electron-builder to v24.13.3" [`#1818`](https://github.com/th-ch/youtube-music/pull/1818)
- chore(deps): update dependency electron-builder to v24.13.3 [`#1774`](https://github.com/th-ch/youtube-music/pull/1774)
- chore(deps): update playwright monorepo to v1.42.1 [`#1816`](https://github.com/th-ch/youtube-music/pull/1816)
- fix: Add scale ratio for tray icons [`#1811`](https://github.com/th-ch/youtube-music/pull/1811)
- Icelandic translation of the readme file [`#1806`](https://github.com/th-ch/youtube-music/pull/1806)
- chore(deps): update dependency electron to v29.1.0 [`#1808`](https://github.com/th-ch/youtube-music/pull/1808)
- chore(deps): update playwright monorepo to v1.42.0 [`#1805`](https://github.com/th-ch/youtube-music/pull/1805)
- chore(deps): update dependency eslint to v8.57.0 [`#1793`](https://github.com/th-ch/youtube-music/pull/1793)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v7.1.0 [`#1800`](https://github.com/th-ch/youtube-music/pull/1800)
- chore(deps): update dependency discord-api-types to v0.37.71 [`#1799`](https://github.com/th-ch/youtube-music/pull/1799)
- chore(deps): update pnpm to v8.15.4 [`#1795`](https://github.com/th-ch/youtube-music/pull/1795)
- chore(deps): update dependency @types/semver to v7.5.8 [`#1797`](https://github.com/th-ch/youtube-music/pull/1797)
- fix: center the pause icon [`#1786`](https://github.com/th-ch/youtube-music/pull/1786)
- fix(deps): update dependency @cliqz/adblocker-electron to v1.26.16 [`#1788`](https://github.com/th-ch/youtube-music/pull/1788)
- fix(deps): update dependency @cliqz/adblocker-electron-preload to v1.26.16 [`#1789`](https://github.com/th-ch/youtube-music/pull/1789)
- fix(deps): update dependency youtubei.js to v9.1.0 [`#1790`](https://github.com/th-ch/youtube-music/pull/1790)
- fix(deps): update dependency i18next to v23.10.0 [`#1785`](https://github.com/th-ch/youtube-music/pull/1785)
- chore(deps): update dependency electron to v29 [`#1773`](https://github.com/th-ch/youtube-music/pull/1773)
- chore(deps): update dependency vite to v5.1.4 [`#1778`](https://github.com/th-ch/youtube-music/pull/1778)
- chore(deps): bump ip from 2.0.0 to 2.0.1 [`#1777`](https://github.com/th-ch/youtube-music/pull/1777)
- fix: add support for Wayland [`#1864`](https://github.com/th-ch/youtube-music/issues/1864)
- fix(style): fix navigation bar items are not working [`#1381`](https://github.com/th-ch/youtube-music/issues/1381) [`#1396`](https://github.com/th-ch/youtube-music/issues/1396) [`#1649`](https://github.com/th-ch/youtube-music/issues/1649)
- fix(ytm-bugs): fixed a `scrollbar-color` bug that affected Chromium 121 and later [`#1737`](https://github.com/th-ch/youtube-music/issues/1737)
- chore(i18n): Translated using Weblate (Icelandic) [`82fa871`](https://github.com/th-ch/youtube-music/commit/82fa8719a96abdfaaa8548a0077f4db2164ec09b)
- chore(i18n): Translated using Weblate (Romanian) [`c871506`](https://github.com/th-ch/youtube-music/commit/c871506a69180308ab4fc587b6e8a33f193087e8)
- chore(i18n): Translated using Weblate (Thai) [`a7d0350`](https://github.com/th-ch/youtube-music/commit/a7d035022a229f0b245694d1fc7a484befe1c269)
#### [v3.3.2](https://github.com/th-ch/youtube-music/compare/v3.3.1...v3.3.2)
> 20 February 2024
- fix: fix bugs in MPRIS, and improve MPRIS [`#1760`](https://github.com/th-ch/youtube-music/pull/1760)
- fix(deps): update dependency electron-updater to v6.1.8 [`#1770`](https://github.com/th-ch/youtube-music/pull/1770)
- chore(deps): update dependency electron-builder to v24.12.0 [`#1771`](https://github.com/th-ch/youtube-music/pull/1771)
- feat(scrobblers): use `BrowserWindow` instead of `shell.openExternal` [`#1758`](https://github.com/th-ch/youtube-music/pull/1758)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v7.0.2 [`#1763`](https://github.com/th-ch/youtube-music/pull/1763)
- chore(deps): update dependency esbuild to v0.20.1 [`#1759`](https://github.com/th-ch/youtube-music/pull/1759)
- fix(deps): update dependency i18next to v23.9.0 [`#1754`](https://github.com/th-ch/youtube-music/pull/1754)
- fix: fixed an issue that caused infinite loops when using Music Together [`#1752`](https://github.com/th-ch/youtube-music/issues/1752)
- chore(deps): rollback dependency electron-builder to v24.9.1 [`8bd05f5`](https://github.com/th-ch/youtube-music/commit/8bd05f525df98671f0a516b159cccab302b7ae99)
- chore(deps): update dependency electron-builder to v24.13.1 [`47b23b4`](https://github.com/th-ch/youtube-music/commit/47b23b414c8feb25c4d9a23d6adb7cbf1ac818fb)
- chore(i18n): Translated using Weblate (German) [`47505e9`](https://github.com/th-ch/youtube-music/commit/47505e97482f9e953ee451b968d0950585616ffa)
#### [v3.3.1](https://github.com/th-ch/youtube-music/compare/v3.3.0...v3.3.1)
> 18 February 2024
- Update changelog for v3.3.0 [`6d9bb8e`](https://github.com/th-ch/youtube-music/commit/6d9bb8eb1cc2d892a5552ffb1f7c20859aa80f67)
- hotfix: in-app-menu position issue [`87acf4c`](https://github.com/th-ch/youtube-music/commit/87acf4cf042ba32a000a4aeaec5c17c93501d333)
- release 3.3.1 (HOTFIX) [`a6ed8bf`](https://github.com/th-ch/youtube-music/commit/a6ed8bf3aa20ca8e950e85d88f981ccf9edc7498)
#### [v3.3.0](https://github.com/th-ch/youtube-music/compare/v3.2.2...v3.3.0)
> 18 February 2024
- fix(deps): update dependency i18next to v23.8.3 [`#1751`](https://github.com/th-ch/youtube-music/pull/1751)
- import fixed ./constants [`#1748`](https://github.com/th-ch/youtube-music/pull/1748)
- chore(deps): update dependency rollup to v4.12.0 [`#1743`](https://github.com/th-ch/youtube-music/pull/1743)
- chore(deps): bump undici from 5.28.2 to 5.28.3 [`#1747`](https://github.com/th-ch/youtube-music/pull/1747)
- chore(deps): update dependency vite to v5.1.3 [`#1742`](https://github.com/th-ch/youtube-music/pull/1742)
- chore(deps): update dependency vite-plugin-solid to v2.10.1 [`#1734`](https://github.com/th-ch/youtube-music/pull/1734)
- chore(deps): update dependency discord-api-types to v0.37.70 [`#1740`](https://github.com/th-ch/youtube-music/pull/1740)
- chore(deps): update dependency electron to v28.2.3 [`#1736`](https://github.com/th-ch/youtube-music/pull/1736)
- chore(deps): update pnpm to v8.15.3 [`#1739`](https://github.com/th-ch/youtube-music/pull/1739)
- chore(deps): update dependency rollup to v4.11.0 [`#1738`](https://github.com/th-ch/youtube-music/pull/1738)
- fix(deps): update dependency solid-js to v1.8.15 [`#1735`](https://github.com/th-ch/youtube-music/pull/1735)
- chore(deps): update dependency vite to v5.1.2 [`#1733`](https://github.com/th-ch/youtube-music/pull/1733)
- chore(deps): update dependency vite-plugin-solid to v2.10.0 [`#1732`](https://github.com/th-ch/youtube-music/pull/1732)
- chore(deps): update pnpm to v8.15.2 [`#1729`](https://github.com/th-ch/youtube-music/pull/1729)
- Update Copyright - 2024 [`#1730`](https://github.com/th-ch/youtube-music/pull/1730)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v7 [`#1728`](https://github.com/th-ch/youtube-music/pull/1728)
- fix(deps): update dependency @floating-ui/dom to v1.6.3 [`#1727`](https://github.com/th-ch/youtube-music/pull/1727)
- chore(deps): update dependency electron to v28.2.2 [`#1717`](https://github.com/th-ch/youtube-music/pull/1717)
- chore(deps): update dependency vite to v5.1.1 [`#1718`](https://github.com/th-ch/youtube-music/pull/1718)
- chore(deps): update dependency @types/semver to v7.5.7 [`#1724`](https://github.com/th-ch/youtube-music/pull/1724)
- fix(deps): update dependency @floating-ui/dom to v1.6.2 [`#1725`](https://github.com/th-ch/youtube-music/pull/1725)
- chore(deps): update dependency rollup to v4.10.0 [`#1719`](https://github.com/th-ch/youtube-music/pull/1719)
- fix(deps): update dependency solid-js to v1.8.14 [`#1713`](https://github.com/th-ch/youtube-music/pull/1713)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.21.0 [`#1711`](https://github.com/th-ch/youtube-music/pull/1711)
- fix(deps): update dependency semver to v7.6.0 [`#1712`](https://github.com/th-ch/youtube-music/pull/1712)
- refactor(in-app-menu): refactor `in-app-menu` plugin [`#1710`](https://github.com/th-ch/youtube-music/pull/1710)
- chore(deps): update playwright monorepo to v1.41.2 [`#1706`](https://github.com/th-ch/youtube-music/pull/1706)
- chore(deps): update dependency electron to v29.0.0-beta.5 [`#1707`](https://github.com/th-ch/youtube-music/pull/1707)
- feat(album-color-theme): support album color theme in all pages [`#1685`](https://github.com/th-ch/youtube-music/pull/1685)
- fix(deps): update dependency youtubei.js to v9.0.2 [`#1704`](https://github.com/th-ch/youtube-music/pull/1704)
- fix(deps): update dependency i18next to v23.8.2 [`#1702`](https://github.com/th-ch/youtube-music/pull/1702)
- feat: Support disabling scrobbling for non-music content [`#1665`](https://github.com/th-ch/youtube-music/pull/1665)
- fix(deps): update dependency youtubei.js to v9 [`#1682`](https://github.com/th-ch/youtube-music/pull/1682)
- chore(deps): update dependency electron to v29.0.0-beta.4 [`#1698`](https://github.com/th-ch/youtube-music/pull/1698)
- fix(deps): update dependency i18next to v23.8.1 [`#1694`](https://github.com/th-ch/youtube-music/pull/1694)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.20.0 [`#1700`](https://github.com/th-ch/youtube-music/pull/1700)
- chore(deps): update pnpm to v8.15.1 [`#1699`](https://github.com/th-ch/youtube-music/pull/1699)
- chore(deps): update dependency esbuild to v0.20.0 [`#1691`](https://github.com/th-ch/youtube-music/pull/1691)
- chore(deps): update pnpm to v8.15.0 [`#1692`](https://github.com/th-ch/youtube-music/pull/1692)
- fix(deps): update dependency i18next to v23.7.20 [`#1684`](https://github.com/th-ch/youtube-music/pull/1684)
- chore(deps): update dependency electron to v29.0.0-beta.3 [`#1683`](https://github.com/th-ch/youtube-music/pull/1683)
- chore(deps): update dependency electron to v29.0.0-beta.2 [`#1681`](https://github.com/th-ch/youtube-music/pull/1681)
- chore(deps): update dependency rollup to v4.9.6 [`#1663`](https://github.com/th-ch/youtube-music/pull/1663)
- chore(deps): update dependency electron to v29.0.0-beta.1 [`#1670`](https://github.com/th-ch/youtube-music/pull/1670)
- fix(deps): update dependency i18next to v23.7.19 [`#1680`](https://github.com/th-ch/youtube-music/pull/1680)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.19.1 [`#1669`](https://github.com/th-ch/youtube-music/pull/1669)
- chore(deps): update pnpm to v8.14.3 [`#1668`](https://github.com/th-ch/youtube-music/pull/1668)
- chore(deps): update dependency vite-plugin-inspect to v0.8.3 [`#1672`](https://github.com/th-ch/youtube-music/pull/1672)
- chore(deps): update dependency esbuild to v0.19.12 [`#1673`](https://github.com/th-ch/youtube-music/pull/1673)
- fix(deps): update dependency @electron/remote to v2.1.2 [`#1676`](https://github.com/th-ch/youtube-music/pull/1676)
- chore: Update issue templates [`#1661`](https://github.com/th-ch/youtube-music/pull/1661)
- chore(deps): update playwright monorepo to v1.41.1 [`#1660`](https://github.com/th-ch/youtube-music/pull/1660)
- fix(deps): update dependency i18next to v23.7.18 [`#1662`](https://github.com/th-ch/youtube-music/pull/1662)
- chore(deps): update actions/dependency-review-action action to v4 [`#1654`](https://github.com/th-ch/youtube-music/pull/1654)
- chore(deps): update dependency electron to v29.0.0-alpha.11 [`#1656`](https://github.com/th-ch/youtube-music/pull/1656)
- chore(deps): update dependency vite to v5.0.12 [security] [`#1659`](https://github.com/th-ch/youtube-music/pull/1659)
- fix(deps): update dependency async-mutex to v0.4.1 [`#1653`](https://github.com/th-ch/youtube-music/pull/1653)
- chore(deps): update playwright monorepo to v1.41.0 [`#1651`](https://github.com/th-ch/youtube-music/pull/1651)
- feat: Better Scrobbler Plugin [`#1640`](https://github.com/th-ch/youtube-music/pull/1640)
- chore(deps): update dependency electron to v29.0.0-alpha.10 [`#1645`](https://github.com/th-ch/youtube-music/pull/1645)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.19.0 [`#1643`](https://github.com/th-ch/youtube-music/pull/1643)
- chore(README): Fix plugins names and add plugins in/to Readme (in menu too) [`#1624`](https://github.com/th-ch/youtube-music/pull/1624)
- fix(album-actions): Fixed album actions [`#1639`](https://github.com/th-ch/youtube-music/pull/1639)
- chore(deps): update playwright monorepo to v1.41.0-beta-1705101589000 [`#1638`](https://github.com/th-ch/youtube-music/pull/1638)
- fix(#1543): fix song control doesn't work [`#1637`](https://github.com/th-ch/youtube-music/pull/1637)
- chore(deps): update playwright monorepo to v1.41.0-beta-1705092460000 [`#1635`](https://github.com/th-ch/youtube-music/pull/1635)
- chore(deps): update dependency rollup to v4.9.5 [`#1629`](https://github.com/th-ch/youtube-music/pull/1629)
- chore(deps): update dependency electron to v29.0.0-alpha.9 [`#1627`](https://github.com/th-ch/youtube-music/pull/1627)
- chore(deps): update dependency electron to v29.0.0-alpha.8 [`#1608`](https://github.com/th-ch/youtube-music/pull/1608)
- fix(deps): update dependency @cliqz/adblocker-electron to v1.26.15 [`#1615`](https://github.com/th-ch/youtube-music/pull/1615)
- chore(deps): update dependency rollup to v4.9.4 [`#1591`](https://github.com/th-ch/youtube-music/pull/1591)
- fix(deps): update dependency @cliqz/adblocker-electron-preload to v1.26.15 [`#1616`](https://github.com/th-ch/youtube-music/pull/1616)
- chore(deps): update pnpm to v8.14.1 [`#1619`](https://github.com/th-ch/youtube-music/pull/1619)
- chore(deps): update dependency eslint-plugin-prettier to v5.1.3 [`#1618`](https://github.com/th-ch/youtube-music/pull/1618)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.18.1 [`#1612`](https://github.com/th-ch/youtube-music/pull/1612)
- fix(deps): update dependency youtubei.js to v8.2.0 [`#1614`](https://github.com/th-ch/youtube-music/pull/1614)
- chore(deps): update dependency electron-vite to v2.0.0 [`#1609`](https://github.com/th-ch/youtube-music/pull/1609)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.18.0 [`#1603`](https://github.com/th-ch/youtube-music/pull/1603)
- chore(deps): update dependency electron-vite to v2.0.0-beta.4 [`#1602`](https://github.com/th-ch/youtube-music/pull/1602)
- fix: fix upgrade button [`#1199`](https://github.com/th-ch/youtube-music/issues/1199)
- fix(mpris): fix mpris invalid position [`#1726`](https://github.com/th-ch/youtube-music/issues/1726)
- fix: discord RPC (fix #1664) [`#1664`](https://github.com/th-ch/youtube-music/issues/1664)
- fix: remove sign-in button (fix #1199) [`#1199`](https://github.com/th-ch/youtube-music/issues/1199)
- Fix #1617 [`#1617`](https://github.com/th-ch/youtube-music/issues/1617)
- fix(crossfade): fix #1633 [`#1633`](https://github.com/th-ch/youtube-music/issues/1633)
- fix: fix #1621 [`#1621`](https://github.com/th-ch/youtube-music/issues/1621)
- fix(tuna-obs): partially fix #1596 [`#1596`](https://github.com/th-ch/youtube-music/issues/1596)
- fix(discord): fix hide duration button [`#1644`](https://github.com/th-ch/youtube-music/issues/1644)
- fix(in-app-menu): fix invalid `margin-top` [`#1597`](https://github.com/th-ch/youtube-music/issues/1597)
- fix(README): fix `plugins` path [`#1598`](https://github.com/th-ch/youtube-music/issues/1598)
- chore(i18n): Translated using Weblate (Vietnamese) [`0528637`](https://github.com/th-ch/youtube-music/commit/05286371353e8b4c36a5b9fe9011ae5dfdc7ee82)
- chore: update pnpm-lock [`fd8d59b`](https://github.com/th-ch/youtube-music/commit/fd8d59bada56dab4e156d22394fe0c5efec5abc4)
- fix(in-app-menu): fix app crash in production [`febc63e`](https://github.com/th-ch/youtube-music/commit/febc63edef375bd82db48b7fb460ec5a601ab872)
#### [v3.2.2](https://github.com/th-ch/youtube-music/compare/v3.2.1...v3.2.2)
> 5 January 2024
- feat(tray): Add song info and paused icon [`#1592`](https://github.com/th-ch/youtube-music/pull/1592)
- fix(skip-silences): fix audio distorted [`#1141`](https://github.com/th-ch/youtube-music/issues/1141)
- chore(deps): update dependency rollup to v4.9.3 [`0c3c380`](https://github.com/th-ch/youtube-music/commit/0c3c3805918adf2a185a7f1dc67ea3af8135863d)
- chore(i18n): Translated using Weblate (Turkish) [`64ea1fd`](https://github.com/th-ch/youtube-music/commit/64ea1fdb58fdf2766ae3284ac1a51bfac8894b36)
- fix(music-together): typing [`895386f`](https://github.com/th-ch/youtube-music/commit/895386f6f8c649f77ea15c88f6fb7ecc5b775554)
#### [v3.2.1](https://github.com/th-ch/youtube-music/compare/v3.2.0...v3.2.1)
> 1 January 2024
- fix: fix #1574 [`#1574`](https://github.com/th-ch/youtube-music/issues/1574)
- fix: fix #1575 [`#1575`](https://github.com/th-ch/youtube-music/issues/1575)
- chore(i18n): Translated using Weblate [`f5aa179`](https://github.com/th-ch/youtube-music/commit/f5aa179cd639eb4b8f70f1264b5b459ebcc16695)
- chore(i18n): Translated using Weblate (English) [`e409165`](https://github.com/th-ch/youtube-music/commit/e409165e1bed85f3d1aea3a565e7b9e462b1e05b)
- chore(i18n): Translated using Weblate (Czech) [`0ca4e34`](https://github.com/th-ch/youtube-music/commit/0ca4e34efd86e877314e5a245f266065b4cf0013)
#### [v3.2.0](https://github.com/th-ch/youtube-music/compare/v3.1.1...v3.2.0)
> 1 January 2024
- feat(album-color-theme): improve `Album Color Theme` style [`#1571`](https://github.com/th-ch/youtube-music/pull/1571)
- feat(menu): add more detail in Menu [`#1570`](https://github.com/th-ch/youtube-music/pull/1570)
- feat(music-together): Add new plugin `Music Together` [`#1562`](https://github.com/th-ch/youtube-music/pull/1562)
- chore(deps): update dependency rollup to v4.9.2 [`#1567`](https://github.com/th-ch/youtube-music/pull/1567)
- fix(deps): update dependency i18next to v23.7.13 [`#1569`](https://github.com/th-ch/youtube-music/pull/1569)
- feat: Add new plugin `Album actions` [`#1515`](https://github.com/th-ch/youtube-music/pull/1515)
- fix(deps): update dependency i18next to v23.7.12 [`#1564`](https://github.com/th-ch/youtube-music/pull/1564)
- fix: Only apply scale factor on Windows [`#1565`](https://github.com/th-ch/youtube-music/pull/1565)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.16.0 [`#1556`](https://github.com/th-ch/youtube-music/pull/1556)
- chore(deps): update pnpm to v8.13.1 [`#1557`](https://github.com/th-ch/youtube-music/pull/1557)
- chore(deps): update dependency ws to v8.16.0 [`#1559`](https://github.com/th-ch/youtube-music/pull/1559)
- fix(deps): update dependency youtubei.js to v8.1.0 [`#1560`](https://github.com/th-ch/youtube-music/pull/1560)
- fix(deps): update dependency node-html-parser to v6.1.12 [`#1554`](https://github.com/th-ch/youtube-music/pull/1554)
- Revert "fix(deps): update dependency @xhayper/discord-rpc to v1.1.2" [`#1552`](https://github.com/th-ch/youtube-music/pull/1552)
- feat(ambient-mode): support ambient mode on `Song section` [`#1555`](https://github.com/th-ch/youtube-music/issues/1555)
- fix: fixed an issue with the download button disappearing [`#1551`](https://github.com/th-ch/youtube-music/issues/1551)
- fix: fix `homebrew cask` [`#1514`](https://github.com/th-ch/youtube-music/issues/1514)
- fix: pnpm build error [`13ef856`](https://github.com/th-ch/youtube-music/commit/13ef8560ff43353030537403be7da82542ba535e)
- chore(i18n): Translated using Weblate (Czech) [`0dc9c6a`](https://github.com/th-ch/youtube-music/commit/0dc9c6a1a90bce6505614617b827e816cbaaf875)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.15.0 [`c5bcd89`](https://github.com/th-ch/youtube-music/commit/c5bcd89f164b51d7380486a8ae35edd0caeea842)
#### [v3.1.1](https://github.com/th-ch/youtube-music/compare/v3.1.0...v3.1.1)
> 18 December 2023
- fix: fix renderer plugin load timing [`#1522`](https://github.com/th-ch/youtube-music/issues/1522)
- chore(i18n): Translated using Weblate (Lithuanian) [`fc1a7cd`](https://github.com/th-ch/youtube-music/commit/fc1a7cda62b6e33e5f5d57a5a6e0adef6a32bf9a)
- chore(i18n): Translated using Weblate (Chinese (Simplified)) [`eba7026`](https://github.com/th-ch/youtube-music/commit/eba7026b89bbfdd3ac07cf728a66ba9bdd274ec0)
- chore(deps): update dependency rollup to v4.8.0 [`a601d0b`](https://github.com/th-ch/youtube-music/commit/a601d0b3d2dee0fabad79a18e1a7dd0ca84ccf01)
#### [v3.1.0](https://github.com/th-ch/youtube-music/compare/v3.0.2...v3.1.0)
> 11 December 2023
- chore(deps): update dependency electron to v28 [`#1498`](https://github.com/th-ch/youtube-music/pull/1498)
- Enable/Disable Navigation without restart [`#1507`](https://github.com/th-ch/youtube-music/pull/1507)
- Turkish(tr)_lang_file [`#1513`](https://github.com/th-ch/youtube-music/pull/1513)
- Skip Disliked Songs [`#1505`](https://github.com/th-ch/youtube-music/pull/1505)
- chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.13.2 [`#1452`](https://github.com/th-ch/youtube-music/pull/1452)
- fix: Homebrew latest release url parsing [`#1496`](https://github.com/th-ch/youtube-music/pull/1496)
- fix: in-player adblocker inject timing issue [`#1478`](https://github.com/th-ch/youtube-music/issues/1478)
- fix(package.json): fix RPM version `libuuid` issue [`#1508`](https://github.com/th-ch/youtube-music/issues/1508)
- Translated using Weblate (Polish) [`7b78ba6`](https://github.com/th-ch/youtube-music/commit/7b78ba67613f14be65a45751efeb06431b405a91)
- Translated using Weblate (French) [`ebc0879`](https://github.com/th-ch/youtube-music/commit/ebc087963b23265ff00528c8305d51597abf587a)
- Translated using Weblate (Chinese (Traditional)) [`020bdc0`](https://github.com/th-ch/youtube-music/commit/020bdc0811ea45ad6c2853c62a05ae6695c5c4f9)
#### [v3.0.2](https://github.com/th-ch/youtube-music/compare/v3.0.1...v3.0.2)
> 3 December 2023
- fix(adblocker): fix In-Player adblocker [`#1478`](https://github.com/th-ch/youtube-music/issues/1478)
- fix(menu): crash on linux [`#1477`](https://github.com/th-ch/youtube-music/issues/1477)
- fix: update pnpm-lock.yaml [`9e2c6b1`](https://github.com/th-ch/youtube-music/commit/9e2c6b1afa33b5708853c8328946e68ec45b09c3)
- Translated using Weblate (Chinese (Traditional)) [`125b69f`](https://github.com/th-ch/youtube-music/commit/125b69fd75a05c3eb893886119e2d9f2332b3e56)
- Translated using Weblate (French) [`15c4551`](https://github.com/th-ch/youtube-music/commit/15c455105b5100a8ee2bd0a4631548d3d455f047)
#### [v3.0.1](https://github.com/th-ch/youtube-music/compare/v3.0.0...v3.0.1)
> 2 December 2023
- hotfix(adblocker): fix #1475 [`#1475`](https://github.com/th-ch/youtube-music/issues/1475)
- Translated using Weblate (French) [`7f02afc`](https://github.com/th-ch/youtube-music/commit/7f02afc5a6839adfe8437d4e2cc8dee13a93b311)
- Update changelog for v3.0.0 [`d8c8bd1`](https://github.com/th-ch/youtube-music/commit/d8c8bd17ecfbdf96ebd29eb4c5748c07876ee242)
- Translated using Weblate (German) [`0660f0b`](https://github.com/th-ch/youtube-music/commit/0660f0b7ce6895ef5800f48ade1da2d7f8e0c1f7)
### [v3.0.0](https://github.com/th-ch/youtube-music/compare/v2.2.0...v3.0.0)
> 2 December 2023
- Add text to Translation section [`#1470`](https://github.com/th-ch/youtube-music/pull/1470)
- fix(deps): update dependency youtubei.js to v8 [`#1473`](https://github.com/th-ch/youtube-music/pull/1473)
- chore(deps): update dependency electron to v27.1.3 [`#1471`](https://github.com/th-ch/youtube-music/pull/1471)
- fix(deps): update dependency @xhayper/discord-rpc to v1.1.1 [`#1472`](https://github.com/th-ch/youtube-music/pull/1472)
- feat: add support i18n [`#1468`](https://github.com/th-ch/youtube-music/pull/1468)
- chore(deps): update dependency electron to v27.1.2 [`#1441`](https://github.com/th-ch/youtube-music/pull/1441)
- Nicer Readme [`#1439`](https://github.com/th-ch/youtube-music/pull/1439)
- Windows Zoom, ScaleFactor [`#1402`](https://github.com/th-ch/youtube-music/pull/1402)
- chore(deps): bump axios from 1.5.1 to 1.6.1 [`#1400`](https://github.com/th-ch/youtube-music/pull/1400)
- Updated mac icon to better reflect the Mac styling [`#1395`](https://github.com/th-ch/youtube-music/pull/1395)
- feat: rename plugins to clarify context [`#1392`](https://github.com/th-ch/youtube-music/pull/1392)
- feat: refactor plugin utils [`#1391`](https://github.com/th-ch/youtube-music/pull/1391)
- feat: plugin auto-importer with `vite-plugin-resolve` [`#1385`](https://github.com/th-ch/youtube-music/pull/1385)
- feat: migrate from `rollup` to `electron-vite` [`#1364`](https://github.com/th-ch/youtube-music/pull/1364)
- feat: enable `context-isolation` [`#1361`](https://github.com/th-ch/youtube-music/pull/1361)
- fix: add workaround for `podcast` type video [`#1362`](https://github.com/th-ch/youtube-music/pull/1362)
- fix: fix broken menu-layout [`#1360`](https://github.com/th-ch/youtube-music/pull/1360)
- Add Homebrew cask install option for MacOS. [`#1357`](https://github.com/th-ch/youtube-music/pull/1357)
- feat: changed Zoom shortcuts to standard [`#1458`](https://github.com/th-ch/youtube-music/issues/1458)
- fix(in-app-menu): fix #1436 [`#1436`](https://github.com/th-ch/youtube-music/issues/1436)
- fix(discord): update application client-id [`#1431`](https://github.com/th-ch/youtube-music/issues/1431)
- chore(deps): update dependency electron to v27.0.4 [`#1324`](https://github.com/th-ch/youtube-music/issues/1324)
- fix(in-app-menu): panel should close with the window when it is closed [`#1389`](https://github.com/th-ch/youtube-music/issues/1389)
- fix: change titleBarOverlay height based on zoomFactor [`#1375`](https://github.com/th-ch/youtube-music/issues/1375)
- fix: fixed an issue if "Always on top" is enabled, the dialog is displayed below the window [`#1379`](https://github.com/th-ch/youtube-music/issues/1379)
- fix: fix winget version (fix #1363) [`#1363`](https://github.com/th-ch/youtube-music/issues/1363)
- feat: run prettier [`a3104fd`](https://github.com/th-ch/youtube-music/commit/a3104fda4b0d58b076d0c737111636a66e468acc)
- Translated using Weblate (Korean) [`b4b7ad8`](https://github.com/th-ch/youtube-music/commit/b4b7ad824b8c489ae483eba139b46e5b200231fc)
- Translated using Weblate (English) [`d2eabaa`](https://github.com/th-ch/youtube-music/commit/d2eabaa4bbccd89eae529eae52cec035e8e2620c)
#### [v2.2.0](https://github.com/th-ch/youtube-music/compare/v2.1.3...v2.2.0)
> 27 October 2023
- feat(ambient-mode): add config for `ambient-mode` plugin [`#1349`](https://github.com/th-ch/youtube-music/pull/1349)
- bump deps [`4248d20`](https://github.com/th-ch/youtube-music/commit/4248d20e8ef926ce7b1d07eb83743755a341d9f6)
- Update changelog for v2.1.3 [`dc73561`](https://github.com/th-ch/youtube-music/commit/dc73561c8a8acfc8ba91aff2dc78e4267869f2fd)
- Bump version to 2.2.0 [`6288d0b`](https://github.com/th-ch/youtube-music/commit/6288d0b171a65ea015922cdf3af6c7bd9a1f269b)
#### [v2.1.3](https://github.com/th-ch/youtube-music/compare/v2.1.2...v2.1.3)
> 23 October 2023
- fix: fixed bugs in downloader [`#1342`](https://github.com/th-ch/youtube-music/pull/1342)
- feat(discord): rename `Listen Along` to `Play on YTM` [`#1341`](https://github.com/th-ch/youtube-music/issues/1341)
- chore(deps): bump deps [`4333891`](https://github.com/th-ch/youtube-music/commit/4333891ccabe42aedf756fd48618be715db13262)
- Update changelog for v2.1.2 [`fa4c69d`](https://github.com/th-ch/youtube-music/commit/fa4c69d228d4e06a7858e2b22fcdfa075a8ca766)
- fix(store): fix listenAlong statement [`bceaa05`](https://github.com/th-ch/youtube-music/commit/bceaa05197d47a4a4bbd22e767d1e4d6ec277514)
#### [v2.1.2](https://github.com/th-ch/youtube-music/compare/v2.1.1...v2.1.2)
> 19 October 2023
- feat(in-app-menu): add an option to hide the window controls [`#1335`](https://github.com/th-ch/youtube-music/pull/1335)
- fix: fixed an issue where the album name was missing [`#1334`](https://github.com/th-ch/youtube-music/pull/1334)
- chore(deps): update dependency electron to v27.0.1 [`#1331`](https://github.com/th-ch/youtube-music/pull/1331)
- fix: fixed an issue where only the first 100 songs in a playlist were downloaded [`#1329`](https://github.com/th-ch/youtube-music/pull/1329)
- Updated readme plugins list [`#1326`](https://github.com/th-ch/youtube-music/pull/1326)
- QOL: Move source code under the src directory. [`#1318`](https://github.com/th-ch/youtube-music/pull/1318)
- feat: migrate from `npm` to `pnpm` [`#1316`](https://github.com/th-ch/youtube-music/pull/1316)
- fix: fix unresponsive (fix #1325) [`#1325`](https://github.com/th-ch/youtube-music/issues/1325)
- fix(blocker): remove the `app.isPackaged` check (fix #1315) [`#1315`](https://github.com/th-ch/youtube-music/issues/1315)
- fix(discord): `Discord RPC fails if a song's title is only one character` (fix #1314) [`#1314`](https://github.com/th-ch/youtube-music/issues/1314)
- chore(deps): Bump @rollup/plugin-commonjs, pnpm version, Remove ytpl [`9705f84`](https://github.com/th-ch/youtube-music/commit/9705f8489d7bf262bfd8b15ab84c2d3485f10eae)
- chore(deps): Bump rollup, @xhayper/discord-rpc version [`00a3e8d`](https://github.com/th-ch/youtube-music/commit/00a3e8d35ec335e1913be19f30ae09dbe0b7acdd)
- chore(deps): update dependency rollup to v4.1.4 [`6774d54`](https://github.com/th-ch/youtube-music/commit/6774d54f5eca432edc2e11743d9d1b1c2fda9ac8)
#### [v2.1.1](https://github.com/th-ch/youtube-music/compare/v2.1.0...v2.1.1)
> 14 October 2023
- hotfix(downloader): can't get an album title (fix #1313) [`#1313`](https://github.com/th-ch/youtube-music/issues/1313)
- Update changelog for v2.1.0 [`92cab89`](https://github.com/th-ch/youtube-music/commit/92cab89d17175741e60e65ea61633e23ebdc1f45)
- Bump version to 2.1.1 [`3bb5bc2`](https://github.com/th-ch/youtube-music/commit/3bb5bc2ca1856f4e222ee1e01e865f1ab804fdba)
- Add "about" menu to show app version [`21c45fa`](https://github.com/th-ch/youtube-music/commit/21c45faf2043cf72a7c14d5cf6c8d848d0448528)
#### [v2.1.0](https://github.com/th-ch/youtube-music/compare/v2.0.4...v2.1.0)
> 14 October 2023
- feat(downloader): Added support for audio format auto-detection [`#1310`](https://github.com/th-ch/youtube-music/pull/1310)
- feat(in-app-menu): enable in-app-menu by default (in Windows) [`#1311`](https://github.com/th-ch/youtube-music/pull/1311)
- fix: winget publish [`#1307`](https://github.com/th-ch/youtube-music/pull/1307)
- hotfix(downloader): fix invalid query selector (fix #1308) [`#1308`](https://github.com/th-ch/youtube-music/issues/1308)
- chore(deps): bump dependencies [`3c6b3ae`](https://github.com/th-ch/youtube-music/commit/3c6b3aeff0aae32adb2f2ad9c091b0a9701d3c24)
- chore(actions): create winget-cla.yml [`37181a7`](https://github.com/th-ch/youtube-music/commit/37181a7b5e2aa5bed6a36298eac3a66aac2762b8)
- Update changelog for v2.0.4 [`e9398ad`](https://github.com/th-ch/youtube-music/commit/e9398adac34a8abb11801e32999a915a8be0ece6)
#### [v2.0.4](https://github.com/th-ch/youtube-music/compare/v2.0.3...v2.0.4)
> 12 October 2023
- hotfix(adblocker): fix `ipcRenderer.sendSync() with ...` [`#1301`](https://github.com/th-ch/youtube-music/pull/1301)
- fix(downloader): Korean filename is broken on non-macOS devices [`#1297`](https://github.com/th-ch/youtube-music/pull/1297)
- chore(deps): bump deps [`b6894dc`](https://github.com/th-ch/youtube-music/commit/b6894dca2974c63fa2945d3a4995665d11eb2a78)
- fix: bump dependencies [`7aa970c`](https://github.com/th-ch/youtube-music/commit/7aa970cebc8e1407ff6937b402ba303e14c73efd)
- fix(downloader): private playlist download [`1d5b299`](https://github.com/th-ch/youtube-music/commit/1d5b2997bd0c72c1c007c57b145509e4a8f77fef)
#### [v2.0.3](https://github.com/th-ch/youtube-music/compare/v2.0.2...v2.0.3)
> 10 October 2023
- feat(discord): add `Hide GitHub link Button` [`#1293`](https://github.com/th-ch/youtube-music/pull/1293)
- feat(deps): bundle `youtubei.js` (temporary solution) [`#1292`](https://github.com/th-ch/youtube-music/pull/1292)
- fix(mpris): fixed an issue where MPRIS information was incorrect [`#1291`](https://github.com/th-ch/youtube-music/pull/1291)
- fix(discord): fixed an issue where `timeChanged` was not being applied to Discord activities [`#1290`](https://github.com/th-ch/youtube-music/pull/1290)
- Fix: typo in README [`#1286`](https://github.com/th-ch/youtube-music/pull/1286)
- fix: chore(deps): update dependency @jellybrick/mpris-service to 2.1.4 (fix #971) [`#971`](https://github.com/th-ch/youtube-music/issues/971)
- chore(deps): Bump `@cliqz/adblocker-electron` to 1.26.8 (fix #1269) [`#1269`](https://github.com/th-ch/youtube-music/issues/1269)
- fix: missing icons taskbar-mediacontrol [`fbf4b3b`](https://github.com/th-ch/youtube-music/commit/fbf4b3b8b5e39c61975e67efc990c45f62de76d8)
- remove: migration scripts [`52ba2dc`](https://github.com/th-ch/youtube-music/commit/52ba2dc9ffd8e235251d1279686f55e33b3fa3bb)
- feat: add migration script [`926b9fb`](https://github.com/th-ch/youtube-music/commit/926b9fb5e6db69b69935ec5d7be9a76a84e54ceb)
#### [v2.0.2](https://github.com/th-ch/youtube-music/compare/v2.0.1...v2.0.2)
> 8 October 2023
- fix: discord-rpc [`#1278`](https://github.com/th-ch/youtube-music/pull/1278)
- Bump version to 2.0.2 [`b5dbfaf`](https://github.com/th-ch/youtube-music/commit/b5dbfaf68691a546d72f5c1818fd3a44802eb0fa)
- Merge pull request #1272 from th-ch/feat/resolves-1265 [`6b7fd5b`](https://github.com/th-ch/youtube-music/commit/6b7fd5ba630888de08004105179c059c6d93e028)
- Merge pull request #1279 from th-ch/fix/1274 [`73a049a`](https://github.com/th-ch/youtube-music/commit/73a049a7bc5161f0d53c252cf510f1e2a6f6eeb3)
#### [v2.0.1](https://github.com/th-ch/youtube-music/compare/v2.0.0...v2.0.1)
> 8 October 2023
- Update changelog for v2.0.0 [`2d69dfd`](https://github.com/th-ch/youtube-music/commit/2d69dfd333c3223ecc7de13a0abc98fd99aa3a2b)
- hotfix: hotfix for #1267 [`c002263`](https://github.com/th-ch/youtube-music/commit/c002263c3bdd51890b8ffb431283afb60405d8fe)
- Bump version to 2.0.1 [`a1f025e`](https://github.com/th-ch/youtube-music/commit/a1f025e23c599fe5eb63b32ea38ee81200d232d6)

View File

@ -1,279 +0,0 @@
import { blockers } from '../plugins/adblocker/blocker-types';
export interface WindowSizeConfig {
width: number;
height: number;
}
export interface DefaultConfig {
'window-size': {
width: number;
height: number;
}
'window-maximized': boolean;
'window-position': {
x: number;
y: number;
}
url: string;
options: {
tray: boolean;
appVisible: boolean;
autoUpdates: boolean;
alwaysOnTop: boolean;
hideMenu: boolean;
hideMenuWarned: boolean;
startAtLogin: boolean;
disableHardwareAcceleration: boolean;
removeUpgradeButton: boolean;
restartOnConfigChanges: boolean;
trayClickPlayPause: boolean;
autoResetAppCache: boolean;
resumeOnStart: boolean;
likeButtons: string;
proxy: string;
startingPage: string;
overrideUserAgent: boolean;
themes: string[];
}
}
const defaultConfig = {
'window-size': {
width: 1100,
height: 550,
},
'window-maximized': false,
'window-position': {
x: -1,
y: -1,
},
'url': 'https://music.youtube.com',
'options': {
tray: false,
appVisible: true,
autoUpdates: true,
alwaysOnTop: false,
hideMenu: false,
hideMenuWarned: false,
startAtLogin: false,
disableHardwareAcceleration: false,
removeUpgradeButton: false,
restartOnConfigChanges: false,
trayClickPlayPause: false,
autoResetAppCache: false,
resumeOnStart: true,
likeButtons: '',
proxy: '',
startingPage: '',
overrideUserAgent: false,
themes: [] as string[],
},
/** please order alphabetically */
'plugins': {
'adblocker': {
enabled: true,
cache: true,
blocker: blockers.InPlayer as string,
additionalBlockLists: [], // Additional list of filters, e.g "https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/filters.txt"
disableDefaultLists: false,
},
'album-color-theme': {},
'ambient-mode': {},
'audio-compressor': {},
'blur-nav-bar': {},
'bypass-age-restrictions': {},
'captions-selector': {
enabled: false,
disableCaptions: false,
autoload: false,
lastCaptionsCode: '',
},
'compact-sidebar': {},
'crossfade': {
enabled: false,
fadeInDuration: 1500, // Ms
fadeOutDuration: 5000, // Ms
secondsBeforeEnd: 10, // S
fadeScaling: 'linear', // 'linear', 'logarithmic' or a positive number in dB
},
'disable-autoplay': {
applyOnce: false,
},
'discord': {
enabled: false,
autoReconnect: true, // If enabled, will try to reconnect to discord every 5 seconds after disconnecting or failing to connect
activityTimoutEnabled: true, // If enabled, the discord rich presence gets cleared when music paused after the time specified below
activityTimoutTime: 10 * 60 * 1000, // 10 minutes
listenAlong: true, // Add a "listen along" button to rich presence
hideDurationLeft: false, // Hides the start and end time of the song to rich presence
},
'downloader': {
enabled: false,
ffmpegArgs: ['-b:a', '256k'], // E.g. ["-b:a", "192k"] for an audio bitrate of 192kb/s
downloadFolder: undefined as string | undefined, // Custom download folder (absolute path)
preset: 'mp3',
skipExisting: false,
playlistMaxItems: undefined as number | undefined,
},
'exponential-volume': {},
'in-app-menu': {},
'last-fm': {
enabled: false,
token: undefined as string | undefined, // Token used for authentication
session_key: undefined as string | undefined, // Session key used for scrobbling
api_root: 'http://ws.audioscrobbler.com/2.0/',
api_key: '04d76faaac8726e60988e14c105d421a', // Api key registered by @semvis123
secret: 'a5d2a36fdf64819290f6982481eaffa2',
},
'lumiastream': {},
'lyrics-genius': {
romanizedLyrics: false,
},
'navigation': {
enabled: true,
},
'no-google-login': {},
'notifications': {
enabled: false,
unpauseNotification: false,
urgency: 'normal', // Has effect only on Linux
// the following has effect only on Windows
interactive: true,
toastStyle: 1, // See plugins/notifications/utils for more info
refreshOnPlayPause: false,
trayControls: true,
hideButtonText: false,
},
'picture-in-picture': {
'enabled': false,
'alwaysOnTop': true,
'savePosition': true,
'saveSize': false,
'hotkey': 'P',
'pip-position': [10, 10],
'pip-size': [450, 275],
'isInPiP': false,
'useNativePiP': false,
},
'playback-speed': {},
'precise-volume': {
enabled: false,
steps: 1, // Percentage of volume to change
arrowsShortcut: true, // Enable ArrowUp + ArrowDown local shortcuts
globalShortcuts: {
volumeUp: '',
volumeDown: '',
},
savedVolume: undefined as number | undefined, // Plugin save volume between session here
},
'quality-changer': {},
'shortcuts': {
enabled: false,
overrideMediaKeys: false,
global: {
previous: '',
playPause: '',
next: '',
} as Record<string, string>,
local: {
previous: '',
playPause: '',
next: '',
} as Record<string, string>,
},
'skip-silences': {
onlySkipBeginning: false,
},
'sponsorblock': {
enabled: false,
apiURL: 'https://sponsor.ajay.app',
categories: [
'sponsor',
'intro',
'outro',
'interaction',
'selfpromo',
'music_offtopic',
],
},
'taskbar-mediacontrol': {},
'touchbar': {},
'tuna-obs': {},
'video-toggle': {
enabled: false,
hideVideo: false,
mode: 'custom',
forceHide: false,
align: '',
},
'visualizer': {
enabled: false,
type: 'butterchurn',
// Config per visualizer
butterchurn: {
preset: 'martin [shadow harlequins shape code] - fata morgana',
renderingFrequencyInMs: 500,
blendTimeInSeconds: 2.7,
},
vudio: {
effect: 'lighting',
accuracy: 128,
lighting: {
maxHeight: 160,
maxSize: 12,
lineWidth: 1,
color: '#49f3f7',
shadowBlur: 2,
shadowColor: 'rgba(244,244,244,.5)',
fadeSide: true,
prettify: false,
horizontalAlign: 'center',
verticalAlign: 'middle',
dottify: true,
},
},
wave: {
animations: [
{
type: 'Cubes',
config: {
bottom: true,
count: 30,
cubeHeight: 5,
fillColor: { gradient: ['#FAD961', '#F76B1C'] },
lineColor: 'rgba(0,0,0,0)',
radius: 20,
},
},
{
type: 'Cubes',
config: {
top: true,
count: 12,
cubeHeight: 5,
fillColor: { gradient: ['#FAD961', '#F76B1C'] },
lineColor: 'rgba(0,0,0,0)',
radius: 10,
},
},
{
type: 'Circles',
config: {
lineColor: {
gradient: ['#FAD961', '#FAD961', '#F76B1C'],
rotate: 90,
},
lineWidth: 4,
diameter: 20,
count: 10,
frequencyBand: 'base',
},
},
],
},
},
},
};
export default defaultConfig;

View File

@ -1,241 +0,0 @@
/* eslint-disable @typescript-eslint/require-await */
import { ipcMain, ipcRenderer } from 'electron';
import defaultConfig from './defaults';
import { getOptions, setMenuOptions, setOptions } from './plugins';
import { sendToFront } from '../providers/app-controls';
import { Entries } from '../utils/type-utils';
export type DefaultPluginsConfig = typeof defaultConfig.plugins;
export type OneOfDefaultConfigKey = keyof DefaultPluginsConfig;
export type OneOfDefaultConfig = typeof defaultConfig.plugins[OneOfDefaultConfigKey];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const activePlugins: { [key in OneOfDefaultConfigKey]?: PluginConfig<any> } = {};
/**
* [!IMPORTANT!]
* The method is **sync** in the main process and **async** in the renderer process.
*/
export const getActivePlugins
= process.type === 'renderer'
? async () => ipcRenderer.invoke('get-active-plugins')
: () => activePlugins;
if (process.type === 'browser') {
ipcMain.handle('get-active-plugins', getActivePlugins);
}
/**
* [!IMPORTANT!]
* The method is **sync** in the main process and **async** in the renderer process.
*/
export const isActive
= process.type === 'renderer'
? async (plugin: string) =>
plugin in (await ipcRenderer.invoke('get-active-plugins'))
: (plugin: string): boolean => plugin in activePlugins;
interface PluginConfigOptions {
enableFront: boolean;
initialOptions?: OneOfDefaultConfig;
}
/**
* This class is used to create a dynamic synced config for plugins.
*
* [!IMPORTANT!]
* The methods are **sync** in the main process and **async** in the renderer process.
*
* @param {string} name - The name of the plugin.
* @param {boolean} [options.enableFront] - Whether the config should be available in front.js. Default: false.
* @param {object} [options.initialOptions] - The initial options for the plugin. Default: loaded from store.
*
* @example
* const { PluginConfig } = require("../../config/dynamic");
* const config = new PluginConfig("plugin-name", { enableFront: true });
* module.exports = { ...config };
*
* // or
*
* module.exports = (win, options) => {
* const config = new PluginConfig("plugin-name", {
* enableFront: true,
* initialOptions: options,
* });
* setupMyPlugin(win, config);
* };
*/
export type ConfigType<T extends OneOfDefaultConfigKey> = typeof defaultConfig.plugins[T];
type ValueOf<T> = T[keyof T];
type Mode<T, Mode extends 'r' | 'm'> = Mode extends 'r' ? Promise<T> : T;
export class PluginConfig<T extends OneOfDefaultConfigKey> {
private readonly name: string;
private readonly config: ConfigType<T>;
private readonly defaultConfig: ConfigType<T>;
private readonly enableFront: boolean;
private subscribers: { [key in keyof ConfigType<T>]?: (config: ConfigType<T>) => void } = {};
private allSubscribers: ((config: ConfigType<T>) => void)[] = [];
constructor(
name: T,
options: PluginConfigOptions = {
enableFront: false,
},
) {
const pluginDefaultConfig = defaultConfig.plugins[name] ?? {};
const pluginConfig = options.initialOptions || getOptions(name) || {};
this.name = name;
this.enableFront = options.enableFront;
this.defaultConfig = pluginDefaultConfig;
this.config = { ...pluginDefaultConfig, ...pluginConfig };
if (this.enableFront) {
this.setupFront();
}
activePlugins[name] = this;
}
get<Key extends keyof ConfigType<T> = keyof ConfigType<T>>(key: Key): ConfigType<T>[Key] {
return this.config?.[key];
}
set(key: keyof ConfigType<T>, value: ValueOf<ConfigType<T>>) {
this.config[key] = value;
this.onChange(key);
this.save();
}
getAll(): ConfigType<T> {
return { ...this.config };
}
setAll(options: Partial<ConfigType<T>>) {
if (!options || typeof options !== 'object') {
throw new Error('Options must be an object.');
}
let changed = false;
for (const [key, value] of Object.entries(options) as Entries<typeof options>) {
if (this.config[key] !== value) {
if (value !== undefined) this.config[key] = value;
this.onChange(key, false);
changed = true;
}
}
if (changed) {
for (const fn of this.allSubscribers) {
fn(this.config);
}
}
this.save();
}
getDefaultConfig() {
return this.defaultConfig;
}
/**
* Use this method to set an option and restart the app if `appConfig.restartOnConfigChange === true`
*
* Used for options that require a restart to take effect.
*/
setAndMaybeRestart(key: keyof ConfigType<T>, value: ValueOf<ConfigType<T>>) {
this.config[key] = value;
setMenuOptions(this.name, this.config);
this.onChange(key);
}
subscribe(valueName: keyof ConfigType<T>, fn: (config: ConfigType<T>) => void) {
this.subscribers[valueName] = fn;
}
subscribeAll(fn: (config: ConfigType<T>) => void) {
this.allSubscribers.push(fn);
}
/** Called only from back */
private save() {
setOptions(this.name, this.config);
}
private onChange(valueName: keyof ConfigType<T>, single: boolean = true) {
this.subscribers[valueName]?.(this.config[valueName] as ConfigType<T>);
if (single) {
for (const fn of this.allSubscribers) {
fn(this.config);
}
}
}
private setupFront() {
const ignoredMethods = ['subscribe', 'subscribeAll'];
if (process.type === 'renderer') {
for (const [fnName, fn] of Object.entries(this) as Entries<this>) {
if (typeof fn !== 'function' || fn.name in ignoredMethods) {
return;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-return
this[fnName] = (async (...args: any) => await ipcRenderer.invoke(
`${this.name}-config-${String(fnName)}`,
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
...args,
)) as typeof this[keyof this];
this.subscribe = (valueName, fn: (config: ConfigType<T>) => void) => {
if (valueName in this.subscribers) {
console.error(`Already subscribed to ${String(valueName)}`);
}
this.subscribers[valueName] = fn;
ipcRenderer.on(
`${this.name}-config-changed-${String(valueName)}`,
(_, value: ConfigType<T>) => {
fn(value);
},
);
ipcRenderer.send(`${this.name}-config-subscribe`, valueName);
};
this.subscribeAll = (fn: (config: ConfigType<T>) => void) => {
ipcRenderer.on(`${this.name}-config-changed`, (_, value: ConfigType<T>) => {
fn(value);
});
ipcRenderer.send(`${this.name}-config-subscribe-all`);
};
}
} else if (process.type === 'browser') {
for (const [fnName, fn] of Object.entries(this) as Entries<this>) {
if (typeof fn !== 'function' || fn.name in ignoredMethods) {
return;
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-return
ipcMain.handle(`${this.name}-config-${String(fnName)}`, (_, ...args) => fn(...args));
}
ipcMain.on(`${this.name}-config-subscribe`, (_, valueName: keyof ConfigType<T>) => {
this.subscribe(valueName, (value) => {
sendToFront(`${this.name}-config-changed-${String(valueName)}`, value);
});
});
ipcMain.on(`${this.name}-config-subscribe-all`, () => {
this.subscribeAll((value) => {
sendToFront(`${this.name}-config-changed`, value);
});
});
}
}
}

View File

@ -1,53 +0,0 @@
import Store from 'electron-store';
import defaultConfig from './defaults';
import plugins from './plugins';
import store from './store';
import { restart } from '../providers/app-controls';
const set = (key: string, value: unknown) => {
store.set(key, value);
};
function setMenuOption(key: string, value: unknown) {
set(key, value);
if (store.get('options.restartOnConfigChanges')) {
restart();
}
}
// MAGIC OF TYPESCRIPT
type Prev = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...0[]]
type Join<K, P> = K extends string | number ?
P extends string | number ?
`${K}${'' extends P ? '' : '.'}${P}`
: never : never;
type Paths<T, D extends number = 10> = [D] extends [never] ? never : T extends object ?
{ [K in keyof T]-?: K extends string | number ?
`${K}` | Join<K, Paths<T[K], Prev[D]>>
: never
}[keyof T] : ''
type SplitKey<K> = K extends `${infer A}.${infer B}` ? [A, B] : [K, string];
type PathValue<T, K extends string> =
SplitKey<K> extends [infer A extends keyof T, infer B extends string]
? PathValue<T[A], B>
: T;
const get = <Key extends Paths<typeof defaultConfig>>(key: Key) => store.get(key) as PathValue<typeof defaultConfig, typeof key>;
export default {
defaultConfig,
get,
set,
setMenuOption,
edit: () => store.openInEditor(),
watch(cb: Parameters<Store['onDidChange']>[1]) {
store.onDidChange('options', cb);
store.onDidChange('plugins', cb);
},
plugins,
};

View File

@ -1,63 +0,0 @@
import store from './store';
import defaultConfig from './defaults';
import { restart } from '../providers/app-controls';
import { Entries } from '../utils/type-utils';
interface Plugin {
enabled: boolean;
}
type DefaultPluginsConfig = typeof defaultConfig.plugins;
export function getEnabled() {
const plugins = store.get('plugins') as DefaultPluginsConfig;
return (Object.entries(plugins) as Entries<DefaultPluginsConfig>).filter(([plugin]) =>
isEnabled(plugin),
);
}
export function isEnabled(plugin: string) {
const pluginConfig = (store.get('plugins') as Record<string, Plugin>)[plugin];
return pluginConfig !== undefined && pluginConfig.enabled;
}
export function setOptions<T>(plugin: string, options: T) {
const plugins = store.get('plugins') as Record<string, T>;
store.set('plugins', {
...plugins,
[plugin]: {
...plugins[plugin],
...options,
},
});
}
export function setMenuOptions<T>(plugin: string, options: T) {
setOptions(plugin, options);
if (store.get('options.restartOnConfigChanges')) {
restart();
}
}
export function getOptions<T>(plugin: string): T {
return (store.get('plugins') as Record<string, T>)[plugin];
}
export function enable(plugin: string) {
setMenuOptions(plugin, { enabled: true });
}
export function disable(plugin: string) {
setMenuOptions(plugin, { enabled: false });
}
export default {
isEnabled,
getEnabled,
enable,
disable,
setOptions,
setMenuOptions,
getOptions,
};

View File

@ -1,124 +0,0 @@
import Store from 'electron-store';
import Conf from 'conf';
import defaults from './defaults';
const setDefaultPluginOptions = (store: Conf<Record<string, unknown>>, plugin: keyof typeof defaults.plugins) => {
if (!store.get(`plugins.${plugin}`)) {
store.set(`plugins.${plugin}`, defaults.plugins[plugin]);
}
};
const migrations = {
'>=1.20.0'(store: Conf<Record<string, unknown>>) {
setDefaultPluginOptions(store, 'visualizer');
if (store.get('plugins.notifications.toastStyle') === undefined) {
const pluginOptions = store.get('plugins.notifications') || {};
store.set('plugins.notifications', {
...defaults.plugins.notifications,
...pluginOptions,
});
}
if (store.get('options.ForceShowLikeButtons')) {
store.delete('options.ForceShowLikeButtons');
store.set('options.likeButtons', 'force');
}
},
'>=1.17.0'(store: Conf<Record<string, unknown>>) {
setDefaultPluginOptions(store, 'picture-in-picture');
if (store.get('plugins.video-toggle.mode') === undefined) {
store.set('plugins.video-toggle.mode', 'custom');
}
},
'>=1.14.0'(store: Conf<Record<string, unknown>>) {
if (
typeof store.get('plugins.precise-volume.globalShortcuts') !== 'object'
) {
store.set('plugins.precise-volume.globalShortcuts', {});
}
if (store.get('plugins.hide-video-player.enabled')) {
store.delete('plugins.hide-video-player');
store.set('plugins.video-toggle.enabled', true);
}
},
'>=1.13.0'(store: Conf<Record<string, unknown>>) {
if (store.get('plugins.discord.listenAlong') === undefined) {
store.set('plugins.discord.listenAlong', true);
}
},
'>=1.12.0'(store: Conf<Record<string, unknown>>) {
const options = store.get('plugins.shortcuts') as Record<string, {
action: string;
shortcut: unknown;
}[] | Record<string, unknown>>;
let updated = false;
for (const optionType of ['global', 'local']) {
if (Array.isArray(options[optionType])) {
const optionsArray = options[optionType] as {
action: string;
shortcut: unknown;
}[];
const updatedOptions: Record<string, unknown> = {};
for (const optionObject of optionsArray) {
if (optionObject.action && optionObject.shortcut) {
updatedOptions[optionObject.action] = optionObject.shortcut;
}
}
options[optionType] = updatedOptions;
updated = true;
}
}
if (updated) {
store.set('plugins.shortcuts', options);
}
},
'>=1.11.0'(store: Conf<Record<string, unknown>>) {
if (store.get('options.resumeOnStart') === undefined) {
store.set('options.resumeOnStart', true);
}
},
'>=1.7.0'(store: Conf<Record<string, unknown>>) {
const enabledPlugins = store.get('plugins') as string[];
if (!Array.isArray(enabledPlugins)) {
console.warn('Plugins are not in array format, cannot migrate');
return;
}
// Include custom options
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const plugins: Record<string, any> = {
adblocker: {
enabled: true,
cache: true,
additionalBlockLists: [],
},
downloader: {
enabled: false,
ffmpegArgs: [], // E.g. ["-b:a", "192k"] for an audio bitrate of 192kb/s
downloadFolder: undefined, // Custom download folder (absolute path)
},
};
for (const enabledPlugin of enabledPlugins) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
plugins[enabledPlugin] = {
...plugins[enabledPlugin],
enabled: true,
};
}
store.set('plugins', plugins);
},
};
export default new Store({
defaults,
clearInvalidConfig: false,
migrations,
});

View File

@ -478,7 +478,7 @@
</a>
</li>
</ul>
<div class="footer-copyright">© 2021 th-ch</div>
<div class="footer-copyright">© 2024 th-ch</div>
</div>
</div>
</div>

388
docs/readme/README-is.md Normal file
View File

@ -0,0 +1,388 @@
<div align="center">
# YouTube Tónlist
[![GitHub release](https://img.shields.io/github/release/th-ch/youtube-music.svg?style=for-the-badge&logo=youtube-music)](https://github.com/th-ch/youtube-music/releases/)
[![GitHub license](https://img.shields.io/github/license/th-ch/youtube-music.svg?style=for-the-badge)](https://github.com/th-ch/youtube-music/blob/master/LICENSE)
[![eslint code style](https://img.shields.io/badge/code_style-eslint-5ed9c7.svg?style=for-the-badge)](https://github.com/th-ch/youtube-music/blob/master/.eslintrc.js)
[![Build status](https://img.shields.io/github/actions/workflow/status/th-ch/youtube-music/build.yml?branch=master&style=for-the-badge&logo=youtube-music)](https://GitHub.com/th-ch/youtube-music/releases/)
[![GitHub All Releases](https://img.shields.io/github/downloads/th-ch/youtube-music/total?style=for-the-badge&logo=youtube-music)](https://GitHub.com/th-ch/youtube-music/releases/)
[![AUR](https://img.shields.io/aur/version/youtube-music-bin?color=blueviolet&style=for-the-badge&logo=youtube-music)](https://aur.archlinux.org/packages/youtube-music-bin)
[![Known Vulnerabilities](https://snyk.io/test/github/th-ch/youtube-music/badge.svg)](https://snyk.io/test/github/th-ch/youtube-music)
</div>
![Screenshot](../../web/screenshot.jpg "Screenshot")
<div align="center">
<a href="https://github.com/th-ch/youtube-music/releases/latest">
<img src="../../web/youtube-music.svg" width="400" height="100" alt="YouTube Music SVG">
</a>
</div>
**Electron umbúðir utan um YouTube Tónlist sem inniheldur:**
- Innfæddur útlit og tilfinning, miðar að því að halda upprunalegu viðmótinu
- Rammi fyrir sérsniðnar viðbætur: breyttu YouTube Tónlist að þínum þörfum (stíl, efni, eiginleikar), virkjaðu/slökktu á viðbætur í
einn smellur
## Sýnishornsmynd
| Spilaraskjár (albúmslitaþema & umhverfisljós) |
|:---------------------------------------------------------------------------------------------------------:|
|![Screenshot1](https://github.com/th-ch/youtube-music/assets/16558115/53efdf73-b8fa-4d7b-a235-b96b91ea77fc)|
## Efni
- [Eiginleikar](#eiginleikar)
- [Tiltæk viðbætur](#tiltæk-viðbætur)
- [Þýðing](#þýðing)
- [Sækja](#sækja)
- [Arch Linux](#arch-linux)
- [MacOS](#macos)
- [Windows](#windows)
- [Hvernig á að setja upp án nettengingar? (í Windows)](#hvernig-á-að-setja-upp-án-nettengingar-í-windows)
- [Þemu](#þemu)
- [Þróun](#þróun)
- [Búðu til þín eigin viðbætur](#búðu-til-þín-eigin-viðbætur)
- [Er að búa til viðbót](#er-að-búa-til-viðbót)
- [Algeng notkunartilvik](#algeng-notkunartilvik)
- [Byggja](#byggja)
- [Framleiðsluforskoðun](#framleiðsluforskoðun)
- [Prófanir](#prófanir)
- [Leyfi](#leyfi)
- [Algengustu spurningar](#algengustu-spurningar)
## Eiginleikar:
- **Sjálfvirk staðfesting þegar gert er hlé** (Alltaf virkt): slökkva á
["Halda áfram að horfa?"](https://user-images.githubusercontent.com/61631665/129977894-01c60740-7ec6-4bf0-9a2c-25da24491b0e.png)
popup sem gerir hlé á tónlist eftir ákveðinn tíma
- Og meira...
## Tiltæk viðbætur:
- **Auglýsingablokkari**: Lokaðu fyrir allar auglýsingar og rakningar úr kassanum
- **Albúmsaðgerðir**: Bætir Ódíslika, Mislíkt, Líkt, og Ólíkt til að nota þetta á öll lög á spilunarlista eða albúm
- **Albúmslitaþema**: Beitir kraftmikið þema og sjónrænum áhrifum sem byggjast á litavali albúmsins
- **Umhverfishamur**: Beitir lýsingaráhrifum með því að varpa mildum litum úr myndbandinu í bakgrunn skjásins
- **Hljóðþjöppur**: Notaðu þjöppun á hljóð (lækkar hljóðstyrk háværustu hluta merkis og hækkar hljóðstyrk í mýkstu hlutunum)
- **Þoka Leiðsagnarstika**: Gerir leiðsögustikuna gagnsæja og óskýrt
- **Farið Framhjá Aldurstakmörkunum**: Framhjá aldursstaðfestingu YouTube
- **Yfirskriftarval**: Virkja skjátexta
- **Fyrirferðarlítillhliðarstika**: Stilltu hliðarstikuna alltaf í þétta stillingu
- **Krossfæra**: Krossfæra á milli lög
- **Slökkva á Sjálfvirkri Spilun**: Gerir lag að byrja í "hlé" ham
- **[Discord](https://discord.com/) Rík Nærveru**: Sýndu vinum þínum hvað þú hlustar á
með [Rík Nærveru](https://user-images.githubusercontent.com/28219076/104362104-a7a0b980-5513-11eb-9744-bb89eabe0016.png)
- **Niðurhalari**: Niðurhalum
MP3 [beint úr viðmótinu](https://user-images.githubusercontent.com/61631665/129977677-83a7d067-c192-45e1-98ae-b5a4927393be.png) [(youtube-dl)](https://github.com/ytdl-org/youtube-dl)
- **Veldibundiðrúmmál**: Gerir hljóðstyrkssleðann [veldisvísis](https://greasyfork.org/en/scripts/397686-youtube-music-fix-volume-ratio/)
svo það er auðveldara að velja lægra hljóðstyrk.
- **Valmynd í Forriti**: [Gefur börum flott, dökkt útlit](https://user-images.githubusercontent.com/78568641/112215894-923dbf00-8c29-11eb-95c3-3ce15db27eca.png)
> (sjá [þessa færslu](https://github.com/th-ch/youtube-music/issues/410#issuecomment-952060709) ef þú átt í vandræðum
með að fá aðgang að valmyndinni eftir að hafa virkjað þessa viðbót og fela valmyndarvalkostinn)
- **Scrobbler**: Bætir við scrobbling stuðningi fyrir [Last.fm](https://www.last.fm/) og [ListenBrainz](https://listenbrainz.org/)
- **Lumia Stream**: Bætir við [Lumia Stream](https://lumiastream.com/) stuðningi
- **Söngtexti Snilld**: Bætir stuðningi við texta fyrir flest lög
- **Tónlist Saman**: Deila spilunarlista með öðrum. Þegar gestgjafinn spilar lag munu allir aðrir heyra sama lagið
- **Leiðsögn**: Næsta/Til baka leiðsagnarörvar beint samþættar í viðmótinu, eins og í uppáhalds vafranum þínum
- **Engin Google Innskráning**: Fjarlægðu Google innskráningarhnappa og tengla úr viðmótinu
- **Tilkynningar**: Birta tilkynningu þegar lag byrjar að spila
([gagnvirkartilkynningar](https://user-images.githubusercontent.com/78568641/114102651-63ce0e00-98d0-11eb-9dfe-c5a02bb54f9c.png) eru fáanlegar á Windows)
- **Mynd-í-Mynd**: Gerir kleift að skipta forritinu yfir í mynd-í-mynd stillingu
- **Spilunarhraði**: Hlustaðu hratt, hlustaðu hægt!
[Bætir við sleða sem stjórnar lagahraðanum](https://user-images.githubusercontent.com/61631665/129976003-e55db5ba-bf42-448c-a059-26a009775e68.png)
- **Nákvæmshljóðstyrkur**: Stjórnaðu hljóðstyrknum nákvæmlega með músarhjóli/hraðtökkum, með sérsniðnum HUD og sérsniðnum hljóðstyrksþrepum
- **Flýtileiðir (og MPRIS)**: Leyfir að stilla alþjóðlegarflýtilyklar fyrir spilun (spila/gera hlé/næsta/fyrri) +
óvirkja [media osd](https://user-images.githubusercontent.com/84923831/128601225-afa38c1f-dea8-4209-9f72-0f84c1dd8b54.png)
með því að hnekkja miðlunarlyklum + virkja Ctrl/CMD + F til að leita + virkja linux mpris stuðning fyrir
miðlunarlyklar + [sérsniðnir flýtilyklar](https://github.com/Araxeus/youtube-music/blob/1e591d6a3df98449bcda6e63baab249b28026148/providers/song-controls.js#L13-L50)
fyrir [háþróaða notendur](https://github.com/th-ch/youtube-music/issues/106#issuecomment-952156902)
- **Slepptu Lögum sem Mislíkuðust**: Sleppir mislíkaði lög
- **Slepptu Þögnum**: Slepptu sjálfkrafa þagnarköflum í lögum
- [**Styrktarblokk**](https://github.com/ajayyy/SponsorBlock): Sleppur sjálfkrafa hlutum sem ekki eru tónlist, eins og inngangur/lok
eða hlutar af tónlistarmyndböndum þar sem lag er ekki að spila
- **Miðlunarstýringarverkefnastikunnar**: Stjórnaðu spilun frá [Windows verkefnastikunni þinni](https://user-images.githubusercontent.com/78568641/111916130-24a35e80-8a82-11eb-80c8-5021c1aa27f4.png)
- **Snertistiku**: Sérsniðið Snertistikuútlit fyrir macOS
- **Tuna OBS**: Samþætting við [OBS](https://obsproject.com/)
viðbótina [Tuna](https://obsproject.com/forum/resources/tuna.843/)
- **Myndbandgæðisbreyting**: Leyfir að breyta myndbandgæðum með
[hnappi](https://user-images.githubusercontent.com/78568641/138574366-70324a5e-2d64-4f6a-acdd-dc2a2b9cecc5.png) á
myndbandsyfirlaginu
- **Myndbandsrofi**: Bætir við [hnappi](https://user-images.githubusercontent.com/28893833/173663950-63e6610e-a532-49b7-9afa-54cb57ddfc15.png) til
að skipta á milli myndbands/lagshams. Getur einnig valfrjálst fjarlægt allan myndbandsflipann
- **Sjónrænir**: Mismunandi tónlist sjónrænir
## Þýðing
Þú getur aðstoðað við þýðingar á [Hosted Weblate](https://hosted.weblate.org/projects/youtube-music/).
<a href="https://hosted.weblate.org/engage/youtube-music/">
<img src="https://hosted.weblate.org/widget/youtube-music/i18n/multi-auto.svg" alt="translation status" />
<img src="https://hosted.weblate.org/widget/youtube-music/i18n/287x66-black.png" alt="translation status 2" />
</a>
## Sækja
Þú getur skoðað [nýjustu útgáfuna](https://github.com/th-ch/youtube-music/releases/latest) til að finna fljótt
nýjustu útgáfuna.
### Arch Linux
Settu upp [`youtube-music-bin`](https://aur.archlinux.org/packages/youtube-music-bin) pakkann frá AUR. Fyrir AUR uppsetningarleiðbeiningar skaltu skoða
þessa [wiki síðu](https://wiki.archlinux.org/index.php/Arch_User_Repository#Installing_packages).
### MacOS
Þú getur sett upp appið með því að nota Homebrew (sjá [cask skilgreiningu](https://github.com/th-ch/homebrew-youtube-music))
```bash
brew install th-ch/youtube-music/youtube-music
```
Ef þú setur upp forritið handvirkt og færð villu "er skemmd og ekki er hægt að opna það," þegar þú ræsir forritið skaltu keyra eftirfarandi í flugstöðinni:
```bash
xattr -cr /Applications/YouTube\ Music.app
```
### Windows
Þú getur notað [Scoop pakkastjórnun](https://scoop.sh) til að setja upp `youtube-music` pakkann frá
[`extras` fötuna](https://github.com/ScoopInstaller/Extras).
```bash
scoop bucket add extras
scoop install extras/youtube-music
```
Að öðrum kosti geturðu notað [Winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/), Windows 11s
opinber CLI pakkastjóri til að setja upp `th-ch.YouTubeMusic` pakkann.
*Athugið: Microsoft Defender SmartScreen gæti lokað uppsetningunni þar sem hún er frá „óþekktum útgefanda“. Þetta er einnig
satt fyrir handvirka uppsetningu þegar reynt er að keyra executable(.exe) eftir handvirkt niðurhal hér á github (sama
skrá).*
```bash
winget install th-ch.YouTubeMusic
```
#### Hvernig á að setja upp án nettengingar? (í Windows)
- Sæktu `*.nsis.7z` skrána fyrir _arkitektúr tækisins þíns_ á [útgáfusíðu](https://github.com/th-ch/youtube-music/releases/latest).
- `x64` fyrir 64-bita Windows
- `ia32` fyrir 32-bita Windows
- `arm64` fyrir ARM64 Windows
- Sæktu uppsetningarforrit á útgáfusíðu. (`*-Setup.exe`)
- Settu þær í **sömu möppuna**.
- Keyrðu uppsetningarforritið.
## Þemu
Þú getur hlaðið CSS skrám til að breyta útliti forritsins (Valkostir > Sjónræn klip > Þemu).
Sum fyrirframskilgreind þemu eru fáanleg á https://github.com/kerichdev/themes-for-ytmdesktop-player.
## Þróun
```bash
git clone https://github.com/th-ch/youtube-music
cd youtube-music
pnpm install --frozen-lockfile
pnpm dev
```
## Búðu til þín eigin viðbætur
Með því að nota viðbætur geturðu:
- vinna með appið - `BrowserWindow` frá electron er sent til viðbótarstjórans
- breyttu framhliðinni með því að vinna með HTML/CSS
### Er að búa til viðbót
Búðu til möppu í `src/plugins/YOUR-PLUGIN-NAME`:
- `index.ts`: aðal skránni af viðbótin
```typescript
import style from './style.css?inline'; // flytja inn stíl sem inline
import { createPlugin } from '@/utils';
export default createPlugin({
name: 'Plugin Label',
restartNeeded: true, // ef gildi er satt, ytmusic show endurræsa gluggann
config: {
enabled: false,
}, // sérsniðnastillingar þinn
stylesheets: [style], // sérsniðnastílinn þinn
menu: async ({ getConfig, setConfig }) => {
// Allar *stillingaraðferðir eru umvafnar Lofor<T>
const config = await getConfig();
return [
{
label: 'menu',
submenu: [1, 2, 3].map((value) => ({
label: `value ${value}`,
type: 'radio',
checked: config.value === value,
click() {
setConfig({ value });
},
})),
},
];
},
backend: {
start({ window, ipc }) {
window.maximize();
// þú getur tengst við renderer viðbótina
ipc.handle('some-event', () => {
return 'hello';
});
},
// það kviknaði þegar stillingum var breytt
onConfigChange(newConfig) { /* ... */ },
// it fired when plugin disabled
stop(context) { /* ... */ },
},
renderer: {
async start(context) {
console.log(await context.ipc.invoke('some-event'));
},
// Aðeins krókur sem er í boði fyrir renderer
onPlayerApiReady(api: YoutubePlayer, context: RendererContext) {
// stilltu stillingar viðbótarinnar auðveldlega
context.setConfig({ myConfig: api.getVolume() });
},
onConfigChange(newConfig) { /* ... */ },
stop(_context) { /* ... */ },
},
preload: {
async start({ getConfig }) {
const config = await getConfig();
},
onConfigChange(newConfig) {},
stop(_context) {},
},
});
```
### Algeng notkunartilvik
- er að sprauta sérsniðnum CSS: búðu til `style.css` skrá í sömu möppu þá:
```typescript
// index.ts
import style from './style.css?inline'; // flytja inn stíl sem inline
import { createPlugin } from '@/utils';
export default createPlugin({
name: 'Plugin Label',
restartNeeded: true, // ef gildi er satt, ytmusic show endurræsa gluggann
config: {
enabled: false,
}, // sérsniðnastillingar þinn
stylesheets: [style], // sérsniðnastílinn þinn
renderer() {} // skilgreina renderer krók
});
```
- Ef þú vilt breyta HTML:
```typescript
import { createPlugin } from '@/utils';
export default createPlugin({
name: 'Plugin Label',
restartNeeded: true, // ef gildi er satt, ytmusic show endurræsa gluggann
config: {
enabled: false,
}, // sérsniðnastillingar þinn
renderer() {
// Fjarlægðu innskráningarhnappinn
document.querySelector(".sign-in-link.ytmusic-nav-bar").remove();
} // skilgreina renderer krók
});
```
- samskipti á milli að framan og aftan: hægt að gera með því að nota ipcMain eininguna frá electron. Sjá `index.ts` skrá og
dæmi í 'styrktarblokk' viðbótinni.
## Byggja
1. Klóna geymsluna
2. Fylgdu [þessa handbók](https://pnpm.io/installation) til að setja upp 'pnpm'
3. Keyrðu `pnpm install --frozen-lockfile` til að setja upp ósjálfstæði
4. Keyrðu `pnpm build:OS`
- `pnpm dist:win` - Windows
- `pnpm dist:linux` - Linux
- `pnpm dist:mac` - MacOS
Byggir appið fyrir macOS, Linux og Windows,
með því að nota [electron-builder](https://github.com/electron-userland/electron-builder).
## Framleiðsluforskoðun
```bash
pnpm start
```
## Prófanir
```bash
pnpm test
```
Notar [Playwright](https://playwright.dev/) til að prófa forritið.
## Leyfi
MIT © [th-ch](https://github.com/th-ch/youtube-music)
## Algengustu Spurningar
### Hvers vegna forritavalmynd birtist ekki?
Ef valmöguleikinn „Fela valmynd“ er á - þú getur sýnt valmyndina með <kbd>alt</kbd> lyklinum (eða <kbd>\`</kbd> [bakka]
ef þú notar viðbótina fyrir valmynd í forriti)

348
docs/readme/README-ko.md Normal file
View File

@ -0,0 +1,348 @@
<div align="center">
# 유튜브 뮤직 (YouTube Music)
[![GitHub release](https://img.shields.io/github/release/th-ch/youtube-music.svg?style=for-the-badge&logo=youtube-music)](https://github.com/th-ch/youtube-music/releases/)
[![GitHub license](https://img.shields.io/github/license/th-ch/youtube-music.svg?style=for-the-badge)](https://github.com/th-ch/youtube-music/blob/master/LICENSE)
[![eslint code style](https://img.shields.io/badge/code_style-eslint-5ed9c7.svg?style=for-the-badge)](https://github.com/th-ch/youtube-music/blob/master/.eslintrc.js)
[![Build status](https://img.shields.io/github/actions/workflow/status/th-ch/youtube-music/build.yml?branch=master&style=for-the-badge&logo=youtube-music)](https://GitHub.com/th-ch/youtube-music/releases/)
[![GitHub All Releases](https://img.shields.io/github/downloads/th-ch/youtube-music/total?style=for-the-badge&logo=youtube-music)](https://GitHub.com/th-ch/youtube-music/releases/)
[![AUR](https://img.shields.io/aur/version/youtube-music-bin?color=blueviolet&style=for-the-badge&logo=youtube-music)](https://aur.archlinux.org/packages/youtube-music-bin)
[![Known Vulnerabilities](https://snyk.io/test/github/th-ch/youtube-music/badge.svg)](https://snyk.io/test/github/th-ch/youtube-music)
</div>
![Screenshot](../../web/screenshot.jpg "Screenshot")
<div align="center">
<a href="https://github.com/th-ch/youtube-music/releases/latest">
<img src="../../web/youtube-music.svg" width="400" height="100" alt="YouTube Music SVG">
</a>
</div>
**유튜브 뮤직의 Electron 래퍼; 기능:**
- 원래의 인터페이스를 유지하는 것을 목표로 하는 네이티브 디자인 및 느낌
- 맞춤 플러그인을 위한 프레임워크: 스타일, 콘텐츠, 기능 등 필요에 따라 유튜브 뮤직을 변경하고, 클릭 한 번으로 플러그인을 활성화/비활성화할 수 있습니다.
## Content
- [기능](#기능)
- [사용 가능한 플러그인](#사용-가능한-플러그인)
- [번역](#번역)
- [다운로드](#다운로드)
- [Arch Linux](#arch-linux)
- [MacOS](#macos)
- [Windows](#windows)
- [(Windows에서) 네트워크에 연결하지 않고 설치하는 방법은 무엇인가요?](#windows에서-네트워크에-연결하지-않고-설치하는-방법은-무엇인가요)
- [테마](#테마)
- [개발](#개발)
- [나만의 플러그인 만들기](#나만의-플러그인-만들기)
- [플러그인 만들기](#플러그인-만들기)
- [일반적인 사용 예](#일반적인-사용-예)
- [빌드](#빌드)
- [프로덕션 빌드 미리보기](#프로덕션-빌드-미리보기)
- [테스트](#테스트)
- [라이선스](#라이선스)
- [자주 묻는 질문](#자주-묻는-질문)
## 기능:
- **일시 정지 시 자동 확인** (항상 활성화 됨): 일정 시간이 지나면 음악을 일시 정지하는 ["계속 시청하시겠습니까?"](https://user-images.githubusercontent.com/61631665/129977894-01c60740-7ec6-4bf0-9a2c-25da24491b0e.png) 팝업을 비활성화합니다.
- 이외에 더 많은 기능 ...
## 사용 가능한 플러그인:
- **애드블록**: 모든 광고와 트래커를 즉시 차단합니다
- **앨범 컬러 기반 테마**: 앨범 색상 팔레트를 기반으로 동적 테마 및 시각 효과를 적용합니다
- **앰비언트 모드**: 영상의 간접 조명을 화면 배경에 투사합니다.
- **오디오 컴프레서**: 오디오에 컴프레서를 적용합니다 (신호에서 가장 시끄러운 부분의 음량을 낮추고 가장 조용한 부분의 음량을 높임)
- **네비게이션 바 흐림 효과**: 내비게이션 바를 투명하고 흐릿하게 만듭니다
- **나이 제한 우회**: 유튜브의 나이 제한을 우회합니다
- **자막 선택기**: 자막을 활성화합니다
- **컴팩트 사이드바**: 사이드바를 항상 컴팩트 모드로 설정합니다
- **크로스페이드**: 노래 사이에 크로스페이드 효과를 적용합니다
- **자동 재생 해제**: 노래를 '일시 정지' 모드로 시작하게 합니다
- [**디스코드 활동 상태**](https://discord.com/): [활동 상태 (Rich Presence)](https://user-images.githubusercontent.com/28219076/104362104-a7a0b980-5513-11eb-9744-bb89eabe0016.png)를 사용하여 친구들에게 내가 듣는 음악을 보여주세요
- **다운로더**: UI에서 [직접](https://user-images.githubusercontent.com/61631665/129977677-83a7d067-c192-45e1-98ae-b5a4927393be.png) MP3/소스 오디오를 다운로드하세요
- **지수 볼륨**: 음량 슬라이더를 [지수적](https://greasyfork.org/en/scripts/397686-youtube-music-fix-volume-ratio/)으로 만들어 더 낮은 음량을 쉽게 선택할 수 있도록 합니다.
- **인앱 메뉴**: [메뉴 표시줄을 더 멋지게, 그리고 다크 또는 앨범의 색상으로 만듭니다](https://user-images.githubusercontent.com/78568641/112215894-923dbf00-8c29-11eb-95c3-3ce15db27eca.png)
> (이 플러그인 및 메뉴 숨기기 옵션을 활성화한 후 메뉴에 액세스하는 데 문제가 있는 경우 [이 글](https://github.com/th-ch/youtube-music/issues/410#issuecomment-952060709)을 참조하세요)
- [**Last.fm**](https://www.last.fm/): Last.fm에 대한 스크러블 지원을 추가합니다
- **Lumia Stream**: [Lumia Stream](https://lumiastream.com/) 지원을 추가합니다
- **Genius 가사**: 더 많은 곡에 대해 가사 지원을 추가합니다
- **네비게이션**: 브라우저에서처럼, UI에 직접 통합된 앞으로/뒤로 탐색하는 화살표를 추가합니다
- **Google 로그인 제거**: UI에서 Google 로그인 버튼 및 링크 제거하기
- **알림**: 노래 재생이 시작되면 알림을 표시 (Windows에서는 [대화형 알림](https://user-images.githubusercontent.com/78568641/114102651-63ce0e00-98d0-11eb-9dfe-c5a02bb54f9c.png) 사용 가능)
- **PiP**: 앱을 PiP 모드로 전환할 수 있게 허용합니다
- **재생 속도**: 빨리 듣거나, 천천히 들어보세요! [노래 속도를 제어하는 슬라이더를 추가합니다](https://user-images.githubusercontent.com/61631665/129976003-e55db5ba-bf42-448c-a059-26a009775e68.png)
- **정확한 음량**: 사용자 지정 HUD와 사용자 지정 음량 단계 및 마우스 휠/단축키를 사용하여 음량을 정확하게 제어하세요
- **영상 품질 체인저**: 영상 오버레이의 [버튼](https://user-images.githubusercontent.com/78568641/138574366-70324a5e-2d64-4f6a-acdd-dc2a2b9cecc5.png)으로 영상 품질을 변경할 수 있게 합니다
- **단축키 (& MPRIS)**: 재생을 위한 전역 단축키 설정 허용 (재생/일시 정지/다음/이전) + 미디어 키를 재정의하여 [미디어 osd](https://user-images.githubusercontent.com/84923831/128601225-afa38c1f-dea8-4209-9f72-0f84c1dd8b54.png) 비활성화 + Ctrl/CMD + F 검색 활성화 + 미디어 키에 대한 리눅스 MPRIS 지원 활성화 + [고급 사용자](https://github.com/th-ch/youtube-music/issues/106#issuecomment-952156902)를 위한 [사용자 지정 단축키](https://github.com/Araxeus/youtube-music/blob/1e591d6a3df98449bcda6e63baab249b28026148/providers/song-controls.js#L13-L50) 지원
- **무음 건너뛰기** - 노래의 무음 부분을 자동으로 건너뜁니다
- [**SponsorBlock**](https://github.com/ajayyy/SponsorBlock): 인트로/아웃트로와 같은 음악이 아닌 부분이나, 노래가 재생되지 않는 뮤직 비디오의 일부를 자동으로 건너뜁니다
- **작업표시줄 미디어 컨트롤**: [Windows 작업표시줄](https://user-images.githubusercontent.com/78568641/111916130-24a35e80-8a82-11eb-80c8-5021c1aa27f4.png)에서 재생을 제어하세요
- **TouchBar**: macOS 사용자를 위한 TouchBar 위젯을 추가합니다
- **Tuna-OBS**: [OBS](https://obsproject.com/)의 플러그인, [Tuna](https://obsproject.com/forum/resources/tuna.843/)와 통합을 활성화합니다
- **영상 전환**: 영상/노래 모드를 전환하는 [버튼](https://user-images.githubusercontent.com/28893833/173663950-63e6610e-a532-49b7-9afa-54cb57ddfc15.png)을 추가합니다. 선택적으로 전체 영상 탭을 제거할 수도 있습니다
- **비주얼라이저**: 플레이어에 시각화 도구 추가
## 번역
[Hosted Weblate](https://hosted.weblate.org/projects/youtube-music/)에서 번역을 도울 수 있습니다.
<a href="https://hosted.weblate.org/engage/youtube-music/">
<img src="https://hosted.weblate.org/widget/youtube-music/i18n/multi-auto.svg" alt="번역 상태" />
<img src="https://hosted.weblate.org/widget/youtube-music/i18n/287x66-black.png" alt="번역 상태 2" />
</a>
## 다운로드
[최신 릴리즈](https://github.com/th-ch/youtube-music/releases/latest)를 확인하여 최신 버전을 빠르게 찾을 수 있습니다.
### Arch Linux
AUR에서 [`youtube-music-bin`](https://aur.archlinux.org/packages/youtube-music-bin) 패키지를 설치합니다. AUR 설치 지침은 [이 위키 페이지](https://wiki.archlinux.org/index.php/Arch_User_Repository#Installing_packages)를 참조하세요.
### MacOS
Homebrew를 사용하여 앱을 설치할 수 있습니다:
```bash
brew install --cask https://raw.githubusercontent.com/th-ch/youtube-music/master/youtube-music.rb
```
(앱을 수동으로 설치하고) 앱을 실행할 때 `손상되었기 때문에 열 수 없습니다.`라는 오류가 발생하면 터미널에서 다음을 실행하세요:
```bash
xattr -cr /Applications/YouTube\ Music.app
```
### Windows
[Scoop 패키지 매니저](https://scoop.sh)를 사용하여 [`extras` 버킷](https://github.com/ScoopInstaller/Extras)에서 `youtube-music` 패키지를 설치할 수 있습니다.
```bash
scoop bucket add extras
scoop install extras/youtube-music
```
또는 Windows 11의 공식 CLI 패키지 관리자인 [Winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/)을 사용하여 `th-ch.YouTubeMusic` 패키지를 설치할 수 있습니다.
*참고: "알 수 없는 게시자"의 파일이기 때문에 Microsoft Defender의 SmartScreen에서 설치를 차단할 수 있습니다. 이는 GitHub에서 동일 파일을 수동으로 다운로드한 후 실행 파일(.exe)을 실행하려고 할 때도 마찬가지로 발생합니다.*
```bash
winget install th-ch.YouTubeMusic
```
#### (Windows에서) 네트워크에 연결하지 않고 설치하는 방법은 무엇인가요?
- [릴리즈 페이지](https://github.com/th-ch/youtube-music/releases/latest)에서 _본인 기기 아키텍처_에 맞는 `*.nsis.7z` 파일을 다운로드하세요.
- `x64`는 64비트 Windows 용입니다.
- `ia32`는 32비트 Windows 용입니다.
- `arm64`는 ARM64 Windows 용입니다.
- 릴리즈 페이지에서 설치기를 다운로드하세요. (`*-Setup.exe`)
- 두 파일을 **동일한 위치**에 놓아주세요.
- 설치기를 실행하세요.
## 테마
CSS 파일을 로드하여 애플리케이션의 모양을 변경할 수 있습니다(설정 > 시각적 변경 > 테마).
일부 사전 정의 테마는 https://github.com/kerichdev/themes-for-ytmdesktop-player 에서 사용할 수 있습니다.
## 개발
```bash
git clone https://github.com/th-ch/youtube-music
cd youtube-music
pnpm install --frozen-lockfile
pnpm dev
```
## 나만의 플러그인 만들기
플러그인을 사용하면 할 수 있는 것들:
- 앱 조작 - Electron에서 `BrowserWindow`가 플러그인 핸들러로 전달
- HTML/CSS를 조작하여 프론트엔드를 변경
### 플러그인 만들기
`plugins/나만의-플러그인-이름`에 폴더를 만듭니다:
- `index.ts`: 플러그인의 메인 파일입니다.
```typescript
import style from './style.css?inline'; // 스타일을 인라인으로 가져옵니다
import { createPlugin } from '@/utils';
export default createPlugin({
name: 'Plugin Label',
restartNeeded: true, // 값이 true면, YTM은 재시작 다이얼로그를 표시합니다
config: {
enabled: false,
}, // 나의 커스텀 config
stylesheets: [style], // 나의 스타일
menu: async ({ getConfig, setConfig }) => {
// 모든 *Config 메서드는 Promise<T>로 래핑됩니다
const config = await getConfig();
return [
{
label: 'menu',
submenu: [1, 2, 3].map((value) => ({
label: `value ${value}`,
type: 'radio',
checked: config.value === value,
click() {
setConfig({ value });
},
})),
},
];
},
backend: {
start({ window, ipc }) {
window.maximize();
// 이를 사용하여 렌더러 플러그인과 통신할 수 있습니다
ipc.handle('some-event', () => {
return 'hello';
});
},
// config가 변경되면 실행됩니다
onConfigChange(newConfig) { /* ... */ },
// 플러그인이 비활성화되면 실행됩니다
stop(context) { /* ... */ },
},
renderer: {
async start(context) {
console.log(await context.ipc.invoke('some-event'));
},
// 렌더러에서만 사용 가능한 훅입니다
onPlayerApiReady(api: YoutubePlayer, context: RendererContext<T>) {
// 플러그인의 config를 간단하게 설정할 수 있습니다
context.setConfig({ myConfig: api.getVolume() });
},
onConfigChange(newConfig) { /* ... */ },
stop(_context) { /* ... */ },
},
preload: {
async start({ getConfig }) {
const config = await getConfig();
},
onConfigChange(newConfig) {},
stop(_context) {},
},
});
```
### 일반적인 사용 예
- 사용자 정의 CSS 삽입: 같은 폴더에 `style.css` 파일을 생성합니다:
```typescript
// index.ts
import style from './style.css?inline'; // 스타일을 인라인으로 가져옵니다
import { createPlugin } from '@/utils';
export default createPlugin({
name: 'Plugin Label',
restartNeeded: true, // 값이 true면, YTM은 재시작 다이얼로그를 표시합니다
config: {
enabled: false,
}, // 나의 커스텀 config
stylesheets: [style], // 나의 커스텀 스타일
renderer() {} // 렌더러 훅 정의
});
```
- HTML을 변경하려는 경우:
```typescript
import { createPlugin } from '@/utils';
export default createPlugin({
name: 'Plugin Label',
restartNeeded: true, // 값이 true면, YTM은 재시작 다이얼로그를 표시합니다
config: {
enabled: false,
}, // 나의 커스텀 config
renderer() {
// 로그인 버튼을 제거합니다
document.querySelector(".sign-in-link.ytmusic-nav-bar").remove();
} // 렌더러 훅 정의
});
```
- 프론트엔드와 백엔드 간의 통신: Electron의 `ipcMain` 모듈을 사용하여 수행할 수 있습니다. `SponsorBlock` 플러그인의 `index.ts` 파일과 예제를 참조하세요.
## 빌드
1. 레포지토리를 복제 (clone) 합니다
2. [이 가이드](https://pnpm.io/installation)에 따라 `pnpm`을 설치합니다.
3. `pnpm install --frozen-lockfile`을 실행하여 종속성을 설치합니다.
4. `pnpm build:OS`을 실행합니다.
- `pnpm dist:win` - Windows
- `pnpm dist:linux` - Linux
- `pnpm dist:mac` - MacOS
[electron-builder](https://github.com/electron-userland/electron-builder)를 사용하여 macOS, Linux 및 Windows용 앱을 빌드합니다.
## 프로덕션 빌드 미리보기
```bash
pnpm start
```
## 테스트
```bash
pnpm test
```
[Playwright](https://playwright.dev/)를 사용하여 앱을 테스트합니다.
## 라이선스
MIT © [th-ch](https://github.com/th-ch/youtube-music)
## 자주 묻는 질문
### 앱 메뉴가 표시되지 않는 이유는 무엇인가요?
`메뉴 숨기기` 옵션이 켜져 있는 경우 - <kbd>alt</kbd> 키(또는 인앱 메뉴 플러그인을 사용하는 경우 <kbd>\`</kbd> [백틱] 키)로 메뉴를 표시할 수 있습니다.

160
electron.vite.config.mts Normal file
View File

@ -0,0 +1,160 @@
import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { defineConfig, defineViteConfig } from 'electron-vite';
import builtinModules from 'builtin-modules';
import viteResolve from 'vite-plugin-resolve';
import Inspect from 'vite-plugin-inspect';
import { pluginVirtualModuleGenerator } from './vite-plugins/plugin-importer.mjs';
import pluginLoader from './vite-plugins/plugin-loader.mjs';
import type { UserConfig } from 'vite';
import { i18nImporter } from './vite-plugins/i18n-importer.mjs';
import solidPlugin from 'vite-plugin-solid';
const __dirname = dirname(fileURLToPath(import.meta.url));
const resolveAlias = {
'@': resolve(__dirname, './src'),
'@assets': resolve(__dirname, './assets'),
};
export default defineConfig({
main: defineViteConfig(({ mode }) => {
const commonConfig: UserConfig = {
plugins: [
pluginLoader('backend'),
viteResolve({
'virtual:i18n': i18nImporter(),
'virtual:plugins': pluginVirtualModuleGenerator('main'),
}),
],
publicDir: 'assets',
build: {
lib: {
entry: 'src/index.ts',
formats: ['cjs'],
},
outDir: 'dist/main',
commonjsOptions: {
ignoreDynamicRequires: true,
},
rollupOptions: {
external: ['electron', 'custom-electron-prompt', ...builtinModules],
input: './src/index.ts',
},
},
resolve: {
alias: resolveAlias,
},
};
if (mode === 'development') {
commonConfig.plugins?.push(
Inspect({ build: true, outputDir: '.vite-inspect/backend' }),
);
return commonConfig;
}
return {
...commonConfig,
build: {
...commonConfig.build,
minify: true,
cssMinify: true,
},
};
}),
preload: defineViteConfig(({ mode }) => {
const commonConfig: UserConfig = {
plugins: [
pluginLoader('preload'),
viteResolve({
'virtual:i18n': i18nImporter(),
'virtual:plugins': pluginVirtualModuleGenerator('preload'),
}),
],
build: {
lib: {
entry: 'src/preload.ts',
formats: ['cjs'],
},
outDir: 'dist/preload',
commonjsOptions: {
ignoreDynamicRequires: true,
},
rollupOptions: {
external: ['electron', 'custom-electron-prompt', ...builtinModules],
input: './src/preload.ts',
},
},
resolve: {
alias: resolveAlias,
},
};
if (mode === 'development') {
commonConfig.plugins?.push(
Inspect({ build: true, outputDir: '.vite-inspect/preload' }),
);
return commonConfig;
}
return {
...commonConfig,
build: {
...commonConfig.build,
minify: true,
cssMinify: true,
},
};
}),
renderer: defineViteConfig(({ mode }) => {
const commonConfig: UserConfig = {
plugins: [
pluginLoader('renderer'),
viteResolve({
'virtual:i18n': i18nImporter(),
'virtual:plugins': pluginVirtualModuleGenerator('renderer'),
}),
solidPlugin(),
],
root: './src/',
build: {
lib: {
entry: 'src/index.html',
formats: ['iife'],
name: 'renderer',
},
outDir: 'dist/renderer',
commonjsOptions: {
ignoreDynamicRequires: true,
},
rollupOptions: {
external: ['electron', ...builtinModules],
input: './src/index.html',
},
},
resolve: {
alias: resolveAlias,
},
};
if (mode === 'development') {
commonConfig.plugins?.push(
Inspect({ build: true, outputDir: '.vite-inspect/renderer' }),
);
return commonConfig;
}
return {
...commonConfig,
build: {
...commonConfig.build,
minify: true,
cssMinify: true,
},
};
}),
});

View File

@ -1,50 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Cannot load YouTube Music</title>
<style>
body {
background: #000;
}
.container {
margin: 0;
font-family: Roboto, Arial, sans-serif;
font-size: 20px;
font-weight: 500;
color: rgba(255, 255, 255, 0.5);
position: absolute;
top: 50%;
left: 50%;
margin-right: -50%;
transform: translate(-50%, -50%);
text-align: center;
}
.button {
background: #065fd4;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: white;
font: inherit;
text-transform: uppercase;
text-decoration: none;
border-radius: 2px;
font-size: 16px;
font-weight: normal;
text-align: center;
padding: 8px 22px;
display: inline-block;
}
</style>
</head>
<body>
<div class="container">
<p>Cannot load YouTube Music… Internet disconnected?</p>
<a class="button" href="#" onclick="reload()">Retry</a>
</div>
</body>
</html>

642
index.ts
View File

@ -1,642 +0,0 @@
import path from 'node:path';
import { BrowserWindow, app, screen, globalShortcut, session, shell, dialog, ipcMain } from 'electron';
import enhanceWebRequest from 'electron-better-web-request';
import is from 'electron-is';
import unhandled from 'electron-unhandled';
import { autoUpdater } from 'electron-updater';
import electronDebug from 'electron-debug';
import { BetterWebRequest } from 'electron-better-web-request/lib/electron-better-web-request';
import config from './config';
import { setApplicationMenu } from './menu';
import { fileExists, injectCSS, injectCSSAsFile } from './plugins/utils';
import { isTesting } from './utils/testing';
import { setUpTray } from './tray';
import { setupSongInfo } from './providers/song-info';
import { restart, setupAppControls } from './providers/app-controls';
import { APP_PROTOCOL, handleProtocol, setupProtocolHandler } from './providers/protocol-handler';
import adblocker from './plugins/adblocker/back';
import albumColorTheme from './plugins/album-color-theme/back';
import ambientMode from './plugins/ambient-mode/back';
import blurNavigationBar from './plugins/blur-nav-bar/back';
import captionsSelector from './plugins/captions-selector/back';
import crossfade from './plugins/crossfade/back';
import discord from './plugins/discord/back';
import downloader from './plugins/downloader/back';
import inAppMenu from './plugins/in-app-menu/back';
import lastFm from './plugins/last-fm/back';
import lumiaStream from './plugins/lumiastream/back';
import lyricsGenius from './plugins/lyrics-genius/back';
import navigation from './plugins/navigation/back';
import noGoogleLogin from './plugins/no-google-login/back';
import notifications from './plugins/notifications/back';
import pictureInPicture, { setOptions as pipSetOptions } from './plugins/picture-in-picture/back';
import preciseVolume from './plugins/precise-volume/back';
import qualityChanger from './plugins/quality-changer/back';
import shortcuts from './plugins/shortcuts/back';
import sponsorBlock from './plugins/sponsorblock/back';
import taskbarMediaControl from './plugins/taskbar-mediacontrol/back';
import touchbar from './plugins/touchbar/back';
import tunaObs from './plugins/tuna-obs/back';
import videoToggle from './plugins/video-toggle/back';
import visualizer from './plugins/visualizer/back';
import youtubeMusicCSS from './youtube-music.css';
// Catch errors and log them
unhandled({
logger: console.error,
showDialog: false,
});
// Disable Node options if the env var is set
process.env.NODE_OPTIONS = '';
// Prevent window being garbage collected
let mainWindow: Electron.BrowserWindow | null;
autoUpdater.autoDownload = false;
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
app.exit();
}
// SharedArrayBuffer: Required for downloader (@ffmpeg/core-mt)
// OverlayScrollbar: Required for overlay scrollbars
app.commandLine.appendSwitch('enable-features', 'OverlayScrollbar,SharedArrayBuffer');
if (config.get('options.disableHardwareAcceleration')) {
if (is.dev()) {
console.log('Disabling hardware acceleration');
}
app.disableHardwareAcceleration();
}
if (is.linux() && config.plugins.isEnabled('shortcuts')) {
// Stops chromium from launching its own MPRIS service
app.commandLine.appendSwitch('disable-features', 'MediaSessionService');
}
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
electronDebug({
showDevTools: false, // Disable automatic devTools on new window
});
let icon = 'assets/youtube-music.png';
if (process.platform === 'win32') {
icon = 'assets/generated/icon.ico';
} else if (process.platform === 'darwin') {
icon = 'assets/generated/icon.icns';
}
function onClosed() {
// Dereference the window
// For multiple Windows store them in an array
mainWindow = null;
}
const mainPlugins = {
'adblocker': adblocker,
'album-color-theme': albumColorTheme,
'ambient-mode': ambientMode,
'blur-nav-bar': blurNavigationBar,
'captions-selector': captionsSelector,
'crossfade': crossfade,
'discord': discord,
'downloader': downloader,
'in-app-menu': inAppMenu,
'last-fm': lastFm,
'lumiastream': lumiaStream,
'lyrics-genius': lyricsGenius,
'navigation': navigation,
'no-google-login': noGoogleLogin,
'notifications': notifications,
'picture-in-picture': pictureInPicture,
'precise-volume': preciseVolume,
'quality-changer': qualityChanger,
'shortcuts': shortcuts,
'sponsorblock': sponsorBlock,
'taskbar-mediacontrol': undefined as typeof taskbarMediaControl | undefined,
'touchbar': undefined as typeof touchbar | undefined,
'tuna-obs': tunaObs,
'video-toggle': videoToggle,
'visualizer': visualizer,
};
export const mainPluginNames = Object.keys(mainPlugins);
if (is.windows()) {
mainPlugins['taskbar-mediacontrol'] = taskbarMediaControl;
delete mainPlugins['touchbar'];
} else if (is.macOS()) {
mainPlugins['touchbar'] = touchbar;
delete mainPlugins['taskbar-mediacontrol'];
} else {
delete mainPlugins['touchbar'];
delete mainPlugins['taskbar-mediacontrol'];
}
ipcMain.handle('get-main-plugin-names', () => Object.keys(mainPlugins));
function loadPlugins(win: BrowserWindow) {
injectCSS(win.webContents, youtubeMusicCSS);
// Load user CSS
const themes: string[] = config.get('options.themes');
if (Array.isArray(themes)) {
for (const cssFile of themes) {
fileExists(
cssFile,
() => {
injectCSSAsFile(win.webContents, cssFile);
},
() => {
console.warn(`CSS file "${cssFile}" does not exist, ignoring`);
},
);
}
}
win.webContents.once('did-finish-load', () => {
if (is.dev()) {
console.log('did finish load');
win.webContents.openDevTools();
}
});
for (const [plugin, options] of config.plugins.getEnabled()) {
try {
if (Object.hasOwn(mainPlugins, plugin)) {
console.log('Loaded plugin - ' + plugin);
const handler = mainPlugins[plugin as keyof typeof mainPlugins];
if (handler) {
handler(win, options as never);
}
}
} catch (e) {
console.error(`Failed to load plugin "${plugin}"`, e);
}
}
}
function createMainWindow() {
const windowSize = config.get('window-size');
const windowMaximized = config.get('window-maximized');
const windowPosition: Electron.Point = config.get('window-position');
const useInlineMenu = config.plugins.isEnabled('in-app-menu');
const win = new BrowserWindow({
icon,
width: windowSize.width,
height: windowSize.height,
backgroundColor: '#000',
show: false,
webPreferences: {
// TODO: re-enable contextIsolation once it can work with FFMpeg.wasm
// Possible bundling? https://github.com/ffmpegwasm/ffmpeg.wasm/issues/126
contextIsolation: false,
preload: path.join(__dirname, 'preload.js'),
nodeIntegrationInSubFrames: true,
...(isTesting()
? undefined
: {
// Sandbox is only enabled in tests for now
// See https://www.electronjs.org/docs/latest/tutorial/sandbox#preload-scripts
sandbox: false,
}),
},
frame: !is.macOS() && !useInlineMenu,
titleBarOverlay: {
color: '#00000000',
symbolColor: '#ffffff',
height: 36,
},
titleBarStyle: useInlineMenu
? 'hidden'
: (is.macOS()
? 'hiddenInset'
: 'default'),
autoHideMenuBar: config.get('options.hideMenu'),
});
loadPlugins(win);
if (windowPosition) {
const { x: windowX, y: windowY } = windowPosition;
const winSize = win.getSize();
const displaySize
= screen.getDisplayNearestPoint(windowPosition).bounds;
if (
windowX + winSize[0] < displaySize.x - 8
|| windowX - winSize[0] > displaySize.x + displaySize.width
|| windowY < displaySize.y - 8
|| windowY > displaySize.y + displaySize.height
) {
// Window is offscreen
if (is.dev()) {
console.log(
`Window tried to render offscreen, windowSize=${String(winSize)}, displaySize=${String(displaySize)}, position=${String(windowPosition)}`,
);
}
} else {
win.setPosition(windowX, windowY);
}
}
if (windowMaximized) {
win.maximize();
}
if (config.get('options.alwaysOnTop')) {
win.setAlwaysOnTop(true);
}
const urlToLoad = config.get('options.resumeOnStart')
? config.get('url')
: config.defaultConfig.url;
win.webContents.loadURL(urlToLoad);
win.on('closed', onClosed);
type PiPOptions = typeof config.defaultConfig.plugins['picture-in-picture'];
const setPiPOptions = config.plugins.isEnabled('picture-in-picture')
// eslint-disable-next-line @typescript-eslint/no-var-requires
? (key: string, value: unknown) => pipSetOptions({ [key]: value })
: () => {};
win.on('move', () => {
if (win.isMaximized()) {
return;
}
const position = win.getPosition();
const isPiPEnabled: boolean
= config.plugins.isEnabled('picture-in-picture')
&& config.plugins.getOptions<PiPOptions>('picture-in-picture').isInPiP;
if (!isPiPEnabled) {
lateSave('window-position', { x: position[0], y: position[1] });
} else if (config.plugins.getOptions<PiPOptions>('picture-in-picture').savePosition) {
lateSave('pip-position', position, setPiPOptions);
}
});
let winWasMaximized: boolean;
win.on('resize', () => {
const windowSize = win.getSize();
const isMaximized = win.isMaximized();
const isPiPEnabled
= config.plugins.isEnabled('picture-in-picture')
&& config.plugins.getOptions<PiPOptions>('picture-in-picture').isInPiP;
if (!isPiPEnabled && winWasMaximized !== isMaximized) {
winWasMaximized = isMaximized;
config.set('window-maximized', isMaximized);
}
if (isMaximized) {
return;
}
if (!isPiPEnabled) {
lateSave('window-size', {
width: windowSize[0],
height: windowSize[1],
});
} else if (config.plugins.getOptions<PiPOptions>('picture-in-picture').saveSize) {
lateSave('pip-size', windowSize, setPiPOptions);
}
});
const savedTimeouts: Record<string, NodeJS.Timeout | undefined> = {};
function lateSave(key: string, value: unknown, fn: (key: string, value: unknown) => void = config.set) {
if (savedTimeouts[key]) {
clearTimeout(savedTimeouts[key]);
}
savedTimeouts[key] = setTimeout(() => {
fn(key, value);
savedTimeouts[key] = undefined;
}, 600);
}
app.on('render-process-gone', (event, webContents, details) => {
showUnresponsiveDialog(win, details);
});
win.once('ready-to-show', () => {
if (config.get('options.appVisible')) {
win.show();
}
});
removeContentSecurityPolicy();
return win;
}
app.once('browser-window-created', (event, win) => {
if (config.get('options.overrideUserAgent')) {
// User agents are from https://developers.whatismybrowser.com/useragents/explore/
const originalUserAgent = win.webContents.userAgent;
const userAgents = {
mac: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 12.1; rv:95.0) Gecko/20100101 Firefox/95.0',
windows: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:95.0) Gecko/20100101 Firefox/95.0',
linux: 'Mozilla/5.0 (Linux x86_64; rv:95.0) Gecko/20100101 Firefox/95.0',
};
const updatedUserAgent
= is.macOS() ? userAgents.mac
: (is.windows() ? userAgents.windows
: userAgents.linux);
win.webContents.userAgent = updatedUserAgent;
app.userAgentFallback = updatedUserAgent;
win.webContents.session.webRequest.onBeforeSendHeaders((details, cb) => {
// This will only happen if login failed, and "retry" was pressed
if (win.webContents.getURL().startsWith('https://accounts.google.com') && details.url.startsWith('https://accounts.google.com')) {
details.requestHeaders['User-Agent'] = originalUserAgent;
}
cb({ requestHeaders: details.requestHeaders });
});
}
setupSongInfo(win);
setupAppControls();
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(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.on('will-prevent-unload', (event) => {
event.preventDefault();
});
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
// Unregister all shortcuts.
globalShortcut.unregisterAll();
});
app.on('activate', () => {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) {
mainWindow = createMainWindow();
} else if (!mainWindow.isVisible()) {
mainWindow.show();
}
});
app.on('ready', () => {
if (config.get('options.autoResetAppCache')) {
// Clear cache after 20s
const clearCacheTimeout = setTimeout(() => {
if (is.dev()) {
console.log('Clearing app cache.');
}
session.defaultSession.clearCache();
clearTimeout(clearCacheTimeout);
}, 20_000);
}
// 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 = 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
shell.writeShortcutLink(
shortcutPath,
error === 'needUpdate' ? 'update' : 'create',
{
target: appLocation,
cwd: path.dirname(appLocation),
description: 'YouTube Music Desktop App - including custom plugins',
appUserModelId: appID,
},
);
}
}
}
mainWindow = createMainWindow();
setApplicationMenu(mainWindow);
setUpTray(app, mainWindow);
setupProtocolHandler(mainWindow);
app.on('second-instance', (_, commandLine) => {
const uri = `${APP_PROTOCOL}://`;
const protocolArgv = commandLine.find((arg) => arg.startsWith(uri));
if (protocolArgv) {
const lastIndex = protocolArgv.endsWith('/') ? -1 : undefined;
const command = protocolArgv.slice(uri.length, lastIndex);
if (is.dev()) {
console.debug(`Received command over protocol: "${command}"`);
}
handleProtocol(command);
return;
}
if (!mainWindow) {
return;
}
if (mainWindow.isMinimized()) {
mainWindow.restore();
}
if (!mainWindow.isVisible()) {
mainWindow.show();
}
mainWindow.focus();
});
// Autostart at login
app.setLoginItemSettings({
openAtLogin: config.get('options.startAtLogin'),
});
if (!is.dev() && config.get('options.autoUpdates')) {
const updateTimeout = setTimeout(() => {
autoUpdater.checkForUpdatesAndNotify();
clearTimeout(updateTimeout);
}, 2000);
autoUpdater.on('update-available', () => {
const downloadLink
= 'https://github.com/th-ch/youtube-music/releases/latest';
const dialogOptions: Electron.MessageBoxOptions = {
type: 'info',
buttons: ['OK', 'Download', 'Disable updates'],
title: 'Application Update',
message: 'A new version is available',
detail: `A new version is available and can be downloaded at ${downloadLink}`,
};
dialog.showMessageBox(dialogOptions).then((dialogOutput) => {
switch (dialogOutput.response) {
// Download
case 1: {
shell.openExternal(downloadLink);
break;
}
// Disable updates
case 2: {
config.set('options.autoUpdates', false);
break;
}
default: {
break;
}
}
});
});
}
if (config.get('options.hideMenu') && !config.get('options.hideMenuWarned')) {
dialog.showMessageBox(mainWindow, {
type: 'info', title: 'Hide Menu Enabled',
message: "Menu is hidden, use 'Alt' to show it (or 'Escape' if using in-app-menu)",
});
config.set('options.hideMenuWarned', true);
}
// Optimized for Mac OS X
if (is.macOS() && !config.get('options.appVisible')) {
app.dock.hide();
}
let forceQuit = false;
app.on('before-quit', () => {
forceQuit = true;
});
if (is.macOS() || config.get('options.tray')) {
mainWindow.on('close', (event) => {
// Hide the window instead of quitting (quit is available in tray options)
if (!forceQuit) {
event.preventDefault();
mainWindow!.hide();
}
});
}
});
function showUnresponsiveDialog(win: BrowserWindow, details: Electron.RenderProcessGoneDetails) {
if (details) {
console.log('Unresponsive Error!\n' + JSON.stringify(details, null, '\t'));
}
dialog.showMessageBox(win, {
type: 'error',
title: 'Window Unresponsive',
message: 'The Application is Unresponsive',
detail: 'We are sorry for the inconvenience! please choose what to do:',
buttons: ['Wait', 'Relaunch', 'Quit'],
cancelId: 0,
}).then((result) => {
switch (result.response) {
case 1: {
restart();
break;
}
case 2: {
app.quit();
break;
}
}
});
}
// HACK: electron-better-web-request's typing is wrong
type BetterSession = Omit<Electron.Session, 'webRequest'> & { webRequest: BetterWebRequest & Electron.WebRequest };
function removeContentSecurityPolicy(
betterSession: BetterSession = session.defaultSession as BetterSession,
) {
// Allows defining multiple "onHeadersReceived" listeners
// by enhancing the session.
// Some plugins (e.g. adblocker) also define a "onHeadersReceived" listener
enhanceWebRequest(betterSession);
// Custom listener to tweak the content security policy
betterSession.webRequest.onHeadersReceived((details, callback) => {
details.responseHeaders ??= {};
// Remove the content security policy
delete details.responseHeaders['content-security-policy-report-only'];
delete details.responseHeaders['content-security-policy'];
callback({ cancel: false, responseHeaders: details.responseHeaders });
});
type ResolverListener = { apply: () => Promise<Record<string, unknown>>; context: unknown };
// When multiple listeners are defined, apply them all
betterSession.webRequest.setResolver('onHeadersReceived', async (listeners: ResolverListener[]) => {
return listeners.reduce<Promise<Record<string, unknown>>>(
async (accumulator: Promise<Record<string, unknown>>, listener: ResolverListener) => {
const acc = await accumulator;
if (acc.cancel) {
return acc;
}
const result = await listener.apply();
return { ...accumulator, ...result };
},
Promise.resolve({ cancel: false }),
);
});
}

472
menu.ts
View File

@ -1,472 +0,0 @@
import is from 'electron-is';
import { app, BrowserWindow, clipboard, dialog, Menu } from 'electron';
import prompt from 'custom-electron-prompt';
import { restart } from './providers/app-controls';
import config from './config';
import { startingPages } from './providers/extracted-data';
import promptOptions from './providers/prompt-options';
import adblockerMenu from './plugins/adblocker/menu';
import captionsSelectorMenu from './plugins/captions-selector/menu';
import crossfadeMenu from './plugins/crossfade/menu';
import disableAutoplayMenu from './plugins/disable-autoplay/menu';
import discordMenu from './plugins/discord/menu';
import downloaderMenu from './plugins/downloader/menu';
import lyricsGeniusMenu from './plugins/lyrics-genius/menu';
import notificationsMenu from './plugins/notifications/menu';
import pictureInPictureMenu from './plugins/picture-in-picture/menu';
import preciseVolumeMenu from './plugins/precise-volume/menu';
import shortcutsMenu from './plugins/shortcuts/menu';
import videoToggleMenu from './plugins/video-toggle/menu';
import visualizerMenu from './plugins/visualizer/menu';
import { getAvailablePluginNames } from './plugins/utils';
export type MenuTemplate = Electron.MenuItemConstructorOptions[];
// True only if in-app-menu was loaded on launch
const inAppMenuActive = config.plugins.isEnabled('in-app-menu');
const betaPlugins = ['crossfade', 'lumiastream'];
const pluginMenus = {
'adblocker': adblockerMenu,
'disable-autoplay': disableAutoplayMenu,
'captions-selector': captionsSelectorMenu,
'crossfade': crossfadeMenu,
'discord': discordMenu,
'downloader': downloaderMenu,
'lyrics-genius': lyricsGeniusMenu,
'notifications': notificationsMenu,
'picture-in-picture': pictureInPictureMenu,
'precise-volume': preciseVolumeMenu,
'shortcuts': shortcutsMenu,
'video-toggle': videoToggleMenu,
'visualizer': visualizerMenu,
};
const pluginEnabledMenu = (plugin: string, label = '', hasSubmenu = false, refreshMenu: (() => void ) | undefined = undefined): Electron.MenuItemConstructorOptions => ({
label: label || plugin,
type: 'checkbox',
checked: config.plugins.isEnabled(plugin),
click(item: Electron.MenuItem) {
if (item.checked) {
config.plugins.enable(plugin);
} else {
config.plugins.disable(plugin);
}
if (hasSubmenu) {
refreshMenu?.();
}
},
});
export const mainMenuTemplate = (win: BrowserWindow): MenuTemplate => {
const refreshMenu = () => {
setApplicationMenu(win);
if (inAppMenuActive) {
win.webContents.send('refreshMenu');
}
};
return [
{
label: 'Plugins',
submenu:
getAvailablePluginNames().map((pluginName) => {
let pluginLabel = pluginName;
if (betaPlugins.includes(pluginLabel)) {
pluginLabel += ' [beta]';
}
if (Object.hasOwn(pluginMenus, pluginName)) {
const getPluginMenu = pluginMenus[pluginName as keyof typeof pluginMenus];
if (!config.plugins.isEnabled(pluginName)) {
return pluginEnabledMenu(pluginName, pluginLabel, true, refreshMenu);
}
return {
label: pluginLabel,
submenu: [
pluginEnabledMenu(pluginName, 'Enabled', true, refreshMenu),
{ type: 'separator' },
...getPluginMenu(win, config.plugins.getOptions(pluginName), refreshMenu),
],
} satisfies Electron.MenuItemConstructorOptions;
}
return pluginEnabledMenu(pluginName, pluginLabel);
}),
},
{
label: 'Options',
submenu: [
{
label: 'Auto-update',
type: 'checkbox',
checked: config.get('options.autoUpdates'),
click(item) {
config.setMenuOption('options.autoUpdates', item.checked);
},
},
{
label: 'Resume last song when app starts',
type: 'checkbox',
checked: config.get('options.resumeOnStart'),
click(item) {
config.setMenuOption('options.resumeOnStart', item.checked);
},
},
{
label: 'Starting page',
submenu: (() => {
const subMenuArray: Electron.MenuItemConstructorOptions[] = Object.keys(startingPages).map((name) => ({
label: name,
type: 'radio',
checked: config.get('options.startingPage') === name,
click() {
config.set('options.startingPage', name);
},
}));
subMenuArray.unshift({
label: 'Unset',
type: 'radio',
checked: config.get('options.startingPage') === '',
click() {
config.set('options.startingPage', '');
},
});
return subMenuArray;
})(),
},
{
label: 'Visual Tweaks',
submenu: [
{
label: 'Remove upgrade button',
type: 'checkbox',
checked: config.get('options.removeUpgradeButton'),
click(item) {
config.setMenuOption('options.removeUpgradeButton', item.checked);
},
},
{
label: 'Like buttons',
submenu: [
{
label: 'Default',
type: 'radio',
checked: !config.get('options.likeButtons'),
click() {
config.set('options.likeButtons', '');
},
},
{
label: 'Force show',
type: 'radio',
checked: config.get('options.likeButtons') === 'force',
click() {
config.set('options.likeButtons', 'force');
},
},
{
label: 'Hide',
type: 'radio',
checked: config.get('options.likeButtons') === 'hide',
click() {
config.set('options.likeButtons', 'hide');
},
},
],
},
{
label: 'Theme',
submenu: [
{
label: 'No theme',
type: 'radio',
checked: config.get('options.themes')?.length === 0, // Todo rename "themes"
click() {
config.set('options.themes', []);
},
},
{ type: 'separator' },
{
label: 'Import custom CSS file',
type: 'normal',
async click() {
const { filePaths } = await dialog.showOpenDialog({
filters: [{ name: 'CSS Files', extensions: ['css'] }],
properties: ['openFile', 'multiSelections'],
});
if (filePaths) {
config.set('options.themes', filePaths);
}
},
},
],
},
],
},
{
label: 'Single instance lock',
type: 'checkbox',
checked: true,
click(item) {
if (!item.checked && app.hasSingleInstanceLock()) {
app.releaseSingleInstanceLock();
} else if (item.checked && !app.hasSingleInstanceLock()) {
app.requestSingleInstanceLock();
}
},
},
{
label: 'Always on top',
type: 'checkbox',
checked: config.get('options.alwaysOnTop'),
click(item) {
config.setMenuOption('options.alwaysOnTop', item.checked);
win.setAlwaysOnTop(item.checked);
},
},
...(is.windows() || is.linux()
? [
{
label: 'Hide menu',
type: 'checkbox',
checked: config.get('options.hideMenu'),
click(item) {
config.setMenuOption('options.hideMenu', item.checked);
if (item.checked && !config.get('options.hideMenuWarned')) {
dialog.showMessageBox(win, {
type: 'info', title: 'Hide Menu Enabled',
message: 'Menu will be hidden on next launch, use [Alt] to show it (or backtick [`] if using in-app-menu)',
});
}
},
},
]
: []) satisfies Electron.MenuItemConstructorOptions[],
...(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.setMenuOption('options.startAtLogin', item.checked);
},
},
]
: []) satisfies Electron.MenuItemConstructorOptions[],
{
label: 'Tray',
submenu: [
{
label: 'Disabled',
type: 'radio',
checked: !config.get('options.tray'),
click() {
config.setMenuOption('options.tray', false);
config.setMenuOption('options.appVisible', true);
},
},
{
label: 'Enabled + app visible',
type: 'radio',
checked: config.get('options.tray') && config.get('options.appVisible'),
click() {
config.setMenuOption('options.tray', true);
config.setMenuOption('options.appVisible', true);
},
},
{
label: 'Enabled + app hidden',
type: 'radio',
checked: config.get('options.tray') && !config.get('options.appVisible'),
click() {
config.setMenuOption('options.tray', true);
config.setMenuOption('options.appVisible', false);
},
},
{ type: 'separator' },
{
label: 'Play/Pause on click',
type: 'checkbox',
checked: config.get('options.trayClickPlayPause'),
click(item) {
config.setMenuOption('options.trayClickPlayPause', item.checked);
},
},
],
},
{ type: 'separator' },
{
label: 'Advanced options',
submenu: [
{
label: 'Set Proxy',
type: 'normal',
async click(item) {
await setProxy(item, win);
},
},
{
label: 'Override useragent',
type: 'checkbox',
checked: config.get('options.overrideUserAgent'),
click(item) {
config.setMenuOption('options.overrideUserAgent', item.checked);
},
},
{
label: 'Disable hardware acceleration',
type: 'checkbox',
checked: config.get('options.disableHardwareAcceleration'),
click(item) {
config.setMenuOption('options.disableHardwareAcceleration', item.checked);
},
},
{
label: 'Restart on config changes',
type: 'checkbox',
checked: config.get('options.restartOnConfigChanges'),
click(item) {
config.setMenuOption('options.restartOnConfigChanges', item.checked);
},
},
{
label: 'Reset App cache when app starts',
type: 'checkbox',
checked: config.get('options.autoResetAppCache'),
click(item) {
config.setMenuOption('options.autoResetAppCache', item.checked);
},
},
{ type: 'separator' },
is.macOS()
? {
label: 'Toggle DevTools',
// Cannot use "toggleDevTools" role in macOS
click() {
const { webContents } = win;
if (webContents.isDevToolsOpened()) {
webContents.closeDevTools();
} else {
webContents.openDevTools();
}
},
}
: { role: 'toggleDevTools' },
{
label: 'Edit config.json',
click() {
config.edit();
},
},
],
},
],
},
{
label: 'View',
submenu: [
{ role: 'reload' },
{ role: 'forceReload' },
{ type: 'separator' },
{ role: 'zoomIn', accelerator: process.platform === 'darwin' ? 'Cmd+I' : 'Ctrl+I' },
{ role: 'zoomOut', accelerator: process.platform === 'darwin' ? 'Cmd+O' : 'Ctrl+O' },
{ role: 'resetZoom' },
{ type: 'separator' },
{ role: 'togglefullscreen' },
],
},
{
label: 'Navigation',
submenu: [
{
label: 'Go back',
click() {
if (win.webContents.canGoBack()) {
win.webContents.goBack();
}
},
},
{
label: 'Go forward',
click() {
if (win.webContents.canGoForward()) {
win.webContents.goForward();
}
},
},
{
label: 'Copy current URL',
click() {
const currentURL = win.webContents.getURL();
clipboard.writeText(currentURL);
},
},
{
label: 'Restart App',
click: restart,
},
{ role: 'quit' },
],
},
];
};
export const setApplicationMenu = (win: Electron.BrowserWindow) => {
const menuTemplate: MenuTemplate = [...mainMenuTemplate(win)];
if (process.platform === 'darwin') {
const { name } = app;
menuTemplate.unshift({
label: name,
submenu: [
{ role: 'about' },
{ type: 'separator' },
{ role: 'hide' },
{ role: 'hideOthers' },
{ role: 'unhide' },
{ type: 'separator' },
{ role: 'selectAll' },
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' },
{ type: 'separator' },
{ role: 'minimize' },
{ role: 'close' },
{ role: 'quit' },
],
});
}
const menu = Menu.buildFromTemplate(menuTemplate);
Menu.setApplicationMenu(menu);
};
async function setProxy(item: Electron.MenuItem, win: BrowserWindow) {
const output = await prompt({
title: 'Set Proxy',
label: 'Enter Proxy Address: (leave empty to disable)',
value: config.get('options.proxy'),
type: 'input',
inputAttrs: {
type: 'url',
placeholder: "Example: 'socks5://127.0.0.1:9999",
},
width: 450,
...promptOptions(),
}, win);
if (typeof output === 'string') {
config.setMenuOption('options.proxy', output);
item.checked = output !== '';
} else { // User pressed cancel
item.checked = !item.checked; // Reset checkbox
}
}

9671
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,9 @@
{
"name": "youtube-music",
"productName": "YouTube Music",
"version": "2.0.2",
"version": "3.3.5",
"description": "YouTube Music Desktop App - including custom plugins",
"main": "./dist/index.js",
"main": "./dist/main/index.js",
"license": "MIT",
"repository": "th-ch/youtube-music",
"author": {
@ -17,31 +17,18 @@
"files": [
"!*",
"dist",
"assets",
"license",
"!node_modules",
"node_modules/custom-electron-prompt/**",
"node_modules/youtubei.js/**",
"node_modules/undici/**",
"node_modules/@fastify/busboy/**",
"node_modules/jintr/**",
"node_modules/acorn/**",
"node_modules/tslib/**",
"node_modules/semver/**",
"node_modules/lru-cache/**",
"node_modules/detect-libc/**",
"node_modules/color/**",
"node_modules/color-convert/**",
"node_modules/color-string/**",
"node_modules/color-name/**",
"node_modules/simple-swizzle/**",
"node_modules/is-arrayish/**",
"node_modules/@cliqz/adblocker-electron-preload/**",
"node_modules/@cliqz/adblocker-content/**",
"node_modules/@cliqz/adblocker-extended-selectors/**",
"node_modules/@ffmpeg.wasm/core-mt/**",
"!node_modules/**/*.map",
"!node_modules/**/*.ts"
],
"asarUnpack": [
"assets"
],
"mac": {
"identity": null,
"target": [
@ -90,6 +77,11 @@
"rpm"
]
},
"rpm": {
"depends": [
"/usr/lib64/libuuid.so.1"
]
},
"snap": {
"slots": [
{
@ -104,109 +96,137 @@
}
},
"scripts": {
"test": "npm run build && playwright test",
"test:debug": "DEBUG=pw:browser* npm run build && playwright test",
"rollup:preload": "rollup -c rollup.preload.config.ts --configPlugin @rollup/plugin-typescript --bundleConfigAsCjs",
"rollup:main": "rollup -c rollup.main.config.ts --configPlugin @rollup/plugin-typescript --bundleConfigAsCjs",
"build": "npm run rollup:preload && npm run rollup:main",
"start": "npm run build && electron ./dist/index.js",
"start:debug": "ELECTRON_ENABLE_LOGGING=1 npm run start",
"generate:package": "node utils/generate-package-json.js",
"postinstall": "npm run plugins && npm run clean",
"clean": "del-cli dist && del-cli pack",
"dist": "npm run clean && npm run build && electron-builder --win --mac --linux -p never",
"dist:linux": "npm run clean && npm run build && electron-builder --linux -p never",
"dist:mac": "npm run clean && npm run build && electron-builder --mac dmg:x64 -p never",
"dist:mac:arm64": "npm run clean && npm run build && electron-builder --mac dmg:arm64 -p never",
"dist:win": "npm run clean && npm run build && electron-builder --win -p never",
"dist:win:x64": "npm run clean && npm run build && electron-builder --win nsis-web:x64 -p never",
"test": "playwright test",
"test:debug": "cross-env DEBUG=pw:*,-pw:test:protocol playwright test",
"build": "electron-vite build",
"vite:inspect": "pnpm clean && electron-vite build --mode development && pnpm exec serve .vite-inspect",
"start": "electron-vite preview",
"start:debug": "cross-env ELECTRON_ENABLE_LOGGING=1 pnpm start",
"dev": "electron-vite dev --watch",
"dev:debug": "cross-env ELECTRON_ENABLE_LOGGING=1 pnpm dev",
"clean": "del-cli dist && del-cli pack && del-cli .vite-inspect",
"dist": "pnpm clean && pnpm build && pnpm electron-builder --win --mac --linux -p never",
"dist:linux": "pnpm clean && pnpm build && pnpm electron-builder --linux -p never",
"dist:mac": "pnpm clean && pnpm build && pnpm electron-builder --mac dmg:x64 -p never",
"dist:mac:arm64": "pnpm clean && pnpm build && pnpm electron-builder --mac dmg:arm64 -p never",
"dist:win": "pnpm clean && pnpm build && pnpm electron-builder --win -p never",
"dist:win:x64": "pnpm clean && pnpm build && pnpm electron-builder --win nsis-web:x64 -p never",
"lint": "eslint .",
"changelog": "auto-changelog",
"plugins": "npm run plugin:bypass-age-restrictions",
"plugin:bypass-age-restrictions": "del-cli node_modules/simple-youtube-age-restriction-bypass/package.json && npm run generate:package simple-youtube-age-restriction-bypass",
"release:linux": "npm run clean && npm run build && electron-builder --linux -p always -c.snap.publish=github",
"release:mac": "npm run clean && npm run build && electron-builder --mac -p always",
"release:win": "npm run clean && npm run build && electron-builder --win -p always",
"changelog": "npx --yes auto-changelog",
"release:linux": "pnpm clean && pnpm build && pnpm electron-builder --linux -p always -c.snap.publish=github",
"release:mac": "pnpm clean && pnpm build && pnpm electron-builder --mac -p always",
"release:win": "pnpm clean && pnpm build && pnpm electron-builder --win -p always",
"typecheck": "tsc -p tsconfig.json --noEmit"
},
"engines": {
"node": ">=16.0.0"
"node": ">=18.0.0"
},
"pnpm": {
"overrides": {
"usocket": "1.0.1",
"node-gyp": "10.0.1",
"xml2js": "0.6.2",
"node-fetch": "3.3.2",
"@electron/universal": "2.0.1",
"@babel/runtime": "7.23.8"
},
"patchedDependencies": {
"vudio@2.1.1": "patches/vudio@2.1.1.patch",
"@xhayper/discord-rpc@1.1.2": "patches/@xhayper__discord-rpc@1.1.2.patch"
}
},
"dependencies": {
"@cliqz/adblocker-electron": "1.26.7",
"@cliqz/adblocker-electron": "1.26.16",
"@cliqz/adblocker-electron-preload": "1.26.16",
"@electron-toolkit/tsconfig": "1.0.1",
"@electron/remote": "2.1.2",
"@ffmpeg.wasm/core-mt": "0.12.0",
"@ffmpeg.wasm/main": "0.12.0",
"@foobar404/wave": "2.0.4",
"@xhayper/discord-rpc": "1.0.23",
"async-mutex": "0.4.0",
"butterchurn": "2.6.7",
"butterchurn-presets": "2.4.7",
"@floating-ui/dom": "1.6.3",
"@foobar404/wave": "2.0.5",
"@jellybrick/electron-better-web-request": "1.0.4",
"@jellybrick/mpris-service": "2.1.4",
"@xhayper/discord-rpc": "1.1.2",
"async-mutex": "0.5.0",
"butterchurn": "3.0.0-beta.4",
"butterchurn-presets": "3.0.0-beta.4",
"color": "4.2.3",
"conf": "10.2.0",
"custom-electron-prompt": "1.5.7",
"electron-better-web-request": "1.0.1",
"dbus-next": "0.10.2",
"deepmerge-ts": "5.1.0",
"electron-debug": "3.2.0",
"electron-is": "3.0.0",
"electron-localshortcut": "3.2.1",
"electron-store": "8.1.0",
"electron-store": "8.2.0",
"electron-unhandled": "4.0.1",
"electron-updater": "6.1.4",
"electron-updater": "6.1.8",
"fast-average-color": "9.4.0",
"fast-equals": "5.0.1",
"filenamify": "6.0.0",
"howler": "2.2.4",
"html-to-text": "9.0.5",
"i18next": "23.10.1",
"keyboardevent-from-electron-accelerator": "2.0.0",
"keyboardevents-areequal": "0.2.2",
"mpris-service": "2.1.2",
"node-html-parser": "6.1.12",
"node-id3": "0.2.6",
"simple-youtube-age-restriction-bypass": "git+https://github.com/MiepHD/Simple-YouTube-Age-Restriction-Bypass.git#v2.5.5",
"peerjs": "1.5.2",
"reconnecting-websocket": "4.4.0",
"semver": "7.6.0",
"serve": "14.2.1",
"simple-youtube-age-restriction-bypass": "github:organization/Simple-YouTube-Age-Restriction-Bypass#v2.5.9",
"solid-floating-ui": "0.3.1",
"solid-js": "1.8.16",
"solid-styled-components": "0.28.5",
"solid-transition-group": "0.2.3",
"ts-morph": "22.0.0",
"vudio": "2.1.1",
"ws": "8.16.0",
"x11": "2.3.0",
"youtubei.js": "6.4.1",
"ytpl": "2.3.0"
},
"overrides": {
"rollup": "4.0.2",
"node-gyp": "9.4.0",
"xml2js": "0.6.2",
"dbus-next": "0.10.2",
"node-fetch": "2.7.0",
"@electron/universal": "1.4.2",
"electron": "27.0.0-beta.9"
"youtubei.js": "9.1.0"
},
"devDependencies": {
"@playwright/test": "1.38.1",
"@rollup/plugin-commonjs": "25.0.5",
"@rollup/plugin-image": "3.0.3",
"@rollup/plugin-json": "6.0.1",
"@rollup/plugin-node-resolve": "15.2.2",
"@rollup/plugin-terser": "0.4.4",
"@rollup/plugin-typescript": "11.1.5",
"@rollup/plugin-wasm": "6.2.2",
"@playwright/test": "1.42.1",
"@total-typescript/ts-reset": "0.5.1",
"@types/electron-localshortcut": "3.1.1",
"@types/howler": "2.2.9",
"@types/html-to-text": "9.0.2",
"@typescript-eslint/eslint-plugin": "6.7.4",
"auto-changelog": "2.4.0",
"@types/color": "3.0.6",
"@types/electron-localshortcut": "3.1.3",
"@types/howler": "2.2.11",
"@types/html-to-text": "9.0.4",
"@types/semver": "7.5.8",
"@types/ws": "8.5.10",
"@typescript-eslint/eslint-plugin": "7.4.0",
"bufferutil": "4.0.8",
"builtin-modules": "3.3.0",
"cross-env": "7.0.3",
"del-cli": "5.1.0",
"electron": "27.0.0-beta.9",
"electron-builder": "24.6.4",
"discord-api-types": "0.37.76",
"electron": "29.1.6",
"electron-builder": "24.9.1",
"electron-devtools-installer": "3.2.0",
"eslint": "8.51.0",
"eslint-plugin-import": "2.28.1",
"eslint-plugin-prettier": "5.0.0",
"node-gyp": "9.4.0",
"playwright": "1.38.1",
"rollup": "4.0.2",
"rollup-plugin-copy": "3.5.0",
"rollup-plugin-import-css": "3.3.4",
"rollup-plugin-string": "3.0.0",
"typescript": "5.2.2"
"electron-vite": "2.1.0",
"esbuild": "0.20.2",
"eslint": "8.57.0",
"eslint-import-resolver-exports": "1.0.0-beta.5",
"eslint-import-resolver-typescript": "3.6.1",
"eslint-plugin-import": "2.29.1",
"eslint-plugin-prettier": "5.1.3",
"glob": "10.3.10",
"node-gyp": "10.1.0",
"playwright": "1.42.1",
"rollup": "4.13.1",
"typescript": "5.4.3",
"utf-8-validate": "6.0.3",
"vite": "5.2.6",
"vite-plugin-inspect": "0.8.3",
"vite-plugin-resolve": "2.5.1",
"vite-plugin-solid": "2.10.2"
},
"auto-changelog": {
"hideCredit": true,
"package": true,
"unreleased": true,
"output": "changelog.md"
}
},
"packageManager": "pnpm@8.15.5"
}

View File

@ -0,0 +1,17 @@
diff --git a/package.json b/package.json
index 40db5dfbd8a4455ce2987d8115eca9882e1f9f14..414fc6986b9c0cc288908eb0107b90c4bfd916b2 100644
--- a/package.json
+++ b/package.json
@@ -25,11 +25,7 @@
},
"dependencies": {
"axios": "^1.6.2",
- "ws": "^8.15.1"
- },
- "optionalDependencies": {
- "bufferutil": "^4.0.8",
- "utf-8-validate": "^6.0.3"
+ "ws": "^8.16.0"
},
"devDependencies": {
"@types/node": "^14.*",

20
patches/vudio@2.1.1.patch Normal file
View File

@ -0,0 +1,20 @@
diff --git a/umd/vudio.js b/umd/vudio.js
index d0d1127e57125ad4e77442af2db4a26998c7b385..c0b66bd4327c65c31dc6e588bfa4ae6ec70bd3b8 100644
--- a/umd/vudio.js
+++ b/umd/vudio.js
@@ -147,7 +147,6 @@
source.connect(this.analyser);
this.analyser.fftSize = this.option.accuracy * 2;
- this.analyser.connect(audioContext.destination);
this.freqByteData = new Uint8Array(this.analyser.frequencyBinCount);
@@ -207,7 +206,6 @@
source.connect(this.analyser);
this.analyser.fftSize = this.option.accuracy * 2;
- this.analyser.connect(audioContext.destination);
},
__rebuildData : function (freqByteData, horizontalAlign) {

View File

@ -1,19 +0,0 @@
import { BrowserWindow } from 'electron';
import { loadAdBlockerEngine } from './blocker';
import { shouldUseBlocklists } from './config';
import type { ConfigType } from '../../config/dynamic';
type AdBlockOptions = ConfigType<'adblocker'>;
export default async (win: BrowserWindow, options: AdBlockOptions) => {
if (await shouldUseBlocklists()) {
loadAdBlockerEngine(
win.webContents.session,
options.cache,
options.additionalBlockLists,
options.disableDefaultLists,
);
}
};

View File

@ -1,73 +0,0 @@
// Used for caching
import path from 'node:path';
import fs, { promises } from 'node:fs';
import { ElectronBlocker } from '@cliqz/adblocker-electron';
import { app } from 'electron';
const SOURCES = [
'https://raw.githubusercontent.com/kbinani/adblock-youtube-ads/master/signed.txt',
// UBlock Origin
'https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/filters.txt',
'https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/filters-2020.txt',
'https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/filters-2021.txt',
'https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/filters-2022.txt',
'https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/filters-2023.txt',
// Fanboy Annoyances
'https://secure.fanboy.co.nz/fanboy-annoyance_ubo.txt',
];
export const loadAdBlockerEngine = (
session: Electron.Session | undefined = undefined,
cache = true,
additionalBlockLists = [],
disableDefaultLists: boolean | unknown[] = false,
) => {
// Only use cache if no additional blocklists are passed
let cacheDirectory: string;
if (app.isPackaged) {
cacheDirectory = path.join(app.getPath('userData'), 'adblock_cache');
} else {
cacheDirectory = path.resolve(__dirname, 'adblock_cache');
}
if (!fs.existsSync(cacheDirectory)) {
fs.mkdirSync(cacheDirectory);
}
const cachingOptions
= cache && additionalBlockLists.length === 0
? {
path: path.join(cacheDirectory, 'adblocker-engine.bin'),
read: promises.readFile,
write: promises.writeFile,
}
: undefined;
const lists = [
...(
(disableDefaultLists && !Array.isArray(disableDefaultLists)) ||
(Array.isArray(disableDefaultLists) && disableDefaultLists.length > 0) ? [] : SOURCES
),
...additionalBlockLists,
];
ElectronBlocker.fromLists(
fetch,
lists,
{
// When generating the engine for caching, do not load network filters
// So that enhancing the session works as expected
// Allowing to define multiple webRequest listeners
loadNetworkFilters: session !== undefined,
},
cachingOptions,
)
.then((blocker) => {
if (session) {
blocker.enableBlockingInSession(session);
} else {
console.log('Successfully generated adBlocker engine.');
}
})
.catch((error) => console.log('Error loading adBlocker engine', error));
};
export default { loadAdBlockerEngine };

View File

@ -1,15 +0,0 @@
/* eslint-disable @typescript-eslint/await-thenable */
/* renderer */
import { blockers } from './blocker-types';
import { PluginConfig } from '../../config/dynamic';
const config = new PluginConfig('adblocker', { enableFront: true });
export const shouldUseBlocklists = async () => await config.get('blocker') !== blockers.InPlayer;
export default Object.assign(config, {
shouldUseBlocklists,
blockers,
});

View File

@ -1,4 +0,0 @@
export default () => {
const path = '@cliqz/adblocker-electron-preload'; // prevent require hoisting
require(path);
};

View File

@ -1,3 +0,0 @@
const inject: () => void;
export default inject;

View File

@ -1,21 +0,0 @@
import config from './config';
import { blockers } from './blocker-types';
import { MenuTemplate } from '../../menu';
export default (): MenuTemplate => {
return [
{
label: 'Blocker',
submenu: Object.values(blockers).map((blocker: string) => ({
label: blocker,
type: 'radio',
checked: (config.get('blocker') || blockers.WithBlocklists) === blocker,
click() {
config.set('blocker', blocker);
},
})),
},
];
};

View File

@ -1,15 +0,0 @@
import config from './config';
import inject from './inject';
import injectCliqzPreload from './inject-cliqz-preload';
import { blockers } from './blocker-types';
export default async () => {
if (await config.shouldUseBlocklists()) {
// Preload adblocker to inject scripts/styles
injectCliqzPreload();
// eslint-disable-next-line @typescript-eslint/await-thenable
} else if ((await config.get('blocker')) === blockers.InPlayer) {
inject();
}
};

View File

@ -1,9 +0,0 @@
import { BrowserWindow } from 'electron';
import style from './style.css';
import { injectCSS } from '../utils';
export default (win: BrowserWindow) => {
injectCSS(win.webContents, style);
};

View File

@ -1,127 +0,0 @@
import { FastAverageColor } from 'fast-average-color';
import { ConfigType } from '../../config/dynamic';
function hexToHSL(H: string) {
// Convert hex to RGB first
let r = 0;
let g = 0;
let b = 0;
if (H.length == 4) {
r = Number('0x' + H[1] + H[1]);
g = Number('0x' + H[2] + H[2]);
b = Number('0x' + H[3] + H[3]);
} else if (H.length == 7) {
r = Number('0x' + H[1] + H[2]);
g = Number('0x' + H[3] + H[4]);
b = Number('0x' + H[5] + H[6]);
}
// Then to HSL
r /= 255;
g /= 255;
b /= 255;
const cmin = Math.min(r, g, b);
const cmax = Math.max(r, g, b);
const delta = cmax - cmin;
let h: number;
let s: number;
let l: number;
if (delta == 0) {
h = 0;
} else if (cmax == r) {
h = ((g - b) / delta) % 6;
} else if (cmax == g) {
h = ((b - r) / delta) + 2;
} else {
h = ((r - g) / delta) + 4;
}
h = Math.round(h * 60);
if (h < 0) {
h += 360;
}
l = (cmax + cmin) / 2;
s = delta == 0 ? 0 : delta / (1 - Math.abs((2 * l) - 1));
s = +(s * 100).toFixed(1);
l = +(l * 100).toFixed(1);
//return "hsl(" + h + "," + s + "%," + l + "%)";
return [h,s,l];
}
let hue = 0;
let saturation = 0;
let lightness = 0;
function changeElementColor(element: HTMLElement | null, hue: number, saturation: number, lightness: number){
if (element) {
element.style.backgroundColor = `hsl(${hue}, ${saturation}%, ${lightness}%)`;
}
}
export default (_: ConfigType<'album-color-theme'>) => {
// updated elements
const playerPage = document.querySelector<HTMLElement>('#player-page');
const navBarBackground = document.querySelector<HTMLElement>('#nav-bar-background');
const ytmusicPlayerBar = document.querySelector<HTMLElement>('ytmusic-player-bar');
const playerBarBackground = document.querySelector<HTMLElement>('#player-bar-background');
const sidebarBig = document.querySelector<HTMLElement>('#guide-wrapper');
const sidebarSmall = document.querySelector<HTMLElement>('#mini-guide-background');
const ytmusicAppLayout = document.querySelector<HTMLElement>('#layout');
const observer = new MutationObserver((mutationsList) => {
for (const mutation of mutationsList) {
if (mutation.type === 'attributes') {
const isPageOpen = ytmusicAppLayout?.hasAttribute('player-page-open');
if (isPageOpen) {
changeElementColor(sidebarSmall, hue, saturation, lightness - 30);
} else {
if (sidebarSmall) {
sidebarSmall.style.backgroundColor = 'black';
}
}
}
}
});
if (playerPage) {
observer.observe(playerPage, { attributes: true });
}
document.addEventListener('apiLoaded', (apiEvent) => {
const fastAverageColor = new FastAverageColor();
apiEvent.detail.addEventListener('videodatachange', (name: string) => {
if (name === 'dataloaded') {
const playerResponse = apiEvent.detail.getPlayerResponse();
const thumbnail = playerResponse?.videoDetails?.thumbnail?.thumbnails?.at(0);
if (thumbnail) {
fastAverageColor.getColorAsync(thumbnail.url)
.then((albumColor) => {
if (albumColor) {
[hue, saturation, lightness] = hexToHSL(albumColor.hex);
changeElementColor(playerPage, hue, saturation, lightness - 30);
changeElementColor(navBarBackground, hue, saturation, lightness - 15);
changeElementColor(ytmusicPlayerBar, hue, saturation, lightness - 15);
changeElementColor(playerBarBackground, hue, saturation, lightness - 15);
changeElementColor(sidebarBig, hue, saturation, lightness - 15);
if (ytmusicAppLayout?.hasAttribute('player-page-open')) {
changeElementColor(sidebarSmall, hue, saturation, lightness - 30);
}
const ytRightClickList = document.querySelector<HTMLElement>('tp-yt-paper-listbox');
changeElementColor(ytRightClickList, hue, saturation, lightness - 15);
} else {
if (playerPage) {
playerPage.style.backgroundColor = '#000000';
}
}
})
.catch((e) => console.error(e));
}
}
});
});
};

View File

@ -1,29 +0,0 @@
yt-page-navigation-progress {
--yt-page-navigation-container-color: #00000046 !important;
--yt-page-navigation-progress-color: white !important;
}
#player-page {
transition: transform 300ms,background-color 300ms cubic-bezier(0.2,0,0.6,1) !important;
}
#nav-bar-background {
transition: opacity 200ms,background-color 300ms cubic-bezier(0.2,0,0.6,1) !important;
}
#mini-guide-background {
transition: opacity 200ms,background-color 300ms cubic-bezier(0.2,0,0.6,1) !important;
border-right: 0px !important;
}
#guide-wrapper {
transition: opacity 200ms,background-color 300ms cubic-bezier(0.2,0,0.6,1) !important;
}
#img, #player, .song-media-controls.style-scope.ytmusic-player {
border-radius: 2% !important;
}
#items {
border-radius: 10px !important;
}

View File

@ -1,10 +0,0 @@
import { BrowserWindow } from 'electron';
import style from './style.css';
import { injectCSS } from '../utils';
export default (win: BrowserWindow) => {
injectCSS(win.webContents, style);
};

View File

@ -1,138 +0,0 @@
import { ConfigType } from '../../config/dynamic';
export default (_: ConfigType<'ambient-mode'>) => {
const interpolationTime = 3000; // interpolation time (ms)
const framerate = 30; // frame
const qualityRatio = 50; // width size (pixel)
let unregister: (() => void) | null = null;
const injectBlurVideo = (): (() => void) | null => {
const songVideo = document.querySelector<HTMLDivElement>('#song-video');
const video = document.querySelector<HTMLVideoElement>('#song-video .html5-video-container > video');
const wrapper = document.querySelector('#song-video > .player-wrapper');
if (!songVideo) return null;
if (!video) return null;
if (!wrapper) return null;
const blurCanvas = document.createElement('canvas');
blurCanvas.classList.add('html5-blur-canvas');
const context = blurCanvas.getContext('2d', { willReadFrequently: true });
/* effect */
let lastEffectWorkId: number | null = null;
let lastImageData: ImageData | null = null;
const onSync = () => {
if (typeof lastEffectWorkId === 'number') cancelAnimationFrame(lastEffectWorkId);
lastEffectWorkId = requestAnimationFrame(() => {
if (!context) return;
const width = qualityRatio;
let height = Math.max(Math.floor(blurCanvas.height / blurCanvas.width * width), 1);
if (!Number.isFinite(height)) height = width;
context.globalAlpha = 1;
if (lastImageData) {
const frameOffset = (1 / framerate) * (1000 / interpolationTime);
context.globalAlpha = 1 - (frameOffset * 2); // because of alpha value must be < 1
context.putImageData(lastImageData, 0, 0);
context.globalAlpha = frameOffset;
}
context.drawImage(video, 0, 0, width, height);
const nowImageData = context.getImageData(0, 0, width, height);
lastImageData = nowImageData;
lastEffectWorkId = null;
});
};
const applyVideoAttributes = () => {
const rect = video.getBoundingClientRect();
const newWidth = Math.floor(video.width || rect.width);
const newHeight = Math.floor(video.height || rect.height);
if (newWidth === 0 || newHeight === 0) return;
blurCanvas.width = qualityRatio;
blurCanvas.height = Math.floor(newHeight / newWidth * qualityRatio);
blurCanvas.style.width = `${newWidth}px`;
blurCanvas.style.height = `${newHeight}px`;
};
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'attributes') {
applyVideoAttributes();
}
});
});
const resizeObserver = new ResizeObserver(() => {
applyVideoAttributes();
});
/* hooking */
let canvasInterval: NodeJS.Timeout | null = null;
canvasInterval = setInterval(onSync, Math.max(1, Math.ceil(1000 / framerate)));
applyVideoAttributes();
observer.observe(songVideo, { attributes: true });
resizeObserver.observe(songVideo);
window.addEventListener('resize', applyVideoAttributes);
const onPause = () => {
if (canvasInterval) clearInterval(canvasInterval);
canvasInterval = null;
};
const onPlay = () => {
if (canvasInterval) clearInterval(canvasInterval);
canvasInterval = setInterval(onSync, Math.max(1, Math.ceil(1000 / framerate)));
};
songVideo.addEventListener('pause', onPause);
songVideo.addEventListener('play', onPlay);
/* injecting */
wrapper.prepend(blurCanvas);
/* cleanup */
return () => {
if (canvasInterval) clearInterval(canvasInterval);
songVideo.removeEventListener('pause', onPause);
songVideo.removeEventListener('play', onPlay);
observer.disconnect();
resizeObserver.disconnect();
window.removeEventListener('resize', applyVideoAttributes);
wrapper.removeChild(blurCanvas);
};
};
const playerPage = document.querySelector<HTMLElement>('#player-page');
const ytmusicAppLayout = document.querySelector<HTMLElement>('#layout');
const observer = new MutationObserver((mutationsList) => {
for (const mutation of mutationsList) {
if (mutation.type === 'attributes') {
const isPageOpen = ytmusicAppLayout?.hasAttribute('player-page-open');
if (isPageOpen) {
unregister?.();
unregister = injectBlurVideo() ?? null;
} else {
unregister?.();
unregister = null;
}
}
}
});
if (playerPage) {
observer.observe(playerPage, { attributes: true });
}
};

View File

@ -1,7 +0,0 @@
#song-video canvas.html5-blur-canvas{
position: absolute;
left: 0;
top: 0;
filter: blur(100px);
}

View File

@ -1,17 +0,0 @@
export default () =>
document.addEventListener('audioCanPlay', (e) => {
const { audioContext } = e.detail;
const compressor = audioContext.createDynamicsCompressor();
compressor.threshold.value = -50;
compressor.ratio.value = 12;
compressor.knee.value = 40;
compressor.attack.value = 0;
compressor.release.value = 0.25;
e.detail.audioSource.connect(compressor);
compressor.connect(audioContext.destination);
}, {
once: true, // Only create the audio compressor once, not on each video
passive: true,
});

View File

@ -1,9 +0,0 @@
import { BrowserWindow } from 'electron';
import style from './style.css';
import { injectCSS } from '../utils';
export default (win: BrowserWindow) => {
injectCSS(win.webContents, style);
};

View File

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

View File

@ -1,4 +0,0 @@
export default () => {
// See https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass#userscript
require('simple-youtube-age-restriction-bypass/dist/Simple-YouTube-Age-Restriction-Bypass.user.js');
};

View File

@ -1,19 +0,0 @@
import { BrowserWindow, ipcMain } from 'electron';
import prompt from 'custom-electron-prompt';
import promptOptions from '../../providers/prompt-options';
export default (win: BrowserWindow) => {
ipcMain.handle('captionsSelector', async (_, captionLabels: Record<string, string>, currentIndex: string) => await prompt(
{
title: 'Choose Caption',
label: `Current Caption: ${captionLabels[currentIndex] || 'None'}`,
type: 'select',
value: currentIndex,
selectOptions: captionLabels,
resizable: true,
...promptOptions(),
},
win,
));
};

View File

@ -1,4 +0,0 @@
import { PluginConfig } from '../../config/dynamic';
const config = new PluginConfig('captions-selector', { enableFront: true });
export default config;

View File

@ -1,101 +0,0 @@
/* eslint-disable @typescript-eslint/await-thenable */
/* renderer */
import { ipcRenderer } from 'electron';
import configProvider from './config';
import CaptionsSettingsButtonHTML from './templates/captions-settings-template.html';
import { ElementFromHtml } from '../utils';
import { YoutubePlayer } from '../../types/youtube-player';
import type { ConfigType } from '../../config/dynamic';
interface LanguageOptions {
displayName: string;
id: string | null;
is_default: boolean;
is_servable: boolean;
is_translateable: boolean;
kind: string;
languageCode: string; // 2 length
languageName: string;
name: string | null;
vss_id: string;
}
let config: ConfigType<'captions-selector'>;
const $ = <Element extends HTMLElement>(selector: string): Element => document.querySelector(selector)!;
const captionsSettingsButton = ElementFromHtml(CaptionsSettingsButtonHTML);
export default async () => {
// RENDERER
config = await configProvider.getAll();
configProvider.subscribeAll((newConfig) => {
config = newConfig;
});
document.addEventListener('apiLoaded', (event) => setup(event.detail), { once: true, passive: true });
};
function setup(api: YoutubePlayer) {
$('.right-controls-buttons').append(captionsSettingsButton);
let captionTrackList = api.getOption<LanguageOptions[]>('captions', 'tracklist') ?? [];
$('video').addEventListener('srcChanged', () => {
if (config.disableCaptions) {
setTimeout(() => api.unloadModule('captions'), 100);
captionsSettingsButton.style.display = 'none';
return;
}
api.loadModule('captions');
setTimeout(() => {
captionTrackList = api.getOption('captions', 'tracklist') ?? [];
if (config.autoload && config.lastCaptionsCode) {
api.setOption('captions', 'track', {
languageCode: config.lastCaptionsCode,
});
}
captionsSettingsButton.style.display = captionTrackList?.length
? 'inline-block'
: 'none';
}, 250);
});
captionsSettingsButton.addEventListener('click', async () => {
if (captionTrackList?.length) {
const currentCaptionTrack = api.getOption<LanguageOptions>('captions', 'track')!;
let currentIndex = currentCaptionTrack
? captionTrackList.indexOf(captionTrackList.find((track) => track.languageCode === currentCaptionTrack.languageCode)!)
: null;
const captionLabels = [
...captionTrackList.map((track) => track.displayName),
'None',
];
currentIndex = await ipcRenderer.invoke('captionsSelector', captionLabels, currentIndex) as number;
if (currentIndex === null) {
return;
}
const newCaptions = captionTrackList[currentIndex];
configProvider.set('lastCaptionsCode', newCaptions?.languageCode);
if (newCaptions) {
api.setOption('captions', 'track', { languageCode: newCaptions.languageCode });
} else {
api.setOption('captions', 'track', {});
}
setTimeout(() => api.playVideo());
}
});
}

View File

@ -1,22 +0,0 @@
import config from './config';
import { MenuTemplate } from '../../menu';
export default (): MenuTemplate => [
{
label: 'Automatically select last used caption',
type: 'checkbox',
checked: config.get('autoload'),
click(item) {
config.set('autoload', item.checked);
},
},
{
label: 'No captions by default',
type: 'checkbox',
checked: config.get('disableCaptions'),
click(item) {
config.set('disableCaptions', item.checked);
},
},
];

View File

@ -1,17 +0,0 @@
<tp-yt-paper-icon-button aria-disabled="false" aria-label="Open captions selector"
class="player-captions-button style-scope ytmusic-player" icon="yt-icons:subtitles"
role="button" tabindex="0"
title="Open captions selector">
<tp-yt-iron-icon class="style-scope tp-yt-paper-icon-button" id="icon">
<svg class="style-scope yt-icon"
focusable="false" preserveAspectRatio="xMidYMid meet"
style="pointer-events: none; display: block; width: 100%; height: 100%;"
viewBox="0 0 24 24">
<g class="style-scope yt-icon">
<path
class="style-scope tp-yt-iron-icon"
d="M20 4H4c-1.103 0-2 .897-2 2v12c0 1.103.897 2 2 2h16c1.103 0 2-.897 2-2V6c0-1.103-.897-2-2-2zm-9 6H8v4h3v2H8c-1.103 0-2-.897-2-2v-4c0-1.103.897-2 2-2h3v2zm7 0h-3v4h3v2h-3c-1.103 0-2-.897-2-2v-4c0-1.103.897-2 2-2h3v2z"></path>
</g>
</svg>
</tp-yt-iron-icon>
</tp-yt-paper-icon-button>

View File

@ -1,10 +0,0 @@
export default () => {
const compactSidebar = document.querySelector('#mini-guide');
const isCompactSidebarDisabled
= compactSidebar === null
|| window.getComputedStyle(compactSidebar).display === 'none';
if (isCompactSidebarDisabled) {
document.querySelector<HTMLButtonElement>('#button')?.click();
}
};

View File

@ -1,11 +0,0 @@
import { ipcMain } from 'electron';
import { Innertube } from 'youtubei.js';
export default async () => {
const yt = await Innertube.create();
ipcMain.handle('audio-url', async (_, videoID: string) => {
const info = await yt.getBasicInfo(videoID);
return info.streaming_data?.formats[0].decipher(yt.session.player);
});
};

View File

@ -1,4 +0,0 @@
import { PluginConfig } from '../../config/dynamic';
const config = new PluginConfig('crossfade', { enableFront: true });
export default config;

View File

@ -1,164 +0,0 @@
/* eslint-disable @typescript-eslint/await-thenable */
/* renderer */
import { ipcRenderer } from 'electron';
import { Howl } from 'howler';
// Extracted from https://github.com/bitfasching/VolumeFader
import { VolumeFader } from './fader';
import configProvider from './config';
import defaultConfigs from '../../config/defaults';
import type { ConfigType } from '../../config/dynamic';
let transitionAudio: Howl; // Howler audio used to fade out the current music
let firstVideo = true;
let waitForTransition: Promise<unknown>;
const defaultConfig = defaultConfigs.plugins.crossfade;
let config: ConfigType<'crossfade'>;
const configGetNumber = (key: keyof ConfigType<'crossfade'>): number => Number(config[key]) || (defaultConfig[key] as number);
const getStreamURL = async (videoID: string) => ipcRenderer.invoke('audio-url', videoID) as Promise<string>;
const getVideoIDFromURL = (url: string) => new URLSearchParams(url.split('?')?.at(-1)).get('v');
const isReadyToCrossfade = () => transitionAudio && transitionAudio.state() === 'loaded';
const watchVideoIDChanges = (cb: (id: string) => void) => {
window.navigation.addEventListener('navigate', (event) => {
const currentVideoID = getVideoIDFromURL(
(event.currentTarget as Navigation).currentEntry?.url ?? '',
);
const nextVideoID = getVideoIDFromURL(event.destination.url ?? '');
if (
nextVideoID
&& currentVideoID
&& (firstVideo || nextVideoID !== currentVideoID)
) {
if (isReadyToCrossfade()) {
crossfade(() => {
cb(nextVideoID);
});
} else {
cb(nextVideoID);
firstVideo = false;
}
}
});
};
const createAudioForCrossfade = (url: string) => {
if (transitionAudio) {
transitionAudio.unload();
}
transitionAudio = new Howl({
src: url,
html5: true,
volume: 0,
});
syncVideoWithTransitionAudio();
};
const syncVideoWithTransitionAudio = () => {
const video = document.querySelector('video')!;
const videoFader = new VolumeFader(video, {
fadeScaling: configGetNumber('fadeScaling'),
fadeDuration: configGetNumber('fadeInDuration'),
});
transitionAudio.play();
transitionAudio.seek(video.currentTime);
video.addEventListener('seeking', () => {
transitionAudio.seek(video.currentTime);
});
video.addEventListener('pause', () => {
transitionAudio.pause();
});
video.addEventListener('play', () => {
transitionAudio.play();
transitionAudio.seek(video.currentTime);
// Fade in
const videoVolume = video.volume;
video.volume = 0;
videoFader.fadeTo(videoVolume);
});
// Exit just before the end for the transition
const transitionBeforeEnd = () => {
if (
video.currentTime >= video.duration - configGetNumber('secondsBeforeEnd')
&& isReadyToCrossfade()
) {
video.removeEventListener('timeupdate', transitionBeforeEnd);
// Go to next video - XXX: does not support "repeat 1" mode
document.querySelector<HTMLButtonElement>('.next-button')?.click();
}
};
video.addEventListener('timeupdate', transitionBeforeEnd);
};
const onApiLoaded = () => {
watchVideoIDChanges(async (videoID) => {
await waitForTransition;
const url = await getStreamURL(videoID);
if (!url) {
return;
}
await createAudioForCrossfade(url);
});
};
const crossfade = (cb: () => void) => {
if (!isReadyToCrossfade()) {
cb();
return;
}
let resolveTransition: () => void;
waitForTransition = new Promise<void>((resolve) => {
resolveTransition = resolve;
});
const video = document.querySelector('video')!;
const fader = new VolumeFader(transitionAudio._sounds[0]._node, {
initialVolume: video.volume,
fadeScaling: configGetNumber('fadeScaling'),
fadeDuration: configGetNumber('fadeOutDuration'),
});
// Fade out the music
video.volume = 0;
fader.fadeOut(() => {
resolveTransition();
cb();
});
};
export default async () => {
config = await configProvider.getAll();
configProvider.subscribeAll((newConfig) => {
config = newConfig;
});
document.addEventListener('apiLoaded', onApiLoaded, {
once: true,
passive: true,
});
};

View File

@ -1,86 +0,0 @@
import prompt from 'custom-electron-prompt';
import { BrowserWindow } from 'electron';
import config from './config';
import promptOptions from '../../providers/prompt-options';
import configOptions from '../../config/defaults';
import { MenuTemplate } from '../../menu';
import type { ConfigType } from '../../config/dynamic';
const defaultOptions = configOptions.plugins.crossfade;
export default (win: BrowserWindow): MenuTemplate => [
{
label: 'Advanced',
async click() {
const newOptions = await promptCrossfadeValues(win, config.getAll());
if (newOptions) {
config.setAll(newOptions);
}
},
},
];
async function promptCrossfadeValues(win: BrowserWindow, options: ConfigType<'crossfade'>): Promise<Partial<ConfigType<'crossfade'>> | undefined> {
const res = await prompt(
{
title: 'Crossfade Options',
type: 'multiInput',
multiInputOptions: [
{
label: 'Fade in duration (ms)',
value: options.fadeInDuration || defaultOptions.fadeInDuration,
inputAttrs: {
type: 'number',
required: true,
min: '0',
step: '100',
},
},
{
label: 'Fade out duration (ms)',
value: options.fadeOutDuration || defaultOptions.fadeOutDuration,
inputAttrs: {
type: 'number',
required: true,
min: '0',
step: '100',
},
},
{
label: 'Crossfade x seconds before end',
value:
options.secondsBeforeEnd || defaultOptions.secondsBeforeEnd,
inputAttrs: {
type: 'number',
required: true,
min: '0',
},
},
{
label: 'Fade scaling',
selectOptions: { linear: 'Linear', logarithmic: 'Logarithmic' },
value: options.fadeScaling || defaultOptions.fadeScaling,
},
],
resizable: true,
height: 360,
...promptOptions(),
},
win,
).catch(console.error);
if (!res) {
return undefined;
}
return {
fadeInDuration: Number(res[0]),
fadeOutDuration: Number(res[1]),
secondsBeforeEnd: Number(res[2]),
fadeScaling: res[3],
};
}

View File

@ -1,23 +0,0 @@
import type { ConfigType } from '../../config/dynamic';
export default (options: ConfigType<'disable-autoplay'>) => {
const timeUpdateListener = (e: Event) => {
if (e.target instanceof HTMLVideoElement) {
e.target.pause();
}
};
document.addEventListener('apiLoaded', (apiEvent) => {
const eventListener = (name: string) => {
if (options.applyOnce) {
apiEvent.detail.removeEventListener('videodatachange', eventListener);
}
if (name === 'dataloaded') {
apiEvent.detail.pauseVideo();
document.querySelector<HTMLVideoElement>('video')?.addEventListener('timeupdate', timeUpdateListener, { once: true });
}
};
apiEvent.detail.addEventListener('videodatachange', eventListener);
}, { once: true, passive: true });
};

View File

@ -1,20 +0,0 @@
import { BrowserWindow } from 'electron';
import { setMenuOptions } from '../../config/plugins';
import { MenuTemplate } from '../../menu';
import type { ConfigType } from '../../config/dynamic';
export default (_: BrowserWindow, options: ConfigType<'disable-autoplay'>): MenuTemplate => [
{
label: 'Applies only on startup',
type: 'checkbox',
checked: options.applyOnce,
click() {
setMenuOptions('disable-autoplay', {
applyOnce: !options.applyOnce,
});
}
}
];

View File

@ -1,206 +0,0 @@
import { app, dialog } from 'electron';
import { Client as DiscordClient } from '@xhayper/discord-rpc';
import { dev } from 'electron-is';
import { SetActivity } from '@xhayper/discord-rpc/dist/structures/ClientUser';
import registerCallback, { type SongInfoCallback, type SongInfo } from '../../providers/song-info';
import type { ConfigType } from '../../config/dynamic';
// Application ID registered by @Zo-Bro-23
const clientId = '1043858434585526382';
export interface Info {
rpc: DiscordClient;
ready: boolean;
autoReconnect: boolean;
lastSongInfo?: SongInfo;
}
const info: Info = {
rpc: new DiscordClient({
clientId,
}),
ready: false,
autoReconnect: true,
lastSongInfo: undefined,
};
/**
* @type {(() => void)[]}
*/
const refreshCallbacks: (() => void)[] = [];
const resetInfo = () => {
info.ready = false;
clearTimeout(clearActivity);
if (dev()) {
console.log('discord disconnected');
}
for (const cb of refreshCallbacks) {
cb();
}
};
const connectTimeout = () => new Promise((resolve, reject) => setTimeout(() => {
if (!info.autoReconnect || info.rpc.isConnected) {
return;
}
info.rpc.login().then(resolve).catch(reject);
}, 5000));
const connectRecursive = () => {
if (!info.autoReconnect || info.rpc.isConnected) {
return;
}
connectTimeout().catch(connectRecursive);
};
let window: Electron.BrowserWindow;
export const connect = (showError = false) => {
if (info.rpc.isConnected) {
if (dev()) {
console.log('Attempted to connect with active connection');
}
return;
}
info.ready = false;
// Startup the rpc client
info.rpc.login().catch((error: Error) => {
resetInfo();
if (dev()) {
console.error(error);
}
if (info.autoReconnect) {
connectRecursive();
} else if (showError) {
dialog.showMessageBox(window, {
title: 'Connection failed',
message: error.message || String(error),
type: 'error',
});
}
});
};
let clearActivity: NodeJS.Timeout | undefined;
let updateActivity: SongInfoCallback;
type DiscordOptions = ConfigType<'discord'>;
export default (
win: Electron.BrowserWindow,
options: DiscordOptions,
) => {
info.rpc.on('connected', () => {
if (dev()) {
console.log('discord connected');
}
for (const cb of refreshCallbacks) {
cb();
}
});
info.rpc.on('ready', () => {
info.ready = true;
if (info.lastSongInfo) {
updateActivity(info.lastSongInfo);
}
});
info.rpc.on('disconnected', () => {
resetInfo();
if (info.autoReconnect) {
connectTimeout();
}
});
info.autoReconnect = options.autoReconnect;
window = win;
// We get multiple events
// Next song: PAUSE(n), PAUSE(n+1), PLAY(n+1)
// Skip time: PAUSE(N), PLAY(N)
updateActivity = (songInfo) => {
if (songInfo.title.length === 0 && songInfo.artist.length === 0) {
return;
}
info.lastSongInfo = songInfo;
// Stop the clear activity timout
clearTimeout(clearActivity);
// Stop early if discord connection is not ready
// do this after clearTimeout to avoid unexpected clears
if (!info.rpc || !info.ready) {
return;
}
// Clear directly if timeout is 0
if (songInfo.isPaused && options.activityTimoutEnabled && options.activityTimoutTime === 0) {
info.rpc.user?.clearActivity().catch(console.error);
return;
}
// Song information changed, so lets update the rich presence
// @see https://discord.com/developers/docs/topics/gateway#activity-object
// not all options are transfered through https://github.com/discordjs/RPC/blob/6f83d8d812c87cb7ae22064acd132600407d7d05/src/client.js#L518-530
const activityInfo: SetActivity = {
details: songInfo.title,
state: songInfo.artist,
largeImageKey: songInfo.imageSrc ?? '',
largeImageText: songInfo.album ?? '',
buttons: [
...(options.listenAlong ? [{ label: 'Listen Along', url: songInfo.url ?? '' }] : []),
{ label: 'View App On GitHub', url: 'https://github.com/th-ch/youtube-music' },
],
};
if (songInfo.isPaused) {
// Add a paused icon to show that the song is paused
activityInfo.smallImageKey = 'paused';
activityInfo.smallImageText = 'Paused';
// Set start the timer so the activity gets cleared after a while if enabled
if (options.activityTimoutEnabled) {
clearActivity = setTimeout(() => info.rpc.user?.clearActivity().catch(console.error), options.activityTimoutTime ?? 10_000);
}
} else if (!options.hideDurationLeft) {
// Add the start and end time of the song
const songStartTime = Date.now() - ((songInfo.elapsedSeconds ?? 0) * 1000);
activityInfo.startTimestamp = songStartTime;
activityInfo.endTimestamp
= songStartTime + (songInfo.songDuration * 1000);
}
info.rpc.user?.setActivity(activityInfo).catch(console.error);
};
// If the page is ready, register the callback
win.once('ready-to-show', () => {
registerCallback(updateActivity);
connect();
});
app.on('window-all-closed', clear);
};
export const clear = () => {
if (info.rpc) {
info.rpc.user?.clearActivity();
}
clearTimeout(clearActivity);
};
export const registerRefresh = (cb: () => void) => refreshCallbacks.push(cb);
export const isConnected = () => info.rpc !== null;

View File

@ -1,89 +0,0 @@
import prompt from 'custom-electron-prompt';
import { clear, connect, isConnected, registerRefresh } from './back';
import { setMenuOptions } from '../../config/plugins';
import promptOptions from '../../providers/prompt-options';
import { singleton } from '../../providers/decorators';
import { MenuTemplate } from '../../menu';
import type { ConfigType } from '../../config/dynamic';
const registerRefreshOnce = singleton((refreshMenu: () => void) => {
registerRefresh(refreshMenu);
});
type DiscordOptions = ConfigType<'discord'>;
export default (win: Electron.BrowserWindow, options: DiscordOptions, refreshMenu: () => void): MenuTemplate => {
registerRefreshOnce(refreshMenu);
return [
{
label: isConnected() ? 'Connected' : 'Reconnect',
enabled: !isConnected(),
click: () => connect(),
},
{
label: 'Auto reconnect',
type: 'checkbox',
checked: options.autoReconnect,
click(item: Electron.MenuItem) {
options.autoReconnect = item.checked;
setMenuOptions('discord', options);
},
},
{
label: 'Clear activity',
click: clear,
},
{
label: 'Clear activity after timeout',
type: 'checkbox',
checked: options.activityTimoutEnabled,
click(item: Electron.MenuItem) {
options.activityTimoutEnabled = item.checked;
setMenuOptions('discord', options);
},
},
{
label: 'Listen Along',
type: 'checkbox',
checked: options.listenAlong,
click(item: Electron.MenuItem) {
options.listenAlong = item.checked;
setMenuOptions('discord', options);
},
},
{
label: 'Hide duration left',
type: 'checkbox',
checked: options.hideDurationLeft,
click(item: Electron.MenuItem) {
options.hideDurationLeft = item.checked;
setMenuOptions('discord', options);
},
},
{
label: 'Set inactivity timeout',
click: () => setInactivityTimeout(win, options),
},
];
};
async function setInactivityTimeout(win: Electron.BrowserWindow, options: DiscordOptions) {
const output = await prompt({
title: 'Set Inactivity Timeout',
label: 'Enter inactivity timeout in seconds:',
value: String(Math.round((options.activityTimoutTime ?? 0) / 1e3)),
type: 'counter',
counterOptions: { minimum: 0, multiFire: true },
width: 450,
...promptOptions(),
}, win);
if (output) {
options.activityTimoutTime = Math.round(~~output * 1e3);
setMenuOptions('discord', options);
}
}

View File

@ -1,587 +0,0 @@
import { createWriteStream, existsSync, mkdirSync, writeFileSync, } from 'node:fs';
import { join } from 'node:path';
import { randomBytes } from 'node:crypto';
import { app, BrowserWindow, dialog, ipcMain, net } from 'electron';
import { ClientType, Innertube, UniversalCache, Utils } from 'youtubei.js';
import is from 'electron-is';
import ytpl from 'ytpl';
// REPLACE with youtubei getplaylist https://github.com/LuanRT/YouTube.js#getplaylistid
import filenamify from 'filenamify';
import { Mutex } from 'async-mutex';
import { createFFmpeg } from '@ffmpeg.wasm/main';
import NodeID3, { TagConstants } from 'node-id3';
import PlayerErrorMessage from 'youtubei.js/dist/src/parser/classes/PlayerErrorMessage';
import { FormatOptions } from 'youtubei.js/dist/src/types/FormatUtils';
import TrackInfo from 'youtubei.js/dist/src/parser/ytmusic/TrackInfo';
import { VideoInfo } from 'youtubei.js/dist/src/parser/youtube';
import { cropMaxWidth, getFolder, presets, sendFeedback as sendFeedback_, setBadge } from './utils';
import config from './config';
import style from './style.css';
import { fetchFromGenius } from '../lyrics-genius/back';
import { isEnabled } from '../../config/plugins';
import { cleanupName, getImage, SongInfo } from '../../providers/song-info';
import { injectCSS } from '../utils';
import { cache } from '../../providers/decorators';
import type { GetPlayerResponse } from '../../types/get-player-response';
type CustomSongInfo = SongInfo & { trackId?: string };
const ffmpeg = createFFmpeg({
log: false,
logger() {
}, // Console.log,
progress() {
}, // Console.log,
});
const ffmpegMutex = new Mutex();
let yt: Innertube;
let win: BrowserWindow;
let playingUrl: string;
const sendError = (error: Error, source?: string) => {
win.setProgressBar(-1); // Close progress bar
setBadge(0); // Close badge
sendFeedback_(win); // Reset feedback
const songNameMessage = source ? `\nin ${source}` : '';
const cause = error.cause ? `\n\n${String(error.cause)}` : '';
const message = `${error.toString()}${songNameMessage}${cause}`;
console.error(message, error, error?.stack);
dialog.showMessageBox({
type: 'info',
buttons: ['OK'],
title: 'Error in download!',
message: 'Argh! Apologies, download failed…',
detail: message,
});
};
export default async (win_: BrowserWindow) => {
win = win_;
injectCSS(win.webContents, style);
const cookie = (await win.webContents.session.cookies.get({ url: 'https://music.youtube.com' })).map((it) =>
it.name + '=' + it.value + ';'
).join('');
yt = await Innertube.create({
cache: new UniversalCache(false),
cookie,
generate_session_locally: true,
fetch: async (input: RequestInfo | URL, init?: RequestInit) => {
const url =
typeof input === 'string' ?
new URL(input) :
input instanceof URL ?
input : new URL(input.url);
if (init?.body && !init.method) {
init.method = 'POST';
}
const request = new Request(
url,
input instanceof Request ? input : undefined,
);
return net.fetch(request, init);
}
});
ipcMain.on('download-song', (_, url: string) => downloadSong(url));
ipcMain.on('video-src-changed', (_, data: GetPlayerResponse) => {
playingUrl = data.microformat.microformatDataRenderer.urlCanonical;
});
ipcMain.on('download-playlist-request', async (_event, url: string) =>
downloadPlaylist(url),
);
};
export async function downloadSong(
url: string,
playlistFolder: string | undefined = undefined,
trackId: string | undefined = undefined,
increasePlaylistProgress: (value: number) => void = () => {
},
) {
let resolvedName;
try {
await downloadSongUnsafe(
url,
(name: string) => resolvedName = name,
playlistFolder,
trackId,
increasePlaylistProgress,
);
} catch (error: unknown) {
sendError(error as Error, resolvedName || url);
}
}
async function downloadSongUnsafe(
url: string,
setName: (name: string) => void,
playlistFolder: string | undefined = undefined,
trackId: string | undefined = undefined,
increasePlaylistProgress: (value: number) => void = () => {},
) {
const sendFeedback = (message: unknown, progress?: number) => {
if (!playlistFolder) {
sendFeedback_(win, message);
if (progress && !isNaN(progress)) {
win.setProgressBar(progress);
}
}
};
sendFeedback('Downloading...', 2);
const id = getVideoId(url);
if (typeof id !== 'string') throw new Error('Video not found');
let info: TrackInfo | VideoInfo = await yt.music.getInfo(id);
if (!info) {
throw new Error('Video not found');
}
const metadata = getMetadata(info);
if (metadata.album === 'N/A') {
metadata.album = '';
}
metadata.trackId = trackId;
const dir
= playlistFolder || config.get('downloadFolder') || app.getPath('downloads');
const name = `${metadata.artist ? `${metadata.artist} - ` : ''}${
metadata.title
}`;
setName(name);
let playabilityStatus = info.playability_status;
let bypassedResult = null;
if (playabilityStatus.status === 'LOGIN_REQUIRED') {
// Try to bypass the age restriction
bypassedResult = await getAndroidTvInfo(id);
playabilityStatus = bypassedResult.playability_status;
if (playabilityStatus.status === 'LOGIN_REQUIRED') {
throw new Error(
`[${playabilityStatus.status}] ${playabilityStatus.reason}`,
);
}
info = bypassedResult;
}
if (playabilityStatus.status === 'UNPLAYABLE') {
const errorScreen = playabilityStatus.error_screen as PlayerErrorMessage | null;
throw new Error(
`[${playabilityStatus.status}] ${errorScreen?.reason.text}: ${errorScreen?.subreason.text}`,
);
}
const preset = config.get('preset') ?? 'mp3';
let presetSetting: { extension: string; ffmpegArgs: string[] } | null = null;
if (preset === 'opus') {
presetSetting = presets[preset];
}
const filename = filenamify(`${name}.${presetSetting?.extension ?? 'mp3'}`, {
replacement: '_',
maxLength: 255,
});
const filePath = join(dir, filename);
if (config.get('skipExisting') && existsSync(filePath)) {
sendFeedback(null, -1);
return;
}
const downloadOptions: FormatOptions = {
type: 'audio', // Audio, video or video+audio
quality: 'best', // Best, bestefficiency, 144p, 240p, 480p, 720p and so on.
format: 'any', // Media container format
};
const format = info.chooseFormat(downloadOptions);
const stream = await info.download(downloadOptions);
console.info(
`Downloading ${metadata.artist} - ${metadata.title} [${metadata.videoId}]`,
);
const iterableStream = Utils.streamToIterable(stream);
if (!existsSync(dir)) {
mkdirSync(dir);
}
const ffmpegArgs = config.get('ffmpegArgs');
if (presetSetting && presetSetting?.extension !== 'mp3') {
const file = createWriteStream(filePath);
let downloaded = 0;
const total: number = format.content_length ?? 1;
for await (const chunk of iterableStream) {
downloaded += chunk.length;
const ratio = downloaded / total;
const progress = Math.floor(ratio * 100);
sendFeedback(`Download: ${progress}%`, ratio);
increasePlaylistProgress(ratio);
file.write(chunk);
}
await ffmpegWriteTags(
filePath,
metadata,
presetSetting.ffmpegArgs,
ffmpegArgs,
);
sendFeedback(null, -1);
} else {
const fileBuffer = await iterableStreamToMP3(
iterableStream,
metadata,
ffmpegArgs,
format.content_length ?? 0,
sendFeedback,
increasePlaylistProgress,
);
if (fileBuffer) {
const buffer = await writeID3(Buffer.from(fileBuffer), metadata, sendFeedback);
if (buffer) {
writeFileSync(filePath, buffer);
}
}
}
sendFeedback(null, -1);
console.info(`Done: "${filePath}"`);
}
async function iterableStreamToMP3(
stream: AsyncGenerator<Uint8Array, void>,
metadata: CustomSongInfo,
ffmpegArgs: string[],
contentLength: number,
sendFeedback: (str: string, value?: number) => void,
increasePlaylistProgress: (value: number) => void = () => {
},
) {
const chunks = [];
let downloaded = 0;
for await (const chunk of stream) {
downloaded += chunk.length;
chunks.push(chunk);
const ratio = downloaded / contentLength;
const progress = Math.floor(ratio * 100);
sendFeedback(`Download: ${progress}%`, ratio);
// 15% for download, 85% for conversion
// This is a very rough estimate, trying to make the progress bar look nice
increasePlaylistProgress(ratio * 0.15);
}
sendFeedback('Loading…', 2); // Indefinite progress bar after download
const buffer = Buffer.concat(chunks);
const safeVideoName = randomBytes(32).toString('hex');
const releaseFFmpegMutex = await ffmpegMutex.acquire();
try {
if (!ffmpeg.isLoaded()) {
await ffmpeg.load();
}
sendFeedback('Preparing file…');
ffmpeg.FS('writeFile', safeVideoName, buffer);
sendFeedback('Converting…');
ffmpeg.setProgress(({ ratio }) => {
sendFeedback(`Converting: ${Math.floor(ratio * 100)}%`, ratio);
increasePlaylistProgress(0.15 + (ratio * 0.85));
});
try {
await ffmpeg.run(
'-i',
safeVideoName,
...ffmpegArgs,
...getFFmpegMetadataArgs(metadata),
`${safeVideoName}.mp3`,
);
} finally {
ffmpeg.FS('unlink', safeVideoName);
}
sendFeedback('Saving…');
try {
return ffmpeg.FS('readFile', `${safeVideoName}.mp3`);
} finally {
ffmpeg.FS('unlink', `${safeVideoName}.mp3`);
}
} catch (error: unknown) {
sendError(error as Error, safeVideoName);
} finally {
releaseFFmpegMutex();
}
}
const getCoverBuffer = cache(async (url: string) => {
const nativeImage = cropMaxWidth(await getImage(url));
return nativeImage && !nativeImage.isEmpty() ? nativeImage.toPNG() : null;
});
async function writeID3(buffer: Buffer, metadata: CustomSongInfo, sendFeedback: (str: string, value?: number) => void) {
try {
sendFeedback('Writing ID3 tags...');
const tags: NodeID3.Tags = {};
// Create the metadata tags
tags.title = metadata.title;
tags.artist = metadata.artist;
if (metadata.album) {
tags.album = metadata.album;
}
const coverBuffer = await getCoverBuffer(metadata.imageSrc ?? '');
if (coverBuffer) {
tags.image = {
mime: 'image/png',
type: {
id: TagConstants.AttachedPicture.PictureType.FRONT_COVER,
},
description: 'thumbnail',
imageBuffer: coverBuffer,
};
}
if (isEnabled('lyrics-genius')) {
const lyrics = await fetchFromGenius(metadata);
if (lyrics) {
tags.unsynchronisedLyrics = {
language: '',
text: lyrics,
};
}
}
if (metadata.trackId) {
tags.trackNumber = metadata.trackId;
}
return NodeID3.write(tags, buffer);
} catch (error: unknown) {
sendError(error as Error, `${metadata.artist} - ${metadata.title}`);
return null;
}
}
export async function downloadPlaylist(givenUrl?: string | URL) {
try {
givenUrl = new URL(givenUrl ?? '');
} catch {
return;
}
const playlistId
= getPlaylistID(givenUrl)
|| getPlaylistID(new URL(win.webContents.getURL()))
|| getPlaylistID(new URL(playingUrl));
if (!playlistId) {
sendError(new Error('No playlist ID found'));
return;
}
const sendFeedback = (message?: unknown) => sendFeedback_(win, message);
console.log(`trying to get playlist ID: '${playlistId}'`);
sendFeedback('Getting playlist info…');
let playlist: ytpl.Result;
try {
playlist = await ytpl(playlistId, {
limit: config.get('playlistMaxItems') || Number.POSITIVE_INFINITY,
});
} catch (error: unknown) {
sendError(
Error(`Error getting playlist info: make sure it isn't a private or "Mixed for you" playlist\n\n${String(error)}`),
);
return;
}
if (playlist.items.length === 0) {
sendError(new Error('Playlist is empty'));
}
if (playlist.items.length === 1) {
sendFeedback('Playlist has only one item, downloading it directly');
await downloadSong(playlist.items[0].url);
return;
}
const isAlbum = playlist.title.startsWith('Album - ');
if (isAlbum) {
playlist.title = playlist.title.slice(8);
}
const safePlaylistTitle = filenamify(playlist.title, { replacement: ' ' });
const folder = getFolder(config.get('downloadFolder') ?? '');
const playlistFolder = join(folder, safePlaylistTitle);
if (existsSync(playlistFolder)) {
if (!config.get('skipExisting')) {
sendError(new Error(`The folder ${playlistFolder} already exists`));
return;
}
} else {
mkdirSync(playlistFolder, { recursive: true });
}
dialog.showMessageBox({
type: 'info',
buttons: ['OK'],
title: 'Started Download',
message: `Downloading Playlist "${playlist.title}"`,
detail: `(${playlist.items.length} songs)`,
});
if (is.dev()) {
console.log(
`Downloading playlist "${playlist.title}" - ${playlist.items.length} songs (${playlistId})`,
);
}
win.setProgressBar(2); // Starts with indefinite bar
setBadge(playlist.items.length);
let counter = 1;
const progressStep = 1 / playlist.items.length;
const increaseProgress = (itemPercentage: number) => {
const currentProgress = (counter - 1) / (playlist.items.length ?? 1);
const newProgress = currentProgress + (progressStep * itemPercentage);
win.setProgressBar(newProgress);
};
try {
for (const song of playlist.items) {
sendFeedback(`Downloading ${counter}/${playlist.items.length}...`);
const trackId = isAlbum ? counter : undefined;
await downloadSong(
song.url,
playlistFolder,
trackId?.toString(),
increaseProgress,
).catch((error) =>
sendError(
new Error(`Error downloading "${song.author.name} - ${song.title}":\n ${error}`)
),
);
win.setProgressBar(counter / playlist.items.length);
setBadge(playlist.items.length - counter);
counter++;
}
} catch (error: unknown) {
sendError(error as Error);
} finally {
win.setProgressBar(-1); // Close progress bar
setBadge(0); // Close badge counter
sendFeedback(); // Clear feedback
}
}
async function ffmpegWriteTags(filePath: string, metadata: CustomSongInfo, presetFFmpegArgs: string[] = [], ffmpegArgs: string[] = []) {
const releaseFFmpegMutex = await ffmpegMutex.acquire();
try {
if (!ffmpeg.isLoaded()) {
await ffmpeg.load();
}
await ffmpeg.run(
'-i',
filePath,
...getFFmpegMetadataArgs(metadata),
...presetFFmpegArgs,
...ffmpegArgs,
filePath,
);
} catch (error: unknown) {
sendError(error as Error);
} finally {
releaseFFmpegMutex();
}
}
function getFFmpegMetadataArgs(metadata: CustomSongInfo) {
if (!metadata) {
return [];
}
return [
...(metadata.title ? ['-metadata', `title=${metadata.title}`] : []),
...(metadata.artist ? ['-metadata', `artist=${metadata.artist}`] : []),
...(metadata.album ? ['-metadata', `album=${metadata.album}`] : []),
...(metadata.trackId ? ['-metadata', `track=${metadata.trackId}`] : []),
];
}
// Playlist radio modifier needs to be cut from playlist ID
const INVALID_PLAYLIST_MODIFIER = 'RDAMPL';
const getPlaylistID = (aURL: URL) => {
const result
= aURL?.searchParams.get('list') || aURL?.searchParams.get('playlist');
if (result?.startsWith(INVALID_PLAYLIST_MODIFIER)) {
return result.slice(INVALID_PLAYLIST_MODIFIER.length);
}
return result;
};
const getVideoId = (url: URL | string): string | null => {
return (new URL(url)).searchParams.get('v');
};
const getMetadata = (info: TrackInfo): CustomSongInfo => ({
videoId: info.basic_info.id!,
title: cleanupName(info.basic_info.title!),
artist: cleanupName(info.basic_info.author!),
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-explicit-any
album: (info.player_overlays?.browser_media_session as any)?.album?.text as string | undefined,
imageSrc: info.basic_info.thumbnail?.find((t) => !t.url.endsWith('.webp'))?.url,
views: info.basic_info.view_count!,
songDuration: info.basic_info.duration!,
});
// This is used to bypass age restrictions
const getAndroidTvInfo = async (id: string): Promise<VideoInfo> => {
const innertube = await Innertube.create({
client_type: ClientType.TV_EMBEDDED,
generate_session_locally: true,
retrieve_player: true,
});
// GetInfo 404s with the bypass, so we use getBasicInfo instead
// that's fine as we only need the streaming data
return await innertube.getBasicInfo(id, 'TV_EMBEDDED');
};

View File

@ -1,4 +0,0 @@
import { PluginConfig } from '../../config/dynamic';
const config = new PluginConfig('downloader');
export default config;

View File

@ -1,83 +0,0 @@
import { ipcRenderer } from 'electron';
import downloadHTML from './templates/download.html';
import defaultConfig from '../../config/defaults';
import { getSongMenu } from '../../providers/dom-elements';
import { ElementFromHtml } from '../utils';
import { getSongInfo } from '../../providers/song-info-front';
let menu: Element | null = null;
let progress: Element | null = null;
const downloadButton = ElementFromHtml(downloadHTML);
let doneFirstLoad = false;
const menuObserver = new MutationObserver(() => {
if (!menu) {
menu = getSongMenu();
if (!menu) {
return;
}
}
if (menu.contains(downloadButton)) {
return;
}
const menuUrl = document.querySelector<HTMLAnchorElement>('tp-yt-paper-listbox [tabindex="0"] #navigation-endpoint')?.href;
if (!menuUrl?.includes('watch?') && doneFirstLoad) {
return;
}
menu.prepend(downloadButton);
progress = document.querySelector('#ytmcustom-download');
if (doneFirstLoad) {
return;
}
setTimeout(() => doneFirstLoad ||= true, 500);
});
// TODO: re-enable once contextIsolation is set to true
// contextBridge.exposeInMainWorld("downloader", {
// download: () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-member-access
(global as any).download = () => {
let videoUrl = getSongMenu()
// Selector of first button which is always "Start Radio"
?.querySelector('ytmusic-menu-navigation-item-renderer[tabindex="0"] #navigation-endpoint')
?.getAttribute('href');
if (videoUrl) {
if (videoUrl.startsWith('watch?')) {
videoUrl = defaultConfig.url + '/' + videoUrl;
}
if (videoUrl.includes('?playlist=')) {
ipcRenderer.send('download-playlist-request', videoUrl);
return;
}
} else {
videoUrl = getSongInfo().url || window.location.href;
}
ipcRenderer.send('download-song', videoUrl);
};
export default () => {
document.addEventListener('apiLoaded', () => {
menuObserver.observe(document.querySelector('ytmusic-popup-container')!, {
childList: true,
subtree: true,
});
}, { once: true, passive: true });
ipcRenderer.on('downloader-feedback', (_, feedback: string) => {
if (progress) {
progress.innerHTML = feedback || 'Download';
} else {
console.warn('Cannot update progress');
}
});
};

View File

@ -1,45 +0,0 @@
import { dialog } from 'electron';
import { downloadPlaylist } from './back';
import { defaultMenuDownloadLabel, getFolder, presets } from './utils';
import config from './config';
import { MenuTemplate } from '../../menu';
export default (): MenuTemplate => [
{
label: defaultMenuDownloadLabel,
click: () => downloadPlaylist(),
},
{
label: 'Choose download folder',
click() {
const result = dialog.showOpenDialogSync({
properties: ['openDirectory', 'createDirectory'],
defaultPath: getFolder(config.get('downloadFolder') ?? ''),
});
if (result) {
config.set('downloadFolder', result[0]);
} // Else = user pressed cancel
},
},
{
label: 'Presets',
submenu: Object.keys(presets).map((preset) => ({
label: preset,
type: 'radio',
checked: config.get('preset') === preset,
click() {
config.set('preset', preset);
},
})),
},
{
label: 'Skip existing files',
type: 'checkbox',
checked: config.get('skipExisting'),
click(item) {
config.set('skipExisting', item.checked);
},
},
];

View File

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

View File

@ -1,3 +0,0 @@
<svg fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="#ffffff" d="m4.21 4.387.083-.094a1 1 0 0 1 1.32-.083l.094.083L12 10.585l6.293-6.292a1 1 0 1 1 1.414 1.414L13.415 12l6.292 6.293a1 1 0 0 1 .083 1.32l-.083.094a1 1 0 0 1-1.32.083l-.094-.083L12 13.415l-6.293 6.292a1 1 0 0 1-1.414-1.414L10.585 12 4.293 5.707a1 1 0 0 1-.083-1.32l.083-.094-.083.094Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 392 B

View File

@ -1,3 +0,0 @@
<svg fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="#ffffff" d="M6 3h12a3 3 0 0 1 3 3v12a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3V6a3 3 0 0 1 3-3Zm0 2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1H6Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 252 B

View File

@ -1,3 +0,0 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M3 17h12a1 1 0 0 1 .117 1.993L15 19H3a1 1 0 0 1-.117-1.993L3 17h12H3Zm0-6h18a1 1 0 0 1 .117 1.993L21 13H3a1 1 0 0 1-.117-1.993L3 11h18H3Zm0-6h15a1 1 0 0 1 .117 1.993L18 7H3a1 1 0 0 1-.117-1.993L3 5h15H3Z" fill="#ffffff"/>
</svg>

Before

Width:  |  Height:  |  Size: 338 B

View File

@ -1,3 +0,0 @@
<svg fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="#ffffff" d="M3.755 12.5h16.492a.75.75 0 0 0 0-1.5H3.755a.75.75 0 0 0 0 1.5Z" />
</svg>

Before

Width:  |  Height:  |  Size: 174 B

View File

@ -1,3 +0,0 @@
<svg fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="#ffffff" d="M7.518 5H6.009a3.25 3.25 0 0 1 3.24-3h8.001A4.75 4.75 0 0 1 22 6.75v8a3.25 3.25 0 0 1-3 3.24v-1.508a1.75 1.75 0 0 0 1.5-1.732v-8a3.25 3.25 0 0 0-3.25-3.25h-8A1.75 1.75 0 0 0 7.518 5ZM5.25 6A3.25 3.25 0 0 0 2 9.25v9.5A3.25 3.25 0 0 0 5.25 22h9.5A3.25 3.25 0 0 0 18 18.75v-9.5A3.25 3.25 0 0 0 14.75 6h-9.5ZM3.5 9.25c0-.966.784-1.75 1.75-1.75h9.5c.967 0 1.75.784 1.75 1.75v9.5a1.75 1.75 0 0 1-1.75 1.75h-9.5a1.75 1.75 0 0 1-1.75-1.75v-9.5Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 546 B

View File

@ -1,65 +0,0 @@
import { register } from 'electron-localshortcut';
import { BrowserWindow, Menu, MenuItem, ipcMain } from 'electron';
import titlebarStyle from './titlebar.css';
import { injectCSS } from '../utils';
// Tracks menu visibility
export default (win: BrowserWindow) => {
injectCSS(win.webContents, titlebarStyle);
win.once('ready-to-show', () => {
register(win, '`', () => {
win.webContents.send('toggleMenu');
});
});
ipcMain.handle(
'get-menu',
() => JSON.parse(JSON.stringify(
Menu.getApplicationMenu(),
(key: string, value: unknown) => (key !== 'commandsMap' && key !== 'menu') ? value : undefined),
),
);
const getMenuItemById = (commandId: number): MenuItem | null => {
const menu = Menu.getApplicationMenu();
let target: MenuItem | null = null;
const stack = [...menu?.items ?? []];
while (stack.length > 0) {
const now = stack.shift();
now?.submenu?.items.forEach((item) => stack.push(item));
if (now?.commandId === commandId) {
target = now;
break;
}
}
return target;
};
ipcMain.handle('menu-event', (event, commandId: number) => {
const target = getMenuItemById(commandId);
if (target) target.click(undefined, BrowserWindow.fromWebContents(event.sender), event.sender);
});
ipcMain.handle('get-menu-by-id', (_, commandId: number) => {
const result = getMenuItemById(commandId);
return JSON.parse(JSON.stringify(
result,
(key: string, value: unknown) => (key !== 'commandsMap' && key !== 'menu') ? value : undefined),
);
});
ipcMain.handle('window-is-maximized', () => win.isMaximized());
ipcMain.handle('window-close', () => win.close());
ipcMain.handle('window-minimize', () => win.minimize());
ipcMain.handle('window-maximize', () => win.maximize());
ipcMain.handle('window-unmaximize', () => win.unmaximize());
};

View File

@ -1,156 +0,0 @@
import { ipcRenderer, Menu } from 'electron';
import { createPanel } from './menu/panel';
import logo from './assets/menu.svg';
import close from './assets/close.svg';
import minimize from './assets/minimize.svg';
import maximize from './assets/maximize.svg';
import unmaximize from './assets/unmaximize.svg';
import { isEnabled } from '../../config/plugins';
import config from '../../config';
function $<E extends Element = Element>(selector: string) {
return document.querySelector<E>(selector);
}
const isMacOS = navigator.userAgent.includes('Macintosh');
const isNotWindowsOrMacOS = !navigator.userAgent.includes('Windows') && !isMacOS;
export default async () => {
let hideMenu = config.get('options.hideMenu');
const titleBar = document.createElement('title-bar');
const navBar = document.querySelector<HTMLDivElement>('#nav-bar-background');
if (isMacOS) titleBar.style.setProperty('--offset-left', '70px');
logo.classList.add('title-bar-icon');
const logoClick = () => {
hideMenu = !hideMenu;
let visibilityStyle: string;
if (hideMenu) {
visibilityStyle = 'hidden';
} else {
visibilityStyle = 'visible';
}
const menus = document.querySelectorAll<HTMLElement>('menu-button');
menus.forEach((menu) => {
menu.style.visibility = visibilityStyle;
});
};
logo.onclick = logoClick;
ipcRenderer.on('toggleMenu', logoClick);
if (!isMacOS) titleBar.appendChild(logo);
document.body.appendChild(titleBar);
titleBar.appendChild(logo);
const addWindowControls = async () => {
// Create window control buttons
const minimizeButton = document.createElement('button');
minimizeButton.classList.add('window-control');
minimizeButton.appendChild(minimize);
minimizeButton.onclick = () => ipcRenderer.invoke('window-minimize');
const maximizeButton = document.createElement('button');
if (await ipcRenderer.invoke('window-is-maximized')) {
maximizeButton.classList.add('window-control');
maximizeButton.appendChild(unmaximize);
} else {
maximizeButton.classList.add('window-control');
maximizeButton.appendChild(maximize);
}
maximizeButton.onclick = async () => {
if (await ipcRenderer.invoke('window-is-maximized')) {
// change icon to maximize
maximizeButton.removeChild(maximizeButton.firstChild!);
maximizeButton.appendChild(maximize);
// call unmaximize
await ipcRenderer.invoke('window-unmaximize');
} else {
// change icon to unmaximize
maximizeButton.removeChild(maximizeButton.firstChild!);
maximizeButton.appendChild(unmaximize);
// call maximize
await ipcRenderer.invoke('window-maximize');
}
};
const closeButton = document.createElement('button');
closeButton.classList.add('window-control');
closeButton.appendChild(close);
closeButton.onclick = () => ipcRenderer.invoke('window-close');
// Create a container div for the window control buttons
const windowControlsContainer = document.createElement('div');
windowControlsContainer.classList.add('window-controls-container');
windowControlsContainer.appendChild(minimizeButton);
windowControlsContainer.appendChild(maximizeButton);
windowControlsContainer.appendChild(closeButton);
// Add window control buttons to the title bar
titleBar.appendChild(windowControlsContainer);
};
if (isNotWindowsOrMacOS) await addWindowControls();
if (navBar) {
const observer = new MutationObserver((mutations) => {
mutations.forEach(() => {
titleBar.style.setProperty('--titlebar-background-color', navBar.style.backgroundColor);
document.querySelector('html')!.style.setProperty('--titlebar-background-color', navBar.style.backgroundColor);
});
});
observer.observe(navBar, { attributes : true, attributeFilter : ['style'] });
}
const updateMenu = async () => {
const children = [...titleBar.children];
children.forEach((child) => {
if (child !== logo) child.remove();
});
const menu = await ipcRenderer.invoke('get-menu') as Menu | null;
if (!menu) return;
menu.items.forEach((menuItem) => {
const menu = document.createElement('menu-button');
createPanel(titleBar, menu, menuItem.submenu?.items ?? []);
menu.append(menuItem.label);
titleBar.appendChild(menu);
if (hideMenu) {
menu.style.visibility = 'hidden';
}
});
if (isNotWindowsOrMacOS) await addWindowControls();
};
await updateMenu();
document.title = 'Youtube Music';
ipcRenderer.on('refreshMenu', () => {
updateMenu();
});
if (isEnabled('picture-in-picture')) {
ipcRenderer.on('pip-toggle', () => {
updateMenu();
});
}
// Increases the right margin of Navbar background when the scrollbar is visible to avoid blocking it (z-index doesn't affect it)
document.addEventListener('apiLoaded', () => {
const htmlHeadStyle = $('head > div > style');
if (htmlHeadStyle) {
// HACK: This is a hack to remove the scrollbar width
htmlHeadStyle.innerHTML = htmlHeadStyle.innerHTML.replace('html::-webkit-scrollbar {width: var(--ytmusic-scrollbar-width);', 'html::-webkit-scrollbar {');
}
}, { once: true, passive: true });
};

View File

@ -1,10 +0,0 @@
const Icons = {
submenu: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none" /><polyline points="9 6 15 12 9 18" /></svg>',
checkbox: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 12l5 5l10 -10" /></svg>',
radio: {
checked: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" style="padding: 2px"><path fill="currentColor" d="M10,5 C7.2,5 5,7.2 5,10 C5,12.8 7.2,15 10,15 C12.8,15 15,12.8 15,10 C15,7.2 12.8,5 10,5 L10,5 Z M10,0 C4.5,0 0,4.5 0,10 C0,15.5 4.5,20 10,20 C15.5,20 20,15.5 20,10 C20,4.5 15.5,0 10,0 L10,0 Z M10,18 C5.6,18 2,14.4 2,10 C2,5.6 5.6,2 10,2 C14.4,2 18,5.6 18,10 C18,14.4 14.4,18 10,18 L10,18 Z" /></svg>',
unchecked: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" style="padding: 2px"><path fill="currentColor" d="M10,0 C4.5,0 0,4.5 0,10 C0,15.5 4.5,20 10,20 C15.5,20 20,15.5 20,10 C20,4.5 15.5,0 10,0 L10,0 Z M10,18 C5.6,18 2,14.4 2,10 C2,5.6 5.6,2 10,2 C14.4,2 18,5.6 18,10 C18,14.4 14.4,18 10,18 L10,18 Z" /></svg>',
},
};
export default Icons;

View File

@ -1,134 +0,0 @@
import { nativeImage, type MenuItem, ipcRenderer, Menu } from 'electron';
import Icons from './icons';
import { ElementFromHtml } from '../../utils';
interface PanelOptions {
placement?: 'bottom' | 'right';
order?: number;
}
export const createPanel = (
parent: HTMLElement,
anchor: HTMLElement,
items: MenuItem[],
options: PanelOptions = { placement: 'bottom', order: 0 },
) => {
const childPanels: HTMLElement[] = [];
const panel = document.createElement('menu-panel');
panel.style.zIndex = `${options.order}`;
const updateIconState = (iconWrapper: HTMLElement, item: MenuItem) => {
if (item.type === 'checkbox') {
if (item.checked) iconWrapper.innerHTML = Icons.checkbox;
else iconWrapper.innerHTML = '';
} else if (item.type === 'radio') {
if (item.checked) iconWrapper.innerHTML = Icons.radio.checked;
else iconWrapper.innerHTML = Icons.radio.unchecked;
} else {
const nativeImageIcon = typeof item.icon === 'string' ? nativeImage.createFromPath(item.icon) : item.icon;
const iconURL = nativeImageIcon?.toDataURL();
if (iconURL) iconWrapper.style.background = `url(${iconURL})`;
}
};
const radioGroups: [MenuItem, HTMLElement][] = [];
items.map((item) => {
if (item.type === 'separator') return panel.appendChild(document.createElement('menu-separator'));
const menu = document.createElement('menu-item');
const iconWrapper = document.createElement('menu-icon');
updateIconState(iconWrapper, item);
menu.appendChild(iconWrapper);
menu.append(item.label);
menu.addEventListener('click', async () => {
await ipcRenderer.invoke('menu-event', item.commandId);
const menuItem = await ipcRenderer.invoke('get-menu-by-id', item.commandId) as MenuItem | null;
if (menuItem) {
updateIconState(iconWrapper, menuItem);
if (menuItem.type === 'radio') {
await Promise.all(
radioGroups.map(async ([item, iconWrapper]) => {
if (item.commandId === menuItem.commandId) return;
const newItem = await ipcRenderer.invoke('get-menu-by-id', item.commandId) as MenuItem | null;
if (newItem) updateIconState(iconWrapper, newItem);
})
);
}
}
});
if (item.type === 'radio') {
radioGroups.push([item, iconWrapper]);
}
if (item.type === 'submenu') {
const subMenuIcon = document.createElement('menu-icon');
subMenuIcon.appendChild(ElementFromHtml(Icons.submenu));
menu.appendChild(subMenuIcon);
const [child, , children] = createPanel(parent, menu, item.submenu?.items ?? [], {
placement: 'right',
order: (options?.order ?? 0) + 1,
});
childPanels.push(child);
children.push(...children);
}
panel.appendChild(menu);
});
/* methods */
const isOpened = () => panel.getAttribute('open') === 'true';
const close = () => panel.setAttribute('open', 'false');
const open = () => {
const rect = anchor.getBoundingClientRect();
if (options.placement === 'bottom') {
panel.style.setProperty('--x', `${rect.x}px`);
panel.style.setProperty('--y', `${rect.y + rect.height}px`);
} else {
panel.style.setProperty('--x', `${rect.x + rect.width}px`);
panel.style.setProperty('--y', `${rect.y}px`);
}
panel.setAttribute('open', 'true');
// Children are placed below their parent item, which can cause
// long lists to squeeze their children at the bottom of the screen
// (This needs to be done *after* setAttribute)
panel.classList.remove('position-by-bottom');
if (options.placement === 'right' && panel.scrollHeight > panel.clientHeight ) {
panel.style.setProperty('--y', `${rect.y + rect.height}px`);
panel.classList.add('position-by-bottom');
}
};
anchor.addEventListener('click', () => {
if (isOpened()) close();
else open();
});
document.body.addEventListener('click', (event) => {
const path = event.composedPath();
const isInside = path.some((it) => it === panel || it === anchor || childPanels.includes(it as HTMLElement));
if (!isInside) close();
});
parent.appendChild(panel);
return [
panel,
{ isOpened, close, open },
childPanels,
] as const;
};

View File

@ -1,180 +0,0 @@
:root {
--titlebar-background-color: #030303;
--menu-bar-height: 36px;
}
title-bar {
-webkit-app-region: drag;
box-sizing: border-box;
position: fixed;
top: 0;
z-index: 10000000;
width: 100%;
height: var(--menu-bar-height, 36px);
display: flex;
flex-flow: row;
justify-content: flex-start;
align-items: center;
gap: 4px;
color: #f1f1f1;
font-size: 14px;
padding: 4px 12px;
padding-left: var(--offset-left, 12px);
background-color: var(--titlebar-background-color, #030303);
user-select: none;
transition: opacity 200ms ease 0s, background-color 300ms cubic-bezier(0.2, 0, 0.6, 1) 0s;
}
menu-button {
-webkit-app-region: none;
display: flex;
justify-content: center;
align-items: center;
align-self: stretch;
padding: 2px 8px;
border-radius: 4px;
cursor: pointer;
}
menu-button:hover {
background-color: rgba(255, 255, 255, 0.1);
}
menu-panel {
position: fixed;
top: var(--y, 0);
left: var(--x, 0);
max-height: calc(100vh - var(--menu-bar-height, 36px) - 16px - var(--y, 0));
display: flex;
flex-flow: column;
justify-content: flex-start;
align-items: stretch;
gap: 0;
overflow: auto;
padding: 4px;
border-radius: 8px;
pointer-events: none;
background-color: color-mix(in srgb, var(--titlebar-background-color, #030303) 50%, rgba(0, 0, 0, 0.1));
backdrop-filter: blur(8px);
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05), 0 2px 8px rgba(0, 0, 0, 0.2);
z-index: 0;
opacity: 0;
transform: scale(0.8);
transform-origin: top left;
transition: opacity 200ms ease 0s, transform 200ms ease 0s;
}
menu-panel[open="true"] {
pointer-events: all;
opacity: 1;
transform: scale(1);
}
menu-panel.position-by-bottom {
top: unset;
bottom: calc(100vh - var(--y, 100%));
max-height: calc(var(--y, 0) - var(--menu-bar-height, 36px) - 16px);
}
menu-item {
-webkit-app-region: none;
min-height: 32px;
height: 32px;
display: grid;
grid-template-columns: 32px 1fr minmax(32px, auto);
justify-content: flex-start;
align-items: center;
border-radius: 4px;
cursor: pointer;
}
menu-item:hover {
background-color: rgba(255, 255, 255, 0.1);
}
menu-item > menu-icon {
height: 32px;
padding: 4px;
box-sizing: border-box;
}
menu-separator {
min-height: 1px;
height: 1px;
margin: 4px 0;
background-color: rgba(255, 255, 255, 0.2);
}
/* classes */
.title-bar-icon {
height: calc(100% - 8px);
object-fit: cover;
margin-left: -4px;
}
/* Window control container */
.window-controls-container {
-webkit-app-region: no-drag;
display: flex;
justify-content: flex-end; /* Align to the right end of the title-bar */
align-items: center;
gap: 4px; /* Add spacing between the window control buttons */
position: absolute; /* Position it absolutely within title-bar */
right: 4px; /* Adjust the right position as needed */
}
/* Window control buttons */
.window-control {
width: 24px;
height: 24px;
background: none;
border: none;
cursor: pointer;
display: flex;
align-items: center;
color: #f1f1f1;
font-size: 14px;
padding: 0;
}
/* youtube-music style */
ytmusic-app-layout {
margin-top: var(--menu-bar-height, 36px) !important;
}
ytmusic-app-layout>[slot=nav-bar], #nav-bar-background.ytmusic-app-layout {
top: var(--menu-bar-height, 36px) !important;
}
#nav-bar-divider.ytmusic-app-layout {
top: calc(var(--ytmusic-nav-bar-height) + var(--menu-bar-height, 36px)) !important;
}
ytmusic-app[is-bauhaus-sidenav-enabled] #guide-spacer.ytmusic-app,
ytmusic-app[is-bauhaus-sidenav-enabled] #mini-guide-spacer.ytmusic-app {
margin-top: calc(var(--ytmusic-nav-bar-height) + var(--menu-bar-height, 36px)) !important;
}
ytmusic-app-layout>[slot=player-page] {
margin-top: var(--menu-bar-height);
height: calc(100vh - var(--menu-bar-height) - var(--ytmusic-nav-bar-height) - var(--ytmusic-player-bar-height)) !important;
}
ytmusic-guide-renderer {
height: calc(100vh - var(--menu-bar-height) - var(--ytmusic-nav-bar-height)) !important;
}

View File

@ -1,203 +0,0 @@
import crypto from 'node:crypto';
import { BrowserWindow, net, shell } from 'electron';
import { setOptions } from '../../config/plugins';
import registerCallback, { SongInfo } from '../../providers/song-info';
import defaultConfig from '../../config/defaults';
import type { ConfigType } from '../../config/dynamic';
type LastFMOptions = ConfigType<'last-fm'>;
interface LastFmData {
method: string,
timestamp?: number,
}
interface LastFmSongData {
track?: string,
duration?: number,
artist?: string,
album?: string,
api_key: string,
sk?: string,
format: string,
method: string,
timestamp?: number,
api_sig?: string,
}
const createFormData = (parameters: LastFmSongData) => {
// Creates the body for in the post request
const formData = new URLSearchParams();
for (const key in parameters) {
formData.append(key, String(parameters[key as keyof LastFmSongData]));
}
return formData;
};
const createQueryString = (parameters: Record<string, unknown>, apiSignature: string) => {
// Creates a querystring
const queryData = [];
parameters.api_sig = apiSignature;
for (const key in parameters) {
queryData.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(parameters[key]))}`);
}
return '?' + queryData.join('&');
};
const createApiSig = (parameters: LastFmSongData, secret: string) => {
// This function creates the api signature, see: https://www.last.fm/api/authspec
const keys = Object.keys(parameters);
keys.sort();
let sig = '';
for (const key of keys) {
if (String(key) === 'format') {
continue;
}
sig += `${key}${parameters[key as keyof LastFmSongData]}`;
}
sig += secret;
sig = crypto.createHash('md5').update(sig, 'utf-8').digest('hex');
return sig;
};
const createToken = async ({ api_key: apiKey, api_root: apiRoot, secret }: LastFMOptions) => {
// Creates and stores the auth token
const data = {
method: 'auth.gettoken',
api_key: apiKey,
format: 'json',
};
const apiSigature = createApiSig(data, secret);
const response = await net.fetch(`${apiRoot}${createQueryString(data, apiSigature)}`);
const json = await response.json() as Record<string, string>;
return json?.token;
};
const authenticate = async (config: LastFMOptions) => {
// Asks the user for authentication
await shell.openExternal(`https://www.last.fm/api/auth/?api_key=${config.api_key}&token=${config.token}`);
};
const getAndSetSessionKey = async (config: LastFMOptions) => {
// Get and store the session key
const data = {
api_key: config.api_key,
format: 'json',
method: 'auth.getsession',
token: config.token,
};
const apiSignature = createApiSig(data, config.secret);
const response = await net.fetch(`${config.api_root}${createQueryString(data, apiSignature)}`);
const json = await response.json() as {
error?: string,
session?: {
key: string,
}
};
if (json.error) {
config.token = await createToken(config);
await authenticate(config);
setOptions('last-fm', config);
}
if (json.session) {
config.session_key = json.session.key;
}
setOptions('last-fm', config);
return config;
};
const postSongDataToAPI = async (songInfo: SongInfo, config: LastFMOptions, data: LastFmData) => {
// This sends a post request to the api, and adds the common data
if (!config.session_key) {
await getAndSetSessionKey(config);
}
const postData: LastFmSongData = {
track: songInfo.title,
duration: songInfo.songDuration,
artist: songInfo.artist,
...(songInfo.album ? { album: songInfo.album } : undefined), // Will be undefined if current song is a video
api_key: config.api_key,
sk: config.session_key,
format: 'json',
...data,
};
postData.api_sig = createApiSig(postData, config.secret);
const formData = createFormData(postData);
net.fetch('https://ws.audioscrobbler.com/2.0/', { method: 'POST', body: formData })
.catch(async (error: {
response?: {
data?: {
error: number,
}
}
}) => {
if (error?.response?.data?.error === 9) {
// Session key is invalid, so remove it from the config and reauthenticate
config.session_key = undefined;
config.token = await createToken(config);
await authenticate(config);
setOptions('last-fm', config);
}
});
};
const addScrobble = (songInfo: SongInfo, config: LastFMOptions) => {
// This adds one scrobbled song to last.fm
const data = {
method: 'track.scrobble',
timestamp: Math.trunc((Date.now() - (songInfo.elapsedSeconds ?? 0)) / 1000),
};
postSongDataToAPI(songInfo, config, data);
};
const setNowPlaying = (songInfo: SongInfo, config: LastFMOptions) => {
// 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: NodeJS.Timeout | undefined;
const lastfm = async (_win: BrowserWindow, config: LastFMOptions) => {
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 halfway through, or has passed the 4-minute mark
const scrobbleTime = Math.min(Math.ceil(songInfo.songDuration / 2), 4 * 60);
if (scrobbleTime > (songInfo.elapsedSeconds ?? 0)) {
// Scrobble still needs to happen
const timeToWait = (scrobbleTime - (songInfo.elapsedSeconds ?? 0)) * 1000;
scrobbleTimer = setTimeout(addScrobble, timeToWait, songInfo, config);
}
}
});
};
export default lastfm;

View File

@ -1,82 +0,0 @@
import { BrowserWindow , net } from 'electron';
import registerCallback from '../../providers/song-info';
const secToMilisec = (t?: number) => t ? Math.round(Number(t) * 1e3) : undefined;
const previousStatePaused = null;
type LumiaData = {
origin: string;
eventType: string;
url?: string;
videoId?: string;
playlistId?: string;
cover?: string|null;
cover_url?: string|null;
title?: string;
artists?: string[];
status?: string;
progress?: number;
duration?: number;
album_url?: string|null;
album?: string|null;
views?: number;
isPaused?: boolean;
}
const data: LumiaData = {
origin: 'youtubemusic',
eventType: 'switchSong',
};
const post = (data: LumiaData) => {
const port = 39231;
const headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Access-Control-Allow-Headers': '*',
'Access-Control-Allow-Origin': '*',
};
const url = `http://localhost:${port}/api/media`;
net.fetch(url, { method: 'POST', body: JSON.stringify({ token: 'lsmedia_ytmsI7812', data }), headers })
.catch((error: { code: number, errno: number }) => {
console.log(
`Error: '${
error.code || error.errno
}' - when trying to access lumiastream webserver at port ${port}`
);
});
};
export default (_: BrowserWindow) => {
registerCallback((songInfo) => {
if (!songInfo.title && !songInfo.artist) {
return;
}
if (previousStatePaused === null) {
data.eventType = 'switchSong';
} else if (previousStatePaused !== songInfo.isPaused) {
data.eventType = 'playPause';
}
data.duration = secToMilisec(songInfo.songDuration);
data.progress = secToMilisec(songInfo.elapsedSeconds);
data.url = songInfo.url;
data.videoId = songInfo.videoId;
data.playlistId = songInfo.playlistId;
data.cover = songInfo.imageSrc;
data.cover_url = songInfo.imageSrc;
data.album_url = songInfo.imageSrc;
data.title = songInfo.title;
data.artists = [songInfo.artist];
data.status = songInfo.isPaused ? 'stopped' : 'playing';
data.isPaused = songInfo.isPaused;
data.album = songInfo.album;
data.views = songInfo.views;
post(data);
});
};

View File

@ -1,19 +0,0 @@
import { BrowserWindow, MenuItem } from 'electron';
import { LyricGeniusType, toggleRomanized } from './back';
import { setOptions } from '../../config/plugins';
import { MenuTemplate } from '../../menu';
export default (_: BrowserWindow, options: LyricGeniusType): MenuTemplate => [
{
label: 'Romanized Lyrics',
type: 'checkbox',
checked: options.romanizedLyrics,
click(item: MenuItem) {
options.romanizedLyrics = item.checked;
setOptions('lyrics-genius', options);
toggleRomanized();
},
},
];

View File

@ -1,13 +0,0 @@
import { BrowserWindow } from 'electron';
import style from './style.css';
import { injectCSS } from '../utils';
export function handle(win: BrowserWindow) {
injectCSS(win.webContents, style, () => {
win.webContents.send('navigation-css-ready');
});
}
export default handle;

View File

@ -1,20 +0,0 @@
import { ipcRenderer } from 'electron';
import forwardHTML from './templates/forward.html';
import backHTML from './templates/back.html';
import { ElementFromHtml } from '../utils';
export function run() {
ipcRenderer.on('navigation-css-ready', () => {
const forwardButton = ElementFromHtml(forwardHTML);
const backButton = ElementFromHtml(backHTML);
const menu = document.querySelector('#right-content');
if (menu) {
menu.prepend(backButton, forwardButton);
}
});
}
export default run;

View File

@ -1,33 +0,0 @@
<div
class="style-scope ytmusic-pivot-bar-renderer navigation-item"
onclick="history.back()"
role="tab"
tab-id="FEmusic_back"
>
<div
aria-disabled="false"
class="search-icon style-scope ytmusic-search-box"
role="button"
tabindex="0"
title="Go to previous page"
>
<div
class="tab-icon style-scope paper-icon-button navigation-icon"
id="icon"
>
<svg
class="style-scope iron-icon"
focusable="false"
preserveAspectRatio="xMidYMid meet"
style="pointer-events: none; display: block; width: 100%; height: 100%"
viewBox="0 0 492 492"
>
<g class="style-scope iron-icon">
<path
d="M109.3 265.2l218.9 218.9c5.1 5.1 11.8 7.9 19 7.9s14-2.8 19-7.9l16.1-16.1c10.5-10.5 10.5-27.6 0-38.1L198.6 246.1 382.7 62c5.1-5.1 7.9-11.8 7.9-19 0-7.2-2.8-14-7.9-19L366.5 7.9c-5.1-5.1-11.8-7.9-19-7.9-7.2 0-14 2.8-19 7.9L109.3 227c-5.1 5.1-7.9 11.9-7.8 19.1 0 7.2 2.8 14 7.8 19.1z"
></path>
</g>
</svg>
</div>
</div>
</div>

View File

@ -1,9 +0,0 @@
import { BrowserWindow } from 'electron';
import style from './style.css';
import { injectCSS } from '../utils';
export default (win: BrowserWindow) => {
injectCSS(win.webContents, style);
};

View File

@ -1,37 +0,0 @@
function removeLoginElements() {
const elementsToRemove = [
'.sign-in-link.ytmusic-nav-bar',
'.ytmusic-pivot-bar-renderer[tab-id="FEmusic_liked"]',
];
for (const selector of elementsToRemove) {
const node = document.querySelector(selector);
if (node) {
node.remove();
}
}
// Remove the library button
const libraryIconPath
= 'M16,6v2h-2v5c0,1.1-0.9,2-2,2s-2-0.9-2-2s0.9-2,2-2c0.37,0,0.7,0.11,1,0.28V6H16z M18,20H4V6H3v15h15V20z M21,3H6v15h15V3z M7,4h13v13H7V4z';
const observer = new MutationObserver(() => {
const menuEntries = document.querySelectorAll(
'#items ytmusic-guide-entry-renderer',
);
menuEntries.forEach((item) => {
const icon = item.querySelector('path');
if (icon) {
observer.disconnect();
if (icon.getAttribute('d') === libraryIconPath) {
item.remove();
}
}
});
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
}
export default removeLoginElements;

View File

@ -1,5 +0,0 @@
import { PluginConfig } from '../../config/dynamic';
const config = new PluginConfig('notifications');
export default config;

View File

@ -1,257 +0,0 @@
import path from 'node:path';
import { app, BrowserWindow, ipcMain, Notification } from 'electron';
import { icons, notificationImage, saveTempIcon, secondsToMinutes, ToastStyles } from './utils';
import config from './config';
import getSongControls from '../../providers/song-controls';
import registerCallback, { SongInfo } from '../../providers/song-info';
import { changeProtocolHandler } from '../../providers/protocol-handler';
import { setTrayOnClick, setTrayOnDoubleClick } from '../../tray';
import { getMediaIconLocation } from '../utils';
let songControls: ReturnType<typeof getSongControls>;
let savedNotification: Notification | undefined;
export default (win: BrowserWindow) => {
songControls = getSongControls(win);
let currentSeconds = 0;
ipcMain.on('apiLoaded', () => win.webContents.send('setupTimeChangedListener'));
ipcMain.on('timeChanged', (_, t: number) => currentSeconds = t);
if (app.isPackaged) {
saveTempIcon();
}
let savedSongInfo: SongInfo;
let lastUrl: string | undefined;
// Register songInfoCallback
registerCallback((songInfo) => {
if (!songInfo.artist && !songInfo.title) {
return;
}
savedSongInfo = { ...songInfo };
if (!songInfo.isPaused
&& (songInfo.url !== lastUrl || config.get('unpauseNotification'))
) {
lastUrl = songInfo.url;
sendNotification(songInfo);
}
});
if (config.get('trayControls')) {
setTrayOnClick(() => {
if (savedNotification) {
savedNotification.close();
savedNotification = undefined;
} else if (savedSongInfo) {
sendNotification({
...savedSongInfo,
elapsedSeconds: currentSeconds,
});
}
});
setTrayOnDoubleClick(() => {
if (win.isVisible()) {
win.hide();
} else {
win.show();
}
});
}
app.once('before-quit', () => {
savedNotification?.close();
});
changeProtocolHandler(
(cmd) => {
if (Object.keys(songControls).includes(cmd)) {
songControls[cmd as keyof typeof songControls]();
if (config.get('refreshOnPlayPause') && (
cmd === 'pause'
|| (cmd === 'play' && !config.get('unpauseNotification'))
)
) {
setImmediate(() =>
sendNotification({
...savedSongInfo,
isPaused: cmd === 'pause',
elapsedSeconds: currentSeconds,
}),
);
}
}
},
);
};
function sendNotification(songInfo: SongInfo) {
const iconSrc = notificationImage(songInfo);
savedNotification?.close();
let icon: string;
if (typeof iconSrc === 'object') {
icon = iconSrc.toDataURL();
} else {
icon = iconSrc;
}
savedNotification = new Notification({
title: songInfo.title || 'Playing',
body: songInfo.artist,
icon: iconSrc,
silent: true,
// https://learn.microsoft.com/en-us/uwp/schemas/tiles/toastschema/schema-root
// https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/toast-schema
// https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/adaptive-interactive-toasts?tabs=xml
// https://learn.microsoft.com/en-us/uwp/api/windows.ui.notifications.toasttemplatetype
toastXml: getXml(songInfo, icon),
});
savedNotification.on('close', () => {
savedNotification = undefined;
});
savedNotification.show();
}
const getXml = (songInfo: SongInfo, iconSrc: string) => {
switch (config.get('toastStyle')) {
default:
case ToastStyles.logo:
case ToastStyles.legacy: {
return xmlLogo(songInfo, iconSrc);
}
case ToastStyles.banner_top_custom: {
return xmlBannerTopCustom(songInfo, iconSrc);
}
case ToastStyles.hero: {
return xmlHero(songInfo, iconSrc);
}
case ToastStyles.banner_bottom: {
return xmlBannerBottom(songInfo, iconSrc);
}
case ToastStyles.banner_centered_bottom: {
return xmlBannerCenteredBottom(songInfo, iconSrc);
}
case ToastStyles.banner_centered_top: {
return xmlBannerCenteredTop(songInfo, iconSrc);
}
}
};
const display = (kind: keyof typeof icons) => {
if (config.get('toastStyle') === ToastStyles.legacy) {
return `content="${icons[kind]}"`;
}
return `\
content="${config.get('hideButtonText') ? '' : kind.charAt(0).toUpperCase() + kind.slice(1)}"\
imageUri="file:///${path.resolve(getMediaIconLocation(), `${kind}.png`)}"
`;
};
const getButton = (kind: keyof typeof icons) =>
`<action ${display(kind)} activationType="protocol" arguments="youtubemusic://${kind}"/>`;
const getButtons = (isPaused: boolean) => `\
<actions>
${getButton('previous')}
${isPaused ? getButton('play') : getButton('pause')}
${getButton('next')}
</actions>\
`;
const toast = (content: string, isPaused: boolean) => `\
<toast>
<audio silent="true" />
<visual>
<binding template="ToastGeneric">
${content}
</binding>
</visual>
${getButtons(isPaused)}
</toast>`;
const xmlImage = ({ title, artist, isPaused }: SongInfo, imgSrc: string, placement: string) => toast(`\
<image id="1" src="${imgSrc}" name="Image" ${placement}/>
<text id="1">${title}</text>
<text id="2">${artist}</text>\
`, isPaused ?? false);
const xmlLogo = (songInfo: SongInfo, imgSrc: string) => xmlImage(songInfo, imgSrc, 'placement="appLogoOverride"');
const xmlHero = (songInfo: SongInfo, imgSrc: string) => xmlImage(songInfo, imgSrc, 'placement="hero"');
const xmlBannerBottom = (songInfo: SongInfo, imgSrc: string) => xmlImage(songInfo, imgSrc, '');
const xmlBannerTopCustom = (songInfo: SongInfo, imgSrc: string) => toast(`\
<image id="1" src="${imgSrc}" name="Image" />
<text></text>
<group>
<subgroup>
<text hint-style="body">${songInfo.title}</text>
<text hint-style="captionSubtle">${songInfo.artist}</text>
</subgroup>
${xmlMoreData(songInfo)}
</group>\
`, songInfo.isPaused ?? false);
const xmlMoreData = ({ album, elapsedSeconds, songDuration }: SongInfo) => `\
<subgroup hint-textStacking="bottom">
${album
? `<text hint-style="captionSubtle" hint-wrap="true" hint-align="right">${album}</text>` : ''}
<text hint-style="captionSubtle" hint-wrap="true" hint-align="right">${secondsToMinutes(elapsedSeconds ?? 0)} / ${secondsToMinutes(songDuration)}</text>
</subgroup>\
`;
const xmlBannerCenteredBottom = ({ title, artist, isPaused }: SongInfo, imgSrc: string) => toast(`\
<text></text>
<group>
<subgroup hint-weight="1" hint-textStacking="center">
<text hint-align="center" hint-style="${titleFontPicker(title)}">${title}</text>
<text hint-align="center" hint-style="SubtitleSubtle">${artist}</text>
</subgroup>
</group>
<image id="1" src="${imgSrc}" name="Image" hint-removeMargin="true" />\
`, isPaused ?? false);
const xmlBannerCenteredTop = ({ title, artist, isPaused }: SongInfo, imgSrc: string) => toast(`\
<image id="1" src="${imgSrc}" name="Image" />
<text></text>
<group>
<subgroup hint-weight="1" hint-textStacking="center">
<text hint-align="center" hint-style="${titleFontPicker(title)}">${title}</text>
<text hint-align="center" hint-style="SubtitleSubtle">${artist}</text>
</subgroup>
</group>\
`, isPaused ?? false);
const titleFontPicker = (title: string) => {
if (title.length <= 13) {
return 'Header';
}
if (title.length <= 22) {
return 'Subheader';
}
if (title.length <= 26) {
return 'Title';
}
return 'Subtitle';
};

View File

@ -1,93 +0,0 @@
import is from 'electron-is';
import { BrowserWindow, MenuItem } from 'electron';
import { snakeToCamel, ToastStyles, urgencyLevels } from './utils';
import config from './config';
import { MenuTemplate } from '../../menu';
import type { ConfigType } from '../../config/dynamic';
const getMenu = (options: ConfigType<'notifications'>): MenuTemplate => {
if (is.linux()) {
return [
{
label: 'Notification Priority',
submenu: urgencyLevels.map((level) => ({
label: level.name,
type: 'radio',
checked: options.urgency === level.value,
click: () => config.set('urgency', level.value),
})),
}
];
} else if (is.windows()) {
return [
{
label: 'Interactive Notifications',
type: 'checkbox',
checked: options.interactive,
// Doesn't update until restart
click: (item: MenuItem) => config.setAndMaybeRestart('interactive', item.checked),
},
{
// Submenu with settings for interactive notifications (name shouldn't be too long)
label: 'Interactive Settings',
submenu: [
{
label: 'Open/Close on tray click',
type: 'checkbox',
checked: options.trayControls,
click: (item: MenuItem) => config.set('trayControls', item.checked),
},
{
label: 'Hide Button Text',
type: 'checkbox',
checked: options.hideButtonText,
click: (item: MenuItem) => config.set('hideButtonText', item.checked),
},
{
label: 'Refresh on Play/Pause',
type: 'checkbox',
checked: options.refreshOnPlayPause,
click: (item: MenuItem) => config.set('refreshOnPlayPause', item.checked),
},
],
},
{
label: 'Style',
submenu: getToastStyleMenuItems(options),
},
];
} else {
return [];
}
};
export default (_win: BrowserWindow, options: ConfigType<'notifications'>): MenuTemplate => [
...getMenu(options),
{
label: 'Show notification on unpause',
type: 'checkbox',
checked: options.unpauseNotification,
click: (item: MenuItem) => config.set('unpauseNotification', item.checked),
},
];
export function getToastStyleMenuItems(options: ConfigType<'notifications'>) {
const array = Array.from({ length: Object.keys(ToastStyles).length });
// ToastStyles index starts from 1
for (const [name, index] of Object.entries(ToastStyles)) {
array[index - 1] = {
label: snakeToCamel(name),
type: 'radio',
checked: options.toastStyle === index,
click: () => config.set('toastStyle', index),
} satisfies Electron.MenuItemConstructorOptions;
}
return array as Electron.MenuItemConstructorOptions[];
}

View File

@ -1,108 +0,0 @@
import path from 'node:path';
import fs from 'node:fs';
import { app, NativeImage } from 'electron';
import config from './config';
import { cache } from '../../providers/decorators';
import { SongInfo } from '../../providers/song-info';
import { getAssetsDirectoryLocation } from '../utils';
const icon = 'assets/youtube-music.png';
const userData = app.getPath('userData');
const temporaryIcon = path.join(userData, 'tempIcon.png');
const temporaryBanner = path.join(userData, 'tempBanner.png');
export const ToastStyles = {
logo: 1,
banner_centered_top: 2,
hero: 3,
banner_top_custom: 4,
banner_centered_bottom: 5,
banner_bottom: 6,
legacy: 7,
};
export const icons = {
play: '\u{1405}', // ᐅ
pause: '\u{2016}', // ‖
next: '\u{1433}', //
previous: '\u{1438}', //
};
export const urgencyLevels = [
{ name: 'Low', value: 'low' },
{ name: 'Normal', value: 'normal' },
{ name: 'High', value: 'critical' },
];
const nativeImageToLogo = cache((nativeImage: NativeImage) => {
const temporaryImage = nativeImage.resize({ height: 256 });
const margin = Math.max(temporaryImage.getSize().width - 256, 0);
return temporaryImage.crop({
x: Math.round(margin / 2),
y: 0,
width: 256,
height: 256,
});
});
export const notificationImage = (songInfo: SongInfo) => {
if (!songInfo.image) {
return icon;
}
if (!config.get('interactive')) {
return nativeImageToLogo(songInfo.image);
}
switch (config.get('toastStyle')) {
case ToastStyles.logo:
case ToastStyles.legacy: {
return saveImage(nativeImageToLogo(songInfo.image), temporaryIcon);
}
default: {
return saveImage(songInfo.image, temporaryBanner);
}
}
};
export const saveImage = cache((img: NativeImage, savePath: string) => {
try {
fs.writeFileSync(savePath, img.toPNG());
} catch (error: unknown) {
console.log(`Error writing song icon to disk:\n${String(error)}`);
return icon;
}
return savePath;
});
export const saveTempIcon = () => {
for (const kind of Object.keys(icons)) {
const destinationPath = path.join(userData, 'icons', `${kind}.png`);
if (fs.existsSync(destinationPath)) {
continue;
}
const iconPath = path.resolve(path.resolve(getAssetsDirectoryLocation(), 'media-icons-black'), `${kind}.png`);
fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
fs.copyFile(iconPath, destinationPath, () => {});
}
};
export const snakeToCamel = (string_: string) => string_.replaceAll(/([-_][a-z]|^[a-z])/g, (group) =>
group.toUpperCase()
.replace('-', ' ')
.replace('_', ' '),
);
export const secondsToMinutes = (seconds: number) => {
const minutes = Math.floor(seconds / 60);
const secondsLeft = seconds % 60;
return `${minutes}:${secondsLeft < 10 ? '0' : ''}${secondsLeft}`;
};

View File

@ -1,111 +0,0 @@
import { app, BrowserWindow, ipcMain } from 'electron';
import style from './style.css';
import { injectCSS } from '../utils';
import { setOptions as setPluginOptions } from '../../config/plugins';
import type { ConfigType } from '../../config/dynamic';
let isInPiP = false;
let originalPosition: number[];
let originalSize: number[];
let originalFullScreen: boolean;
let originalMaximized: boolean;
let win: BrowserWindow;
type PiPOptions = ConfigType<'picture-in-picture'>;
let options: Partial<PiPOptions>;
const pipPosition = () => (options.savePosition && options['pip-position']) || [10, 10];
const pipSize = () => (options.saveSize && options['pip-size']) || [450, 275];
const setLocalOptions = (_options: Partial<PiPOptions>) => {
options = { ...options, ..._options };
setPluginOptions('picture-in-picture', _options);
};
const togglePiP = () => {
isInPiP = !isInPiP;
setLocalOptions({ isInPiP });
if (isInPiP) {
originalFullScreen = win.isFullScreen();
if (originalFullScreen) {
win.setFullScreen(false);
}
originalMaximized = win.isMaximized();
if (originalMaximized) {
win.unmaximize();
}
originalPosition = win.getPosition();
originalSize = win.getSize();
win.webContents.on('before-input-event', blockShortcutsInPiP);
win.setMaximizable(false);
win.setFullScreenable(false);
win.webContents.send('pip-toggle', true);
app.dock?.hide();
win.setVisibleOnAllWorkspaces(true, {
visibleOnFullScreen: true,
});
app.dock?.show();
if (options.alwaysOnTop) {
win.setAlwaysOnTop(true, 'screen-saver', 1);
}
} else {
win.webContents.removeListener('before-input-event', blockShortcutsInPiP);
win.setMaximizable(true);
win.setFullScreenable(true);
win.webContents.send('pip-toggle', false);
win.setVisibleOnAllWorkspaces(false);
win.setAlwaysOnTop(false);
if (originalFullScreen) {
win.setFullScreen(true);
}
if (originalMaximized) {
win.maximize();
}
}
const [x, y] = isInPiP ? pipPosition() : originalPosition;
const [w, h] = isInPiP ? pipSize() : originalSize;
win.setPosition(x, y);
win.setSize(w, h);
win.setWindowButtonVisibility?.(!isInPiP);
};
const blockShortcutsInPiP = (event: Electron.Event, input: Electron.Input) => {
const key = input.key.toLowerCase();
if (key === 'f') {
event.preventDefault();
} else if (key === 'escape') {
togglePiP();
event.preventDefault();
}
};
export default (_win: BrowserWindow, _options: PiPOptions) => {
options ??= _options;
win ??= _win;
setLocalOptions({ isInPiP });
injectCSS(win.webContents, style);
ipcMain.on('picture-in-picture', () => {
togglePiP();
});
};
export const setOptions = setLocalOptions;

View File

@ -1,75 +0,0 @@
import prompt from 'custom-electron-prompt';
import { BrowserWindow } from 'electron';
import { setOptions } from './back';
import promptOptions from '../../providers/prompt-options';
import { MenuTemplate } from '../../menu';
import type { ConfigType } from '../../config/dynamic';
export default (win: BrowserWindow, options: ConfigType<'picture-in-picture'>): MenuTemplate => [
{
label: 'Always on top',
type: 'checkbox',
checked: options.alwaysOnTop,
click(item) {
setOptions({ alwaysOnTop: item.checked });
win.setAlwaysOnTop(item.checked);
},
},
{
label: 'Save window position',
type: 'checkbox',
checked: options.savePosition,
click(item) {
setOptions({ savePosition: item.checked });
},
},
{
label: 'Save window size',
type: 'checkbox',
checked: options.saveSize,
click(item) {
setOptions({ saveSize: item.checked });
},
},
{
label: 'Hotkey',
type: 'checkbox',
checked: !!options.hotkey,
async click(item) {
const output = await prompt({
title: 'Picture in Picture Hotkey',
label: 'Choose a hotkey for toggling Picture in Picture',
type: 'keybind',
keybindOptions: [{
value: 'hotkey',
label: 'Hotkey',
default: options.hotkey,
}],
...promptOptions(),
}, win);
if (output) {
const { value, accelerator } = output[0];
setOptions({ [value]: accelerator });
item.checked = !!accelerator;
} else {
// Reset checkbox if prompt was canceled
item.checked = !item.checked;
}
},
},
{
label: 'Use native PiP',
type: 'checkbox',
checked: options.useNativePiP,
click(item) {
setOptions({ useNativePiP: item.checked });
},
},
];

View File

@ -1,117 +0,0 @@
import sliderHTML from './templates/slider.html';
import { getSongMenu } from '../../providers/dom-elements';
import { ElementFromHtml } from '../utils';
import { singleton } from '../../providers/decorators';
function $<E extends Element = Element>(selector: string) {
return document.querySelector<E>(selector);
}
const slider = ElementFromHtml(sliderHTML);
const roundToTwo = (n: number) => Math.round(n * 1e2) / 1e2;
const MIN_PLAYBACK_SPEED = 0.07;
const MAX_PLAYBACK_SPEED = 16;
let playbackSpeed = 1;
const updatePlayBackSpeed = () => {
const videoElement = $<HTMLVideoElement>('video');
if (videoElement) {
videoElement.playbackRate = playbackSpeed;
}
const playbackSpeedElement = $('#playback-speed-value');
if (playbackSpeedElement) {
playbackSpeedElement.innerHTML = String(playbackSpeed);
}
};
let menu: Element | null = null;
const setupSliderListener = singleton(() => {
$('#playback-speed-slider')?.addEventListener('immediate-value-changed', (e) => {
playbackSpeed = (e as CustomEvent<{ value: number; }>).detail.value || MIN_PLAYBACK_SPEED;
if (isNaN(playbackSpeed)) {
playbackSpeed = 1;
}
updatePlayBackSpeed();
});
});
const observePopupContainer = () => {
const observer = new MutationObserver(() => {
if (!menu) {
menu = getSongMenu();
}
if (
menu &&
(menu.parentElement as HTMLElement & { eventSink_: Element | null })
?.eventSink_
?.matches('ytmusic-menu-renderer.ytmusic-player-bar')&& !menu.contains(slider)
) {
menu.prepend(slider);
setupSliderListener();
}
});
const popupContainer = $('ytmusic-popup-container');
if (popupContainer) {
observer.observe(popupContainer, {
childList: true,
subtree: true,
});
}
};
const observeVideo = () => {
const video = $<HTMLVideoElement>('video');
if (video) {
video.addEventListener('ratechange', forcePlaybackRate);
video.addEventListener('srcChanged', forcePlaybackRate);
}
};
const setupWheelListener = () => {
slider.addEventListener('wheel', (e) => {
e.preventDefault();
if (isNaN(playbackSpeed)) {
playbackSpeed = 1;
}
// E.deltaY < 0 means wheel-up
playbackSpeed = roundToTwo(e.deltaY < 0
? Math.min(playbackSpeed + 0.01, MAX_PLAYBACK_SPEED)
: Math.max(playbackSpeed - 0.01, MIN_PLAYBACK_SPEED),
);
updatePlayBackSpeed();
// Update slider position
const playbackSpeedSilder = $<HTMLElement & { value: number }>('#playback-speed-slider');
if (playbackSpeedSilder) {
playbackSpeedSilder.value = playbackSpeed;
}
});
};
function forcePlaybackRate(e: Event) {
if (e.target instanceof HTMLVideoElement) {
const videoElement = e.target;
if (videoElement.playbackRate !== playbackSpeed) {
videoElement.playbackRate = playbackSpeed;
}
}
}
export default () => {
document.addEventListener('apiLoaded', () => {
observePopupContainer();
observeVideo();
setupWheelListener();
}, { once: true, passive: true });
};

View File

@ -1,28 +0,0 @@
import { globalShortcut, BrowserWindow } from 'electron';
import volumeHudStyle from './volume-hud.css';
import { injectCSS } from '../utils';
import type { ConfigType } from '../../config/dynamic';
/*
This is used to determine if plugin is actually active
(not if it's only enabled in options)
*/
let isEnabled = false;
export const enabled = () => isEnabled;
export default (win: BrowserWindow, options: ConfigType<'precise-volume'>) => {
isEnabled = true;
injectCSS(win.webContents, volumeHudStyle);
if (options.globalShortcuts?.volumeUp) {
globalShortcut.register((options.globalShortcuts.volumeUp), () => win.webContents.send('changeVolume', true));
}
if (options.globalShortcuts?.volumeDown) {
globalShortcut.register((options.globalShortcuts.volumeDown), () => win.webContents.send('changeVolume', false));
}
};

View File

@ -1,270 +0,0 @@
import { ipcRenderer } from 'electron';
import { setOptions, setMenuOptions, isEnabled } from '../../config/plugins';
import { debounce } from '../../providers/decorators';
import { YoutubePlayer } from '../../types/youtube-player';
import type { ConfigType } from '../../config/dynamic';
function $<E extends Element = Element>(selector: string) {
return document.querySelector<E>(selector);
}
let api: YoutubePlayer;
let options: ConfigType<'precise-volume'>;
export default (_options: ConfigType<'precise-volume'>) => {
options = _options;
document.addEventListener('apiLoaded', (e) => {
api = e.detail;
ipcRenderer.on('changeVolume', (_, toIncrease: boolean) => changeVolume(toIncrease));
ipcRenderer.on('setVolume', (_, value: number) => setVolume(value));
firstRun();
}, { once: true, passive: true });
};
// Without this function it would rewrite config 20 time when volume change by 20
const writeOptions = debounce(() => {
setOptions('precise-volume', options);
}, 1000);
export const moveVolumeHud = debounce((showVideo: boolean) => {
const volumeHud = $<HTMLElement>('#volumeHud');
if (!volumeHud) {
return;
}
volumeHud.style.top = showVideo
? `${($('ytmusic-player')!.clientHeight - $('video')!.clientHeight) / 2}px`
: '0';
}, 250);
const hideVolumeHud = debounce((volumeHud: HTMLElement) => {
volumeHud.style.opacity = '0';
}, 2000);
const hideVolumeSlider = debounce((slider: HTMLElement) => {
slider.classList.remove('on-hover');
}, 2500);
/** Restore saved volume and setup tooltip */
function firstRun() {
if (typeof options.savedVolume === 'number') {
// Set saved volume as tooltip
setTooltip(options.savedVolume);
if (api.getVolume() !== options.savedVolume) {
setVolume(options.savedVolume);
}
}
setupPlaybar();
setupLocalArrowShortcuts();
// Workaround: computedStyleMap().get(string) returns CSSKeywordValue instead of CSSStyleValue
const noVid = ($('#main-panel')?.computedStyleMap().get('display') as CSSKeywordValue)?.value === 'none';
injectVolumeHud(noVid);
if (!noVid) {
setupVideoPlayerOnwheel();
if (!isEnabled('video-toggle')) {
// Video-toggle handles hud positioning on its own
const videoMode = () => api.getPlayerResponse().videoDetails?.musicVideoType !== 'MUSIC_VIDEO_TYPE_ATV';
$('video')?.addEventListener('srcChanged', () => moveVolumeHud(videoMode()));
}
}
// Change options from renderer to keep sync
ipcRenderer.on('setOptions', (_event, newOptions = {}) => {
Object.assign(options, newOptions);
setMenuOptions('precise-volume', options);
});
}
function injectVolumeHud(noVid: boolean) {
if (noVid) {
const position = 'top: 18px; right: 60px;';
const mainStyle = 'font-size: xx-large;';
$('.center-content.ytmusic-nav-bar')?.insertAdjacentHTML(
'beforeend',
`<span id="volumeHud" style="${position + mainStyle}"></span>`,
);
} else {
const position = 'top: 10px; left: 10px;';
const mainStyle = 'font-size: xxx-large; webkit-text-stroke: 1px black; font-weight: 600;';
$('#song-video')?.insertAdjacentHTML(
'afterend',
`<span id="volumeHud" style="${position + mainStyle}"></span>`,
);
}
}
function showVolumeHud(volume: number) {
const volumeHud = $<HTMLElement>('#volumeHud');
if (!volumeHud) {
return;
}
volumeHud.textContent = `${volume}%`;
volumeHud.style.opacity = '1';
hideVolumeHud(volumeHud);
}
/** Add onwheel event to video player */
function setupVideoPlayerOnwheel() {
const panel = $<HTMLElement>('#main-panel');
if (!panel) return;
panel.addEventListener('wheel', (event) => {
event.preventDefault();
// Event.deltaY < 0 means wheel-up
changeVolume(event.deltaY < 0);
});
}
function saveVolume(volume: number) {
options.savedVolume = volume;
writeOptions();
}
/** Add onwheel event to play bar and also track if play bar is hovered */
function setupPlaybar() {
const playerbar = $<HTMLElement>('ytmusic-player-bar');
if (!playerbar) return;
playerbar.addEventListener('wheel', (event) => {
event.preventDefault();
// Event.deltaY < 0 means wheel-up
changeVolume(event.deltaY < 0);
});
// Keep track of mouse position for showVolumeSlider()
playerbar.addEventListener('mouseenter', () => {
playerbar.classList.add('on-hover');
});
playerbar.addEventListener('mouseleave', () => {
playerbar.classList.remove('on-hover');
});
setupSliderObserver();
}
/** Save volume + Update the volume tooltip when volume-slider is manually changed */
function setupSliderObserver() {
const sliderObserver = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.target instanceof HTMLInputElement) {
// This checks that volume-slider was manually set
const target = mutation.target;
const targetValueNumeric = Number(target.value);
if (mutation.oldValue !== target.value
&& (typeof options.savedVolume !== 'number' || Math.abs(options.savedVolume - targetValueNumeric) > 4)) {
// Diff>4 means it was manually set
setTooltip(targetValueNumeric);
saveVolume(targetValueNumeric);
}
}
}
});
const slider = $('#volume-slider');
if (!slider) return;
// Observing only changes in 'value' of volume-slider
sliderObserver.observe(slider, {
attributeFilter: ['value'],
attributeOldValue: true,
});
}
function setVolume(value: number) {
api.setVolume(value);
// Save the new volume
saveVolume(value);
// Change slider position (important)
updateVolumeSlider();
// Change tooltips to new value
setTooltip(value);
// Show volume slider
showVolumeSlider();
// Show volume HUD
showVolumeHud(value);
}
/** If (toIncrease = false) then volume decrease */
function changeVolume(toIncrease: boolean) {
// Apply volume change if valid
const steps = Number(options.steps || 1);
setVolume(toIncrease
? Math.min(api.getVolume() + steps, 100)
: Math.max(api.getVolume() - steps, 0));
}
function updateVolumeSlider() {
const savedVolume = options.savedVolume ?? 0;
// Slider value automatically rounds to multiples of 5
for (const slider of ['#volume-slider', '#expand-volume-slider']) {
const silderElement = $<HTMLInputElement>(slider);
if (silderElement) {
silderElement.value = String(savedVolume > 0 && savedVolume < 5 ? 5 : savedVolume);
}
}
}
function showVolumeSlider() {
const slider = $<HTMLElement>('#volume-slider');
if (!slider) return;
// This class display the volume slider if not in minimized mode
slider.classList.add('on-hover');
hideVolumeSlider(slider);
}
// 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: number) {
for (const target of tooltipTargets) {
const tooltipTargetElement = $<HTMLElement>(target);
if (tooltipTargetElement) {
tooltipTargetElement.title = `${volume}%`;
}
}
}
function setupLocalArrowShortcuts() {
if (options.arrowsShortcut) {
window.addEventListener('keydown', (event) => {
if ($<HTMLElement & { opened: boolean }>('ytmusic-search-box')?.opened) {
return;
}
switch (event.code) {
case 'ArrowUp': {
event.preventDefault();
changeVolume(true);
break;
}
case 'ArrowDown': {
event.preventDefault();
changeVolume(false);
break;
}
}
});
}
}

View File

@ -1,94 +0,0 @@
import prompt, { KeybindOptions } from 'custom-electron-prompt';
import { BrowserWindow, MenuItem } from 'electron';
import { enabled } from './back';
import { setMenuOptions } from '../../config/plugins';
import promptOptions from '../../providers/prompt-options';
import { MenuTemplate } from '../../menu';
import type { ConfigType } from '../../config/dynamic';
function changeOptions(changedOptions: Partial<ConfigType<'precise-volume'>>, options: ConfigType<'precise-volume'>, win: BrowserWindow) {
for (const option in changedOptions) {
// HACK: Weird TypeScript error
(options as Record<string, unknown>)[option] = (changedOptions as Record<string, unknown>)[option];
}
// Dynamically change setting if plugin is enabled
if (enabled()) {
win.webContents.send('setOptions', changedOptions);
} else { // Fallback to usual method if disabled
setMenuOptions('precise-volume', options);
}
}
export default (win: BrowserWindow, options: ConfigType<'precise-volume'>): MenuTemplate => [
{
label: 'Local Arrowkeys Controls',
type: 'checkbox',
checked: Boolean(options.arrowsShortcut),
click(item) {
changeOptions({ arrowsShortcut: item.checked }, options, win);
},
},
{
label: 'Global Hotkeys',
type: 'checkbox',
checked: Boolean(options.globalShortcuts?.volumeUp ?? options.globalShortcuts?.volumeDown),
click: (item) => promptGlobalShortcuts(win, options, item),
},
{
label: 'Set Custom Volume Steps',
click: () => promptVolumeSteps(win, options),
},
];
// Helper function for globalShortcuts prompt
const kb = (label_: string, value_: string, default_: string): KeybindOptions => ({ 'value': value_, 'label': label_, 'default': default_ || undefined });
async function promptVolumeSteps(win: BrowserWindow, options: ConfigType<'precise-volume'>) {
const output = await prompt({
title: 'Volume Steps',
label: 'Choose Volume Increase/Decrease Steps',
value: options.steps || 1,
type: 'counter',
counterOptions: { minimum: 0, maximum: 100, multiFire: true },
width: 380,
...promptOptions(),
}, win);
if (output || output === 0) { // 0 is somewhat valid
changeOptions({ steps: output }, options, win);
}
}
async function promptGlobalShortcuts(win: BrowserWindow, options: ConfigType<'precise-volume'>, item: MenuItem) {
const output = await prompt({
title: 'Global Volume Keybinds',
label: 'Choose Global Volume Keybinds:',
type: 'keybind',
keybindOptions: [
kb('Increase Volume', 'volumeUp', options.globalShortcuts?.volumeUp),
kb('Decrease Volume', 'volumeDown', options.globalShortcuts?.volumeDown),
],
...promptOptions(),
}, win);
if (output) {
const newGlobalShortcuts: {
volumeUp: string;
volumeDown: string;
} = { volumeUp: '', volumeDown: '' };
for (const { value, accelerator } of output) {
newGlobalShortcuts[value as keyof typeof newGlobalShortcuts] = accelerator;
}
changeOptions({ globalShortcuts: newGlobalShortcuts }, options, win);
item.checked = Boolean(options.globalShortcuts.volumeUp) || Boolean(options.globalShortcuts.volumeDown);
} else {
// Reset checkbox if prompt was canceled
item.checked = !item.checked;
}
}

View File

@ -1,41 +0,0 @@
/* what */
/* eslint-disable @typescript-eslint/ban-ts-comment */
import is from 'electron-is';
const ignored = {
id: ['volume-slider', 'expand-volume-slider'],
types: ['mousewheel', 'keydown', 'keyup'],
};
function overrideAddEventListener() {
// YO WHAT ARE YOU DOING NOW?!?!
// Save native addEventListener
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/unbound-method
Element.prototype._addEventListener = Element.prototype.addEventListener;
// Override addEventListener to Ignore specific events in volume-slider
Element.prototype.addEventListener = function (type: string, listener: (event: Event) => void, useCapture = false) {
if (!(
ignored.id.includes(this.id)
&& ignored.types.includes(type)
)) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
(this as any)._addEventListener(type, listener, useCapture);
} else if (is.dev()) {
console.log(`Ignoring event: "${this.id}.${type}()"`);
}
};
}
export default () => {
overrideAddEventListener();
// Restore original function after finished loading to avoid keeping Element.prototype altered
window.addEventListener('load', () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-member-access
Element.prototype.addEventListener = (Element.prototype as any)._addEventListener;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-member-access
(Element.prototype as any)._addEventListener = undefined;
}, { once: true });
};

View File

@ -1,13 +0,0 @@
import { ipcMain, dialog } from 'electron';
export default () => {
ipcMain.handle('qualityChanger', async (_, qualityLabels: string[], currentIndex: number) => await dialog.showMessageBox({
type: 'question',
buttons: qualityLabels,
defaultId: currentIndex,
title: 'Choose Video Quality',
message: 'Choose Video Quality:',
detail: `Current Quality: ${qualityLabels[currentIndex]}`,
cancelId: -1,
}));
};

View File

@ -1,40 +0,0 @@
import { ipcRenderer } from 'electron';
import qualitySettingsTemplate from './templates/qualitySettingsTemplate.html';
import { ElementFromHtml } from '../utils';
import { YoutubePlayer } from '../../types/youtube-player';
function $(selector: string): HTMLElement | null {
return document.querySelector(selector);
}
const qualitySettingsButton = ElementFromHtml(qualitySettingsTemplate);
function setup(event: CustomEvent<YoutubePlayer>) {
const api = event.detail;
$('.top-row-buttons.ytmusic-player')?.prepend(qualitySettingsButton);
qualitySettingsButton.addEventListener('click', function chooseQuality() {
setTimeout(() => $('#player')?.click());
const qualityLevels = api.getAvailableQualityLevels();
const currentIndex = qualityLevels.indexOf(api.getPlaybackQuality());
ipcRenderer.invoke('qualityChanger', api.getAvailableQualityLabels(), currentIndex).then((promise: { response: number }) => {
if (promise.response === -1) {
return;
}
const newQuality = qualityLevels[promise.response];
api.setPlaybackQualityRange(newQuality);
api.setPlaybackQuality(newQuality);
});
});
}
export default () => {
document.addEventListener('apiLoaded', setup, { once: true, passive: true });
};

View File

@ -1,69 +0,0 @@
import { BrowserWindow, globalShortcut } from 'electron';
import is from 'electron-is';
import electronLocalshortcut from 'electron-localshortcut';
import registerMPRIS from './mpris';
import getSongControls from '../../providers/song-controls';
import type { ConfigType } from '../../config/dynamic';
function _registerGlobalShortcut(webContents: Electron.WebContents, shortcut: string, action: (webContents: Electron.WebContents) => void) {
globalShortcut.register(shortcut, () => {
action(webContents);
});
}
function _registerLocalShortcut(win: BrowserWindow, shortcut: string, action: (webContents: Electron.WebContents) => void) {
electronLocalshortcut.register(win, shortcut, () => {
action(win.webContents);
});
}
function registerShortcuts(win: BrowserWindow, options: ConfigType<'shortcuts'>) {
const songControls = getSongControls(win);
const { playPause, next, previous, search } = songControls;
if (options.overrideMediaKeys) {
_registerGlobalShortcut(win.webContents, 'MediaPlayPause', playPause);
_registerGlobalShortcut(win.webContents, 'MediaNextTrack', next);
_registerGlobalShortcut(win.webContents, 'MediaPreviousTrack', previous);
}
_registerLocalShortcut(win, 'CommandOrControl+F', search);
_registerLocalShortcut(win, 'CommandOrControl+L', search);
if (is.linux()) {
registerMPRIS(win);
}
const { global, local } = options;
const shortcutOptions = { global, local };
for (const optionType in shortcutOptions) {
registerAllShortcuts(shortcutOptions[optionType as 'global' | 'local'], optionType);
}
function registerAllShortcuts(container: Record<string, string>, type: string) {
for (const action in container) {
if (!container[action]) {
continue; // Action accelerator is empty
}
console.debug(`Registering ${type} shortcut`, container[action], ':', action);
const actionCallback: () => void = songControls[action as keyof typeof songControls];
if (typeof actionCallback !== 'function') {
console.warn('Invalid action', action);
continue;
}
if (type === 'global') {
_registerGlobalShortcut(win.webContents, container[action], actionCallback);
} else { // Type === "local"
_registerLocalShortcut(win, local[action], actionCallback);
}
}
}
}
export default registerShortcuts;

View File

@ -1,67 +0,0 @@
import prompt, { KeybindOptions } from 'custom-electron-prompt';
import { BrowserWindow } from 'electron';
import { setMenuOptions } from '../../config/plugins';
import promptOptions from '../../providers/prompt-options';
import { MenuTemplate } from '../../menu';
import type { ConfigType } from '../../config/dynamic';
export default (win: BrowserWindow, options: ConfigType<'shortcuts'>): MenuTemplate => [
{
label: 'Set Global Song Controls',
click: () => promptKeybind(options, win),
},
{
label: 'Override MediaKeys',
type: 'checkbox',
checked: options.overrideMediaKeys,
click: (item) => setOption(options, 'overrideMediaKeys', item.checked),
},
];
function setOption<Key extends keyof ConfigType<'shortcuts'> = keyof ConfigType<'shortcuts'>>(
options: ConfigType<'shortcuts'>,
key: Key | null = null,
newValue: ConfigType<'shortcuts'>[Key] | null = null,
) {
if (key && newValue !== null) {
options[key] = newValue;
}
setMenuOptions('shortcuts', options);
}
// Helper function for keybind prompt
const kb = (label_: string, value_: string, default_: string): KeybindOptions => ({ value: value_, label: label_, default: default_ });
async function promptKeybind(options: ConfigType<'shortcuts'>, win: BrowserWindow) {
const output = await prompt({
title: 'Global Keybinds',
label: 'Choose Global Keybinds for Songs Control:',
type: 'keybind',
keybindOptions: [ // If default=undefined then no default is used
kb('Previous', 'previous', options.global?.previous),
kb('Play / Pause', 'playPause', options.global?.playPause),
kb('Next', 'next', options.global?.next),
],
height: 270,
...promptOptions(),
}, win);
if (output) {
if (!options.global) {
options.global = {};
}
for (const { value, accelerator } of output) {
options.global[value] = accelerator;
}
setOption(options);
}
// Else -> pressed cancel
}

Some files were not shown because too many files have changed in this diff Show More