Compare commits

..

1 Commits

Author SHA1 Message Date
dda35ef959 chore(deps): update dependency vite to v7.2.5 2025-11-18 13:32:24 +00:00
20 changed files with 283 additions and 1159 deletions

View File

@ -45,7 +45,7 @@
}, },
"pnpm": { "pnpm": {
"overrides": { "overrides": {
"vite": "npm:rolldown-vite@7.1.8", "vite": "npm:rolldown-vite@7.2.5",
"node-gyp": "11.4.2", "node-gyp": "11.4.2",
"xml2js": "0.6.2", "xml2js": "0.6.2",
"node-fetch": "3.3.2", "node-fetch": "3.3.2",
@ -90,7 +90,6 @@
"butterchurn-presets": "3.0.0-beta.4", "butterchurn-presets": "3.0.0-beta.4",
"color": "5.0.0", "color": "5.0.0",
"conf": "14.0.0", "conf": "14.0.0",
"crypto-js": "^4.2.0",
"custom-electron-prompt": "1.5.8", "custom-electron-prompt": "1.5.8",
"deepmerge-ts": "7.1.5", "deepmerge-ts": "7.1.5",
"delay": "6.0.0", "delay": "6.0.0",
@ -135,7 +134,7 @@
"virtua": "0.42.3", "virtua": "0.42.3",
"vudio": "2.1.1", "vudio": "2.1.1",
"x11": "2.3.0", "x11": "2.3.0",
"youtubei.js": "^16.0.1", "youtubei.js": "15.0.1",
"zod": "4.1.5" "zod": "4.1.5"
}, },
"devDependencies": { "devDependencies": {
@ -145,7 +144,6 @@
"@playwright/test": "1.55.0", "@playwright/test": "1.55.0",
"@stylistic/eslint-plugin": "5.3.1", "@stylistic/eslint-plugin": "5.3.1",
"@total-typescript/ts-reset": "0.6.1", "@total-typescript/ts-reset": "0.6.1",
"@types/crypto-js": "^4.2.2",
"@types/electron-localshortcut": "3.1.3", "@types/electron-localshortcut": "3.1.3",
"@types/howler": "2.2.12", "@types/howler": "2.2.12",
"@types/html-to-text": "9.0.4", "@types/html-to-text": "9.0.4",
@ -175,7 +173,7 @@
"typescript": "5.9.2", "typescript": "5.9.2",
"typescript-eslint": "8.43.0", "typescript-eslint": "8.43.0",
"utf-8-validate": "6.0.5", "utf-8-validate": "6.0.5",
"vite": "npm:rolldown-vite@7.1.8", "vite": "npm:rolldown-vite@7.2.5",
"vite-plugin-inspect": "11.3.3", "vite-plugin-inspect": "11.3.3",
"vite-plugin-resolve": "2.5.2", "vite-plugin-resolve": "2.5.2",
"vite-plugin-solid": "2.11.8", "vite-plugin-solid": "2.11.8",

504
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@
"console": { "console": {
"plugins": { "plugins": {
"execute-failed": "Erweiterung {{pluginName}}::{{contextName}} konnte nicht ausgeführt werden", "execute-failed": "Erweiterung {{pluginName}}::{{contextName}} konnte nicht ausgeführt werden",
"executed-at-ms": "Erweiterung {{pluginName}}::{{contextName}} in {{ms}}ms ausgeführt", "executed-at-ms": "Erweiterung {{pluginName}}::{{contextName}} ausgeführt in {{ms}}ms",
"initialize-failed": "Initialisierung der Erweiterung \"{{pluginName}}\" fehlgeschlagen", "initialize-failed": "Initialisierung der Erweiterung \"{{pluginName}}\" fehlgeschlagen",
"load-all": "Lade alle Erweiterungen", "load-all": "Lade alle Erweiterungen",
"load-failed": "Laden der Erweiterung \"{{pluginName}}\" fehlgeschlagen", "load-failed": "Laden der Erweiterung \"{{pluginName}}\" fehlgeschlagen",

View File

@ -237,8 +237,7 @@
"submenu": { "submenu": {
"percent": "{{ratio}}%" "percent": "{{ratio}}%"
} }
}, }
"enable-seekbar": "Enable seekbar theming"
}, },
"name": "Album Color Theme" "name": "Album Color Theme"
}, },

View File

@ -31,15 +31,6 @@
}, },
"theme": { "theme": {
"css-file-not-found": "CSS faili „{{cssFile}}“ pole olemas, seega eirame eelistust" "css-file-not-found": "CSS faili „{{cssFile}}“ pole olemas, seega eirame eelistust"
},
"unresponsive": {
"details": "Tõrge ei vasta!\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Programmi vahemälu kustutamine"
},
"window": {
"tried-to-render-offscreen": "Akent prooviti renderdada väljaspool ekraani, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
} }
}, },
"dialog": { "dialog": {
@ -98,29 +89,9 @@
"submenu": { "submenu": {
"auto-reset-app-cache": "Rakenduse käivitamisel lähtesta puhverdatud andmed", "auto-reset-app-cache": "Rakenduse käivitamisel lähtesta puhverdatud andmed",
"disable-hardware-acceleration": "Lülita raudvaraline kiirendamine välja", "disable-hardware-acceleration": "Lülita raudvaraline kiirendamine välja",
"edit-config-json": "Muuda config.json faili", "edit-config-json": "Muuda config.json faili"
"override-user-agent": "Jõudlusta User-Agent",
"restart-on-config-changes": "Taaskäivita pärast konfiguratsiooni muutmist",
"set-proxy": {
"label": "Määra proxy",
"prompt": {
"label": "Sisesta proxy aadress: (jäta täitmata, et välja lülitada)",
"placeholder": "Näide: SOCKS5://127.0.0.1:9999",
"title": "Määra proxy"
}
},
"toggle-dev-tools": "Lülita sisse arendaja tööriistad"
} }
}, },
"always-on-top": "Alati esiplaanil",
"auto-update": "Automaatsed uuendused",
"hide-menu": {
"dialog": {
"message": "Järgmisel käivitamisel jääb menüü peidetuks, kasutage [Alt] klahvi, et näidata (või [`], kui kasutate rakendusesisest menüüd)",
"title": "Menüü peitmine on sisse lülitatud"
},
"label": "Peida menüü"
},
"language": { "language": {
"dialog": { "dialog": {
"message": "Keele muutmine jõustub peale uuesti käivitamist", "message": "Keele muutmine jõustub peale uuesti käivitamist",
@ -132,38 +103,13 @@
} }
}, },
"resume-on-start": "Rakenduse käivitamisel jätka viimatiesitatud loo esitamist", "resume-on-start": "Rakenduse käivitamisel jätka viimatiesitatud loo esitamist",
"single-instance-lock": "Ühe instantsi lukk",
"start-at-login": "Käivita sisselogimisel", "start-at-login": "Käivita sisselogimisel",
"starting-page": { "starting-page": {
"label": "Avaleht", "label": "Avaleht",
"unset": "Määramata" "unset": "Määramata"
}, },
"tray": {
"label": "Trey",
"submenu": {
"disabled": "Välja lülitatud",
"enabled-and-hide-app": "Sisse lülitatud ja rakendus peidetud",
"enabled-and-show-app": "Sisse lülitatud ja rakendus nähtav",
"play-pause-on-click": "Mängi/Peata klõpsates"
}
},
"visual-tweaks": { "visual-tweaks": {
"label": "Visuaalsed muudatused",
"submenu": { "submenu": {
"custom-window-title": {
"label": "Kohandatud akna tiitel",
"prompt": {
"label": "Sisesta kohandatud akna tiitel: (jäta täitmata, et välja lülitada)",
"placeholder": "Näide: Pear Desktop"
}
},
"like-buttons": {
"default": "Vaikimisi",
"force-show": "Sunni näitama",
"hide": "Peida",
"label": "Meeldib nupud"
},
"remove-upgrade-button": "Eemalda upgrade nupp",
"theme": { "theme": {
"dialog": { "dialog": {
"button": { "button": {
@ -173,11 +119,7 @@
"remove-theme": "Kas oled kindel, et soovid enda loodud kujunduse eemaldada?", "remove-theme": "Kas oled kindel, et soovid enda loodud kujunduse eemaldada?",
"remove-theme-message": "Sellega saab sinu loodud kujundus eemdladatud" "remove-theme-message": "Sellega saab sinu loodud kujundus eemdladatud"
}, },
"label": "Kujundus", "label": "Kujundus"
"submenu": {
"import-css-file": "Impordi kohandatud CSS fail",
"no-theme": "Ilma kujunduseta"
}
} }
} }
} }
@ -225,28 +167,8 @@
}, },
"name": "Reklaamiblokeerija" "name": "Reklaamiblokeerija"
}, },
"album-actions": {
"description": "Lisab Undislike, Ebameeldiv, Meeldiv ja Unlike nupud selle rakendamiseks kõikidele loendisse või albumisse kuuluvatele lauludele.",
"name": "Albumi aktsioonid"
},
"album-color-theme": {
"description": "Rakendab dünaamilist teemat ja visuaalseid efekte, mis põhinevad albumi värvipalettil",
"menu": {
"color-mix-ratio": {
"label": "Värvide segamissuhe",
"submenu": {
"percent": "{{suhe}}%"
}
}
},
"name": "Albumi värviteema"
},
"ambient-mode": { "ambient-mode": {
"description": "Rakendab valgusefekti, projitseerides videost õrnad värvid ekraani taustale",
"menu": { "menu": {
"blur-amount": {
"label": "Hägusus"
},
"opacity": { "opacity": {
"submenu": { "submenu": {
"percent": "{{opacity}}%" "percent": "{{opacity}}%"

View File

@ -53,158 +53,8 @@
"later": "Despois", "later": "Despois",
"restart-now": "Reiniciar Agora" "restart-now": "Reiniciar Agora"
}, },
"detail": "O plugin \"{{pluginName}}\" precisa dun reinicio para tomar efecto", "detail": "O plugin \"{{pluginName}}\" precisa dun reinicio para tomar efecto"
"message": "\"{{pluginName}}\" precisa reiniciar",
"title": "Requírese reinicio"
},
"unresponsive": {
"buttons": {
"quit": "Saír",
"relaunch": "Lanzar de novo",
"wait": "Agardar"
},
"detail": "Desculpa o inconveniente! Por favor escolle que facer:",
"message": "A aplicación non responde",
"title": "A xanela non responde"
},
"update-available": {
"buttons": {
"disable": "Desactivar actualizacións",
"download": "Descarregar",
"ok": "OK"
},
"detail": "Está dispoñíbel unha nova versión que se pode descarregar de {{downloadLink}}",
"message": "Hai una nova versión dispoñíbel",
"title": "Actualización dispoñíbel"
} }
},
"menu": {
"about": "Sobre",
"navigation": {
"label": "Navegación",
"submenu": {
"copy-current-url": "Copiar o URL actual",
"go-back": "Atrás",
"go-forward": "Adiante",
"quit": "Saír",
"restart": "Reiniciar a aplicación"
}
},
"options": {
"label": "Opcións",
"submenu": {
"advanced-options": {
"label": "Opcións avanzadas",
"submenu": {
"auto-reset-app-cache": "Reiniciar a caché cando a aplicación arrinque",
"disable-hardware-acceleration": "Desactivar a aceleración hardware",
"edit-config-json": "Editar config.json",
"override-user-agent": "Substituír o User-Agent",
"restart-on-config-changes": "Reiniciar ao alterar a configuración",
"set-proxy": {
"label": "Configurar o proxy",
"prompt": {
"label": "Introducir o enderezo do proxy (deixar baleiro para desactivalo)",
"placeholder": "Exemplo: SOCKS5://127.0.0.1:9999",
"title": "Configurar o proxy"
}
},
"toggle-dev-tools": "Activar ou desactivar as DevTools"
}
},
"always-on-top": "Sempre en primeiro plano",
"auto-update": "Actualización automática",
"hide-menu": {
"dialog": {
"message": "O menú ocultarase no próximo inicio; use [Alt] para mostralo (ou a tecla [`] se emprega o menú integrado)",
"title": "Ocultar Menú activado"
},
"label": "Ocultar Menú"
},
"language": {
"dialog": {
"message": "A lingua hase mudar despois do reinicio",
"title": "Mudouse a lingua"
},
"label": "Lingua",
"submenu": {
"to-help-translate": "Quere axudar a traducir? Prema aquí"
}
},
"resume-on-start": "Retomar a última canción ao iniciar a aplicación",
"single-instance-lock": "Bloqueo de instancia única",
"start-at-login": "Iniciar co inicio de sesión",
"starting-page": {
"label": "Páxina de inicio",
"unset": "Sen definir"
},
"tray": {
"label": "Bandexa",
"submenu": {
"disabled": "Desactivado",
"enabled-and-hide-app": "Activado e ocultar a aplicación",
"enabled-and-show-app": "Activado e mostrar a aplicación",
"play-pause-on-click": "Reproducir/Pausar ao premer"
}
},
"visual-tweaks": {
"label": "Axustes visuais",
"submenu": {
"custom-window-title": {
"label": "Título de xanela personalizado",
"prompt": {
"label": "Introduza o título personalizado da xanela (deixe baleiro para desactivala)",
"placeholder": "Exemplo: Pear Desktop"
}
},
"like-buttons": {
"default": "Predeterminado",
"force-show": "Forzar a visualización",
"hide": "Agochar",
"label": "Botóns de Gústame"
},
"remove-upgrade-button": "Retirar o botón de anovación",
"theme": {
"dialog": {
"button": {
"cancel": "Cancelar",
"remove": "Retirar"
},
"remove-theme": "Estás certo que queres retirar o tema personalizado?",
"remove-theme-message": "Isto ha retirar o tema personalizado"
},
"label": "Tema",
"submenu": {
"import-css-file": "Importar arquivo CSS personalizado",
"no-theme": "Sen tema"
}
}
}
}
}
},
"plugins": {
"enabled": "Activado",
"label": "Complementos",
"new": "NOVO"
},
"view": {
"label": "Vista",
"submenu": {
"force-reload": "Forzar recarga",
"reload": "Recargar",
"reset-zoom": "Tamaño real",
"toggle-fullscreen": "Alternar Pantalla Completa",
"zoom-in": "Achegarse",
"zoom-out": "Afastarse"
}
}
},
"tray": {
"next": "Seguinte",
"play-pause": "Reproducir/Pausar",
"previous": "Anterior",
"quit": "Saír"
} }
} }
} }

View File

@ -150,11 +150,6 @@
"visual-tweaks": { "visual-tweaks": {
"label": "תיקונים חזותיים", "label": "תיקונים חזותיים",
"submenu": { "submenu": {
"custom-window-title": {
"prompt": {
"placeholder": "לדוגמה: שולחן כתיבה אגסי"
}
},
"like-buttons": { "like-buttons": {
"default": "ברירת מחדל", "default": "ברירת מחדל",
"force-show": "הפעל בכוח", "force-show": "הפעל בכוח",
@ -206,8 +201,8 @@
"restart": "הפעל מחדש", "restart": "הפעל מחדש",
"show": "הראה חלון", "show": "הראה חלון",
"tooltip": { "tooltip": {
"default": "שולחן כתיבה אגסי", "default": "יוטיוב מיוזיק",
"with-song-info": "שולחן כתיב אגסי: {{יוצר}} - {{כותרת}}" "with-song-info": "יוטיוב מיוזיק: {{artist}} - {{title}}"
} }
} }
}, },
@ -217,7 +212,7 @@
"name": "הגבר מהירות פרסומת" "name": "הגבר מהירות פרסומת"
}, },
"adblocker": { "adblocker": {
"description": "חסום את כל המודעות והמעקבים", "description": "חסום את כל המודעות והמעקב מחוץ לקופסה",
"menu": { "menu": {
"blocker": "חוסם" "blocker": "חוסם"
}, },
@ -249,7 +244,6 @@
} }
}, },
"buffer": { "buffer": {
"label": "חוצץ",
"submenu": { "submenu": {
"buffer": "{{buffer}}" "buffer": "{{buffer}}"
} }
@ -386,11 +380,6 @@
}, },
"templates": { "templates": {
"title": "פתח בחירת כתוביות" "title": "פתח בחירת כתוביות"
},
"toast": {
"caption-changed": "תרגום שונה ל {{שפה}}",
"caption-disabled": "תרגום בוטל",
"no-captions": "אין תרגום זמין לשיר הזה"
} }
}, },
"compact-sidebar": { "compact-sidebar": {
@ -402,11 +391,9 @@
"menu": { "menu": {
"advanced": "מתקדם" "advanced": "מתקדם"
}, },
"name": "התפיידות צלב[בית]",
"prompt": { "prompt": {
"options": { "options": {
"multi-input": { "multi-input": {
"fade-in-duration": "תתפייד בזמן[מילישניות]",
"fade-scaling": { "fade-scaling": {
"linear": "לינארי", "linear": "לינארי",
"logarithmic": "לוגריתמי" "logarithmic": "לוגריתמי"

View File

@ -154,7 +154,7 @@
"label": "कस्टम विंडो टाइटल", "label": "कस्टम विंडो टाइटल",
"prompt": { "prompt": {
"label": "कस्टम विंडो टाइटल डालें: (डिसएबल करने के लिए खाली छोड़ें)", "label": "कस्टम विंडो टाइटल डालें: (डिसएबल करने के लिए खाली छोड़ें)",
"placeholder": "उदाहरण: पियर डेस्कटॉप" "placeholder": "उदाहरण: यूट्यूब संगीत"
} }
}, },
"like-buttons": { "like-buttons": {
@ -208,8 +208,8 @@
"restart": "ऐप पुनः प्रारंभ करें", "restart": "ऐप पुनः प्रारंभ करें",
"show": "ऐप दिखाए", "show": "ऐप दिखाए",
"tooltip": { "tooltip": {
"default": "पियर डेस्कटॉप", "default": "यूट्यूब म्यूजिक",
"with-song-info": "पियर डेस्कटॉप: {{artist}} - {{title}}" "with-song-info": "यूट्यूब म्यूजिक: {{artist}} - {{title}}"
} }
} }
}, },
@ -426,13 +426,7 @@
"menu": { "menu": {
"device-selector": "डिवाइस चुनें" "device-selector": "डिवाइस चुनें"
}, },
"name": "अपनी पसंद का आउटपुट डिवाइस", "name": "अपनी पसंद का आउटपुट डिवाइस"
"prompt": {
"device-selector": {
"label": "उपयोग किए जाने वाला आउटपुट मीडिया चुने",
"title": "आउटपुट डिवाइस चुनें"
}
}
}, },
"disable-autoplay": { "disable-autoplay": {
"description": "गीत को \"रुके हुए \" मोड में शुरू करता है", "description": "गीत को \"रुके हुए \" मोड में शुरू करता है",
@ -457,15 +451,7 @@
"hide-duration-left": "शेष अवधि छिपाएँ", "hide-duration-left": "शेष अवधि छिपाएँ",
"hide-github-button": "GitHub लिंक के बटन को छिपाएँ", "hide-github-button": "GitHub लिंक के बटन को छिपाएँ",
"play-on-pear-desktop": "Pear Desktop म्यूज़िक पर चलाएँ", "play-on-pear-desktop": "Pear Desktop म्यूज़िक पर चलाएँ",
"set-inactivity-timeout": "निष्क्रियता समय समाप्ति सेट करें", "set-inactivity-timeout": "निष्क्रियता समय समाप्ति सेट करें"
"set-status-display-type": {
"label": "स्टेटस टेक्स्ट",
"submenu": {
"artist": "{artist} को सुन रहे है",
"pear-desktop": "Pear Desktop सुन रहे है",
"title": "{song title} सुन रहे है"
}
}
}, },
"name": "डिस्कॉर्ड रिच प्रेजेंस", "name": "डिस्कॉर्ड रिच प्रेजेंस",
"prompt": { "prompt": {
@ -757,7 +743,6 @@
"listenbrainz": { "listenbrainz": {
"token": "listenbrainz उपयोगकर्ता टोकन दर्ज करें" "token": "listenbrainz उपयोगकर्ता टोकन दर्ज करें"
}, },
"scrobble-alternative-artist": "वैकल्पिक आर्टिस्ट का उपयोग करें",
"scrobble-alternative-title": "वैकल्पिक शीर्षक का उपयोग करें", "scrobble-alternative-title": "वैकल्पिक शीर्षक का उपयोग करें",
"scrobble-other-media": "अन्य मीडिया स्क्रोबल करें" "scrobble-other-media": "अन्य मीडिया स्क्रोबल करें"
}, },
@ -803,12 +788,9 @@
"name": "साइलेंस स्किप करें" "name": "साइलेंस स्किप करें"
}, },
"sponsorblock": { "sponsorblock": {
"description": "गाने के वीडियो में जहाँ म्यूजिक नहीं चलता, जैसे शुरुआत या अंत के हिस्से, उन्हें अपने आप स्किप कर देता है", "description": "Intro/Outro जैसे गैर-संगीत भागों को स्किप करता है",
"name": "SponsorBlock" "name": "SponsorBlock"
}, },
"synced-lyrics": {
"description": "LRClib जैसे सोर्सेज के उपयोग से, गानों के लिए सिंक किए गए लिरिक्स देता है।"
},
"video-toggle": { "video-toggle": {
"menu": { "menu": {
"align": { "align": {

View File

@ -237,8 +237,7 @@
"submenu": { "submenu": {
"percent": "{{ratio}}%" "percent": "{{ratio}}%"
} }
}, }
"enable-seekbar": "재생바 색조 변경 활성화"
}, },
"name": "앨범 컬러 기반 테마" "name": "앨범 컬러 기반 테마"
}, },

View File

@ -462,7 +462,7 @@
"label": "Text stavu", "label": "Text stavu",
"submenu": { "submenu": {
"artist": "Aktuálne si prehráva {artist}", "artist": "Aktuálne si prehráva {artist}",
"pear-desktop": "Počúvať Pear Desktop", "pear-desktop": "Aktuálne prehráva Pear Desktop",
"title": "Aktuálne si prehráva {song title}" "title": "Aktuálne si prehráva {song title}"
} }
} }
@ -731,85 +731,11 @@
"title": "Výber kvality videa" "title": "Výber kvality videa"
} }
} }
},
"description": "Umožňuje zmeniť kvalitu videa pomocou tlačidla v prekrytí videa",
"name": "Zmena kvality videa",
"renderer": {
"quality-settings-button": {
"label": "Otvoriť nastavenia kvality prehrávača"
}
} }
}, },
"scrobbler": {
"description": "Pridať podporu scrobbling (napr. last.fm, Listenbrainz)",
"dialog": {
"lastfm": {
"auth-failed": {
"message": "Nepodarilo sa autentifikovať s Last.fm\nSkryť vyskakovacie okno do ďalšieho reštartu.",
"title": "Autentifikácia zlyhala"
}
}
},
"menu": {
"lastfm": {
"api-settings": "Last.fm API Nastavenia"
},
"listenbrainz": {
"token": "Vložiť ListenBrainz používateľský token"
},
"scrobble-alternative-artist": "Použiť alternatívnych umelcov",
"scrobble-alternative-title": "Použiť alternatívne názvy",
"scrobble-other-media": "Scrobble iných médií"
},
"name": "Scrobbler",
"prompt": {
"lastfm": {
"api-key": "Last.fm API kľúč",
"api-secret": "Last.fm API tajomstvo"
},
"listenbrainz": {
"token": {
"label": "Vlož svoj ListenBrainz používateľský token:",
"title": "ListenBrainz token"
}
}
}
},
"shortcuts": {
"description": "Povoľuje nastaviť globálne klávesové skratky pre prehrávanie (Prehrať/Pozastaviť/Ďalšie/Predošlé) a vypínať media OSD prepisovaním media kľúčov, zapne Ctrl/CMD + F pre vyhľadávanie, zapne Linux MPRIS podporu pre media kľúče a vlastné klávesové skratky pre pokročilých používateľov.",
"menu": {
"override-media-keys": "Prepísať Media Kľúče",
"set-keybinds": "Globálne ovládanie skladieb"
},
"name": "Skratky (& MPRIS)",
"prompt": {
"keybind": {
"keybind-options": {
"next": "Ďalšia",
"play-pause": "Prehrať / Pauza",
"previous": "Predošlá"
},
"label": "Zvoliť globálne klávesové skratky na ovládanie skladieb:",
"title": "Globálne klávesové skratky"
}
}
},
"skip-disliked-songs": {
"description": "Preskakuje skladby označené Nepáči sa",
"name": "Preskakovať skladby označené Nepáči sa"
},
"skip-silences": {
"description": "Automaticky preskakovať tiché časti v hudbe",
"name": "Preskakuj tiché časti"
},
"sponsorblock": {
"description": "Automaticky preskakuje nehudbné časti ako intro/outro, alebo tie časti videoklipov v ktorých nehrá hudba",
"name": "Sponzorský blok"
},
"synced-lyrics": { "synced-lyrics": {
"description": "Poskytuje synchronizované texty k skladbám, pričom používa poskytovateľov ako LRClib.", "description": "Poskytuje synchronizované texty k skladbám, pričom používa poskytovateľov ako LRClib.",
"errors": { "errors": {
"fetch": "⚠️\t\tPri získavaní textu sa vyskytla chyba. \n\tSkúste znova neskôr.",
"not-found": "⚠Pre túto skladbu nebol nájdený žiadny text." "not-found": "⚠Pre túto skladbu nebol nájdený žiadny text."
}, },
"menu": { "menu": {

View File

@ -461,9 +461,7 @@
"set-status-display-type": { "set-status-display-type": {
"label": "Статус", "label": "Статус",
"submenu": { "submenu": {
"artist": "Ви слухаєте {artist}", "pear-desktop": "Відтворення з Pear Desktop"
"pear-desktop": "Відтворення з Pear Desktop",
"title": "Ви слухаєте {song title}"
} }
} }
}, },
@ -733,12 +731,7 @@
} }
}, },
"description": "Дозволяє змінювати якість відео за допомогою кнопки на відео оверлеї", "description": "Дозволяє змінювати якість відео за допомогою кнопки на відео оверлеї",
"name": "Зміна якості відео", "name": "Зміна якості відео"
"renderer": {
"quality-settings-button": {
"label": "Відкрити налаштування якості плеєру"
}
}
}, },
"scrobbler": { "scrobbler": {
"description": "Додає підтримку скроблінгу (last.fm, Listenbrainz тощо)", "description": "Додає підтримку скроблінгу (last.fm, Listenbrainz тощо)",
@ -757,7 +750,6 @@
"listenbrainz": { "listenbrainz": {
"token": "Ввести токен користувача ListenBrainz" "token": "Ввести токен користувача ListenBrainz"
}, },
"scrobble-alternative-artist": "Використати іншого виконавця",
"scrobble-alternative-title": "Використовувати альтернативні назви", "scrobble-alternative-title": "Використовувати альтернативні назви",
"scrobble-other-media": "Скробилити інші медіа" "scrobble-other-media": "Скробилити інші медіа"
}, },
@ -843,14 +835,6 @@
"label": "Зробити текст пісні ідеально синхронізованим", "label": "Зробити текст пісні ідеально синхронізованим",
"tooltip": "Обчисли до мілісекунд відображення наступного рядка (може мати невеликий вплив на продуктивність)" "tooltip": "Обчисли до мілісекунд відображення наступного рядка (може мати невеликий вплив на продуктивність)"
}, },
"preferred-provider": {
"label": "Пріорітетний Провайдер",
"none": {
"label": "Жоден",
"tooltip": "Нема провайдера за замовчуванням"
},
"tooltip": "Оберіть якого провайдера використовувати за замовчуванням"
},
"romanization": { "romanization": {
"label": "Транслітерувати текст пісень", "label": "Транслітерувати текст пісень",
"tooltip": "Якщо текст пісні іншою мовою, спробувати його відобразити латинською версією." "tooltip": "Якщо текст пісні іншою мовою, спробувати його відобразити латинською версією."
@ -883,27 +867,6 @@
"description": "Додає віджет TouchBar для користувачів macOS", "description": "Додає віджет TouchBar для користувачів macOS",
"name": "TouchBar" "name": "TouchBar"
}, },
"transparent-player": {
"description": "Зробити вікно програми прозорим",
"menu": {
"opacity": {
"label": "Прозорість",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Тип",
"submenu": {
"acrylic": "Акриловий",
"mica": "Міка",
"none": "Жоден",
"tabbed": "З роздільниками"
}
}
},
"name": "Прозорий Плеєр"
},
"tuna-obs": { "tuna-obs": {
"description": "Інтеграція з плагіном Tuna для OBS", "description": "Інтеграція з плагіном Tuna для OBS",
"name": "Tuna OBS" "name": "Tuna OBS"
@ -935,8 +898,7 @@
}, },
"name": "Перемикач відео", "name": "Перемикач відео",
"templates": { "templates": {
"button-song": "Пісня", "button-song": "Пісня"
"button-video": "Відео"
} }
}, },
"visualizer": { "visualizer": {

View File

@ -151,9 +151,7 @@
"label": "بصری تبدیلیاں", "label": "بصری تبدیلیاں",
"submenu": { "submenu": {
"custom-window-title": { "custom-window-title": {
"label": "اپنی مرضی کا ونڈو عنوان",
"prompt": { "prompt": {
"label": "اپنی مرضی کا ونڈو عنوان درج کریں: (بند کرنے کے لیے خالی چھوڑ دیں)",
"placeholder": "مثال: پیئر ڈیسک ٹاپ" "placeholder": "مثال: پیئر ڈیسک ٹاپ"
} }
}, },
@ -221,8 +219,7 @@
"description": "شروغ سے تمام اشتہارات اور ٹریکنگ بلاک کردیں", "description": "شروغ سے تمام اشتہارات اور ٹریکنگ بلاک کردیں",
"menu": { "menu": {
"blocker": "بلاکر" "blocker": "بلاکر"
}, }
"name": "ایڈ بلاکر"
} }
} }
} }

View File

@ -237,8 +237,7 @@
"submenu": { "submenu": {
"percent": "{{ratio}}%" "percent": "{{ratio}}%"
} }
}, }
"enable-seekbar": "启用进度条主题"
}, },
"name": "专辑配色主题" "name": "专辑配色主题"
}, },
@ -463,8 +462,8 @@
"label": "状态文本", "label": "状态文本",
"submenu": { "submenu": {
"artist": "在听 {artist}", "artist": "在听 {artist}",
"pear-desktop": "在听 Pear Desktop", "title": "在听 {song title}",
"title": "在听 {song title}" "pear-desktop": "在听 Pear Desktop"
} }
} }
}, },

View File

@ -10,32 +10,41 @@ const COLOR_KEY = '--ytmusic-album-color';
const DARK_COLOR_KEY = '--ytmusic-album-color-dark'; const DARK_COLOR_KEY = '--ytmusic-album-color-dark';
const RATIO_KEY = '--ytmusic-album-color-ratio'; const RATIO_KEY = '--ytmusic-album-color-ratio';
type Config = { export default createPlugin<
enabled: boolean; unknown,
ratio: number; unknown,
enableSeekbar: boolean; {
}; color?: ColorInstance;
darkColor?: ColorInstance;
type Renderer = { playerPage: HTMLElement | null;
getMixedColor( navBarBackground: HTMLElement | null;
color: string, ytmusicPlayerBar: HTMLElement | null;
key: string, playerBarBackground: HTMLElement | null;
alpha?: number, sidebarBig: HTMLElement | null;
ratioMultiply?: number, sidebarSmall: HTMLElement | null;
): string; ytmusicAppLayout: HTMLElement | null;
updateColor(alpha: number): void;
onConfigChange(newConfig: Config): void;
};
export default createPlugin({ getMixedColor(
color: string,
key: string,
alpha?: number,
ratioMultiply?: number,
): string;
updateColor(alpha: number): void;
},
{
enabled: boolean;
ratio: number;
}
>({
name: () => t('plugins.album-color-theme.name'), name: () => t('plugins.album-color-theme.name'),
description: () => t('plugins.album-color-theme.description'), description: () => t('plugins.album-color-theme.description'),
restartNeeded: false, restartNeeded: false,
config: { config: {
enabled: false, enabled: false,
ratio: 0.5, ratio: 0.5,
enableSeekbar: true, },
} satisfies Config as Config,
stylesheets: [style], stylesheets: [style],
menu: async ({ getConfig, setConfig }) => { menu: async ({ getConfig, setConfig }) => {
const ratioList = [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]; const ratioList = [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1];
@ -59,28 +68,18 @@ export default createPlugin({
}, },
})), })),
}, },
{
label: t('plugins.album-color-theme.menu.enable-seekbar'),
type: 'checkbox',
checked: config.enableSeekbar,
click(item) {
setConfig({ enableSeekbar: item.checked });
},
},
]; ];
}, },
renderer: { renderer: {
playerPage: null as HTMLElement | null, playerPage: null,
navBarBackground: null as HTMLElement | null, navBarBackground: null,
ytmusicPlayerBar: null as HTMLElement | null, ytmusicPlayerBar: null,
playerBarBackground: null as HTMLElement | null, playerBarBackground: null,
sidebarBig: null as HTMLElement | null, sidebarBig: null,
sidebarSmall: null as HTMLElement | null, sidebarSmall: null,
ytmusicAppLayout: null as HTMLElement | null, ytmusicAppLayout: null,
color: null as ColorInstance | null,
darkColor: null as ColorInstance | null,
start() { async start({ getConfig }) {
this.playerPage = document.querySelector<HTMLElement>('#player-page'); this.playerPage = document.querySelector<HTMLElement>('#player-page');
this.navBarBackground = document.querySelector<HTMLElement>( this.navBarBackground = document.querySelector<HTMLElement>(
'#nav-bar-background', '#nav-bar-background',
@ -95,11 +94,14 @@ export default createPlugin({
'#mini-guide-background', '#mini-guide-background',
); );
this.ytmusicAppLayout = document.querySelector<HTMLElement>('#layout'); this.ytmusicAppLayout = document.querySelector<HTMLElement>('#layout');
},
async onPlayerApiReady(playerApi, { getConfig }) {
const config = await getConfig();
(this as Renderer).onConfigChange(config);
const config = await getConfig();
document.documentElement.style.setProperty(
RATIO_KEY,
`${~~(config.ratio * 100)}%`,
);
},
onPlayerApiReady(playerApi) {
const fastAverageColor = new FastAverageColor(); const fastAverageColor = new FastAverageColor();
document.addEventListener('videodatachange', async (event) => { document.addEventListener('videodatachange', async (event) => {
@ -150,7 +152,7 @@ export default createPlugin({
alpha = value; alpha = value;
} }
} }
(this as Renderer).updateColor(alpha ?? 1); this.updateColor(alpha ?? 1);
}); });
}, },
onConfigChange(config) { onConfigChange(config) {
@ -158,15 +160,8 @@ export default createPlugin({
RATIO_KEY, RATIO_KEY,
`${~~(config.ratio * 100)}%`, `${~~(config.ratio * 100)}%`,
); );
if (config.enableSeekbar) document.body.classList.add('seekbar-theme');
else document.body.classList.remove('seekbar-theme');
}, },
getMixedColor( getMixedColor(color: string, key: string, alpha = 1, ratioMultiply) {
color: string,
key: string,
alpha = 1,
ratioMultiply?: number,
) {
const keyColor = `rgba(var(${key}), ${alpha})`; const keyColor = `rgba(var(${key}), ${alpha})`;
let colorRatio = `var(${RATIO_KEY}, 50%)`; let colorRatio = `var(${RATIO_KEY}, 50%)`;
@ -212,39 +207,26 @@ export default createPlugin({
'--yt-spec-black-pure-alpha-80': 'rgba(0,0,0,0.8)', '--yt-spec-black-pure-alpha-80': 'rgba(0,0,0,0.8)',
'--yt-spec-black-1-alpha-98': 'rgba(40,40,40,0.98)', '--yt-spec-black-1-alpha-98': 'rgba(40,40,40,0.98)',
'--yt-spec-black-1-alpha-95': 'rgba(40,40,40,0.95)', '--yt-spec-black-1-alpha-95': 'rgba(40,40,40,0.95)',
'--paper-toast-background-color': '#323232',
'--ytmusic-search-background': '#030303',
'--paper-slider-knob-color': '#f03',
'--paper-dialog-background-color': '#212121',
'--paper-progress-active-color-1': '#f03',
'--paper-progress-active-color-2': '#ff2791',
'--yt-spec-inverted-background': '#f3f3f3',
'background': 'rgba(3, 3, 3)',
'--ytmusic-background': 'rgba(3, 3, 3)',
}; };
const colorKeyMap: Record<string, string> = {
'background': DARK_COLOR_KEY,
'--ytmusic-background': DARK_COLOR_KEY,
};
const ratioMap: Record<string, number> = {
'--paper-progress-active-color-1': 1.75,
'--paper-progress-active-color-2': 1.75,
'--yt-spec-inverted-background': 1.75,
};
const getMixedColor = (this as Renderer).getMixedColor.bind(this);
Object.entries(variableMap).map(([variable, color]) => { Object.entries(variableMap).map(([variable, color]) => {
const key = colorKeyMap[variable] ?? COLOR_KEY;
const ratio = ratioMap[variable] ?? undefined;
document.documentElement.style.setProperty( document.documentElement.style.setProperty(
variable, variable,
getMixedColor(color, key, alpha, ratio), this.getMixedColor(color, COLOR_KEY, alpha),
'important', 'important',
); );
}); });
document.body.style.setProperty(
'background',
this.getMixedColor('rgba(3, 3, 3)', DARK_COLOR_KEY, alpha),
'important',
);
document.documentElement.style.setProperty(
'--ytmusic-background',
// #030303
this.getMixedColor('rgba(3, 3, 3)', DARK_COLOR_KEY, alpha),
'important',
);
}, },
}, },
}); });

View File

@ -81,14 +81,3 @@ ytmusic-browse-response[has-background]:not([disable-gradient]) .background-grad
#background.immersive-background.style-scope.ytmusic-browse-response { #background.immersive-background.style-scope.ytmusic-browse-response {
opacity: 0.6; opacity: 0.6;
} }
ytmusic-search-box[is-bauhaus-sidenav-enabled] {
--ytmusic-search-background: var(--ytmusic-color-black3) !important;
}
.seekbar-theme #progress-bar.ytmusic-player-bar {
--paper-slider-active-color: linear-gradient(to right, var(--paper-progress-active-color-1) 80%, var(--paper-progress-active-color-2) 100%) !important;
--paper-slider-knob-color: var(--paper-progress-active-color-1) !important;
--paper-slider-knob-start-color: var(--paper-progress-active-color-2) !important;
}

View File

@ -8,7 +8,6 @@ import {
UniversalCache, UniversalCache,
Utils, Utils,
YTNodes, YTNodes,
Platform,
} from '\u0079\u006f\u0075\u0074\u0075\u0062\u0065i.js'; } from '\u0079\u006f\u0075\u0074\u0075\u0062\u0065i.js';
import is from 'electron-is'; import is from 'electron-is';
import filenamify from 'filenamify'; import filenamify from 'filenamify';
@ -58,22 +57,6 @@ const ffmpeg = lazy(async () =>
); );
const ffmpegMutex = new Mutex(); const ffmpegMutex = new Mutex();
Platform.shim.eval = async (data: Types.BuildScriptResult, env: Record<string, Types.VMPrimative>) => {
const properties = [];
if(env.n) {
properties.push(`n: exportedVars.nFunction("${env.n}")`)
}
if (env.sig) {
properties.push(`sig: exportedVars.sigFunction("${env.sig}")`)
}
const code = `${data.output}\nreturn { ${properties.join(', ')} }`;
return new Function(code)();
}
let yt: Innertube; let yt: Innertube;
let win: BrowserWindow; let win: BrowserWindow;
let playingUrl: string; let playingUrl: string;
@ -148,6 +131,7 @@ export const onMainLoad = async ({
yt = await Innertube.create({ yt = await Innertube.create({
cache: new UniversalCache(false), cache: new UniversalCache(false),
player_id: '0004de42',
cookie: await getCookieFromWindow(win), cookie: await getCookieFromWindow(win),
generate_session_locally: true, generate_session_locally: true,
fetch: getNetFetchAsFetch(), fetch: getNetFetchAsFetch(),

View File

@ -77,11 +77,10 @@ export class LRCLib implements LyricProvider {
} }
const filteredResults = []; const filteredResults = [];
const artists = artist.split(/[&,]/g).map((i) => i.trim());
for (const item of data) { for (const item of data) {
const { artistName } = item; const { artistName } = item;
const artists = artist.split(/[&,]/g).map((i) => i.trim());
const itemArtists = artistName.split(/[&,]/g).map((i) => i.trim()); const itemArtists = artistName.split(/[&,]/g).map((i) => i.trim());
// Try to match using artist name first // Try to match using artist name first

View File

@ -1,340 +0,0 @@
// Code adapted from https://greasyfork.org/en/scripts/548724-youtube-music-spotify-%E7%BD%91%E6%98%93%E4%BA%91%E6%AD%8C%E8%AF%8D%E6%98%BE%E7%A4%BA
// which is licenced under the MIT licence
import CryptoJS from 'crypto-js';
import { jaroWinkler } from '@skyra/jaro-winkler';
import { z } from 'zod';
import { LRC } from '../parsers/lrc';
import type { LyricProvider, LyricResult, SearchSongInfo } from '../types';
const EAPI_AES_KEY = 'e82ckenh8dichen8';
const EAPI_ENCODE_KEY = '3go8&$8*3*3h0k(2)2';
const EAPI_CHECK_TOKEN =
'9ca17ae2e6ffcda170e2e6ee8ad85dba908ca4d74da9ac8ea2d44e938f9eadc66da5a8979af572a5a9b68ac12af0feaec3b92aa69af9b1d372f6b8adccb35e968b9bb6c14f908d0099fb6ff48efdacd361f5b6ee9e';
const EAPI_BASE_HEADERS = {
'User-Agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) NeteaseMusicDesktop/3.0.14.2534',
};
const EAPI_BASE_COOKIES = {
os: 'osx',
appver: '3.0.14',
requestId: 0,
osver: '15.6.1',
};
const artistSchema = z.object({ id: z.number(), name: z.string() });
const songSchema = z.object({
resourceId: z.coerce.number(),
baseInfo: z.object({
simpleSongData: z.object({
name: z.string(),
ar: z.array(artistSchema).optional(),
dt: z.number(),
}),
}),
});
const searchResponseDataSchema = z.object({
resources: z.array(songSchema).default([]),
});
const searchResponseSchema = z.object({
code: z.number(),
message: z.string(),
data: searchResponseDataSchema,
});
type Song = z.infer<typeof songSchema>;
const lyricPartSchema = z.object({ lyric: z.string().nullable() });
const lyricResponseSchema = z.object({
lrc: lyricPartSchema.optional(),
tlyric: lyricPartSchema.optional(),
romalrc: lyricPartSchema.optional(),
});
export class Netease implements LyricProvider {
name = 'Netease';
baseUrl = 'https://interface.music.163.com';
cookies: Record<string, string> = {};
initialized = false;
private encode(id: string): string {
// XOR step (unchanged)
let xoredString = '';
for (let i = 0; i < id.length; i++) {
const charCode =
id.charCodeAt(i) ^
EAPI_ENCODE_KEY.charCodeAt(i % EAPI_ENCODE_KEY.length);
xoredString += String.fromCharCode(charCode);
}
// MD5 -> Base64 using crypto-js
const hash = CryptoJS.MD5(CryptoJS.enc.Latin1.parse(xoredString)).toString(
CryptoJS.enc.Base64,
);
// Build a binary WordArray for "id hash"
const combinedWordArray = CryptoJS.enc.Latin1.parse(id + ' ' + hash);
// Convert to Base64 (replaces Buffer.from(...).toString("base64"))
return CryptoJS.enc.Base64.stringify(combinedWordArray);
}
private async register() {
const deviceId = '7B79802670C7A45DB9091976D71E0AE829E28926C6C34A1B8644';
const username = this.encode(deviceId);
try {
await this.eapi('/register/anonimous', { username }, { _nmclfl: '1' });
this.initialized = true;
} catch (e) {
throw new Error(`Registration failed: ${e}`);
}
}
private async eapi(
path: string,
data: Record<string, unknown> = {},
params: Record<string, string> = {},
) {
const header = { ...EAPI_BASE_COOKIES };
const bodyData = { ...data, header: JSON.stringify(header) };
const body = JSON.stringify(bodyData);
const sign = CryptoJS.MD5(
`nobody/api${path}use${body}md5forencrypt`,
).toString();
const payload = `/api${path}-36cd479b6b5-${body}-36cd479b6b5-${sign}`;
const key = CryptoJS.enc.Utf8.parse(EAPI_AES_KEY);
const encrypted = CryptoJS.AES.encrypt(payload, key, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
}).ciphertext.toString(CryptoJS.enc.Hex);
const cookieString = Object.entries({ ...this.cookies })
.map(([k, v]) => `${k}=${v}`)
.join('; ');
const queryStr = new URLSearchParams(params).toString();
const url = `${this.baseUrl}/eapi${path}${queryStr ? `?${queryStr}` : ''}`;
const response = await fetch(url, {
method: 'POST',
headers: {
...EAPI_BASE_HEADERS,
'Content-Type': 'application/x-www-form-urlencoded',
'Cookie': cookieString,
},
body: `params=${encodeURIComponent(encrypted.toUpperCase())}`,
});
const setCookieHeader = response.headers.get('set-cookie');
if (setCookieHeader) {
const cookieStrings = setCookieHeader.split(/,(?=\s*[^=;\s]+=)/);
for (const cookieStr of cookieStrings) {
const parts = cookieStr.split(';')[0].split('=');
if (parts.length === 2) {
this.cookies[parts[0].trim()] = parts[1].trim();
}
}
}
if (!response.ok) {
throw new Error(`bad HTTPStatus(${response.statusText})`);
}
const json = await response.json();
z.object({ code: z.literal(200) }).parse(json);
return json;
}
private async searchSongs(keyword: string, limit = 10): Promise<Song[]> {
const response = await this.eapi(
'/search/song/list/page',
{
offset: '0',
scene: 'NORMAL',
needCorrect: 'true',
checkToken: EAPI_CHECK_TOKEN,
keyword,
limit: limit.toString(),
verifyId: 1,
},
{
_nmclfl: '1',
},
);
const parsed = searchResponseSchema.parse(response);
return parsed.data?.resources || [];
}
private async getLyric(id: number) {
const response = await this.eapi(
'/song/lyric/v1',
{
id,
tv: '-1',
yv: '-1',
rv: '-1',
lv: '-1',
verifyId: 1,
},
{
_nmclfl: '1',
},
);
return lyricResponseSchema.parse(response);
}
private splitTitle(title: string): string[] {
const masterPattern =
/(?:[「『](?<content>.+?)[」』])|(?:【.*?】|〖.*?〗|\(.*?\)|.*?)|(?<delimiter>\s+-\s+|\s*[/|:|│]\s*)/i;
const noiseWords = /\b(MV|PV)\b|\b(?:covered by|feat?|ft?)\b.+/gi;
const parse = (str: string): string[] => {
if (!str?.trim()) return [];
const match = str.match(masterPattern);
if (!match || match.index === undefined) return [str];
const before = str.substring(0, match.index);
const after = str.substring(match.index + match[0].length);
const { delimiter, content } = match.groups || {};
if (delimiter && (before.trim().length < 2 || after.trim().length < 2)) {
const remaining = parse(after);
return [
before + match[0] + (remaining[0] || ''),
...remaining.slice(1),
];
}
return [...parse(before), ...(content ? [content] : []), ...parse(after)];
};
return [
...new Set(
parse(title)
.map((p) => p.replace(noiseWords, '').trim())
.filter((p) => p.length > 0),
),
];
}
async search({
title,
artist,
songDuration,
}: SearchSongInfo): Promise<LyricResult | null> {
if (!this.initialized) {
await this.register();
}
const parts = this.splitTitle(title);
if (parts.length === 0) {
parts.push(title);
}
const keywords = [...parts];
if (parts[0] !== artist) keywords.push(`${parts[0]} ${artist}`);
const results = await Promise.all(
keywords.map((kw) => this.searchSongs(kw, 10)),
);
const calcTitleScore = (searchTitle: string) => {
let avgScore = 0;
parts.forEach((part, idx) => {
let weight = 1 / (idx * 2 + 1); // Earlier parts have higher weight
if (searchTitle.startsWith(part)) weight *= 2;
// Bonus for prefix match
else if (searchTitle.includes(part)) weight *= 1.5; // Bonus for substring match
avgScore += (jaroWinkler(part, searchTitle) * weight) / parts.length;
});
const score = Math.max(jaroWinkler(title, searchTitle), avgScore);
return score;
};
const artists = artist.split(/[&,]/g).map((i) => i.trim());
const filteredResults = [];
for (const result of results.flat()) {
const {
baseInfo: {
simpleSongData: { name, ar: itemArtists },
},
} = result;
const permutations = [];
for (const artistA of artists) {
for (const artistB of itemArtists ?? []) {
permutations.push([
artistA.toLowerCase(),
artistB.name.toLowerCase(),
]);
}
}
for (const artistA of itemArtists ?? []) {
for (const artistB of artists) {
permutations.push([
artistA.name.toLowerCase(),
artistB.toLowerCase(),
]);
}
}
const ratio =
calcTitleScore(name) +
Math.max(...permutations.map(([x, y]) => jaroWinkler(x, y)));
if (ratio < 1.8) continue;
filteredResults.push(result);
}
const closestResult = filteredResults[0];
if (!closestResult) {
return null;
}
if (
Math.abs(closestResult.baseInfo.simpleSongData.dt / 1000 - songDuration) >
15
) {
return null;
}
const lyric = await this.getLyric(closestResult.resourceId);
if (!lyric || !lyric.lrc?.lyric) return null;
const lyrics = stripMetadata(lyric.lrc.lyric);
const lines = LRC.parse(lyrics).lines.map((l) => ({
...l,
status: 'upcoming' as const,
}));
if (lines.length === 0 && !lyrics.trim()) return null;
return {
title: closestResult.baseInfo.simpleSongData.name,
artists:
closestResult.baseInfo.simpleSongData.ar?.map((a) => a.name) ?? [],
lines,
lyrics: lyrics,
};
}
}
const stripMetadata = (lyrics: string) => {
return lyrics
.split('\n')
.filter((line) => {
if (!line.includes('{')) return true;
try {
JSON.parse(line);
return false;
} catch {}
return true;
})
.join('\n');
};

View File

@ -7,7 +7,6 @@ export enum ProviderNames {
LRCLib = 'LRCLib', LRCLib = 'LRCLib',
MusixMatch = 'MusixMatch', MusixMatch = 'MusixMatch',
LyricsGenius = 'LyricsGenius', LyricsGenius = 'LyricsGenius',
NetEase = 'NetEase',
// Megalobiz = 'Megalobiz', // Megalobiz = 'Megalobiz',
} }

View File

@ -3,13 +3,11 @@ import { YTMusic } from './YTMusic';
import { LRCLib } from './LRCLib'; import { LRCLib } from './LRCLib';
import { MusixMatch } from './MusixMatch'; import { MusixMatch } from './MusixMatch';
import { LyricsGenius } from './LyricsGenius'; import { LyricsGenius } from './LyricsGenius';
import { Netease } from './NetEase';
export const providers = { export const providers = {
[ProviderNames.YTMusic]: new YTMusic(), [ProviderNames.YTMusic]: new YTMusic(),
[ProviderNames.LRCLib]: new LRCLib(), [ProviderNames.LRCLib]: new LRCLib(),
[ProviderNames.MusixMatch]: new MusixMatch(), [ProviderNames.MusixMatch]: new MusixMatch(),
[ProviderNames.LyricsGenius]: new LyricsGenius(), [ProviderNames.LyricsGenius]: new LyricsGenius(),
[ProviderNames.NetEase]: new Netease(),
// [ProviderNames.Megalobiz]: new Megalobiz(), // Disabled because it is too unstable and slow // [ProviderNames.Megalobiz]: new Megalobiz(), // Disabled because it is too unstable and slow
} as const; } as const;