fix: apply fix from eslint

This commit is contained in:
JellyBrick
2025-07-10 14:14:43 +09:00
parent 2cfc38757a
commit 1da83ff27c
29 changed files with 1654 additions and 168 deletions

View File

@ -2,6 +2,7 @@
import eslint from '@eslint/js';
import prettier from 'eslint-plugin-prettier/recommended';
import solid from 'eslint-plugin-solid/configs/recommended';
import stylistic from '@stylistic/eslint-plugin-js';
import tsEslint from 'typescript-eslint';
@ -12,6 +13,7 @@ export default tsEslint.config(
tsEslint.configs.eslintRecommended,
...tsEslint.configs.recommendedTypeChecked,
prettier,
solid,
{ ignores: ['dist', 'node_modules', '*.config.*js', '*.test.*js'] },
{
plugins: {

View File

@ -163,6 +163,7 @@
"eslint-import-resolver-typescript": "4.4.4",
"eslint-plugin-import": "2.32.0",
"eslint-plugin-prettier": "5.5.1",
"eslint-plugin-solid": "0.14.5",
"glob": "11.0.3",
"node-gyp": "11.2.0",
"playwright": "1.53.2",

59
pnpm-lock.yaml generated
View File

@ -329,6 +329,9 @@ importers:
eslint-plugin-prettier:
specifier: 5.5.1
version: 5.5.1(@types/eslint@9.6.1)(eslint-config-prettier@10.1.5(eslint@9.30.1))(eslint@9.30.1)(prettier@3.5.2)
eslint-plugin-solid:
specifier: 0.14.5
version: 0.14.5(eslint@9.30.1)(typescript@5.8.3)
glob:
specifier: 11.0.3
version: 11.0.3
@ -2501,6 +2504,13 @@ packages:
eslint-config-prettier:
optional: true
eslint-plugin-solid@0.14.5:
resolution: {integrity: sha512-nfuYK09ah5aJG/oEN6P1qziy1zLgW4PDWe75VNPi4CEFYk1x2AEqwFeQfEPR7gNn0F2jOeqKhx2E+5oNCOBYWQ==}
engines: {node: '>=18.0.0'}
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0
typescript: '>=4.8.4'
eslint-scope@8.4.0:
resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@ -2882,6 +2892,10 @@ packages:
html-entities@2.3.3:
resolution: {integrity: sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==}
html-tags@3.3.1:
resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==}
engines: {node: '>=8'}
html-to-text@9.0.5:
resolution: {integrity: sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==}
engines: {node: '>=14'}
@ -2982,6 +2996,9 @@ packages:
ini@1.3.8:
resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
inline-style-parser@0.2.4:
resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==}
internal-slot@1.1.0:
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
engines: {node: '>= 0.4'}
@ -3066,6 +3083,10 @@ packages:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
is-html@2.0.0:
resolution: {integrity: sha512-S+OpgB5i7wzIue/YSE5hg0e5ZYfG3hhpNh9KGl6ayJ38p7ED6wxQLd1TV91xHpcTvw90KMJ9EwN3F/iNflHBVg==}
engines: {node: '>=8'}
is-inside-container@1.0.0:
resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==}
engines: {node: '>=14.16'}
@ -3265,6 +3286,9 @@ packages:
jszip@3.10.1:
resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==}
kebab-case@1.0.2:
resolution: {integrity: sha512-7n6wXq4gNgBELfDCpzKc+mRrZFs7D+wgfF5WRFLNAr4DA/qtr9Js8uOAVAfHhuLMfAcQ0pRKqbpjx+TcJVdE1Q==}
keyboardevent-from-electron-accelerator@2.0.0:
resolution: {integrity: sha512-iQcmNA0M4ETMNi0kG/q0h/43wZk7rMeKYrXP7sqKIJbHkTU8Koowgzv+ieR/vWJbOwxx5nDC3UnudZ0aLSu4VA==}
@ -3274,6 +3298,9 @@ packages:
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
known-css-properties@0.30.0:
resolution: {integrity: sha512-VSWXYUnsPu9+WYKkfmJyLKtIvaRJi1kXUqVmBACORXZQxT5oZDsoZ2vQP+bQFDnWtpI/4eq3MLoRMjI2fnLzTQ==}
kuromoji@0.1.2:
resolution: {integrity: sha512-V0dUf+C2LpcPEXhoHLMAop/bOht16Dyr+mDiIE39yX3vqau7p80De/koFqpiTcL1zzdZlc3xuHZ8u5gjYRfFaQ==}
@ -4443,6 +4470,9 @@ packages:
stubborn-fs@1.2.5:
resolution: {integrity: sha512-H2N9c26eXjzL/S/K+i/RHHcFanE74dptvvjM8iwzwbVcWY/zjBbgRqF3K0DY4+OD+uTTASTBvDoxPDaPN02D7g==}
style-to-object@1.0.9:
resolution: {integrity: sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==}
sumchecker@3.0.1:
resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==}
engines: {node: '>= 8.0'}
@ -7374,6 +7404,19 @@ snapshots:
'@types/eslint': 9.6.1
eslint-config-prettier: 10.1.5(eslint@9.30.1)
eslint-plugin-solid@0.14.5(eslint@9.30.1)(typescript@5.8.3):
dependencies:
'@typescript-eslint/utils': 8.36.0(eslint@9.30.1)(typescript@5.8.3)
eslint: 9.30.1
estraverse: 5.3.0
is-html: 2.0.0
kebab-case: 1.0.2
known-css-properties: 0.30.0
style-to-object: 1.0.9
typescript: 5.8.3
transitivePeerDependencies:
- supports-color
eslint-scope@8.4.0:
dependencies:
esrecurse: 4.3.0
@ -7828,6 +7871,8 @@ snapshots:
html-entities@2.3.3: {}
html-tags@3.3.1: {}
html-to-text@9.0.5:
dependencies:
'@selderee/plugin-htmlparser2': 0.11.0
@ -7937,6 +7982,8 @@ snapshots:
ini@1.3.8: {}
inline-style-parser@0.2.4: {}
internal-slot@1.1.0:
dependencies:
es-errors: 1.3.0
@ -8026,6 +8073,10 @@ snapshots:
dependencies:
is-extglob: 2.1.1
is-html@2.0.0:
dependencies:
html-tags: 3.3.1
is-inside-container@1.0.0:
dependencies:
is-docker: 3.0.0
@ -8221,6 +8272,8 @@ snapshots:
readable-stream: 2.3.8
setimmediate: 1.0.5
kebab-case@1.0.2: {}
keyboardevent-from-electron-accelerator@2.0.0: {}
keyboardevents-areequal@0.2.2: {}
@ -8229,6 +8282,8 @@ snapshots:
dependencies:
json-buffer: 3.0.1
known-css-properties@0.30.0: {}
kuromoji@0.1.2(patch_hash=4a948f1ea45c61779fa371feb020253ccf7a24e1f7c6b2e250b3ce53d86216d4):
dependencies:
async: 2.6.4
@ -9415,6 +9470,10 @@ snapshots:
stubborn-fs@1.2.5: {}
style-to-object@1.0.9:
dependencies:
inline-style-parser: 0.2.4
sumchecker@3.0.1:
dependencies:
debug: 4.4.1

View File

@ -76,7 +76,7 @@ export default createPlugin({
async onPlayerApiReady(_, { getConfig }) {
const config = await getConfig();
if (config.blocker === blockers.AdSpeedup) {
await loadAdSpeedup();
loadAdSpeedup();
}
},
},

View File

@ -12,7 +12,7 @@ export const DislikeButton = (props: DislikeButtonProps) => (
class="like-menu yt-spec-button-shape-next yt-spec-button-shape-next--text yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--icon-button"
aria-pressed="false"
aria-label="Dislike all"
onClick={props.onClick}
onClick={(e) => props.onClick?.(e)}
>
<div
class="yt-spec-button-shape-next__icon"
@ -34,7 +34,7 @@ export const DislikeButton = (props: DislikeButtonProps) => (
}}
aria-hidden="true"
>
<div style="width: 24px; height: 24px">
<div style={{ 'width': '24px', 'height': '24px' }}>
<svg
viewBox="0 0 24 24"
preserveAspectRatio="xMidYMid meet"
@ -50,7 +50,7 @@ export const DislikeButton = (props: DislikeButtonProps) => (
<path
d="M18,4h3v10h-3V4z M5.23,14h4.23l-1.52,4.94C7.62,19.97,8.46,21,9.62,21c0.58,0,1.14-0.24,1.52-0.65L17,14V4H6.57 C5.5,4,4.59,4.67,4.38,5.61l-1.34,6C2.77,12.85,3.82,14,5.23,14z"
class="style-scope yt-icon"
></path>
/>
</g>
</svg>
</div>
@ -76,7 +76,7 @@ export const DislikeButton = (props: DislikeButtonProps) => (
<path
d="M18,4h3v10h-3V4z M5.23,14h4.23l-1.52,4.94C7.62,19.97,8.46,21,9.62,21c0.58,0,1.14-0.24,1.52-0.65L17,14V4H6.57 C5.5,4,4.59,4.67,4.38,5.61l-1.34,6C2.77,12.85,3.82,14,5.23,14z"
class="style-scope yt-icon"
></path>
/>
</g>
</svg>
</div>
@ -90,8 +90,8 @@ export const DislikeButton = (props: DislikeButtonProps) => (
class="yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response"
aria-hidden="true"
>
<div class="yt-spec-touch-feedback-shape__stroke"></div>
<div class="yt-spec-touch-feedback-shape__fill"></div>
<div class="yt-spec-touch-feedback-shape__stroke" />
<div class="yt-spec-touch-feedback-shape__fill" />
</div>
</yt-touch-feedback-shape>
</button>

View File

@ -12,7 +12,7 @@ export const LikeButton = (props: LikeButtonProps) => (
class="like-menu yt-spec-button-shape-next yt-spec-button-shape-next--text yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--icon-button"
aria-pressed="false"
aria-label="Like all"
onClick={props.onClick}
onClick={(e) => props.onClick?.(e)}
>
<div
class="yt-spec-button-shape-next__icon"
@ -34,7 +34,7 @@ export const LikeButton = (props: LikeButtonProps) => (
}}
aria-hidden="true"
>
<div style="width: 24px; height: 24px">
<div style={{ 'width': '24px', 'height': '24px' }}>
<svg
viewBox="0 0 24 24"
preserveAspectRatio="xMidYMid meet"
@ -50,12 +50,12 @@ export const LikeButton = (props: LikeButtonProps) => (
<path
d="M3,11h3v10H3V11z M18.77,11h-4.23l1.52-4.94C16.38,5.03,15.54,4,14.38,4c-0.58,0-1.14,0.24-1.52,0.65L7,11v10h10.43 c1.06,0,1.98-0.67,2.19-1.61l1.34-6C21.23,12.15,20.18,11,18.77,11z"
class="style-scope yt-icon"
></path>
/>
</g>
</svg>
</div>
</div>
<div style="width: 24px; height: 24px">
<div style={{ 'width': '24px', 'height': '24px' }}>
<svg
viewBox="0 0 24 24"
preserveAspectRatio="xMidYMid meet"
@ -71,18 +71,18 @@ export const LikeButton = (props: LikeButtonProps) => (
<path
d="M3,11h3v10H3V11z M18.77,11h-4.23l1.52-4.94C16.38,5.03,15.54,4,14.38,4c-0.58,0-1.14,0.24-1.52,0.65L7,11v10h10.43 c1.06,0,1.98-0.67,2.19-1.61l1.34-6C21.23,12.15,20.18,11,18.77,11z"
class="style-scope yt-icon"
></path>
/>
</g>
</svg>
</div>
</div>
<yt-touch-feedback-shape style="border-radius: inherit">
<yt-touch-feedback-shape style={{ 'border-radius': 'inherit' }}>
<div
class="yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response"
aria-hidden="true"
>
<div class="yt-spec-touch-feedback-shape__stroke"></div>
<div class="yt-spec-touch-feedback-shape__fill"></div>
<div class="yt-spec-touch-feedback-shape__stroke" />
<div class="yt-spec-touch-feedback-shape__fill" />
</div>
</yt-touch-feedback-shape>
</button>

View File

@ -12,7 +12,7 @@ export const UnDislikeButton = (props: UnDislikeButtonProps) => (
class="like-menu yt-spec-button-shape-next yt-spec-button-shape-next--text yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--icon-button"
aria-pressed="false"
aria-label="Undislike all"
onClick={props.onClick}
onClick={(e) => props.onClick?.(e)}
>
<div
class="yt-spec-button-shape-next__icon"
@ -55,7 +55,7 @@ export const UnDislikeButton = (props: UnDislikeButtonProps) => (
<path
d="M17,4h-1H6.57C5.5,4,4.59,4.67,4.38,5.61l-1.34,6C2.77,12.85,3.82,14,5.23,14h4.23l-1.52,4.94C7.62,19.97,8.46,21,9.62,21 c0.58,0,1.14-0.24,1.52-0.65L17,14h4V4H17z M10.4,19.67C10.21,19.88,9.92,20,9.62,20c-0.26,0-0.5-0.11-0.63-0.3 c-0.07-0.1-0.15-0.26-0.09-0.47l1.52-4.94l0.4-1.29H9.46H5.23c-0.41,0-0.8-0.17-1.03-0.46c-0.12-0.15-0.25-0.4-0.18-0.72l1.34-6 C5.46,5.35,5.97,5,6.57,5H16v8.61L10.4,19.67z M20,13h-3V5h3V13z"
class="style-scope yt-icon"
></path>
/>
</g>
</svg>
</div>
@ -81,7 +81,7 @@ export const UnDislikeButton = (props: UnDislikeButtonProps) => (
<path
d="M17,4h-1H6.57C5.5,4,4.59,4.67,4.38,5.61l-1.34,6C2.77,12.85,3.82,14,5.23,14h4.23l-1.52,4.94C7.62,19.97,8.46,21,9.62,21 c0.58,0,1.14-0.24,1.52-0.65L17,14h4V4H17z M10.4,19.67C10.21,19.88,9.92,20,9.62,20c-0.26,0-0.5-0.11-0.63-0.3 c-0.07-0.1-0.15-0.26-0.09-0.47l1.52-4.94l0.4-1.29H9.46H5.23c-0.41,0-0.8-0.17-1.03-0.46c-0.12-0.15-0.25-0.4-0.18-0.72l1.34-6 C5.46,5.35,5.97,5,6.57,5H16v8.61L10.4,19.67z M20,13h-3V5h3V13z"
class="style-scope yt-icon"
></path>
/>
</g>
</svg>
</div>
@ -95,8 +95,8 @@ export const UnDislikeButton = (props: UnDislikeButtonProps) => (
class="yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response"
aria-hidden="true"
>
<div class="yt-spec-touch-feedback-shape__stroke"></div>
<div class="yt-spec-touch-feedback-shape__fill"></div>
<div class="yt-spec-touch-feedback-shape__stroke" />
<div class="yt-spec-touch-feedback-shape__fill" />
</div>
</yt-touch-feedback-shape>
</button>

View File

@ -12,7 +12,7 @@ export const UnLikeButton = (props: UnLikeButtonProps) => (
class="like-menu yt-spec-button-shape-next yt-spec-button-shape-next--text yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--icon-button"
aria-pressed="false"
aria-label="Unlike all"
onClick={props.onClick}
onClick={(e) => props.onClick?.(e)}
>
<div
class="yt-spec-button-shape-next__icon"
@ -55,7 +55,7 @@ export const UnLikeButton = (props: UnLikeButtonProps) => (
<path
d="M18.77,11h-4.23l1.52-4.94C16.38,5.03,15.54,4,14.38,4c-0.58,0-1.14,0.24-1.52,0.65L7,11H3v10h4h1h9.43 c1.06,0,1.98-0.67,2.19-1.61l1.34-6C21.23,12.15,20.18,11,18.77,11z M7,20H4v-8h3V20z M19.98,13.17l-1.34,6 C18.54,19.65,18.03,20,17.43,20H8v-8.61l5.6-6.06C13.79,5.12,14.08,5,14.38,5c0.26,0,0.5,0.11,0.63,0.3 c0.07,0.1,0.15,0.26,0.09,0.47l-1.52,4.94L13.18,12h1.35h4.23c0.41,0,0.8,0.17,1.03,0.46C19.92,12.61,20.05,12.86,19.98,13.17z"
class="style-scope yt-icon"
></path>
/>
</g>
</svg>
</div>
@ -81,7 +81,7 @@ export const UnLikeButton = (props: UnLikeButtonProps) => (
<path
d="M18.77,11h-4.23l1.52-4.94C16.38,5.03,15.54,4,14.38,4c-0.58,0-1.14,0.24-1.52,0.65L7,11H3v10h4h1h9.43 c1.06,0,1.98-0.67,2.19-1.61l1.34-6C21.23,12.15,20.18,11,18.77,11z M7,20H4v-8h3V20z M19.98,13.17l-1.34,6 C18.54,19.65,18.03,20,17.43,20H8v-8.61l5.6-6.06C13.79,5.12,14.08,5,14.38,5c0.26,0,0.5,0.11,0.63,0.3 c0.07,0.1,0.15,0.26,0.09,0.47l-1.52,4.94L13.18,12h1.35h4.23c0.41,0,0.8,0.17,1.03,0.46C19.92,12.61,20.05,12.86,19.98,13.17z"
class="style-scope yt-icon"
></path>
/>
</g>
</svg>
</div>
@ -95,8 +95,8 @@ export const UnLikeButton = (props: UnLikeButtonProps) => (
class="yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response"
aria-hidden="true"
>
<div class="yt-spec-touch-feedback-shape__stroke"></div>
<div class="yt-spec-touch-feedback-shape__fill"></div>
<div class="yt-spec-touch-feedback-shape__stroke" />
<div class="yt-spec-touch-feedback-shape__fill" />
</div>
</yt-touch-feedback-shape>
</button>

View File

@ -120,7 +120,8 @@ export const backend = createBackend<BackendType, APIServerConfig>({
info: {
version: '1.0.0',
title: 'Youtube Music API Server',
description: 'Note: You need to get an access token using the `/auth/{id}` endpoint first to call any API endpoints under `/api`.',
description:
'Note: You need to get an access token using the `/auth/{id}` endpoint first to call any API endpoints under `/api`.',
},
security: [
{

View File

@ -12,7 +12,7 @@ export const CaptionsSettingButton = (props: CaptionsSettingsButtonProps) => (
role={'button'}
tabindex={0}
title={props.label}
on:click={props.onClick}
on:click={(e) => props.onClick(e)}
>
<span class="yt-icon-shape style-scope yt-icon yt-spec-icon-shape">
<div
@ -26,14 +26,19 @@ export const CaptionsSettingButton = (props: CaptionsSettingsButtonProps) => (
<svg
class="style-scope yt-icon"
preserveAspectRatio="xMidYMid meet"
style="pointer-events: none; display: block; width: 100%; height: 100%"
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 yt-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>
</div>

View File

@ -39,7 +39,10 @@ import type { GetPlayerResponse } from '@/types/get-player-response';
import type { FormatOptions } from 'node_modules/youtubei.js/dist/src/types';
import type { VideoInfo } from 'node_modules/youtubei.js/dist/src/parser/youtube';
import type { PlayerErrorMessage } from 'node_modules/youtubei.js/dist/src/parser/nodes';
import type { TrackInfo, Playlist } from 'node_modules/youtubei.js/dist/src/parser/ytmusic';
import type {
TrackInfo,
Playlist,
} from 'node_modules/youtubei.js/dist/src/parser/ytmusic';
type CustomSongInfo = SongInfo & { trackId?: string };

View File

@ -38,7 +38,7 @@ export const onMenu = async ({
...deepmerge(
defaultConfig.downloadOnFinish,
config.downloadOnFinish,
)!,
),
enabled: item.checked,
},
});
@ -62,7 +62,7 @@ export const onMenu = async ({
...deepmerge(
defaultConfig.downloadOnFinish,
config.downloadOnFinish,
)!,
),
folder: result[0],
},
});
@ -87,7 +87,7 @@ export const onMenu = async ({
...deepmerge(
defaultConfig.downloadOnFinish,
config.downloadOnFinish,
)!,
),
mode: 'seconds',
},
});
@ -105,7 +105,7 @@ export const onMenu = async ({
...deepmerge(
defaultConfig.downloadOnFinish,
config.downloadOnFinish,
)!,
),
mode: 'percent',
},
});
@ -168,7 +168,7 @@ export const onMenu = async ({
...deepmerge(
defaultConfig.downloadOnFinish,
config.downloadOnFinish,
)!,
),
seconds: Number(res[0]),
percent: Number(res[1]),
},

View File

@ -1,5 +1,5 @@
import { createSignal, JSX, Show, splitProps } from 'solid-js';
import { mergeProps, Portal } from 'solid-js/web';
import { createSignal, JSX, Show, splitProps, mergeProps } from 'solid-js';
import { Portal } from 'solid-js/web';
import { css } from 'solid-styled-components';
import { Transition } from 'solid-transition-group';
import {

View File

@ -6,7 +6,7 @@ export interface BackButtonProps {
export const BackButton = (props: BackButtonProps) => (
<div
class="style-scope ytmusic-pivot-bar-renderer navigation-item"
onClick={props.onClick}
onClick={(e) => props.onClick?.(e)}
role="tab"
tab-id="FEmusic_back"
>
@ -33,7 +33,7 @@ export const BackButton = (props: BackButtonProps) => (
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>
<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" />
</g>
</svg>
</div>

View File

@ -6,7 +6,7 @@ export interface ForwardButtonProps {
export const ForwardButton = (props: ForwardButtonProps) => (
<div
class="style-scope ytmusic-pivot-bar-renderer navigation-item"
onClick={props.onClick}
onClick={(e) => props.onClick?.(e)}
role="tab"
tab-id="FEmusic_next"
>
@ -37,7 +37,7 @@ export const ForwardButton = (props: ForwardButtonProps) => (
d="M382.7,226.8L163.7,7.9c-5.1-5.1-11.8-7.9-19-7.9s-14,2.8-19,7.9L109.5,24c-10.5,10.5-10.5,27.6,0,38.1
l183.9,183.9L109.3,430c-5.1,5.1-7.9,11.8-7.9,19c0,7.2,2.8,14,7.9,19l16.1,16.1c5.1,5.1,11.8,7.9,19,7.9s14-2.8,19-7.9L382.7,265
c5.1-5.1,7.9-11.9,7.8-19.1C390.5,238.7,387.8,231.9,382.7,226.8z"
></path>
/>
</g>
</svg>
</div>

View File

@ -23,4 +23,4 @@ export default createPlugin({
}
}
},
});
});

View File

@ -8,7 +8,7 @@ export const PictureInPictureButton = (props: PictureInPictureButtonProps) => (
class="yt-simple-endpoint style-scope ytmusic-menu-navigation-item-renderer"
id="navigation-endpoint"
tabindex={-1}
onClick={props.onClick}
onClick={(e) => props.onClick?.(e)}
>
<div class="icon ytmd-menu-item style-scope ytmusic-menu-navigation-item-renderer">
<svg

View File

@ -26,13 +26,13 @@ export const PlaybackSpeedSlider = (props: PlaybackSpeedSliderProps) => (
aria-valuenow={props.speed}
class="volume-slider style-scope ytmusic-player-bar on-hover"
dir="ltr"
on:immediate-value-changed={props.onImmediateValueChanged}
onWheel={props.onWheel}
on:immediate-value-changed={(e) => props.onImmediateValueChanged?.(e)}
onWheel={(e) => props.onWheel?.(e)}
max="2"
min="0"
role="slider"
step="0.125"
style="display: inherit !important"
style={{ 'display': 'inherit !important' }}
tabindex="0"
title={props.title}
value={props.speed}
@ -48,7 +48,7 @@ export const PlaybackSpeedSlider = (props: PlaybackSpeedSliderProps) => (
class="style-scope tp-yt-paper-slider"
id="sliderBar"
role="progressbar"
style="touch-action: none"
style={{ 'touch-action': 'none' }}
value="1"
>
<div
@ -59,12 +59,12 @@ export const PlaybackSpeedSlider = (props: PlaybackSpeedSliderProps) => (
class="style-scope tp-yt-paper-progress"
hidden={true}
id="secondaryProgress"
style="transform: scaleX(0)"
style={{ 'transform': 'scaleX(0)' }}
/>
<div
class="style-scope tp-yt-paper-progress"
id="primaryProgress"
style="transform: scaleX(0.5)"
style={{ 'transform': 'scaleX(0.5)' }}
/>
</div>
</tp-yt-paper-progress>
@ -72,7 +72,7 @@ export const PlaybackSpeedSlider = (props: PlaybackSpeedSliderProps) => (
<div
class="slider-knob style-scope tp-yt-paper-slider"
id="sliderKnob"
style="left: 50%; touch-action: none"
style={{ 'left': '50%', 'touch-action': 'none' }}
>
<input
class="slider-knob-inner style-scope tp-yt-paper-slider"

View File

@ -12,7 +12,7 @@ export const QualitySettingButton = (props: QualitySettingButtonProps) => (
role={'button'}
tabindex={0}
title={props.label}
on:click={props.onClick}
on:click={(e) => props.onClick(e)}
>
<span class="yt-icon-shape style-scope yt-icon yt-spec-icon-shape">
<div
@ -26,14 +26,19 @@ export const QualitySettingButton = (props: QualitySettingButtonProps) => (
<svg
class="style-scope yt-icon"
preserveAspectRatio="xMidYMid meet"
style="pointer-events: none; display: block; width: 100%; height: 100%"
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 yt-icon"
d="M19.43 12.98c.04-.32.07-.64.07-.98s-.03-.66-.07-.98l2.1-1.65c.2-.15.25-.42.13-.64l-2-3.46c-.12-.22-.4-.3-.6-.22l-2.5 1c-.52-.4-1.08-.73-1.7-.98l-.37-2.65c-.06-.24-.27-.42-.5-.42h-4c-.27 0-.48.18-.5.42l-.4 2.65c-.6.25-1.17.6-1.7.98l-2.48-1c-.23-.1-.5 0-.6.22l-2 3.46c-.14.22-.08.5.1.64l2.12 1.65c-.04.32-.07.65-.07.98s.02.66.06.98l-2.1 1.65c-.2.15-.25.42-.13.64l2 3.46c.12.22.4.3.6.22l2.5-1c.52.4 1.08.73 1.7.98l.37 2.65c.04.24.25.42.5.42h4c.25 0 .46-.18.5-.42l.37-2.65c.6-.25 1.17-.6 1.7-.98l2.48 1c.23.1.5 0 .6-.22l2-3.46c.13-.22.08-.5-.1-.64l-2.12-1.65zM12 15.5c-1.93 0-3.5-1.57-3.5-3.5s1.57-3.5 3.5-3.5 3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z"
></path>
/>
</g>
</svg>
</div>

View File

@ -1,12 +1,13 @@
import { createBackend } from '@/utils';
import { net } from 'electron';
import { createBackend } from '@/utils';
const handlers = {
// Note: This will only be used for Forbidden headers, e.g. User-Agent, Authority, Cookie, etc.
// See: https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_request_header
async fetch(
url: string,
init: RequestInit
init: RequestInit,
): Promise<[number, string, Record<string, string>]> {
const res = await net.fetch(url, init);
return [
@ -19,7 +20,9 @@ const handlers = {
export const backend = createBackend({
start(ctx) {
ctx.ipc.handle('synced-lyrics:fetch', handlers.fetch);
ctx.ipc.handle('synced-lyrics:fetch', (url: string, init: RequestInit) =>
handlers.fetch(url, init),
);
},
stop(ctx) {
ctx.ipc.removeHandler('synced-lyrics:fetch');

View File

@ -17,7 +17,6 @@ export class LRCLib implements LyricProvider {
songDuration,
tags,
}: SearchSongInfo): Promise<LyricResult | null> {
let query = new URLSearchParams({
artist_name: artist,
track_name: title,
@ -59,7 +58,7 @@ export class LRCLib implements LyricProvider {
if (!Array.isArray(data)) {
throw new Error(`Expected an array, instead got ${typeof data}`);
}
// If still no results, try with the original title as fallback
if (data.length === 0 && alternativeTitle) {
query = new URLSearchParams({ q: title });
@ -98,15 +97,15 @@ export class LRCLib implements LyricProvider {
}
}
let ratio = Math.max(
...permutations.map(([x, y]) => jaroWinkler(x, y)),
);
let ratio = Math.max(...permutations.map(([x, y]) => jaroWinkler(x, y)));
// If direct artist match is below threshold and we have tags, try matching with tags
if (ratio <= 0.9 && tags && tags.length > 0) {
// Filter out the artist from tags to avoid duplicate comparisons
const filteredTags = tags.filter(tag => tag.toLowerCase() !== artist.toLowerCase());
const filteredTags = tags.filter(
(tag) => tag.toLowerCase() !== artist.toLowerCase(),
);
const tagPermutations = [];
// Compare each tag with each item artist
for (const tag of filteredTags) {
@ -114,19 +113,19 @@ export class LRCLib implements LyricProvider {
tagPermutations.push([tag.toLowerCase(), itemArtist.toLowerCase()]);
}
}
// Compare each item artist with each tag
for (const itemArtist of itemArtists) {
for (const tag of filteredTags) {
tagPermutations.push([itemArtist.toLowerCase(), tag.toLowerCase()]);
}
}
if (tagPermutations.length > 0) {
const tagRatio = Math.max(
...tagPermutations.map(([x, y]) => jaroWinkler(x, y)),
);
// Use the best match ratio between direct artist match and tag match
ratio = Math.max(ratio, tagRatio);
}

View File

@ -37,10 +37,13 @@ type LyricsStore = {
};
const initialData = () =>
providerNames.reduce((acc, name) => {
acc[name] = { state: 'fetching', data: null, error: null };
return acc;
}, {} as LyricsStore['lyrics']);
providerNames.reduce(
(acc, name) => {
acc[name] = { state: 'fetching', data: null, error: null };
return acc;
},
{} as LyricsStore['lyrics'],
);
export const [lyricsStore, setLyricsStore] = createStore<LyricsStore>({
provider: providerNames[0],

View File

@ -10,11 +10,13 @@ interface PlainLyricsProps {
export const PlainLyrics = (props: PlainLyricsProps) => {
const [romanization, setRomanization] = createSignal('');
createEffect(async () => {
createEffect(() => {
if (!config()?.romanization) return;
const input = canonicalize(props.line);
setRomanization(canonicalize(await romanize(input)));
romanize(input).then((result) => {
setRomanization(canonicalize(result));
});
});
return (

View File

@ -1,13 +1,14 @@
import { createEffect, createMemo, For, Show, createSignal } from 'solid-js';
import { VirtualizerHandle } from 'virtua/solid';
import { LineLyrics } from '@/plugins/synced-lyrics/types';
import { config } from '../renderer';
import { _ytAPI } from '..';
import { canonicalize, romanize, simplifyUnicode } from '../utils';
import { VirtualizerHandle } from 'virtua/solid';
import { LineLyrics } from '@/plugins/synced-lyrics/types';
interface SyncedLineProps {
scroller: VirtualizerHandle;
index: number;
@ -27,80 +28,57 @@ export const SyncedLine = (props: SyncedLineProps) => {
const [romanization, setRomanization] = createSignal('');
createEffect(async () => {
createEffect(() => {
if (!config()?.romanization) return;
const input = canonicalize(text());
setRomanization(canonicalize(await romanize(input)));
romanize(input).then((result) => {
setRomanization(canonicalize(result));
});
});
if (!text()) {
return (
<yt-formatted-string
text={{
runs: [{ text: '' }],
}}
/>
);
}
return (
<div
class={`synced-line ${props.status}`}
onClick={() => {
_ytAPI?.seekTo((props.line.timeInMs + 10) / 1000);
}}
>
<div dir="auto" class="description ytmusic-description-shelf-renderer">
<Show
when={text()}
fallback={
<yt-formatted-string
text={{
runs: [
{ text: config()?.showTimeCodes ? `[${props.line.time}] ` : '' },
],
runs: [{ text: '' }],
}}
/>
}
>
<div
class={`synced-line ${props.status}`}
onClick={() => {
_ytAPI?.seekTo((props.line.timeInMs + 10) / 1000);
}}
>
<div dir="auto" class="description ytmusic-description-shelf-renderer">
<yt-formatted-string
text={{
runs: [
{
text: config()?.showTimeCodes ? `[${props.line.time}] ` : '',
},
],
}}
/>
<div
class="text-lyrics"
ref={(div: HTMLDivElement) => {
// TODO: Investigate the animation, even though the duration is properly set, all lines have the same animation duration
div.style.setProperty(
'--lyrics-duration',
`${props.line.duration / 1000}s`,
'important',
);
}}
style={{ 'display': 'flex', 'flex-direction': 'column' }}
>
<span>
<For each={text().split(' ')}>
{(word, index) => {
return (
<span
style={{
'transition-delay': `${index() * 0.05}s`,
'animation-delay': `${index() * 0.05}s`,
}}
>
<yt-formatted-string
text={{
runs: [{ text: `${word} ` }],
}}
/>
</span>
);
}}
</For>
</span>
<Show
when={
config()?.romanization &&
simplifyUnicode(text()) !== simplifyUnicode(romanization())
}
<div
class="text-lyrics"
ref={(div: HTMLDivElement) => {
// TODO: Investigate the animation, even though the duration is properly set, all lines have the same animation duration
div.style.setProperty(
'--lyrics-duration',
`${props.line.duration / 1000}s`,
'important',
);
}}
style={{ 'display': 'flex', 'flex-direction': 'column' }}
>
<span class="romaji">
<For each={romanization().split(' ')}>
<span>
<For each={text().split(' ')}>
{(word, index) => {
return (
<span
@ -119,9 +97,37 @@ export const SyncedLine = (props: SyncedLineProps) => {
}}
</For>
</span>
</Show>
<Show
when={
config()?.romanization &&
simplifyUnicode(text()) !== simplifyUnicode(romanization())
}
>
<span class="romaji">
<For each={romanization().split(' ')}>
{(word, index) => {
return (
<span
style={{
'transition-delay': `${index() * 0.05}s`,
'animation-delay': `${index() * 0.05}s`,
}}
>
<yt-formatted-string
text={{
runs: [{ text: `${word} ` }],
}}
/>
</span>
);
}}
</For>
</span>
</Show>
</div>
</div>
</div>
</div>
</Show>
);
};

View File

@ -14,7 +14,7 @@ import type { SyncedLyricsPluginConfig } from '../types';
export let _ytAPI: YoutubePlayer | null = null;
export let netFetch: (
url: string,
init?: RequestInit
init?: RequestInit,
) => Promise<[number, string, Record<string, string>]>;
export const renderer = createRenderer<
@ -56,7 +56,7 @@ export const renderer = createRenderer<
if (!this.updateTimestampInterval) {
this.updateTimestampInterval = setInterval(
() => setCurrentTime((_ytAPI?.getCurrentTime() ?? 0) * 1000),
100
100,
);
}

View File

@ -10,11 +10,11 @@ import pinyin from 'tiny-pinyin';
import { lazy } from 'lazy-var';
import { detect } from 'tinyld';
import { waitForElement } from '@/utils/wait-for-element';
import { LyricsRenderer, setIsVisible } from './renderer';
import { detect } from 'tinyld';
export const selectors = {
head: '#tabsContent > .tab-header:nth-of-type(2)',
body: {

View File

@ -183,7 +183,7 @@ export default createPlugin({
setVideoState(target.checked);
}}
songButtonText={t('plugins.video-toggle.templates.button-song')}
videoButtonText={t('plugins.video-toggle.templates.button-video',)}
videoButtonText={t('plugins.video-toggle.templates.button-video')}
/>
</Show>
),

View File

@ -9,8 +9,8 @@ export const VideoSwitchButton = (props: VideoSwitchButtonProps) => (
<div
class="video-switch-button"
data-video-button-text={props.videoButtonText}
on:click={props.onClick}
onChange={props.onChange}
on:click={(e) => props.onClick?.(e)}
onChange={(e) => props.onChange?.(e)}
>
<input
checked={true}
@ -18,7 +18,10 @@ export const VideoSwitchButton = (props: VideoSwitchButtonProps) => (
class="video-switch-button-checkbox"
type="checkbox"
/>
<label class="video-switch-button-label" for="video-toggle-video-switch-button-checkbox">
<label
class="video-switch-button-label"
for="video-toggle-video-switch-button-checkbox"
>
<span class="video-switch-button-label-span">{props.songButtonText}</span>
</label>
</div>

File diff suppressed because one or more lines are too long