Repository: web-scrobbler/web-scrobbler
Pull request #4005 (2023-08-02 16:39:55)

 Update SiriusXM track meta filtering
I've been working to really cull down the extra cruft in the SiriusXM connector submissions lately. I have used the connector a fair bit recently and it's significantly improved the quality of submitted scrobbles. #3977 was focused on filtering out info that shouldn't be sent as scrobbles to lastfm. This update is focused on removing extraneous info within the tracks being sent from the various channels the service offers. 

- Refactored the existing filter to handle removing the year (78), (Artist Name Cover) and EXCLUSIVE from submitted tracks
- Updated the connector comments

## SiriusXMU initial track
![image](https://github.com/web-scrobbler/web-scrobbler/assets/2962327/f046fe79-1aec-4894-9ff1-c042e21c38c0)

## Successful lastfm submission with Cover brackets and EXCLUSIVE removed
![image](https://github.com/web-scrobbler/web-scrobbler/assets/2962327/7c2be528-47a4-4cf7-b73b-a65c0f48f76b)




Patch:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 91a50382..5e1a17f6 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -12,13 +12,10 @@ jobs:
       - name: Checkout the repository
         uses: actions/checkout@v3
 
-      - name: Get Node.js version from .nvmrc
-        run: echo "NODE_VERSION=$(cat .nvmrc)" >> $GITHUB_ENV
-
       - name: Setup Node.js
         uses: actions/setup-node@v3
         with:
-          node-version: '${{ env.NODE_VERSION }}'
+          node-version-file: '.nvmrc'
 
       - name: Install dependencies
         run: npm ci
@@ -47,7 +44,7 @@ jobs:
       - name: Setup Node.js
         uses: actions/setup-node@v3
         with:
-          node-version: '${{ env.NODE_VERSION }}'
+          node-version-file: '.nvmrc'
 
       - name: Install dependencies
         run: npm ci
@@ -69,13 +66,10 @@ jobs:
       - name: Checkout the repository
         uses: actions/checkout@v3
 
-      - name: Get Node.js version from .nvmrc
-        run: echo "NODE_VERSION=$(cat .nvmrc)" >> $GITHUB_ENV
-
       - name: Setup Node.js
         uses: actions/setup-node@v3
         with:
-          node-version: '${{ env.NODE_VERSION }}'
+          node-version-file: '.nvmrc'
 
       - name: Install dependencies
         run: npm ci
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 58b96b1e..e54383b0 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -13,13 +13,10 @@ jobs:
       - name: Checkout the repository
         uses: actions/checkout@v3
 
-      - name: Get Node.js version from .nvmrc
-        run: echo "NODE_VERSION=$(cat .nvmrc)" >> $GITHUB_ENV
-
       - name: Setup Node.js
         uses: actions/setup-node@v3
         with:
-          node-version: '${{ env.NODE_VERSION }}'
+          node-version-file: '.nvmrc'
 
       - name: Install dependencies
         run: npm ci
@@ -43,13 +40,10 @@ jobs:
       - name: Checkout the repository
         uses: actions/checkout@v3
 
-      - name: Get Node.js version from .nvmrc
-        run: echo "NODE_VERSION=$(cat .nvmrc)" >> $GITHUB_ENV
-
       - name: Setup Node.js
         uses: actions/setup-node@v3
         with:
-          node-version: '${{ env.NODE_VERSION }}'
+          node-version-file: '.nvmrc'
 
       - name: Install dependencies
         run: npm ci
@@ -73,13 +67,10 @@ jobs:
       - name: Checkout the repository
         uses: actions/checkout@v3
 
-      - name: Get Node.js version from .nvmrc
-        run: echo "NODE_VERSION=$(cat .nvmrc)" >> $GITHUB_ENV
-
       - name: Setup Node.js
         uses: actions/setup-node@v3
         with:
-          node-version: '${{ env.NODE_VERSION }}'
+          node-version-file: '.nvmrc'
 
       - name: Install dependencies
         run: npm ci
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 2c48094f..4f86951c 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -12,13 +12,10 @@ jobs:
       - name: Checkout the repository
         uses: actions/checkout@v3
 
-      - name: Get Node.js version from .nvmrc
-        run: echo "NODE_VERSION=$(cat .nvmrc)" >> $GITHUB_ENV
-
       - name: Setup Node.js
         uses: actions/setup-node@v3
         with:
-          node-version: '${{ env.NODE_VERSION }}'
+          node-version-file: '.nvmrc'
 
       - name: Install dependencies
         run: npm ci
diff --git a/.github/workflows/release-extension.yml b/.github/workflows/release-extension.yml
index cb161beb..72cc4041 100644
--- a/.github/workflows/release-extension.yml
+++ b/.github/workflows/release-extension.yml
@@ -24,12 +24,10 @@ jobs:
         run: |
           git config user.name "web-scrobbler-bot"
           git config user.email "${{ secrets.BOT_EMAIL }}"
-      - name: Get Node.js version from .nvmrc
-        run: echo "NODE_VERSION=$(cat .nvmrc)" >> $GITHUB_ENV
       - name: Setup Node.js
         uses: actions/setup-node@v3
         with:
-          node-version: '${{ env.NODE_VERSION }}'
+          node-version-file: '.nvmrc'
       - name: Install dependencies
         run: npm ci
       - name: Release
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 30b63056..04cac65a 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -11,16 +11,13 @@ jobs:
       - name: Checkout the repository
         uses: actions/checkout@v3
 
-      - name: Get Node.js version from .nvmrc
-        run: echo "NODE_VERSION=$(cat .nvmrc)" >> $GITHUB_ENV
-
       - name: Get the extension version from ref
         run: echo "EXT_VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV
 
       - name: Setup Node.js
         uses: actions/setup-node@v3
         with:
-          node-version: '${{ env.NODE_VERSION }}'
+          node-version-file: '.nvmrc'
 
       - name: Install dependencies
         run: npm ci
diff --git a/.nvmrc b/.nvmrc
index 603606bc..4a1f488b 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1,1 +1,1 @@
-18.17.0
+18.17.1
diff --git a/package-lock.json b/package-lock.json
index 02122394..2d0393c5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,7 +13,7 @@
         "@web-scrobbler/metadata-filter": "3.0.1",
         "blueimp-md5": "2.19.0",
         "stylelint": "13.13.1",
-        "vite": "4.4.7",
+        "vite": "4.4.9",
         "webextension-polyfill": "0.10.0"
       },
       "devDependencies": {
@@ -22,17 +22,17 @@
         "@types/fs-extra": "11.0.1",
         "@types/imagemin": "8.0.1",
         "@types/imagemin-jpegtran": "5.0.1",
-        "@types/node": "20.4.5",
-        "@types/react": "18.2.17",
+        "@types/node": "20.5.1",
+        "@types/react": "18.2.20",
         "@types/webextension-polyfill": "0.10.1",
-        "@typescript-eslint/eslint-plugin": "5.60.1",
+        "@typescript-eslint/eslint-plugin": "6.3.0",
         "archiver": "5.3.1",
         "canvas": "2.11.2",
         "chokidar": "3.5.3",
-        "esbuild": "0.18.17",
-        "eslint": "8.46.0",
+        "esbuild": "0.19.2",
+        "eslint": "8.47.0",
         "eslint-config-web-scrobbler": "2.1.1",
-        "eslint-plugin-react": "7.33.0",
+        "eslint-plugin-react": "7.33.1",
         "eslint-plugin-solid": "0.12.1",
         "eslint-plugin-tsdoc": "0.2.17",
         "fs-extra": "11.1.1",
@@ -40,20 +40,20 @@
         "imagemin": "8.0.1",
         "imagemin-jpegtran": "7.0.0",
         "imagemin-pngquant": "9.0.2",
-        "prettier": "3.0.0",
+        "prettier": "3.0.1",
         "prettier-config-web-scrobbler": "0.1.0",
         "remark": "14.0.3",
         "remark-cli": "11.0.0",
         "remark-preset-lint-recommended": "6.1.3",
-        "sass": "1.64.1",
-        "solid-js": "1.7.8",
+        "sass": "1.66.1",
+        "solid-js": "1.7.11",
         "stylelint-config-web-scrobbler": "0.2.0",
         "tsx": "3.12.7",
         "typescript": "5.1.6",
         "vite-plugin-conditional-compiler": "0.1.1",
         "vite-plugin-solid": "2.7.0",
         "vite-plugin-static-copy": "0.17.0",
-        "vitest": "0.33.0"
+        "vitest": "0.34.2"
       },
       "engines": {
         "node": ">=18.0.0"
@@ -1073,12 +1073,13 @@
       }
     },
     "node_modules/@esbuild/android-arm": {
-      "version": "0.18.17",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.17.tgz",
-      "integrity": "sha512-wHsmJG/dnL3OkpAcwbgoBTTMHVi4Uyou3F5mf58ZtmUyIKfcdA7TROav/6tCzET4A3QW2Q2FC+eFneMU+iyOxg==",
+      "version": "0.19.2",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.2.tgz",
+      "integrity": "sha512-tM8yLeYVe7pRyAu9VMi/Q7aunpLwD139EY1S99xbQkT4/q2qa6eA4ige/WJQYdJ8GBL1K33pPFhPfPdJ/WzT8Q==",
       "cpu": [
         "arm"
       ],
+      "dev": true,
       "optional": true,
       "os": [
         "android"
@@ -1088,12 +1089,13 @@
       }
     },
     "node_modules/@esbuild/android-arm64": {
-      "version": "0.18.17",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.17.tgz",
-      "integrity": "sha512-9np+YYdNDed5+Jgr1TdWBsozZ85U1Oa3xW0c7TWqH0y2aGghXtZsuT8nYRbzOMcl0bXZXjOGbksoTtVOlWrRZg==",
+      "version": "0.19.2",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.2.tgz",
+      "integrity": "sha512-lsB65vAbe90I/Qe10OjkmrdxSX4UJDjosDgb8sZUKcg3oefEuW2OT2Vozz8ef7wrJbMcmhvCC+hciF8jY/uAkw==",
       "cpu": [
         "arm64"
       ],
+      "dev": true,
       "optional": true,
       "os": [
         "android"
@@ -1103,12 +1105,13 @@
       }
     },
     "node_modules/@esbuild/android-x64": {
-      "version": "0.18.17",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.17.tgz",
-      "integrity": "sha512-O+FeWB/+xya0aLg23hHEM2E3hbfwZzjqumKMSIqcHbNvDa+dza2D0yLuymRBQQnC34CWrsJUXyH2MG5VnLd6uw==",
+      "version": "0.19.2",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.2.tgz",
+      "integrity": "sha512-qK/TpmHt2M/Hg82WXHRc/W/2SGo/l1thtDHZWqFq7oi24AjZ4O/CpPSu6ZuYKFkEgmZlFoa7CooAyYmuvnaG8w==",
       "cpu": [
         "x64"
       ],
+      "dev": true,
       "optional": true,
       "os": [
         "android"
@@ -1118,12 +1121,13 @@
       }
     },
     "node_modules/@esbuild/darwin-arm64": {
-      "version": "0.18.17",
-      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.17.tgz",
-      "integrity": "sha512-M9uJ9VSB1oli2BE/dJs3zVr9kcCBBsE883prage1NWz6pBS++1oNn/7soPNS3+1DGj0FrkSvnED4Bmlu1VAE9g==",
+      "version": "0.19.2",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.2.tgz",
+      "integrity": "sha512-Ora8JokrvrzEPEpZO18ZYXkH4asCdc1DLdcVy8TGf5eWtPO1Ie4WroEJzwI52ZGtpODy3+m0a2yEX9l+KUn0tA==",
       "cpu": [
         "arm64"
       ],
+      "dev": true,
       "optional": true,
       "os": [
         "darwin"
@@ -1133,12 +1137,13 @@
       }
     },
     "node_modules/@esbuild/darwin-x64": {
-      "version": "0.18.17",
-      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.17.tgz",
-      "integrity": "sha512-XDre+J5YeIJDMfp3n0279DFNrGCXlxOuGsWIkRb1NThMZ0BsrWXoTg23Jer7fEXQ9Ye5QjrvXpxnhzl3bHtk0g==",
+      "version": "0.19.2",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.2.tgz",
+      "integrity": "sha512-tP+B5UuIbbFMj2hQaUr6EALlHOIOmlLM2FK7jeFBobPy2ERdohI4Ka6ZFjZ1ZYsrHE/hZimGuU90jusRE0pwDw==",
       "cpu": [
         "x64"
       ],
+      "dev": true,
       "optional": true,
       "os": [
         "darwin"
@@ -1148,12 +1153,13 @@
       }
     },
     "node_modules/@esbuild/freebsd-arm64": {
-      "version": "0.18.17",
-      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.17.tgz",
-      "integrity": "sha512-cjTzGa3QlNfERa0+ptykyxs5A6FEUQQF0MuilYXYBGdBxD3vxJcKnzDlhDCa1VAJCmAxed6mYhA2KaJIbtiNuQ==",
+      "version": "0.19.2",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.2.tgz",
+      "integrity": "sha512-YbPY2kc0acfzL1VPVK6EnAlig4f+l8xmq36OZkU0jzBVHcOTyQDhnKQaLzZudNJQyymd9OqQezeaBgkTGdTGeQ==",
       "cpu": [
         "arm64"
       ],
+      "dev": true,
       "optional": true,
       "os": [
         "freebsd"
@@ -1163,12 +1169,13 @@
       }
     },
     "node_modules/@esbuild/freebsd-x64": {
-      "version": "0.18.17",
-      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.17.tgz",
-      "integrity": "sha512-sOxEvR8d7V7Kw8QqzxWc7bFfnWnGdaFBut1dRUYtu+EIRXefBc/eIsiUiShnW0hM3FmQ5Zf27suDuHsKgZ5QrA==",
+      "version": "0.19.2",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.2.tgz",
+      "integrity": "sha512-nSO5uZT2clM6hosjWHAsS15hLrwCvIWx+b2e3lZ3MwbYSaXwvfO528OF+dLjas1g3bZonciivI8qKR/Hm7IWGw==",
       "cpu": [
         "x64"
       ],
+      "dev": true,
       "optional": true,
       "os": [
         "freebsd"
@@ -1178,12 +1185,13 @@
       }
     },
     "node_modules/@esbuild/linux-arm": {
-      "version": "0.18.17",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.17.tgz",
-      "integrity": "sha512-2d3Lw6wkwgSLC2fIvXKoMNGVaeY8qdN0IC3rfuVxJp89CRfA3e3VqWifGDfuakPmp90+ZirmTfye1n4ncjv2lg==",
+      "version": "0.19.2",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.2.tgz",
+      "integrity": "sha512-Odalh8hICg7SOD7XCj0YLpYCEc+6mkoq63UnExDCiRA2wXEmGlK5JVrW50vZR9Qz4qkvqnHcpH+OFEggO3PgTg==",
       "cpu": [
         "arm"
       ],
+      "dev": true,
       "optional": true,
       "os": [
         "linux"
@@ -1193,12 +1201,13 @@
       }
     },
     "node_modules/@esbuild/linux-arm64": {
-      "version": "0.18.17",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.17.tgz",
-      "integrity": "sha512-c9w3tE7qA3CYWjT+M3BMbwMt+0JYOp3vCMKgVBrCl1nwjAlOMYzEo+gG7QaZ9AtqZFj5MbUc885wuBBmu6aADQ==",
+      "version": "0.19.2",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.2.tgz",
+      "integrity": "sha512-ig2P7GeG//zWlU0AggA3pV1h5gdix0MA3wgB+NsnBXViwiGgY77fuN9Wr5uoCrs2YzaYfogXgsWZbm+HGr09xg==",
       "cpu": [
         "arm64"
       ],
+      "dev": true,
       "optional": true,
       "os": [
         "linux"
@@ -1208,12 +1217,13 @@
       }
     },
     "node_modules/@esbuild/linux-ia32": {
-      "version": "0.18.17",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.17.tgz",
-      "integrity": "sha512-1DS9F966pn5pPnqXYz16dQqWIB0dmDfAQZd6jSSpiT9eX1NzKh07J6VKR3AoXXXEk6CqZMojiVDSZi1SlmKVdg==",
+      "version": "0.19.2",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.2.tgz",
+      "integrity": "sha512-mLfp0ziRPOLSTek0Gd9T5B8AtzKAkoZE70fneiiyPlSnUKKI4lp+mGEnQXcQEHLJAcIYDPSyBvsUbKUG2ri/XQ==",
       "cpu": [
         "ia32"
       ],
+      "dev": true,
       "optional": true,
       "os": [
         "linux"
@@ -1223,12 +1233,13 @@
       }
     },
     "node_modules/@esbuild/linux-loong64": {
-      "version": "0.18.17",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.17.tgz",
-      "integrity": "sha512-EvLsxCk6ZF0fpCB6w6eOI2Fc8KW5N6sHlIovNe8uOFObL2O+Mr0bflPHyHwLT6rwMg9r77WOAWb2FqCQrVnwFg==",
+      "version": "0.19.2",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.2.tgz",
+      "integrity": "sha512-hn28+JNDTxxCpnYjdDYVMNTR3SKavyLlCHHkufHV91fkewpIyQchS1d8wSbmXhs1fiYDpNww8KTFlJ1dHsxeSw==",
       "cpu": [
         "loong64"
       ],
+      "dev": true,
       "optional": true,
       "os": [
         "linux"
@@ -1238,12 +1249,13 @@
       }
     },
     "node_modules/@esbuild/linux-mips64el": {
-      "version": "0.18.17",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.17.tgz",
-      "integrity": "sha512-e0bIdHA5p6l+lwqTE36NAW5hHtw2tNRmHlGBygZC14QObsA3bD4C6sXLJjvnDIjSKhW1/0S3eDy+QmX/uZWEYQ==",
+      "version": "0.19.2",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.2.tgz",
+      "integrity": "sha512-KbXaC0Sejt7vD2fEgPoIKb6nxkfYW9OmFUK9XQE4//PvGIxNIfPk1NmlHmMg6f25x57rpmEFrn1OotASYIAaTg==",
       "cpu": [
         "mips64el"
       ],
+      "dev": true,
       "optional": true,
       "os": [
         "linux"
@@ -1253,12 +1265,13 @@
       }
     },
     "node_modules/@esbuild/linux-ppc64": {
-      "version": "0.18.17",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.17.tgz",
-      "integrity": "sha512-BAAilJ0M5O2uMxHYGjFKn4nJKF6fNCdP1E0o5t5fvMYYzeIqy2JdAP88Az5LHt9qBoUa4tDaRpfWt21ep5/WqQ==",
+      "version": "0.19.2",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.2.tgz",
+      "integrity": "sha512-dJ0kE8KTqbiHtA3Fc/zn7lCd7pqVr4JcT0JqOnbj4LLzYnp+7h8Qi4yjfq42ZlHfhOCM42rBh0EwHYLL6LEzcw==",
       "cpu": [
         "ppc64"
       ],
+      "dev": true,
       "optional": true,
       "os": [
         "linux"
@@ -1268,12 +1281,13 @@
       }
     },
     "node_modules/@esbuild/linux-riscv64": {
-      "version": "0.18.17",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.17.tgz",
-      "integrity": "sha512-Wh/HW2MPnC3b8BqRSIme/9Zhab36PPH+3zam5pqGRH4pE+4xTrVLx2+XdGp6fVS3L2x+DrsIcsbMleex8fbE6g==",
+      "version": "0.19.2",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.2.tgz",
+      "integrity": "sha512-7Z/jKNFufZ/bbu4INqqCN6DDlrmOTmdw6D0gH+6Y7auok2r02Ur661qPuXidPOJ+FSgbEeQnnAGgsVynfLuOEw==",
       "cpu": [
         "riscv64"
       ],
+      "dev": true,
       "optional": true,
       "os": [
         "linux"
@@ -1283,12 +1297,13 @@
       }
     },
     "node_modules/@esbuild/linux-s390x": {
-      "version": "0.18.17",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.17.tgz",
-      "integrity": "sha512-j/34jAl3ul3PNcK3pfI0NSlBANduT2UO5kZ7FCaK33XFv3chDhICLY8wJJWIhiQ+YNdQ9dxqQctRg2bvrMlYgg==",
+      "version": "0.19.2",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.2.tgz",
+      "integrity": "sha512-U+RinR6aXXABFCcAY4gSlv4CL1oOVvSSCdseQmGO66H+XyuQGZIUdhG56SZaDJQcLmrSfRmx5XZOWyCJPRqS7g==",
       "cpu": [
         "s390x"
       ],
+      "dev": true,
       "optional": true,
       "os": [
         "linux"
@@ -1298,12 +1313,13 @@
       }
     },
     "node_modules/@esbuild/linux-x64": {
-      "version": "0.18.17",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.17.tgz",
-      "integrity": "sha512-QM50vJ/y+8I60qEmFxMoxIx4de03pGo2HwxdBeFd4nMh364X6TIBZ6VQ5UQmPbQWUVWHWws5MmJXlHAXvJEmpQ==",
+      "version": "0.19.2",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.2.tgz",
+      "integrity": "sha512-oxzHTEv6VPm3XXNaHPyUTTte+3wGv7qVQtqaZCrgstI16gCuhNOtBXLEBkBREP57YTd68P0VgDgG73jSD8bwXQ==",
       "cpu": [
         "x64"
       ],
+      "dev": true,
       "optional": true,
       "os": [
         "linux"
@@ -1313,12 +1329,13 @@
       }
     },
     "node_modules/@esbuild/netbsd-x64": {
-      "version": "0.18.17",
-      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.17.tgz",
-      "integrity": "sha512-/jGlhWR7Sj9JPZHzXyyMZ1RFMkNPjC6QIAan0sDOtIo2TYk3tZn5UDrkE0XgsTQCxWTTOcMPf9p6Rh2hXtl5TQ==",
+      "version": "0.19.2",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.2.tgz",
+      "integrity": "sha512-WNa5zZk1XpTTwMDompZmvQLHszDDDN7lYjEHCUmAGB83Bgs20EMs7ICD+oKeT6xt4phV4NDdSi/8OfjPbSbZfQ==",
       "cpu": [
         "x64"
       ],
+      "dev": true,
       "optional": true,
       "os": [
         "netbsd"
@@ -1328,12 +1345,13 @@
       }
     },
     "node_modules/@esbuild/openbsd-x64": {
-      "version": "0.18.17",
-      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.17.tgz",
-      "integrity": "sha512-rSEeYaGgyGGf4qZM2NonMhMOP/5EHp4u9ehFiBrg7stH6BYEEjlkVREuDEcQ0LfIl53OXLxNbfuIj7mr5m29TA==",
+      "version": "0.19.2",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.2.tgz",
+      "integrity": "sha512-S6kI1aT3S++Dedb7vxIuUOb3oAxqxk2Rh5rOXOTYnzN8JzW1VzBd+IqPiSpgitu45042SYD3HCoEyhLKQcDFDw==",
       "cpu": [
         "x64"
       ],
+      "dev": true,
       "optional": true,
       "os": [
         "openbsd"
@@ -1343,12 +1361,13 @@
       }
     },
     "node_modules/@esbuild/sunos-x64": {
-      "version": "0.18.17",
-      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.17.tgz",
-      "integrity": "sha512-Y7ZBbkLqlSgn4+zot4KUNYst0bFoO68tRgI6mY2FIM+b7ZbyNVtNbDP5y8qlu4/knZZ73fgJDlXID+ohY5zt5g==",
+      "version": "0.19.2",
+      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.2.tgz",
+      "integrity": "sha512-VXSSMsmb+Z8LbsQGcBMiM+fYObDNRm8p7tkUDMPG/g4fhFX5DEFmjxIEa3N8Zr96SjsJ1woAhF0DUnS3MF3ARw==",
       "cpu": [
         "x64"
       ],
+      "dev": true,
       "optional": true,
       "os": [
         "sunos"
@@ -1358,12 +1377,13 @@
       }
     },
     "node_modules/@esbuild/win32-arm64": {
-      "version": "0.18.17",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.17.tgz",
-      "integrity": "sha512-bwPmTJsEQcbZk26oYpc4c/8PvTY3J5/QK8jM19DVlEsAB41M39aWovWoHtNm78sd6ip6prilxeHosPADXtEJFw==",
+      "version": "0.19.2",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.2.tgz",
+      "integrity": "sha512-5NayUlSAyb5PQYFAU9x3bHdsqB88RC3aM9lKDAz4X1mo/EchMIT1Q+pSeBXNgkfNmRecLXA0O8xP+x8V+g/LKg==",
       "cpu": [
         "arm64"
       ],
+      "dev": true,
       "optional": true,
       "os": [
         "win32"
@@ -1373,12 +1393,13 @@
       }
     },
     "node_modules/@esbuild/win32-ia32": {
-      "version": "0.18.17",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.17.tgz",
-      "integrity": "sha512-H/XaPtPKli2MhW+3CQueo6Ni3Avggi6hP/YvgkEe1aSaxw+AeO8MFjq8DlgfTd9Iz4Yih3QCZI6YLMoyccnPRg==",
+      "version": "0.19.2",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.2.tgz",
+      "integrity": "sha512-47gL/ek1v36iN0wL9L4Q2MFdujR0poLZMJwhO2/N3gA89jgHp4MR8DKCmwYtGNksbfJb9JoTtbkoe6sDhg2QTA==",
       "cpu": [
         "ia32"
       ],
+      "dev": true,
       "optional": true,
       "os": [
         "win32"
@@ -1388,12 +1409,13 @@
       }
     },
     "node_modules/@esbuild/win32-x64": {
-      "version": "0.18.17",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.17.tgz",
-      "integrity": "sha512-fGEb8f2BSA3CW7riJVurug65ACLuQAzKq0SSqkY2b2yHHH0MzDfbLyKIGzHwOI/gkHcxM/leuSW6D5w/LMNitA==",
+      "version": "0.19.2",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.2.tgz",
+      "integrity": "sha512-tcuhV7ncXBqbt/Ybf0IyrMcwVOAPDckMK9rXNHtF17UTK18OKLpg08glminN06pt2WCoALhXdLfSPbVvK/6fxw==",
       "cpu": [
         "x64"
       ],
+      "dev": true,
       "optional": true,
       "os": [
         "win32"
@@ -1427,9 +1449,9 @@
       }
     },
     "node_modules/@eslint/eslintrc": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.1.tgz",
-      "integrity": "sha512-9t7ZA7NGGK8ckelF0PQCfcxIUzs1Md5rrO6U/c+FIQNanea5UZC0wqKXH4vHBccmu4ZJgZ2idtPeW7+Q2npOEA==",
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz",
+      "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==",
       "dev": true,
       "dependencies": {
         "ajv": "^6.12.4",
@@ -1450,9 +1472,9 @@
       }
     },
     "node_modules/@eslint/js": {
-      "version": "8.46.0",
-      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.46.0.tgz",
-      "integrity": "sha512-a8TLtmPi8xzPkCbp/OGFUo5yhRkHM2Ko9kOWP4znJr0WAhWyThaw3PnwX4vOTWOAMsV2uRt32PPDcEz63esSaA==",
+      "version": "8.47.0",
+      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.47.0.tgz",
+      "integrity": "sha512-P6omY1zv5MItm93kLM8s2vr1HICJH8v0dvddDhysbIuZ+vcjOHg5Zbkf1mTkcmi2JA9oBG2anOkRnW8WJTS8Og==",
       "dev": true,
       "engines": {
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -2291,9 +2313,9 @@
       "dev": true
     },
     "node_modules/@types/json-schema": {
-      "version": "7.0.11",
-      "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
-      "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
+      "version": "7.0.12",
+      "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz",
+      "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==",
       "dev": true
     },
     "node_modules/@types/jsonfile": {
@@ -2325,9 +2347,9 @@
       "dev": true
     },
     "node_modules/@types/node": {
-      "version": "20.4.5",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.5.tgz",
-      "integrity": "sha512-rt40Nk13II9JwQBdeYqmbn2Q6IVTA5uPhvSO+JVqdXw/6/4glI6oR9ezty/A9Hg5u7JH4OmYmuQ+XvjKm0Datg==",
+      "version": "20.5.1",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.1.tgz",
+      "integrity": "sha512-4tT2UrL5LBqDwoed9wZ6N3umC4Yhz3W3FloMmiiG4JwmUJWpie0c7lcnUNd4gtMKuDEO4wRVS8B6Xa0uMRsMKg==",
       "devOptional": true
     },
     "node_modules/@types/normalize-package-data": {
@@ -2347,9 +2369,9 @@
       "dev": true
     },
     "node_modules/@types/react": {
-      "version": "18.2.17",
-      "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.17.tgz",
-      "integrity": "sha512-u+e7OlgPPh+aryjOm5UJMX32OvB2E3QASOAqVMY6Ahs90djagxwv2ya0IctglNbNTexC12qCSMZG47KPfy1hAA==",
+      "version": "18.2.20",
+      "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.20.tgz",
+      "integrity": "sha512-WKNtmsLWJM/3D5mG4U84cysVY31ivmyw85dE84fOCk5Hx78wezB/XEjVPWl2JTZ5FkEeaTJf+VgUAUn3PE7Isw==",
       "dev": true,
       "dependencies": {
         "@types/prop-types": "*",
@@ -2373,9 +2395,9 @@
       "dev": true
     },
     "node_modules/@types/semver": {
-      "version": "7.3.13",
-      "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz",
-      "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==",
+      "version": "7.5.0",
+      "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz",
+      "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==",
       "dev": true
     },
     "node_modules/@types/supports-color": {
@@ -2402,32 +2424,34 @@
       "dev": true
     },
     "node_modules/@typescript-eslint/eslint-plugin": {
-      "version": "5.60.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.60.1.tgz",
-      "integrity": "sha512-KSWsVvsJsLJv3c4e73y/Bzt7OpqMCADUO846bHcuWYSYM19bldbAeDv7dYyV0jwkbMfJ2XdlzwjhXtuD7OY6bw==",
+      "version": "6.3.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.3.0.tgz",
+      "integrity": "sha512-IZYjYZ0ifGSLZbwMqIip/nOamFiWJ9AH+T/GYNZBWkVcyNQOFGtSMoWV7RvY4poYCMZ/4lHzNl796WOSNxmk8A==",
       "dev": true,
       "dependencies": {
-        "@eslint-community/regexpp": "^4.4.0",
-        "@typescript-eslint/scope-manager": "5.60.1",
-        "@typescript-eslint/type-utils": "5.60.1",
-        "@typescript-eslint/utils": "5.60.1",
+        "@eslint-community/regexpp": "^4.5.1",
+        "@typescript-eslint/scope-manager": "6.3.0",
+        "@typescript-eslint/type-utils": "6.3.0",
+        "@typescript-eslint/utils": "6.3.0",
+        "@typescript-eslint/visitor-keys": "6.3.0",
         "debug": "^4.3.4",
-        "grapheme-splitter": "^1.0.4",
-        "ignore": "^5.2.0",
+        "graphemer": "^1.4.0",
+        "ignore": "^5.2.4",
+        "natural-compare": "^1.4.0",
         "natural-compare-lite": "^1.4.0",
-        "semver": "^7.3.7",
-        "tsutils": "^3.21.0"
+        "semver": "^7.5.4",
+        "ts-api-utils": "^1.0.1"
       },
       "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+        "node": "^16.0.0 || >=18.0.0"
       },
       "funding": {
         "type": "opencollective",
         "url": "https://opencollective.com/typescript-eslint"
       },
       "peerDependencies": {
-        "@typescript-eslint/parser": "^5.0.0",
-        "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
+        "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha",
+        "eslint": "^7.0.0 || ^8.0.0"
       },
       "peerDependenciesMeta": {
         "typescript": {
@@ -2435,27 +2459,87 @@
         }
       }
     },
+    "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": {
+      "version": "6.3.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.3.0.tgz",
+      "integrity": "sha512-WlNFgBEuGu74ahrXzgefiz/QlVb+qg8KDTpknKwR7hMH+lQygWyx0CQFoUmMn1zDkQjTBBIn75IxtWss77iBIQ==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/types": "6.3.0",
+        "@typescript-eslint/visitor-keys": "6.3.0"
+      },
+      "engines": {
+        "node": "^16.0.0 || >=18.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
+    "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": {
+      "version": "6.3.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.3.0.tgz",
+      "integrity": "sha512-hLLg3BZE07XHnpzglNBG8P/IXq/ZVXraEbgY7FM0Cnc1ehM8RMdn9mat3LubJ3KBeYXXPxV1nugWbQPjGeJk6Q==",
+      "dev": true,
+      "dependencies": {
+        "@eslint-community/eslint-utils": "^4.4.0",
+        "@types/json-schema": "^7.0.12",
+        "@types/semver": "^7.5.0",
+        "@typescript-eslint/scope-manager": "6.3.0",
+        "@typescript-eslint/types": "6.3.0",
+        "@typescript-eslint/typescript-estree": "6.3.0",
+        "semver": "^7.5.4"
+      },
+      "engines": {
+        "node": "^16.0.0 || >=18.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "eslint": "^7.0.0 || ^8.0.0"
+      }
+    },
+    "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": {
+      "version": "6.3.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.3.0.tgz",
+      "integrity": "sha512-kEhRRj7HnvaSjux1J9+7dBen15CdWmDnwrpyiHsFX6Qx2iW5LOBUgNefOFeh2PjWPlNwN8TOn6+4eBU3J/gupw==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/types": "6.3.0",
+        "eslint-visitor-keys": "^3.4.1"
+      },
+      "engines": {
+        "node": "^16.0.0 || >=18.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
     "node_modules/@typescript-eslint/parser": {
-      "version": "5.57.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.57.1.tgz",
-      "integrity": "sha512-hlA0BLeVSA/wBPKdPGxoVr9Pp6GutGoY380FEhbVi0Ph4WNe8kLvqIRx76RSQt1lynZKfrXKs0/XeEk4zZycuA==",
+      "version": "6.3.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.3.0.tgz",
+      "integrity": "sha512-ibP+y2Gr6p0qsUkhs7InMdXrwldjxZw66wpcQq9/PzAroM45wdwyu81T+7RibNCh8oc0AgrsyCwJByncY0Ongg==",
       "dev": true,
       "peer": true,
       "dependencies": {
-        "@typescript-eslint/scope-manager": "5.57.1",
-        "@typescript-eslint/types": "5.57.1",
-        "@typescript-eslint/typescript-estree": "5.57.1",
+        "@typescript-eslint/scope-manager": "6.3.0",
+        "@typescript-eslint/types": "6.3.0",
+        "@typescript-eslint/typescript-estree": "6.3.0",
+        "@typescript-eslint/visitor-keys": "6.3.0",
         "debug": "^4.3.4"
       },
       "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+        "node": "^16.0.0 || >=18.0.0"
       },
       "funding": {
         "type": "opencollective",
         "url": "https://opencollective.com/typescript-eslint"
       },
       "peerDependencies": {
-        "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
+        "eslint": "^7.0.0 || ^8.0.0"
       },
       "peerDependenciesMeta": {
         "typescript": {
@@ -2464,17 +2548,17 @@
       }
     },
     "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": {
-      "version": "5.57.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.57.1.tgz",
-      "integrity": "sha512-N/RrBwEUKMIYxSKl0oDK5sFVHd6VI7p9K5MyUlVYAY6dyNb/wHUqndkTd3XhpGlXgnQsBkRZuu4f9kAHghvgPw==",
+      "version": "6.3.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.3.0.tgz",
+      "integrity": "sha512-WlNFgBEuGu74ahrXzgefiz/QlVb+qg8KDTpknKwR7hMH+lQygWyx0CQFoUmMn1zDkQjTBBIn75IxtWss77iBIQ==",
       "dev": true,
       "peer": true,
       "dependencies": {
-        "@typescript-eslint/types": "5.57.1",
-        "@typescript-eslint/visitor-keys": "5.57.1"
+        "@typescript-eslint/types": "6.3.0",
+        "@typescript-eslint/visitor-keys": "6.3.0"
       },
       "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+        "node": "^16.0.0 || >=18.0.0"
       },
       "funding": {
         "type": "opencollective",
@@ -2482,17 +2566,17 @@
       }
     },
     "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": {
-      "version": "5.57.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.57.1.tgz",
-      "integrity": "sha512-RjQrAniDU0CEk5r7iphkm731zKlFiUjvcBS2yHAg8WWqFMCaCrD0rKEVOMUyMMcbGPZ0bPp56srkGWrgfZqLRA==",
+      "version": "6.3.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.3.0.tgz",
+      "integrity": "sha512-kEhRRj7HnvaSjux1J9+7dBen15CdWmDnwrpyiHsFX6Qx2iW5LOBUgNefOFeh2PjWPlNwN8TOn6+4eBU3J/gupw==",
       "dev": true,
       "peer": true,
       "dependencies": {
-        "@typescript-eslint/types": "5.57.1",
-        "eslint-visitor-keys": "^3.3.0"
+        "@typescript-eslint/types": "6.3.0",
+        "eslint-visitor-keys": "^3.4.1"
       },
       "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+        "node": "^16.0.0 || >=18.0.0"
       },
       "funding": {
         "type": "opencollective",
@@ -2530,25 +2614,25 @@
       }
     },
     "node_modules/@typescript-eslint/type-utils": {
-      "version": "5.60.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.60.1.tgz",
-      "integrity": "sha512-vN6UztYqIu05nu7JqwQGzQKUJctzs3/Hg7E2Yx8rz9J+4LgtIDFWjjl1gm3pycH0P3mHAcEUBd23LVgfrsTR8A==",
+      "version": "6.3.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.3.0.tgz",
+      "integrity": "sha512-7Oj+1ox1T2Yc8PKpBvOKWhoI/4rWFd1j7FA/rPE0lbBPXTKjdbtC+7Ev0SeBjEKkIhKWVeZSP+mR7y1Db1CdfQ==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/typescript-estree": "5.60.1",
-        "@typescript-eslint/utils": "5.60.1",
+        "@typescript-eslint/typescript-estree": "6.3.0",
+        "@typescript-eslint/utils": "6.3.0",
         "debug": "^4.3.4",
-        "tsutils": "^3.21.0"
+        "ts-api-utils": "^1.0.1"
       },
       "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+        "node": "^16.0.0 || >=18.0.0"
       },
       "funding": {
         "type": "opencollective",
         "url": "https://opencollective.com/typescript-eslint"
       },
       "peerDependencies": {
-        "eslint": "*"
+        "eslint": "^7.0.0 || ^8.0.0"
       },
       "peerDependenciesMeta": {
         "typescript": {
@@ -2556,54 +2640,72 @@
         }
       }
     },
-    "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": {
-      "version": "5.60.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.60.1.tgz",
-      "integrity": "sha512-zDcDx5fccU8BA0IDZc71bAtYIcG9PowaOwaD8rjYbqwK7dpe/UMQl3inJ4UtUK42nOCT41jTSCwg76E62JpMcg==",
+    "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/scope-manager": {
+      "version": "6.3.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.3.0.tgz",
+      "integrity": "sha512-WlNFgBEuGu74ahrXzgefiz/QlVb+qg8KDTpknKwR7hMH+lQygWyx0CQFoUmMn1zDkQjTBBIn75IxtWss77iBIQ==",
       "dev": true,
+      "dependencies": {
+        "@typescript-eslint/types": "6.3.0",
+        "@typescript-eslint/visitor-keys": "6.3.0"
+      },
       "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+        "node": "^16.0.0 || >=18.0.0"
       },
       "funding": {
         "type": "opencollective",
         "url": "https://opencollective.com/typescript-eslint"
       }
     },
-    "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": {
-      "version": "5.60.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.60.1.tgz",
-      "integrity": "sha512-hkX70J9+2M2ZT6fhti5Q2FoU9zb+GeZK2SLP1WZlvUDqdMbEKhexZODD1WodNRyO8eS+4nScvT0dts8IdaBzfw==",
+    "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": {
+      "version": "6.3.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.3.0.tgz",
+      "integrity": "sha512-hLLg3BZE07XHnpzglNBG8P/IXq/ZVXraEbgY7FM0Cnc1ehM8RMdn9mat3LubJ3KBeYXXPxV1nugWbQPjGeJk6Q==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "5.60.1",
-        "@typescript-eslint/visitor-keys": "5.60.1",
-        "debug": "^4.3.4",
-        "globby": "^11.1.0",
-        "is-glob": "^4.0.3",
-        "semver": "^7.3.7",
-        "tsutils": "^3.21.0"
+        "@eslint-community/eslint-utils": "^4.4.0",
+        "@types/json-schema": "^7.0.12",
+        "@types/semver": "^7.5.0",
+        "@typescript-eslint/scope-manager": "6.3.0",
+        "@typescript-eslint/types": "6.3.0",
+        "@typescript-eslint/typescript-estree": "6.3.0",
+        "semver": "^7.5.4"
       },
       "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+        "node": "^16.0.0 || >=18.0.0"
       },
       "funding": {
         "type": "opencollective",
         "url": "https://opencollective.com/typescript-eslint"
       },
-      "peerDependenciesMeta": {
-        "typescript": {
-          "optional": true
-        }
+      "peerDependencies": {
+        "eslint": "^7.0.0 || ^8.0.0"
+      }
+    },
+    "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": {
+      "version": "6.3.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.3.0.tgz",
+      "integrity": "sha512-kEhRRj7HnvaSjux1J9+7dBen15CdWmDnwrpyiHsFX6Qx2iW5LOBUgNefOFeh2PjWPlNwN8TOn6+4eBU3J/gupw==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/types": "6.3.0",
+        "eslint-visitor-keys": "^3.4.1"
+      },
+      "engines": {
+        "node": "^16.0.0 || >=18.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
       }
     },
     "node_modules/@typescript-eslint/types": {
-      "version": "5.57.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.57.1.tgz",
-      "integrity": "sha512-bSs4LOgyV3bJ08F5RDqO2KXqg3WAdwHCu06zOqcQ6vqbTJizyBhuh1o1ImC69X4bV2g1OJxbH71PJqiO7Y1RuA==",
+      "version": "6.3.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.3.0.tgz",
+      "integrity": "sha512-K6TZOvfVyc7MO9j60MkRNWyFSf86IbOatTKGrpTQnzarDZPYPVy0oe3myTMq7VjhfsUAbNUW8I5s+2lZvtx1gg==",
       "dev": true,
-      "peer": true,
       "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+        "node": "^16.0.0 || >=18.0.0"
       },
       "funding": {
         "type": "opencollective",
@@ -2611,22 +2713,21 @@
       }
     },
     "node_modules/@typescript-eslint/typescript-estree": {
-      "version": "5.57.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.57.1.tgz",
-      "integrity": "sha512-A2MZqD8gNT0qHKbk2wRspg7cHbCDCk2tcqt6ScCFLr5Ru8cn+TCfM786DjPhqwseiS+PrYwcXht5ztpEQ6TFTw==",
+      "version": "6.3.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.3.0.tgz",
+      "integrity": "sha512-Xh4NVDaC4eYKY4O3QGPuQNp5NxBAlEvNQYOqJquR2MePNxO11E5K3t5x4M4Mx53IZvtpW+mBxIT0s274fLUocg==",
       "dev": true,
-      "peer": true,
       "dependencies": {
-        "@typescript-eslint/types": "5.57.1",
-        "@typescript-eslint/visitor-keys": "5.57.1",
+        "@typescript-eslint/types": "6.3.0",
+        "@typescript-eslint/visitor-keys": "6.3.0",
         "debug": "^4.3.4",
         "globby": "^11.1.0",
         "is-glob": "^4.0.3",
-        "semver": "^7.3.7",
-        "tsutils": "^3.21.0"
+        "semver": "^7.5.4",
+        "ts-api-utils": "^1.0.1"
       },
       "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+        "node": "^16.0.0 || >=18.0.0"
       },
       "funding": {
         "type": "opencollective",
@@ -2639,17 +2740,16 @@
       }
     },
     "node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/visitor-keys": {
-      "version": "5.57.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.57.1.tgz",
-      "integrity": "sha512-RjQrAniDU0CEk5r7iphkm731zKlFiUjvcBS2yHAg8WWqFMCaCrD0rKEVOMUyMMcbGPZ0bPp56srkGWrgfZqLRA==",
+      "version": "6.3.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.3.0.tgz",
+      "integrity": "sha512-kEhRRj7HnvaSjux1J9+7dBen15CdWmDnwrpyiHsFX6Qx2iW5LOBUgNefOFeh2PjWPlNwN8TOn6+4eBU3J/gupw==",
       "dev": true,
-      "peer": true,
       "dependencies": {
-        "@typescript-eslint/types": "5.57.1",
-        "eslint-visitor-keys": "^3.3.0"
+        "@typescript-eslint/types": "6.3.0",
+        "eslint-visitor-keys": "^3.4.1"
       },
       "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+        "node": "^16.0.0 || >=18.0.0"
       },
       "funding": {
         "type": "opencollective",
@@ -2753,13 +2853,13 @@
       }
     },
     "node_modules/@vitest/expect": {
-      "version": "0.33.0",
-      "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.33.0.tgz",
-      "integrity": "sha512-sVNf+Gla3mhTCxNJx+wJLDPp/WcstOe0Ksqz4Vec51MmgMth/ia0MGFEkIZmVGeTL5HtjYR4Wl/ZxBxBXZJTzQ==",
+      "version": "0.34.2",
+      "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.34.2.tgz",
+      "integrity": "sha512-EZm2dMNlLyIfDMha17QHSQcg2KjeAZaXd65fpPzXY5bvnfx10Lcaz3N55uEe8PhF+w4pw+hmrlHLLlRn9vkBJg==",
       "dev": true,
       "dependencies": {
-        "@vitest/spy": "0.33.0",
-        "@vitest/utils": "0.33.0",
+        "@vitest/spy": "0.34.2",
+        "@vitest/utils": "0.34.2",
         "chai": "^4.3.7"
       },
       "funding": {
@@ -2767,12 +2867,12 @@
       }
     },
     "node_modules/@vitest/runner": {
-      "version": "0.33.0",
-      "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.33.0.tgz",
-      "integrity": "sha512-UPfACnmCB6HKRHTlcgCoBh6ppl6fDn+J/xR8dTufWiKt/74Y9bHci5CKB8tESSV82zKYtkBJo9whU3mNvfaisg==",
+      "version": "0.34.2",
+      "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.34.2.tgz",
+      "integrity": "sha512-8ydGPACVX5tK3Dl0SUwxfdg02h+togDNeQX3iXVFYgzF5odxvaou7HnquALFZkyVuYskoaHUOqOyOLpOEj5XTA==",
       "dev": true,
       "dependencies": {
-        "@vitest/utils": "0.33.0",
+        "@vitest/utils": "0.34.2",
         "p-limit": "^4.0.0",
         "pathe": "^1.1.1"
       },
@@ -2808,9 +2908,9 @@
       }
     },
     "node_modules/@vitest/snapshot": {
-      "version": "0.33.0",
-      "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-0.33.0.tgz",
-      "integrity": "sha512-tJjrl//qAHbyHajpFvr8Wsk8DIOODEebTu7pgBrP07iOepR5jYkLFiqLq2Ltxv+r0uptUb4izv1J8XBOwKkVYA==",
+      "version": "0.34.2",
+      "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-0.34.2.tgz",
+      "integrity": "sha512-qhQ+xy3u4mwwLxltS4Pd4SR+XHv4EajiTPNY3jkIBLUApE6/ce72neJPSUQZ7bL3EBuKI+NhvzhGj3n5baRQUQ==",
       "dev": true,
       "dependencies": {
         "magic-string": "^0.30.1",
@@ -2822,9 +2922,9 @@
       }
     },
     "node_modules/@vitest/spy": {
-      "version": "0.33.0",
-      "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.33.0.tgz",
-      "integrity": "sha512-Kv+yZ4hnH1WdiAkPUQTpRxW8kGtH8VRTnus7ZTGovFYM1ZezJpvGtb9nPIjPnptHbsyIAxYZsEpVPYgtpjGnrg==",
+      "version": "0.34.2",
+      "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.34.2.tgz",
+      "integrity": "sha512-yd4L9OhfH6l0Av7iK3sPb3MykhtcRN5c5K5vm1nTbuN7gYn+yvUVVsyvzpHrjqS7EWqn9WsPJb7+0c3iuY60tA==",
       "dev": true,
       "dependencies": {
         "tinyspy": "^2.1.1"
@@ -2834,9 +2934,9 @@
       }
     },
     "node_modules/@vitest/utils": {
-      "version": "0.33.0",
-      "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.33.0.tgz",
-      "integrity": "sha512-pF1w22ic965sv+EN6uoePkAOTkAPWM03Ri/jXNyMIKBb/XHLDPfhLvf/Fa9g0YECevAIz56oVYXhodLvLQ/awA==",
+      "version": "0.34.2",
+      "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.34.2.tgz",
+      "integrity": "sha512-Lzw+kAsTPubhoQDp1uVAOP6DhNia1GMDsI9jgB0yMn+/nDaPieYQ88lKqz/gGjSHL4zwOItvpehec9OY+rS73w==",
       "dev": true,
       "dependencies": {
         "diff-sequences": "^29.4.3",
@@ -4874,9 +4974,10 @@
       }
     },
     "node_modules/esbuild": {
-      "version": "0.18.17",
-      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.17.tgz",
-      "integrity": "sha512-1GJtYnUxsJreHYA0Y+iQz2UEykonY66HNWOb0yXYZi9/kNrORUEHVg87eQsCtqh59PEJ5YVZJO98JHznMJSWjg==",
+      "version": "0.19.2",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.2.tgz",
+      "integrity": "sha512-G6hPax8UbFakEj3hWO0Vs52LQ8k3lnBhxZWomUJDxfz3rZTLqF5k/FCzuNdLx2RbpBiQQF9H9onlDDH1lZsnjg==",
+      "dev": true,
       "hasInstallScript": true,
       "bin": {
         "esbuild": "bin/esbuild"
@@ -4885,28 +4986,28 @@
         "node": ">=12"
       },
       "optionalDependencies": {
-        "@esbuild/android-arm": "0.18.17",
-        "@esbuild/android-arm64": "0.18.17",
-        "@esbuild/android-x64": "0.18.17",
-        "@esbuild/darwin-arm64": "0.18.17",
-        "@esbuild/darwin-x64": "0.18.17",
-        "@esbuild/freebsd-arm64": "0.18.17",
-        "@esbuild/freebsd-x64": "0.18.17",
-        "@esbuild/linux-arm": "0.18.17",
-        "@esbuild/linux-arm64": "0.18.17",
-        "@esbuild/linux-ia32": "0.18.17",
-        "@esbuild/linux-loong64": "0.18.17",
-        "@esbuild/linux-mips64el": "0.18.17",
-        "@esbuild/linux-ppc64": "0.18.17",
-        "@esbuild/linux-riscv64": "0.18.17",
-        "@esbuild/linux-s390x": "0.18.17",
-        "@esbuild/linux-x64": "0.18.17",
-        "@esbuild/netbsd-x64": "0.18.17",
-        "@esbuild/openbsd-x64": "0.18.17",
-        "@esbuild/sunos-x64": "0.18.17",
-        "@esbuild/win32-arm64": "0.18.17",
-        "@esbuild/win32-ia32": "0.18.17",
-        "@esbuild/win32-x64": "0.18.17"
+        "@esbuild/android-arm": "0.19.2",
+        "@esbuild/android-arm64": "0.19.2",
+        "@esbuild/android-x64": "0.19.2",
+        "@esbuild/darwin-arm64": "0.19.2",
+        "@esbuild/darwin-x64": "0.19.2",
+        "@esbuild/freebsd-arm64": "0.19.2",
+        "@esbuild/freebsd-x64": "0.19.2",
+        "@esbuild/linux-arm": "0.19.2",
+        "@esbuild/linux-arm64": "0.19.2",
+        "@esbuild/linux-ia32": "0.19.2",
+        "@esbuild/linux-loong64": "0.19.2",
+        "@esbuild/linux-mips64el": "0.19.2",
+        "@esbuild/linux-ppc64": "0.19.2",
+        "@esbuild/linux-riscv64": "0.19.2",
+        "@esbuild/linux-s390x": "0.19.2",
+        "@esbuild/linux-x64": "0.19.2",
+        "@esbuild/netbsd-x64": "0.19.2",
+        "@esbuild/openbsd-x64": "0.19.2",
+        "@esbuild/sunos-x64": "0.19.2",
+        "@esbuild/win32-arm64": "0.19.2",
+        "@esbuild/win32-ia32": "0.19.2",
+        "@esbuild/win32-x64": "0.19.2"
       }
     },
     "node_modules/escalade": {
@@ -4930,15 +5031,15 @@
       }
     },
     "node_modules/eslint": {
-      "version": "8.46.0",
-      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.46.0.tgz",
-      "integrity": "sha512-cIO74PvbW0qU8e0mIvk5IV3ToWdCq5FYG6gWPHHkx6gNdjlbAYvtfHmlCMXxjcoVaIdwy/IAt3+mDkZkfvb2Dg==",
+      "version": "8.47.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.47.0.tgz",
+      "integrity": "sha512-spUQWrdPt+pRVP1TTJLmfRNJJHHZryFmptzcafwSvHsceV81djHOdnEeDmkdotZyLNjDhrOasNK8nikkoG1O8Q==",
       "dev": true,
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.2.0",
         "@eslint-community/regexpp": "^4.6.1",
-        "@eslint/eslintrc": "^2.1.1",
-        "@eslint/js": "^8.46.0",
+        "@eslint/eslintrc": "^2.1.2",
+        "@eslint/js": "^8.47.0",
         "@humanwhocodes/config-array": "^0.11.10",
         "@humanwhocodes/module-importer": "^1.0.1",
         "@nodelib/fs.walk": "^1.2.8",
@@ -4949,7 +5050,7 @@
         "doctrine": "^3.0.0",
         "escape-string-regexp": "^4.0.0",
         "eslint-scope": "^7.2.2",
-        "eslint-visitor-keys": "^3.4.2",
+        "eslint-visitor-keys": "^3.4.3",
         "espree": "^9.6.1",
         "esquery": "^1.4.2",
         "esutils": "^2.0.2",
@@ -5023,9 +5124,9 @@
       }
     },
     "node_modules/eslint-plugin-react": {
-      "version": "7.33.0",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.0.tgz",
-      "integrity": "sha512-qewL/8P34WkY8jAqdQxsiL82pDUeT7nhs8IsuXgfgnsEloKCT4miAV9N9kGtx7/KM9NH/NCGUE7Edt9iGxLXFw==",
+      "version": "7.33.1",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.1.tgz",
+      "integrity": "sha512-L093k0WAMvr6VhNwReB8VgOq5s2LesZmrpPdKz/kZElQDzqS7G7+DnKoqT+w4JwuiGeAhAvHO0fvy0Eyk4ejDA==",
       "dev": true,
       "dependencies": {
         "array-includes": "^3.1.6",
@@ -5147,9 +5248,9 @@
       }
     },
     "node_modules/eslint-visitor-keys": {
-      "version": "3.4.2",
-      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.2.tgz",
-      "integrity": "sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw==",
+      "version": "3.4.3",
+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+      "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
       "dev": true,
       "engines": {
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -6020,9 +6121,9 @@
       }
     },
     "node_modules/globals": {
-      "version": "13.20.0",
-      "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz",
-      "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==",
+      "version": "13.21.0",
+      "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz",
+      "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==",
       "dev": true,
       "dependencies": {
         "type-fest": "^0.20.2"
@@ -6130,12 +6231,6 @@
       "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
       "dev": true
     },
-    "node_modules/grapheme-splitter": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz",
-      "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
-      "dev": true
-    },
     "node_modules/graphemer": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
@@ -9398,9 +9493,9 @@
       }
     },
     "node_modules/prettier": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0.tgz",
-      "integrity": "sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==",
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.1.tgz",
+      "integrity": "sha512-fcOWSnnpCrovBsmFZIGIy9UqK2FaI7Hqax+DIO0A9UxeVoY4iweyaFjS5TavZN97Hfehph0nhsZnjlVKzEQSrQ==",
       "dev": true,
       "bin": {
         "prettier": "bin/prettier.cjs"
@@ -9425,9 +9520,9 @@
       }
     },
     "node_modules/pretty-format": {
-      "version": "29.6.1",
-      "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz",
-      "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==",
+      "version": "29.6.2",
+      "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.2.tgz",
+      "integrity": "sha512-1q0oC8eRveTg5nnBEWMXAU2qpv65Gnuf2eCQzSjxpWFkPaPARwqZZDGuNE0zPAZfTCHzIk3A8dIjwlQKKLphyg==",
       "dev": true,
       "dependencies": {
         "@jest/schemas": "^29.6.0",
@@ -10248,9 +10343,9 @@
       }
     },
     "node_modules/rollup": {
-      "version": "3.26.2",
-      "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.26.2.tgz",
-      "integrity": "sha512-6umBIGVz93er97pMgQO08LuH3m6PUb3jlDUUGFsNJB6VgTCUaDFpupf5JfU30529m/UKOgmiX+uY6Sx8cOYpLA==",
+      "version": "3.28.0",
+      "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.0.tgz",
+      "integrity": "sha512-d7zhvo1OUY2SXSM6pfNjgD5+d0Nz87CUp4mt8l/GgVP3oBsPwzNvSzyu1me6BSG9JIgWNTVcafIXBIyM8yQ3yw==",
       "bin": {
         "rollup": "dist/bin/rollup"
       },
@@ -10330,9 +10425,9 @@
       }
     },
     "node_modules/sass": {
-      "version": "1.64.1",
-      "resolved": "https://registry.npmjs.org/sass/-/sass-1.64.1.tgz",
-      "integrity": "sha512-16rRACSOFEE8VN7SCgBu1MpYCyN7urj9At898tyzdXFhC+a+yOX5dXwAR7L8/IdPJ1NB8OYoXmD55DM30B2kEQ==",
+      "version": "1.66.1",
+      "resolved": "https://registry.npmjs.org/sass/-/sass-1.66.1.tgz",
+      "integrity": "sha512-50c+zTsZOJVgFfTgwwEzkjA3/QACgdNsKueWPyAR0mRINIvLAStVQBbPg14iuqEQ74NPDbXzJARJ/O4SI1zftA==",
       "devOptional": true,
       "dependencies": {
         "chokidar": ">=3.0.0 <4.0.0",
@@ -10543,9 +10638,9 @@
       }
     },
     "node_modules/solid-js": {
-      "version": "1.7.8",
-      "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.7.8.tgz",
-      "integrity": "sha512-XHBWk1FvFd0JMKljko7FfhefJMTSgYEuVKcQ2a8hzRXfiuSJAGsrPPafqEo+f6l+e8Oe3cROSpIL6kbzjC1fjQ==",
+      "version": "1.7.11",
+      "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.7.11.tgz",
+      "integrity": "sha512-JkuvsHt8jqy7USsy9xJtT18aF9r2pFO+GB8JQ2XGTvtF49rGTObB46iebD25sE3qVNvIbwglXOXdALnJq9IHtQ==",
       "dependencies": {
         "csstype": "^3.1.0",
         "seroval": "^0.5.0"
@@ -11551,9 +11646,9 @@
       "dev": true
     },
     "node_modules/tinypool": {
-      "version": "0.6.0",
-      "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.6.0.tgz",
-      "integrity": "sha512-FdswUUo5SxRizcBc6b1GSuLpLjisa8N8qMyYoP3rl+bym+QauhtJP5bvZY1ytt8krKGmMLYIRl36HBZfeAoqhQ==",
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.7.0.tgz",
+      "integrity": "sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==",
       "dev": true,
       "engines": {
         "node": ">=14.0.0"
@@ -11670,6 +11765,18 @@
         "url": "https://github.com/sponsors/wooorm"
       }
     },
+    "node_modules/ts-api-utils": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.1.tgz",
+      "integrity": "sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==",
+      "dev": true,
+      "engines": {
+        "node": ">=16.13.0"
+      },
+      "peerDependencies": {
+        "typescript": ">=4.2.0"
+      }
+    },
     "node_modules/tslib": {
       "version": "1.14.1",
       "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
@@ -11795,9 +11902,9 @@
       }
     },
     "node_modules/ufo": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.1.2.tgz",
-      "integrity": "sha512-TrY6DsjTQQgyS3E3dBaOXf0TpPD8u9FVrVYmKVegJuFw51n/YB9XPt+U6ydzFG5ZIN7+DIjPbNmXoBj9esYhgQ==",
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.2.0.tgz",
+      "integrity": "sha512-RsPyTbqORDNDxqAdQPQBpgqhWle1VcTSou/FraClYlHf6TZnQcGslpLcAphNR+sQW4q5lLWLbOsRlh9j24baQg==",
       "dev": true
     },
     "node_modules/unbox-primitive": {
@@ -12486,13 +12593,13 @@
       }
     },
     "node_modules/vite": {
-      "version": "4.4.7",
-      "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.7.tgz",
-      "integrity": "sha512-6pYf9QJ1mHylfVh39HpuSfMPojPSKVxZvnclX1K1FyZ1PXDOcLBibdq5t1qxJSnL63ca8Wf4zts6mD8u8oc9Fw==",
+      "version": "4.4.9",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz",
+      "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==",
       "dependencies": {
         "esbuild": "^0.18.10",
-        "postcss": "^8.4.26",
-        "rollup": "^3.25.2"
+        "postcss": "^8.4.27",
+        "rollup": "^3.27.1"
       },
       "bin": {
         "vite": "bin/vite.js"
@@ -12540,9 +12647,9 @@
       }
     },
     "node_modules/vite-node": {
-      "version": "0.33.0",
-      "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.33.0.tgz",
-      "integrity": "sha512-19FpHYbwWWxDr73ruNahC+vtEdza52kA90Qb3La98yZ0xULqV8A5JLNPUff0f5zID4984tW7l3DH2przTJUZSw==",
+      "version": "0.34.2",
+      "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.34.2.tgz",
+      "integrity": "sha512-JtW249Zm3FB+F7pQfH56uWSdlltCo1IOkZW5oHBzeQo0iX4jtC7o1t9aILMGd9kVekXBP2lfJBEQt9rBh07ebA==",
       "dev": true,
       "dependencies": {
         "cac": "^6.7.14",
@@ -12620,6 +12727,372 @@
       "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
       "dev": true
     },
+    "node_modules/vite/node_modules/@esbuild/android-arm": {
+      "version": "0.18.20",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
+      "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
+      "cpu": [
+        "arm"
+      ],
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/android-arm64": {
+      "version": "0.18.20",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
+      "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/android-x64": {
+      "version": "0.18.20",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
+      "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/darwin-arm64": {
+      "version": "0.18.20",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
+      "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/darwin-x64": {
+      "version": "0.18.20",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
+      "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/freebsd-arm64": {
+      "version": "0.18.20",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
+      "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/freebsd-x64": {
+      "version": "0.18.20",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
+      "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/linux-arm": {
+      "version": "0.18.20",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
+      "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
+      "cpu": [
+        "arm"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/linux-arm64": {
+      "version": "0.18.20",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
+      "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/linux-ia32": {
+      "version": "0.18.20",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
+      "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
+      "cpu": [
+        "ia32"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/linux-loong64": {
+      "version": "0.18.20",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
+      "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
+      "cpu": [
+        "loong64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/linux-mips64el": {
+      "version": "0.18.20",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
+      "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
+      "cpu": [
+        "mips64el"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/linux-ppc64": {
+      "version": "0.18.20",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
+      "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
+      "cpu": [
+        "ppc64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/linux-riscv64": {
+      "version": "0.18.20",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
+      "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
+      "cpu": [
+        "riscv64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/linux-s390x": {
+      "version": "0.18.20",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
+      "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
+      "cpu": [
+        "s390x"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/linux-x64": {
+      "version": "0.18.20",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
+      "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/netbsd-x64": {
+      "version": "0.18.20",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
+      "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/openbsd-x64": {
+      "version": "0.18.20",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
+      "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/sunos-x64": {
+      "version": "0.18.20",
+      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
+      "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "sunos"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/win32-arm64": {
+      "version": "0.18.20",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
+      "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/win32-ia32": {
+      "version": "0.18.20",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
+      "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
+      "cpu": [
+        "ia32"
+      ],
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/win32-x64": {
+      "version": "0.18.20",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
+      "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/esbuild": {
+      "version": "0.18.20",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
+      "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
+      "hasInstallScript": true,
+      "bin": {
+        "esbuild": "bin/esbuild"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "optionalDependencies": {
+        "@esbuild/android-arm": "0.18.20",
+        "@esbuild/android-arm64": "0.18.20",
+        "@esbuild/android-x64": "0.18.20",
+        "@esbuild/darwin-arm64": "0.18.20",
+        "@esbuild/darwin-x64": "0.18.20",
+        "@esbuild/freebsd-arm64": "0.18.20",
+        "@esbuild/freebsd-x64": "0.18.20",
+        "@esbuild/linux-arm": "0.18.20",
+        "@esbuild/linux-arm64": "0.18.20",
+        "@esbuild/linux-ia32": "0.18.20",
+        "@esbuild/linux-loong64": "0.18.20",
+        "@esbuild/linux-mips64el": "0.18.20",
+        "@esbuild/linux-ppc64": "0.18.20",
+        "@esbuild/linux-riscv64": "0.18.20",
+        "@esbuild/linux-s390x": "0.18.20",
+        "@esbuild/linux-x64": "0.18.20",
+        "@esbuild/netbsd-x64": "0.18.20",
+        "@esbuild/openbsd-x64": "0.18.20",
+        "@esbuild/sunos-x64": "0.18.20",
+        "@esbuild/win32-arm64": "0.18.20",
+        "@esbuild/win32-ia32": "0.18.20",
+        "@esbuild/win32-x64": "0.18.20"
+      }
+    },
     "node_modules/vite/node_modules/picocolors": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@@ -12667,19 +13140,19 @@
       }
     },
     "node_modules/vitest": {
-      "version": "0.33.0",
-      "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.33.0.tgz",
-      "integrity": "sha512-1CxaugJ50xskkQ0e969R/hW47za4YXDUfWJDxip1hwbnhUjYolpfUn2AMOulqG/Dtd9WYAtkHmM/m3yKVrEejQ==",
+      "version": "0.34.2",
+      "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.34.2.tgz",
+      "integrity": "sha512-WgaIvBbjsSYMq/oiMlXUI7KflELmzM43BEvkdC/8b5CAod4ryAiY2z8uR6Crbi5Pjnu5oOmhKa9sy7uk6paBxQ==",
       "dev": true,
       "dependencies": {
         "@types/chai": "^4.3.5",
         "@types/chai-subset": "^1.3.3",
         "@types/node": "*",
-        "@vitest/expect": "0.33.0",
-        "@vitest/runner": "0.33.0",
-        "@vitest/snapshot": "0.33.0",
-        "@vitest/spy": "0.33.0",
-        "@vitest/utils": "0.33.0",
+        "@vitest/expect": "0.34.2",
+        "@vitest/runner": "0.34.2",
+        "@vitest/snapshot": "0.34.2",
+        "@vitest/spy": "0.34.2",
+        "@vitest/utils": "0.34.2",
         "acorn": "^8.9.0",
         "acorn-walk": "^8.2.0",
         "cac": "^6.7.14",
@@ -12692,9 +13165,9 @@
         "std-env": "^3.3.3",
         "strip-literal": "^1.0.1",
         "tinybench": "^2.5.0",
-        "tinypool": "^0.6.0",
+        "tinypool": "^0.7.0",
         "vite": "^3.0.0 || ^4.0.0",
-        "vite-node": "0.33.0",
+        "vite-node": "0.34.2",
         "why-is-node-running": "^2.2.2"
       },
       "bin": {
diff --git a/package.json b/package.json
index 7bc06512..8c3bea38 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,7 @@
     "@web-scrobbler/metadata-filter": "3.0.1",
     "blueimp-md5": "2.19.0",
     "stylelint": "13.13.1",
-    "vite": "4.4.7",
+    "vite": "4.4.9",
     "webextension-polyfill": "0.10.0"
   },
   "devDependencies": {
@@ -39,17 +39,17 @@
     "@types/fs-extra": "11.0.1",
     "@types/imagemin": "8.0.1",
     "@types/imagemin-jpegtran": "5.0.1",
-    "@types/node": "20.4.5",
-    "@types/react": "18.2.17",
+    "@types/node": "20.5.1",
+    "@types/react": "18.2.20",
     "@types/webextension-polyfill": "0.10.1",
-    "@typescript-eslint/eslint-plugin": "5.60.1",
+    "@typescript-eslint/eslint-plugin": "6.3.0",
     "archiver": "5.3.1",
     "canvas": "2.11.2",
     "chokidar": "3.5.3",
-    "esbuild": "0.18.17",
-    "eslint": "8.46.0",
+    "esbuild": "0.19.2",
+    "eslint": "8.47.0",
     "eslint-config-web-scrobbler": "2.1.1",
-    "eslint-plugin-react": "7.33.0",
+    "eslint-plugin-react": "7.33.1",
     "eslint-plugin-solid": "0.12.1",
     "eslint-plugin-tsdoc": "0.2.17",
     "fs-extra": "11.1.1",
@@ -57,20 +57,20 @@
     "imagemin": "8.0.1",
     "imagemin-jpegtran": "7.0.0",
     "imagemin-pngquant": "9.0.2",
-    "prettier": "3.0.0",
+    "prettier": "3.0.1",
     "prettier-config-web-scrobbler": "0.1.0",
     "remark": "14.0.3",
     "remark-cli": "11.0.0",
     "remark-preset-lint-recommended": "6.1.3",
-    "sass": "1.64.1",
-    "solid-js": "1.7.8",
+    "sass": "1.66.1",
+    "solid-js": "1.7.11",
     "stylelint-config-web-scrobbler": "0.2.0",
     "tsx": "3.12.7",
     "typescript": "5.1.6",
     "vite-plugin-conditional-compiler": "0.1.1",
     "vite-plugin-solid": "2.7.0",
     "vite-plugin-static-copy": "0.17.0",
-    "vitest": "0.33.0"
+    "vitest": "0.34.2"
   },
   "engines": {
     "node": ">=18.0.0"
diff --git a/scripts/log.ts b/scripts/log.ts
index 465d6014..672086ec 100644
--- a/scripts/log.ts
+++ b/scripts/log.ts
@@ -1,6 +1,6 @@
 type ColorType = 'success' | 'info' | 'error' | 'warning' | keyof typeof COLORS;
 
-export default function colorLog(message: any, type?: ColorType): void {
+export default function colorLog(message: string, type?: ColorType): void {
 	let color: string = type || COLORS.FgBlack;
 
 	switch (type) {
diff --git a/src/connectors/deezer-dom-inject.ts b/src/connectors/deezer-dom-inject.ts
index 734bc197..071d3cbc 100644
--- a/src/connectors/deezer-dom-inject.ts
+++ b/src/connectors/deezer-dom-inject.ts
@@ -1,3 +1,5 @@
+import { State } from '@/core/types';
+
 deezerInitConnector();
 
 function deezerInitConnector() {
@@ -23,7 +25,16 @@ function deezerAddEventListeners() {
 	if (!('Events' in window)) {
 		return;
 	}
-	const ev = window.Events as any;
+	const ev = window.Events as {
+		player: {
+			play: unknown;
+			playing: unknown;
+			paused: unknown;
+			resume: unknown;
+			finish: unknown;
+		};
+		subscribe: (_: unknown, __: unknown) => void;
+	};
 	ev.subscribe(ev.player.play, deezerSendEvent);
 	ev.subscribe(ev.player.playing, deezerSendEvent);
 	ev.subscribe(ev.player.paused, deezerSendEvent);
@@ -40,15 +51,33 @@ function deezerSendEvent() {
 			isPlaying: deezerIsPlaying(),
 			isPodcast: deezerIsPodcast(),
 		},
-		'*'
+		'*',
 	);
 }
 
+interface DeezerMedia {
+	__TYPE__: string;
+	EXTERNAL?: unknown;
+	SNG_ID?: string;
+	SNG_TITLE?: string;
+	VERSION?: string;
+	ART_NAME?: string;
+	ALB_TITLE?: string;
+	ALB_PICTURE: string;
+	SHOW_NAME?: string;
+	EPISODE_TITLE?: string;
+	EPISODE_ID?: string;
+}
+
 function deezerGetCurrentMediaInfo() {
 	if (!('dzPlayer' in window)) {
 		return;
 	}
-	const player = window.dzPlayer as any;
+	const player = window.dzPlayer as {
+		getCurrentSong: () => DeezerMedia;
+		getPosition: () => number | null;
+		getDuration: () => number | null;
+	};
 	const currentMedia = player.getCurrentSong();
 
 	// Radio stations don't provide track info
@@ -60,13 +89,7 @@ function deezerGetCurrentMediaInfo() {
 	const currentTime = player.getPosition();
 	const duration = player.getDuration();
 
-	let trackInfo: {
-		artist: any;
-		track: any;
-		uniqueID: any;
-		duration?: any;
-		currentTime?: any;
-	} | null = null;
+	let trackInfo: State | null = null;
 
 	switch (mediaType) {
 		case 'episode': {
@@ -83,7 +106,7 @@ function deezerGetCurrentMediaInfo() {
 	if (!trackInfo) {
 		Util.debugLog(
 			`Unable to load track info for ${mediaType} media type`,
-			'warn'
+			'warn',
 		);
 		return null;
 	}
@@ -94,7 +117,7 @@ function deezerGetCurrentMediaInfo() {
 	return trackInfo;
 }
 
-function deezerGetTrackInfo(currentMedia: any) {
+function deezerGetTrackInfo(currentMedia: DeezerMedia) {
 	let trackTitle = currentMedia.SNG_TITLE;
 	const trackVersion = currentMedia.VERSION;
 	if (trackVersion) {
@@ -110,7 +133,7 @@ function deezerGetTrackInfo(currentMedia: any) {
 	};
 }
 
-function deezerGetEpisodeInfo(currentMedia: any) {
+function deezerGetEpisodeInfo(currentMedia: DeezerMedia) {
 	return {
 		artist: currentMedia.SHOW_NAME,
 		track: currentMedia.EPISODE_TITLE,
@@ -122,14 +145,16 @@ function deezerIsPlaying() {
 	if (!('dzPlayer' in window)) {
 		return;
 	}
-	return (window.dzPlayer as any).isPlaying();
+	return (window.dzPlayer as { isPlaying: () => boolean }).isPlaying();
 }
 
 function deezerIsPodcast() {
 	if (!('dzPlayer' in window)) {
 		return;
 	}
-	const currentMedia = (window.dzPlayer as any).getCurrentSong();
+	const currentMedia = (
+		window.dzPlayer as { getCurrentSong: () => DeezerMedia }
+	).getCurrentSong();
 	return currentMedia.__TYPE__ === 'episode';
 }
 
diff --git a/src/connectors/deezer.ts b/src/connectors/deezer.ts
index b7d37f5f..0d27d774 100644
--- a/src/connectors/deezer.ts
+++ b/src/connectors/deezer.ts
@@ -1,3 +1,4 @@
+import { State } from '@/core/types';
 export {};
 
 const deezerFilter = MetadataFilter.createRemasteredFilter();
@@ -18,7 +19,7 @@ Connector.onScriptEvent = (event) => {
 	switch (event.data.type) {
 		case 'DEEZER_STATE':
 			({ trackInfo, isPlaying, isPodcast } = event.data as {
-				trackInfo: any;
+				trackInfo: State;
 				isPlaying: boolean;
 				isPodcast: boolean;
 			});
diff --git a/src/connectors/eggs-dom-inject.ts b/src/connectors/eggs-dom-inject.ts
index b98722da..1806eec9 100644
--- a/src/connectors/eggs-dom-inject.ts
+++ b/src/connectors/eggs-dom-inject.ts
@@ -16,10 +16,20 @@ if (eggsIsArtistPage) {
 	observer.observe(document.body, { childList: true });
 } else {
 	if ('player' in window) {
-		(window.player as any).addEventListener(
-			'onStateChange',
-			eggsOnYoutubeSongStateChange
-		);
+		(
+			window.player as {
+				addEventListener: (
+					_: string,
+					__: (ev: {
+						data: number;
+						target: {
+							getCurrentTime: () => number;
+							getDuration: () => number;
+						};
+					}) => void,
+				) => void;
+			}
+		).addEventListener('onStateChange', eggsOnYoutubeSongStateChange);
 	}
 }
 
@@ -35,7 +45,7 @@ function eggsToggleExternalPlayer(mutationList: MutationRecord[]) {
 		// external player has been closed
 		if (
 			(removedList[0] as HTMLElement).classList.contains(
-				'fancybox-overlay'
+				'fancybox-overlay',
 			)
 		) {
 			eggsOnYoutubeClose();
@@ -46,7 +56,7 @@ function eggsToggleExternalPlayer(mutationList: MutationRecord[]) {
 
 function eggsReplaceYoutubeVideo() {
 	const videoFrame = document.querySelector(
-		'.fancybox-inner iframe'
+		'.fancybox-inner iframe',
 	) as HTMLIFrameElement;
 	eggsVideoId = videoFrame.src.split('/').pop()?.split('?')[0];
 
@@ -62,7 +72,7 @@ function eggsReplaceYoutubeVideo() {
 		videoFrame.contentWindow &&
 			videoFrame.contentWindow.postMessage(
 				message,
-				'https://www.youtube.com'
+				'https://www.youtube.com',
 			);
 
 		message = JSON.stringify({
@@ -75,7 +85,7 @@ function eggsReplaceYoutubeVideo() {
 		videoFrame.contentWindow &&
 			videoFrame.contentWindow.postMessage(
 				message,
-				'https://www.youtube.com'
+				'https://www.youtube.com',
 			);
 	});
 }
@@ -95,7 +105,7 @@ window.addEventListener('message', (event) => {
 	}
 });
 
-function eggsOnYoutubeStateChange(data: any) {
+function eggsOnYoutubeStateChange(data: { info: number }) {
 	const currentPlayer = document.querySelector(`a[href*="${eggsVideoId}"]`);
 	const parentElmt =
 		(currentPlayer && currentPlayer.closest('li')) || document;
@@ -112,14 +122,14 @@ function eggsOnYoutubeStateChange(data: any) {
 			},
 			trackInfo: {
 				artist: parentElmt.querySelector(
-					`.artist_name${eggsIsArtistPage ? '' : ' a'}`
+					`.artist_name${eggsIsArtistPage ? '' : ' a'}`,
 				)?.textContent,
 				track: parentElmt.querySelector(
-					`.product_name${eggsIsArtistPage ? ' a' : ' p'}`
+					`.product_name${eggsIsArtistPage ? ' a' : ' p'}`,
 				)?.textContent,
 			},
 		},
-		'*'
+		'*',
 	);
 }
 
@@ -138,18 +148,21 @@ function eggsOnYoutubeClose() {
 			},
 			trackInfo: {
 				artist: parentElmt.querySelector(
-					`.artist_name${eggsIsArtistPage ? '' : ' a'}`
+					`.artist_name${eggsIsArtistPage ? '' : ' a'}`,
 				)?.textContent,
 				track: parentElmt.querySelector(
-					`.product_name${eggsIsArtistPage ? ' a' : ' p'}`
+					`.product_name${eggsIsArtistPage ? ' a' : ' p'}`,
 				)?.textContent,
 			},
 		},
-		'*'
+		'*',
 	);
 }
 
-function eggsOnYoutubeSongStateChange(event: any) {
+function eggsOnYoutubeSongStateChange(event: {
+	data: number;
+	target: { getCurrentTime: () => number; getDuration: () => number };
+}) {
 	const currentPlayer = document.querySelector(`a[href*="${eggsVideoId}"]`);
 	const parentElmt =
 		(currentPlayer && currentPlayer.closest('li')) || document;
@@ -166,18 +179,20 @@ function eggsOnYoutubeSongStateChange(event: any) {
 			},
 			trackInfo: {
 				artist: parentElmt.querySelector(
-					`.artist_name${eggsIsArtistPage ? '' : ' a'}`
+					`.artist_name${eggsIsArtistPage ? '' : ' a'}`,
 				)?.textContent,
 				track: parentElmt.querySelector(
-					`.product_name${eggsIsArtistPage ? ' a' : ' p'}`
+					`.product_name${eggsIsArtistPage ? ' a' : ' p'}`,
 				)?.textContent,
 			},
 		},
-		'*'
+		'*',
 	);
 }
 
-function eggsGetTimestamps(data: any) {
+function eggsGetTimestamps(data: {
+	info?: { currentTime?: number; duration?: number };
+}) {
 	if (data.info) {
 		if (data.info.currentTime) {
 			eggsCurrentTime = data.info.currentTime;
diff --git a/src/connectors/eggs.ts b/src/connectors/eggs.ts
index 9a10ed5d..db8ef103 100644
--- a/src/connectors/eggs.ts
+++ b/src/connectors/eggs.ts
@@ -10,7 +10,7 @@ let isPlaying = false;
 
 Connector.getTimeInfo = () => {
 	const { currentTime, duration } = document.querySelector(
-		'#aPlayer'
+		'#aPlayer',
 	) as HTMLAudioElement;
 	return { currentTime, duration };
 };
@@ -83,7 +83,7 @@ function checkToggleArtist(mutationList: MutationRecord[]) {
 		// external player has been closed
 		if (
 			(removedList[0] as HTMLElement).classList.contains(
-				'fancybox-overlay'
+				'fancybox-overlay',
 			)
 		) {
 			setArtistConnector();
@@ -91,8 +91,10 @@ function checkToggleArtist(mutationList: MutationRecord[]) {
 	}
 }
 
-Connector.onScriptEvent = (event: any) => {
-	({ trackInfo, isPlaying, timeInfo } = event.data);
+Connector.onScriptEvent = (event: MessageEvent<Record<string, unknown>>) => {
+	trackInfo = event.data.trackInfo as object;
+	isPlaying = event.data.isPlaying as boolean;
+	timeInfo = event.data.timeInfo as object;
 
 	if (event.data.playerType === 'youtube') {
 		Connector.onStateChanged();
diff --git a/src/connectors/globalplayer.ts b/src/connectors/globalplayer.ts
index 9f03aa39..a9867b6c 100644
--- a/src/connectors/globalplayer.ts
+++ b/src/connectors/globalplayer.ts
@@ -1,14 +1,16 @@
 export {};
 
-const showNameSelector = '.show-info-with-image__text__title';
+const showNameSelector =
+	'div[class^="style_playbarInfo"] div[data-testid="show-info-title"]';
 
-Connector.playerSelector = '.track-container';
-
-Connector.trackSelector = '.track-container .show-info__text__title';
-
-Connector.artistSelector = '.track-container .show-info__text__subtitle';
-
-Connector.playButtonSelector = '.circular-play-button';
+Connector.playerSelector = ".globalplayer";
+Connector.trackSelector =
+	'div[data-testid="show-info-card"] h1[data-testid="show-info-title"]';
+Connector.artistSelector =
+	'div[data-testid="show-info-card"] h2[data-testid="show-info-subtitle"]';
+Connector.playButtonSelector =
+	'button[data-testid="play-pause-button"][aria-pressed="false"]';
+Connector.trackArtSelector = 'div[class^="style_showInfoCard"] img.current-img';
 
 Connector.isScrobblingAllowed = () => {
 	return Connector.getTrack() !== getShowName();
diff --git a/src/connectors/idagio.ts b/src/connectors/idagio.ts
index df0d5552..768db963 100644
--- a/src/connectors/idagio.ts
+++ b/src/connectors/idagio.ts
@@ -23,7 +23,7 @@ Connector.currentTimeSelector = '.player-PlayerProgress__progress--2F0qB>span';
 Connector.durationSelector = '.player-PlayerProgress__timeTotal--3aHlj span';
 
 Connector.isPlaying = () =>
-	Util.getTextFromSelectors(pauseButtonSelector) === 'Pause';
+	Util.getTextFromSelectors(pauseButtonSelector)?.toUpperCase() === 'PAUSE';
 
 Connector.isScrobblingAllowed = () =>
 	Util.getTextFromSelectors('.player-PlayerInfo__recordingInfo--15VMv') !==
diff --git a/src/connectors/musickit-dom-inject.ts b/src/connectors/musickit-dom-inject.ts
index e69e73e4..05f7706e 100644
--- a/src/connectors/musickit-dom-inject.ts
+++ b/src/connectors/musickit-dom-inject.ts
@@ -5,19 +5,48 @@
  */
 musicKitAddEventListeners();
 
+interface MusicKitWindow {
+	MusicKit: {
+		Events: {
+			metadataDidChange: unknown;
+			playbackStateDidChange: unknown;
+			nowPlayingItemDidChange: unknown;
+		};
+		getInstance: () => MusicKitInstance;
+	};
+}
+
+interface MusicKitInstance {
+	player?: MusicKitInstance;
+	addEventListener: (_: unknown, __: () => void) => void;
+	nowPlayingItem: {
+		albumName: string;
+		title: string;
+		artistName: string;
+		artworkURL: string;
+		container?: {
+			attributes?: { [key: string]: string };
+		};
+		id: unknown;
+	};
+	currentPlaybackDuration: number;
+	currentPlaybackTime: number;
+	isPlaying: boolean;
+}
+
 async function musicKitAddEventListeners() {
-	const Events = (window as any).MusicKit.Events;
+	const Events = (window as unknown as MusicKitWindow).MusicKit.Events;
 	const instance = await musicKitInstance();
 	instance.addEventListener(Events.metadataDidChange, musicKitSendEvent);
 	instance.addEventListener(Events.playbackStateDidChange, musicKitSendEvent);
 	instance.addEventListener(
 		Events.nowPlayingItemDidChange,
-		musicKitSendEvent
+		musicKitSendEvent,
 	);
 }
 
 async function musicKitInstance() {
-	const api = (window as any).MusicKit;
+	const api = (window as unknown as MusicKitWindow).MusicKit;
 
 	const initInstance = api?.getInstance();
 	// If the instance is already available, return it immediately
@@ -27,7 +56,7 @@ async function musicKitInstance() {
 
 	// Otherwise, poll until it is available
 	let i = 0;
-	return new Promise<any>((resolve, reject) => {
+	return new Promise<MusicKitInstance>((resolve, reject) => {
 		const interval = setInterval(() => {
 			const instance = api?.getInstance();
 			if (instance) {
@@ -52,7 +81,7 @@ async function musicKitSendEvent() {
 			trackInfo: await musicKitGetTrackInfo(),
 			isPlaying: await musicKitIsPlaying(),
 		},
-		'*'
+		'*',
 	);
 }
 
diff --git a/src/connectors/musickit.ts b/src/connectors/musickit.ts
index d8634b02..c7e96261 100644
--- a/src/connectors/musickit.ts
+++ b/src/connectors/musickit.ts
@@ -14,7 +14,7 @@ Connector.getTrackInfo = () => trackInfo;
 Connector.onScriptEvent = (e) => {
 	switch (e.data.type) {
 		case 'MUSICKIT_STATE':
-			trackInfo = e.data.trackInfo as any;
+			trackInfo = e.data.trackInfo as object;
 			isPlaying = e.data.isPlaying as boolean;
 
 			Connector.onStateChanged();
diff --git a/src/connectors/plex.ts b/src/connectors/plex.ts
index 010302b7..b8ad0ac0 100644
--- a/src/connectors/plex.ts
+++ b/src/connectors/plex.ts
@@ -6,7 +6,12 @@ const plexFilter = MetadataFilter.createFilter({
 	album: removeUnknownAlbum,
 });
 
-Connector.playerSelector = '[class^=Player-miniPlayerContainer]';
+Connector.useMediaSessionApi();
+
+Connector.playerSelector = [
+	'[class^=PlayerContainer-container]',
+    '[class^=Player-miniPlayerContainer]',
+];
 
 Connector.trackSelector = `${titleContainer} a[class*=MetadataPosterTitle]`;
 
diff --git a/src/connectors/sber-zvuk-dom-inject.ts b/src/connectors/sber-zvuk-dom-inject.ts
index f9fc3107..212ecf17 100644
--- a/src/connectors/sber-zvuk-dom-inject.ts
+++ b/src/connectors/sber-zvuk-dom-inject.ts
@@ -1,4 +1,19 @@
-function sberGetTrackInfo(track: any, currentTime: number) {
+interface TrackInfo {
+	title: string;
+	credits: string;
+	duration: number;
+	id: string;
+	image: { src: string };
+	release_title: string;
+}
+
+interface QueueEventObject {
+	Queue: QueueEventObject;
+	currentTrack: TrackInfo;
+	Player: { currentTime: number };
+}
+
+function sberGetTrackInfo(track: TrackInfo, currentTime: number) {
 	const { title, credits, duration, id, image, release_title: album } = track;
 	const trackArt = image.src.replace('{size}', '400x400');
 
@@ -13,7 +28,12 @@ function sberGetTrackInfo(track: any, currentTime: number) {
 	};
 }
 
-function sberQueueEvent(event: any) {
+function sberQueueEvent(event: {
+	type: string;
+	name: string;
+	newValue: unknown;
+	object: QueueEventObject;
+}) {
 	let type = null;
 	let queue;
 
@@ -40,7 +60,7 @@ function sberQueueEvent(event: any) {
 
 	const trackInfo = sberGetTrackInfo(
 		queue.currentTrack,
-		queue.Player.currentTime
+		queue.Player.currentTime,
 	);
 
 	window.postMessage(
@@ -49,12 +69,16 @@ function sberQueueEvent(event: any) {
 			type,
 			trackInfo,
 		},
-		'*'
+		'*',
 	);
 }
 
 function sberSetupEventListeners() {
-	(window as any).newPlayer.$mobx.observe(sberQueueEvent);
+	(
+		window as unknown as {
+			newPlayer: { $mobx: { observe: (ev: object) => void } };
+		}
+	).newPlayer.$mobx.observe(sberQueueEvent);
 }
 
 sberSetupEventListeners();
diff --git a/src/connectors/sber-zvuk.ts b/src/connectors/sber-zvuk.ts
index 990d352b..044db261 100644
--- a/src/connectors/sber-zvuk.ts
+++ b/src/connectors/sber-zvuk.ts
@@ -17,7 +17,7 @@ Connector.onScriptEvent = (event) => {
 			break;
 	}
 
-	trackInfo = event.data.trackInfo as any;
+	trackInfo = event.data.trackInfo as object;
 
 	Connector.onStateChanged();
 };
diff --git a/src/connectors/siriusxm-player.ts b/src/connectors/siriusxm-player.ts
index b79484f1..171d6172 100644
--- a/src/connectors/siriusxm-player.ts
+++ b/src/connectors/siriusxm-player.ts
@@ -1,14 +1,19 @@
 export {};
 
 /*
- * On some "decade" stations SiriusXM appends the last two digits
+ * Station Meta Filter
+ * - On some "decade" stations SiriusXM appends the last two digits
  * of the release year to the song title like this: Where Is The Love (03).
+ * - On The Covers Channel, it adds Heroes (David Bowie Cover) to the track
+ * - A lot of stations seem to employ adding EXCLUSIVE to the track name, so that is now removed
  *
- * This filter removes such suffixes.
+ * Filter Station Meta
+ * - There is now a filter for much of the station meta on SiriusXM. Removes social media handles, URLs, shownames
  */
 
-const filter = MetadataFilter.createFilter({ track: removeYear });
-const removeYearRe = /\s\(\d{2}\)\s?$/g;
+const filter = MetadataFilter.createFilter({
+	track: [removeYear, removeCover, removeExclusive],
+});
 
 Connector.playerSelector = ".sxm-player-controls";
 Connector.artistSelector = ".sxm-player-controls .artist-name";
@@ -18,7 +23,7 @@ Connector.isPlaying = () => {
 	return (
 		Util.getAttrFromSelectors(
 			".sxm-player-controls .play-pause-btn",
-			"title"
+			"title",
 		) === "Pause"
 	);
 };
@@ -46,12 +51,20 @@ Connector.isScrobblingAllowed = () => {
 	];
 
 	return !filteredTerms.some(
-		(term) => artist?.includes(term) || track?.includes(term)
+		(term) => artist?.includes(term) || track?.includes(term),
 	);
 };
 
 Connector.applyFilter(filter);
 
+function removeExclusive(track: string) {
+	return track.replace(/\sEXCLUSIVE/g, "");
+}
+
+function removeCover(track: string) {
+	return track.replace(/\([^)]*\b(?:cover|Cover)\b[^)]*\)/g, "");
+}
+
 function removeYear(track: string) {
-	return track.replace(removeYearRe, "");
+	return track.replace(/\s\(\d{2}\)\s?$/g, "");
 }
diff --git a/src/connectors/soundcloud.ts b/src/connectors/soundcloud.ts
index ecae120b..998de6f9 100644
--- a/src/connectors/soundcloud.ts
+++ b/src/connectors/soundcloud.ts
@@ -43,11 +43,12 @@ Connector.getRemainingTime = () => {
 Connector.isPlaying = () => Util.hasElementClass('.playControl', 'playing');
 
 Connector.getUniqueID = () => {
-	return (
+	const url = new URL((
 		document.querySelector(
 			'.playbackSoundBadge__titleLink'
 		) as HTMLAnchorElement
-	).href;
+	).href);
+	return url.origin + url.pathname;
 };
 
 Connector.getOriginUrl = () => {
diff --git a/src/connectors/trackerhub.ts b/src/connectors/trackerhub.ts
new file mode 100644
index 00000000..d312091e
--- /dev/null
+++ b/src/connectors/trackerhub.ts
@@ -0,0 +1,4 @@
+export {};
+
+Connector.useMediaSessionApi();
+Connector.playerSelector = '#player';
diff --git a/src/connectors/vk-dom-inject.ts b/src/connectors/vk-dom-inject.ts
index 16b9024e..be93500f 100644
--- a/src/connectors/vk-dom-inject.ts
+++ b/src/connectors/vk-dom-inject.ts
@@ -13,14 +13,24 @@ const VK_INFO_ADDITIONAL = 16;
 
 vkSetupEventListeners();
 
+interface VKWindow {
+	ap: {
+		_currentAudio?: string[];
+		_impl: { _currentAudioEl?: { currentTime: string } };
+		subscribers: {
+			push: (_: object) => void;
+		};
+	};
+}
+
 function vkSendUpdateEvent(type: string) {
-	const audioObject = (window as any).ap._currentAudio;
+	const audioObject = (window as unknown as VKWindow).ap._currentAudio;
 	if (!audioObject) {
 		return;
 	}
 
-	const audioElImpl = (window as any).ap._impl._currentAudioEl || {};
-	const { currentTime } = audioElImpl;
+	const currentTime = (window as unknown as VKWindow).ap._impl._currentAudioEl
+		?.currentTime;
 
 	/*
 	 * VK player sets current time equal to song duration on startup.
@@ -52,13 +62,13 @@ function vkSendUpdateEvent(type: string) {
 				artist: audioObject[VK_INFO_ARTIST],
 			},
 		},
-		'*'
+		'*',
 	);
 }
 
 function vkSetupEventListeners() {
 	for (const e of ['start', 'progress', 'pause', 'stop']) {
-		(window as any).ap.subscribers.push({
+		(window as unknown as VKWindow).ap.subscribers.push({
 			et: e,
 			cb: vkSendUpdateEvent.bind(null, e),
 		});
diff --git a/src/connectors/vk.ts b/src/connectors/vk.ts
index e9975356..3f4e0a4b 100644
--- a/src/connectors/vk.ts
+++ b/src/connectors/vk.ts
@@ -6,8 +6,8 @@ let isPlaying = false;
 const vkFilter = MetadataFilter.createFilter(
 	MetadataFilter.createFilterSetForFields(
 		['artist', 'track', 'album', 'albumArtist'],
-		MetadataFilter.decodeHtmlEntities
-	)
+		MetadataFilter.decodeHtmlEntities,
+	),
 ).extend(MetadataFilter.createRemasteredFilter());
 
 Connector.isPlaying = () => isPlaying;
@@ -25,7 +25,7 @@ Connector.onScriptEvent = (event) => {
 			break;
 	}
 
-	trackInfo = event.data.trackInfo as any;
+	trackInfo = event.data.trackInfo as object;
 
 	Connector.onStateChanged();
 };
diff --git a/src/connectors/yandex-music-dom-inject.ts b/src/connectors/yandex-music-dom-inject.ts
index 06810ffc..b89729b1 100644
--- a/src/connectors/yandex-music-dom-inject.ts
+++ b/src/connectors/yandex-music-dom-inject.ts
@@ -1,4 +1,22 @@
-const YandexAPI = (window as any).externalAPI;
+interface YandexWindow {
+	externalAPI: {
+		on: (_: unknown, __: () => void) => void;
+		EVENT_STATE: unknown;
+		EVENT_TRACK: unknown;
+		isPlaying: () => boolean;
+		getCurrentTrack: () => {
+			title: string;
+			album: { title: string };
+			cover: string;
+			artists: { title: string }[];
+			duration: number;
+			link: string;
+		};
+		getProgress: () => { position: number };
+	};
+}
+
+const YandexAPI = (window as unknown as YandexWindow).externalAPI;
 
 yandexSetupListeners();
 
@@ -15,7 +33,7 @@ function yandexOnEvent() {
 			trackInfo: yandexGetTrackInfo(),
 			isPlaying: YandexAPI.isPlaying(),
 		},
-		'*'
+		'*',
 	);
 }
 
diff --git a/src/connectors/yandex-music.ts b/src/connectors/yandex-music.ts
index d3865739..42e84c7b 100644
--- a/src/connectors/yandex-music.ts
+++ b/src/connectors/yandex-music.ts
@@ -10,7 +10,7 @@ Connector.getTrackInfo = () => trackInfo;
 Connector.onScriptEvent = (e) => {
 	switch (e.data.type) {
 		case 'YANDEX_MUSIC_STATE':
-			trackInfo = e.data.trackInfo as any;
+			trackInfo = e.data.trackInfo as object;
 			isPlaying = e.data.isPlaying as boolean;
 
 			Connector.onStateChanged();
diff --git a/src/connectors/youtube.ts b/src/connectors/youtube.ts
index ac62daf0..6688a736 100644
--- a/src/connectors/youtube.ts
+++ b/src/connectors/youtube.ts
@@ -65,7 +65,14 @@ let getTrackInfoFromYtMusicEnabled = false;
 let currentVideoDescription: string | null = null;
 let artistTrackFromDescription: TrackInfoWithAlbum | null = null;
 
-const getTrackInfoFromYoutubeMusicCache: any = {};
+const getTrackInfoFromYoutubeMusicCache: {
+	[videoId: string]: {
+		done?: boolean;
+		recognisedByYtMusic?: boolean;
+		videoId?: string | null;
+		currentTrackInfo?: { artist?: string; track?: string };
+	};
+} = {};
 
 const trackInfoGetters = [
 	getTrackInfoFromChapters,
@@ -117,7 +124,7 @@ Connector.getTrackInfo = () => {
 
 Connector.getTimeInfo = () => {
 	const videoElement = document.querySelector(
-		videoSelector
+		videoSelector,
 	) as HTMLVideoElement;
 	if (videoElement && !areChaptersAvailable()) {
 		let { currentTime, duration, playbackRate } = videoElement;
@@ -187,7 +194,7 @@ Connector.applyFilter(
 	MetadataFilter.createYouTubeFilter().append({
 		artist: [removeLtrRtlChars, removeNumericPrefix],
 		track: [removeLtrRtlChars, removeNumericPrefix],
-	})
+	}),
 );
 
 function setupEventListener() {
@@ -202,7 +209,7 @@ function areChaptersAvailable() {
 	// SponsorBlock extension hijacks chapter element. Ignore it.
 	if (
 		document.querySelector(
-			'.ytp-chapter-title-content.sponsorBlock-segment-title'
+			'.ytp-chapter-title-content.sponsorBlock-segment-title',
 		)
 	) {
 		return false;
@@ -220,7 +227,7 @@ function getVideoId() {
 	 */
 	const miniPlayerVideoUrl = Util.getAttrFromSelectors(
 		'ytd-miniplayer[active] [selected] a',
-		'href'
+		'href',
 	);
 	if (miniPlayerVideoUrl) {
 		return Util.getYtVideoIdFromUrl(miniPlayerVideoUrl);
@@ -228,7 +235,7 @@ function getVideoId() {
 
 	const videoIDDesktop = Util.getAttrFromSelectors(
 		'ytd-watch-flexy',
-		'video-id'
+		'video-id',
 	);
 	if (videoIDDesktop) {
 		return videoIDDesktop;
@@ -264,7 +271,7 @@ function getVideoCategory() {
 		.catch((err) => {
 			Util.debugLog(
 				`Failed to fetch category for ${videoId}: ${err}`,
-				'warn'
+				'warn',
 			);
 			categoryCache.set(videoId, categoryUnknown);
 		});
@@ -399,7 +406,7 @@ function getTrackInfoFromYoutubeMusic() {
 				done: true,
 				recognisedByYtMusic:
 					videoInfo.videoDetails?.musicVideoType?.startsWith(
-						'MUSIC_VIDEO_'
+						'MUSIC_VIDEO_',
 					) || false,
 			};
 
@@ -421,7 +428,7 @@ function getTrackInfoFromYoutubeMusic() {
 		.catch((err) => {
 			Util.debugLog(
 				`Failed to fetch youtube music data for ${videoId}: ${err}`,
-				'warn'
+				'warn',
 			);
 			getTrackInfoFromYoutubeMusicCache[videoId ?? ''] = {
 				done: true,
@@ -449,7 +456,7 @@ function getTrackInfoFromChapters() {
 
 function getTrackInfoFromTitle() {
 	let { artist, track } = Util.processYtVideoTitle(
-		Util.getTextFromSelectors(videoTitleSelector)
+		Util.getTextFromSelectors(videoTitleSelector),
 	);
 	if (!artist) {
 		artist = Util.getTextFromSelectors(channelNameSelector);
@@ -480,7 +487,7 @@ function removeNumericPrefix(text: string) {
 
 function isVideoStartedPlaying() {
 	const videoElement = document.querySelector(
-		videoSelector
+		videoSelector,
 	) as HTMLVideoElement;
 	return videoElement && videoElement.currentTime > 0;
 }
diff --git a/src/core/connectors.ts b/src/core/connectors.ts
index 3911266c..41f919ae 100644
--- a/src/core/connectors.ts
+++ b/src/core/connectors.ts
@@ -2106,4 +2106,10 @@ export default <ConnectorMeta[]>[
 		js: 'radcap.js',
 		id: 'radcap',
 	},
+	{
+		label: 'TrackerHub',
+		matches: ['*://trackerhub.vercel.app/*'],
+		js: 'trackerhub.js',
+		id: 'trackerhub',
+	},
 ];
diff --git a/src/core/content/util.ts b/src/core/content/util.ts
index 77bd2514..cba5a511 100644
--- a/src/core/content/util.ts
+++ b/src/core/content/util.ts
@@ -101,7 +101,7 @@ export function stringToSeconds(str: string | null | undefined): number {
  */
 export function findSeparator(
 	str: string | null,
-	separators: Separator[] | null = null
+	separators: Separator[] | null = null,
 ): { index: number; length: number } | null {
 	if (str === null || str.length === 0) {
 		return null;
@@ -145,7 +145,7 @@ export function joinArtists(artists: Node[]): string | null {
 export function splitArtistTrack(
 	str: string | null | undefined,
 	separators: Separator[] | null = null,
-	swap = false
+	swap = false,
 ): ArtistTrackInfo {
 	if (!str) {
 		return { artist: null, track: null };
@@ -181,7 +181,7 @@ export function removeRecordSide(str: string | null): string | null {
 export function splitArtistAlbum(
 	str: string | null | undefined,
 	separators: Separator[] | null = null,
-	swap = false
+	swap = false,
 ): { artist: string | null; album: string | null } {
 	const [artist, album] = splitString(str, separators, swap);
 	return { artist, album };
@@ -197,7 +197,7 @@ export function splitArtistAlbum(
 export function splitTimeInfo(
 	str: string | null,
 	separator: Separator = '/',
-	swap = false
+	swap = false,
 ): TimeInfo {
 	if (str === null) {
 		return {
@@ -206,7 +206,7 @@ export function splitTimeInfo(
 		};
 	}
 	const [currentTime, duration] = splitString(str, [separator], swap).map(
-		(e) => stringToSeconds(e)
+		(e) => stringToSeconds(e),
 	);
 
 	return { currentTime, duration };
@@ -222,7 +222,7 @@ export function splitTimeInfo(
 export function splitString(
 	str: string | null | undefined,
 	separators: Separator[] | null,
-	swap = false
+	swap = false,
 ): [string | null, string | null] {
 	let first = null;
 	let second = null;
@@ -265,7 +265,7 @@ export function escapeBadTimeValues(time: unknown): number | null {
  * @returns Track art URL
  */
 export function extractUrlFromCssProperty(
-	cssProperty: string | null | undefined
+	cssProperty: string | null | undefined,
 ): string | null {
 	if (!cssProperty) {
 		return null;
@@ -313,7 +313,7 @@ export function throttle<T>(func: () => T, wait: number): () => T | undefined {
  * @returns True if object is empty; false otherwise
  */
 export function isArtistTrackEmpty(
-	artistTrack: ArtistTrackInfo | null
+	artistTrack: ArtistTrackInfo | null,
 ): boolean {
 	return !(artistTrack && artistTrack.artist && artistTrack.track);
 }
@@ -329,7 +329,7 @@ export function isArtistTrackEmpty(
 export function fillEmptyFields(
 	target: State,
 	source: State | null | undefined,
-	fields: (keyof State)[] | undefined
+	fields: (keyof State)[] | undefined,
 ): State {
 	if (!source || !Array.isArray(fields)) {
 		return target;
@@ -352,7 +352,7 @@ export function fillEmptyFields(
  * @returns Track info
  */
 export function getMediaSessionInfo(
-	mediaSession: MediaSession
+	mediaSession: MediaSession,
 ): BaseState | null {
 	if (!(mediaSession && mediaSession.metadata)) {
 		return null;
@@ -383,7 +383,7 @@ export function getMediaSessionInfo(
 /* istanbul ignore next */
 export function getTextFromSelectors(
 	selectors: string | string[] | null,
-	defaultValue: string | null = null
+	defaultValue: string | null = null,
 ): string | null {
 	if (selectors === null) {
 		return defaultValue;
@@ -419,7 +419,7 @@ export function getTextFromSelectors(
 export function getAttrFromSelectors(
 	selectors: string | string[] | null,
 	attr: string,
-	defaultValue: string | null = null
+	defaultValue: string | null = null,
 ): string | null {
 	if (selectors === null) {
 		return defaultValue;
@@ -452,7 +452,7 @@ export function getAttrFromSelectors(
 export function bindListeners(
 	selectors: string | string[],
 	events: string | string[],
-	fn: (e: Event) => void
+	fn: (e: Event) => void,
 ) {
 	const elements = queryElements(selectors);
 	if (!elements) {
@@ -478,7 +478,7 @@ export function bindListeners(
  */
 /* istanbul ignore next */
 export function getSecondsFromSelectors(
-	selectors: string | string[] | null
+	selectors: string | string[] | null,
 ): number | undefined {
 	return stringToSeconds(getTextFromSelectors(selectors) ?? '');
 }
@@ -490,7 +490,7 @@ export function getSecondsFromSelectors(
  */
 /* istanbul ignore next */
 export function extractImageUrlFromSelectors(
-	selectors: string | string[] | null
+	selectors: string | string[] | null,
 ): string | null {
 	if (selectors === null) {
 		return null;
@@ -524,7 +524,7 @@ export function extractImageUrlFromSelectors(
  */
 export function getCSSPropertyFromSelectors(
 	selectors: string | string[],
-	property: string
+	property: string,
 ) {
 	const elements = queryElements(selectors);
 	if (!elements) {
@@ -557,7 +557,7 @@ export function getCSSProperty(element: Element, property: string) {
 /* istanbul ignore next */
 export function hasElementClass(
 	selectors: string | string[] | null,
-	cls: string
+	cls: string,
 ): boolean {
 	if (selectors === null) {
 		return false;
@@ -601,7 +601,7 @@ function visibleFilter(elements: NodeListOf<HTMLElement>) {
  */
 /* istanbul ignore next */
 export function isElementVisible(
-	selectors: string | string[] | null | undefined
+	selectors: string | string[] | null | undefined,
 ): boolean {
 	if (!selectors) {
 		return false;
@@ -617,7 +617,7 @@ export function isElementVisible(
  * @returns Value, or null if not found
  */
 export function getValueFromSelectors(
-	selectors: string | string[]
+	selectors: string | string[],
 ): string | null {
 	const element = queryElements(selectors);
 	if (!element || !('value' in element)) {
@@ -635,7 +635,7 @@ export function getValueFromSelectors(
  */
 export function getDataFromSelectors(
 	selectors: string | string[],
-	name: string
+	name: string,
 ): string | null {
 	return getAttrFromSelectors(selectors, `data-${name}`);
 }
@@ -649,7 +649,7 @@ export function getDataFromSelectors(
  */
 /* istanbul ignore next */
 export function queryElements(
-	selectors: string | string[] | null | undefined
+	selectors: string | string[] | null | undefined,
 ): NodeListOf<HTMLElement> | null {
 	if (!selectors) {
 		return null;
@@ -665,7 +665,7 @@ export function queryElements(
 
 	for (const selector of selectors) {
 		const elements = document.querySelectorAll(
-			selector
+			selector,
 		) as NodeListOf<HTMLElement>;
 		if (elements.length > 0) {
 			return elements;
@@ -684,7 +684,7 @@ export function queryElements(
 /* istanbul ignore next */
 export async function getOption(
 	connector: string,
-	key: string
+	key: string,
 ): Promise<boolean> {
 	const awaitedStorage = await BrowserStorage;
 	const data = await awaitedStorage
@@ -747,7 +747,7 @@ export function injectScriptIntoDocument(scriptUrl: string): void {
  * @param logType - Log type
  */
 /* istanbul ignore next */
-export function debugLog(text: any, logType: DebugLogType = 'log'): void {
+export function debugLog(text: unknown, logType: DebugLogType = 'log'): void {
 	const logFunc = console[logType];
 
 	if (typeof logFunc !== 'function') {
@@ -797,7 +797,7 @@ export const ytTitleRegExps = [
  * @returns Object containing artist and track fields
  */
 export function processYtVideoTitle(
-	videoTitle: string | null | undefined
+	videoTitle: string | null | undefined,
 ): ArtistTrackInfo {
 	let artist = null;
 	let track = null;
@@ -861,12 +861,12 @@ export function processYtVideoTitle(
 export function isYtVideoDescriptionValid(desc: string | null): desc is string {
 	return Boolean(
 		desc &&
-			(desc.startsWith(ytDescFirstLine) || desc.endsWith(ytDescLastLine))
+			(desc.startsWith(ytDescFirstLine) || desc.endsWith(ytDescLastLine)),
 	);
 }
 
 export function parseYtVideoDescription(
-	desc: string | null
+	desc: string | null,
 ): TrackInfoWithAlbum | null {
 	if (!isYtVideoDescriptionValid(desc)) {
 		return null;
@@ -899,8 +899,8 @@ export function parseYtVideoDescription(
 	} else {
 		[track, artist, ...featArtists] = trackInfo;
 
-		const areFeatArtistPresent = featArtists.some((artist) =>
-			track?.includes(artist)
+		const areFeatArtistPresent = featArtists.some(
+			(artist) => track?.includes(artist),
 		);
 		if (!areFeatArtistPresent) {
 			const featArtistsStr = featArtists.join(ARTIST_SEPARATOR);
@@ -942,7 +942,7 @@ export const scArtistTrackRe = /(.+)\s[:\u2013-\u2015-]\s(.+)/;
  * @returns Object contains artist and track fields
  */
 export function processSoundCloudTrack(
-	track: string | null | undefined
+	track: string | null | undefined,
 ): ArtistTrackInfo {
 	if (!track) {
 		return { artist: null, track: null };
diff --git a/src/types/declaration.d.ts b/src/types/declaration.d.ts
index d834a6c6..62207cb0 100644
--- a/src/types/declaration.d.ts
+++ b/src/types/declaration.d.ts
@@ -4,6 +4,7 @@ declare module '*.scss' {
 }
 
 declare module '*.svg' {
+	// eslint-disable-next-line @typescript-eslint/no-explicit-any
 	const content: any;
 	export default content;
 }
diff --git a/src/util/communication.ts b/src/util/communication.ts
index a424059c..1cf66619 100644
--- a/src/util/communication.ts
+++ b/src/util/communication.ts
@@ -140,23 +140,23 @@ interface SpecificContentListener<K extends keyof BackgroundCommunications> {
 	type: K;
 	fn: (
 		payload: BackgroundCommunications[K]['payload'],
-		sender: browser.Runtime.MessageSender
+		sender: browser.Runtime.MessageSender,
 	) => BackgroundCommunications[K]['response'];
 }
 
 type ContentListener = <R>(
 	cont: <T extends keyof BackgroundCommunications>(
-		prop: SpecificContentListener<T>
-	) => R
+		prop: SpecificContentListener<T>,
+	) => R,
 ) => R;
 
 export function contentListener<T extends keyof BackgroundCommunications>(
-	property: SpecificContentListener<T>
+	property: SpecificContentListener<T>,
 ): ContentListener {
 	return <R>(
 		cont: <T extends keyof BackgroundCommunications>(
-			prop: SpecificContentListener<T>
-		) => R
+			prop: SpecificContentListener<T>,
+		) => R,
 	) => cont(property);
 }
 
@@ -167,6 +167,7 @@ interface BackgroundMessage<K extends keyof BackgroundCommunications> {
 
 export function setupContentListeners(...listeners: ContentListener[]) {
 	browser.runtime.onMessage.addListener(
+		// eslint-disable-next-line @typescript-eslint/no-explicit-any
 		(message: BackgroundMessage<any>, sender) => {
 			let done = false;
 			for (const l of listeners) {
@@ -181,15 +182,15 @@ export function setupContentListeners(...listeners: ContentListener[]) {
 					return Promise.resolve(response);
 				}
 			}
-		}
+		},
 	);
 }
 
 export async function sendBackgroundMessage<
-	K extends keyof BackgroundCommunications
+	K extends keyof BackgroundCommunications,
 >(
 	tabId: number,
-	message: BackgroundMessage<K>
+	message: BackgroundMessage<K>,
 ): Promise<BackgroundCommunications[K]['response']> {
 	return browser.tabs.sendMessage(tabId, message);
 }
@@ -202,23 +203,23 @@ interface SpecificBackgroundListener<K extends keyof ContentCommunications> {
 	type: K;
 	fn: (
 		payload: ContentCommunications[K]['payload'],
-		sender: browser.Runtime.MessageSender
+		sender: browser.Runtime.MessageSender,
 	) => ContentCommunications[K]['response'];
 }
 
 type BackgroundListener = <R>(
 	cont: <T extends keyof ContentCommunications>(
-		prop: SpecificBackgroundListener<T>
-	) => R
+		prop: SpecificBackgroundListener<T>,
+	) => R,
 ) => R;
 
 export function backgroundListener<T extends keyof ContentCommunications>(
-	property: SpecificBackgroundListener<T>
+	property: SpecificBackgroundListener<T>,
 ): BackgroundListener {
 	return <R>(
 		cont: <T extends keyof ContentCommunications>(
-			prop: SpecificBackgroundListener<T>
-		) => R
+			prop: SpecificBackgroundListener<T>,
+		) => R,
 	) => cont(property);
 }
 
@@ -229,6 +230,7 @@ interface ContentMessage<K extends keyof ContentCommunications> {
 
 export function setupBackgroundListeners(...listeners: BackgroundListener[]) {
 	browser.runtime.onMessage.addListener(
+		// eslint-disable-next-line @typescript-eslint/no-explicit-any
 		(message: ContentMessage<any>, sender) => {
 			let done = false;
 			for (const l of listeners) {
@@ -243,12 +245,12 @@ export function setupBackgroundListeners(...listeners: BackgroundListener[]) {
 					return Promise.resolve(response);
 				}
 			}
-		}
+		},
 	);
 }
 
 export async function sendContentMessage<K extends keyof ContentCommunications>(
-	message: ContentMessage<K>
+	message: ContentMessage<K>,
 ): Promise<ContentCommunications[K]['response']> {
 	return browser.runtime.sendMessage(message);
 }
@@ -261,23 +263,23 @@ interface SpecificPopupListener<K extends keyof PopupCommunications> {
 	type: K;
 	fn: (
 		payload: PopupCommunications[K]['payload'],
-		sender: browser.Runtime.MessageSender
+		sender: browser.Runtime.MessageSender,
 	) => PopupCommunications[K]['response'];
 }
 
 type PopupListener = <R>(
 	cont: <T extends keyof PopupCommunications>(
-		prop: SpecificPopupListener<T>
-	) => R
+		prop: SpecificPopupListener<T>,
+	) => R,
 ) => R;
 
 export function popupListener<T extends keyof PopupCommunications>(
-	property: SpecificPopupListener<T>
+	property: SpecificPopupListener<T>,
 ): PopupListener {
 	return <R>(
 		cont: <T extends keyof PopupCommunications>(
-			prop: SpecificPopupListener<T>
-		) => R
+			prop: SpecificPopupListener<T>,
+		) => R,
 	) => cont(property);
 }
 
@@ -288,6 +290,7 @@ interface PopupMessage<K extends keyof PopupCommunications> {
 
 export function setupPopupListeners(...listeners: PopupListener[]) {
 	browser.runtime.onMessage.addListener(
+		// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Set regardless of previous state
 		(message: PopupMessage<any>, sender) => {
 			let done = false;
 			for (const l of listeners) {
@@ -302,12 +305,12 @@ export function setupPopupListeners(...listeners: PopupListener[]) {
 					return Promise.resolve(response);
 				}
 			}
-		}
+		},
 	);
 }
 
 export async function sendPopupMessage<K extends keyof PopupCommunications>(
-	message: PopupMessage<K>
+	message: PopupMessage<K>,
 ): Promise<PopupCommunications[K]['response']> {
 	return browser.runtime.sendMessage(message);
 }
diff --git a/src/util/emitter.ts b/src/util/emitter.ts
index 008de0e0..980a734a 100644
--- a/src/util/emitter.ts
+++ b/src/util/emitter.ts
@@ -2,7 +2,8 @@
  * Class that emulates event emitter behavior while having strict typing
  */
 export default class EventEmitter<
-	T extends Record<string, (...args: any[]) => void>
+	// eslint-disable-next-line @typescript-eslint/no-explicit-any
+	T extends Record<string, (...args: any[]) => void>,
 > {
 	private events: Map<keyof T, T[keyof T][]> = new Map();
 	/**
@@ -26,7 +27,7 @@ export default class EventEmitter<
 		const curEvents = this.events.get(event) || [];
 		this.events.set(
 			event,
-			curEvents.filter((e) => e !== fn)
+			curEvents.filter((e) => e !== fn),
 		);
 	}
 
diff --git a/src/util/regex.ts b/src/util/regex.ts
index c38cb887..36edb784 100644
--- a/src/util/regex.ts
+++ b/src/util/regex.ts
@@ -1,4 +1,3 @@
-import ClonedSong from '@/core/object/cloned-song';
 import Song, { BaseSong } from '@/core/object/song';
 import * as BrowserStorage from '@/core/storage/browser-storage';
 
@@ -107,7 +106,7 @@ export function searchMatches(search: string | null, text: string): boolean {
 function replaceField(
 	search: string | null,
 	replace: string | null,
-	text: string
+	text: string,
 ): string {
 	if (search === null || replace === null) {
 		return text;
@@ -131,7 +130,7 @@ function replaceField(
 export function replaceFields(
 	search: RegexFields,
 	replace: RegexFields,
-	fields: EditedFields
+	fields: EditedFields,
 ): EditedFields {
 	for (const _field of Object.keys(fields)) {
 		const field = _field as FieldType;
@@ -144,7 +143,7 @@ export function replaceFields(
 		fields[field] = replaceField(
 			search[field],
 			replace[field],
-			fields[field]
+			fields[field],
 		);
 	}
 	return fields;
@@ -191,7 +190,7 @@ export function editSong(edit: RegexEdit, song: Song) {
 	const fields = replaceFields(
 		edit.search,
 		edit.replace,
-		getProcessedFields(song)
+		getProcessedFields(song),
 	);
 
 	for (const [_key, field] of Object.entries(fields)) {
@@ -234,7 +233,7 @@ export function getSongField(clonedSong: BaseSong, type: FieldType): string {
  */
 export function getSongFieldNoRegex(
 	clonedSong: BaseSong,
-	type: FieldType
+	type: FieldType,
 ): string {
 	return (clonedSong.noRegex[type] || clonedSong.parsed[type]) ?? '';
 }


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index c6e6275b..265d199b 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -12,13 +12,10 @@ jobs:
       - name: Checkout the repository
         uses: actions/checkout@v3
 
-      - name: Get Node.js version from .nvmrc
-        run: echo "NODE_VERSION=$(cat .nvmrc)" >> $GITHUB_ENV
-
       - name: Setup Node.js
         uses: actions/setup-node@v3
         with:
-          node-version: '${{ env.NODE_VERSION }}'
+          node-version-file: '.nvmrc'
 
       - name: Install dependencies
         run: npm ci
diff --git a/tests/background/song.test.ts b/tests/background/song.test.ts
index 0ebcc36e..9808c3a2 100644
--- a/tests/background/song.test.ts
+++ b/tests/background/song.test.ts
@@ -61,7 +61,7 @@ const DUMMY_CONNECTOR = {
  */
 function createSong(
 	parsedData: ParsedSongData,
-	processedData?: ProcessedSongData
+	processedData?: ProcessedSongData,
 ) {
 	const parsedDataCopy: ParsedSongData = {};
 	for (const prop in defaultParsedData) {
@@ -143,7 +143,7 @@ function testGetDuration() {
 	it('should return parsed duration if no processed duration', () => {
 		const song = createSong(
 			{ duration: parsedDuration },
-			{ duration: processedDuration }
+			{ duration: processedDuration },
 		);
 		expect(song.getDuration()).equals(parsedDuration);
 	});
@@ -378,11 +378,11 @@ function testEquals() {
 	});
 
 	it('should not equal null value', () => {
-		expect(songWithUniqueId.equals(null as any)).to.be.false;
+		expect(songWithUniqueId.equals(null)).to.be.false;
 	});
 
 	it('should not equal non-song object', () => {
-		expect(songWithUniqueId.equals(23 as any)).to.be.false;
+		expect(songWithUniqueId.equals(23)).to.be.false;
 	});
 }
 
diff --git a/tests/background/util.test.ts b/tests/background/util.test.ts
index b42d1523..059830d9 100644
--- a/tests/background/util.test.ts
+++ b/tests/background/util.test.ts
@@ -226,7 +226,7 @@ function runTests() {
 function testDebugLog() {
 	it('should throw an error if type is invalid', () => {
 		function callInvalidDebugLog() {
-			Util.debugLog('Test', 'invalid_type123' as any);
+			Util.debugLog('Test', 'invalid_type123');
 		}
 
 		expect(callInvalidDebugLog).to.throw();
@@ -271,7 +271,10 @@ function testTimeoutPromise() {
  * @param func - Function to be tested
  * @param testData - Array of test data
  */
-function testFunction(func: (...args: any[]) => unknown, testData: TestData[]) {
+function testFunction(
+	func: (...args: unknown[]) => unknown,
+	testData: TestData[],
+) {
 	const boundFunc = func.bind(Util);
 
 	for (const data of testData) {
diff --git a/tests/content/util.test.ts b/tests/content/util.test.ts
index a6404a51..3ce7f645 100644
--- a/tests/content/util.test.ts
+++ b/tests/content/util.test.ts
@@ -2,7 +2,7 @@ import { TestData } from '#/types/types';
 import { expect, it, describe } from 'vitest';
 
 // trick webextension polyfill into thinking this is running in the browser
-(globalThis as any).chrome = {
+(globalThis as { chrome: unknown }).chrome = {
 	runtime: {
 		id: 'mock',
 	},
@@ -1098,7 +1098,10 @@ const testData = [
  * @param func - Function to be tested
  * @param testData - Array of test data
  */
-function testFunction(func: (...args: any[]) => unknown, testData: TestData[]) {
+function testFunction(
+	func: (...args: unknown[]) => unknown,
+	testData: TestData[],
+) {
 	const boundFunc = func.bind(Util);
 
 	for (const data of testData) {
diff --git a/tests/mocks/webextension-polyfill.ts b/tests/mocks/webextension-polyfill.ts
index d2431881..76d09c68 100644
--- a/tests/mocks/webextension-polyfill.ts
+++ b/tests/mocks/webextension-polyfill.ts
@@ -27,7 +27,7 @@ class StorageAreaStub {
 }
 
 const messages = {
-	getSongInfo: (payload: any) => {
+	getSongInfo: (payload: { song: CloneableSong }) => {
 		if (
 			payload.song.processed.artist === 'フミンニッキ' &&
 			payload.song.processed.track === 'Re:start'