diff --git a/.npmrc b/.npmrc index 21278c0263..cb6ce7b20e 100644 --- a/.npmrc +++ b/.npmrc @@ -1,2 +1,2 @@ engine-strict=true -scripts-prepend-node-path=true +scripts-prepend-node-path=true \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index ab74706da8..0000000000 --- a/README.md +++ /dev/null @@ -1,291 +0,0 @@ -
- -# :pear: Pear Desktop - -[![GitHub release](https://img.shields.io/github/release/pear-devs/pear-desktop.svg?style=for-the-badge)](https://github.com/pear-devs/pear-desktop/releases/) -[![GitHub license](https://img.shields.io/github/license/pear-devs/pear-desktop.svg?style=for-the-badge)](https://github.com/pear-devs/pear-desktop/blob/master/license) -[![eslint code style](https://img.shields.io/badge/code_style-eslint-5ed9c7.svg?style=for-the-badge)](https://github.com/pear-devs/pear-desktop/blob/master/eslint.config.mjs) -[![Build status](https://img.shields.io/github/actions/workflow/status/pear-devs/pear-desktop/build.yml?branch=master&style=for-the-badge)](https://GitHub.com/pear-devs/pear-desktop/releases/) -[![GitHub All Releases](https://img.shields.io/github/downloads/pear-devs/pear-desktop/total?style=for-the-badge)](https://GitHub.com/pear-devs/pear-desktop/releases/) - -[![Known Vulnerabilities](https://snyk.io/test/github/pear-devs/pear-desktop/badge.svg)](https://snyk.io/test/github/pear-devs/pear-desktop) - -
- - - -- Native look & feel extension - -> [!IMPORTANT] -> ⚠️ Disclaimer -> -> **No Affiliation** -> -> This project, and its contributors, are not affiliated with, authorized by, endorsed by, or in any way officially connected with Google LLC, YouTube, or any of their subsidiaries or affiliates. **This is an independent, non-profit, and unofficial extension developed by a team of volunteers with the goal of providing a desktop experience.** -> -> **Trademarks** -> -> The names "Google" and "YouTube Music", as well as related names, marks, emblems, and images, are registered trademarks of their respective owners. Any use of these trademarks is for identification and reference purposes only and does not imply any association with the trademark holder. We have no intention of infringing upon these trademarks or causing harm to the trademark holders. -> -> **Limitation of Liability** -> -> This application (extension) is provided "AS IS", and you use it at your own risk. In no event shall the developers or contributors be liable for any claim, damages, or other liability, including any legal consequences, arising from, out of, or in connection with the software or the use or other dealings in the software. The responsibility for any and all outcomes of using this software rests entirely with the user. - -## Content - -- [Features](#features) -- [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) - -## Translation - -You can help with translation on [Hosted Weblate](https://bit.ly/48n5YF7). - - - translation status - translation status 2 - - -## Download - -You can check out the [latest release](https://github.com/pear-devs/pear-desktop/releases/latest) to quickly find the -latest version. - -### Arch Linux - -Install the [`pear-desktop`](https://aur.archlinux.org/packages/pear-desktop) 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 - -You can install the app using Homebrew (see the [cask definition](https://github.com/pear-devs/pear-desktop-homebrew)): - -```bash -brew install pear-devs/pear-desktop -``` - -If you install the app manually and get an error "is damaged and can’t be opened." when launching the app, run the following in the Terminal: - -```bash -/usr/bin/xattr -cr /Applications/Pear\ Desktop.app -``` - -### Windows - -You can use the [Scoop package manager](https://scoop.sh) to install the `pear-desktop` package from -the [`extras` bucket](https://github.com/ScoopInstaller/Extras). - -```bash -scoop bucket add extras -scoop install extras/pear-desktop -``` - -Alternately you can use [Winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/), Windows 11s -official CLI package manager to install the `pear-devs.pear-desktop` package. - -*Note: Microsoft Defender SmartScreen might block the installation since it is from an "unknown publisher". This is also -true for the manual installation when trying to run the executable(.exe) after a manual download here on github (same -file).* - -```bash -winget install pear-devs.pear-desktop -``` - -#### How to install without a network connection? (in Windows) - -- Download the `*.nsis.7z` file for _your device architecture_ in [release page](https://github.com/pear-devs/pear-desktop/releases/latest). - - `x64` for 64-bit Windows - - `ia32` for 32-bit Windows - - `arm64` for ARM64 Windows -- Download installer in release page. (`*-Setup.exe`) -- Place them in the **same directory**. -- Run the installer. - -## Themes - -You can load CSS files to change the look of the application (Options > Visual Tweaks > Themes). - -Some predefined themes are available in https://github.com/kerichdev/themes-for-ytmdesktop-player. - -## Dev - -```bash -git clone https://github.com/pear-devs/pear-desktop -cd pear-desktop -pnpm install --frozen-lockfile -pnpm dev -``` - -## Build your own plugins - -Using plugins, you can: - -- manipulate the app - the `BrowserWindow` from electron is passed to the plugin handler -- change the front by manipulating the HTML/CSS - -### Creating a plugin - -Create a folder in `src/plugins/YOUR-PLUGIN-NAME`: - -- `index.ts`: the main file of the plugin -```typescript -import style from './style.css?inline'; // import style as inline - -import { createPlugin } from '@/utils'; - -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 - 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(); - - // 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, context) { - // 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 - -- injecting custom CSS: create a `style.css` file in the same folder then: - -```typescript -// index.ts -import style from './style.css?inline'; // import style as inline - -import { createPlugin } from '@/utils'; - -export default createPlugin({ - name: 'Plugin Label', - restartNeeded: true, // if value is true, pear-desktop will show a restart dialog - config: { - enabled: false, - }, // your custom config - stylesheets: [style], // your custom style - renderer() {} // define renderer hook -}); -``` - -- If you want to change the HTML: - -```typescript -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() { - console.log('hello from renderer'); - } // define renderer hook -}); -``` - -- 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. Follow [this guide](https://pnpm.io/installation) to install `pnpm` -3. Run `pnpm install --frozen-lockfile` to install dependencies -4. Run `pnpm build:OS` - -- `pnpm dist:win` - Windows -- `pnpm dist:linux` - Linux (amd64) -- `pnpm dist:linux:deb-arm64` - Linux (arm64 for Debian) -- `pnpm dist:linux:rpm-arm64` - Linux (arm64 for Fedora) -- `pnpm dist:mac` - macOS (amd64) -- `pnpm dist:mac:arm64` - macOS (arm64) - -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 -pnpm test -``` - -Uses [Playwright](https://playwright.dev/) to test the app. - -## License - -MIT © [pear-devs](https://github.com/pear-devs/pear-desktop) - -## FAQ - -### Why apps menu isn't showing up? - -If `Hide Menu` option is on - you can show the menu with the alt key (or \` [backtick] if using -the in-app-menu plugin) diff --git a/assets/generated/icons/mac/icon.icns b/assets/generated/icons/mac/icon.icns new file mode 100644 index 0000000000..a0bbf946db Binary files /dev/null and b/assets/generated/icons/mac/icon.icns differ diff --git a/assets/generated/icons/png/1024x1024.png b/assets/generated/icons/png/1024x1024.png new file mode 100644 index 0000000000..871036c9c2 Binary files /dev/null and b/assets/generated/icons/png/1024x1024.png differ diff --git a/assets/generated/icons/png/128x128.png b/assets/generated/icons/png/128x128.png new file mode 100644 index 0000000000..ea4b116959 Binary files /dev/null and b/assets/generated/icons/png/128x128.png differ diff --git a/assets/generated/icons/png/16x16.png b/assets/generated/icons/png/16x16.png new file mode 100644 index 0000000000..5693f2ccf4 Binary files /dev/null and b/assets/generated/icons/png/16x16.png differ diff --git a/assets/generated/icons/png/24x24.png b/assets/generated/icons/png/24x24.png new file mode 100644 index 0000000000..a7512dd213 Binary files /dev/null and b/assets/generated/icons/png/24x24.png differ diff --git a/assets/generated/icons/png/256x256.png b/assets/generated/icons/png/256x256.png new file mode 100644 index 0000000000..522d8b1623 Binary files /dev/null and b/assets/generated/icons/png/256x256.png differ diff --git a/assets/generated/icons/png/32x32.png b/assets/generated/icons/png/32x32.png new file mode 100644 index 0000000000..e110cc4c30 Binary files /dev/null and b/assets/generated/icons/png/32x32.png differ diff --git a/assets/generated/icons/png/48x48.png b/assets/generated/icons/png/48x48.png new file mode 100644 index 0000000000..2117dd907a Binary files /dev/null and b/assets/generated/icons/png/48x48.png differ diff --git a/assets/generated/icons/png/512x512.png b/assets/generated/icons/png/512x512.png new file mode 100644 index 0000000000..031450fe43 Binary files /dev/null and b/assets/generated/icons/png/512x512.png differ diff --git a/assets/generated/icons/png/64x64.png b/assets/generated/icons/png/64x64.png new file mode 100644 index 0000000000..3719f1e732 Binary files /dev/null and b/assets/generated/icons/png/64x64.png differ diff --git a/assets/generated/icons/win/icon.ico b/assets/generated/icons/win/icon.ico new file mode 100644 index 0000000000..419230203c Binary files /dev/null and b/assets/generated/icons/win/icon.ico differ diff --git a/assets/youtube-music-tray-paused.png b/assets/youtube-music-tray-paused.png new file mode 100644 index 0000000000..70e10c079a Binary files /dev/null and b/assets/youtube-music-tray-paused.png differ diff --git a/assets/youtube-music-tray.png b/assets/youtube-music-tray.png new file mode 100644 index 0000000000..bd53e7dc72 Binary files /dev/null and b/assets/youtube-music-tray.png differ diff --git a/assets/youtube-music.png b/assets/youtube-music.png new file mode 100644 index 0000000000..28fcf8bb77 Binary files /dev/null and b/assets/youtube-music.png differ diff --git a/assets/youtube-music.svg b/assets/youtube-music.svg new file mode 100644 index 0000000000..ea40b33627 --- /dev/null +++ b/assets/youtube-music.svg @@ -0,0 +1,6 @@ + + + + + diff --git a/package.json b/package.json index b628989b61..b7ba64985b 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "dev:debug": "pnpm cross-env ELECTRON_ENABLE_LOGGING=1 pnpm dev", "clean": "pnpm del-cli dist && pnpm del-cli pack && pnpm 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:linux": "pnpm clean && pnpm build && pnpm electron-builder --linux deb --x64 -p never", "dist:linux:deb-arm64": "pnpm clean && pnpm build && pnpm electron-builder --linux deb:arm64 -p never", "dist:linux:rpm-arm64": "pnpm clean && pnpm build && pnpm electron-builder --linux rpm:arm64 -p never", "dist:mac": "pnpm clean && pnpm build && pnpm electron-builder --mac dmg:x64 -p never", @@ -64,19 +64,19 @@ }, "dependencies": { "@dehoist/romanize-thai": "1.0.0", - "@electron-toolkit/tsconfig": "1.0.1", + "@electron-toolkit/tsconfig": "2.0.0", "@electron/remote": "2.1.3", - "@ffmpeg.wasm/core-mt": "0.12.0", - "@ffmpeg.wasm/main": "0.12.0", + "@ffmpeg.wasm/core-mt": "0.13.2", + "@ffmpeg.wasm/main": "0.13.1", "@floating-ui/dom": "1.7.4", "@foobar404/wave": "2.0.5", - "@ghostery/adblocker-electron": "2.11.6", - "@ghostery/adblocker-electron-preload": "2.11.6", - "@hono/node-server": "1.19.1", + "@ghostery/adblocker-electron": "2.12.4", + "@ghostery/adblocker-electron-preload": "2.12.4", + "@hono/node-server": "1.19.5", "@hono/node-ws": "1.2.0", "@hono/swagger-ui": "0.5.2", - "@hono/zod-openapi": "1.1.0", - "@hono/zod-validator": "0.7.2", + "@hono/zod-openapi": "1.1.3", + "@hono/zod-validator": "0.7.4", "@jellybrick/dbus-next": "0.10.3", "@jellybrick/electron-better-web-request": "1.0.4", "@jellybrick/mpris-service": "2.1.5", @@ -88,28 +88,28 @@ "bgutils-js": "3.2.0", "butterchurn": "3.0.0-beta.5", "butterchurn-presets": "3.0.0-beta.4", - "color": "5.0.0", - "conf": "14.0.0", + "color": "5.0.2", + "conf": "15.0.2", "custom-electron-prompt": "1.5.8", "deepmerge-ts": "7.1.5", "delay": "6.0.0", "electron-debug": "4.1.0", "electron-is": "3.0.0", "electron-localshortcut": "3.2.1", - "electron-store": "10.1.0", + "electron-store": "11.0.2", "electron-unhandled": "5.0.0", "electron-updater": "6.6.2", - "es-hangul": "2.3.5", + "es-hangul": "2.3.8", "fast-average-color": "9.5.0", - "fast-equals": "5.2.2", + "fast-equals": "5.3.2", "fflate": "0.8.2", - "filenamify": "6.0.0", + "filenamify": "7.0.0", "hanja": "1.1.5", - "happy-dom": "18.0.1", - "hono": "4.9.6", + "happy-dom": "20.0.2", + "hono": "4.9.12", "howler": "2.2.4", "html-to-text": "9.0.5", - "i18next": "25.5.2", + "i18next": "25.6.0", "jimp": "1.6.0", "keyboardevent-from-electron-accelerator": "2.0.0", "keyboardevents-areequal": "0.2.2", @@ -118,10 +118,11 @@ "kuroshiro-analyzer-kuromoji": "1.1.0", "lazy-var": "2.2.2", "mdui": "2.1.4", + "node-abort-controller": "^3.1.1", "node-html-parser": "7.0.1", "node-id3": "0.2.9", "peerjs": "1.5.5", - "semver": "7.7.2", + "semver": "7.7.3", "serve": "14.2.5", "socks": "2.8.7", "solid-element": "1.9.1", @@ -131,18 +132,19 @@ "solid-transition-group": "0.3.0", "tiny-pinyin": "1.3.2", "tinyld": "1.3.4", - "virtua": "0.42.3", + "virtua": "0.45.3", "vudio": "2.1.1", "x11": "2.3.0", - "youtubei.js": "15.0.1", - "zod": "4.1.5" + "youtubei.js": "16.0.1", + "yt-dlp-exec": "^1.0.2", + "zod": "4.1.12" }, "devDependencies": { "@electron-toolkit/tsconfig": "1.0.1", - "@eslint/js": "9.35.0", + "@eslint/js": "9.37.0", "@malept/flatpak-bundler": "0.4.0", - "@playwright/test": "1.55.0", - "@stylistic/eslint-plugin": "5.3.1", + "@playwright/test": "1.56.0", + "@stylistic/eslint-plugin": "5.4.0", "@total-typescript/ts-reset": "0.6.1", "@types/electron-localshortcut": "3.1.3", "@types/howler": "2.2.12", @@ -151,15 +153,15 @@ "@types/trusted-types": "2.0.7", "bufferutil": "4.0.9", "builtin-modules": "5.0.0", - "cross-env": "10.0.0", - "del-cli": "6.0.0", - "discord-api-types": "0.38.23", - "electron": "38.0.0", + "cross-env": "10.1.0", + "del-cli": "7.0.0", + "discord-api-types": "0.38.30", + "electron": "38.3.0", "electron-builder": "26.0.12", "electron-builder-squirrel-windows": "26.0.12", "electron-devtools-installer": "4.0.0", - "electron-vite": "4.0.0", - "eslint": "9.35.0", + "electron-vite": "4.0.1", + "eslint": "9.37.0", "eslint-config-prettier": "10.1.8", "eslint-import-resolver-exports": "1.0.0-beta.5", "eslint-import-resolver-typescript": "4.4.4", @@ -168,15 +170,15 @@ "eslint-plugin-solid": "0.14.5", "glob": "11.0.3", "node-gyp": "11.4.2", - "playwright": "1.55.0", - "ts-morph": "27.0.0", - "typescript": "5.9.2", - "typescript-eslint": "8.43.0", + "playwright": "1.56.0", + "ts-morph": "27.0.2", + "typescript": "5.9.3", + "typescript-eslint": "8.46.1", "utf-8-validate": "6.0.5", "vite": "npm:rolldown-vite@7.1.8", "vite-plugin-inspect": "11.3.3", "vite-plugin-resolve": "2.5.2", - "vite-plugin-solid": "2.11.8", + "vite-plugin-solid": "2.11.9", "ws": "8.18.3" }, "auto-changelog": { @@ -185,4 +187,4 @@ "unreleased": true, "output": "changelog.md" } -} +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9b0d3db093..7768618cba 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -40,17 +40,17 @@ importers: specifier: 1.0.0 version: 1.0.0 '@electron-toolkit/tsconfig': - specifier: 1.0.1 - version: 1.0.1(@types/node@24.3.0) + specifier: 2.0.0 + version: 2.0.0(@types/node@24.8.0) '@electron/remote': specifier: 2.1.3 - version: 2.1.3(electron@38.0.0) + version: 2.1.3(electron@38.3.0) '@ffmpeg.wasm/core-mt': - specifier: 0.12.0 - version: 0.12.0 + specifier: 0.13.2 + version: 0.13.2 '@ffmpeg.wasm/main': - specifier: 0.12.0 - version: 0.12.0 + specifier: 0.13.1 + version: 0.13.1 '@floating-ui/dom': specifier: 1.7.4 version: 1.7.4 @@ -58,26 +58,26 @@ importers: specifier: 2.0.5 version: 2.0.5 '@ghostery/adblocker-electron': - specifier: 2.11.6 - version: 2.11.6(electron@38.0.0) + specifier: 2.12.4 + version: 2.12.4(electron@38.3.0) '@ghostery/adblocker-electron-preload': - specifier: 2.11.6 - version: 2.11.6(electron@38.0.0) + specifier: 2.12.4 + version: 2.12.4(electron@38.3.0) '@hono/node-server': - specifier: 1.19.1 - version: 1.19.1(hono@4.9.6) + specifier: 1.19.5 + version: 1.19.5(hono@4.9.12) '@hono/node-ws': specifier: 1.2.0 - version: 1.2.0(@hono/node-server@1.19.1(hono@4.9.6))(bufferutil@4.0.9)(hono@4.9.6)(utf-8-validate@6.0.5) + version: 1.2.0(@hono/node-server@1.19.5(hono@4.9.12))(bufferutil@4.0.9)(hono@4.9.12)(utf-8-validate@6.0.5) '@hono/swagger-ui': specifier: 0.5.2 - version: 0.5.2(hono@4.9.6) + version: 0.5.2(hono@4.9.12) '@hono/zod-openapi': - specifier: 1.1.0 - version: 1.1.0(hono@4.9.6)(zod@4.1.5) + specifier: 1.1.3 + version: 1.1.3(hono@4.9.12)(zod@4.1.12) '@hono/zod-validator': - specifier: 0.7.2 - version: 0.7.2(hono@4.9.6)(zod@4.1.5) + specifier: 0.7.4 + version: 0.7.4(hono@4.9.12)(zod@4.1.12) '@jellybrick/dbus-next': specifier: 0.10.3 version: 0.10.3 @@ -112,14 +112,14 @@ importers: specifier: 3.0.0-beta.4 version: 3.0.0-beta.4 color: - specifier: 5.0.0 - version: 5.0.0 + specifier: 5.0.2 + version: 5.0.2 conf: - specifier: 14.0.0 - version: 14.0.0 + specifier: 15.0.2 + version: 15.0.2 custom-electron-prompt: specifier: 1.5.8 - version: 1.5.8(electron@38.0.0) + version: 1.5.8(electron@38.3.0) deepmerge-ts: specifier: 7.1.5 version: 7.1.5 @@ -136,8 +136,8 @@ importers: specifier: 3.2.1 version: 3.2.1 electron-store: - specifier: 10.1.0 - version: 10.1.0 + specifier: 11.0.2 + version: 11.0.2 electron-unhandled: specifier: 5.0.0 version: 5.0.0 @@ -145,29 +145,29 @@ importers: specifier: 6.6.2 version: 6.6.2 es-hangul: - specifier: 2.3.5 - version: 2.3.5 + specifier: 2.3.8 + version: 2.3.8 fast-average-color: specifier: 9.5.0 version: 9.5.0 fast-equals: - specifier: 5.2.2 - version: 5.2.2 + specifier: 5.3.2 + version: 5.3.2 fflate: specifier: 0.8.2 version: 0.8.2 filenamify: - specifier: 6.0.0 - version: 6.0.0 + specifier: 7.0.0 + version: 7.0.0 hanja: specifier: 1.1.5 version: 1.1.5 happy-dom: - specifier: 18.0.1 - version: 18.0.1 + specifier: 20.0.2 + version: 20.0.2 hono: - specifier: 4.9.6 - version: 4.9.6 + specifier: 4.9.12 + version: 4.9.12 howler: specifier: 2.2.4 version: 2.2.4 @@ -175,8 +175,8 @@ importers: specifier: 9.0.5 version: 9.0.5 i18next: - specifier: 25.5.2 - version: 25.5.2(typescript@5.9.2) + specifier: 25.6.0 + version: 25.6.0(typescript@5.9.3) jimp: specifier: 1.6.0 version: 1.6.0 @@ -201,6 +201,9 @@ importers: mdui: specifier: 2.1.4 version: 2.1.4(patch_hash=008b925e23862ced06c38f9765eef4322eb8a93cb5e9a6b684fd717fec0ea0d9) + node-abort-controller: + specifier: ^3.1.1 + version: 3.1.1 node-html-parser: specifier: 7.0.1 version: 7.0.1 @@ -211,8 +214,8 @@ importers: specifier: 1.5.5 version: 1.5.5 semver: - specifier: 7.7.2 - version: 7.7.2 + specifier: 7.7.3 + version: 7.7.3 serve: specifier: 14.2.5 version: 14.2.5 @@ -241,8 +244,8 @@ importers: specifier: 1.3.4 version: 1.3.4 virtua: - specifier: 0.42.3 - version: 0.42.3(solid-js@1.9.9) + specifier: 0.45.3 + version: 0.45.3(solid-js@1.9.9) vudio: specifier: 2.1.1 version: 2.1.1(patch_hash=0e06c2ed11c02bdc490c209fa80070e98517c2735c641f5738b6e15d7dc1959d) @@ -250,24 +253,27 @@ importers: specifier: 2.3.0 version: 2.3.0 youtubei.js: - specifier: 15.0.1 - version: 15.0.1 + specifier: 16.0.1 + version: 16.0.1 + yt-dlp-exec: + specifier: ^1.0.2 + version: 1.0.2 zod: - specifier: 4.1.5 - version: 4.1.5 + specifier: 4.1.12 + version: 4.1.12 devDependencies: '@eslint/js': - specifier: 9.35.0 - version: 9.35.0 + specifier: 9.37.0 + version: 9.37.0 '@malept/flatpak-bundler': specifier: 0.4.0 version: 0.4.0(patch_hash=c787371eeb2af011ea934e8818a0dad6d7dcb2df31bbb1686babc7231af0183c) '@playwright/test': - specifier: 1.55.0 - version: 1.55.0 + specifier: 1.56.0 + version: 1.56.0 '@stylistic/eslint-plugin': - specifier: 5.3.1 - version: 5.3.1(eslint@9.35.0) + specifier: 5.4.0 + version: 5.4.0(eslint@9.37.0) '@total-typescript/ts-reset': specifier: 0.6.1 version: 0.6.1 @@ -293,17 +299,17 @@ importers: specifier: 5.0.0 version: 5.0.0 cross-env: - specifier: 10.0.0 - version: 10.0.0 + specifier: 10.1.0 + version: 10.1.0 del-cli: - specifier: 6.0.0 - version: 6.0.0 + specifier: 7.0.0 + version: 7.0.0 discord-api-types: - specifier: 0.38.23 - version: 0.38.23 + specifier: 0.38.30 + version: 0.38.30 electron: - specifier: 38.0.0 - version: 38.0.0 + specifier: 38.3.0 + version: 38.3.0 electron-builder: specifier: 26.0.12 version: 26.0.12(electron-builder-squirrel-windows@26.0.12) @@ -314,29 +320,29 @@ importers: specifier: 4.0.0 version: 4.0.0 electron-vite: - specifier: 4.0.0 - version: 4.0.0(rolldown-vite@7.1.8(@types/node@24.3.0)(esbuild@0.25.9)(yaml@2.8.1)) + specifier: 4.0.1 + version: 4.0.1(rolldown-vite@7.1.8(@types/node@24.8.0)(esbuild@0.25.11)(yaml@2.8.1)) eslint: - specifier: 9.35.0 - version: 9.35.0 + specifier: 9.37.0 + version: 9.37.0 eslint-config-prettier: specifier: 10.1.8 - version: 10.1.8(eslint@9.35.0) + version: 10.1.8(eslint@9.37.0) eslint-import-resolver-exports: specifier: 1.0.0-beta.5 - version: 1.0.0-beta.5(eslint-plugin-import@2.32.0)(eslint@9.35.0) + version: 1.0.0-beta.5(eslint-plugin-import@2.32.0)(eslint@9.37.0) eslint-import-resolver-typescript: specifier: 4.4.4 - version: 4.4.4(eslint-plugin-import@2.32.0)(eslint@9.35.0) + version: 4.4.4(eslint-plugin-import@2.32.0)(eslint@9.37.0) eslint-plugin-import: specifier: 2.32.0 - version: 2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0)(typescript@5.9.2))(eslint-import-resolver-typescript@4.4.4)(eslint@9.35.0) + version: 2.32.0(@typescript-eslint/parser@8.46.1(eslint@9.37.0)(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.37.0) eslint-plugin-prettier: specifier: 5.5.4 - version: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.35.0))(eslint@9.35.0)(prettier@3.6.2) + version: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.37.0))(eslint@9.37.0)(prettier@3.6.2) eslint-plugin-solid: specifier: 0.14.5 - version: 0.14.5(eslint@9.35.0)(typescript@5.9.2) + version: 0.14.5(eslint@9.37.0)(typescript@5.9.3) glob: specifier: 11.0.3 version: 11.0.3 @@ -344,32 +350,32 @@ importers: specifier: 11.4.2 version: 11.4.2 playwright: - specifier: 1.55.0 - version: 1.55.0 + specifier: 1.56.0 + version: 1.56.0 ts-morph: - specifier: 27.0.0 - version: 27.0.0 + specifier: 27.0.2 + version: 27.0.2 typescript: - specifier: 5.9.2 - version: 5.9.2 + specifier: 5.9.3 + version: 5.9.3 typescript-eslint: - specifier: 8.43.0 - version: 8.43.0(eslint@9.35.0)(typescript@5.9.2) + specifier: 8.46.1 + version: 8.46.1(eslint@9.37.0)(typescript@5.9.3) utf-8-validate: specifier: 6.0.5 version: 6.0.5 vite: specifier: npm:rolldown-vite@7.1.8 - version: rolldown-vite@7.1.8(@types/node@24.3.0)(esbuild@0.25.9)(yaml@2.8.1) + version: rolldown-vite@7.1.8(@types/node@24.8.0)(esbuild@0.25.11)(yaml@2.8.1) vite-plugin-inspect: specifier: 11.3.3 - version: 11.3.3(rolldown-vite@7.1.8(@types/node@24.3.0)(esbuild@0.25.9)(yaml@2.8.1)) + version: 11.3.3(rolldown-vite@7.1.8(@types/node@24.8.0)(esbuild@0.25.11)(yaml@2.8.1)) vite-plugin-resolve: specifier: 2.5.2 version: 2.5.2 vite-plugin-solid: - specifier: 2.11.8 - version: 2.11.8(rolldown-vite@7.1.8(@types/node@24.3.0)(esbuild@0.25.9)(yaml@2.8.1))(solid-js@1.9.9) + specifier: 2.11.9 + version: 2.11.9(rolldown-vite@7.1.8(@types/node@24.8.0)(esbuild@0.25.11)(yaml@2.8.1))(solid-js@1.9.9) ws: specifier: 8.18.3 version: 8.18.3(bufferutil@4.0.9)(utf-8-validate@6.0.5) @@ -379,10 +385,6 @@ packages: 7zip-bin@5.2.0: resolution: {integrity: sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==} - '@ampproject/remapping@2.3.0': - resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} - engines: {node: '>=6.0.0'} - '@assemblyscript/loader@0.17.14': resolution: {integrity: sha512-+PVTOfla/0XMLRTQLJFPg4u40XcdTfon6GGea70hBGi8Pd7ZymIXyVUR+vK8wt5Jb4MVKTKPIz43Myyebw5mZA==} @@ -395,12 +397,12 @@ packages: resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.28.0': - resolution: {integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==} + '@babel/compat-data@7.28.4': + resolution: {integrity: sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==} engines: {node: '>=6.9.0'} - '@babel/core@7.28.3': - resolution: {integrity: sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==} + '@babel/core@7.28.4': + resolution: {integrity: sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==} engines: {node: '>=6.9.0'} '@babel/generator@7.28.3': @@ -445,12 +447,12 @@ packages: resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.28.3': - resolution: {integrity: sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==} + '@babel/helpers@7.28.4': + resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} engines: {node: '>=6.9.0'} - '@babel/parser@7.28.3': - resolution: {integrity: sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==} + '@babel/parser@7.28.4': + resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==} engines: {node: '>=6.0.0'} hasBin: true @@ -474,16 +476,16 @@ packages: resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.28.3': - resolution: {integrity: sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==} + '@babel/traverse@7.28.4': + resolution: {integrity: sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==} engines: {node: '>=6.9.0'} - '@babel/types@7.28.2': - resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} + '@babel/types@7.28.4': + resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} engines: {node: '>=6.9.0'} - '@bufbuild/protobuf@2.6.3': - resolution: {integrity: sha512-w/gJKME9mYN7ZoUAmSMAWXk4hkVpxRKvEJCb3dV5g9wwWdxTJJ0ayOJAVcNxtdqaxDyFuC0uz4RSGVacJ030PQ==} + '@bufbuild/protobuf@2.9.0': + resolution: {integrity: sha512-rnJenoStJ8nvmt9Gzye8nkYd6V22xUAnu4086ER7h1zJ508vStko4pMvDeQ446ilDTFpV5wnoc5YS7XvMwwMqA==} '@dehoist/romanize-thai@1.0.0': resolution: {integrity: sha512-6SqD4vyZ48otnypLXMh901CeQetoP5ptYOaIr58N6zDqjjoN0bHszMb5d/6AXJJQf8kIvbmSWBeuDrbAWLajPQ==} @@ -496,16 +498,16 @@ packages: resolution: {integrity: sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==} engines: {node: '>=18'} - '@discordjs/rest@2.5.1': - resolution: {integrity: sha512-Tg9840IneBcbrAjcGaQzHUJWFNq1MMWZjTdjJ0WS/89IffaNKc++iOvffucPxQTF/gviO9+9r8kEPea1X5J2Dw==} + '@discordjs/rest@2.6.0': + resolution: {integrity: sha512-RDYrhmpB7mTvmCKcpj+pc5k7POKszS4E2O9TYc+U+Y4iaCP+r910QdO43qmpOja8LRr1RJ0b3U+CqVsnPqzf4w==} engines: {node: '>=18'} '@discordjs/util@1.1.1': resolution: {integrity: sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g==} engines: {node: '>=18'} - '@electron-toolkit/tsconfig@1.0.1': - resolution: {integrity: sha512-M0Mol3odspvtCuheyujLNAW7bXq7KFNYVMRtpjFa4ZfES4MuklXBC7Nli/omvc+PRKlrklgAGx3l4VakjNo8jg==} + '@electron-toolkit/tsconfig@2.0.0': + resolution: {integrity: sha512-AdPsP770WhW7b260h13SHMdmjEEHJL6xFtgi3jwgdsSQbJOkJLeNnnpZW9qxTPCvmRI6vmdzWz5K3gibFS6SNg==} peerDependencies: '@types/node': '*' @@ -566,176 +568,176 @@ packages: engines: {node: '>=14.14'} hasBin: true - '@emnapi/core@1.4.5': - resolution: {integrity: sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==} + '@emnapi/core@1.5.0': + resolution: {integrity: sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==} - '@emnapi/runtime@1.4.5': - resolution: {integrity: sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==} + '@emnapi/runtime@1.5.0': + resolution: {integrity: sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==} - '@emnapi/wasi-threads@1.0.4': - resolution: {integrity: sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==} + '@emnapi/wasi-threads@1.1.0': + resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} '@epic-web/invariant@1.0.0': resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==} - '@esbuild/aix-ppc64@0.25.9': - resolution: {integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==} + '@esbuild/aix-ppc64@0.25.11': + resolution: {integrity: sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.25.9': - resolution: {integrity: sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==} + '@esbuild/android-arm64@0.25.11': + resolution: {integrity: sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.25.9': - resolution: {integrity: sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==} + '@esbuild/android-arm@0.25.11': + resolution: {integrity: sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.25.9': - resolution: {integrity: sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==} + '@esbuild/android-x64@0.25.11': + resolution: {integrity: sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.25.9': - resolution: {integrity: sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==} + '@esbuild/darwin-arm64@0.25.11': + resolution: {integrity: sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.25.9': - resolution: {integrity: sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==} + '@esbuild/darwin-x64@0.25.11': + resolution: {integrity: sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.25.9': - resolution: {integrity: sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==} + '@esbuild/freebsd-arm64@0.25.11': + resolution: {integrity: sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.9': - resolution: {integrity: sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==} + '@esbuild/freebsd-x64@0.25.11': + resolution: {integrity: sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.25.9': - resolution: {integrity: sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==} + '@esbuild/linux-arm64@0.25.11': + resolution: {integrity: sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.25.9': - resolution: {integrity: sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==} + '@esbuild/linux-arm@0.25.11': + resolution: {integrity: sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.25.9': - resolution: {integrity: sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==} + '@esbuild/linux-ia32@0.25.11': + resolution: {integrity: sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.25.9': - resolution: {integrity: sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==} + '@esbuild/linux-loong64@0.25.11': + resolution: {integrity: sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.25.9': - resolution: {integrity: sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==} + '@esbuild/linux-mips64el@0.25.11': + resolution: {integrity: sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.25.9': - resolution: {integrity: sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==} + '@esbuild/linux-ppc64@0.25.11': + resolution: {integrity: sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.25.9': - resolution: {integrity: sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==} + '@esbuild/linux-riscv64@0.25.11': + resolution: {integrity: sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.25.9': - resolution: {integrity: sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==} + '@esbuild/linux-s390x@0.25.11': + resolution: {integrity: sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.25.9': - resolution: {integrity: sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==} + '@esbuild/linux-x64@0.25.11': + resolution: {integrity: sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.25.9': - resolution: {integrity: sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==} + '@esbuild/netbsd-arm64@0.25.11': + resolution: {integrity: sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.9': - resolution: {integrity: sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==} + '@esbuild/netbsd-x64@0.25.11': + resolution: {integrity: sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.25.9': - resolution: {integrity: sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==} + '@esbuild/openbsd-arm64@0.25.11': + resolution: {integrity: sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.9': - resolution: {integrity: sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==} + '@esbuild/openbsd-x64@0.25.11': + resolution: {integrity: sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.25.9': - resolution: {integrity: sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==} + '@esbuild/openharmony-arm64@0.25.11': + resolution: {integrity: sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.25.9': - resolution: {integrity: sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==} + '@esbuild/sunos-x64@0.25.11': + resolution: {integrity: sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.25.9': - resolution: {integrity: sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==} + '@esbuild/win32-arm64@0.25.11': + resolution: {integrity: sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.25.9': - resolution: {integrity: sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==} + '@esbuild/win32-ia32@0.25.11': + resolution: {integrity: sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.25.9': - resolution: {integrity: sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==} + '@esbuild/win32-x64@0.25.11': + resolution: {integrity: sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==} engines: {node: '>=18'} cpu: [x64] os: [win32] - '@eslint-community/eslint-utils@4.8.0': - resolution: {integrity: sha512-MJQFqrZgcW0UNYLGOuQpey/oTN59vyWwplvCGZztn1cKz9agZPPYpJB7h2OMmuu7VLqkvEjN8feFZJmxNF9D+Q==} + '@eslint-community/eslint-utils@4.9.0': + resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 @@ -748,36 +750,36 @@ packages: resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/config-helpers@0.3.1': - resolution: {integrity: sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==} + '@eslint/config-helpers@0.4.0': + resolution: {integrity: sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/core@0.15.2': - resolution: {integrity: sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==} + '@eslint/core@0.16.0': + resolution: {integrity: sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/eslintrc@3.3.1': resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.35.0': - resolution: {integrity: sha512-30iXE9whjlILfWobBkNerJo+TXYsgVM5ERQwMcMKCHckHflCmf7wXDAHlARoWnh0s1U72WqlbeyE7iAcCzuCPw==} + '@eslint/js@9.37.0': + resolution: {integrity: sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/object-schema@2.1.6': resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.3.5': - resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==} + '@eslint/plugin-kit@0.4.0': + resolution: {integrity: sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@ffmpeg.wasm/core-mt@0.12.0': - resolution: {integrity: sha512-M9pjL7JQX4AYl3WI8vGcPGPTz/O7JmhW8ac/fHA3oXTxoRAPwYSY/OsY1N9C0XahIM0+fxa1QSLN9Ekz8sBM/Q==} + '@ffmpeg.wasm/core-mt@0.13.2': + resolution: {integrity: sha512-3UgpMoOhpTslxluDvBrh0MULeNy55W+WWG00P+JnnCLpJulQxgi6mnIuGO03d2sfV5pyW/FqAwut+fiMLbRsPg==} - '@ffmpeg.wasm/main@0.12.0': - resolution: {integrity: sha512-LILAKTrU3Rga2iXLsF9jeFxe2hNQFjWlrKuXPWSdCFeQ7Kg69fO4WwjNJ0CzjOyO6qtndRQMNKqf//N4fLYUBA==} - engines: {node: '>=12.16.1'} + '@ffmpeg.wasm/main@0.13.1': + resolution: {integrity: sha512-WoEd9xp/N9VWddZ3y1cdRK/el52ZKLoqOS+BNQZcsbLQpkQuHrFG93+zY4VjMZ0aWno4pQG4TSgLzsexpukpHw==} + engines: {node: '>=14.0.0'} '@floating-ui/core@1.7.3': resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} @@ -794,30 +796,30 @@ packages: '@gar/promisify@1.1.3': resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} - '@ghostery/adblocker-content@2.11.6': - resolution: {integrity: sha512-K+hJR27fYpRGoUQnza2p0BmN86htN1ASoRxF73mAdf1CNDSyfPNCNlpoKt83Z9CbYBdEglXOWItazB9hRMs7gw==} + '@ghostery/adblocker-content@2.12.4': + resolution: {integrity: sha512-JACPKvhfioE0jhzvoViljWu3eiFZ0oP3F3IKDOqIRuHywTa63W3ZXEiX4crKKPiLNp1kPsuFV7YRv5l2GJxNDg==} - '@ghostery/adblocker-electron-preload@2.11.6': - resolution: {integrity: sha512-WU6vbixwgBtoc6Sx2jn/X5pw9vj3IXoYPhuE/Rm+TrD2nO/ywAOiNV0XkQp3hIHxpJ4GXv/VDJM3Cy6cX41Nhg==} + '@ghostery/adblocker-electron-preload@2.12.4': + resolution: {integrity: sha512-TOufxWRe/h78Wx7KDlWPnscWCHVNNtq6pnSoclV9CkG0pnN+1uLh+DAeLpyYp5fBZgxjjZD0gXUB/plJYB3Nfg==} peerDependencies: electron: '>11' - '@ghostery/adblocker-electron@2.11.6': - resolution: {integrity: sha512-4px+3460q4mE+g1bTCxd5Upcfkp/fp0Cpl5FM0j/0u9L2f4KXsk6Djv1S5xMh928m1Li1XWNki3A2aM0E4TDNQ==} + '@ghostery/adblocker-electron@2.12.4': + resolution: {integrity: sha512-yndYBenpd1cYj9snIbTZPaXrOUaDxY42GqEbUFPgHg17BS19JbIXfiTGrg0gVPRh8BzbVC5WQXTfHlNIowQwsw==} peerDependencies: electron: '>11' - '@ghostery/adblocker-extended-selectors@2.11.6': - resolution: {integrity: sha512-jFKNwwWYYxMHaIqGCLO/sR8oRBcGcVD3bCLPZ9wLBoTMTtCnwdXRlMxWwnNi7Bw5PSw7aw8w6hcEfHdcbdU+Qw==} + '@ghostery/adblocker-extended-selectors@2.12.4': + resolution: {integrity: sha512-t6AQiLgXjZBeXRlMX59RataQWGeghaYB4u7EGIrFrhl6DkJjwneWLirXfeyQVVAai5R2TZ6Br0UCA4P/i3UNDA==} - '@ghostery/adblocker@2.11.6': - resolution: {integrity: sha512-fUkLvWDObCMjjwUHw0/jt3N11wT7FBWJVFUXsUN4Do7J3Henp63JZQw1RYv2Y15NT0idVERR9IhbjJdd3/w48w==} + '@ghostery/adblocker@2.12.4': + resolution: {integrity: sha512-lUrMCeReX72IQ+xK6tAcROAVDn+OknW/kTokMcII9TJf6axUzpjM+v0shBYbsMghliADFz8P/dgArC2oCix9Eg==} '@ghostery/url-parser@1.3.0': resolution: {integrity: sha512-FEzdSeiva0Mt3bR4xePFzthhjT4IzvA5QTvS1xXkNyLpMGeq40mb3V2fSs0ZItRaP9IybZthDfHUSbQ1HLdx4Q==} - '@hono/node-server@1.19.1': - resolution: {integrity: sha512-h44e5s+ByUriaRIbeS/C74O8v90m0A95luyYQGMF7KEn96KkYMXO7bZAwombzTpjQTU4e0TkU8U1WBIXlwuwtA==} + '@hono/node-server@1.19.5': + resolution: {integrity: sha512-iBuhh+uaaggeAuf+TftcjZyWh2GEgZcVGXkNtskLVoWaXhnJtC5HLHrU8W1KHDoucqO1MswwglmkWLFyiDn4WQ==} engines: {node: '>=18.14.1'} peerDependencies: hono: ^4 @@ -834,15 +836,15 @@ packages: peerDependencies: hono: '*' - '@hono/zod-openapi@1.1.0': - resolution: {integrity: sha512-S4jVR+A/jI4MA/RKJqmpjdHAN2l/EsqLnKHBv68x3WxV1NGVe3Sh7f6LV6rHEGYNHfiqpD75664A/erc+r9dQA==} + '@hono/zod-openapi@1.1.3': + resolution: {integrity: sha512-ikA8p0Jt7yplxOqbYwdh8rCQWaGN4bu8zK1HbCWqfWT9clo87L32D0eAQ/r0tJodtZbTV5d1vPB75FCkUt1Jdg==} engines: {node: '>=16.0.0'} peerDependencies: hono: '>=4.3.6' zod: ^4.0.0 - '@hono/zod-validator@0.7.2': - resolution: {integrity: sha512-ub5eL/NeZ4eLZawu78JpW/J+dugDAYhwqUIdp9KYScI6PZECij4Hx4UsrthlEUutqDDhPwRI0MscUfNkvn/mqQ==} + '@hono/zod-validator@0.7.4': + resolution: {integrity: sha512-biKGn3BRJVaftZlIPMyK+HCe/UHAjJ6sH0UyXe3+v0OcgVr9xfImDROTJFLtn9e3XEEAHGZIM9U6evu85abm8Q==} peerDependencies: hono: '>=3.9.0' zod: ^3.25.0 || ^4.0.0 @@ -851,18 +853,14 @@ packages: resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} - '@humanfs/node@0.16.6': - resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} engines: {node: '>=18.18.0'} '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} - '@humanwhocodes/retry@0.3.1': - resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} - engines: {node: '>=18.18'} - '@humanwhocodes/retry@0.4.3': resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} @@ -1007,6 +1005,9 @@ packages: '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} @@ -1014,8 +1015,8 @@ packages: '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - '@jridgewell/trace-mapping@0.3.30': - resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==} + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} '@lit-labs/ssr-dom-shim@1.4.0': resolution: {integrity: sha512-ficsEARKnmmW5njugNYKipTm4SFnbik7CXtoencDZzmzo/dQ+2Q0bgkzJuoJP20Aj0F+izzJjOqsnkd6F/o1bw==} @@ -1053,8 +1054,8 @@ packages: '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} - '@napi-rs/wasm-runtime@1.0.3': - resolution: {integrity: sha512-rZxtMsLwjdXkMUGC3WwsPwLNVqVqnTJT6MNIB6e+5fhMcSCPP0AOsNWuMQ5mdCq6HNjs/ZeWAEchpqeprqBD2Q==} + '@napi-rs/wasm-runtime@1.0.7': + resolution: {integrity: sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==} '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} @@ -1104,8 +1105,8 @@ packages: resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - '@playwright/test@1.55.0': - resolution: {integrity: sha512-04IXzPwHrW69XusN/SIdDdKZBzMfOT9UNT/YiJit/xpy2VuAoB8NHc8Aplb96zsWDddLnbkPL3TsmrS04ZU2xQ==} + '@playwright/test@1.56.0': + resolution: {integrity: sha512-Tzh95Twig7hUwwNe381/K3PggZBZblKUe2wv25oIpzWLr6Z0m4KgV1ZVIjnR6GM9ANEqjZD7XsZEa6JL/7YEgg==} engines: {node: '>=18'} hasBin: true @@ -1257,8 +1258,8 @@ packages: peerDependencies: solid-js: ^1.6.12 - '@stylistic/eslint-plugin@5.3.1': - resolution: {integrity: sha512-Ykums1VYonM0TgkD0VteVq9mrlO2FhF48MDJnPyv3MktIB2ydtuhlO0AfWm7xnW1kyf5bjOqA6xc7JjviuVTxg==} + '@stylistic/eslint-plugin@5.4.0': + resolution: {integrity: sha512-UG8hdElzuBDzIbjG1QDwnYH0MQ73YLXDFHgZzB4Zh/YJfnw8XNsloVtytqzx0I2Qky9THSdpTmi8Vjn/pf/Lew==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: '>=9.0.0' @@ -1277,11 +1278,11 @@ packages: '@total-typescript/ts-reset@0.6.1': resolution: {integrity: sha512-cka47fVSo6lfQDIATYqb/vO1nvFfbPw7uWLayIXIhGETj0wcOOlrlkobOMDNQOFr9QOafegUPq13V2+6vtD7yg==} - '@ts-morph/common@0.28.0': - resolution: {integrity: sha512-4w6X/oFmvXcwux6y6ExfM/xSqMHw20cYwFJH+BlYrtGa6nwY9qGq8GXnUs1sVYeF2o/KT3S8hAH6sKBI3VOkBg==} + '@ts-morph/common@0.28.1': + resolution: {integrity: sha512-W74iWf7ILp1ZKNYXY5qbddNaml7e9Sedv5lvU1V8lftlitkc9Pq1A+jlH23ltDgWYeZFFEqGCD1Ies9hqu3O+g==} - '@tybys/wasm-util@0.10.0': - resolution: {integrity: sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==} + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -1334,14 +1335,14 @@ packages: '@types/node@16.9.1': resolution: {integrity: sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==} - '@types/node@20.19.11': - resolution: {integrity: sha512-uug3FEEGv0r+jrecvUUpbY8lLisvIjg6AAic6a2bSP5OEOLeJsDSnvhCDov7ipFFMXS3orMpzlmi0ZcuGkBbow==} + '@types/node@20.19.21': + resolution: {integrity: sha512-CsGG2P3I5y48RPMfprQGfy4JPRZ6csfC3ltBZSRItG3ngggmNY/qs2uZKp4p9VbrpqNNSMzUZNFZKzgOGnd/VA==} - '@types/node@22.17.2': - resolution: {integrity: sha512-gL6z5N9Jm9mhY+U2KXZpteb+09zyffliRkZyZOHODGATyC5B1Jt/7TzuuiLkFsSUMLbS1OLmlj/E+/3KF4Q/4w==} + '@types/node@22.18.10': + resolution: {integrity: sha512-anNG/V/Efn/YZY4pRzbACnKxNKoBng2VTFydVu8RRs5hQjikP8CQfaeAV59VFSCzKNp90mXiVXW2QzV56rwMrg==} - '@types/node@24.3.0': - resolution: {integrity: sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==} + '@types/node@24.8.0': + resolution: {integrity: sha512-5x08bUtU8hfboMTrJ7mEO4CpepS9yBwAqcL52y86SWNmbPX8LVbNs3EP4cNrIZgdjk2NAlP2ahNihozpoZIxSg==} '@types/plist@3.0.5': resolution: {integrity: sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==} @@ -1364,100 +1365,63 @@ packages: '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - '@typescript-eslint/eslint-plugin@8.43.0': - resolution: {integrity: sha512-8tg+gt7ENL7KewsKMKDHXR1vm8tt9eMxjJBYINf6swonlWgkYn5NwyIgXpbbDxTNU5DgpDFfj95prcTq2clIQQ==} + '@typescript-eslint/eslint-plugin@8.46.1': + resolution: {integrity: sha512-rUsLh8PXmBjdiPY+Emjz9NX2yHvhS11v0SR6xNJkm5GM1MO9ea/1GoDKlHHZGrOJclL/cZ2i/vRUYVtjRhrHVQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.43.0 + '@typescript-eslint/parser': ^8.46.1 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.43.0': - resolution: {integrity: sha512-B7RIQiTsCBBmY+yW4+ILd6mF5h1FUwJsVvpqkrgpszYifetQ2Ke+Z4u6aZh0CblkUGIdR59iYVyXqqZGkZ3aBw==} + '@typescript-eslint/parser@8.46.1': + resolution: {integrity: sha512-6JSSaBZmsKvEkbRUkf7Zj7dru/8ZCrJxAqArcLaVMee5907JdtEbKGsZ7zNiIm/UAkpGUkaSMZEXShnN2D1HZA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.42.0': - resolution: {integrity: sha512-vfVpLHAhbPjilrabtOSNcUDmBboQNrJUiNAGoImkZKnMjs2TIcWG33s4Ds0wY3/50aZmTMqJa6PiwkwezaAklg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/project-service@8.43.0': - resolution: {integrity: sha512-htB/+D/BIGoNTQYffZw4uM4NzzuolCoaA/BusuSIcC8YjmBYQioew5VUZAYdAETPjeed0hqCaW7EHg+Robq8uw==} + '@typescript-eslint/project-service@8.46.1': + resolution: {integrity: sha512-FOIaFVMHzRskXr5J4Jp8lFVV0gz5ngv3RHmn+E4HYxSJ3DgDzU7fVI1/M7Ijh1zf6S7HIoaIOtln1H5y8V+9Zg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.42.0': - resolution: {integrity: sha512-51+x9o78NBAVgQzOPd17DkNTnIzJ8T/O2dmMBLoK9qbY0Gm52XJcdJcCl18ExBMiHo6jPMErUQWUv5RLE51zJw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/scope-manager@8.43.0': - resolution: {integrity: sha512-daSWlQ87ZhsjrbMLvpuuMAt3y4ba57AuvadcR7f3nl8eS3BjRc8L9VLxFLk92RL5xdXOg6IQ+qKjjqNEimGuAg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/tsconfig-utils@8.42.0': - resolution: {integrity: sha512-kHeFUOdwAJfUmYKjR3CLgZSglGHjbNTi1H8sTYRYV2xX6eNz4RyJ2LIgsDLKf8Yi0/GL1WZAC/DgZBeBft8QAQ==} + '@typescript-eslint/scope-manager@8.46.1': + resolution: {integrity: sha512-weL9Gg3/5F0pVQKiF8eOXFZp8emqWzZsOJuWRUNtHT+UNV2xSJegmpCNQHy37aEQIbToTq7RHKhWvOsmbM680A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/tsconfig-utils@8.43.0': - resolution: {integrity: sha512-ALC2prjZcj2YqqL5X/bwWQmHA2em6/94GcbB/KKu5SX3EBDOsqztmmX1kMkvAJHzxk7TazKzJfFiEIagNV3qEA==} + '@typescript-eslint/tsconfig-utils@8.46.1': + resolution: {integrity: sha512-X88+J/CwFvlJB+mK09VFqx5FE4H5cXD+H/Bdza2aEWkSb8hnWIQorNcscRl4IEo1Cz9VI/+/r/jnGWkbWPx54g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.43.0': - resolution: {integrity: sha512-qaH1uLBpBuBBuRf8c1mLJ6swOfzCXryhKND04Igr4pckzSEW9JX5Aw9AgW00kwfjWJF0kk0ps9ExKTfvXfw4Qg==} + '@typescript-eslint/type-utils@8.46.1': + resolution: {integrity: sha512-+BlmiHIiqufBxkVnOtFwjah/vrkF4MtKKvpXrKSPLCkCtAp8H01/VV43sfqA98Od7nJpDcFnkwgyfQbOG0AMvw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.42.0': - resolution: {integrity: sha512-LdtAWMiFmbRLNP7JNeY0SqEtJvGMYSzfiWBSmx+VSZ1CH+1zyl8Mmw1TT39OrtsRvIYShjJWzTDMPWZJCpwBlw==} + '@typescript-eslint/types@8.46.1': + resolution: {integrity: sha512-C+soprGBHwWBdkDpbaRC4paGBrkIXxVlNohadL5o0kfhsXqOC6GYH2S/Obmig+I0HTDl8wMaRySwrfrXVP8/pQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/types@8.43.0': - resolution: {integrity: sha512-vQ2FZaxJpydjSZJKiSW/LJsabFFvV7KgLC5DiLhkBcykhQj8iK9BOaDmQt74nnKdLvceM5xmhaTF+pLekrxEkw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/typescript-estree@8.42.0': - resolution: {integrity: sha512-ku/uYtT4QXY8sl9EDJETD27o3Ewdi72hcXg1ah/kkUgBvAYHLwj2ofswFFNXS+FL5G+AGkxBtvGt8pFBHKlHsQ==} + '@typescript-eslint/typescript-estree@8.46.1': + resolution: {integrity: sha512-uIifjT4s8cQKFQ8ZBXXyoUODtRoAd7F7+G8MKmtzj17+1UbdzFl52AzRyZRyKqPHhgzvXunnSckVu36flGy8cg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/typescript-estree@8.43.0': - resolution: {integrity: sha512-7Vv6zlAhPb+cvEpP06WXXy/ZByph9iL6BQRBDj4kmBsW98AqEeQHlj/13X+sZOrKSo9/rNKH4Ul4f6EICREFdw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/utils@8.42.0': - resolution: {integrity: sha512-JnIzu7H3RH5BrKC4NoZqRfmjqCIS1u3hGZltDYJgkVdqAezl4L9d1ZLw+36huCujtSBSAirGINF/S4UxOcR+/g==} + '@typescript-eslint/utils@8.46.1': + resolution: {integrity: sha512-vkYUy6LdZS7q1v/Gxb2Zs7zziuXN0wxqsetJdeZdRe/f5dwJFglmuvZBfTUivCtjH725C1jWCDfpadadD95EDQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.43.0': - resolution: {integrity: sha512-S1/tEmkUeeswxd0GGcnwuVQPFWo8NzZTOMxCvw8BX7OMxnNae+i8Tm7REQen/SwUIPoPqfKn7EaZ+YLpiB3k9g==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/visitor-keys@8.42.0': - resolution: {integrity: sha512-3WbiuzoEowaEn8RSnhJBrxSwX8ULYE9CXaPepS2C2W3NSA5NNIvBaslpBSBElPq0UGr0xVJlXFWOAKIkyylydQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/visitor-keys@8.43.0': - resolution: {integrity: sha512-T+S1KqRD4sg/bHfLwrpF/K3gQLBM1n7Rp7OjjikjTEssI2YJzQpi5WXoynOaQ93ERIuq3O8RBTOUYDKszUCEHw==} + '@typescript-eslint/visitor-keys@8.46.1': + resolution: {integrity: sha512-ptkmIf2iDkNUjdeu2bQqhFPV1m6qTnFFjg7PPDjxKWaMaP0Z6I9l30Jr3g5QqbZGdw8YdYvLp+XnqnWWZOg/NA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@unrs/resolver-binding-android-arm-eabi@1.11.1': @@ -1555,8 +1519,8 @@ packages: cpu: [x64] os: [win32] - '@vladfrangu/async_event_emitter@2.4.6': - resolution: {integrity: sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA==} + '@vladfrangu/async_event_emitter@2.4.7': + resolution: {integrity: sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==} engines: {node: '>=v14.0.0', npm: '>=7.0.0'} '@xhayper/discord-rpc@1.3.0': @@ -1636,20 +1600,20 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - ansi-regex@6.1.0: - resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} engines: {node: '>=12'} ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} - ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} - ansis@4.1.0: - resolution: {integrity: sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w==} + ansis@4.2.0: + resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} engines: {node: '>=14'} any-base@1.1.0: @@ -1761,11 +1725,15 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + baseline-browser-mapping@2.8.17: + resolution: {integrity: sha512-j5zJcx6golJYTG6c05LUZ3Z8Gi+M62zRT/ycz4Xq4iCOdpcxwg7ngEYD4KA0eWZC7U17qh/Smq8bYbACJ0ipBA==} + hasBin: true + bgutils-js@3.2.0: resolution: {integrity: sha512-CacO15JvxbclbLeCAAm9DETGlLuisRGWpPigoRvNsccSCPEC4pwYwA2g2x/pv7Om/sk79d4ib35V5HHmxPBpDg==} - birpc@2.5.0: - resolution: {integrity: sha512-VSWO/W6nNQdyP520F1mhf+Lc2f8pjGQOtoHHm7Ze8Go1kX7akpVIrtTa0fn+HB0QJEDVacl6aO08YE0PgXfdnQ==} + birpc@2.6.1: + resolution: {integrity: sha512-LPnFhlDpdSH6FJhJyn4M0kFO7vtQ5iPw24FnG0y21q09xC7e8+1LeR31S1MAIrDAHp4m7aas4bEkTDTvMAtebQ==} bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} @@ -1797,8 +1765,8 @@ packages: browser-extension-url-match@1.2.0: resolution: {integrity: sha512-+O/t71m1opNU5KG/bJkeNLvXLp0OxlFekjdR8w6waUOyWhkL6+bnQ6dCDoJxc6YF6ZQM0r64ag/d9K4m05ULsg==} - browserslist@4.25.2: - resolution: {integrity: sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==} + browserslist@4.26.3: + resolution: {integrity: sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -1887,8 +1855,8 @@ packages: resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==} engines: {node: '>=14.16'} - caniuse-lite@1.0.30001735: - resolution: {integrity: sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==} + caniuse-lite@1.0.30001751: + resolution: {integrity: sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==} chalk-template@0.4.0: resolution: {integrity: sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==} @@ -1924,8 +1892,8 @@ packages: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} - clean-stack@5.2.0: - resolution: {integrity: sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==} + clean-stack@5.3.0: + resolution: {integrity: sha512-9ngPTOhYGQqNVSfeJkYXHmF7AGWp4/nN5D/QqNQs3Dvxd1Kk/WpjHfNujKHYUQ/5CoGyOyFNoWSPk5afzP0QVg==} engines: {node: '>=14.16'} cli-boxes@3.0.0: @@ -1966,23 +1934,23 @@ packages: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} - color-convert@3.1.0: - resolution: {integrity: sha512-TVoqAq8ZDIpK5lsQY874DDnu65CSsc9vzq0wLpNQ6UMBq81GSZocVazPiBbYGzngzBOIRahpkTzCLVe2at4MfA==} + color-convert@3.1.2: + resolution: {integrity: sha512-UNqkvCDXstVck3kdowtOTWROIJQwafjOfXSmddoDrXo4cewMKmusCeF22Q24zvjR8nwWib/3S/dfyzPItPEiJg==} engines: {node: '>=14.6'} color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - color-name@2.0.0: - resolution: {integrity: sha512-SbtvAMWvASO5TE2QP07jHBMXKafgdZz8Vrsrn96fiL+O92/FN/PLARzUW5sKt013fjAprK2d2iCn2hk2Xb5oow==} + color-name@2.0.2: + resolution: {integrity: sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==} engines: {node: '>=12.20'} - color-string@2.0.1: - resolution: {integrity: sha512-5z9FbYTZPAo8iKsNEqRNv+OlpBbDcoE+SY9GjLfDUHEfcNNV7tS9eSAlFHEaub/r5tBL9LtskAeq1l9SaoZ5tQ==} + color-string@2.1.2: + resolution: {integrity: sha512-RxmjYxbWemV9gKu4zPgiZagUxbH3RQpEIO77XoSSX0ivgABDZ+h8Zuash/EMFLTI4N9QgFPOJ6JQpPZKFxa+dA==} engines: {node: '>=18'} - color@5.0.0: - resolution: {integrity: sha512-16BlyiuyLq3MLxpRWyOTiWsO3ii/eLQLJUQXBSNcxMBBSnyt1ee9YUdaozQp03ifwm5woztEZGDbk9RGVuCsdw==} + color@5.0.2: + resolution: {integrity: sha512-e2hz5BzbUPcYlIRHo8ieAhYgoajrJr+hWoceg6E345TPsATMUKqDgzt8fSXZJJbxfpiPzkWyphz8yn8At7q3fA==} engines: {node: '>=18'} combined-stream@1.0.8: @@ -2005,8 +1973,8 @@ packages: resolution: {integrity: sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==} engines: {node: '>=0.10.0'} - component-register@0.8.7: - resolution: {integrity: sha512-clPS/o1RNfJw7L1/w4q+nkj6l7JV32kFHCx6vW5nSPOEly4B9olMeADNilEgpLV/DdeS7y8JXhHKx9YvSj8vqQ==} + component-register@0.8.8: + resolution: {integrity: sha512-djhwcxjY+X9dacaYUEOkOm7tda8uOEDiMDigWysu3xv54M8o6XDlsjR1qt5Y8QLGiKg51fqXFIR2HUTmt9ys0Q==} compressible@2.0.18: resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} @@ -2019,8 +1987,8 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - conf@14.0.0: - resolution: {integrity: sha512-L6BuueHTRuJHQvQVc6YXYZRtN5vJUtOdCTLn0tRYYV5azfbAFcPghB5zEE40mVrV6w7slMTqUfkDomutIK14fw==} + conf@15.0.2: + resolution: {integrity: sha512-JBSrutapCafTrddF9dH3lc7+T2tBycGF4uPkI4Js+g4vLLEhG6RZcFi3aJd5zntdf5tQxAejJt8dihkoQ/eSJw==} engines: {node: '>=20'} config-file-ts@0.2.8-rc1: @@ -2045,8 +2013,8 @@ packages: cross-dirname@0.1.0: resolution: {integrity: sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==} - cross-env@10.0.0: - resolution: {integrity: sha512-aU8qlEK/nHYtVuN4p7UQgAwVljzMg8hB4YK5ThRqD2l/ziSnryncPNn7bMLt5cFYsKVKBh8HqLqyCoTupEUu7Q==} + cross-env@10.1.0: + resolution: {integrity: sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==} engines: {node: '>=20'} hasBin: true @@ -2069,6 +2037,10 @@ packages: peerDependencies: electron: '>=10.0.0' + dargs@7.0.0: + resolution: {integrity: sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==} + engines: {node: '>=8'} + data-uri-to-buffer@4.0.1: resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} engines: {node: '>= 12'} @@ -2105,8 +2077,8 @@ packages: supports-color: optional: true - debug@4.4.1: - resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} peerDependencies: supports-color: '*' @@ -2164,13 +2136,13 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} - del-cli@6.0.0: - resolution: {integrity: sha512-9nitGV2W6KLFyya4qYt4+9AKQFL+c0Ehj5K7V7IwlxTc6RMCfQUGY9E9pLG6e8TQjtwXpuiWIGGZb3mfVxyZkw==} + del-cli@7.0.0: + resolution: {integrity: sha512-fRl4pWJYu9WFQH8jXdQUYvcD0IMtij9WEc7qmB7xOyJEweNJNuE7iKmqNeoOT1DbBUjtRjxlw8Y63qKBI/NQ1g==} engines: {node: '>=18'} hasBin: true - del@8.0.0: - resolution: {integrity: sha512-R6ep6JJ+eOBZsBr9esiNN1gxFbZE4Q2cULkUSFumGYecAiS6qodDvcPx/sFuWHMNul7DWmrtoEOpYSm7o6tbSA==} + del@8.0.1: + resolution: {integrity: sha512-gPqh0mKTPvaUZGAuHbrBUYKZWBNAeHG7TU3QH5EhVwPMyKvmfJaNXhcD2jTcXsJRRcffuho4vaYweu80dRrMGA==} engines: {node: '>=18'} delay@6.0.0: @@ -2181,8 +2153,8 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} - detect-libc@2.0.4: - resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} detect-node@2.1.0: @@ -2191,8 +2163,8 @@ packages: dir-compare@4.2.0: resolution: {integrity: sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==} - discord-api-types@0.38.23: - resolution: {integrity: sha512-C8VjK0yxBUq1dakxGpUXQm4VSC7R+aaD2SIr3paj2a0bP/LRok1AqHiezp30GruK6Ba9FtQAKqYUMJPzsqv7IQ==} + discord-api-types@0.38.30: + resolution: {integrity: sha512-KhAqlBrg+rVK+Ob7INMF5o63yW4/GUzRatG/AjyVsIO8lgcLyR8qCl2HokIVzWwmzkJYG0CEPXsKMOqau3E8NA==} dmg-builder@26.0.12: resolution: {integrity: sha512-59CAAjAhTaIMCN8y9kD573vDkxbs1uhDcrFLHSgutYdPcGOU35Rf95725snvzEOy4BFB7+eLJ8djCNPmGwG67w==} @@ -2220,9 +2192,9 @@ packages: domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} - dot-prop@9.0.0: - resolution: {integrity: sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==} - engines: {node: '>=18'} + dot-prop@10.1.0: + resolution: {integrity: sha512-MVUtAugQMOff5RnBy2d9N31iG0lNwg1qAoAOn7pOK5wf94WIaE3My2p3uwTQuvS2AcqchkcR3bHByjaM0mmi7Q==} + engines: {node: '>=20'} dotenv-expand@11.0.7: resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==} @@ -2290,12 +2262,12 @@ packages: electron-publish@26.0.11: resolution: {integrity: sha512-a8QRH0rAPIWH9WyyS5LbNvW9Ark6qe63/LqDB7vu2JXYpi0Gma5Q60Dh4tmTqhOBQt0xsrzD8qE7C+D7j+B24A==} - electron-store@10.1.0: - resolution: {integrity: sha512-oL8bRy7pVCLpwhmXy05Rh/L6O93+k9t6dqSw0+MckIc3OmCTZm6Mp04Q4f/J0rtu84Ky6ywkR8ivtGOmrq+16w==} + electron-store@11.0.2: + resolution: {integrity: sha512-4VkNRdN+BImL2KcCi41WvAYbh6zLX5AUTi4so68yPqiItjbgTjqpEnGAqasgnG+lB6GuAyUltKwVopp6Uv+gwQ==} engines: {node: '>=20'} - electron-to-chromium@1.5.203: - resolution: {integrity: sha512-uz4i0vLhfm6dLZWbz/iH88KNDV+ivj5+2SA+utpgjKaj9Q0iDLuwk6Idhe9BTxciHudyx6IvTvijhkPvFGUQ0g==} + electron-to-chromium@1.5.237: + resolution: {integrity: sha512-icUt1NvfhGLar5lSWH3tHNzablaA5js3HVHacQimfP8ViEBOQv+L7DKEuHdbTZ0SKCO1ogTJTIL1Gwk9S6Qvcg==} electron-unhandled@5.0.0: resolution: {integrity: sha512-spH7quQUrNWgTp1rJNa0sCPTPHwKywctnHLVbrQxpjxojQLhGxXHMdETBkag0No8x9Jwo/c6r2T/nfeoG+4Cxw==} @@ -2304,8 +2276,8 @@ packages: electron-updater@6.6.2: resolution: {integrity: sha512-Cr4GDOkbAUqRHP5/oeOmH/L2Bn6+FQPxVLZtPbcmKZC63a1F3uu5EefYOssgZXG3u/zBlubbJ5PJdITdMVggbw==} - electron-vite@4.0.0: - resolution: {integrity: sha512-U3d7GlKjl4BPs1sxYTHn1N8YlMmPvlx1t62UVQ1zouWoBxZs5Hd5yrJcQ+rmMnko/ASbIcx/KFOTXh8YcqLv7w==} + electron-vite@4.0.1: + resolution: {integrity: sha512-QqacJbA8f1pmwUTqki1qLL5vIBaOQmeq13CZZefZ3r3vKVaIoC7cpoTgE+KPKxJDFTax+iFZV0VYvLVWPiQ8Aw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -2319,8 +2291,8 @@ packages: resolution: {integrity: sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==} engines: {node: '>=8.0.0'} - electron@38.0.0: - resolution: {integrity: sha512-egljptiPJqbL/oamFCEY+g3RNeONWTVxZSGeyLqzK8xq106JhzuxnhJZ3sxt4DzJFaofbGyGJA37Oe9d+gVzYw==} + electron@38.3.0: + resolution: {integrity: sha512-Wij4AzX4SAV0X/ktq+NrWrp5piTCSS8F6YWh1KAcG+QRtNzyns9XLKERP68nFHIwfprhxF2YCN2uj7nx9DaeJw==} engines: {node: '>= 12.20.55'} hasBin: true @@ -2377,8 +2349,8 @@ packages: es-get-iterator@1.1.3: resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==} - es-hangul@2.3.5: - resolution: {integrity: sha512-1Fdd5jr48/6/BxU4ucMpByaI2dbFhonipsCgAHYi1UbAGNLT0ELwBj1cdepl9j9ERsDS9nf4Zy4tPSEccaOEOw==} + es-hangul@2.3.8: + resolution: {integrity: sha512-VrJuqYBC7W04aKYjCnswomuJNXQRc0q33SG1IltVrRofi2YEE6FwVDPlsEJIdKbHwsOpbBL/mk9sUaBxVpbd+w==} es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} @@ -2399,8 +2371,8 @@ packages: es6-error@4.1.1: resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} - esbuild@0.25.9: - resolution: {integrity: sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==} + esbuild@0.25.11: + resolution: {integrity: sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==} engines: {node: '>=18'} hasBin: true @@ -2517,8 +2489,8 @@ packages: resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.35.0: - resolution: {integrity: sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg==} + eslint@9.37.0: + resolution: {integrity: sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -2568,8 +2540,8 @@ packages: exif-parser@0.1.12: resolution: {integrity: sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==} - exponential-backoff@3.1.2: - resolution: {integrity: sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==} + exponential-backoff@3.1.3: + resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} extract-zip@2.0.1: resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} @@ -2593,8 +2565,8 @@ packages: fast-diff@1.3.0: resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} - fast-equals@5.2.2: - resolution: {integrity: sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==} + fast-equals@5.3.2: + resolution: {integrity: sha512-6rxyATwPCkaFIL3JLqw8qXqMpIZ942pTX/tbQFkRsDGblS8tNGtlUauA/+mt6RUfqn/4MoEr+WDkYoIQbibWuQ==} engines: {node: '>=6.0.0'} fast-glob@3.3.3: @@ -2607,8 +2579,8 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fast-uri@3.0.6: - resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==} + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} fastq@1.19.1: resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} @@ -2643,13 +2615,13 @@ packages: filelist@1.0.4: resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} - filename-reserved-regex@3.0.0: - resolution: {integrity: sha512-hn4cQfU6GOT/7cFHXBqeBg2TbrMBgdD0kcjLhvSQYYwm3s4B6cjvBfb7nBALJLAXqmU5xajSa7X2NnUud/VCdw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + filename-reserved-regex@4.0.0: + resolution: {integrity: sha512-9ZT504KxEQDamsOogZImAWGEN24R1uFAxU3ZS4AZqn2ooidmN68Olh7n4/RcA4lLatZztjA0ZSuxeLHVoCc8JA==} + engines: {node: '>=20'} - filenamify@6.0.0: - resolution: {integrity: sha512-vqIlNogKeyD3yzrm0yhRMQg8hOVwYcYRfjEoODd49iCprMn4HL85gK3HcykQE53EPIpX3HcAbGA5ELQv216dAQ==} - engines: {node: '>=16'} + filenamify@7.0.0: + resolution: {integrity: sha512-nfY7iUozEErS4qKYu5X/DT+rafo43o642xAG/3AmZFp7chhVB20gsU4kH8KJZLDhyEZ2o4kHMSBDLDNBJ4r/cw==} + engines: {node: '>=20'} fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} @@ -2689,8 +2661,8 @@ packages: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} - fs-extra@11.3.1: - resolution: {integrity: sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==} + fs-extra@11.3.2: + resolution: {integrity: sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==} engines: {node: '>=14.14'} fs-extra@7.0.1: @@ -2736,6 +2708,10 @@ packages: functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + generator-function@2.0.1: + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: '>= 0.4'} + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -2764,8 +2740,8 @@ packages: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} - get-tsconfig@4.10.1: - resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} + get-tsconfig@4.12.0: + resolution: {integrity: sha512-LScr2aNr2FbjAjZh2C6X6BxRx1/x+aTDExct/xyq2XKbYOiG5c0aK7pMsSuyc0brz3ibr/lbQiHD9jzt4lccJw==} gifwrap@0.10.1: resolution: {integrity: sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw==} @@ -2812,8 +2788,8 @@ packages: resolution: {integrity: sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==} engines: {node: '>=18'} - goober@2.1.16: - resolution: {integrity: sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==} + goober@2.1.18: + resolution: {integrity: sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw==} peerDependencies: csstype: ^3.0.10 @@ -2834,8 +2810,8 @@ packages: hanja@1.1.5: resolution: {integrity: sha512-gKNEkX+IIBmxXUIm5Lf859YlvHnGD01kbiHL+7CoV1ZxGI+EttflD1k/Ntz8zbymMVIlVELunTk8pK5SNYOVlg==} - happy-dom@18.0.1: - resolution: {integrity: sha512-qn+rKOW7KWpVTtgIUi6RVmTBZJSe2k0Db0vh1f7CWrWclkkc7/Q+FrOfkZIb2eiErLyqu5AXEzE7XthO9JVxRA==} + happy-dom@20.0.2: + resolution: {integrity: sha512-pYOyu624+6HDbY+qkjILpQGnpvZOusItCk+rvF5/V+6NkcgTKnbOldpIy22tBnxoaLtlM9nXgoqAcW29/B7CIw==} engines: {node: '>=20.0.0'} has-bigints@1.1.0: @@ -2869,8 +2845,8 @@ packages: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true - hono@4.9.6: - resolution: {integrity: sha512-doVjXhSFvYZ7y0dNokjwwSahcrAfdz+/BCLvAMa/vHLzjj8+CFyV5xteThGUsKdkaasgN+gF2mUxao+SGLpUeA==} + hono@4.9.12: + resolution: {integrity: sha512-SrTC0YxqPwnN7yKa8gg/giLyQ2pILCKoideIHbYbFQlWZjYt68D2A4Ae1hehO/aDQ6RmTcpqOV/O2yBtMzx/VQ==} engines: {node: '>=16.9.0'} hosted-git-info@4.1.0: @@ -2924,8 +2900,8 @@ packages: humanize-ms@1.2.1: resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} - i18next@25.5.2: - resolution: {integrity: sha512-lW8Zeh37i/o0zVr+NoCHfNnfvVw+M6FQbRp36ZZ/NyHDJ3NJVpp2HhAUyU9WafL5AssymNoOjMRB48mmx2P6Hw==} + i18next@25.6.0: + resolution: {integrity: sha512-tTn8fLrwBYtnclpL5aPXK/tAYBLWVvoHM1zdfXoRNLcI+RvtMsoZRV98ePlaW3khHYKuNh/Q65W/+NVFUeIwVw==} peerDependencies: typescript: ^5 peerDependenciesMeta: @@ -3063,8 +3039,8 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - is-generator-function@1.1.0: - resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} + is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} engines: {node: '>= 0.4'} is-glob@4.0.3: @@ -3150,8 +3126,9 @@ packages: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} - is-url@1.2.4: - resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==} + is-unix@2.0.13: + resolution: {integrity: sha512-gkano7Y6lisBN/ajkPCrZ/7UNt/cv90J8Glwh7ATCJugk5kIeqzhjDlHz7dgPdn9MZk5NJhREPDH/EEDiRIGVA==} + engines: {node: '>= 12'} is-weakmap@2.0.2: resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} @@ -3187,8 +3164,8 @@ packages: resolution: {integrity: sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==} engines: {node: '>= 8.0.0'} - isbinaryfile@5.0.4: - resolution: {integrity: sha512-YKBKVkKhty7s8rxddb40oOkuP0NbaeXrQvLin6QMHL7Ypiy2RW9LwOVrVgZRyOrhQlayMd9t+D8yDy8MKFTSDQ==} + isbinaryfile@5.0.6: + resolution: {integrity: sha512-I+NmIfBHUl+r2wcDd6JwE9yWje/PIVY/R5/CmV8dXLZd5K+L9X2klAOwfAHNnondLXkbHyTAleQAWonpTJBTtw==} engines: {node: '>= 18.0.0'} isexe@2.0.0: @@ -3214,9 +3191,6 @@ packages: resolution: {integrity: sha512-YcwCHw1kiqEeI5xRpDlPPBGL2EOpBKLwO4yIBJcXWHPj5PnA5urGq0jbyhM5KoNpypQ6VboSoxc9D8HyfvngSg==} engines: {node: '>=18'} - jintr@3.3.1: - resolution: {integrity: sha512-nnOzyhf0SLpbWuZ270Omwbj5LcXUkTcZkVnK8/veJXtSZOiATM5gMZMdmzN75FmTyj+NVgrGaPdH12zIJ24oIA==} - jpeg-js@0.4.4: resolution: {integrity: sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==} @@ -3315,68 +3289,74 @@ packages: lie@3.3.0: resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} - lightningcss-darwin-arm64@1.30.1: - resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==} + lightningcss-android-arm64@1.30.2: + resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.30.2: + resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [darwin] - lightningcss-darwin-x64@1.30.1: - resolution: {integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==} + lightningcss-darwin-x64@1.30.2: + resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [darwin] - lightningcss-freebsd-x64@1.30.1: - resolution: {integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==} + lightningcss-freebsd-x64@1.30.2: + resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [freebsd] - lightningcss-linux-arm-gnueabihf@1.30.1: - resolution: {integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==} + lightningcss-linux-arm-gnueabihf@1.30.2: + resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==} engines: {node: '>= 12.0.0'} cpu: [arm] os: [linux] - lightningcss-linux-arm64-gnu@1.30.1: - resolution: {integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==} + lightningcss-linux-arm64-gnu@1.30.2: + resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - lightningcss-linux-arm64-musl@1.30.1: - resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==} + lightningcss-linux-arm64-musl@1.30.2: + resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - lightningcss-linux-x64-gnu@1.30.1: - resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==} + lightningcss-linux-x64-gnu@1.30.2: + resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - lightningcss-linux-x64-musl@1.30.1: - resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==} + lightningcss-linux-x64-musl@1.30.2: + resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - lightningcss-win32-arm64-msvc@1.30.1: - resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==} + lightningcss-win32-arm64-msvc@1.30.2: + resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [win32] - lightningcss-win32-x64-msvc@1.30.1: - resolution: {integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==} + lightningcss-win32-x64-msvc@1.30.2: + resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [win32] - lightningcss@1.30.1: - resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==} + lightningcss@1.30.2: + resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} engines: {node: '>= 12.0.0'} lit-element@4.2.1: @@ -3422,8 +3402,8 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@11.1.0: - resolution: {integrity: sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==} + lru-cache@11.2.2: + resolution: {integrity: sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==} engines: {node: 20 || >=22} lru-cache@5.1.1: @@ -3440,8 +3420,8 @@ packages: magic-bytes.js@1.12.1: resolution: {integrity: sha512-ThQLOhN86ZkJ7qemtVRGYM+gRgR8GEXNli9H/PMvpnZsE44Xfh3wx9kGJaldg314v85m+bFW6WBMaVHJc/c3zA==} - magic-string@0.30.17: - resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + magic-string@0.30.19: + resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} make-fetch-happen@10.2.1: resolution: {integrity: sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==} @@ -3465,9 +3445,9 @@ packages: mdui@2.1.4: resolution: {integrity: sha512-QtK5xia5HXtVO7yH30QjwvvNruw5JdrJL1MEc1k6S/ZfsbHOj6BxxdYjrdv2HiN5ikkGqt5CIbZdFyq6shaZyw==} - meow@13.2.0: - resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} - engines: {node: '>=18'} + meow@14.0.0: + resolution: {integrity: sha512-JhC3R1f6dbspVtmF3vKjAWz1EVIvwFrGGPLSdU6rK79xBwHWTuHoLnRX/t1/zHS1Ch1Y2UtIrih7DAHuH9JFJA==} + engines: {node: '>=20'} merge-anything@5.1.7: resolution: {integrity: sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ==} @@ -3480,6 +3460,10 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + meriyah@6.1.4: + resolution: {integrity: sha512-Sz8FzjzI0kN13GK/6MVEsVzMZEPvOhnmmI1lU5+/1cGOiK3QUahntrNNtdVeihrO7t9JpoH75iMNXg6R6uWflQ==} + engines: {node: '>=18.0.0'} + micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -3592,8 +3576,8 @@ packages: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} engines: {node: '>= 8'} - minizlib@3.0.2: - resolution: {integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==} + minizlib@3.1.0: + resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} engines: {node: '>= 18'} mkdirp@0.5.6: @@ -3605,11 +3589,6 @@ packages: engines: {node: '>=10'} hasBin: true - mkdirp@3.0.1: - resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} - engines: {node: '>=10'} - hasBin: true - mrmime@2.0.1: resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} engines: {node: '>=10'} @@ -3625,8 +3604,8 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - napi-postinstall@0.3.3: - resolution: {integrity: sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow==} + napi-postinstall@0.3.4: + resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} hasBin: true @@ -3641,10 +3620,13 @@ packages: resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} engines: {node: '>= 0.6'} - node-abi@3.75.0: - resolution: {integrity: sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==} + node-abi@3.78.0: + resolution: {integrity: sha512-E2wEyrgX/CqvicaQYU3Ze1PFGjc4QYPGsjUrlYkqAE0WjHEZwgOsGMPMzkMse4LjJbDmaEuDX3CM036j5K2DSQ==} engines: {node: '>=10'} + node-abort-controller@3.1.1: + resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} + node-addon-api@1.7.2: resolution: {integrity: sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==} @@ -3675,8 +3657,8 @@ packages: node-id3@0.2.9: resolution: {integrity: sha512-dSxhuxrkkGVRgUhDHFxdY0pilzOREcodO01HcZWfaRkCaPWGmo0dOgD8ygyL6ln4Iv4cmfRxAWn1WD9bIB9Bhw==} - node-releases@2.0.19: - resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + node-releases@2.0.25: + resolution: {integrity: sha512-4auku8B/vw5psvTiiN9j1dAOsXvMoGqJuKJcR+dTdqiXEK20mMTk1UEo3HS16LeGQsVG6+qKTPM9u/qQ2LqATA==} nopt@6.0.0: resolution: {integrity: sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==} @@ -3894,13 +3876,13 @@ packages: resolution: {integrity: sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==} hasBin: true - playwright-core@1.55.0: - resolution: {integrity: sha512-GvZs4vU3U5ro2nZpeiwyb0zuFaqb9sUiAJuyrWpcGouD8y9/HLgGbNRjIph7zU9D3hnPaisMl9zG9CgFi/biIg==} + playwright-core@1.56.0: + resolution: {integrity: sha512-1SXl7pMfemAMSDn5rkPeZljxOCYAmQnYLBTExuh6E8USHXGSX3dx6lYZN/xPpTz1vimXmPA9CDnILvmJaB8aSQ==} engines: {node: '>=18'} hasBin: true - playwright@1.55.0: - resolution: {integrity: sha512-sdCWStblvV1YU909Xqx0DhOjPZE4/5lJsIS84IfN9dAZfcl/CIZ5O8l3o0j7hPMjDvqoTF8ZUcc+i/GL5erstA==} + playwright@1.56.0: + resolution: {integrity: sha512-X5Q1b8lOdWIE4KAoHpW3SE8HvUB+ZZsUoN64ZhjnN8dOb1UpujxBtENGiZFE+9F/yhzJwYa+ca3u43FeLbboHA==} engines: {node: '>=18'} hasBin: true @@ -3933,6 +3915,10 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + presentable-error@0.0.1: + resolution: {integrity: sha512-E6rsNU1QNJgB3sjj7OANinGncFKuK+164sLXw1/CqBjj/EkXSoSdHCtWQGBNlREIGLnL7IEUEGa08YFVUbrhVg==} + engines: {node: '>=16'} + prettier-linter-helpers@1.0.0: resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} engines: {node: '>=6.0.0'} @@ -4018,9 +4004,6 @@ packages: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} - regenerator-runtime@0.13.11: - resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} - regexp.prototype.flags@1.5.4: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} @@ -4137,8 +4120,8 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - run-applescript@7.0.0: - resolution: {integrity: sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==} + run-applescript@7.1.0: + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} engines: {node: '>=18'} run-parallel@1.2.0: @@ -4188,8 +4171,8 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.7.2: - resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} engines: {node: '>=10'} hasBin: true @@ -4201,8 +4184,8 @@ packages: resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} engines: {node: '>=10'} - seroval-plugins@1.3.2: - resolution: {integrity: sha512-0QvCV2lM3aj/U3YozDiVwx9zpH0q8A60CTWIv4Jszj/givcudPb48B+rkU5D51NJ0pTpweGMttHjboPa9/zoIQ==} + seroval-plugins@1.3.3: + resolution: {integrity: sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w==} engines: {node: '>=10'} peerDependencies: seroval: ^1.0 @@ -4273,8 +4256,8 @@ packages: resolution: {integrity: sha512-kWJDCr9EWtZ+/EYYM5MareWj2cRnZGF93YDNpH4jQiHB+hBIZnfPFSQiVMzZOdk+zXWqTZ/9fTeQNu2DqeiudA==} engines: {node: '>=20.12.2'} - sirv@3.0.1: - resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==} + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} engines: {node: '>=18'} slash@5.1.0: @@ -4405,8 +4388,8 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - strip-ansi@7.1.0: - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} engines: {node: '>=12'} strip-bom@3.0.0: @@ -4432,8 +4415,8 @@ 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==} + style-to-object@1.0.11: + resolution: {integrity: sha512-5A560JmXr7wDyGLK12Nq/EYS38VkGlglVzkis1JEdbGWSnbQIEhZzTJhzURXN5/8WwwFCs/f/VVcmkTppbXLow==} sumchecker@3.0.1: resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==} @@ -4451,12 +4434,16 @@ packages: resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==} engines: {node: ^14.18.0 || >=16.0.0} + tagged-tag@1.0.0: + resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} + engines: {node: '>=20'} + tar@6.2.1: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} - tar@7.4.3: - resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} + tar@7.5.1: + resolution: {integrity: sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==} engines: {node: '>=18'} temp-file@3.4.0: @@ -4481,10 +4468,6 @@ packages: tinycolor2@1.6.0: resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} - tinyglobby@0.2.14: - resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} - engines: {node: '>=12.0.0'} - tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} @@ -4494,11 +4477,11 @@ packages: engines: {node: '>= 12.10.0', npm: '>= 6.12.0', yarn: '>= 1.20.0'} hasBin: true - tldts-core@7.0.12: - resolution: {integrity: sha512-3K76aXywJFduGRsOYoY5JzINLs/WMlOkeDwPL+8OCPq2Rh39gkSDtWAxdJQlWjpun/xF/LHf29yqCi6VC/rHDA==} + tldts-core@7.0.17: + resolution: {integrity: sha512-DieYoGrP78PWKsrXr8MZwtQ7GLCUeLxihtjC1jZsW1DnvSMdKPitJSe8OSYDM2u5H6g3kWJZpePqkp43TfLh0g==} - tldts-experimental@7.0.12: - resolution: {integrity: sha512-riDDp/JrJJuh/2GV1Tgg6OnOwcWlgleBKi4/xkhd0DW7tvvmNjmWXCh2nj7Z2G1k9S/AGN4RiiPRbvhGJUCxeg==} + tldts-experimental@7.0.17: + resolution: {integrity: sha512-NGYJUDuOyb5UzoOKLufzSY2hLSlu7uEdobD+VzkWexuYOr/dqHnGhLRUoVWv1aifLV9gwBTY3XObCAnCnEA81w==} tmp-promise@3.0.3: resolution: {integrity: sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==} @@ -4528,8 +4511,8 @@ packages: peerDependencies: typescript: '>=4.8.4' - ts-morph@27.0.0: - resolution: {integrity: sha512-xcqelpTR5PCuZMs54qp9DE3t7tPgA2v/P1/qdW4ke5b3Y5liTGTYj6a/twT35EQW/H5okRqp1UOqwNlgg0K0eQ==} + ts-morph@27.0.2: + resolution: {integrity: sha512-fhUhgeljcrdZ+9DZND1De1029PrE+cMkIP7ooqkLRTrRLTqcki2AstsyJm0vRNbTbVCNJ0idGlbBrfqc7/nA8w==} tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} @@ -4549,9 +4532,9 @@ packages: resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} engines: {node: '>=12.20'} - type-fest@4.41.0: - resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} - engines: {node: '>=16'} + type-fest@5.1.0: + resolution: {integrity: sha512-wQ531tuWvB6oK+pchHIu5lHe5f5wpSCqB8Kf4dWQRbOYc9HTge7JL0G4Qd44bh6QuJCccIzL3bugb8GI0MwHrg==} + engines: {node: '>=20'} typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} @@ -4569,20 +4552,20 @@ packages: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} - typescript-eslint@8.43.0: - resolution: {integrity: sha512-FyRGJKUGvcFekRRcBKFBlAhnp4Ng8rhe8tuvvkR9OiU0gfd4vyvTRQHEckO6VDlH57jbeUQem2IpqPq9kLJH+w==} + typescript-eslint@8.46.1: + resolution: {integrity: sha512-VHgijW803JafdSsDO8I761r3SHrgk4T00IdyQ+/UsthtgPRsBWQLqoSxOolxTpxRKi1kGXK0bSz4CoAc9ObqJA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - typescript@5.9.2: - resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} hasBin: true - uint8array-extras@1.4.1: - resolution: {integrity: sha512-+NWHrac9dvilNgme+gP4YrBSumsaMZP0fNBtXXFIf33RLLKEcBUKaQZ7ULUbS0sBfcjxIZ4V96OTRkCbM7hxpw==} + uint8array-extras@1.5.0: + resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} engines: {node: '>=18'} unbox-primitive@1.1.0: @@ -4592,8 +4575,8 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - undici-types@7.10.0: - resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==} + undici-types@7.14.0: + resolution: {integrity: sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==} undici@6.21.3: resolution: {integrity: sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==} @@ -4627,8 +4610,8 @@ packages: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} - unplugin-utils@0.3.0: - resolution: {integrity: sha512-JLoggz+PvLVMJo+jZt97hdIIIZ2yTzGgft9e9q8iMrC4ewufl62ekeW7mixBghonn2gVb/ICjyvlmOCUBnJLQg==} + unplugin-utils@0.3.1: + resolution: {integrity: sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==} engines: {node: '>=20.19.0'} unrs-resolver@1.11.1: @@ -4677,8 +4660,8 @@ packages: resolution: {integrity: sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==} engines: {node: '>=0.6.0'} - virtua@0.42.3: - resolution: {integrity: sha512-5FoAKcEvh05qsUF97Yz42SWJ7bwnPExjUYHGuoxz1EUtfWtaOgXaRwnylJbDpA0QcH1rKvJ2qsGRi9MK1fpQbg==} + virtua@0.45.3: + resolution: {integrity: sha512-+nW3VOwXhlte3m4jbS9gVMked/cZo95IKChH0qol+XUApQVZpPaLDGee1BC/rVng0tsMfxs0vJPEPEUAAfKpkw==} peerDependencies: react: '>=16.14.0' react-dom: '>=16.14.0' @@ -4720,8 +4703,8 @@ packages: vite-plugin-resolve@2.5.2: resolution: {integrity: sha512-8twv20M+KIMxkZzAoF1eAUxxxB56NxKdYjIJ309A/30lZ3GAqgiAeGFjVVlLLEpeAcbAwfl9p7jztsQEw7C3Jg==} - vite-plugin-solid@2.11.8: - resolution: {integrity: sha512-hFrCxBfv3B1BmFqnJF4JOCYpjrmi/zwyeKjcomQ0khh8HFyQ8SbuBWQ7zGojfrz6HUOBFrJBNySDi/JgAHytWg==} + vite-plugin-solid@2.11.9: + resolution: {integrity: sha512-bTA6p+bspXZsuulSd2y6aTzegF8xGaJYcq1Uyh/mv+W4DQtzCgL9nN6n2fsTaxp/dMk+ZHHKgGndlNeooqHLKw==} peerDependencies: '@testing-library/jest-dom': ^5.16.6 || ^5.17.0 || ^6.* solid-js: ^1.7.2 @@ -4875,8 +4858,12 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - youtubei.js@15.0.1: - resolution: {integrity: sha512-2slapqJS5NuXKHvcACEknyVz0AjH/TrXaOhDM0q2twQKa54kCmfj+7B/2nGfd20uzAe29zW1ejk2qOc4ABuGkg==} + youtubei.js@16.0.1: + resolution: {integrity: sha512-3802bCAGkBc2/G5WUTc0l/bO5mPYJbQAHL04d9hE9PnrDHoBUT8MN721Yqt4RCNncAXdHcfee9VdJy3Fhq1r5g==} + + yt-dlp-exec@1.0.2: + resolution: {integrity: sha512-swKtruQmGBs+Xrxy0wCZ2FxCT167EpBYIWdj/klTzNB2HrHng/qFlKo/C0WVlopbww8/uMIGQR6grXQ2ObcrAw==} + engines: {node: '>= 12'} zlibjs@0.3.1: resolution: {integrity: sha512-+J9RrgTKOmlxFSDHo0pI1xM6BLVUv+o0ZT9ANtCxGkjIVCCUdx9alUF8Gm+dGLKbkkkidWIHFDZHDMpfITt4+w==} @@ -4884,24 +4871,19 @@ packages: zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} - zod@4.1.5: - resolution: {integrity: sha512-rcUUZqlLJgBC33IT3PNMgsCq6TzLQEG/Ei/KTCU0PedSWRMAXoOUN+4t/0H+Q8bdnLPdqUYnvboJT0bn/229qg==} + zod@4.1.12: + resolution: {integrity: sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==} snapshots: 7zip-bin@5.2.0: {} - '@ampproject/remapping@2.3.0': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.30 - '@assemblyscript/loader@0.17.14': {} - '@asteasolutions/zod-to-openapi@8.1.0(zod@4.1.5)': + '@asteasolutions/zod-to-openapi@8.1.0(zod@4.1.12)': dependencies: openapi3-ts: 4.5.0 - zod: 4.1.5 + zod: 4.1.12 '@babel/code-frame@7.27.1': dependencies: @@ -4909,22 +4891,22 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.28.0': {} + '@babel/compat-data@7.28.4': {} - '@babel/core@7.28.3': + '@babel/core@7.28.4': dependencies: - '@ampproject/remapping': 2.3.0 '@babel/code-frame': 7.27.1 '@babel/generator': 7.28.3 '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.3) - '@babel/helpers': 7.28.3 - '@babel/parser': 7.28.3 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4) + '@babel/helpers': 7.28.4 + '@babel/parser': 7.28.4 '@babel/template': 7.27.2 - '@babel/traverse': 7.28.3 - '@babel/types': 7.28.2 + '@babel/traverse': 7.28.4 + '@babel/types': 7.28.4 + '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 - debug: 4.4.1 + debug: 4.4.3 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -4933,17 +4915,17 @@ snapshots: '@babel/generator@7.28.3': dependencies: - '@babel/parser': 7.28.3 - '@babel/types': 7.28.2 + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.30 + '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 '@babel/helper-compilation-targets@7.27.2': dependencies: - '@babel/compat-data': 7.28.0 + '@babel/compat-data': 7.28.4 '@babel/helper-validator-option': 7.27.1 - browserslist: 4.25.2 + browserslist: 4.26.3 lru-cache: 5.1.1 semver: 6.3.1 @@ -4951,21 +4933,21 @@ snapshots: '@babel/helper-module-imports@7.18.6': dependencies: - '@babel/types': 7.28.2 + '@babel/types': 7.28.4 '@babel/helper-module-imports@7.27.1': dependencies: - '@babel/traverse': 7.28.3 - '@babel/types': 7.28.2 + '@babel/traverse': 7.28.4 + '@babel/types': 7.28.4 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.3)': + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.3 + '@babel/core': 7.28.4 '@babel/helper-module-imports': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.28.3 + '@babel/traverse': 7.28.4 transitivePeerDependencies: - supports-color @@ -4977,23 +4959,23 @@ snapshots: '@babel/helper-validator-option@7.27.1': {} - '@babel/helpers@7.28.3': + '@babel/helpers@7.28.4': dependencies: '@babel/template': 7.27.2 - '@babel/types': 7.28.2 + '@babel/types': 7.28.4 - '@babel/parser@7.28.3': + '@babel/parser@7.28.4': dependencies: - '@babel/types': 7.28.2 + '@babel/types': 7.28.4 - '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.3)': + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.3 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.28.3)': + '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.3 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 '@babel/runtime@7.28.4': {} @@ -5001,27 +4983,27 @@ snapshots: '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.3 - '@babel/types': 7.28.2 + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 - '@babel/traverse@7.28.3': + '@babel/traverse@7.28.4': dependencies: '@babel/code-frame': 7.27.1 '@babel/generator': 7.28.3 '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.3 + '@babel/parser': 7.28.4 '@babel/template': 7.27.2 - '@babel/types': 7.28.2 - debug: 4.4.1 + '@babel/types': 7.28.4 + debug: 4.4.3 transitivePeerDependencies: - supports-color - '@babel/types@7.28.2': + '@babel/types@7.28.4': dependencies: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 - '@bufbuild/protobuf@2.6.3': {} + '@bufbuild/protobuf@2.9.0': {} '@dehoist/romanize-thai@1.0.0': {} @@ -5032,23 +5014,23 @@ snapshots: '@discordjs/collection@2.1.1': {} - '@discordjs/rest@2.5.1': + '@discordjs/rest@2.6.0': dependencies: '@discordjs/collection': 2.1.1 '@discordjs/util': 1.1.1 '@sapphire/async-queue': 1.5.5 '@sapphire/snowflake': 3.5.5 - '@vladfrangu/async_event_emitter': 2.4.6 - discord-api-types: 0.38.23 + '@vladfrangu/async_event_emitter': 2.4.7 + discord-api-types: 0.38.30 magic-bytes.js: 1.12.1 tslib: 2.8.1 undici: 6.21.3 '@discordjs/util@1.1.1': {} - '@electron-toolkit/tsconfig@1.0.1(@types/node@24.3.0)': + '@electron-toolkit/tsconfig@2.0.0(@types/node@24.8.0)': dependencies: - '@types/node': 24.3.0 + '@types/node': 24.8.0 '@electron/asar@3.2.18': dependencies: @@ -5076,7 +5058,7 @@ snapshots: '@electron/get@2.0.3': dependencies: - debug: 4.4.1 + debug: 4.4.3 env-paths: 2.2.1 fs-extra: 8.1.0 got: 11.8.6 @@ -5091,13 +5073,13 @@ snapshots: '@electron/node-gyp@https://codeload.github.com/electron/node-gyp/tar.gz/06b29aafb7708acef8b3669835c8a7857ebc92d2': dependencies: env-paths: 2.2.1 - exponential-backoff: 3.1.2 + exponential-backoff: 3.1.3 glob: 8.1.0 graceful-fs: 4.2.11 make-fetch-happen: 10.2.1 nopt: 6.0.0 proc-log: 2.0.1 - semver: 7.7.2 + semver: 7.7.3 tar: 6.2.1 which: 2.0.2 transitivePeerDependencies: @@ -5106,7 +5088,7 @@ snapshots: '@electron/notarize@2.5.0': dependencies: - debug: 4.4.1 + debug: 4.4.3 fs-extra: 9.1.0 promise-retry: 2.0.1 transitivePeerDependencies: @@ -5115,7 +5097,7 @@ snapshots: '@electron/osx-sign@1.3.1': dependencies: compare-version: 0.1.2 - debug: 4.4.1 + debug: 4.4.3 fs-extra: 10.1.0 isbinaryfile: 4.0.10 minimist: 1.2.8 @@ -5128,30 +5110,30 @@ snapshots: '@electron/node-gyp': https://codeload.github.com/electron/node-gyp/tar.gz/06b29aafb7708acef8b3669835c8a7857ebc92d2 '@malept/cross-spawn-promise': 2.0.0 chalk: 4.1.2 - debug: 4.4.1 - detect-libc: 2.0.4 + debug: 4.4.3 + detect-libc: 2.1.2 fs-extra: 10.1.0 got: 11.8.6 - node-abi: 3.75.0 + node-abi: 3.78.0 node-api-version: 0.2.1 ora: 5.4.1 read-binary-file-arch: 1.0.6 - semver: 7.7.2 + semver: 7.7.3 tar: 6.2.1 yargs: 17.7.2 transitivePeerDependencies: - bluebird - supports-color - '@electron/remote@2.1.3(electron@38.0.0)': + '@electron/remote@2.1.3(electron@38.3.0)': dependencies: - electron: 38.0.0 + electron: 38.3.0 '@electron/universal@3.0.1': dependencies: '@electron/asar': 4.0.1 '@malept/cross-spawn-promise': 2.0.0 - debug: 4.4.1 + debug: 4.4.3 dir-compare: 4.2.0 minimatch: 9.0.5 plist: 3.1.0 @@ -5161,113 +5143,113 @@ snapshots: '@electron/windows-sign@1.2.2': dependencies: cross-dirname: 0.1.0 - debug: 4.4.1 - fs-extra: 11.3.1 + debug: 4.4.3 + fs-extra: 11.3.2 minimist: 1.2.8 postject: 1.0.0-alpha.6 transitivePeerDependencies: - supports-color optional: true - '@emnapi/core@1.4.5': + '@emnapi/core@1.5.0': dependencies: - '@emnapi/wasi-threads': 1.0.4 + '@emnapi/wasi-threads': 1.1.0 tslib: 2.8.1 optional: true - '@emnapi/runtime@1.4.5': + '@emnapi/runtime@1.5.0': dependencies: tslib: 2.8.1 optional: true - '@emnapi/wasi-threads@1.0.4': + '@emnapi/wasi-threads@1.1.0': dependencies: tslib: 2.8.1 optional: true '@epic-web/invariant@1.0.0': {} - '@esbuild/aix-ppc64@0.25.9': + '@esbuild/aix-ppc64@0.25.11': optional: true - '@esbuild/android-arm64@0.25.9': + '@esbuild/android-arm64@0.25.11': optional: true - '@esbuild/android-arm@0.25.9': + '@esbuild/android-arm@0.25.11': optional: true - '@esbuild/android-x64@0.25.9': + '@esbuild/android-x64@0.25.11': optional: true - '@esbuild/darwin-arm64@0.25.9': + '@esbuild/darwin-arm64@0.25.11': optional: true - '@esbuild/darwin-x64@0.25.9': + '@esbuild/darwin-x64@0.25.11': optional: true - '@esbuild/freebsd-arm64@0.25.9': + '@esbuild/freebsd-arm64@0.25.11': optional: true - '@esbuild/freebsd-x64@0.25.9': + '@esbuild/freebsd-x64@0.25.11': optional: true - '@esbuild/linux-arm64@0.25.9': + '@esbuild/linux-arm64@0.25.11': optional: true - '@esbuild/linux-arm@0.25.9': + '@esbuild/linux-arm@0.25.11': optional: true - '@esbuild/linux-ia32@0.25.9': + '@esbuild/linux-ia32@0.25.11': optional: true - '@esbuild/linux-loong64@0.25.9': + '@esbuild/linux-loong64@0.25.11': optional: true - '@esbuild/linux-mips64el@0.25.9': + '@esbuild/linux-mips64el@0.25.11': optional: true - '@esbuild/linux-ppc64@0.25.9': + '@esbuild/linux-ppc64@0.25.11': optional: true - '@esbuild/linux-riscv64@0.25.9': + '@esbuild/linux-riscv64@0.25.11': optional: true - '@esbuild/linux-s390x@0.25.9': + '@esbuild/linux-s390x@0.25.11': optional: true - '@esbuild/linux-x64@0.25.9': + '@esbuild/linux-x64@0.25.11': optional: true - '@esbuild/netbsd-arm64@0.25.9': + '@esbuild/netbsd-arm64@0.25.11': optional: true - '@esbuild/netbsd-x64@0.25.9': + '@esbuild/netbsd-x64@0.25.11': optional: true - '@esbuild/openbsd-arm64@0.25.9': + '@esbuild/openbsd-arm64@0.25.11': optional: true - '@esbuild/openbsd-x64@0.25.9': + '@esbuild/openbsd-x64@0.25.11': optional: true - '@esbuild/openharmony-arm64@0.25.9': + '@esbuild/openharmony-arm64@0.25.11': optional: true - '@esbuild/sunos-x64@0.25.9': + '@esbuild/sunos-x64@0.25.11': optional: true - '@esbuild/win32-arm64@0.25.9': + '@esbuild/win32-arm64@0.25.11': optional: true - '@esbuild/win32-ia32@0.25.9': + '@esbuild/win32-ia32@0.25.11': optional: true - '@esbuild/win32-x64@0.25.9': + '@esbuild/win32-x64@0.25.11': optional: true - '@eslint-community/eslint-utils@4.8.0(eslint@9.35.0)': + '@eslint-community/eslint-utils@4.9.0(eslint@9.37.0)': dependencies: - eslint: 9.35.0 + eslint: 9.37.0 eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.1': {} @@ -5275,21 +5257,23 @@ snapshots: '@eslint/config-array@0.21.0': dependencies: '@eslint/object-schema': 2.1.6 - debug: 4.4.1 + debug: 4.4.3 minimatch: 3.1.2 transitivePeerDependencies: - supports-color - '@eslint/config-helpers@0.3.1': {} + '@eslint/config-helpers@0.4.0': + dependencies: + '@eslint/core': 0.16.0 - '@eslint/core@0.15.2': + '@eslint/core@0.16.0': dependencies: '@types/json-schema': 7.0.15 '@eslint/eslintrc@3.3.1': dependencies: ajv: 6.12.6 - debug: 4.4.1 + debug: 4.4.3 espree: 10.4.0 globals: 14.0.0 ignore: 5.3.2 @@ -5300,22 +5284,18 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@9.35.0': {} + '@eslint/js@9.37.0': {} '@eslint/object-schema@2.1.6': {} - '@eslint/plugin-kit@0.3.5': + '@eslint/plugin-kit@0.4.0': dependencies: - '@eslint/core': 0.15.2 + '@eslint/core': 0.16.0 levn: 0.4.1 - '@ffmpeg.wasm/core-mt@0.12.0': {} + '@ffmpeg.wasm/core-mt@0.13.2': {} - '@ffmpeg.wasm/main@0.12.0': - dependencies: - is-url: 1.2.4 - node-fetch: 3.3.2 - regenerator-runtime: 0.13.11 + '@ffmpeg.wasm/main@0.13.1': {} '@floating-ui/core@1.7.3': dependencies: @@ -5332,79 +5312,77 @@ snapshots: '@gar/promisify@1.1.3': {} - '@ghostery/adblocker-content@2.11.6': + '@ghostery/adblocker-content@2.12.4': dependencies: - '@ghostery/adblocker-extended-selectors': 2.11.6 + '@ghostery/adblocker-extended-selectors': 2.12.4 - '@ghostery/adblocker-electron-preload@2.11.6(electron@38.0.0)': + '@ghostery/adblocker-electron-preload@2.12.4(electron@38.3.0)': dependencies: - '@ghostery/adblocker-content': 2.11.6 - electron: 38.0.0 + '@ghostery/adblocker-content': 2.12.4 + electron: 38.3.0 - '@ghostery/adblocker-electron@2.11.6(electron@38.0.0)': + '@ghostery/adblocker-electron@2.12.4(electron@38.3.0)': dependencies: - '@ghostery/adblocker': 2.11.6 - '@ghostery/adblocker-electron-preload': 2.11.6(electron@38.0.0) - electron: 38.0.0 - tldts-experimental: 7.0.12 + '@ghostery/adblocker': 2.12.4 + '@ghostery/adblocker-electron-preload': 2.12.4(electron@38.3.0) + electron: 38.3.0 + tldts-experimental: 7.0.17 - '@ghostery/adblocker-extended-selectors@2.11.6': {} + '@ghostery/adblocker-extended-selectors@2.12.4': {} - '@ghostery/adblocker@2.11.6': + '@ghostery/adblocker@2.12.4': dependencies: - '@ghostery/adblocker-content': 2.11.6 - '@ghostery/adblocker-extended-selectors': 2.11.6 + '@ghostery/adblocker-content': 2.12.4 + '@ghostery/adblocker-extended-selectors': 2.12.4 '@ghostery/url-parser': 1.3.0 '@remusao/guess-url-type': 2.1.0 '@remusao/small': 2.1.0 '@remusao/smaz': 2.2.0 - tldts-experimental: 7.0.12 + tldts-experimental: 7.0.17 '@ghostery/url-parser@1.3.0': dependencies: - tldts-experimental: 7.0.12 + tldts-experimental: 7.0.17 - '@hono/node-server@1.19.1(hono@4.9.6)': + '@hono/node-server@1.19.5(hono@4.9.12)': dependencies: - hono: 4.9.6 + hono: 4.9.12 - '@hono/node-ws@1.2.0(@hono/node-server@1.19.1(hono@4.9.6))(bufferutil@4.0.9)(hono@4.9.6)(utf-8-validate@6.0.5)': + '@hono/node-ws@1.2.0(@hono/node-server@1.19.5(hono@4.9.12))(bufferutil@4.0.9)(hono@4.9.12)(utf-8-validate@6.0.5)': dependencies: - '@hono/node-server': 1.19.1(hono@4.9.6) - hono: 4.9.6 + '@hono/node-server': 1.19.5(hono@4.9.12) + hono: 4.9.12 ws: 8.18.3(bufferutil@4.0.9)(utf-8-validate@6.0.5) transitivePeerDependencies: - bufferutil - utf-8-validate - '@hono/swagger-ui@0.5.2(hono@4.9.6)': + '@hono/swagger-ui@0.5.2(hono@4.9.12)': dependencies: - hono: 4.9.6 + hono: 4.9.12 - '@hono/zod-openapi@1.1.0(hono@4.9.6)(zod@4.1.5)': + '@hono/zod-openapi@1.1.3(hono@4.9.12)(zod@4.1.12)': dependencies: - '@asteasolutions/zod-to-openapi': 8.1.0(zod@4.1.5) - '@hono/zod-validator': 0.7.2(hono@4.9.6)(zod@4.1.5) - hono: 4.9.6 + '@asteasolutions/zod-to-openapi': 8.1.0(zod@4.1.12) + '@hono/zod-validator': 0.7.4(hono@4.9.12)(zod@4.1.12) + hono: 4.9.12 openapi3-ts: 4.5.0 - zod: 4.1.5 + zod: 4.1.12 - '@hono/zod-validator@0.7.2(hono@4.9.6)(zod@4.1.5)': + '@hono/zod-validator@0.7.4(hono@4.9.12)(zod@4.1.12)': dependencies: - hono: 4.9.6 - zod: 4.1.5 + hono: 4.9.12 + zod: 4.1.12 '@humanfs/core@0.19.1': {} - '@humanfs/node@0.16.6': + '@humanfs/node@0.16.7': dependencies: '@humanfs/core': 0.19.1 - '@humanwhocodes/retry': 0.3.1 + '@humanwhocodes/retry': 0.4.3 '@humanwhocodes/module-importer@1.0.1': {} - '@humanwhocodes/retry@0.3.1': {} - '@humanwhocodes/retry@0.4.3': {} '@isaacs/balanced-match@4.0.1': {} @@ -5417,7 +5395,7 @@ snapshots: dependencies: string-width: 5.1.2 string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.0 + strip-ansi: 7.1.2 strip-ansi-cjs: strip-ansi@6.0.1 wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 @@ -5638,13 +5616,18 @@ snapshots: '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.30 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/resolve-uri@3.1.2': {} '@jridgewell/sourcemap-codec@1.5.5': {} - '@jridgewell/trace-mapping@0.3.30': + '@jridgewell/trace-mapping@0.3.31': dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 @@ -5665,7 +5648,7 @@ snapshots: '@malept/flatpak-bundler@0.4.0(patch_hash=c787371eeb2af011ea934e8818a0dad6d7dcb2df31bbb1686babc7231af0183c)': dependencies: - debug: 4.4.1 + debug: 4.4.3 fs-extra: 9.1.0 lodash: 4.17.21 tmp-promise: 3.0.3 @@ -5697,16 +5680,16 @@ snapshots: '@napi-rs/wasm-runtime@0.2.12': dependencies: - '@emnapi/core': 1.4.5 - '@emnapi/runtime': 1.4.5 - '@tybys/wasm-util': 0.10.0 + '@emnapi/core': 1.5.0 + '@emnapi/runtime': 1.5.0 + '@tybys/wasm-util': 0.10.1 optional: true - '@napi-rs/wasm-runtime@1.0.3': + '@napi-rs/wasm-runtime@1.0.7': dependencies: - '@emnapi/core': 1.4.5 - '@emnapi/runtime': 1.4.5 - '@tybys/wasm-util': 0.10.0 + '@emnapi/core': 1.5.0 + '@emnapi/runtime': 1.5.0 + '@tybys/wasm-util': 0.10.1 optional: true '@nodelib/fs.scandir@2.1.5': @@ -5736,11 +5719,11 @@ snapshots: '@npmcli/fs@2.1.2': dependencies: '@gar/promisify': 1.1.3 - semver: 7.7.2 + semver: 7.7.3 '@npmcli/fs@4.0.0': dependencies: - semver: 7.7.2 + semver: 7.7.3 '@npmcli/move-file@2.0.1': dependencies: @@ -5756,9 +5739,9 @@ snapshots: '@pkgr/core@0.2.9': {} - '@playwright/test@1.55.0': + '@playwright/test@1.56.0': dependencies: - playwright: 1.55.0 + playwright: 1.56.0 '@polka/url@1.0.0-next.29': {} @@ -5811,7 +5794,7 @@ snapshots: '@rolldown/binding-wasm32-wasi@1.0.0-beta.36': dependencies: - '@napi-rs/wasm-runtime': 1.0.3 + '@napi-rs/wasm-runtime': 1.0.7 optional: true '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.36': @@ -5855,11 +5838,11 @@ snapshots: dependencies: solid-js: 1.9.9 - '@stylistic/eslint-plugin@5.3.1(eslint@9.35.0)': + '@stylistic/eslint-plugin@5.4.0(eslint@9.37.0)': dependencies: - '@eslint-community/eslint-utils': 4.8.0(eslint@9.35.0) - '@typescript-eslint/types': 8.42.0 - eslint: 9.35.0 + '@eslint-community/eslint-utils': 4.9.0(eslint@9.37.0) + '@typescript-eslint/types': 8.46.1 + eslint: 9.37.0 eslint-visitor-keys: 4.2.1 espree: 10.4.0 estraverse: 5.3.0 @@ -5875,43 +5858,43 @@ snapshots: '@total-typescript/ts-reset@0.6.1': {} - '@ts-morph/common@0.28.0': + '@ts-morph/common@0.28.1': dependencies: minimatch: 10.0.3 path-browserify: 1.0.1 - tinyglobby: 0.2.14 + tinyglobby: 0.2.15 - '@tybys/wasm-util@0.10.0': + '@tybys/wasm-util@0.10.1': dependencies: tslib: 2.8.1 optional: true '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.28.3 - '@babel/types': 7.28.2 + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.28.0 '@types/babel__generator@7.27.0': dependencies: - '@babel/types': 7.28.2 + '@babel/types': 7.28.4 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.28.3 - '@babel/types': 7.28.2 + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 '@types/babel__traverse@7.28.0': dependencies: - '@babel/types': 7.28.2 + '@babel/types': 7.28.4 '@types/cacheable-request@6.0.3': dependencies: '@types/http-cache-semantics': 4.0.4 '@types/keyv': 3.1.4 - '@types/node': 24.3.0 + '@types/node': 22.18.10 '@types/responselike': 1.0.3 '@types/debug@4.1.12': @@ -5920,7 +5903,7 @@ snapshots: '@types/electron-localshortcut@3.1.3': dependencies: - electron: 38.0.0 + electron: 38.3.0 transitivePeerDependencies: - supports-color @@ -5928,7 +5911,7 @@ snapshots: '@types/fs-extra@9.0.13': dependencies: - '@types/node': 24.3.0 + '@types/node': 24.8.0 '@types/howler@2.2.12': {} @@ -5942,33 +5925,33 @@ snapshots: '@types/keyv@3.1.4': dependencies: - '@types/node': 24.3.0 + '@types/node': 22.18.10 '@types/ms@2.1.0': {} '@types/node@16.9.1': {} - '@types/node@20.19.11': + '@types/node@20.19.21': dependencies: undici-types: 6.21.0 - '@types/node@22.17.2': + '@types/node@22.18.10': dependencies: undici-types: 6.21.0 - '@types/node@24.3.0': + '@types/node@24.8.0': dependencies: - undici-types: 7.10.0 + undici-types: 7.14.0 '@types/plist@3.0.5': dependencies: - '@types/node': 24.3.0 + '@types/node': 24.8.0 xmlbuilder: 15.1.1 optional: true '@types/responselike@1.0.3': dependencies: - '@types/node': 24.3.0 + '@types/node': 22.18.10 '@types/semver@7.7.1': {} @@ -5981,152 +5964,100 @@ snapshots: '@types/yauzl@2.10.3': dependencies: - '@types/node': 24.3.0 + '@types/node': 22.18.10 optional: true - '@typescript-eslint/eslint-plugin@8.43.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0)(typescript@5.9.2))(eslint@9.35.0)(typescript@5.9.2)': + '@typescript-eslint/eslint-plugin@8.46.1(@typescript-eslint/parser@8.46.1(eslint@9.37.0)(typescript@5.9.3))(eslint@9.37.0)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.43.0(eslint@9.35.0)(typescript@5.9.2) - '@typescript-eslint/scope-manager': 8.43.0 - '@typescript-eslint/type-utils': 8.43.0(eslint@9.35.0)(typescript@5.9.2) - '@typescript-eslint/utils': 8.43.0(eslint@9.35.0)(typescript@5.9.2) - '@typescript-eslint/visitor-keys': 8.43.0 - eslint: 9.35.0 + '@typescript-eslint/parser': 8.46.1(eslint@9.37.0)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.46.1 + '@typescript-eslint/type-utils': 8.46.1(eslint@9.37.0)(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.1(eslint@9.37.0)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.46.1 + eslint: 9.37.0 graphemer: 1.4.0 ignore: 7.0.5 natural-compare: 1.4.0 - ts-api-utils: 2.1.0(typescript@5.9.2) - typescript: 5.9.2 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.43.0(eslint@9.35.0)(typescript@5.9.2)': + '@typescript-eslint/parser@8.46.1(eslint@9.37.0)(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.43.0 - '@typescript-eslint/types': 8.43.0 - '@typescript-eslint/typescript-estree': 8.43.0(typescript@5.9.2) - '@typescript-eslint/visitor-keys': 8.43.0 - debug: 4.4.1 - eslint: 9.35.0 - typescript: 5.9.2 + '@typescript-eslint/scope-manager': 8.46.1 + '@typescript-eslint/types': 8.46.1 + '@typescript-eslint/typescript-estree': 8.46.1(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.46.1 + debug: 4.4.3 + eslint: 9.37.0 + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.42.0(typescript@5.9.2)': + '@typescript-eslint/project-service@8.46.1(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.42.0(typescript@5.9.2) - '@typescript-eslint/types': 8.42.0 - debug: 4.4.1 - typescript: 5.9.2 + '@typescript-eslint/tsconfig-utils': 8.46.1(typescript@5.9.3) + '@typescript-eslint/types': 8.46.1 + debug: 4.4.3 + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.43.0(typescript@5.9.2)': + '@typescript-eslint/scope-manager@8.46.1': dependencies: - '@typescript-eslint/tsconfig-utils': 8.43.0(typescript@5.9.2) - '@typescript-eslint/types': 8.43.0 - debug: 4.4.1 - typescript: 5.9.2 - transitivePeerDependencies: - - supports-color + '@typescript-eslint/types': 8.46.1 + '@typescript-eslint/visitor-keys': 8.46.1 - '@typescript-eslint/scope-manager@8.42.0': + '@typescript-eslint/tsconfig-utils@8.46.1(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.42.0 - '@typescript-eslint/visitor-keys': 8.42.0 + typescript: 5.9.3 - '@typescript-eslint/scope-manager@8.43.0': + '@typescript-eslint/type-utils@8.46.1(eslint@9.37.0)(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.43.0 - '@typescript-eslint/visitor-keys': 8.43.0 - - '@typescript-eslint/tsconfig-utils@8.42.0(typescript@5.9.2)': - dependencies: - typescript: 5.9.2 - - '@typescript-eslint/tsconfig-utils@8.43.0(typescript@5.9.2)': - dependencies: - typescript: 5.9.2 - - '@typescript-eslint/type-utils@8.43.0(eslint@9.35.0)(typescript@5.9.2)': - dependencies: - '@typescript-eslint/types': 8.43.0 - '@typescript-eslint/typescript-estree': 8.43.0(typescript@5.9.2) - '@typescript-eslint/utils': 8.43.0(eslint@9.35.0)(typescript@5.9.2) - debug: 4.4.1 - eslint: 9.35.0 - ts-api-utils: 2.1.0(typescript@5.9.2) - typescript: 5.9.2 + '@typescript-eslint/types': 8.46.1 + '@typescript-eslint/typescript-estree': 8.46.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.1(eslint@9.37.0)(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.37.0 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.42.0': {} - - '@typescript-eslint/types@8.43.0': {} - - '@typescript-eslint/typescript-estree@8.42.0(typescript@5.9.2)': - dependencies: - '@typescript-eslint/project-service': 8.42.0(typescript@5.9.2) - '@typescript-eslint/tsconfig-utils': 8.42.0(typescript@5.9.2) - '@typescript-eslint/types': 8.42.0 - '@typescript-eslint/visitor-keys': 8.42.0 - debug: 4.4.1 - fast-glob: 3.3.3 - is-glob: 4.0.3 - minimatch: 9.0.5 - semver: 7.7.2 - ts-api-utils: 2.1.0(typescript@5.9.2) - typescript: 5.9.2 - transitivePeerDependencies: - - supports-color + '@typescript-eslint/types@8.46.1': {} - '@typescript-eslint/typescript-estree@8.43.0(typescript@5.9.2)': + '@typescript-eslint/typescript-estree@8.46.1(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.43.0(typescript@5.9.2) - '@typescript-eslint/tsconfig-utils': 8.43.0(typescript@5.9.2) - '@typescript-eslint/types': 8.43.0 - '@typescript-eslint/visitor-keys': 8.43.0 - debug: 4.4.1 + '@typescript-eslint/project-service': 8.46.1(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.46.1(typescript@5.9.3) + '@typescript-eslint/types': 8.46.1 + '@typescript-eslint/visitor-keys': 8.46.1 + debug: 4.4.3 fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 - semver: 7.7.2 - ts-api-utils: 2.1.0(typescript@5.9.2) - typescript: 5.9.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/utils@8.42.0(eslint@9.35.0)(typescript@5.9.2)': - dependencies: - '@eslint-community/eslint-utils': 4.8.0(eslint@9.35.0) - '@typescript-eslint/scope-manager': 8.42.0 - '@typescript-eslint/types': 8.42.0 - '@typescript-eslint/typescript-estree': 8.42.0(typescript@5.9.2) - eslint: 9.35.0 - typescript: 5.9.2 + semver: 7.7.3 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.43.0(eslint@9.35.0)(typescript@5.9.2)': + '@typescript-eslint/utils@8.46.1(eslint@9.37.0)(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.8.0(eslint@9.35.0) - '@typescript-eslint/scope-manager': 8.43.0 - '@typescript-eslint/types': 8.43.0 - '@typescript-eslint/typescript-estree': 8.43.0(typescript@5.9.2) - eslint: 9.35.0 - typescript: 5.9.2 + '@eslint-community/eslint-utils': 4.9.0(eslint@9.37.0) + '@typescript-eslint/scope-manager': 8.46.1 + '@typescript-eslint/types': 8.46.1 + '@typescript-eslint/typescript-estree': 8.46.1(typescript@5.9.3) + eslint: 9.37.0 + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.42.0': + '@typescript-eslint/visitor-keys@8.46.1': dependencies: - '@typescript-eslint/types': 8.42.0 - eslint-visitor-keys: 4.2.1 - - '@typescript-eslint/visitor-keys@8.43.0': - dependencies: - '@typescript-eslint/types': 8.43.0 + '@typescript-eslint/types': 8.46.1 eslint-visitor-keys: 4.2.1 '@unrs/resolver-binding-android-arm-eabi@1.11.1': @@ -6188,13 +6119,13 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true - '@vladfrangu/async_event_emitter@2.4.6': {} + '@vladfrangu/async_event_emitter@2.4.7': {} '@xhayper/discord-rpc@1.3.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)': dependencies: - '@discordjs/rest': 2.5.1 - '@vladfrangu/async_event_emitter': 2.4.6 - discord-api-types: 0.38.23 + '@discordjs/rest': 2.6.0 + '@vladfrangu/async_event_emitter': 2.4.7 + discord-api-types: 0.38.30 ws: 8.18.3(bufferutil@4.0.9)(utf-8-validate@6.0.5) transitivePeerDependencies: - bufferutil @@ -6220,7 +6151,7 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.4.1 + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -6260,7 +6191,7 @@ snapshots: ajv@8.17.1: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.0.6 + fast-uri: 3.1.0 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -6270,15 +6201,15 @@ snapshots: ansi-regex@5.0.1: {} - ansi-regex@6.1.0: {} + ansi-regex@6.2.2: {} ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 - ansi-styles@6.2.1: {} + ansi-styles@6.2.3: {} - ansis@4.1.0: {} + ansis@4.2.0: {} any-base@1.1.0: {} @@ -6300,7 +6231,7 @@ snapshots: builder-util-runtime: 9.3.1 chromium-pickle-js: 0.2.0 config-file-ts: 0.2.8-rc1 - debug: 4.4.1 + debug: 4.4.3 dmg-builder: 26.0.12(electron-builder-squirrel-windows@26.0.12) dotenv: 16.6.1 dotenv-expand: 11.0.7 @@ -6310,14 +6241,14 @@ snapshots: fs-extra: 10.1.0 hosted-git-info: 4.1.0 is-ci: 3.0.1 - isbinaryfile: 5.0.4 + isbinaryfile: 5.0.6 js-yaml: 4.1.0 json5: 2.2.3 lazy-val: 1.0.5 minimatch: 10.0.3 plist: 3.1.0 resedit: 1.7.2 - semver: 7.7.2 + semver: 7.7.3 tar: 6.2.1 temp-file: 3.4.0 tiny-async-pool: 1.3.0 @@ -6416,20 +6347,20 @@ snapshots: await-to-js@3.0.0: {} - babel-plugin-jsx-dom-expressions@0.40.1(@babel/core@7.28.3): + babel-plugin-jsx-dom-expressions@0.40.1(@babel/core@7.28.4): dependencies: - '@babel/core': 7.28.3 + '@babel/core': 7.28.4 '@babel/helper-module-imports': 7.18.6 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.3) - '@babel/types': 7.28.2 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.4) + '@babel/types': 7.28.4 html-entities: 2.3.3 parse5: 7.3.0 validate-html-nesting: 1.2.3 - babel-preset-solid@1.9.9(@babel/core@7.28.3)(solid-js@1.9.9): + babel-preset-solid@1.9.9(@babel/core@7.28.4)(solid-js@1.9.9): dependencies: - '@babel/core': 7.28.3 - babel-plugin-jsx-dom-expressions: 0.40.1(@babel/core@7.28.3) + '@babel/core': 7.28.4 + babel-plugin-jsx-dom-expressions: 0.40.1(@babel/core@7.28.4) optionalDependencies: solid-js: 1.9.9 @@ -6437,9 +6368,11 @@ snapshots: base64-js@1.5.1: {} + baseline-browser-mapping@2.8.17: {} + bgutils-js@3.2.0: {} - birpc@2.5.0: {} + birpc@2.6.1: {} bl@4.1.0: dependencies: @@ -6482,12 +6415,13 @@ snapshots: dependencies: fancy-regex: 0.5.4 - browserslist@4.25.2: + browserslist@4.26.3: dependencies: - caniuse-lite: 1.0.30001735 - electron-to-chromium: 1.5.203 - node-releases: 2.0.19 - update-browserslist-db: 1.1.3(browserslist@4.25.2) + baseline-browser-mapping: 2.8.17 + caniuse-lite: 1.0.30001751 + electron-to-chromium: 1.5.237 + node-releases: 2.0.25 + update-browserslist-db: 1.1.3(browserslist@4.26.3) buffer-crc32@0.2.13: {} @@ -6509,7 +6443,7 @@ snapshots: builder-util-runtime@9.3.1: dependencies: - debug: 4.4.1 + debug: 4.4.3 sax: 1.4.1 transitivePeerDependencies: - supports-color @@ -6522,7 +6456,7 @@ snapshots: builder-util-runtime: 9.3.1 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.1 + debug: 4.4.3 fs-extra: 10.1.0 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 @@ -6540,7 +6474,7 @@ snapshots: bundle-name@4.1.0: dependencies: - run-applescript: 7.0.0 + run-applescript: 7.1.0 butterchurn-presets@3.0.0-beta.4: dependencies: @@ -6593,7 +6527,7 @@ snapshots: minipass-pipeline: 1.2.4 p-map: 7.0.3 ssri: 12.0.0 - tar: 7.4.3 + tar: 7.5.1 unique-filename: 4.0.0 cacheable-lookup@5.0.4: {} @@ -6629,7 +6563,7 @@ snapshots: camelcase@7.0.1: {} - caniuse-lite@1.0.30001735: {} + caniuse-lite@1.0.30001751: {} chalk-template@0.4.0: dependencies: @@ -6654,7 +6588,7 @@ snapshots: clean-stack@2.2.0: {} - clean-stack@5.2.0: + clean-stack@5.3.0: dependencies: escape-string-regexp: 5.0.0 @@ -6696,22 +6630,22 @@ snapshots: dependencies: color-name: 1.1.4 - color-convert@3.1.0: + color-convert@3.1.2: dependencies: - color-name: 2.0.0 + color-name: 2.0.2 color-name@1.1.4: {} - color-name@2.0.0: {} + color-name@2.0.2: {} - color-string@2.0.1: + color-string@2.1.2: dependencies: - color-name: 2.0.0 + color-name: 2.0.2 - color@5.0.0: + color@5.0.2: dependencies: - color-convert: 3.1.0 - color-string: 2.0.1 + color-convert: 3.1.2 + color-string: 2.1.2 combined-stream@1.0.8: dependencies: @@ -6726,7 +6660,7 @@ snapshots: compare-version@0.1.2: {} - component-register@0.8.7: {} + component-register@0.8.8: {} compressible@2.0.18: dependencies: @@ -6746,22 +6680,22 @@ snapshots: concat-map@0.0.1: {} - conf@14.0.0: + conf@15.0.2: dependencies: ajv: 8.17.1 ajv-formats: 3.0.1(ajv@8.17.1) atomically: 2.0.3 debounce-fn: 6.0.0 - dot-prop: 9.0.0 + dot-prop: 10.1.0 env-paths: 3.0.0 json-schema-typed: 8.0.1 - semver: 7.7.2 - uint8array-extras: 1.4.1 + semver: 7.7.3 + uint8array-extras: 1.5.0 config-file-ts@0.2.8-rc1: dependencies: glob: 10.4.5 - typescript: 5.9.2 + typescript: 5.9.3 content-disposition@0.5.2: {} @@ -6780,7 +6714,7 @@ snapshots: cross-dirname@0.1.0: optional: true - cross-env@10.0.0: + cross-env@10.1.0: dependencies: '@epic-web/invariant': 1.0.0 cross-spawn: 7.0.6 @@ -6803,9 +6737,11 @@ snapshots: csstype@3.1.3: {} - custom-electron-prompt@1.5.8(electron@38.0.0): + custom-electron-prompt@1.5.8(electron@38.3.0): dependencies: - electron: 38.0.0 + electron: 38.3.0 + + dargs@7.0.0: {} data-uri-to-buffer@4.0.1: {} @@ -6839,7 +6775,7 @@ snapshots: dependencies: ms: 2.1.3 - debug@4.4.1: + debug@4.4.3: dependencies: ms: 2.1.3 @@ -6903,25 +6839,27 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 - del-cli@6.0.0: + del-cli@7.0.0: dependencies: - del: 8.0.0 - meow: 13.2.0 + del: 8.0.1 + meow: 14.0.0 + presentable-error: 0.0.1 - del@8.0.0: + del@8.0.1: dependencies: globby: 14.1.0 is-glob: 4.0.3 is-path-cwd: 3.0.0 is-path-inside: 4.0.0 p-map: 7.0.3 + presentable-error: 0.0.1 slash: 5.1.0 delay@6.0.0: {} delayed-stream@1.0.0: {} - detect-libc@2.0.4: {} + detect-libc@2.1.2: {} detect-node@2.1.0: optional: true @@ -6931,7 +6869,7 @@ snapshots: minimatch: 3.1.2 p-limit: 3.1.0 - discord-api-types@0.38.23: {} + discord-api-types@0.38.30: {} dmg-builder@26.0.12(electron-builder-squirrel-windows@26.0.12): dependencies: @@ -6982,9 +6920,9 @@ snapshots: domelementtype: 2.3.0 domhandler: 5.0.3 - dot-prop@9.0.0: + dot-prop@10.1.0: dependencies: - type-fest: 4.41.0 + type-fest: 5.1.0 dotenv-expand@11.0.7: dependencies: @@ -7063,7 +7001,7 @@ snapshots: electron-localshortcut@3.2.1: dependencies: - debug: 4.4.1 + debug: 4.4.3 electron-is-accelerator: 0.1.2 keyboardevent-from-electron-accelerator: 2.0.0 keyboardevents-areequal: 0.2.2 @@ -7083,16 +7021,16 @@ snapshots: transitivePeerDependencies: - supports-color - electron-store@10.1.0: + electron-store@11.0.2: dependencies: - conf: 14.0.0 - type-fest: 4.41.0 + conf: 15.0.2 + type-fest: 5.1.0 - electron-to-chromium@1.5.203: {} + electron-to-chromium@1.5.237: {} electron-unhandled@5.0.0: dependencies: - clean-stack: 5.2.0 + clean-stack: 5.3.0 electron-is-dev: 3.0.1 ensure-error: 4.0.0 lodash.debounce: 4.0.8 @@ -7106,27 +7044,27 @@ snapshots: lazy-val: 1.0.5 lodash.escaperegexp: 4.1.2 lodash.isequal: 4.5.0 - semver: 7.7.2 + semver: 7.7.3 tiny-typed-emitter: 2.1.0 transitivePeerDependencies: - supports-color - electron-vite@4.0.0(rolldown-vite@7.1.8(@types/node@24.3.0)(esbuild@0.25.9)(yaml@2.8.1)): + electron-vite@4.0.1(rolldown-vite@7.1.8(@types/node@24.8.0)(esbuild@0.25.11)(yaml@2.8.1)): dependencies: - '@babel/core': 7.28.3 - '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.28.3) + '@babel/core': 7.28.4 + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.28.4) cac: 6.7.14 - esbuild: 0.25.9 - magic-string: 0.30.17 + esbuild: 0.25.11 + magic-string: 0.30.19 picocolors: 1.1.1 - vite: rolldown-vite@7.1.8(@types/node@24.3.0)(esbuild@0.25.9)(yaml@2.8.1) + vite: rolldown-vite@7.1.8(@types/node@24.8.0)(esbuild@0.25.11)(yaml@2.8.1) transitivePeerDependencies: - supports-color electron-winstaller@5.4.0: dependencies: '@electron/asar': 3.4.1 - debug: 4.4.1 + debug: 4.4.3 fs-extra: 7.0.1 lodash: 4.17.21 temp: 0.9.4 @@ -7135,10 +7073,10 @@ snapshots: transitivePeerDependencies: - supports-color - electron@38.0.0: + electron@38.3.0: dependencies: '@electron/get': 2.0.3 - '@types/node': 22.17.2 + '@types/node': 22.18.10 extract-zip: 2.0.1 transitivePeerDependencies: - supports-color @@ -7243,7 +7181,7 @@ snapshots: isarray: 2.0.5 stop-iteration-iterator: 1.1.0 - es-hangul@2.3.5: {} + es-hangul@2.3.8: {} es-object-atoms@1.1.1: dependencies: @@ -7269,34 +7207,34 @@ snapshots: es6-error@4.1.1: optional: true - esbuild@0.25.9: + esbuild@0.25.11: optionalDependencies: - '@esbuild/aix-ppc64': 0.25.9 - '@esbuild/android-arm': 0.25.9 - '@esbuild/android-arm64': 0.25.9 - '@esbuild/android-x64': 0.25.9 - '@esbuild/darwin-arm64': 0.25.9 - '@esbuild/darwin-x64': 0.25.9 - '@esbuild/freebsd-arm64': 0.25.9 - '@esbuild/freebsd-x64': 0.25.9 - '@esbuild/linux-arm': 0.25.9 - '@esbuild/linux-arm64': 0.25.9 - '@esbuild/linux-ia32': 0.25.9 - '@esbuild/linux-loong64': 0.25.9 - '@esbuild/linux-mips64el': 0.25.9 - '@esbuild/linux-ppc64': 0.25.9 - '@esbuild/linux-riscv64': 0.25.9 - '@esbuild/linux-s390x': 0.25.9 - '@esbuild/linux-x64': 0.25.9 - '@esbuild/netbsd-arm64': 0.25.9 - '@esbuild/netbsd-x64': 0.25.9 - '@esbuild/openbsd-arm64': 0.25.9 - '@esbuild/openbsd-x64': 0.25.9 - '@esbuild/openharmony-arm64': 0.25.9 - '@esbuild/sunos-x64': 0.25.9 - '@esbuild/win32-arm64': 0.25.9 - '@esbuild/win32-ia32': 0.25.9 - '@esbuild/win32-x64': 0.25.9 + '@esbuild/aix-ppc64': 0.25.11 + '@esbuild/android-arm': 0.25.11 + '@esbuild/android-arm64': 0.25.11 + '@esbuild/android-x64': 0.25.11 + '@esbuild/darwin-arm64': 0.25.11 + '@esbuild/darwin-x64': 0.25.11 + '@esbuild/freebsd-arm64': 0.25.11 + '@esbuild/freebsd-x64': 0.25.11 + '@esbuild/linux-arm': 0.25.11 + '@esbuild/linux-arm64': 0.25.11 + '@esbuild/linux-ia32': 0.25.11 + '@esbuild/linux-loong64': 0.25.11 + '@esbuild/linux-mips64el': 0.25.11 + '@esbuild/linux-ppc64': 0.25.11 + '@esbuild/linux-riscv64': 0.25.11 + '@esbuild/linux-s390x': 0.25.11 + '@esbuild/linux-x64': 0.25.11 + '@esbuild/netbsd-arm64': 0.25.11 + '@esbuild/netbsd-x64': 0.25.11 + '@esbuild/openbsd-arm64': 0.25.11 + '@esbuild/openbsd-x64': 0.25.11 + '@esbuild/openharmony-arm64': 0.25.11 + '@esbuild/sunos-x64': 0.25.11 + '@esbuild/win32-arm64': 0.25.11 + '@esbuild/win32-ia32': 0.25.11 + '@esbuild/win32-x64': 0.25.11 escalade@3.2.0: {} @@ -7304,21 +7242,21 @@ snapshots: escape-string-regexp@5.0.0: {} - eslint-config-prettier@10.1.8(eslint@9.35.0): + eslint-config-prettier@10.1.8(eslint@9.37.0): dependencies: - eslint: 9.35.0 + eslint: 9.37.0 eslint-import-context@0.1.9(unrs-resolver@1.11.1): dependencies: - get-tsconfig: 4.10.1 + get-tsconfig: 4.12.0 stable-hash-x: 0.2.0 optionalDependencies: unrs-resolver: 1.11.1 - eslint-import-resolver-exports@1.0.0-beta.5(eslint-plugin-import@2.32.0)(eslint@9.35.0): + eslint-import-resolver-exports@1.0.0-beta.5(eslint-plugin-import@2.32.0)(eslint@9.37.0): dependencies: - eslint: 9.35.0 - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0)(typescript@5.9.2))(eslint-import-resolver-typescript@4.4.4)(eslint@9.35.0) + eslint: 9.37.0 + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.1(eslint@9.37.0)(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.37.0) resolve.exports: 2.0.3 eslint-import-resolver-node@0.3.9: @@ -7329,33 +7267,33 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@4.4.4(eslint-plugin-import@2.32.0)(eslint@9.35.0): + eslint-import-resolver-typescript@4.4.4(eslint-plugin-import@2.32.0)(eslint@9.37.0): dependencies: - debug: 4.4.1 - eslint: 9.35.0 + debug: 4.4.3 + eslint: 9.37.0 eslint-import-context: 0.1.9(unrs-resolver@1.11.1) - get-tsconfig: 4.10.1 + get-tsconfig: 4.12.0 is-bun-module: 2.0.0 stable-hash-x: 0.2.0 - tinyglobby: 0.2.14 + tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0)(typescript@5.9.2))(eslint-import-resolver-typescript@4.4.4)(eslint@9.35.0) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.1(eslint@9.37.0)(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.37.0) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.43.0(eslint@9.35.0)(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.35.0): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.46.1(eslint@9.37.0)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.37.0): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.43.0(eslint@9.35.0)(typescript@5.9.2) - eslint: 9.35.0 + '@typescript-eslint/parser': 8.46.1(eslint@9.37.0)(typescript@5.9.3) + eslint: 9.37.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 4.4.4(eslint-plugin-import@2.32.0)(eslint@9.35.0) + eslint-import-resolver-typescript: 4.4.4(eslint-plugin-import@2.32.0)(eslint@9.37.0) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0)(typescript@5.9.2))(eslint-import-resolver-typescript@4.4.4)(eslint@9.35.0): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.1(eslint@9.37.0)(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.37.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -7364,9 +7302,9 @@ snapshots: array.prototype.flatmap: 1.3.3 debug: 3.2.7 doctrine: 2.1.0 - eslint: 9.35.0 + eslint: 9.37.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.43.0(eslint@9.35.0)(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.35.0) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.46.1(eslint@9.37.0)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.37.0) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -7378,31 +7316,31 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.43.0(eslint@9.35.0)(typescript@5.9.2) + '@typescript-eslint/parser': 8.46.1(eslint@9.37.0)(typescript@5.9.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-prettier@5.5.4(eslint-config-prettier@10.1.8(eslint@9.35.0))(eslint@9.35.0)(prettier@3.6.2): + eslint-plugin-prettier@5.5.4(eslint-config-prettier@10.1.8(eslint@9.37.0))(eslint@9.37.0)(prettier@3.6.2): dependencies: - eslint: 9.35.0 + eslint: 9.37.0 prettier: 3.6.2 prettier-linter-helpers: 1.0.0 synckit: 0.11.11 optionalDependencies: - eslint-config-prettier: 10.1.8(eslint@9.35.0) + eslint-config-prettier: 10.1.8(eslint@9.37.0) - eslint-plugin-solid@0.14.5(eslint@9.35.0)(typescript@5.9.2): + eslint-plugin-solid@0.14.5(eslint@9.37.0)(typescript@5.9.3): dependencies: - '@typescript-eslint/utils': 8.42.0(eslint@9.35.0)(typescript@5.9.2) - eslint: 9.35.0 + '@typescript-eslint/utils': 8.46.1(eslint@9.37.0)(typescript@5.9.3) + eslint: 9.37.0 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.9.2 + style-to-object: 1.0.11 + typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -7415,17 +7353,17 @@ snapshots: eslint-visitor-keys@4.2.1: {} - eslint@9.35.0: + eslint@9.37.0: dependencies: - '@eslint-community/eslint-utils': 4.8.0(eslint@9.35.0) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.37.0) '@eslint-community/regexpp': 4.12.1 '@eslint/config-array': 0.21.0 - '@eslint/config-helpers': 0.3.1 - '@eslint/core': 0.15.2 + '@eslint/config-helpers': 0.4.0 + '@eslint/core': 0.16.0 '@eslint/eslintrc': 3.3.1 - '@eslint/js': 9.35.0 - '@eslint/plugin-kit': 0.3.5 - '@humanfs/node': 0.16.6 + '@eslint/js': 9.37.0 + '@eslint/plugin-kit': 0.4.0 + '@humanfs/node': 0.16.7 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 '@types/estree': 1.0.8 @@ -7433,7 +7371,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.1 + debug: 4.4.3 escape-string-regexp: 4.0.0 eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 @@ -7503,11 +7441,11 @@ snapshots: exif-parser@0.1.12: {} - exponential-backoff@3.1.2: {} + exponential-backoff@3.1.3: {} extract-zip@2.0.1: dependencies: - debug: 4.4.1 + debug: 4.4.3 get-stream: 5.2.0 yauzl: 2.10.0 optionalDependencies: @@ -7526,7 +7464,7 @@ snapshots: fast-diff@1.3.0: {} - fast-equals@5.2.2: {} + fast-equals@5.3.2: {} fast-glob@3.3.3: dependencies: @@ -7540,7 +7478,7 @@ snapshots: fast-levenshtein@2.0.6: {} - fast-uri@3.0.6: {} + fast-uri@3.1.0: {} fastq@1.19.1: dependencies: @@ -7575,11 +7513,11 @@ snapshots: dependencies: minimatch: 5.1.6 - filename-reserved-regex@3.0.0: {} + filename-reserved-regex@4.0.0: {} - filenamify@6.0.0: + filenamify@7.0.0: dependencies: - filename-reserved-regex: 3.0.0 + filename-reserved-regex: 4.0.0 fill-range@7.1.1: dependencies: @@ -7626,7 +7564,7 @@ snapshots: jsonfile: 6.2.0 universalify: 2.0.1 - fs-extra@11.3.1: + fs-extra@11.3.2: dependencies: graceful-fs: 4.2.11 jsonfile: 6.2.0 @@ -7681,6 +7619,8 @@ snapshots: functions-have-names@1.2.3: {} + generator-function@2.0.1: {} + gensync@1.0.0-beta.2: {} get-caller-file@2.0.5: {} @@ -7715,7 +7655,7 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 - get-tsconfig@4.10.1: + get-tsconfig@4.12.0: dependencies: resolve-pkg-maps: 1.0.0 @@ -7773,7 +7713,7 @@ snapshots: es6-error: 4.1.1 matcher: 3.0.0 roarr: 2.15.4 - semver: 7.7.2 + semver: 7.7.3 serialize-error: 7.0.1 optional: true @@ -7793,7 +7733,7 @@ snapshots: slash: 5.1.0 unicorn-magic: 0.3.0 - goober@2.1.16(csstype@3.1.3): + goober@2.1.18(csstype@3.1.3): dependencies: csstype: 3.1.3 @@ -7819,9 +7759,9 @@ snapshots: hanja@1.1.5: {} - happy-dom@18.0.1: + happy-dom@20.0.2: dependencies: - '@types/node': 20.19.11 + '@types/node': 20.19.21 '@types/whatwg-mimetype': 3.0.2 whatwg-mimetype: 3.0.0 @@ -7849,7 +7789,7 @@ snapshots: he@1.2.0: {} - hono@4.9.6: {} + hono@4.9.12: {} hosted-git-info@4.1.0: dependencies: @@ -7882,14 +7822,14 @@ snapshots: dependencies: '@tootallnate/once': 2.0.0 agent-base: 6.0.2 - debug: 4.4.1 + debug: 4.4.3 transitivePeerDependencies: - supports-color http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.4 - debug: 4.4.1 + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -7901,14 +7841,14 @@ snapshots: https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.4.1 + debug: 4.4.3 transitivePeerDependencies: - supports-color https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.4 - debug: 4.4.1 + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -7918,11 +7858,11 @@ snapshots: dependencies: ms: 2.1.3 - i18next@25.5.2(typescript@5.9.2): + i18next@25.6.0(typescript@5.9.3): dependencies: '@babel/runtime': 7.28.4 optionalDependencies: - typescript: 5.9.2 + typescript: 5.9.3 iconv-corefoundation@1.1.7: dependencies: @@ -8010,7 +7950,7 @@ snapshots: is-bun-module@2.0.0: dependencies: - semver: 7.7.2 + semver: 7.7.3 is-callable@1.2.7: {} @@ -8045,9 +7985,10 @@ snapshots: is-fullwidth-code-point@3.0.0: {} - is-generator-function@1.1.0: + is-generator-function@1.1.2: dependencies: call-bound: 1.0.4 + generator-function: 2.0.1 get-proto: 1.0.1 has-tostringtag: 1.0.2 safe-regex-test: 1.1.0 @@ -8119,7 +8060,7 @@ snapshots: is-unicode-supported@0.1.0: {} - is-url@1.2.4: {} + is-unix@2.0.13: {} is-weakmap@2.0.2: {} @@ -8148,7 +8089,7 @@ snapshots: isbinaryfile@4.0.10: {} - isbinaryfile@5.0.4: {} + isbinaryfile@5.0.6: {} isexe@2.0.0: {} @@ -8200,10 +8141,6 @@ snapshots: '@jimp/types': 1.6.0 '@jimp/utils': 1.6.0 - jintr@3.3.1: - dependencies: - acorn: 8.15.0 - jpeg-js@0.4.4: {} js-tokens@4.0.0: {} @@ -8295,50 +8232,54 @@ snapshots: dependencies: immediate: 3.0.6 - lightningcss-darwin-arm64@1.30.1: + lightningcss-android-arm64@1.30.2: + optional: true + + lightningcss-darwin-arm64@1.30.2: optional: true - lightningcss-darwin-x64@1.30.1: + lightningcss-darwin-x64@1.30.2: optional: true - lightningcss-freebsd-x64@1.30.1: + lightningcss-freebsd-x64@1.30.2: optional: true - lightningcss-linux-arm-gnueabihf@1.30.1: + lightningcss-linux-arm-gnueabihf@1.30.2: optional: true - lightningcss-linux-arm64-gnu@1.30.1: + lightningcss-linux-arm64-gnu@1.30.2: optional: true - lightningcss-linux-arm64-musl@1.30.1: + lightningcss-linux-arm64-musl@1.30.2: optional: true - lightningcss-linux-x64-gnu@1.30.1: + lightningcss-linux-x64-gnu@1.30.2: optional: true - lightningcss-linux-x64-musl@1.30.1: + lightningcss-linux-x64-musl@1.30.2: optional: true - lightningcss-win32-arm64-msvc@1.30.1: + lightningcss-win32-arm64-msvc@1.30.2: optional: true - lightningcss-win32-x64-msvc@1.30.1: + lightningcss-win32-x64-msvc@1.30.2: optional: true - lightningcss@1.30.1: + lightningcss@1.30.2: dependencies: - detect-libc: 2.0.4 + detect-libc: 2.1.2 optionalDependencies: - lightningcss-darwin-arm64: 1.30.1 - lightningcss-darwin-x64: 1.30.1 - lightningcss-freebsd-x64: 1.30.1 - lightningcss-linux-arm-gnueabihf: 1.30.1 - lightningcss-linux-arm64-gnu: 1.30.1 - lightningcss-linux-arm64-musl: 1.30.1 - lightningcss-linux-x64-gnu: 1.30.1 - lightningcss-linux-x64-musl: 1.30.1 - lightningcss-win32-arm64-msvc: 1.30.1 - lightningcss-win32-x64-msvc: 1.30.1 + lightningcss-android-arm64: 1.30.2 + lightningcss-darwin-arm64: 1.30.2 + lightningcss-darwin-x64: 1.30.2 + lightningcss-freebsd-x64: 1.30.2 + lightningcss-linux-arm-gnueabihf: 1.30.2 + lightningcss-linux-arm64-gnu: 1.30.2 + lightningcss-linux-arm64-musl: 1.30.2 + lightningcss-linux-x64-gnu: 1.30.2 + lightningcss-linux-x64-musl: 1.30.2 + lightningcss-win32-arm64-msvc: 1.30.2 + lightningcss-win32-x64-msvc: 1.30.2 lit-element@4.2.1: dependencies: @@ -8381,7 +8322,7 @@ snapshots: lru-cache@10.4.3: {} - lru-cache@11.1.0: {} + lru-cache@11.2.2: {} lru-cache@5.1.1: dependencies: @@ -8395,7 +8336,7 @@ snapshots: magic-bytes.js@1.12.1: {} - magic-string@0.30.17: + magic-string@0.30.19: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -8460,7 +8401,7 @@ snapshots: ssr-window: 5.0.1 tslib: 2.8.1 - meow@13.2.0: {} + meow@14.0.0: {} merge-anything@5.1.7: dependencies: @@ -8470,6 +8411,8 @@ snapshots: merge2@1.4.1: {} + meriyah@6.1.4: {} + micromatch@4.0.8: dependencies: braces: 3.0.3 @@ -8539,7 +8482,7 @@ snapshots: dependencies: minipass: 7.1.2 minipass-sized: 1.0.3 - minizlib: 3.0.2 + minizlib: 3.1.0 optionalDependencies: encoding: 0.1.13 @@ -8568,7 +8511,7 @@ snapshots: minipass: 3.3.6 yallist: 4.0.0 - minizlib@3.0.2: + minizlib@3.1.0: dependencies: minipass: 7.1.2 @@ -8578,8 +8521,6 @@ snapshots: mkdirp@1.0.4: {} - mkdirp@3.0.1: {} - mrmime@2.0.1: {} ms@2.0.0: {} @@ -8588,7 +8529,7 @@ snapshots: nanoid@3.3.11: {} - napi-postinstall@0.3.3: {} + napi-postinstall@0.3.4: {} natural-compare@1.4.0: {} @@ -8596,16 +8537,18 @@ snapshots: negotiator@1.0.0: {} - node-abi@3.75.0: + node-abi@3.78.0: dependencies: - semver: 7.7.2 + semver: 7.7.3 + + node-abort-controller@3.1.1: {} node-addon-api@1.7.2: optional: true node-api-version@0.2.1: dependencies: - semver: 7.7.2 + semver: 7.7.3 node-domexception@1.0.0: {} @@ -8620,14 +8563,14 @@ snapshots: node-gyp@11.4.2: dependencies: env-paths: 2.2.1 - exponential-backoff: 3.1.2 + exponential-backoff: 3.1.3 graceful-fs: 4.2.11 make-fetch-happen: 14.0.3 nopt: 8.1.0 proc-log: 5.0.0 - semver: 7.7.2 - tar: 7.4.3 - tinyglobby: 0.2.14 + semver: 7.7.3 + tar: 7.5.1 + tinyglobby: 0.2.15 which: 5.0.0 transitivePeerDependencies: - supports-color @@ -8641,7 +8584,7 @@ snapshots: dependencies: iconv-lite: 0.6.2 - node-releases@2.0.19: {} + node-releases@2.0.25: {} nopt@6.0.0: dependencies: @@ -8814,7 +8757,7 @@ snapshots: path-scurry@2.0.0: dependencies: - lru-cache: 11.1.0 + lru-cache: 11.2.2 minipass: 7.1.2 path-to-regexp@3.3.0: {} @@ -8856,11 +8799,11 @@ snapshots: dependencies: pngjs: 6.0.0 - playwright-core@1.55.0: {} + playwright-core@1.56.0: {} - playwright@1.55.0: + playwright@1.56.0: dependencies: - playwright-core: 1.55.0 + playwright-core: 1.56.0 optionalDependencies: fsevents: 2.3.2 @@ -8889,6 +8832,8 @@ snapshots: prelude-ls@1.2.1: {} + presentable-error@0.0.1: {} + prettier-linter-helpers@1.0.0: dependencies: fast-diff: 1.3.0 @@ -8934,7 +8879,7 @@ snapshots: read-binary-file-arch@1.0.6: dependencies: - debug: 4.4.1 + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -8977,8 +8922,6 @@ snapshots: get-proto: 1.0.1 which-builtin-type: 1.2.1 - regenerator-runtime@0.13.11: {} - regexp.prototype.flags@1.5.4: dependencies: call-bind: 1.0.8 @@ -9050,17 +8993,17 @@ snapshots: sprintf-js: 1.1.3 optional: true - rolldown-vite@7.1.8(@types/node@24.3.0)(esbuild@0.25.9)(yaml@2.8.1): + rolldown-vite@7.1.8(@types/node@24.8.0)(esbuild@0.25.11)(yaml@2.8.1): dependencies: fdir: 6.5.0(picomatch@4.0.3) - lightningcss: 1.30.1 + lightningcss: 1.30.2 picomatch: 4.0.3 postcss: 8.5.6 rolldown: 1.0.0-beta.36 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 24.3.0 - esbuild: 0.25.9 + '@types/node': 24.8.0 + esbuild: 0.25.11 fsevents: 2.3.3 yaml: 2.8.1 @@ -9069,7 +9012,7 @@ snapshots: '@oxc-project/runtime': 0.87.0 '@oxc-project/types': 0.87.0 '@rolldown/pluginutils': 1.0.0-beta.36 - ansis: 4.1.0 + ansis: 4.2.0 optionalDependencies: '@rolldown/binding-android-arm64': 1.0.0-beta.36 '@rolldown/binding-darwin-arm64': 1.0.0-beta.36 @@ -9086,7 +9029,7 @@ snapshots: '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.36 '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.36 - run-applescript@7.0.0: {} + run-applescript@7.1.0: {} run-parallel@1.2.0: dependencies: @@ -9136,7 +9079,7 @@ snapshots: semver@6.3.1: {} - semver@7.7.2: {} + semver@7.7.3: {} serialize-error@11.0.3: dependencies: @@ -9147,7 +9090,7 @@ snapshots: type-fest: 0.13.1 optional: true - seroval-plugins@1.3.2(seroval@1.3.2): + seroval-plugins@1.3.3(seroval@1.3.2): dependencies: seroval: 1.3.2 @@ -9243,11 +9186,11 @@ snapshots: simple-update-notifier@2.0.0: dependencies: - semver: 7.7.2 + semver: 7.7.3 simple-xml-to-json@1.2.3: {} - sirv@3.0.1: + sirv@3.0.2: dependencies: '@polka/url': 1.0.0-next.29 mrmime: 2.0.1 @@ -9267,7 +9210,7 @@ snapshots: socks-proxy-agent@7.0.0: dependencies: agent-base: 6.0.2 - debug: 4.4.1 + debug: 4.4.3 socks: 2.8.7 transitivePeerDependencies: - supports-color @@ -9275,7 +9218,7 @@ snapshots: socks-proxy-agent@8.0.5: dependencies: agent-base: 7.1.4 - debug: 4.4.1 + debug: 4.4.3 socks: 2.8.7 transitivePeerDependencies: - supports-color @@ -9287,7 +9230,7 @@ snapshots: solid-element@1.9.1(solid-js@1.9.9): dependencies: - component-register: 0.8.7 + component-register: 0.8.8 solid-js: 1.9.9 solid-floating-ui@0.3.1(@floating-ui/dom@1.7.4)(solid-js@1.9.9): @@ -9299,13 +9242,13 @@ snapshots: dependencies: csstype: 3.1.3 seroval: 1.3.2 - seroval-plugins: 1.3.2(seroval@1.3.2) + seroval-plugins: 1.3.3(seroval@1.3.2) solid-refresh@0.6.3(solid-js@1.9.9): dependencies: '@babel/generator': 7.28.3 '@babel/helper-module-imports': 7.27.1 - '@babel/types': 7.28.2 + '@babel/types': 7.28.4 solid-js: 1.9.9 transitivePeerDependencies: - supports-color @@ -9313,7 +9256,7 @@ snapshots: solid-styled-components@0.28.5(solid-js@1.9.9): dependencies: csstype: 3.1.3 - goober: 2.1.16(csstype@3.1.3) + goober: 2.1.18(csstype@3.1.3) solid-js: 1.9.9 solid-transition-group@0.3.0(solid-js@1.9.9): @@ -9372,7 +9315,7 @@ snapshots: dependencies: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 - strip-ansi: 7.1.0 + strip-ansi: 7.1.2 string.prototype.trim@1.2.10: dependencies: @@ -9409,9 +9352,9 @@ snapshots: dependencies: ansi-regex: 5.0.1 - strip-ansi@7.1.0: + strip-ansi@7.1.2: dependencies: - ansi-regex: 6.1.0 + ansi-regex: 6.2.2 strip-bom@3.0.0: {} @@ -9428,13 +9371,13 @@ snapshots: stubborn-fs@1.2.5: {} - style-to-object@1.0.9: + style-to-object@1.0.11: dependencies: inline-style-parser: 0.2.4 sumchecker@3.0.1: dependencies: - debug: 4.4.1 + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -9448,6 +9391,8 @@ snapshots: dependencies: '@pkgr/core': 0.2.9 + tagged-tag@1.0.0: {} + tar@6.2.1: dependencies: chownr: 2.0.0 @@ -9457,13 +9402,12 @@ snapshots: mkdirp: 1.0.4 yallist: 4.0.0 - tar@7.4.3: + tar@7.5.1: dependencies: '@isaacs/fs-minipass': 4.0.1 chownr: 3.0.0 minipass: 7.1.2 - minizlib: 3.0.2 - mkdirp: 3.0.1 + minizlib: 3.1.0 yallist: 5.0.0 temp-file@3.4.0: @@ -9488,11 +9432,6 @@ snapshots: tinycolor2@1.6.0: {} - tinyglobby@0.2.14: - dependencies: - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.3) @@ -9500,11 +9439,11 @@ snapshots: tinyld@1.3.4: {} - tldts-core@7.0.12: {} + tldts-core@7.0.17: {} - tldts-experimental@7.0.12: + tldts-experimental@7.0.17: dependencies: - tldts-core: 7.0.12 + tldts-core: 7.0.17 tmp-promise@3.0.3: dependencies: @@ -9527,13 +9466,13 @@ snapshots: dependencies: utf8-byte-length: 1.0.5 - ts-api-utils@2.1.0(typescript@5.9.2): + ts-api-utils@2.1.0(typescript@5.9.3): dependencies: - typescript: 5.9.2 + typescript: 5.9.3 - ts-morph@27.0.0: + ts-morph@27.0.2: dependencies: - '@ts-morph/common': 0.28.0 + '@ts-morph/common': 0.28.1 code-block-writer: 13.0.3 tsconfig-paths@3.15.0: @@ -9554,7 +9493,9 @@ snapshots: type-fest@2.19.0: {} - type-fest@4.41.0: {} + type-fest@5.1.0: + dependencies: + tagged-tag: 1.0.0 typed-array-buffer@1.0.3: dependencies: @@ -9589,20 +9530,20 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript-eslint@8.43.0(eslint@9.35.0)(typescript@5.9.2): + typescript-eslint@8.46.1(eslint@9.37.0)(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.43.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0)(typescript@5.9.2))(eslint@9.35.0)(typescript@5.9.2) - '@typescript-eslint/parser': 8.43.0(eslint@9.35.0)(typescript@5.9.2) - '@typescript-eslint/typescript-estree': 8.43.0(typescript@5.9.2) - '@typescript-eslint/utils': 8.43.0(eslint@9.35.0)(typescript@5.9.2) - eslint: 9.35.0 - typescript: 5.9.2 + '@typescript-eslint/eslint-plugin': 8.46.1(@typescript-eslint/parser@8.46.1(eslint@9.37.0)(typescript@5.9.3))(eslint@9.37.0)(typescript@5.9.3) + '@typescript-eslint/parser': 8.46.1(eslint@9.37.0)(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.46.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.1(eslint@9.37.0)(typescript@5.9.3) + eslint: 9.37.0 + typescript: 5.9.3 transitivePeerDependencies: - supports-color - typescript@5.9.2: {} + typescript@5.9.3: {} - uint8array-extras@1.4.1: {} + uint8array-extras@1.5.0: {} unbox-primitive@1.1.0: dependencies: @@ -9613,7 +9554,7 @@ snapshots: undici-types@6.21.0: {} - undici-types@7.10.0: {} + undici-types@7.14.0: {} undici@6.21.3: {} @@ -9639,14 +9580,14 @@ snapshots: universalify@2.0.1: {} - unplugin-utils@0.3.0: + unplugin-utils@0.3.1: dependencies: pathe: 2.0.3 picomatch: 4.0.3 unrs-resolver@1.11.1: dependencies: - napi-postinstall: 0.3.3 + napi-postinstall: 0.3.4 optionalDependencies: '@unrs/resolver-binding-android-arm-eabi': 1.11.1 '@unrs/resolver-binding-android-arm64': 1.11.1 @@ -9674,9 +9615,9 @@ snapshots: mkdirp: 0.5.6 yaku: 0.16.7 - update-browserslist-db@1.1.3(browserslist@4.25.2): + update-browserslist-db@1.1.3(browserslist@4.26.3): dependencies: - browserslist: 4.25.2 + browserslist: 4.26.3 escalade: 3.2.0 picocolors: 1.1.1 @@ -9714,32 +9655,32 @@ snapshots: extsprintf: 1.4.1 optional: true - virtua@0.42.3(solid-js@1.9.9): + virtua@0.45.3(solid-js@1.9.9): optionalDependencies: solid-js: 1.9.9 - vite-dev-rpc@1.1.0(rolldown-vite@7.1.8(@types/node@24.3.0)(esbuild@0.25.9)(yaml@2.8.1)): + vite-dev-rpc@1.1.0(rolldown-vite@7.1.8(@types/node@24.8.0)(esbuild@0.25.11)(yaml@2.8.1)): dependencies: - birpc: 2.5.0 - vite: rolldown-vite@7.1.8(@types/node@24.3.0)(esbuild@0.25.9)(yaml@2.8.1) - vite-hot-client: 2.1.0(rolldown-vite@7.1.8(@types/node@24.3.0)(esbuild@0.25.9)(yaml@2.8.1)) + birpc: 2.6.1 + vite: rolldown-vite@7.1.8(@types/node@24.8.0)(esbuild@0.25.11)(yaml@2.8.1) + vite-hot-client: 2.1.0(rolldown-vite@7.1.8(@types/node@24.8.0)(esbuild@0.25.11)(yaml@2.8.1)) - vite-hot-client@2.1.0(rolldown-vite@7.1.8(@types/node@24.3.0)(esbuild@0.25.9)(yaml@2.8.1)): + vite-hot-client@2.1.0(rolldown-vite@7.1.8(@types/node@24.8.0)(esbuild@0.25.11)(yaml@2.8.1)): dependencies: - vite: rolldown-vite@7.1.8(@types/node@24.3.0)(esbuild@0.25.9)(yaml@2.8.1) + vite: rolldown-vite@7.1.8(@types/node@24.8.0)(esbuild@0.25.11)(yaml@2.8.1) - vite-plugin-inspect@11.3.3(rolldown-vite@7.1.8(@types/node@24.3.0)(esbuild@0.25.9)(yaml@2.8.1)): + vite-plugin-inspect@11.3.3(rolldown-vite@7.1.8(@types/node@24.8.0)(esbuild@0.25.11)(yaml@2.8.1)): dependencies: - ansis: 4.1.0 - debug: 4.4.1 + ansis: 4.2.0 + debug: 4.4.3 error-stack-parser-es: 1.0.5 ohash: 2.0.11 open: 10.2.0 perfect-debounce: 2.0.0 - sirv: 3.0.1 - unplugin-utils: 0.3.0 - vite: rolldown-vite@7.1.8(@types/node@24.3.0)(esbuild@0.25.9)(yaml@2.8.1) - vite-dev-rpc: 1.1.0(rolldown-vite@7.1.8(@types/node@24.3.0)(esbuild@0.25.9)(yaml@2.8.1)) + sirv: 3.0.2 + unplugin-utils: 0.3.1 + vite: rolldown-vite@7.1.8(@types/node@24.8.0)(esbuild@0.25.11)(yaml@2.8.1) + vite-dev-rpc: 1.1.0(rolldown-vite@7.1.8(@types/node@24.8.0)(esbuild@0.25.11)(yaml@2.8.1)) transitivePeerDependencies: - supports-color @@ -9747,22 +9688,22 @@ snapshots: dependencies: lib-esm: 0.4.2 - vite-plugin-solid@2.11.8(rolldown-vite@7.1.8(@types/node@24.3.0)(esbuild@0.25.9)(yaml@2.8.1))(solid-js@1.9.9): + vite-plugin-solid@2.11.9(rolldown-vite@7.1.8(@types/node@24.8.0)(esbuild@0.25.11)(yaml@2.8.1))(solid-js@1.9.9): dependencies: - '@babel/core': 7.28.3 + '@babel/core': 7.28.4 '@types/babel__core': 7.20.5 - babel-preset-solid: 1.9.9(@babel/core@7.28.3)(solid-js@1.9.9) + babel-preset-solid: 1.9.9(@babel/core@7.28.4)(solid-js@1.9.9) merge-anything: 5.1.7 solid-js: 1.9.9 solid-refresh: 0.6.3(solid-js@1.9.9) - vite: rolldown-vite@7.1.8(@types/node@24.3.0)(esbuild@0.25.9)(yaml@2.8.1) - vitefu: 1.1.1(rolldown-vite@7.1.8(@types/node@24.3.0)(esbuild@0.25.9)(yaml@2.8.1)) + vite: rolldown-vite@7.1.8(@types/node@24.8.0)(esbuild@0.25.11)(yaml@2.8.1) + vitefu: 1.1.1(rolldown-vite@7.1.8(@types/node@24.8.0)(esbuild@0.25.11)(yaml@2.8.1)) transitivePeerDependencies: - supports-color - vitefu@1.1.1(rolldown-vite@7.1.8(@types/node@24.3.0)(esbuild@0.25.9)(yaml@2.8.1)): + vitefu@1.1.1(rolldown-vite@7.1.8(@types/node@24.8.0)(esbuild@0.25.11)(yaml@2.8.1)): optionalDependencies: - vite: rolldown-vite@7.1.8(@types/node@24.3.0)(esbuild@0.25.9)(yaml@2.8.1) + vite: rolldown-vite@7.1.8(@types/node@24.8.0)(esbuild@0.25.11)(yaml@2.8.1) vudio@2.1.1(patch_hash=0e06c2ed11c02bdc490c209fa80070e98517c2735c641f5738b6e15d7dc1959d): {} @@ -9796,7 +9737,7 @@ snapshots: is-async-function: 2.1.1 is-date-object: 1.1.0 is-finalizationregistry: 1.1.1 - is-generator-function: 1.1.0 + is-generator-function: 1.1.2 is-regex: 1.2.1 is-weakref: 1.1.1 isarray: 2.0.5 @@ -9843,9 +9784,9 @@ snapshots: wrap-ansi@8.1.0: dependencies: - ansi-styles: 6.2.1 + ansi-styles: 6.2.3 string-width: 5.1.2 - strip-ansi: 7.1.0 + strip-ansi: 7.1.2 wrappy@1.0.2: {} @@ -9904,14 +9845,21 @@ snapshots: yocto-queue@0.1.0: {} - youtubei.js@15.0.1: + youtubei.js@16.0.1: dependencies: - '@bufbuild/protobuf': 2.6.3 - jintr: 3.3.1 - undici: 6.21.3 + '@bufbuild/protobuf': 2.9.0 + meriyah: 6.1.4 + + yt-dlp-exec@1.0.2: + dependencies: + dargs: 7.0.0 + execa: 5.1.1 + is-unix: 2.0.13 + mkdirp: 1.0.4 + node-fetch: 3.3.2 zlibjs@0.3.1: {} zod@3.25.76: {} - zod@4.1.5: {} + zod@4.1.12: {} diff --git a/src/config/store.ts b/src/config/store.ts index f371293a13..4a0720eb68 100644 --- a/src/config/store.ts +++ b/src/config/store.ts @@ -11,6 +11,42 @@ export type IStore = InstanceType< >; const migrations = { + '>=3.12.0'(store: IStore) { + // Synced Lyrics: migrate placeholder defaults + const syncedLyricsConfig = store.get('plugins.synced-lyrics') as + | SyncedLyricsPluginConfig + | undefined; + + if (!syncedLyricsConfig) return; + + const current = syncedLyricsConfig.defaultTextString as + | string + | string[] + | undefined; + let updatedValue: string | string[] | undefined; + + if (Array.isArray(current)) { + const asJson = JSON.stringify(current); + // Migrate progressive sequences to non-cumulative variants + if (asJson === JSON.stringify(['•', '••', '•••'])) { + updatedValue = ['•', '•', '•']; + } else if (asJson === JSON.stringify(['.', '..', '...'])) { + updatedValue = ['.', '.', '.']; + } + } else if (typeof current === 'string') { + // Replace regular space placeholder with NBSP to preserve layout + if (current === ' ') { + updatedValue = '\u00A0'; + } + } + + if (updatedValue !== undefined) { + store.set('plugins.synced-lyrics', { + ...syncedLyricsConfig, + defaultTextString: updatedValue, + } as SyncedLyricsPluginConfig); + } + }, '>=3.10.0'(store: IStore) { const lyricGeniusConfig = store.get('plugins.lyrics-genius') as | { diff --git a/src/i18n/resources/en.json b/src/i18n/resources/en.json index 4cf482aa3b..a501ebde73 100644 --- a/src/i18n/resources/en.json +++ b/src/i18n/resources/en.json @@ -214,21 +214,21 @@ } }, "plugins": { - "ad-speedup": { - "description": "If an ad play it mutes the audio and sets playback speed to 16x", - "name": "Ad Speedup" - }, - "adblocker": { - "description": "Block all ads and tracking out of the box", + + "adblocker": { + "name": "Ad Blocker", + "description": "Automatically blocks ads and trackers using community-powered filter lists that stay up-to-date. Includes advanced blocking for YouTube.", "menu": { - "blocker": "Blocker" - }, - "name": "Ad Blocker" + "enableYoutubeSpecificBlocking": "Enable Aggressive YouTube Blocking" + } }, + "album-actions": { "description": "Adds Undislike, Dislike, Like, and Unlike buttons to apply this to all songs in a playlist or album", "name": "Album Actions" }, + + "album-color-theme": { "description": "Applies a dynamic theme and visual effects based on the album color palette", "menu": { @@ -828,6 +828,10 @@ "line-effect": { "label": "Line effect", "submenu": { + "enhanced": { + "label": "Enhanced", + "tooltip": "A refined lyric effect for smoother, more enjoyable reading." + }, "fancy": { "label": "Fancy", "tooltip": "Use large, app-like effects on the current line" @@ -847,6 +851,10 @@ }, "tooltip": "Choose the effect to apply to the current line" }, + "show-empty-line-symbols": { + "label": "Show character between lyrics", + "tooltip": "Choose whether to always display the character between empty lyric lines." + }, "precise-timing": { "label": "Make the lyrics perfectly synced", "tooltip": "Calculate to the milisecond the display of the next line (can have a small impact on performance)" diff --git a/src/plugins/adblocker/.gitignore b/src/plugins/adblocker/.gitignore new file mode 100644 index 0000000000..af053a2be1 --- /dev/null +++ b/src/plugins/adblocker/.gitignore @@ -0,0 +1 @@ +/ad-blocker-engine.bin diff --git a/src/plugins/adblocker/adblocker.service.ts b/src/plugins/adblocker/adblocker.service.ts new file mode 100644 index 0000000000..3b5f830999 --- /dev/null +++ b/src/plugins/adblocker/adblocker.service.ts @@ -0,0 +1,140 @@ +import path from 'node:path'; +import fs, { promises as fsPromises } from 'node:fs'; +import { createHash } from 'node:crypto'; +import { app, net, Session } from 'electron'; +import { ElectronBlocker } from '@ghostery/adblocker-electron'; +import { DEFAULT_BLOCKLISTS } from './lists.config'; + +const TWENTY_FOUR_HOURS_IN_MS = 24 * 60 * 60 * 1000; + +export class AdBlockerService { + private blocker: ElectronBlocker | undefined; + private session: Session; + private updateInterval: NodeJS.Timeout | undefined; + private isRunning = false; + + constructor(session: Session) { + this.session = session; + } + + public async start(config: { + cache: boolean; + additionalBlockLists: string[]; + disableDefaultLists: boolean; + }) { + if (this.isRunning) { + console.log('AdBlockerService is already running. Reconfiguring...'); + await this.stop(); // Stop before re-starting to apply new config + } + + console.log('Starting AdBlockerService...'); + await this.loadEngine(config); + this.scheduleUpdates(config); + this.isRunning = true; + } + + public async stop() { + console.log('Stopping AdBlockerService...'); + if (this.updateInterval) { + clearInterval(this.updateInterval); + this.updateInterval = undefined; + } + if (this.blocker && this.session) { + this.blocker.disableBlockingInSession(this.session); + } + this.blocker = undefined; + this.isRunning = false; + } + + public isEnabled(): boolean { + return ( + this.isRunning && + this.blocker !== undefined && + this.blocker.isBlockingEnabled(this.session) + ); + } + + private scheduleUpdates(config: { + cache: boolean; + additionalBlockLists: string[]; + disableDefaultLists: boolean; + }) { + if (this.updateInterval) clearInterval(this.updateInterval); + + this.updateInterval = setInterval(async () => { + console.log('Performing scheduled adblock list update...'); + try { + // Force a re-fetch by disabling the read cache for this run + const tempConfig = { ...config, cache: false }; + await this.loadEngine(tempConfig, true); + console.log('Adblock lists updated successfully.'); + } catch (error) { + console.error('Failed to update adblock lists:', error); + } + }, TWENTY_FOUR_HOURS_IN_MS); + } + + private async loadEngine( + config: { + cache: boolean; + additionalBlockLists: string[]; + disableDefaultLists: boolean; + }, + isUpdate = false, + ) { + const lists = [ + ...(config.disableDefaultLists ? [] : DEFAULT_BLOCKLISTS), + ...config.additionalBlockLists, + ]; + + if (lists.length === 0) { + console.warn('No blocklists provided. Adblocker will not be effective.'); + return; + } + + // Generate a unique cache key based on the list URLs. + // This makes the cache far more reliable. + const cacheDirectory = path.join(app.getPath('userData'), 'adblock_cache'); + if (!fs.existsSync(cacheDirectory)) { + fs.mkdirSync(cacheDirectory); + } + const listHash = createHash('md5').update(lists.join(',')).digest('hex'); + const cachePath = path.join(cacheDirectory, `engine-${listHash}.bin`); + + const cachingOptions = config.cache + ? { + path: cachePath, + read: fsPromises.readFile, + write: fsPromises.writeFile, + } + : undefined; + + try { + // Temporarily disable blocking if we are performing an update + if (isUpdate && this.blocker) { + this.blocker.disableBlockingInSession(this.session); + } + + this.blocker = await ElectronBlocker.fromLists( + (url: string) => net.fetch(url), + lists, + { + enableCompression: true, + // We always want network filters loaded for the session + loadNetworkFilters: true, + }, + cachingOptions, + ); + + // IMPORTANT: This enables cosmetic filtering via the preload script + this.blocker.enableBlockingInSession(this.session); + console.log('Adblocker engine loaded and enabled.'); + } catch (error) { + console.error('Error loading AdBlocker engine:', error); + // If loading fails, ensure the old blocker (if any) is re-enabled + if (isUpdate && this.blocker) { + this.blocker.enableBlockingInSession(this.session); + } + } + } +} \ No newline at end of file diff --git a/src/plugins/adblocker/index.ts b/src/plugins/adblocker/index.ts new file mode 100644 index 0000000000..6ce1b4127b --- /dev/null +++ b/src/plugins/adblocker/index.ts @@ -0,0 +1,102 @@ +import { contextBridge, webFrame } from 'electron'; +import { createPlugin } from '@/utils'; +import { AdBlockerService } from './adblocker.service'; +import { inject, isInjected } from './injectors/inject'; +import { t } from '@/i18n'; + +interface AdblockerConfig { + enabled: boolean; + cache: boolean; + enableYoutubeSpecificBlocking: boolean; + additionalBlockLists: string[]; + disableDefaultLists: boolean; +} + +let adblockerService: AdBlockerService | null = null; + +export default createPlugin({ + name: () => t('plugins.adblocker.name'), + description: () => t('plugins.adblocker.description'), + restartNeeded: true, + config: { + enabled: true, + cache: true, + enableYoutubeSpecificBlocking: true, // Defaults to strongest blocking + additionalBlockLists: [], + disableDefaultLists: false, + } as AdblockerConfig, + menu: async ({ getConfig, setConfig }) => { + const config = await getConfig(); + + return [ + { + label: t('plugins.adblocker.menu.enableYoutubeSpecificBlocking'), // New string key + type: 'checkbox', + checked: config.enableYoutubeSpecificBlocking, + click(item) { + setConfig({ enableYoutubeSpecificBlocking: item.checked }); + }, + }, + ]; + }, + + + backend: { + async start({ getConfig, window }) { + const config = await getConfig(); + if (config.enabled) { + if (!adblockerService) { + adblockerService = new AdBlockerService(window.webContents.session); + } + await adblockerService.start(config); + } + }, + async stop() { + if (adblockerService) { + await adblockerService.stop(); + adblockerService = null; + } + }, + async onConfigChange(newConfig) { + if (!adblockerService) return; + + if (newConfig.enabled) { + // The service's start method handles re-configuration + await adblockerService.start(newConfig); + } else { + await adblockerService.stop(); + } + }, + }, + + preload: { + script: ` + // Inject the YouTube-specific ad pruner + const _prunerFn = window._pruner; + if (typeof _prunerFn === 'function') { + window._pruner = undefined; + JSON.parse = new Proxy(JSON.parse, { + apply(target, thisArg, args) { + return _prunerFn(Reflect.apply(target, thisArg, args)); + }, + }); + Response.prototype.json = new Proxy(Response.prototype.json, { + apply(target, thisArg, args) { + return Reflect.apply(target, thisArg, args).then(o => _prunerFn(o)); + }, + }); + } + `, + async start({ getConfig }) { + const config = await getConfig(); + if (!config.enabled) return; + + await import('@ghostery/adblocker-electron-preload'); + + if (config.enableYoutubeSpecificBlocking && !isInjected()) { + inject(contextBridge); + await webFrame.executeJavaScript(this.script); + } + }, + }, +}); \ No newline at end of file diff --git a/src/plugins/adblocker/injectors/inject-cliqz-preload.ts b/src/plugins/adblocker/injectors/inject-cliqz-preload.ts new file mode 100644 index 0000000000..27463f0bee --- /dev/null +++ b/src/plugins/adblocker/injectors/inject-cliqz-preload.ts @@ -0,0 +1,3 @@ +export default async () => { + await import('@ghostery/adblocker-electron-preload'); +}; diff --git a/src/plugins/adblocker/injectors/inject.d.ts b/src/plugins/adblocker/injectors/inject.d.ts new file mode 100644 index 0000000000..10062acc3c --- /dev/null +++ b/src/plugins/adblocker/injectors/inject.d.ts @@ -0,0 +1,5 @@ +import type { ContextBridge } from 'electron'; + +export const inject: (contextBridge: ContextBridge) => void; + +export const isInjected: () => boolean; diff --git a/src/plugins/adblocker/injectors/inject.js b/src/plugins/adblocker/injectors/inject.js new file mode 100644 index 0000000000..6e6219fe97 --- /dev/null +++ b/src/plugins/adblocker/injectors/inject.js @@ -0,0 +1,259 @@ +/* eslint-disable */ + +// Source: https://addons.mozilla.org/en-US/firefox/addon/adblock-for-youtube/ +// https://robwu.nl/crxviewer/?crx=https%3A%2F%2Faddons.mozilla.org%2Fen-US%2Ffirefox%2Faddon%2Fadblock-for-youtube%2F + +/* + Parts of this code is derived from set-constant.js: + https://github.com/gorhill/uBlock/blob/5de0ce975753b7565759ac40983d31978d1f84ca/assets/resources/scriptlets.js#L704 + */ + +let injected = false; + +export const isInjected = () => injected; + +/** + * @param {Electron.ContextBridge} contextBridge + * @returns {*} + */ +export const inject = (contextBridge) => { + injected = true; + { + const pruner = function (o) { + delete o.playerAds; + delete o.adPlacements; + delete o.adSlots; + // + if (o.playerResponse) { + delete o.playerResponse.playerAds; + delete o.playerResponse.adPlacements; + delete o.playerResponse.adSlots; + } + if (o.ytInitialPlayerResponse) { + delete o.ytInitialPlayerResponse.playerAds; + delete o.ytInitialPlayerResponse.adPlacements; + delete o.ytInitialPlayerResponse.adSlots; + } + + // + return o; + } + + contextBridge.exposeInMainWorld('_pruner', pruner); + } + + const chains = [ + { + chain: 'playerResponse.adPlacements', + cValue: 'undefined', + }, + { + chain: 'ytInitialPlayerResponse.playerAds', + cValue: 'undefined', + }, + { + chain: 'ytInitialPlayerResponse.adPlacements', + cValue: 'undefined', + }, + { + chain: 'ytInitialPlayerResponse.adSlots', + cValue: 'undefined', + } + ]; + + chains.forEach(function ({ chain, cValue }) { + const thisScript = document.currentScript; + // + switch (cValue) { + case 'null': { + cValue = null; + break; + } + + case "''": { + cValue = ''; + break; + } + + case 'true': { + cValue = true; + break; + } + + case 'false': { + cValue = false; + break; + } + + case 'undefined': { + cValue = undefined; + break; + } + + case 'noopFunc': { + cValue = function () {}; + + break; + } + + case 'trueFunc': { + cValue = function () { + return true; + }; + + break; + } + + case 'falseFunc': { + cValue = function () { + return false; + }; + + break; + } + + default: { + if (/^\d+$/.test(cValue)) { + cValue = Number.parseFloat(cValue); + // + if (isNaN(cValue)) { + return; + } + + if (Math.abs(cValue) > 0x7f_ff) { + return; + } + } else { + return; + } + } + } + + // + let aborted = false; + const mustAbort = function (v) { + if (aborted) { + return true; + } + + aborted = + v !== undefined && + v !== null && + cValue !== undefined && + cValue !== null && + typeof v !== typeof cValue; + return aborted; + }; + + /* + Support multiple trappers for the same property: + https://github.com/uBlockOrigin/uBlock-issues/issues/156 + */ + + const trapProp = function (owner, prop, configurable, handler) { + if (handler.init(owner[prop]) === false) { + return; + } + + // + const odesc = Object.getOwnPropertyDescriptor(owner, prop); + let previousGetter; + let previousSetter; + if (odesc instanceof Object) { + if (odesc.configurable === false) { + return; + } + + if (odesc.get instanceof Function) { + previousGetter = odesc.get; + } + + if (odesc.set instanceof Function) { + previousSetter = odesc.set; + } + } + + // + Object.defineProperty(owner, prop, { + configurable, + get() { + if (previousGetter !== undefined) { + previousGetter(); + } + + // + return handler.getter(); + }, + set(a) { + if (previousSetter !== undefined) { + previousSetter(a); + } + + // + handler.setter(a); + }, + }); + }; + + const trapChain = function (owner, chain) { + const pos = chain.indexOf('.'); + if (pos === -1) { + trapProp(owner, chain, false, { + v: undefined, + getter() { + return document.currentScript === thisScript ? this.v : cValue; + }, + setter(a) { + if (mustAbort(a) === false) { + return; + } + + cValue = a; + }, + init(v) { + if (mustAbort(v)) { + return false; + } + + // + this.v = v; + return true; + }, + }); + // + return; + } + + // + const prop = chain.slice(0, pos); + const v = owner[prop]; + // + chain = chain.slice(pos + 1); + if (v instanceof Object || (typeof v === 'object' && v !== null)) { + trapChain(v, chain); + return; + } + + // + trapProp(owner, prop, true, { + v: undefined, + getter() { + return this.v; + }, + setter(a) { + this.v = a; + if (a instanceof Object) { + trapChain(a, chain); + } + }, + init(v) { + this.v = v; + return true; + }, + }); + }; + + // + trapChain(window, chain); + }); +}; diff --git a/src/plugins/adblocker/lists.config.ts b/src/plugins/adblocker/lists.config.ts new file mode 100644 index 0000000000..66755dd1f9 --- /dev/null +++ b/src/plugins/adblocker/lists.config.ts @@ -0,0 +1,13 @@ +export const DEFAULT_BLOCKLISTS = [ + 'https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/filters.txt', + 'https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/unbreak.txt', + 'https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/resource-abuse.txt', + 'https://raw.githubusercontent.com/ghostery/adblocker/master/packages/adblocker/assets/ublock-origin/filters.txt', + 'https://raw.githubusercontent.com/ghostery/adblocker/master/packages/adblocker/assets/ublock-origin/quick-fixes.txt', + + // Fanboy's Annoyance List (for popups, cookie notices, etc.) + 'https://secure.fanboy.co.nz/fanboy-annoyance_ubo.txt', + + // AdGuard's comprehensive base filter + 'https://filters.adtidy.org/extension/ublock/filters/2_optimized.txt', +]; \ No newline at end of file diff --git a/src/plugins/downloader/index.ts b/src/plugins/downloader/index.ts index 914f7db76b..944595f54d 100644 --- a/src/plugins/downloader/index.ts +++ b/src/plugins/downloader/index.ts @@ -22,6 +22,9 @@ export type DownloaderPluginConfig = { customPresetSetting: Preset; skipExisting: boolean; playlistMaxItems?: number; + advanced?: { + ytDlpPath?: string; + }; }; export const defaultConfig: DownloaderPluginConfig = { @@ -38,6 +41,9 @@ export const defaultConfig: DownloaderPluginConfig = { customPresetSetting: DefaultPresetList['mp3 (256kbps)'], // Presets skipExisting: false, playlistMaxItems: undefined, + advanced: { + ytDlpPath: undefined, + }, }; export default createPlugin({ @@ -55,4 +61,4 @@ export default createPlugin({ start: onRendererLoad, onPlayerApiReady, }, -}); +}); \ No newline at end of file diff --git a/src/plugins/downloader/main/index.ts b/src/plugins/downloader/main/index.ts index 0f2fe402f2..5a4089e981 100644 --- a/src/plugins/downloader/main/index.ts +++ b/src/plugins/downloader/main/index.ts @@ -1,88 +1,174 @@ -import { existsSync, mkdirSync, writeFileSync } from 'node:fs'; -import { join } from 'node:path'; -import { randomBytes } from 'node:crypto'; +import path from 'node:path'; +import { homedir } from 'node:os'; +import { existsSync, mkdirSync } from 'node:fs'; +import { spawn } from 'node:child_process'; import { app, type BrowserWindow, dialog, ipcMain } from 'electron'; -import { - Innertube, - UniversalCache, - Utils, - YTNodes, -} from '\u0079\u006f\u0075\u0074\u0075\u0062\u0065i.js'; import is from 'electron-is'; -import filenamify from 'filenamify'; -import { Mutex } from 'async-mutex'; -import * as NodeID3 from 'node-id3'; -import { BG, type BgConfig } from 'bgutils-js'; -import { lazy } from 'lazy-var'; import { - cropMaxWidth, getFolder, sendFeedback as sendFeedback_, setBadge, } from './utils'; import { registerCallback, - cleanupName, - getImage, - MediaType, type SongInfo, SongInfoEvent, } from '@/providers/song-info'; -import { getNetFetchAsFetch } from '@/plugins/utils/main'; import { t } from '@/i18n'; -import { DefaultPresetList, type Preset, VideoFormatList } from '../types'; - import type { DownloaderPluginConfig } from '../index'; import type { BackendContext } from '@/types/contexts'; import type { GetPlayerResponse } from '@/types/get-player-response'; -import type { FormatOptions } from 'node_modules/\u0079\u006f\u0075\u0074\u0075\u0062\u0065i.js/dist/src/types'; -import type { VideoInfo } from 'node_modules/\u0079\u006f\u0075\u0074\u0075\u0062\u0065i.js/dist/src/parser/\u0079\u006f\u0075\u0074\u0075\u0062\u0065'; -import type { PlayerErrorMessage } from 'node_modules/\u0079\u006f\u0075\u0074\u0075\u0062\u0065i.js/dist/src/parser/nodes'; -import type { - TrackInfo, - Playlist, -} from 'node_modules/\u0079\u006f\u0075\u0074\u0075\u0062\u0065i.js/dist/src/parser/ytmusic'; - -type CustomSongInfo = SongInfo & { trackId?: string }; - -const ffmpeg = lazy(async () => - (await import('@ffmpeg.wasm/main')).createFFmpeg({ - log: false, - logger() {}, // Console.log, - progress() {}, // Console.log, - }), -); -const ffmpegMutex = new Mutex(); - -let yt: Innertube; -let win: BrowserWindow; -let playingUrl: string; -const isPremium = async () => { - // If signed out, it is understood as non-premium - const isSignedIn = (await win.webContents.executeJavaScript( - '!!yt.config_.LOGGED_IN', - )) as boolean; +// Helper to send OS notification if notifications plugin is enabled +async function sendOsNotification(title: string, body: string) { + try { + // First try to use Electron's built-in Notification API + const { Notification } = await import('electron'); + + if (Notification.isSupported()) { + const notification = new Notification({ + title, + body, + silent: false, + }); + notification.show(); + return true; + } + } catch (error) { + logToFrontend('warn', '⚠️ Could not send notification:', error); + } + return false; +} - if (!isSignedIn) return false; +// Helper to clean URL and convert music.youtube.com to youtube.com for better yt-dlp compatibility +function cleanAndConvertUrl(url: string): string { + try { + const urlObj = new URL(url); - // If signed in, check if the upgrade button is present - const upgradeBtnIconPathData = (await win.webContents.executeJavaScript( - 'document.querySelector(\'iron-iconset-svg[name="yt-sys-icons"] #\u0079\u006f\u0075\u0074\u0075\u0062\u0065_music_monochrome\')?.firstChild?.getAttribute("d")?.substring(0, 15)', - )) as string | null; + // Convert music.youtube.com to youtube.com + if (urlObj.hostname === 'music.youtube.com') { + urlObj.hostname = 'youtube.com'; + } - // Fallback to non-premium if the icon is not found - if (!upgradeBtnIconPathData) return false; + // Remove playlist parameters to prevent downloading entire playlists + const params = new URLSearchParams(urlObj.search); + if (params.has('list')) { + params.delete('list'); + } + if (params.has('index')) { + params.delete('index'); + } - const upgradeButton = `ytmusic-guide-entry-renderer:has(> tp-yt-paper-item > yt-icon path[d^="${upgradeBtnIconPathData}"])`; + urlObj.search = params.toString(); + return urlObj.toString(); + } catch (error) { + console.warn('[Downloader] Failed to clean URL, using original:', error); + return url; + } +} - return (await win.webContents.executeJavaScript( - `!document.querySelector('${upgradeButton}')`, - )) as boolean; -}; +// Cached yt-dlp path to reduce path checks and verbose logging +let cachedYtDlpPath: string | undefined; +let cachedConfig: DownloaderPluginConfig | null = null; + +// Helper function to log both to backend console and frontend +function logToFrontend( + level: 'info' | 'warn' | 'error', + message: string, + ...args: unknown[] +) { + const formattedMessage = args.length > 0 + ? `${message} ${args.join(' ')}` + : message; + + console[level](`[Downloader] ${formattedMessage}`); + + // Also log to frontend for production debugging + try { + win?.webContents?.executeJavaScript( + `console.${level}('[Downloader] ${formattedMessage.replace(/'/g, "\\'")}');` + ); + } catch { + // Ignore errors if window not available + } +} + +// Helper to find yt-dlp path, with caching to reduce verbose logging +function getYtDlpPath(customPath?: string): string | undefined { + // Check cache first to avoid redundant file system checks + if (cachedYtDlpPath && cachedConfig?.advanced?.ytDlpPath === customPath) { + return cachedYtDlpPath; + } + + const checkedPaths: string[] = []; + if (customPath) { + checkedPaths.push(`[custom] ${customPath}`); + if (existsSync(customPath)) { + // Only log yt-dlp path once when plugin loads, not for every download + if (!cachedYtDlpPath) { + logToFrontend('info', '🔧 Using custom yt-dlp path:', customPath); + } + cachedYtDlpPath = customPath; + return customPath; + } + } + + if (is.windows()) { + const candidates = [ + 'C:/yt-dlp.exe', + path.join(homedir(), 'Downloads', 'yt-dlp.exe'), + 'C:/utils/yt-dlp.exe', + ]; + for (const p of candidates) { + checkedPaths.push(p); + if (existsSync(p)) { + // Only log yt-dlp path once when plugin loads, not for every download + if (!cachedYtDlpPath) { + logToFrontend('info', '🔧 Found yt-dlp at:', p); + } + cachedYtDlpPath = p; + return p; + } + } + } else if (is.linux()) { + checkedPaths.push('/usr/bin/yt-dlp'); + if (existsSync('/usr/bin/yt-dlp')) { + // Only log yt-dlp path once when plugin loads, not for every download + if (!cachedYtDlpPath) { + logToFrontend('info', '🔧 Found yt-dlp at: /usr/bin/yt-dlp'); + } + cachedYtDlpPath = '/usr/bin/yt-dlp'; + return '/usr/bin/yt-dlp'; + } + } else if (is.macOS()) { + const macCandidates = [ + '/usr/local/bin/yt-dlp', + '/opt/homebrew/bin/yt-dlp', + ]; + for (const p of macCandidates) { + checkedPaths.push(p); + if (existsSync(p)) { + // Only log yt-dlp path once when plugin loads, not for every download + if (!cachedYtDlpPath) { + logToFrontend('info', '🔧 Found yt-dlp at:', p); + } + cachedYtDlpPath = p; + return p; + } + } + } + + logToFrontend('warn', '⚠️ yt-dlp not found. Paths checked:', checkedPaths); + cachedYtDlpPath = undefined; + return undefined; +} + +let win: BrowserWindow; +let playingUrl: string; +let config: DownloaderPluginConfig; const sendError = (error: Error, source?: string) => { win.setProgressBar(-1); // Close progress bar @@ -98,29 +184,33 @@ const sendError = (error: Error, source?: string) => { : ''; const message = `${error.toString()}${songNameMessage}${cause}`; - console.error(message); + // Print full error to console for debugging + console.error('[Downloader] Error:', message); + if (error instanceof Error && error.stack) { + console.error(error.stack); + } console.trace(error); + + // Try to extract command info from error message + let commandInfo = ''; + const match = message.match(/Command: ([^\n]+)/); + if (match) { + commandInfo = `\n\nCommand attempted: ${match[1]}`; + } + + // Show user-friendly error dialog dialog.showMessageBox(win, { - type: 'info', + type: 'error', buttons: [t('plugins.downloader.backend.dialog.error.buttons.ok')], title: t('plugins.downloader.backend.dialog.error.title'), message: t('plugins.downloader.backend.dialog.error.message'), - detail: message, + detail: + message + + commandInfo + + '\n\nIf this is a yt-dlp error, please check the path in the Downloader plugin settings.', }); }; -export const getCookieFromWindow = async (win: BrowserWindow) => { - return ( - await win.webContents.session.cookies.get({ - url: 'https://music.\u0079\u006f\u0075\u0074\u0075\u0062\u0065.com', - }) - ) - .map((it) => it.name + '=' + it.value) - .join(';'); -}; - -let config: DownloaderPluginConfig; - export const onMainLoad = async ({ window: _win, getConfig, @@ -129,75 +219,11 @@ export const onMainLoad = async ({ win = _win; config = await getConfig(); - yt = await Innertube.create({ - cache: new UniversalCache(false), - player_id: '0004de42', - cookie: await getCookieFromWindow(win), - generate_session_locally: true, - fetch: getNetFetchAsFetch(), - }); - - const requestKey = 'O43z0dpjhgX20SCx4KAo'; - const visitorData = yt.session.context.client.visitorData; - - if (visitorData) { - const cleanUp = (context: Partial) => { - delete context.window; - delete context.document; - }; - - try { - const [width, height] = win.getSize(); - // emulate jsdom using linkedom - const window = new (await import('happy-dom')).Window({ - width, - height, - console, - }); - const document = window.document; - - Object.assign(globalThis, { - window, - document, - }); - - const bgConfig: BgConfig = { - fetch: getNetFetchAsFetch(), - globalObj: globalThis, - identifier: visitorData, - requestKey, - }; - - const bgChallenge = await BG.Challenge.create(bgConfig); - const interpreterJavascript = - bgChallenge?.interpreterJavascript - .privateDoNotAccessOrElseSafeScriptWrappedValue; - - if (interpreterJavascript) { - // This is a workaround to run the interpreterJavascript code - // Maybe there is a better way to do this (e.g. https://github.com/Siubaak/sval ?) - // eslint-disable-next-line @typescript-eslint/no-implied-eval,@typescript-eslint/no-unsafe-call - new Function(interpreterJavascript)(); - - const poTokenResult = await BG.PoToken.generate({ - program: bgChallenge.program, - globalName: bgChallenge.globalName, - bgConfig, - }).finally(() => { - cleanUp(globalThis); - }); - - yt.session.po_token = poTokenResult.poToken; - } else { - cleanUp(globalThis); - } - } catch { - cleanUp(globalThis); - } - } + // Update cache when config changes + cachedConfig = config; ipc.handle('download-song', (url: string) => downloadSong(url)); - ipc.on('peard:video-src-changed', (data: GetPlayerResponse) => { + ipc.on('ytmd:video-src-changed', (data: GetPlayerResponse) => { playingUrl = data.microformat.microformatDataRenderer.urlCanonical; }); ipc.handle('download-playlist-request', async (url: string) => @@ -209,6 +235,13 @@ export const onMainLoad = async ({ export const onConfigChange = (newConfig: DownloaderPluginConfig) => { config = newConfig; + // Update cache when config changes + cachedConfig = newConfig; + + // Reset yt-dlp path cache if custom path changed + if (cachedConfig?.advanced?.ytDlpPath !== newConfig.advanced?.ytDlpPath) { + cachedYtDlpPath = undefined; + } }; export async function downloadSong( @@ -303,8 +336,8 @@ function downloadSongOnFinishSetup({ } }); - ipcMain.on('peard:player-api-loaded', () => { - ipc.send('peard:setup-time-changed-listener'); + ipcMain.on('ytmd:player-api-loaded', () => { + ipc.send('ytmd:setup-time-changed-listener'); }); } @@ -313,7 +346,7 @@ async function downloadSongUnsafe( idOrUrl: string, setName: (name: string) => void, playlistFolder: string | undefined = undefined, - trackId: string | undefined = undefined, + _trackId: string | undefined = undefined, increasePlaylistProgress: (value: number) => void = () => {}, ) { const sendFeedback = (message: unknown, progress?: number) => { @@ -325,297 +358,225 @@ async function downloadSongUnsafe( } }; - sendFeedback(t('plugins.downloader.backend.feedback.downloading'), 2); + sendFeedback(t('plugins.downloader.backend.feedback.downloading'), 0.1); - let id: string | null; + let url: string; if (isId) { - id = idOrUrl; + url = `https://youtube.com/watch?v=${idOrUrl}`; } else { - id = getVideoId(idOrUrl); - if (typeof id !== 'string') - throw new Error( - t('plugins.downloader.backend.feedback.video-id-not-found'), - ); + // Clean up the URL and convert music.youtube.com to youtube.com + url = cleanAndConvertUrl(idOrUrl); } - let info: TrackInfo | VideoInfo = await yt.music.getInfo(id); - - if (!info) { - throw new Error( - t('plugins.downloader.backend.feedback.video-id-not-found'), - ); - } - - const metadata = getMetadata(info); - if (metadata.album === 'N/A') { - metadata.album = ''; - } - - metadata.trackId = trackId; - - const dir = - playlistFolder || config.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; + const dir = playlistFolder || config.downloadFolder || app.getPath('downloads'); + if (!existsSync(dir)) { + mkdirSync(dir, { recursive: true }); } - if (playabilityStatus?.status === 'UNPLAYABLE') { - const errorScreen = - playabilityStatus.error_screen as PlayerErrorMessage | null; + // Find yt-dlp path using cached value or search + const ytDlpPath = getYtDlpPath(config.advanced?.ytDlpPath); + if (!ytDlpPath) { + const allPaths = [ + config.advanced?.ytDlpPath + ? `[custom] ${config.advanced.ytDlpPath}` + : null, + ...(is.windows() + ? [ + 'C:/yt-dlp.exe', + path.join(homedir(), 'Downloads', 'yt-dlp.exe'), + 'C:/utils/yt-dlp.exe', + ] + : is.linux() + ? ['/usr/bin/yt-dlp'] + : is.macOS() + ? ['/usr/local/bin/yt-dlp', '/opt/homebrew/bin/yt-dlp'] + : []), + ].filter(Boolean); + + logToFrontend('error', '❌ yt-dlp not found. Paths checked:', allPaths); throw new Error( - `[${playabilityStatus.status}] ${errorScreen?.reason.text}: ${errorScreen?.subreason.text}`, + 'yt-dlp executable not found.\nPaths checked:\n' + + allPaths.join('\n') + + '\nPlease set the path in the Downloader plugin menu.', ); } - const selectedPreset = config.selectedPreset ?? 'mp3 (256kbps)'; - let presetSetting: Preset; - if (selectedPreset === 'Custom') { - presetSetting = config.customPresetSetting ?? DefaultPresetList['Custom']; - } else if (selectedPreset === 'Source') { - presetSetting = DefaultPresetList['Source']; - } else { - presetSetting = DefaultPresetList['mp3 (256kbps)']; - } - - const downloadOptions: FormatOptions = { - type: (await isPremium()) ? 'audio' : 'video+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); - - let targetFileExtension: string; - if (!presetSetting?.extension) { - targetFileExtension = - VideoFormatList.find((it) => it.itag === format.itag)?.container ?? - 'mp3'; - } else { - targetFileExtension = presetSetting?.extension ?? 'mp3'; - } - - let filename = filenamify(`${name}.${targetFileExtension}`, { - replacement: '_', - maxLength: 255, - }); - if (!is.macOS()) { - filename = filename.normalize('NFC'); - } - const filePath = join(dir, filename); - - if (config.skipExisting && existsSync(filePath)) { - sendFeedback(null, -1); - return; - } - - const stream = await info.download(downloadOptions); + // Enhanced output template with artist and title, higher quality, metadata + const outTemplate = `${dir}/%(artist)s - %(title)s.%(ext)s`; - console.info( - t('plugins.downloader.backend.feedback.download-info', { - artist: metadata.artist, - title: metadata.title, - videoId: metadata.videoId, - }), - ); + // Check if file already exists when skipExisting is enabled + if (config.skipExisting) { + try { + // Get the expected filename by running yt-dlp with --print option first + const infoArgs = ['--print', '%(artist)s - %(title)s.%(ext)s', url]; + const infoProcess = spawn(ytDlpPath, infoArgs, { + stdio: ['pipe', 'pipe', 'pipe'], + shell: false, + }); - const iterableStream = Utils.streamToIterable(stream); + let infoOutput = ''; + infoProcess.stdout?.on('data', (data: Buffer) => { + infoOutput += data.toString(); + }); - if (!existsSync(dir)) { - mkdirSync(dir); - } + const infoExitCode = await new Promise((resolve) => { + infoProcess.on('close', resolve); + }); - let fileBuffer = await iterableStreamToProcessedUint8Array( - iterableStream, - targetFileExtension, - metadata, - presetSetting?.ffmpegArgs ?? [], - format.content_length ?? 0, - sendFeedback, - increasePlaylistProgress, - ); + if (infoExitCode === 0) { + const expectedFilename = infoOutput.trim().replace('.webm', '.mp3').replace('.m4a', '.mp3'); + const expectedPath = path.join(dir, expectedFilename); - if (fileBuffer && targetFileExtension === 'mp3') { - fileBuffer = await writeID3( - Buffer.from(fileBuffer), - metadata, - sendFeedback, - ); - } - - if (fileBuffer) { - writeFileSync(filePath, fileBuffer); + if (existsSync(expectedPath)) { + logToFrontend('info', '⏭️ File already exists, skipping:', expectedPath); + sendFeedback(t('plugins.downloader.backend.feedback.file-already-exists'), -1); + setName(expectedPath); + return; // Skip download + } + } + } catch (error) { + logToFrontend('warn', '⚠️ Could not check for existing file:', error); + // Continue with download if check fails + } } - sendFeedback(null, -1); - console.info( - t('plugins.downloader.backend.feedback.done', { - filePath, - }), - ); -} + // Enhanced args with higher quality and metadata embedding (NO separate thumbnail downloads) + const args = [ + '-x', + '--audio-format', + 'mp3', + '--audio-quality', + '320K', // Higher bitrate + '--embed-thumbnail', // Embed album art into the MP3 + '--embed-metadata', // Embed ID3 tags + '--add-metadata', // Additional metadata + '--convert-thumbnails', + 'jpg', // Convert to standard format (for embedded only, no separate files) + + '-o', + outTemplate, + url, + ]; -async function downloadChunks( - stream: AsyncGenerator, - 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( - t('plugins.downloader.backend.feedback.download-progress', { - percent: 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); + // Add skip existing flag if enabled + if (config.skipExisting) { + args.push('--no-overwrites'); } - return chunks; -} -async function iterableStreamToProcessedUint8Array( - stream: AsyncGenerator, - extension: string, - metadata: CustomSongInfo, - presetFfmpegArgs: string[], - contentLength: number, - sendFeedback: (str: string, value?: number) => void, - increasePlaylistProgress: (value: number) => void = () => {}, -): Promise { - sendFeedback(t('plugins.downloader.backend.feedback.loading'), 2); // Indefinite progress bar after download + // Enhanced colored logging with unicode icons + logToFrontend('info', '⬇️ Starting download...'); + logToFrontend('info', '🎵 URL:', url); - const safeVideoName = randomBytes(32).toString('hex'); + setName(url); // We'll update this when we get the actual filename - return await ffmpegMutex.runExclusive(async () => { - try { - const ffmpegInstance = await ffmpeg.get(); - if (!ffmpegInstance.isLoaded()) { - await ffmpegInstance.load(); - } - - sendFeedback(t('plugins.downloader.backend.feedback.preparing-file')); - ffmpegInstance.FS( - 'writeFile', - safeVideoName, - Buffer.concat( - await downloadChunks( - stream, - contentLength, - sendFeedback, - increasePlaylistProgress, - ), - ), + try { + const ytDlpProcess = spawn(ytDlpPath, args, { + stdio: ['pipe', 'pipe', 'pipe'], + shell: false, // Only spawn once, no shell wrapping + }); + + let output = ''; + let errorOutput = ''; + let downloadedFile = ''; + + ytDlpProcess.stdout?.on('data', (data: Buffer) => { + const chunk = data.toString(); + output += chunk; + + // Parse for actual downloaded filename to show correct path in logs + const destinationMatch = chunk.match( + /\[ExtractAudio\] Destination: (.+)/, ); - - sendFeedback(t('plugins.downloader.backend.feedback.converting')); - - ffmpegInstance.setProgress(({ ratio }) => { - sendFeedback( - t('plugins.downloader.backend.feedback.conversion-progress', { - percent: Math.floor(ratio * 100), - }), - ratio, - ); - increasePlaylistProgress(0.15 + ratio * 0.85); - }); - - const safeVideoNameWithExtension = `${safeVideoName}.${extension}`; - try { - await ffmpegInstance.run( - '-i', - safeVideoName, - ...presetFfmpegArgs, - ...getFFmpegMetadataArgs(metadata), - safeVideoNameWithExtension, - ); - } finally { - ffmpegInstance.FS('unlink', safeVideoName); + if (destinationMatch) { + downloadedFile = destinationMatch[1]; + logToFrontend('info', '📁 Saving to:', downloadedFile); + setName(downloadedFile); } - sendFeedback(t('plugins.downloader.backend.feedback.saving')); + // Parse progress with multiple patterns for better compatibility + let progressMatch = chunk.match(/(\d+(?:\.\d+)?)%/); // Look for percentage with optional decimal + if (!progressMatch) { + progressMatch = output.match(/(\d+(?:\.\d+)?)%/); // Also check accumulated output + } - try { - return ffmpegInstance.FS('readFile', safeVideoNameWithExtension); - } finally { - ffmpegInstance.FS('unlink', safeVideoNameWithExtension); + if (progressMatch) { + const progress = parseFloat(progressMatch[1]) / 100; + if (!isNaN(progress) && progress > 0) { + logToFrontend('info', `📊 Progress: ${Math.floor(progress * 100)}%`); + sendFeedback( + t('plugins.downloader.backend.feedback.download-progress', { + percent: Math.floor(progress * 100), + }), + progress, + ); + increasePlaylistProgress(progress); + } } - } catch (error: unknown) { - sendError(error as Error, safeVideoName); - } - return null; - }); -} -const getCoverBuffer = async (url: string) => { - const nativeImage = cropMaxWidth(await getImage(url)); - return nativeImage && !nativeImage.isEmpty() ? nativeImage.toPNG() : null; -}; + // Detect conversion phase + if (chunk.includes('[ExtractAudio]') || chunk.includes('Converting')) { + sendFeedback(t('plugins.downloader.backend.feedback.converting')); + } + }); -async function writeID3( - buffer: Buffer, - metadata: CustomSongInfo, - sendFeedback: (str: string, value?: number) => void, -) { - try { - sendFeedback(t('plugins.downloader.backend.feedback.writing-id3')); - const tags: NodeID3.Tags = {}; + ytDlpProcess.stderr?.on('data', (data: Buffer) => { + errorOutput += data.toString(); + }); - // Create the metadata tags - tags.title = metadata.title; - tags.artist = metadata.artist; + const exitCode = await new Promise((resolve) => { + ytDlpProcess.on('close', resolve); + }); - if (metadata.album) { - tags.album = metadata.album; + if (exitCode !== 0) { + throw new Error( + `yt-dlp failed with exit code ${exitCode}: ${errorOutput}` + + `\nCommand: ${ytDlpPath} ${args.join(' ')}`, + ); } - const coverBuffer = await getCoverBuffer(metadata.imageSrc ?? ''); - if (coverBuffer) { - tags.image = { - mime: 'image/png', - type: { - id: NodeID3.TagConstants.AttachedPicture.PictureType.FRONT_COVER, - }, - description: 'thumbnail', - imageBuffer: coverBuffer, - }; + sendFeedback(null, -1); + + // Try to determine the final file location more accurately + let finalFile = downloadedFile; + if (!finalFile || finalFile === '') { + // Try to find the file by looking for mp3 files in the directory + try { + const fs = await import('fs'); + const files = fs.readdirSync(dir); + const mp3Files = files.filter((f) => f.endsWith('.mp3')); + if (mp3Files.length > 0) { + // Sort by modification time, get the newest + const fullPaths = mp3Files.map((f) => path.join(dir, f)); + const newest = fullPaths.reduce((a, b) => { + const aStat = fs.statSync(a); + const bStat = fs.statSync(b); + return aStat.mtime > bStat.mtime ? a : b; + }); + finalFile = newest; + logToFrontend('info', '🔍 Found downloaded file:', finalFile); + } + } catch (error) { + logToFrontend('warn', '⚠️ Could not determine final file location:', error); + } } - if (metadata.trackId) { - tags.trackNumber = metadata.trackId; + if (!finalFile) { + finalFile = 'Unknown location (check download folder)'; } - return NodeID3.write(tags, buffer); - } catch (error: unknown) { - sendError(error as Error, `${metadata.artist} - ${metadata.title}`); - return null; + logToFrontend('info', '✅ Download complete!'); + logToFrontend('info', '📂 File saved:', finalFile); + sendOsNotification('Download complete!', `Downloaded: ${path.basename(finalFile)}`).catch(() => {}); + } catch (error) { + logToFrontend('error', '❌ Download failed:', error); + sendOsNotification('Download failed!', String(error)).catch(() => {}); + throw new Error(`Download failed: ${String(error)}`); } } export async function downloadPlaylist(givenUrl?: string | URL) { + logToFrontend('info', '🎵 Starting playlist download...'); + try { givenUrl = new URL(givenUrl ?? ''); } catch { @@ -626,13 +587,21 @@ export async function downloadPlaylist(givenUrl?: string | URL) { getPlaylistID(givenUrl) || getPlaylistID(new URL(playingUrl)); if (!playlistId) { + logToFrontend('error', '❌ No playlist ID found'); sendError( new Error(t('plugins.downloader.backend.feedback.playlist-id-not-found')), ); return; } - const sendFeedback = (message?: unknown) => sendFeedback_(win, message); + logToFrontend('info', '🆔 Playlist ID:', playlistId); + + const sendFeedback = (message?: unknown, progress?: number) => { + sendFeedback_(win, message); + if (typeof progress === 'number' && !isNaN(progress)) { + win.setProgressBar(progress); + } + }; console.log( t('plugins.downloader.backend.feedback.trying-to-get-playlist-id', { @@ -640,186 +609,202 @@ export async function downloadPlaylist(givenUrl?: string | URL) { }), ); sendFeedback(t('plugins.downloader.backend.feedback.getting-playlist-info')); - let playlist: Playlist; - const items: YTNodes.MusicResponsiveListItem[] = []; - try { - playlist = await yt.music.getPlaylist(playlistId); - if (playlist?.items) { - const filteredItems = playlist.items.filter( - (item): item is YTNodes.MusicResponsiveListItem => - item instanceof YTNodes.MusicResponsiveListItem, - ); - items.push(...filteredItems); - } - } catch (error: unknown) { + const dir = getFolder(config.downloadFolder ?? ''); + const playlistUrl = `https://music.youtube.com/playlist?list=${playlistId}`; + + logToFrontend('info', '📁 Download directory:', dir); + logToFrontend('info', '🔗 Playlist URL:', playlistUrl); + + // Find yt-dlp path using cached and fallback logic + const ytDlpPath = getYtDlpPath(config.advanced?.ytDlpPath); + if (!ytDlpPath) { + const allPaths = [ + config.advanced?.ytDlpPath ? `[custom] ${config.advanced.ytDlpPath}` : null, + ...(is.windows() + ? [ + 'C:/yt-dlp.exe', + path.join(homedir(), 'Downloads', 'yt-dlp.exe'), + 'C:/utils/yt-dlp.exe', + ] + : is.linux() + ? ['/usr/bin/yt-dlp'] + : is.macOS() + ? ['/usr/local/bin/yt-dlp', '/opt/homebrew/bin/yt-dlp'] + : []), + ].filter(Boolean); + + logToFrontend('error', '❌ yt-dlp not found for playlist. Paths checked:', allPaths); sendError( - Error( - t('plugins.downloader.backend.feedback.playlist-is-mix-or-private', { - error: String(error), - }), - ), + new Error( + 'yt-dlp executable not found.\nPaths checked:\n' + + allPaths.join('\n') + + '\nPlease set the path in the Downloader plugin menu.' + ) ); return; } - if (!playlist || !playlist.items || playlist.items.length === 0) { - sendError( - new Error(t('plugins.downloader.backend.feedback.playlist-is-empty')), - ); - return; - } - - const normalPlaylistTitle = - playlist.header && 'title' in playlist.header - ? playlist.header?.title?.text - : undefined; - const playlistTitle = - normalPlaylistTitle ?? - playlist.page.contents_memo - ?.get('MusicResponsiveListItemFlexColumn') - ?.at(2) - ?.as(YTNodes.MusicResponsiveListItemFlexColumn)?.title?.text ?? - 'NO_TITLE'; - const isAlbum = !normalPlaylistTitle; - - while (playlist.has_continuation) { - playlist = await playlist.getContinuation(); - - const filteredItems = playlist.items.filter( - (item): item is YTNodes.MusicResponsiveListItem => - item instanceof YTNodes.MusicResponsiveListItem, - ); - - items.push(...filteredItems); - } + const args = [ + '-x', + '--audio-format', + 'mp3', + '--audio-quality', + '320K', // Higher bitrate + '--embed-thumbnail', // Embed album art + '--embed-metadata', // Embed ID3 tags + '--add-metadata', // Additional metadata + '-o', + `${dir}/%(playlist_title)s/%(title)s.%(ext)s`, + playlistUrl, + ]; - if (items.length === 1) { - sendFeedback( - t('plugins.downloader.backend.feedback.playlist-has-only-one-song'), - ); - await downloadSongFromId(items.at(0)!.id!); - return; + // Add skip existing flag if enabled + if (config.skipExisting) { + args.push('--no-overwrites'); } - let safePlaylistTitle = filenamify(playlistTitle, { replacement: ' ' }); - if (!is.macOS()) { - safePlaylistTitle = safePlaylistTitle.normalize('NFC'); - } + try { + logToFrontend('info', '💬 Showing playlist download dialog...'); + + const dialogResult = await dialog.showMessageBox(win, { + type: 'info', + buttons: [ + t('plugins.downloader.backend.dialog.start-download-playlist.buttons.ok'), + 'Cancel' + ], + title: t('plugins.downloader.backend.dialog.start-download-playlist.title'), + message: t( + 'plugins.downloader.backend.dialog.start-download-playlist.message', + { + playlistTitle: playlistId, + }, + ), + detail: t( + 'plugins.downloader.backend.dialog.start-download-playlist.detail', + { + playlistSize: 'Unknown', + }, + ), + }); - const folder = getFolder(config.downloadFolder ?? ''); - const playlistFolder = join(folder, safePlaylistTitle); - if (existsSync(playlistFolder)) { - if (!config.skipExisting) { - sendError( - new Error( - t('plugins.downloader.backend.feedback.folder-already-exists', { - playlistFolder, - }), - ), - ); + // Check if user cancelled + if (dialogResult.response !== 0) { + logToFrontend('info', '❌ User cancelled playlist download'); + sendFeedback('Download cancelled', -1); return; } - } else { - mkdirSync(playlistFolder, { recursive: true }); - } - dialog.showMessageBox(win, { - type: 'info', - buttons: [ - t('plugins.downloader.backend.dialog.start-download-playlist.buttons.ok'), - ], - title: t('plugins.downloader.backend.dialog.start-download-playlist.title'), - message: t( - 'plugins.downloader.backend.dialog.start-download-playlist.message', - { - playlistTitle, - }, - ), - detail: t( - 'plugins.downloader.backend.dialog.start-download-playlist.detail', - { - playlistSize: items.length, - }, - ), - }); + logToFrontend('info', '🚀 Starting yt-dlp process...'); + win.setProgressBar(0.1); // Start with indefinite bar - if (is.dev()) { - console.log( - t('plugins.downloader.backend.feedback.downloading-playlist', { - playlistTitle, - playlistSize: items.length, - playlistId, - }), - ); - } + logToFrontend('info', '⚙️ yt-dlp command:', `${ytDlpPath} ${args.join(' ')}`); - win.setProgressBar(2); // Starts with indefinite bar + const ytDlpProcess = spawn(ytDlpPath, args, { + stdio: ['pipe', 'pipe', 'pipe'], + shell: false // Use shell: false for better stability + }); - setBadge(items.length); + let output = ''; + let errorOutput = ''; + let lastProgressUpdate = 0; - let counter = 1; + logToFrontend('info', '📊 yt-dlp process started, PID:', ytDlpProcess.pid); - const progressStep = 1 / items.length; + ytDlpProcess.stdout?.on('data', (data: Buffer) => { + const chunk = data.toString(); + output += chunk; - const increaseProgress = (itemPercentage: number) => { - const currentProgress = (counter - 1) / (items.length ?? 1); - const newProgress = currentProgress + progressStep * itemPercentage; - win.setProgressBar(newProgress); - }; + // Log chunks for debugging (but limit the amount) + if (chunk.trim()) { + logToFrontend('info', '📥 yt-dlp output:', chunk.trim().substring(0, 200)); + } - try { - for (const song of items) { - sendFeedback( - t('plugins.downloader.backend.feedback.downloading-counter', { - current: counter, - total: items.length, - }), - ); - const trackId = isAlbum ? counter : undefined; - await downloadSongFromId( - song.id!, - playlistFolder, - trackId?.toString(), - increaseProgress, - ).catch((error) => - sendError( - new Error( - t('plugins.downloader.backend.feedback.error-while-downloading', { - author: song.author!.name, - title: song.title!, - error: String(error), - }), - ), - ), - ); + // Parse progress with multiple patterns for better compatibility + let progressMatch = chunk.match(/(\d+(?:\.\d+)?)%/); + if (!progressMatch) { + progressMatch = output.match(/(\d+(?:\.\d+)?)%/); + } - win.setProgressBar(counter / items.length); - setBadge(items.length - counter); - counter++; + if (progressMatch) { + const progress = parseFloat(progressMatch[1]) / 100; + if (!isNaN(progress) && progress > 0 && progress !== lastProgressUpdate) { + lastProgressUpdate = progress; + logToFrontend('info', `📊 Playlist progress: ${Math.floor(progress * 100)}%`); + sendFeedback(`Downloading... ${Math.floor(progress * 100)}%`, progress); + } + } + + // Detect conversion step + if (chunk.includes('[ExtractAudio]') || chunk.includes('Converting')) { + logToFrontend('info', '🔄 Converting audio...'); + sendFeedback('Converting to mp3...'); + } + + // Detect download completion for individual tracks + if (chunk.includes('[ExtractAudio] Destination:')) { + const filename = chunk.match(/\[ExtractAudio\] Destination: (.+)/); + if (filename) { + logToFrontend('info', '✅ Track completed:', path.basename(filename[1])); + } + } + + // Detect download start for tracks + if (chunk.includes('[youtube]') || chunk.includes('[download]')) { + if (chunk.includes('Downloading webpage')) { + logToFrontend('info', '🌐 Fetching track info...'); + } + } + }); + + ytDlpProcess.stderr?.on('data', (data: Buffer) => { + const errorChunk = data.toString(); + errorOutput += errorChunk; + logToFrontend('warn', '⚠️ yt-dlp stderr:', errorChunk.trim().substring(0, 200)); + }); + + ytDlpProcess.on('error', (error) => { + logToFrontend('error', '💥 yt-dlp process error:', error); + }); + + const exitCode = await new Promise((resolve) => { + ytDlpProcess.on('close', (code) => { + logToFrontend('info', '🏁 yt-dlp process closed with code:', code); + resolve(code || 0); + }); + }); + + if (exitCode !== 0) { + logToFrontend('error', '❌ yt-dlp failed with exit code:', exitCode); + logToFrontend('error', '📄 Error output:', errorOutput); + throw new Error( + `yt-dlp failed with exit code ${exitCode}: ${errorOutput}` + + `\nCommand: ${ytDlpPath} ${args.join(' ')}` + ); } + + logToFrontend('info', '🎉 Playlist download completed successfully!'); + sendFeedback('Download complete!', -1); + sendOsNotification('Download complete!', `Saved to: ${dir}`).catch(() => {}); + logToFrontend('info', '✅ Playlist download complete!', `Saved to: ${dir}`); + console.info( + t('plugins.downloader.backend.feedback.done', { + filePath: dir, + }), + ); } catch (error: unknown) { + logToFrontend('error', '💥 Playlist download error:', error); + sendFeedback('Download failed!', -1); + sendOsNotification('Download failed!', String(error)).catch(() => {}); sendError(error as Error); } finally { + logToFrontend('info', '🧹 Cleaning up playlist download...'); win.setProgressBar(-1); // Close progress bar setBadge(0); // Close badge counter sendFeedback(); // Clear feedback } } -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'; @@ -831,31 +816,4 @@ const getPlaylistID = (aURL?: URL): string | null | undefined => { } 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!), - album: info.player_overlays?.browser_media_session?.as( - YTNodes.BrowserMediaSession, - ).album?.text, - imageSrc: info.basic_info.thumbnail?.find((t) => !t.url.endsWith('.webp')) - ?.url, - views: info.basic_info.view_count!, - songDuration: info.basic_info.duration!, - mediaType: MediaType.Audio, -}); - -// This is used to bypass age restrictions -const getAndroidTvInfo = async (id: string): Promise => { - // GetInfo 404s with the bypass, so we use getBasicInfo instead - // that's fine as we only need the streaming data - return await yt.getBasicInfo(id, { - client: 'TV_EMBEDDED', - }); -}; +}; \ No newline at end of file diff --git a/src/plugins/downloader/menu.ts b/src/plugins/downloader/menu.ts index 8838e73ecf..fb0b8cf753 100644 --- a/src/plugins/downloader/menu.ts +++ b/src/plugins/downloader/menu.ts @@ -195,6 +195,36 @@ export const onMenu = async ({ } // Else = user pressed cancel }, }, + { + label: t('plugins.downloader.menu.yt-dlp-location-nice'), + click: async () => { + const ytDlpPathValue = typeof config.advanced?.ytDlpPath === 'string' ? config.advanced.ytDlpPath : ''; + const promptRes = await prompt({ + title: t('plugins.downloader.menu.yt-dlp-location-title'), + label: t('plugins.downloader.menu.yt-dlp-location-label'), + value: ytDlpPathValue, + inputAttrs: { + type: 'text', + placeholder: 'C:/yt-dlp.exe or /usr/bin/yt-dlp', + }, + type: 'input', + ...promptOptions(), + }).catch(console.error); + if (typeof promptRes === 'string') { + setConfig({ + advanced: { + ...(config.advanced || {}), + ytDlpPath: promptRes, + }, + }); + dialog.showMessageBox({ + type: 'info', + message: t('plugins.downloader.menu.yt-dlp-location-saved'), + detail: promptRes, + }); + } + }, + }, { label: t('plugins.downloader.menu.presets'), submenu: Object.keys(DefaultPresetList).map((preset) => ({ @@ -215,4 +245,4 @@ export const onMenu = async ({ }, }, ]; -}; +}; \ No newline at end of file diff --git a/src/plugins/downloader/renderer.tsx b/src/plugins/downloader/renderer.tsx index 533179e7cf..b6d0d14b4f 100644 --- a/src/plugins/downloader/renderer.tsx +++ b/src/plugins/downloader/renderer.tsx @@ -110,4 +110,4 @@ export const onPlayerApiReady = () => { childList: true, subtree: true, }); -}; +}; \ No newline at end of file diff --git a/src/plugins/no-google-login/index.ts b/src/plugins/no-google-login/index.ts new file mode 100644 index 0000000000..5a14e9c64f --- /dev/null +++ b/src/plugins/no-google-login/index.ts @@ -0,0 +1,26 @@ +import style from './style.css?inline'; +import { createPlugin } from '@/utils'; +import { t } from '@/i18n'; + +export default createPlugin({ + name: () => t('plugins.no-google-login.name'), + description: () => t('plugins.no-google-login.description'), + restartNeeded: true, + config: { + enabled: false, + }, + stylesheets: [style], + renderer() { + 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(); + } + } + }, +}); diff --git a/src/plugins/no-google-login/style.css b/src/plugins/no-google-login/style.css new file mode 100644 index 0000000000..874f22b5c0 --- /dev/null +++ b/src/plugins/no-google-login/style.css @@ -0,0 +1,6 @@ +.ytmusic-pivot-bar-renderer[tab-id='FEmusic_liked'], +ytmusic-guide-signin-promo-renderer, +a[href='/music_premium'], +.sign-in-link { + display: none !important; +} diff --git a/src/plugins/synced-lyrics/index.ts b/src/plugins/synced-lyrics/index.ts index 533f05bfb1..53c26afedd 100644 --- a/src/plugins/synced-lyrics/index.ts +++ b/src/plugins/synced-lyrics/index.ts @@ -11,7 +11,7 @@ import type { SyncedLyricsPluginConfig } from './types'; export default createPlugin({ name: () => t('plugins.synced-lyrics.name'), description: () => t('plugins.synced-lyrics.description'), - authors: ['Non0reo', 'ArjixWasTaken', 'KimJammer', 'Strvm'], + authors: ['Non0reo', 'ArjixWasTaken', 'KimJammer', 'Strvm', 'robroid'], restartNeeded: true, addedVersion: '3.5.X', config: { @@ -19,8 +19,9 @@ export default createPlugin({ preciseTiming: true, showLyricsEvenIfInexact: true, showTimeCodes: false, - defaultTextString: '♪', - lineEffect: 'fancy', + defaultTextString: '•••', + lineEffect: 'enhanced', + showEmptyLineSymbols: true, romanization: true, } satisfies SyncedLyricsPluginConfig as SyncedLyricsPluginConfig, diff --git a/src/plugins/synced-lyrics/menu.ts b/src/plugins/synced-lyrics/menu.ts index d1b9e12f41..123b3d4a33 100644 --- a/src/plugins/synced-lyrics/menu.ts +++ b/src/plugins/synced-lyrics/menu.ts @@ -41,22 +41,26 @@ export const menu = async ( ), ], }, - { - label: t('plugins.synced-lyrics.menu.precise-timing.label'), - toolTip: t('plugins.synced-lyrics.menu.precise-timing.tooltip'), - type: 'checkbox', - checked: config.preciseTiming, - click(item) { - ctx.setConfig({ - preciseTiming: item.checked, - }); - }, - }, { label: t('plugins.synced-lyrics.menu.line-effect.label'), toolTip: t('plugins.synced-lyrics.menu.line-effect.tooltip'), type: 'submenu', submenu: [ + { + label: t( + 'plugins.synced-lyrics.menu.line-effect.submenu.enhanced.label', + ), + toolTip: t( + 'plugins.synced-lyrics.menu.line-effect.submenu.enhanced.tooltip', + ), + type: 'radio', + checked: config.lineEffect === 'enhanced', + click() { + ctx.setConfig({ + lineEffect: 'enhanced', + }); + }, + }, { label: t( 'plugins.synced-lyrics.menu.line-effect.submenu.fancy.label', @@ -124,23 +128,52 @@ export const menu = async ( toolTip: t('plugins.synced-lyrics.menu.default-text-string.tooltip'), type: 'submenu', submenu: [ - { label: '♪', value: '♪' }, - { label: '" "', value: ' ' }, - { label: '...', value: ['.', '..', '...'] }, - { label: '•••', value: ['•', '••', '•••'] }, - { label: '———', value: '———' }, - ].map(({ label, value }) => ({ - label, - type: 'radio', - checked: - typeof value === 'string' - ? config.defaultTextString === value - : JSON.stringify(config.defaultTextString) === - JSON.stringify(value), - click() { - ctx.setConfig({ defaultTextString: value }); + ...[ + { label: '•••', value: ['•', '•', '•'] }, + { label: '...', value: ['.', '.', '.'] }, + { label: '♪', value: '♪' }, + { label: '———', value: '———' }, + { label: '(𝑏𝑙𝑎𝑛𝑘)', value: '\u00A0' }, + ].map( + ({ label, value }) => + ({ + label, + type: 'radio', + checked: + JSON.stringify(config.defaultTextString) === + JSON.stringify(value), + enabled: config.showEmptyLineSymbols, + click() { + ctx.setConfig({ defaultTextString: value }); + }, + }) as const, + ), + { type: 'separator' }, + { + label: t('plugins.synced-lyrics.menu.show-empty-line-symbols.label'), + toolTip: t( + 'plugins.synced-lyrics.menu.show-empty-line-symbols.tooltip', + ), + type: 'checkbox', + checked: config.showEmptyLineSymbols ?? false, + click(item) { + ctx.setConfig({ + showEmptyLineSymbols: item.checked, + }); + }, }, - })), + ], + }, + { + label: t('plugins.synced-lyrics.menu.precise-timing.label'), + toolTip: t('plugins.synced-lyrics.menu.precise-timing.tooltip'), + type: 'checkbox', + checked: config.preciseTiming, + click(item) { + ctx.setConfig({ + preciseTiming: item.checked, + }); + }, }, { label: t('plugins.synced-lyrics.menu.romanization.label'), diff --git a/src/plugins/synced-lyrics/parsers/lrc.ts b/src/plugins/synced-lyrics/parsers/lrc.ts index 355c0a4d5c..c6a149dd8c 100644 --- a/src/plugins/synced-lyrics/parsers/lrc.ts +++ b/src/plugins/synced-lyrics/parsers/lrc.ts @@ -1,3 +1,8 @@ +import { + ensureLeadingPaddingEmptyLine, + mergeConsecutiveEmptySyncedLines, +} from '../shared/lines'; + interface LRCTag { tag: string; value: string; @@ -17,7 +22,7 @@ interface LRC { const tagRegex = /^\[(?\w+):\s*(?.+?)\s*\]$/; // prettier-ignore -const lyricRegex = /^\[(?\d+):(?\d+)\.(?\d+)\](?.+)$/; +const lyricRegex = /^\[(?\d+):(?\d+)\.(?\d{1,3})\](?.*)$/; export const LRC = { parse: (text: string): LRC => { @@ -50,13 +55,18 @@ export const LRC = { } const { minutes, seconds, milliseconds, text } = lyric; - const timeInMs = - parseInt(minutes) * 60 * 1000 + - parseInt(seconds) * 1000 + - parseInt(milliseconds); + + // Normalize: take first 2 digits, pad if only 1 digit + const ms2 = milliseconds.padEnd(2, '0').slice(0, 2); + + // Convert to ms (xx → xx0) + const minutesMs = parseInt(minutes) * 60 * 1000; + const secondsMs = parseInt(seconds) * 1000; + const centisMs = parseInt(ms2) * 10; + const timeInMs = minutesMs + secondsMs + centisMs; const currentLine: LRCLine = { - time: `${minutes}:${seconds}:${milliseconds}`, + time: `${minutes.padStart(2, '0')}:${seconds.padStart(2, '0')}.${ms2}`, timeInMs, text: text.trim(), duration: Infinity, @@ -74,15 +84,11 @@ export const LRC = { line.timeInMs += offset; } - const first = lrc.lines.at(0); - if (first && first.timeInMs > 300) { - lrc.lines.unshift({ - time: '0:0:0', - timeInMs: 0, - duration: first.timeInMs, - text: '', - }); - } + // leading padding line if the first line starts late + lrc.lines = ensureLeadingPaddingEmptyLine(lrc.lines, 300, 'span'); + + // Merge consecutive empty lines into a single empty line + lrc.lines = mergeConsecutiveEmptySyncedLines(lrc.lines); return lrc; }, diff --git a/src/plugins/synced-lyrics/providers/LRCLib.ts b/src/plugins/synced-lyrics/providers/LRCLib.ts index cfdea3ab48..14eca592da 100644 --- a/src/plugins/synced-lyrics/providers/LRCLib.ts +++ b/src/plugins/synced-lyrics/providers/LRCLib.ts @@ -2,6 +2,11 @@ import { jaroWinkler } from '@skyra/jaro-winkler'; import { config } from '../renderer/renderer'; import { LRC } from '../parsers/lrc'; +import { + ensureLeadingPaddingEmptyLine, + ensureTrailingEmptyLine, + mergeConsecutiveEmptySyncedLines, +} from '../shared/lines'; import type { LyricProvider, LyricResult, SearchSongInfo } from '../types'; @@ -77,61 +82,59 @@ export class LRCLib implements LyricProvider { } const filteredResults = []; + const SIM_THRESHOLD = 0.9; for (const item of data) { - const { artistName } = item; - - const artists = artist.split(/[&,]/g).map((i) => i.trim()); - const itemArtists = artistName.split(/[&,]/g).map((i) => i.trim()); + // quick duration guard to avoid expensive similarity on far-off matches + if (Math.abs(item.duration - songDuration) > 15) continue; + if (item.instrumental) continue; - // Try to match using artist name first - const permutations = []; - for (const artistA of artists) { - for (const artistB of itemArtists) { - permutations.push([artistA.toLowerCase(), artistB.toLowerCase()]); - } - } + const { artistName } = item; - for (const artistA of itemArtists) { - for (const artistB of artists) { - permutations.push([artistA.toLowerCase(), artistB.toLowerCase()]); + const artists = artist + .split(/[&,]/g) + .map((i) => i.trim().toLowerCase()) + .filter(Boolean); + const itemArtists = artistName + .split(/[&,]/g) + .map((i) => i.trim().toLowerCase()) + .filter(Boolean); + + // fast path: any exact artist match + let ratio = 0; + if (artists.some((a) => itemArtists.includes(a))) { + ratio = 1; + } else { + // compute best pairwise similarity with early exit + outer: for (const a of artists) { + for (const b of itemArtists) { + const r = jaroWinkler(a, b); + if (r > ratio) ratio = r; + if (ratio >= 0.97) break outer; // good enough, stop early + } } } - 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(), + // If direct artist match is below threshold and we have tags, compare tags too + if (ratio <= SIM_THRESHOLD && tags && tags.length > 0) { + const artistSet = new Set(artists); + const filteredTags = Array.from( + new Set( + tags + .map((t) => t.trim().toLowerCase()) + .filter((t) => t && !artistSet.has(t)), + ), ); - const tagPermutations = []; - // Compare each tag with each item artist - for (const tag of filteredTags) { - for (const itemArtist of itemArtists) { - tagPermutations.push([tag.toLowerCase(), itemArtist.toLowerCase()]); + outerTags: for (const t of filteredTags) { + for (const b of itemArtists) { + const r = jaroWinkler(t, b); + if (r > ratio) ratio = r; + if (ratio >= 0.97) break outerTags; } } - - // 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); - } } - if (ratio <= 0.9) continue; + if (ratio <= SIM_THRESHOLD) continue; filteredResults.push(item); } @@ -157,21 +160,36 @@ export class LRCLib implements LyricProvider { const raw = closestResult.syncedLyrics; const plain = closestResult.plainLyrics; - if (!raw && !plain) { - return null; + + if (raw) { + // Prefer synced + const parsed = LRC.parse(raw).lines.map((l) => ({ + ...l, + status: 'upcoming' as const, + })); + const merged = mergeConsecutiveEmptySyncedLines(parsed); + const withLeading = ensureLeadingPaddingEmptyLine(merged, 300, 'span'); + const finalLines = ensureTrailingEmptyLine( + withLeading, + 'midpoint', + songDuration * 1000, + ); + + return { + title: closestResult.trackName, + artists: closestResult.artistName.split(/[&,]/g), + lines: finalLines, + }; + } else if (plain) { + // Fallback to plain if no synced + return { + title: closestResult.trackName, + artists: closestResult.artistName.split(/[&,]/g), + lyrics: plain, + }; } - return { - title: closestResult.trackName, - artists: closestResult.artistName.split(/[&,]/g), - lines: raw - ? LRC.parse(raw).lines.map((l) => ({ - ...l, - status: 'upcoming' as const, - })) - : undefined, - lyrics: plain, - }; + return null; } } diff --git a/src/plugins/synced-lyrics/providers/LyricsGenius.ts b/src/plugins/synced-lyrics/providers/LyricsGenius.ts index 0ebed6285d..e24aaf516d 100644 --- a/src/plugins/synced-lyrics/providers/LyricsGenius.ts +++ b/src/plugins/synced-lyrics/providers/LyricsGenius.ts @@ -89,10 +89,16 @@ export class LyricsGenius implements LyricProvider { return null; } + // final empty line for padding. + let finalLyrics = lyrics; + if (!finalLyrics.endsWith('\n\n')) { + finalLyrics = finalLyrics.endsWith('\n') ? finalLyrics + '\n' : finalLyrics + '\n\n'; + } + return { title: closestHit.result.title, artists: closestHit.result.primary_artists.map(({ name }) => name), - lyrics, + lyrics: finalLyrics, }; } } diff --git a/src/plugins/synced-lyrics/providers/MusixMatch.ts b/src/plugins/synced-lyrics/providers/MusixMatch.ts index 275c13329c..ffb5b004ce 100644 --- a/src/plugins/synced-lyrics/providers/MusixMatch.ts +++ b/src/plugins/synced-lyrics/providers/MusixMatch.ts @@ -1,6 +1,11 @@ import * as z from 'zod'; import { LRC } from '../parsers/lrc'; +import { + ensureLeadingPaddingEmptyLine, + ensureTrailingEmptyLine, + mergeConsecutiveEmptySyncedLines, +} from '../shared/lines'; import { netFetch } from '../renderer'; import type { LyricProvider, LyricResult, SearchSongInfo } from '../types'; @@ -42,10 +47,19 @@ export class MusixMatch implements LyricProvider { title: track.track_name, artists: [track.artist_name], lines: subtitle - ? LRC.parse(subtitle.subtitle.subtitle_body).lines.map((l) => ({ - ...l, - status: 'upcoming' as const, - })) + ? (() => { + const parsed = LRC.parse(subtitle.subtitle.subtitle_body).lines.map( + (l) => ({ ...l, status: 'upcoming' as const }), + ); + + const merged = mergeConsecutiveEmptySyncedLines(parsed); + const withLeading = ensureLeadingPaddingEmptyLine( + merged, + 300, + 'span', + ); + return ensureTrailingEmptyLine(withLeading, 'lastEnd'); + })() : undefined, lyrics: lyrics, }; diff --git a/src/plugins/synced-lyrics/providers/YTMusic.ts b/src/plugins/synced-lyrics/providers/YTMusic.ts index 1888724fd8..e89fc88083 100644 --- a/src/plugins/synced-lyrics/providers/YTMusic.ts +++ b/src/plugins/synced-lyrics/providers/YTMusic.ts @@ -1,3 +1,9 @@ +import { + ensureLeadingPaddingEmptyLine, + ensureTrailingEmptyLine, + mergeConsecutiveEmptySyncedLines, +} from '../shared/lines'; + import type { LyricProvider, LyricResult, SearchSongInfo } from '../types'; import type { MusicPlayerAppElement } from '@/types/music-player-app-element'; @@ -52,13 +58,14 @@ export class YTMusic implements LyricProvider { const synced = syncedLines?.length && syncedLines[0]?.cueRange ? syncedLines.map((it) => ({ - time: this.millisToTime(parseInt(it.cueRange.startTimeMilliseconds)), - timeInMs: parseInt(it.cueRange.startTimeMilliseconds), - duration: parseInt(it.cueRange.endTimeMilliseconds) - - parseInt(it.cueRange.startTimeMilliseconds), - text: it.lyricLine.trim() === '♪' ? '' : it.lyricLine.trim(), - status: 'upcoming' as const, - })) + time: this.millisToTime(parseInt(it.cueRange.startTimeMilliseconds)), + timeInMs: parseInt(it.cueRange.startTimeMilliseconds), + duration: + parseInt(it.cueRange.endTimeMilliseconds) - + parseInt(it.cueRange.startTimeMilliseconds), + text: it.lyricLine.trim() === '♪' ? '' : it.lyricLine.trim(), + status: 'upcoming' as const, + })) : undefined; const plain = !synced @@ -67,41 +74,40 @@ export class YTMusic implements LyricProvider { : contents?.messageRenderer ? contents?.messageRenderer?.text?.runs?.map((it) => it.text).join('\n') : contents?.sectionListRenderer?.contents?.[0] - ?.musicDescriptionShelfRenderer?.description?.runs?.map((it) => - it.text - )?.join('\n') + ?.musicDescriptionShelfRenderer?.description?.runs + ?.map((it) => it.text) + ?.join('\n') : undefined; if (typeof plain === 'string' && plain === 'Lyrics not available') { return null; } - if (synced?.length && synced[0].timeInMs > 300) { - synced.unshift({ - duration: 0, - text: '', - time: '00:00.00', - timeInMs: 0, - status: 'upcoming' as const, - }); - } + const processed = synced + ? (() => { + const merged = mergeConsecutiveEmptySyncedLines(synced); + const withLeading = ensureLeadingPaddingEmptyLine(merged, 300, 'span'); + return ensureTrailingEmptyLine(withLeading, 'lastEnd'); + })() + : undefined; return { title, artists: [artist], lyrics: plain, - lines: synced, + lines: processed, }; } private millisToTime(millis: number) { const minutes = Math.floor(millis / 60000); - const seconds = Math.floor((millis - minutes * 60 * 1000) / 1000); - const remaining = (millis - minutes * 60 * 1000 - seconds * 1000) / 10; + const seconds = Math.floor((millis % 60000) / 1000); + const centiseconds = Math.floor((millis % 1000) / 10); + return `${minutes.toString().padStart(2, '0')}:${seconds .toString() - .padStart(2, '0')}.${remaining.toString().padStart(2, '0')}`; + .padStart(2, '0')}.${centiseconds.toString().padStart(2, '0')}`; } // RATE LIMITED (2 req per sec) diff --git a/src/plugins/synced-lyrics/renderer/components/LyricsPicker.tsx b/src/plugins/synced-lyrics/renderer/components/LyricsPicker.tsx index 3e18626bed..5bebb2874a 100644 --- a/src/plugins/synced-lyrics/renderer/components/LyricsPicker.tsx +++ b/src/plugins/synced-lyrics/renderer/components/LyricsPicker.tsx @@ -34,7 +34,7 @@ import { } from '../../providers'; import { currentLyrics, lyricsStore, setLyricsStore } from '../store'; import { _ytAPI } from '../index'; -import { config } from '../renderer'; +import { config, requestFastScroll } from '../renderer'; import type { PlayerAPIEvents } from '@/types/player-api-events'; @@ -46,6 +46,8 @@ export const providerIdx = createMemo(() => providerNames.indexOf(lyricsStore.provider), ); +const FAST_SWITCH_MS = 2500; + const shouldSwitchProvider = (providerData: ProviderState) => { if (providerData.state === 'error') return true; if (providerData.state === 'fetching') return true; @@ -59,7 +61,6 @@ const shouldSwitchProvider = (providerData: ProviderState) => { const providerBias = (p: ProviderName) => (lyricsStore.lyrics[p].state === 'done' ? 1 : -1) + (lyricsStore.lyrics[p].data?.lines?.length ? 2 : -1) + - // eslint-disable-next-line prettier/prettier (lyricsStore.lyrics[p].data?.lines?.length && p === ProviderNames.YTMusic ? 1 : 0) + @@ -90,6 +91,12 @@ export const LyricsPicker = (props: { const [starredProvider, setStarredProvider] = createSignal(null); + const favoriteProviderKey = (id: string) => `ytmd-sl-starred-${id}`; + const switchProvider = (provider: ProviderName, fastMs = FAST_SWITCH_MS) => { + requestFastScroll(fastMs); + setLyricsStore('provider', provider); + }; + createEffect(() => { const id = videoId(); if (id === null) { @@ -97,14 +104,20 @@ export const LyricsPicker = (props: { return; } - const key = `ytmd-sl-starred-${id}`; + const key = favoriteProviderKey(id); const value = localStorage.getItem(key); if (!value) { setStarredProvider(null); return; } - const parseResult = LocalStorageSchema.safeParse(JSON.parse(value)); + let parsed: unknown = null; + try { + parsed = JSON.parse(value); + } catch { + parsed = null; + } + const parseResult = LocalStorageSchema.safeParse(parsed); if (parseResult.success) { setLyricsStore('provider', parseResult.data.provider); setStarredProvider(parseResult.data.provider); @@ -117,7 +130,7 @@ export const LyricsPicker = (props: { const id = videoId(); if (id === null) return; - const key = `ytmd-sl-starred-${id}`; + const key = favoriteProviderKey(id); setStarredProvider((starredProvider) => { if (lyricsStore.provider === starredProvider) { @@ -152,7 +165,7 @@ export const LyricsPicker = (props: { if (!hasManuallySwitchedProvider()) { const starred = starredProvider(); if (starred !== null) { - setLyricsStore('provider', starred); + switchProvider(starred); return; } @@ -166,29 +179,34 @@ export const LyricsPicker = (props: { force || providerBias(lyricsStore.provider) < providerBias(provider) ) { - setLyricsStore('provider', provider); + switchProvider(provider); } } }); const next = () => { setHasManuallySwitchedProvider(true); - setLyricsStore('provider', (prevProvider) => { - const idx = providerNames.indexOf(prevProvider); - return providerNames[(idx + 1) % providerNames.length]; - }); + const nextProvider = + providerNames[(providerIdx() + 1) % providerNames.length]; + switchProvider(nextProvider); }; const previous = () => { setHasManuallySwitchedProvider(true); - setLyricsStore('provider', (prevProvider) => { - const idx = providerNames.indexOf(prevProvider); - return providerNames[ - (idx + providerNames.length - 1) % providerNames.length + const prev = + providerNames[ + (providerIdx() + providerNames.length - 1) % providerNames.length ]; - }); + switchProvider(prev); }; + const chevronLeft: YtIcons = 'yt-icons:chevron_left'; + const chevronRight: YtIcons = 'yt-icons:chevron_right'; + + const successIcon: YtIcons = 'yt-icons:check-circle'; + const errorIcon: YtIcons = 'yt-icons:error'; + const notFoundIcon: YtIcons = 'yt-icons:warning'; + return (
@@ -211,7 +229,7 @@ export const LyricsPicker = (props: {
@@ -284,7 +302,10 @@ export const LyricsPicker = (props: { {(_, idx) => (
  • setLyricsStore('provider', providerNames[idx()])} + onClick={() => { + setHasManuallySwitchedProvider(true); + switchProvider(providerNames[idx()]); + }} style={{ background: idx() === providerIdx() ? 'white' : 'black', }} diff --git a/src/plugins/synced-lyrics/renderer/components/SyncedLine.tsx b/src/plugins/synced-lyrics/renderer/components/SyncedLine.tsx index b34f0982ea..591cf7b419 100644 --- a/src/plugins/synced-lyrics/renderer/components/SyncedLine.tsx +++ b/src/plugins/synced-lyrics/renderer/components/SyncedLine.tsx @@ -7,7 +7,14 @@ import { type LineLyrics } from '@/plugins/synced-lyrics/types'; import { config, currentTime } from '../renderer'; import { _ytAPI } from '..'; -import { canonicalize, romanize, simplifyUnicode } from '../utils'; +import { + canonicalize, + romanize, + simplifyUnicode, + getSeekTime, + isBlank, + timeCodeText, +} from '../utils'; interface SyncedLineProps { scroller: VirtualizerHandle; @@ -15,27 +22,128 @@ interface SyncedLineProps { line: LineLyrics; status: 'upcoming' | 'current' | 'previous'; + isFinalLine?: boolean; + isFirstEmptyLine?: boolean; } +// small helpers +const END_DELAY_SECONDS = 1.0; // end delay at line end +const WORD_ANIM_DELAY_STEP = 0.05; // seconds per word index + +const computeEndDelayMs = (totalMs: number): number => { + const LONG_MS = 3000; + const SHORT_MS = 1000; + const SHORT_SECONDS = END_DELAY_SECONDS / 2; + const SHORT_FRACTION = 0.8; + const SHORT_MIN_GAP_MS = Math.round(SHORT_SECONDS * 1000 * 0.3); + + if (totalMs > LONG_MS) { + return Math.round(END_DELAY_SECONDS * 1000); + } + if (totalMs >= SHORT_MS) { + const ratio = (totalMs - SHORT_MS) / (LONG_MS - SHORT_MS); + const endDelayDelta = (END_DELAY_SECONDS - SHORT_SECONDS) * ratio; + const endDelaySeconds = SHORT_SECONDS + endDelayDelta; + return Math.round(endDelaySeconds * 1000); + } + return Math.min( + Math.round(totalMs * SHORT_FRACTION), + totalMs - SHORT_MIN_GAP_MS, + ); +}; + +const seekToMs = (ms: number) => { + const precise = config()?.preciseTiming ?? false; + _ytAPI?.seekTo(getSeekTime(ms, precise)); +}; + +const renderWordSpans = (input: string) => ( + + + {(word, index) => ( + + + + )} + + +); + const EmptyLine = (props: SyncedLineProps) => { const states = createMemo(() => { const defaultText = config()?.defaultTextString ?? ''; return Array.isArray(defaultText) ? defaultText : [defaultText]; }); + const isCumulative = createMemo(() => { + const arr = states(); + if (arr.length <= 1) return false; + return arr.every((value) => value === arr[0]); + }); + + const endDelayMsValue = createMemo(() => + computeEndDelayMs(props.line.duration), + ); + const index = createMemo(() => { const progress = currentTime() - props.line.timeInMs; const total = props.line.duration; + const stepCount = states().length; + const precise = config()?.preciseTiming ?? false; + + if (stepCount === 1) return 0; + + const endDelayMs = endDelayMsValue(); - const percentage = Math.min(1, progress / total); - return Math.max(0, Math.floor((states().length - 1) * percentage)); + const effectiveTotal = + total <= 1000 + ? total - endDelayMs + : precise + ? total - endDelayMs + : Math.round((total - endDelayMs) / 1000) * 1000; + + if (effectiveTotal <= 0) return 0; + + const effectiveProgress = precise + ? progress + : Math.round(progress / 1000) * 1000; + const percentage = Math.min(1, effectiveProgress / effectiveTotal); + + return Math.max(0, Math.floor((stepCount - 1) * percentage)); + }); + + const shouldRenderPlaceholder = createMemo(() => { + const isEmpty = isBlank(props.line.text); + const showEmptySymbols = config()?.showEmptyLineSymbols ?? false; + + return isEmpty + ? showEmptySymbols || props.status === 'current' + : props.status === 'current'; + }); + + const isHighlighted = createMemo(() => props.status === 'current'); + const isFinalEmpty = createMemo(() => { + return props.isFinalLine && isBlank(props.line.text); + }); + + const shouldRemovePadding = createMemo(() => { + // remove padding only when this is the first empty line and the configured label is blank (empty string or NBSP) + if (!props.isFirstEmptyLine) return false; + const defaultText = config()?.defaultTextString ?? ''; + const first = Array.isArray(defaultText) ? defaultText[0] : defaultText; + return first === '' || first === '\u00A0'; }); return (
    { - _ytAPI?.seekTo((props.line.timeInMs + 10) / 1000); + seekToMs(props.line.timeInMs); }} >
    @@ -43,37 +151,60 @@ const EmptyLine = (props: SyncedLineProps) => { text={{ runs: [ { - text: config()?.showTimeCodes ? `[${props.line.time}] ` : '', + text: timeCodeText( + props.line.timeInMs, + config()?.preciseTiming ?? false, + config()?.showTimeCodes ?? false, + ), }, ], }} /> -
    - + {props.isFinalLine && isBlank(props.line.text) ? ( - + + + + ) : ( + - } - when={states().length > 1} - > - - - - + + } + when={isCumulative()} + > + + {(text, i) => ( + + + + )} + + + )}
    @@ -98,7 +229,7 @@ export const SyncedLine = (props: SyncedLineProps) => {
    { - _ytAPI?.seekTo((props.line.timeInMs + 10) / 1000); + seekToMs(props.line.timeInMs); }} >
    @@ -106,7 +237,11 @@ export const SyncedLine = (props: SyncedLineProps) => { text={{ runs: [ { - text: config()?.showTimeCodes ? `[${props.line.time}] ` : '', + text: timeCodeText( + props.line.timeInMs, + config()?.preciseTiming ?? false, + config()?.showTimeCodes ?? false, + ), }, ], }} @@ -124,26 +259,7 @@ export const SyncedLine = (props: SyncedLineProps) => { }} style={{ 'display': 'flex', 'flex-direction': 'column' }} > - - - {(word, index) => { - return ( - - - - ); - }} - - + {renderWordSpans(text())} { simplifyUnicode(text()) !== simplifyUnicode(romanization()) } > - - - {(word, index) => { - return ( - - - - ); - }} - - + {renderWordSpans(romanization())}
    diff --git a/src/plugins/synced-lyrics/renderer/renderer.tsx b/src/plugins/synced-lyrics/renderer/renderer.tsx index 24c3d70102..e5147183ea 100644 --- a/src/plugins/synced-lyrics/renderer/renderer.tsx +++ b/src/plugins/synced-lyrics/renderer/renderer.tsx @@ -1,3 +1,4 @@ +/* eslint-disable stylistic/no-mixed-operators */ import { createEffect, createSignal, @@ -10,7 +11,13 @@ import { type VirtualizerHandle, VList } from 'virtua/solid'; import { LyricsPicker } from './components/LyricsPicker'; -import { selectors } from './utils'; +import { + selectors, + getSeekTime, + SFont, + normalizePlainLyrics, + isBlank, +} from './utils'; import { ErrorDisplay, @@ -21,6 +28,7 @@ import { } from './components'; import { currentLyrics } from './store'; +import { clamp, SCROLL_DURATION, LEAD_IN_TIME_MS } from './scrolling'; import type { LineLyrics, SyncedLyricsPluginConfig } from '../types'; @@ -28,13 +36,55 @@ export const [isVisible, setIsVisible] = createSignal(false); export const [config, setConfig] = createSignal(null); +export const [fastScrollUntil, setFastScrollUntil] = createSignal(0); +export const requestFastScroll = (windowMs = 700) => + setFastScrollUntil(performance.now() + windowMs); + +export const [suppressFastUntil, setSuppressFastUntil] = + createSignal(0); +export const suppressFastScroll = (windowMs = 1200) => + setSuppressFastUntil(performance.now() + windowMs); + createEffect(() => { if (!config()?.enabled) return; const root = document.documentElement; + const lineEffect = config()?.lineEffect || 'none'; + document.body.classList.toggle('enhanced-lyrics', lineEffect === 'enhanced'); + switch (lineEffect) { + case 'enhanced': + root.style.setProperty('--lyrics-font-family', 'Satoshi, sans-serif'); + root.style.setProperty('--lyrics-font-size', '3rem'); + root.style.setProperty('--lyrics-line-height', '1.333'); + root.style.setProperty('--lyrics-width', '100%'); + root.style.setProperty('--lyrics-padding', '12.5px'); + root.style.setProperty('--lyrics-will-change', 'transform, opacity'); - // Set the line effect - switch (config()?.lineEffect) { + root.style.setProperty( + '--lyrics-animations', + 'lyrics-glow var(--lyrics-glow-duration) forwards, lyrics-wobble var(--lyrics-wobble-duration) forwards', + ); + + root.style.setProperty('--lyrics-inactive-font-weight', '700'); + root.style.setProperty('--lyrics-inactive-opacity', '0.33'); + root.style.setProperty('--lyrics-inactive-scale', '0.95'); + root.style.setProperty('--lyrics-inactive-offset', '0'); + + root.style.setProperty('--lyrics-active-font-weight', '700'); + root.style.setProperty('--lyrics-active-opacity', '1'); + root.style.setProperty('--lyrics-active-scale', '1'); + root.style.setProperty('--lyrics-active-offset', '0'); + + root.style.setProperty('--lyrics-hover-scale', '0.975'); + root.style.setProperty('--lyrics-hover-opacity', '0.585'); + root.style.setProperty('--lyrics-hover-empty-opacity', '1'); + + root.style.setProperty('--lyrics-empty-opacity', '0.495'); + break; case 'fancy': + root.style.setProperty( + '--lyrics-font-family', + '"Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif', + ); root.style.setProperty('--lyrics-font-size', '3rem'); root.style.setProperty('--lyrics-line-height', '1.333'); root.style.setProperty('--lyrics-width', '100%'); @@ -43,6 +93,7 @@ createEffect(() => { '--lyrics-animations', 'lyrics-glow var(--lyrics-glow-duration) forwards, lyrics-wobble var(--lyrics-wobble-duration) forwards', ); + root.style.setProperty('--lyrics-will-change', 'auto'); root.style.setProperty('--lyrics-inactive-font-weight', '700'); root.style.setProperty('--lyrics-inactive-opacity', '0.33'); @@ -53,8 +104,18 @@ createEffect(() => { root.style.setProperty('--lyrics-active-opacity', '1'); root.style.setProperty('--lyrics-active-scale', '1'); root.style.setProperty('--lyrics-active-offset', '0'); + + root.style.setProperty('--lyrics-hover-scale', '0.95'); + root.style.setProperty('--lyrics-hover-opacity', '0.33'); + root.style.setProperty('--lyrics-hover-empty-opacity', '1'); + + root.style.setProperty('--lyrics-empty-opacity', '1'); break; case 'scale': + root.style.setProperty( + '--lyrics-font-family', + '"Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif', + ); root.style.setProperty( '--lyrics-font-size', 'clamp(1.4rem, 1.1vmax, 3rem)', @@ -66,6 +127,7 @@ createEffect(() => { root.style.setProperty('--lyrics-width', '83%'); root.style.setProperty('--lyrics-padding', '0'); root.style.setProperty('--lyrics-animations', 'none'); + root.style.setProperty('--lyrics-will-change', 'auto'); root.style.setProperty('--lyrics-inactive-font-weight', '400'); root.style.setProperty('--lyrics-inactive-opacity', '0.33'); @@ -76,8 +138,18 @@ createEffect(() => { root.style.setProperty('--lyrics-active-opacity', '1'); root.style.setProperty('--lyrics-active-scale', '1.2'); root.style.setProperty('--lyrics-active-offset', '0'); + + root.style.setProperty('--lyrics-hover-scale', '1'); + root.style.setProperty('--lyrics-hover-opacity', '0.33'); + root.style.setProperty('--lyrics-hover-empty-opacity', '1'); + + root.style.setProperty('--lyrics-empty-opacity', '1'); break; case 'offset': + root.style.setProperty( + '--lyrics-font-family', + '"Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif', + ); root.style.setProperty( '--lyrics-font-size', 'clamp(1.4rem, 1.1vmax, 3rem)', @@ -89,6 +161,7 @@ createEffect(() => { root.style.setProperty('--lyrics-width', '100%'); root.style.setProperty('--lyrics-padding', '0'); root.style.setProperty('--lyrics-animations', 'none'); + root.style.setProperty('--lyrics-will-change', 'auto'); root.style.setProperty('--lyrics-inactive-font-weight', '400'); root.style.setProperty('--lyrics-inactive-opacity', '0.33'); @@ -99,8 +172,18 @@ createEffect(() => { root.style.setProperty('--lyrics-active-opacity', '1'); root.style.setProperty('--lyrics-active-scale', '1'); root.style.setProperty('--lyrics-active-offset', '5%'); + + root.style.setProperty('--lyrics-hover-scale', '1'); + root.style.setProperty('--lyrics-hover-opacity', '0.33'); + root.style.setProperty('--lyrics-hover-empty-opacity', '1'); + + root.style.setProperty('--lyrics-empty-opacity', '1'); break; case 'focus': + root.style.setProperty( + '--lyrics-font-family', + '"Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif', + ); root.style.setProperty( '--lyrics-font-size', 'clamp(1.4rem, 1.1vmax, 3rem)', @@ -112,6 +195,7 @@ createEffect(() => { root.style.setProperty('--lyrics-width', '100%'); root.style.setProperty('--lyrics-padding', '0'); root.style.setProperty('--lyrics-animations', 'none'); + root.style.setProperty('--lyrics-will-change', 'auto'); root.style.setProperty('--lyrics-inactive-font-weight', '400'); root.style.setProperty('--lyrics-inactive-opacity', '0.33'); @@ -122,6 +206,12 @@ createEffect(() => { root.style.setProperty('--lyrics-active-opacity', '1'); root.style.setProperty('--lyrics-active-scale', '1'); root.style.setProperty('--lyrics-active-offset', '0'); + + root.style.setProperty('--lyrics-hover-scale', '1'); + root.style.setProperty('--lyrics-hover-opacity', '0.33'); + root.style.setProperty('--lyrics-hover-empty-opacity', '1'); + + root.style.setProperty('--lyrics-empty-opacity', '1'); break; } }); @@ -143,10 +233,18 @@ type LyricsRendererChild = const lyricsPicker: LyricsRendererChild = { kind: 'LyricsPicker' }; export const [currentTime, setCurrentTime] = createSignal(-1); +export const [scrollTargetIndex, setScrollTargetIndex] = + createSignal(0); export const LyricsRenderer = () => { const [scroller, setScroller] = createSignal(); const [stickyRef, setStickRef] = createSignal(null); + let prevTimeForScroll = -1; + let prevIndexForFast = -1; + + let scrollAnimRaf: number | null = null; + let scrollAnimActive = false; + const tab = document.querySelector(selectors.body.tabRenderer)!; let mouseCoord = 0; @@ -174,6 +272,7 @@ export const LyricsRenderer = () => { }; onMount(() => { + SFont(); const vList = document.querySelector('.synced-lyrics-vlist'); tab.addEventListener('mousemove', mousemoveListener); @@ -190,6 +289,9 @@ export const LyricsRenderer = () => { const [children, setChildren] = createSignal([ { kind: 'LoadingKaomoji' }, ]); + const [firstEmptyIndex, setFirstEmptyIndex] = createSignal( + null, + ); createEffect(() => { const current = currentLyrics(); @@ -210,20 +312,26 @@ export const LyricsRenderer = () => { } if (data?.lines) { - return data.lines.map((line) => ({ + const lines = data.lines; + const firstEmpty = lines.findIndex((l) => isBlank(l.text)); + setFirstEmptyIndex(firstEmpty === -1 ? null : firstEmpty); + + return lines.map((line) => ({ kind: 'SyncedLine' as const, line, })); } if (data?.lyrics) { - const lines = data.lyrics.split('\n').filter((line) => line.trim()); + const lines = normalizePlainLyrics(data.lyrics); + return lines.map((line) => ({ kind: 'PlainLine' as const, line, })); } + setFirstEmptyIndex(null); return [{ kind: 'NotFoundKaomoji' }]; }); }); @@ -232,23 +340,35 @@ export const LyricsRenderer = () => { ('previous' | 'current' | 'upcoming')[] >([]); createEffect(() => { - const time = currentTime(); + const precise = config()?.preciseTiming ?? false; const data = currentLyrics()?.data; + const currentTimeMs = currentTime(); - if (!data || !data.lines) return setStatuses([]); + if (!data || !data.lines) { + setStatuses([]); + return; + } const previous = untrack(statuses); + const current = data.lines.map((line) => { - if (line.timeInMs >= time) return 'upcoming'; - if (time - line.timeInMs >= line.duration) return 'previous'; + const startTimeMs = getSeekTime(line.timeInMs, precise) * 1000; + const endTimeMs = + getSeekTime(line.timeInMs + line.duration, precise) * 1000; + + if (currentTimeMs < startTimeMs) return 'upcoming'; + if (currentTimeMs >= endTimeMs) return 'previous'; return 'current'; }); - if (previous.length !== current.length) return setStatuses(current); - if (previous.every((status, idx) => status === current[idx])) return; + if (previous.length !== current.length) { + setStatuses(current); + return; + } - setStatuses(current); - return; + if (!previous.every((status, idx) => status === current[idx])) { + setStatuses(current); + } }); const [currentIndex, setCurrentIndex] = createSignal(0); @@ -258,20 +378,277 @@ export const LyricsRenderer = () => { setCurrentIndex(index); }); + // when lyrics tab becomes visible again, open a short fast-scroll window createEffect(() => { + if (isVisible()) { + requestFastScroll(1500); + } + }); + + // scroll effect + createEffect(() => { + const visible = isVisible(); const current = currentLyrics(); - const idx = currentIndex(); - const maxIdx = untrack(statuses).length - 1; + const targetIndex = scrollTargetIndex(); + const maxIndex = untrack(statuses).length - 1; + const scrollerInstance = scroller(); + const lineEffect = config()?.lineEffect; + const isEnhanced = lineEffect === 'enhanced'; - if (!scroller() || !current.data?.lines) return; + if (!visible || !scrollerInstance || !current.data?.lines) return; // hacky way to make the "current" line scroll to the center of the screen - const scrollIndex = Math.min(idx + 1, maxIdx); + const scrollIndex = Math.min(targetIndex + 1, maxIndex); + + // animation duration + const calculateDuration = ( + distance: number, + jumpSize: number, + fast: boolean, + ) => { + if (fast) { + return clamp( + SCROLL_DURATION.FAST_BASE + distance * SCROLL_DURATION.FAST_MULT, + SCROLL_DURATION.FAST_MIN, + SCROLL_DURATION.FAST_MAX, + ); + } - scroller()!.scrollToIndex(scrollIndex, { - smooth: true, - align: 'center', - }); + let base: number = SCROLL_DURATION.NORMAL_BASE; + let mult: number = SCROLL_DURATION.NORMAL_MULT; + let min: number = SCROLL_DURATION.NORMAL_MIN; + let max: number = SCROLL_DURATION.NORMAL_MAX; + + if (jumpSize === 1) { + base = SCROLL_DURATION.JUMP1_BASE; + mult = SCROLL_DURATION.JUMP1_MULT; + min = SCROLL_DURATION.JUMP1_MIN; + max = SCROLL_DURATION.JUMP1_MAX; + } else if (jumpSize > 3) { + base = SCROLL_DURATION.JUMP4_BASE; + mult = SCROLL_DURATION.JUMP4_MULT; + min = SCROLL_DURATION.JUMP4_MIN; + max = SCROLL_DURATION.JUMP4_MAX; + } + + const duration = base + distance * mult; + return clamp(duration, min, max); + }; + + // easing function + const easeInOutCubic = (t: number) => { + if (t < 0.5) { + return 4 * t ** 3; + } + const t1 = -2 * t + 2; + return 1 - t1 ** 3 / 2; + }; + + // target scroll offset + const calculateEnhancedTargetOffset = ( + scrollerInstance: VirtualizerHandle, + scrollIndex: number, + currentIndex: number, + ) => { + const viewportSize = scrollerInstance.viewportSize; + const itemOffset = scrollerInstance.getItemOffset(scrollIndex); + const itemSize = scrollerInstance.getItemSize(scrollIndex); + const maxScroll = scrollerInstance.scrollSize - viewportSize; + + if (currentIndex === 0) return 0; + + const viewportCenter = viewportSize / 2; + const itemCenter = itemSize / 2; + const centerOffset = itemOffset - viewportCenter + itemCenter; + + return clamp(centerOffset, 0, maxScroll); + }; + + // enhanced scroll animation + const performEnhancedScroll = ( + scrollerInstance: VirtualizerHandle, + scrollIndex: number, + currentIndex: number, + fast: boolean, + ) => { + const targetOffset = calculateEnhancedTargetOffset( + scrollerInstance, + scrollIndex, + currentIndex, + ); + const startOffset = scrollerInstance.scrollOffset; + + if (startOffset === targetOffset) return; + + const distance = Math.abs(targetOffset - startOffset); + const jumpSize = Math.abs(scrollIndex - currentIndex); + const duration = calculateDuration(distance, jumpSize, fast); + + // offset start time for responsive feel + const animationStartTimeOffsetMs = fast ? 15 : 170; + const startTime = performance.now() - animationStartTimeOffsetMs; + + scrollAnimActive = false; + if (scrollAnimRaf !== null) cancelAnimationFrame(scrollAnimRaf); + + if (distance < 0.5) { + scrollerInstance.scrollTo(targetOffset); + return; + } + + const animate = (now: number) => { + if (!scrollAnimActive) return; + const elapsed = now - startTime; + const progress = Math.min(elapsed / duration, 1); + const eased = easeInOutCubic(progress); + const offsetDiff = (targetOffset - startOffset) * eased; + const currentOffset = startOffset + offsetDiff; + + scrollerInstance.scrollTo(currentOffset); + if (progress < 1 && scrollAnimActive) { + scrollAnimRaf = requestAnimationFrame(animate); + } + }; + + scrollAnimActive = true; + scrollAnimRaf = requestAnimationFrame(animate); + }; + + // validate scroller measurements + const isScrollerReady = ( + scrollerInstance: VirtualizerHandle, + scrollIndex: number, + ) => { + const viewport = scrollerInstance.viewportSize; + const size = scrollerInstance.getItemSize(scrollIndex); + const offset = scrollerInstance.getItemOffset(scrollIndex); + return viewport > 0 && size > 0 && offset >= 0; + }; + + let readyRafId: number | null = null; + + const cleanup = () => { + if (readyRafId !== null) cancelAnimationFrame(readyRafId); + scrollAnimActive = false; + if (scrollAnimRaf !== null) cancelAnimationFrame(scrollAnimRaf); + }; + onCleanup(cleanup); + + // wait for scroller ready + const waitForReady = (tries = 0) => { + const nonEnhanced = !isEnhanced; + const scrollerReady = isScrollerReady(scrollerInstance, scrollIndex); + const hasCurrentIndex = !nonEnhanced || currentIndex() >= 0; + + if ((scrollerReady && hasCurrentIndex) || tries >= 20) { + performScroll(); + } else { + readyRafId = requestAnimationFrame(() => waitForReady(tries + 1)); + } + }; + + const performScroll = () => { + const now = performance.now(); + const inFastWindow = now < fastScrollUntil(); + const suppressed = now < suppressFastUntil(); + + if (!isEnhanced) { + scrollerInstance.scrollToIndex(scrollIndex, { + smooth: true, + align: 'center', + }); + return; + } + + const targetOffset = calculateEnhancedTargetOffset( + scrollerInstance, + scrollIndex, + targetIndex, + ); + const startOffset = scrollerInstance.scrollOffset; + const distance = Math.abs(targetOffset - startOffset); + const viewport = scrollerInstance.viewportSize; + const largeDistance = distance > Math.max(400, viewport * 0.6); + const fast = inFastWindow && !suppressed && largeDistance; + + performEnhancedScroll(scrollerInstance, scrollIndex, targetIndex, fast); + }; + + waitForReady(); + }); + + // handle scroll target updates based on current time + createEffect(() => { + const data = currentLyrics()?.data; + const currentTimeMs = currentTime(); + const idx = currentIndex(); + const lineEffect = config()?.lineEffect; + + if (!data || !data.lines) return; + + // robust fallback if no line is detected as "current" yet + let effIdx = idx; + if (effIdx < 0) { + const lines = data.lines; + const containing = lines.findIndex((l) => { + const start = l.timeInMs; + const end = l.timeInMs + l.duration; + return currentTimeMs >= start && currentTimeMs < end; + }); + if (containing !== -1) { + effIdx = containing; + } else { + let lastBefore = 0; + for (let j = lines.length - 1; j >= 0; j--) { + if (lines[j].timeInMs <= currentTimeMs) { + lastBefore = j; + break; + } + } + effIdx = lastBefore; + } + } + + const jumped = + prevTimeForScroll >= 0 && + Math.abs(currentTimeMs - prevTimeForScroll) > 400; + if ( + jumped && + prevTimeForScroll >= 0 && + performance.now() >= suppressFastUntil() + ) { + const timeDelta = Math.abs(currentTimeMs - prevTimeForScroll); + const lineDelta = + prevIndexForFast >= 0 ? Math.abs(effIdx - prevIndexForFast) : 0; + if (timeDelta > 1500 || lineDelta >= 5) { + requestFastScroll(1500); + } + } + prevTimeForScroll = currentTimeMs; + + const scrollOffset = scroller()?.scrollOffset ?? 0; + if (effIdx === 0 && currentTimeMs > 2000 && !jumped && scrollOffset <= 1) { + return; + } + + if (lineEffect === 'enhanced') { + const nextIdx = Math.min(effIdx + 1, data.lines.length - 1); + const nextLine = data.lines[nextIdx]; + + if (nextLine) { + // start scroll early + const timeUntilNextLine = nextLine.timeInMs - currentTimeMs; + + if (timeUntilNextLine <= LEAD_IN_TIME_MS) { + setScrollTargetIndex(nextIdx); + prevIndexForFast = effIdx; + return; + } + } + } + + prevIndexForFast = effIdx; + setScrollTargetIndex(effIdx); }); return ( @@ -302,6 +679,11 @@ export const LyricsRenderer = () => { diff --git a/src/plugins/synced-lyrics/renderer/scrolling.ts b/src/plugins/synced-lyrics/renderer/scrolling.ts new file mode 100644 index 0000000000..5768e5fd10 --- /dev/null +++ b/src/plugins/synced-lyrics/renderer/scrolling.ts @@ -0,0 +1,23 @@ +export const clamp = (n: number, min: number, max: number) => + Math.max(min, Math.min(max, n)); + +export const SCROLL_DURATION = { + FAST_BASE: 260, + FAST_MULT: 0.28, + FAST_MIN: 240, + FAST_MAX: 680, + NORMAL_BASE: 550, + NORMAL_MULT: 0.7, + NORMAL_MIN: 850, + NORMAL_MAX: 1650, + JUMP1_BASE: 700, + JUMP1_MULT: 0.8, + JUMP1_MIN: 1000, + JUMP1_MAX: 1800, + JUMP4_BASE: 400, + JUMP4_MULT: 0.6, + JUMP4_MIN: 600, + JUMP4_MAX: 1400, +} as const; + +export const LEAD_IN_TIME_MS = 130; diff --git a/src/plugins/synced-lyrics/renderer/utils.tsx b/src/plugins/synced-lyrics/renderer/utils.tsx index 1c6a410bd2..f170fcbbf5 100644 --- a/src/plugins/synced-lyrics/renderer/utils.tsx +++ b/src/plugins/synced-lyrics/renderer/utils.tsx @@ -91,6 +91,11 @@ export const simplifyUnicode = (text?: string) => .trim() : text; +export const isBlank = (text?: string) => { + const simplified = simplifyUnicode(text); + return simplified === undefined || simplified === ''; +}; + // Japanese Shinjitai const shinjitai = [ 20055, 20081, 20120, 20124, 20175, 26469, 20341, 20206, 20253, 20605, 20385, @@ -213,3 +218,60 @@ export const romanize = async (line: string) => { return line; }; + +// timeInMs to seek time in seconds (precise or rounded to nearest second for preciseTiming) +export const getSeekTime = (timeInMs: number, precise: boolean) => + precise ? timeInMs / 1000 : Math.round(timeInMs / 1000); + +// Format a time value in ms into mm:ss or mm:ss.xx depending on preciseTiming +export function formatTime(timeInMs: number, preciseTiming: boolean): string { + if (!preciseTiming) { + const totalSeconds = Math.round(timeInMs / 1000); + const minutes = Math.floor(totalSeconds / 60); + const seconds = totalSeconds % 60; + + return `${minutes.toString().padStart(2, '0')}:${seconds + .toString() + .padStart(2, '0')}`; + } + + const minutes = Math.floor(timeInMs / 60000); + const seconds = Math.floor((timeInMs % 60000) / 1000); + const ms = Math.floor((timeInMs % 1000) / 10); + + return `${minutes.toString().padStart(2, '0')}:${seconds + .toString() + .padStart(2, '0')}.${ms.toString().padStart(2, '0')}`; +} + +// Returns the time code prefix text to embed in yt-formatted-string runs +export const timeCodeText = ( + timeInMs: number, + preciseTiming: boolean, + show: boolean, +) => (show ? `[${formatTime(timeInMs, preciseTiming)}] ` : ''); + +// Normalizes plain-lyrics text into displayable lines, removing empty lines +// while preserving a single trailing empty line if the original text ends with one. +export const normalizePlainLyrics = (raw: string): string[] => { + const rawLines = raw.split('\n'); + const hasTrailingEmpty = + rawLines.length > 0 && isBlank(rawLines[rawLines.length - 1]); + + const lines = rawLines.filter((line, idx) => { + if (!isBlank(line)) return true; + // keep only the final empty line (for padding) if it exists + return hasTrailingEmpty && idx === rawLines.length - 1; + }); + + return lines; +}; + +export const SFont = () => { + if (document.getElementById('satoshi-font-link')) return; + const link = document.createElement('link'); + link.id = 'satoshi-font-link'; + link.rel = 'stylesheet'; + link.href = 'https://api.fontshare.com/v2/css?f[]=satoshi@1&display=swap'; + document.head.appendChild(link); +}; diff --git a/src/plugins/synced-lyrics/shared/lines.ts b/src/plugins/synced-lyrics/shared/lines.ts new file mode 100644 index 0000000000..55e9817677 --- /dev/null +++ b/src/plugins/synced-lyrics/shared/lines.ts @@ -0,0 +1,120 @@ +export interface SyncedLineCore { + text: string; + timeInMs: number; + duration: number; +} + +export function mergeConsecutiveEmptySyncedLines( + input: T[], +): T[] { + const merged: T[] = []; + for (const line of input) { + const isEmpty = !line.text || !line.text.trim(); + if (isEmpty && merged.length > 0) { + const prev = merged[merged.length - 1]; + const prevEmpty = !prev.text || !prev.text.trim(); + if (prevEmpty) { + // extend previous duration to cover this line + const prevEnd = prev.timeInMs + prev.duration; + const thisEnd = line.timeInMs + line.duration; + const newEnd = Math.max(prevEnd, thisEnd); + prev.duration = newEnd - prev.timeInMs; + continue; // skip adding this line + } + } + merged.push(line); + } + return merged; +} + +// adds a leading empty line if the first line starts after the threshold +// - 'span': spans the initial silence (duration = first.timeInMs) +// - 'zero': creates a zero-duration line at the start +export function ensureLeadingPaddingEmptyLine( + input: T[], + thresholdMs = 300, + mode: 'span' | 'zero' = 'span', +): T[] { + if (input.length === 0) return input; + const first = input[0]; + if (first.timeInMs <= thresholdMs) return input; + + const leading: T = Object.assign({}, first, { + timeInMs: 0, + duration: mode === 'span' ? first.timeInMs : 0, + text: '', + }); + + // update the time string if it exists in the object + if ((leading as unknown as { time?: unknown }).time !== undefined) { + (leading as unknown as { time: string }).time = toLrcTime(leading.timeInMs); + } + + return [leading, ...input]; +} + +// ensures a trailing empty line with two strategies: +// - 'lastEnd': adds a zero-duration line at the last end time +// - 'midpoint': adds a line at the midpoint between the last line and song end +export function ensureTrailingEmptyLine( + input: T[], + strategy: 'lastEnd' | 'midpoint', + songEndMs?: number, +): T[] { + if (input.length === 0) return input; + const out = input.slice(); + const last = out[out.length - 1]; + + const isLastEmpty = !last.text || !last.text.trim(); + if (isLastEmpty) return out; // already has an empty line at the end + + const lastEndCandidate = Number.isFinite(last.duration) + ? last.timeInMs + last.duration + : last.timeInMs; + + if (strategy === 'lastEnd') { + const trailing: T = Object.assign({}, last, { + timeInMs: lastEndCandidate, + duration: 0, + text: '', + }); + if ((trailing as unknown as { time?: unknown }).time !== undefined) { + (trailing as unknown as { time: string }).time = toLrcTime( + trailing.timeInMs, + ); + } + out.push(trailing); + return out; + } + + // handle the midpoint strategy + if (typeof songEndMs !== 'number') return out; + if (lastEndCandidate >= songEndMs) return out; + + const midpoint = Math.floor((lastEndCandidate + songEndMs) / 2); + + // adjust the last line to end at the calculated midpoint + last.duration = midpoint - last.timeInMs; + + const trailing: T = Object.assign({}, last, { + timeInMs: midpoint, + duration: songEndMs - midpoint, + text: '', + }); + if ((trailing as unknown as { time?: unknown }).time !== undefined) { + (trailing as unknown as { time: string }).time = toLrcTime( + trailing.timeInMs, + ); + } + out.push(trailing); + return out; +} + +function toLrcTime(ms: number): string { + const minutes = Math.floor(ms / 60000); + const seconds = Math.floor((ms % 60000) / 1000); + const centiseconds = Math.floor((ms % 1000) / 10); + return `${minutes.toString().padStart(2, '0')}:${seconds + .toString() + .padStart(2, '0')}.${centiseconds.toString().padStart(2, '0')}`; +} diff --git a/src/plugins/synced-lyrics/style.css b/src/plugins/synced-lyrics/style.css index 19154b4468..cee03cff88 100644 --- a/src/plugins/synced-lyrics/style.css +++ b/src/plugins/synced-lyrics/style.css @@ -27,7 +27,7 @@ --lyrics-padding: 0; /* Typography */ - --lyrics-font-family: Satoshi, Avenir, -apple-system, BlinkMacSystemFont, + --lyrics-font-family: "Satoshi", Avenir, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Open Sans, Helvetica Neue, sans-serif; --lyrics-font-size: clamp(1.4rem, 1.1vmax, 3rem); @@ -52,12 +52,22 @@ --lyrics-animations: lyrics-glow var(--lyrics-glow-duration) forwards, lyrics-wobble var(--lyrics-wobble-duration) forwards; --lyrics-scale-duration: 0.166s; + --lyrics-scale-hover-duration: 0.3s; --lyrics-opacity-transition: 0.33s; --lyrics-glow-duration: var(--lyrics-duration); --lyrics-wobble-duration: calc(var(--lyrics-duration) / 2); /* Colors */ --glow-color: rgba(255, 255, 255, 0.5); + + /* Other */ + --lyrics-hover-scale: 1; + --lyrics-hover-opacity: 0.33; + --lyrics-hover-empty-opacity: 1; + + --lyrics-empty-opacity: 1; + + --lyrics-will-change: auto; } .lyric-container { @@ -70,28 +80,77 @@ text-align: left !important; } -.synced-line { +.synced-line, +.synced-emptyline { width: var(--lyrics-width, 100%); & .text-lyrics { cursor: pointer; /*fix cuted lyrics-glow and romanized j at line start */ padding-left: 1.5rem; + transform-origin: center left; + transition: transform var(--lyrics-scale-hover-duration) ease-in-out, + opacity var(--lyrics-opacity-transition) ease; + /* will-change fixes jitter but may impact performance, remove if needed */ + will-change: var(--lyrics-will-change); + + & > span > span { + transition: transform var(--lyrics-scale-hover-duration) ease-in-out, + opacity var(--lyrics-opacity-transition) ease; + } + + & > .romaji { + color: var(--ytmusic-text-secondary) !important; + font-size: calc(var(--lyrics-font-size) * 0.7) !important; + font-style: italic !important; + } } - & .text-lyrics > .romaji { - color: var(--ytmusic-text-secondary) !important; - font-size: calc(var(--lyrics-font-size) * 0.7) !important; - font-style: italic !important; + &.final-empty .text-lyrics { + padding-top: 0 !important; + padding-bottom: 0 !important; + height: 1em; + display: block; + } + + &.no-padding .text-lyrics { + padding-top: 0 !important; + padding-bottom: 0 !important; + height: 0.3em; + overflow: hidden; } } +/* Current lines */ +.synced-line.current .text-lyrics > span > span, +.synced-emptyline.current .text-lyrics > span > span { + opacity: var(--lyrics-active-opacity); + animation: var(--lyrics-animations); +} + +/* Non current empty lines */ +.synced-emptyline:not(.current) .text-lyrics { + opacity: var(--lyrics-empty-opacity); +} + +/* Hover effects for non-current lines (enhanced only) */ +.enhanced-lyrics .synced-line:not(.current):hover .text-lyrics, +.enhanced-lyrics .synced-emptyline:not(.current):hover .text-lyrics { + opacity: 1 !important; + transform: scale(var(--lyrics-hover-scale)); +} + +.enhanced-lyrics .synced-line:not(.current):hover .text-lyrics > span > span, +.enhanced-lyrics .synced-emptyline:not(.current):hover .text-lyrics > span > span { + opacity: var(--lyrics-hover-opacity, var(--lyrics-hover-empty-opacity)) !important; +} + .synced-lyrics { display: block; - justify-content: left; + /* justify-content: left; */ text-align: left; margin: 0.5rem 20px 0.5rem 0; - transition: all 0.3s ease-in-out; + transition: all 0.3s ease; } .warning-lyrics { @@ -106,10 +165,8 @@ line-height: var(--lyrics-line-height) !important; padding-top: var(--lyrics-padding); padding-bottom: var(--lyrics-padding); - scale: var(--lyrics-inactive-scale); - translate: var(--lyrics-inactive-offset); - transition: scale var(--lyrics-scale-duration), translate 0.3s ease-in-out; - + transform: scale(var(--lyrics-inactive-scale)) translate(var(--lyrics-inactive-offset)); + transition: transform var(--lyrics-scale-duration) ease-in-out; display: block; text-align: left; margin: var(--global-margin) 0; @@ -117,9 +174,9 @@ &.lrc-header { color: var(--ytmusic-color-grey5) !important; - scale: 0.9; + transform: scale(0.9); height: fit-content; - padding: 0; + /* padding: 0; */ padding-block: 0.2em; } @@ -130,7 +187,8 @@ } } -.text-lyrics > span > span { +.text-lyrics > span > span, +.text-lyrics .placeholder { display: inline-block; white-space: pre-wrap; opacity: var(--lyrics-inactive-opacity); @@ -139,8 +197,7 @@ .current .text-lyrics { font-weight: var(--lyrics-active-font-weight) !important; - scale: var(--lyrics-active-scale); - translate: var(--lyrics-active-offset); + transform: scale(var(--lyrics-active-scale)) translate(var(--lyrics-active-offset)); } .current .text-lyrics > span > span { @@ -200,8 +257,8 @@ cursor: pointer; width: 5px; height: 5px; - margin: 0 4px 0; - border-radius: 200px; + margin: 0 4px; + border-radius: 50%; border: 1px solid #6e7c7c7f; } @@ -232,6 +289,24 @@ div:has(> .lyrics-picker) { } } +.fade { + opacity: 0; + transition: var(--lyrics-opacity-transition) ease; + + &.show { + opacity: var(--lyrics-active-opacity); + } + + &.dim { + opacity: var(--lyrics-inactive-opacity); + } + + &, + .placeholder { + animation-name: none; + } +} + /* Animations */ @keyframes lyrics-wobble { from { diff --git a/src/plugins/synced-lyrics/types.ts b/src/plugins/synced-lyrics/types.ts index dab1edf9ba..db09b0693c 100644 --- a/src/plugins/synced-lyrics/types.ts +++ b/src/plugins/synced-lyrics/types.ts @@ -9,6 +9,7 @@ export type SyncedLyricsPluginConfig = { defaultTextString: string | string[]; showLyricsEvenIfInexact: boolean; lineEffect: LineEffect; + showEmptyLineSymbols: boolean; romanization: boolean; }; @@ -23,7 +24,7 @@ export type LineLyrics = { status: LineLyricsStatus; }; -export type LineEffect = 'fancy' | 'scale' | 'offset' | 'focus'; +export type LineEffect = 'enhanced' | 'fancy' | 'scale' | 'offset' | 'focus'; export interface LyricResult { title: string; diff --git a/test-results/.last-run.json b/test-results/.last-run.json new file mode 100644 index 0000000000..5ede05e128 --- /dev/null +++ b/test-results/.last-run.json @@ -0,0 +1,6 @@ +{ + "status": "failed", + "failedTests": [ + "3c41ab34e2f6ae47f5cb-57c7e7dc7a123c839549" + ] +} \ No newline at end of file