Repository: web-scrobbler/web-scrobbler
Pull request #4301 (2023-11-18 14:35:40)

Add Радио Premium connector
**Describe the changes you made**
Closes: #1569 

**Additional context**
<!-- Add any other context or screenshots here. -->



Patch:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 89a4da6d..178a79fa 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -16,6 +16,7 @@ jobs:
         uses: actions/setup-node@v4
         with:
           node-version-file: '.nvmrc'
+          cache: 'npm'
 
       - name: Install dependencies
         run: npm ci
@@ -24,12 +25,6 @@ jobs:
         run: |
           npm run build chrome
 
-      - name: Upload the extension as an artifact
-        uses: actions/upload-artifact@v3
-        with:
-          name: web-scrobbler-chrome
-          path: build/chrome
-
   build-firefox:
     name: Build the extension for Firefox
     runs-on: ubuntu-latest
@@ -45,6 +40,7 @@ jobs:
         uses: actions/setup-node@v4
         with:
           node-version-file: '.nvmrc'
+          cache: 'npm'
 
       - name: Install dependencies
         run: npm ci
@@ -53,12 +49,6 @@ jobs:
         run: |
           npm run build firefox
 
-      - name: Upload the extension as an artifact
-        uses: actions/upload-artifact@v3
-        with:
-          name: web-scrobbler-firefox
-          path: build/firefox
-
   build-safari:
     name: Build the extension for Safari
     runs-on: macos-latest
@@ -70,6 +60,7 @@ jobs:
         uses: actions/setup-node@v4
         with:
           node-version-file: '.nvmrc'
+          cache: 'npm'
 
       - name: Install dependencies
         run: npm ci
@@ -77,9 +68,3 @@ jobs:
       - name: Build the extension
         run: |
           npm run build safari
-
-      - name: Upload the extension as an artifact
-        uses: actions/upload-artifact@v3
-        with:
-          name: web-scrobbler-safari
-          path: build/safari
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 019a6ca1..ebda7f00 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -17,6 +17,7 @@ jobs:
         uses: actions/setup-node@v4
         with:
           node-version-file: '.nvmrc'
+          cache: 'npm'
 
       - name: Install dependencies
         run: npm ci
@@ -44,6 +45,7 @@ jobs:
         uses: actions/setup-node@v4
         with:
           node-version-file: '.nvmrc'
+          cache: 'npm'
 
       - name: Install dependencies
         run: npm ci
@@ -71,6 +73,7 @@ jobs:
         uses: actions/setup-node@v4
         with:
           node-version-file: '.nvmrc'
+          cache: 'npm'
 
       - name: Install dependencies
         run: npm ci
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index cda1d244..48ad0529 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -16,6 +16,7 @@ jobs:
         uses: actions/setup-node@v4
         with:
           node-version-file: '.nvmrc'
+          cache: 'npm'
 
       - name: Install dependencies
         run: npm ci
diff --git a/.github/workflows/release-extension.yml b/.github/workflows/release-extension.yml
index 6f2d73b4..01b36f31 100644
--- a/.github/workflows/release-extension.yml
+++ b/.github/workflows/release-extension.yml
@@ -20,16 +20,21 @@ jobs:
         uses: actions/checkout@v4
         with:
           token: ${{ secrets.BOT_PAT }}
+
       - name: Setup git config
         run: |
           git config user.name "web-scrobbler-bot"
           git config user.email "${{ secrets.BOT_EMAIL }}"
+
       - name: Setup Node.js
         uses: actions/setup-node@v4
         with:
           node-version-file: '.nvmrc'
+          cache: 'npm'
+
       - name: Install dependencies
         run: npm ci
+
       - name: Release
         run: ./release-extension.sh ${{ inputs.release_type }}
         env:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 75911052..9ff4ec70 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -18,6 +18,7 @@ jobs:
         uses: actions/setup-node@v4
         with:
           node-version-file: '.nvmrc'
+          cache: 'npm'
 
       - name: Install dependencies
         run: npm ci
diff --git a/.nvmrc b/.nvmrc
index f3f52b42..d5a15960 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1,1 +1,1 @@
-20.9.0
+20.10.0
diff --git a/package-lock.json b/package-lock.json
index 4c74f99f..ee616605 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,38 +1,38 @@
 {
   "name": "web-scrobbler",
-  "version": "3.2.0",
+  "version": "3.3.0",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "web-scrobbler",
-      "version": "3.2.0",
+      "version": "3.3.0",
       "license": "MIT",
       "dependencies": {
         "@suid/icons-material": "0.6.11",
         "@web-scrobbler/metadata-filter": "3.0.2",
         "blueimp-md5": "2.19.0",
         "stylelint": "15.11.0",
-        "vite": "4.5.0",
+        "vite": "4.5.1",
         "webextension-polyfill": "0.10.0"
       },
       "devDependencies": {
-        "@types/archiver": "6.0.0",
-        "@types/blueimp-md5": "2.18.0",
+        "@types/archiver": "6.0.2",
+        "@types/blueimp-md5": "2.18.2",
         "@types/fs-extra": "11.0.3",
-        "@types/imagemin": "8.0.2",
-        "@types/imagemin-jpegtran": "5.0.2",
-        "@types/node": "20.8.10",
-        "@types/react": "18.2.34",
+        "@types/imagemin": "8.0.5",
+        "@types/imagemin-jpegtran": "5.0.4",
+        "@types/node": "20.10.4",
+        "@types/react": "18.2.43",
         "@types/webextension-polyfill": "0.10.4",
-        "@typescript-eslint/eslint-plugin": "6.7.5",
+        "@typescript-eslint/eslint-plugin": "6.13.2",
         "@web-scrobbler/eslint-config": "3.0.0",
         "@web-scrobbler/stylelint-config": "1.0.1",
         "archiver": "6.0.1",
         "canvas": "2.11.2",
         "chokidar": "3.5.3",
         "esbuild": "0.19.5",
-        "eslint": "8.51.0",
+        "eslint": "8.55.0",
         "eslint-plugin-react": "7.33.2",
         "eslint-plugin-solid": "0.13.0",
         "eslint-plugin-tsdoc": "0.2.17",
@@ -48,10 +48,10 @@
         "remark-preset-lint-recommended": "6.1.3",
         "sass": "1.69.5",
         "solid-js": "1.8.5",
-        "tsx": "3.14.0",
+        "tsx": "4.6.2",
         "typescript": "5.2.2",
         "vite-plugin-conditional-compiler": "0.2.1",
-        "vite-plugin-solid": "2.7.1",
+        "vite-plugin-solid": "2.8.0",
         "vite-plugin-static-copy": "0.17.0",
         "vitest": "0.34.6"
       },
@@ -82,11 +82,11 @@
       }
     },
     "node_modules/@babel/code-frame": {
-      "version": "7.22.13",
-      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
-      "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
+      "version": "7.23.5",
+      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz",
+      "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==",
       "dependencies": {
-        "@babel/highlight": "^7.22.13",
+        "@babel/highlight": "^7.23.4",
         "chalk": "^2.4.2"
       },
       "engines": {
@@ -158,30 +158,30 @@
       }
     },
     "node_modules/@babel/compat-data": {
-      "version": "7.22.20",
-      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.20.tgz",
-      "integrity": "sha512-BQYjKbpXjoXwFW5jGqiizJQQT/aC7pFm9Ok1OWssonuguICi264lbgMzRp2ZMmRSlfkX6DsWDDcsrctK8Rwfiw==",
+      "version": "7.23.5",
+      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz",
+      "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==",
       "dev": true,
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/core": {
-      "version": "7.23.0",
-      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.0.tgz",
-      "integrity": "sha512-97z/ju/Jy1rZmDxybphrBuI+jtJjFVoz7Mr9yUQVVVi+DNZE333uFQeMOqcCIy1x3WYBIbWftUSLmbNXNT7qFQ==",
+      "version": "7.23.5",
+      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.5.tgz",
+      "integrity": "sha512-Cwc2XjUrG4ilcfOw4wBAK+enbdgwAcAJCfGUItPBKR7Mjw4aEfAFYrLxeRp4jWgtNIKn3n2AlBOfwwafl+42/g==",
       "dev": true,
       "dependencies": {
         "@ampproject/remapping": "^2.2.0",
-        "@babel/code-frame": "^7.22.13",
-        "@babel/generator": "^7.23.0",
+        "@babel/code-frame": "^7.23.5",
+        "@babel/generator": "^7.23.5",
         "@babel/helper-compilation-targets": "^7.22.15",
-        "@babel/helper-module-transforms": "^7.23.0",
-        "@babel/helpers": "^7.23.0",
-        "@babel/parser": "^7.23.0",
+        "@babel/helper-module-transforms": "^7.23.3",
+        "@babel/helpers": "^7.23.5",
+        "@babel/parser": "^7.23.5",
         "@babel/template": "^7.22.15",
-        "@babel/traverse": "^7.23.0",
-        "@babel/types": "^7.23.0",
+        "@babel/traverse": "^7.23.5",
+        "@babel/types": "^7.23.5",
         "convert-source-map": "^2.0.0",
         "debug": "^4.1.0",
         "gensync": "^1.0.0-beta.2",
@@ -206,12 +206,12 @@
       }
     },
     "node_modules/@babel/generator": {
-      "version": "7.23.0",
-      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz",
-      "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==",
+      "version": "7.23.5",
+      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.5.tgz",
+      "integrity": "sha512-BPssCHrBD+0YrxviOa3QzpqwhNIXKEtOa2jQrm4FlmkC2apYgRnQcmPWiGZDlGxiNtltnUFolMe8497Esry+jA==",
       "dev": true,
       "dependencies": {
-        "@babel/types": "^7.23.0",
+        "@babel/types": "^7.23.5",
         "@jridgewell/gen-mapping": "^0.3.2",
         "@jridgewell/trace-mapping": "^0.3.17",
         "jsesc": "^2.5.1"
@@ -273,17 +273,17 @@
       "dev": true
     },
     "node_modules/@babel/helper-create-class-features-plugin": {
-      "version": "7.22.15",
-      "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.15.tgz",
-      "integrity": "sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg==",
+      "version": "7.23.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.5.tgz",
+      "integrity": "sha512-QELlRWxSpgdwdJzSJn4WAhKC+hvw/AtHbbrIoncKHkhKKR/luAlKkgBDcri1EzWAo8f8VvYVryEHN4tax/V67A==",
       "dev": true,
       "dependencies": {
         "@babel/helper-annotate-as-pure": "^7.22.5",
-        "@babel/helper-environment-visitor": "^7.22.5",
-        "@babel/helper-function-name": "^7.22.5",
-        "@babel/helper-member-expression-to-functions": "^7.22.15",
+        "@babel/helper-environment-visitor": "^7.22.20",
+        "@babel/helper-function-name": "^7.23.0",
+        "@babel/helper-member-expression-to-functions": "^7.23.0",
         "@babel/helper-optimise-call-expression": "^7.22.5",
-        "@babel/helper-replace-supers": "^7.22.9",
+        "@babel/helper-replace-supers": "^7.22.20",
         "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
         "@babel/helper-split-export-declaration": "^7.22.6",
         "semver": "^6.3.1"
@@ -363,9 +363,9 @@
       }
     },
     "node_modules/@babel/helper-module-transforms": {
-      "version": "7.23.0",
-      "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz",
-      "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==",
+      "version": "7.23.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz",
+      "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==",
       "dev": true,
       "dependencies": {
         "@babel/helper-environment-visitor": "^7.22.20",
@@ -456,9 +456,9 @@
       }
     },
     "node_modules/@babel/helper-string-parser": {
-      "version": "7.22.5",
-      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
-      "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==",
+      "version": "7.23.4",
+      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz",
+      "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==",
       "dev": true,
       "engines": {
         "node": ">=6.9.0"
@@ -473,32 +473,32 @@
       }
     },
     "node_modules/@babel/helper-validator-option": {
-      "version": "7.22.15",
-      "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz",
-      "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==",
+      "version": "7.23.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz",
+      "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==",
       "dev": true,
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helpers": {
-      "version": "7.23.1",
-      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.1.tgz",
-      "integrity": "sha512-chNpneuK18yW5Oxsr+t553UZzzAs3aZnFm4bxhebsNTeshrC95yA7l5yl7GBAG+JG1rF0F7zzD2EixK9mWSDoA==",
+      "version": "7.23.5",
+      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.5.tgz",
+      "integrity": "sha512-oO7us8FzTEsG3U6ag9MfdF1iA/7Z6dz+MtFhifZk8C8o453rGJFFWUP1t+ULM9TUIAzC9uxXEiXjOiVMyd7QPg==",
       "dev": true,
       "dependencies": {
         "@babel/template": "^7.22.15",
-        "@babel/traverse": "^7.23.0",
-        "@babel/types": "^7.23.0"
+        "@babel/traverse": "^7.23.5",
+        "@babel/types": "^7.23.5"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/highlight": {
-      "version": "7.22.20",
-      "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
-      "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
+      "version": "7.23.4",
+      "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz",
+      "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==",
       "dependencies": {
         "@babel/helper-validator-identifier": "^7.22.20",
         "chalk": "^2.4.2",
@@ -573,9 +573,9 @@
       }
     },
     "node_modules/@babel/parser": {
-      "version": "7.23.0",
-      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
-      "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
+      "version": "7.23.5",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.5.tgz",
+      "integrity": "sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ==",
       "dev": true,
       "bin": {
         "parser": "bin/babel-parser.js"
@@ -585,9 +585,9 @@
       }
     },
     "node_modules/@babel/plugin-syntax-jsx": {
-      "version": "7.22.5",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz",
-      "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==",
+      "version": "7.23.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz",
+      "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==",
       "dev": true,
       "dependencies": {
         "@babel/helper-plugin-utils": "^7.22.5"
@@ -600,9 +600,9 @@
       }
     },
     "node_modules/@babel/plugin-syntax-typescript": {
-      "version": "7.22.5",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz",
-      "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==",
+      "version": "7.23.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz",
+      "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==",
       "dev": true,
       "dependencies": {
         "@babel/helper-plugin-utils": "^7.22.5"
@@ -615,12 +615,12 @@
       }
     },
     "node_modules/@babel/plugin-transform-modules-commonjs": {
-      "version": "7.23.0",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.0.tgz",
-      "integrity": "sha512-32Xzss14/UVc7k9g775yMIvkVK8xwKE0DPdP5JTapr3+Z9w4tzeOuLNY6BXDQR6BdnzIlXnCGAzsk/ICHBLVWQ==",
+      "version": "7.23.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz",
+      "integrity": "sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==",
       "dev": true,
       "dependencies": {
-        "@babel/helper-module-transforms": "^7.23.0",
+        "@babel/helper-module-transforms": "^7.23.3",
         "@babel/helper-plugin-utils": "^7.22.5",
         "@babel/helper-simple-access": "^7.22.5"
       },
@@ -632,15 +632,15 @@
       }
     },
     "node_modules/@babel/plugin-transform-typescript": {
-      "version": "7.22.15",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.15.tgz",
-      "integrity": "sha512-1uirS0TnijxvQLnlv5wQBwOX3E1wCFX7ITv+9pBV2wKEk4K+M5tqDaoNXnTH8tjEIYHLO98MwiTWO04Ggz4XuA==",
+      "version": "7.23.5",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.23.5.tgz",
+      "integrity": "sha512-2fMkXEJkrmwgu2Bsv1Saxgj30IXZdJ+84lQcKKI7sm719oXs0BBw2ZENKdJdR1PjWndgLCEBNXJOri0fk7RYQA==",
       "dev": true,
       "dependencies": {
         "@babel/helper-annotate-as-pure": "^7.22.5",
-        "@babel/helper-create-class-features-plugin": "^7.22.15",
+        "@babel/helper-create-class-features-plugin": "^7.23.5",
         "@babel/helper-plugin-utils": "^7.22.5",
-        "@babel/plugin-syntax-typescript": "^7.22.5"
+        "@babel/plugin-syntax-typescript": "^7.23.3"
       },
       "engines": {
         "node": ">=6.9.0"
@@ -650,16 +650,16 @@
       }
     },
     "node_modules/@babel/preset-typescript": {
-      "version": "7.23.0",
-      "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.23.0.tgz",
-      "integrity": "sha512-6P6VVa/NM/VlAYj5s2Aq/gdVg8FSENCg3wlZ6Qau9AcPaoF5LbN1nyGlR9DTRIw9PpxI94e+ReydsJHcjwAweg==",
+      "version": "7.23.3",
+      "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.23.3.tgz",
+      "integrity": "sha512-17oIGVlqz6CchO9RFYn5U6ZpWRZIngayYCtrPRSgANSwC2V1Jb+iP74nVxzzXJte8b8BYxrL1yY96xfhTBrNNQ==",
       "dev": true,
       "dependencies": {
         "@babel/helper-plugin-utils": "^7.22.5",
         "@babel/helper-validator-option": "^7.22.15",
-        "@babel/plugin-syntax-jsx": "^7.22.5",
-        "@babel/plugin-transform-modules-commonjs": "^7.23.0",
-        "@babel/plugin-transform-typescript": "^7.22.15"
+        "@babel/plugin-syntax-jsx": "^7.23.3",
+        "@babel/plugin-transform-modules-commonjs": "^7.23.3",
+        "@babel/plugin-transform-typescript": "^7.23.3"
       },
       "engines": {
         "node": ">=6.9.0"
@@ -696,19 +696,19 @@
       }
     },
     "node_modules/@babel/traverse": {
-      "version": "7.23.2",
-      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz",
-      "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==",
+      "version": "7.23.5",
+      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.5.tgz",
+      "integrity": "sha512-czx7Xy5a6sapWWRx61m1Ke1Ra4vczu1mCTtJam5zRTBOonfdJ+S/B6HYmGYu3fJtr8GGET3si6IhgWVBhJ/m8w==",
       "dev": true,
       "dependencies": {
-        "@babel/code-frame": "^7.22.13",
-        "@babel/generator": "^7.23.0",
+        "@babel/code-frame": "^7.23.5",
+        "@babel/generator": "^7.23.5",
         "@babel/helper-environment-visitor": "^7.22.20",
         "@babel/helper-function-name": "^7.23.0",
         "@babel/helper-hoist-variables": "^7.22.5",
         "@babel/helper-split-export-declaration": "^7.22.6",
-        "@babel/parser": "^7.23.0",
-        "@babel/types": "^7.23.0",
+        "@babel/parser": "^7.23.5",
+        "@babel/types": "^7.23.5",
         "debug": "^4.1.0",
         "globals": "^11.1.0"
       },
@@ -726,12 +726,12 @@
       }
     },
     "node_modules/@babel/types": {
-      "version": "7.23.0",
-      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
-      "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
+      "version": "7.23.5",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.5.tgz",
+      "integrity": "sha512-ON5kSOJwVO6xXVRTvOI0eOnWe7VdUcIpsovGo9U/Br4Ie4UVFQTboO2cYnDhAGU6Fp+UxSiT+pMft0SMHfuq6w==",
       "dev": true,
       "dependencies": {
-        "@babel/helper-string-parser": "^7.22.5",
+        "@babel/helper-string-parser": "^7.23.4",
         "@babel/helper-validator-identifier": "^7.22.20",
         "to-fast-properties": "^2.0.0"
       },
@@ -1213,9 +1213,9 @@
       }
     },
     "node_modules/@eslint/eslintrc": {
-      "version": "2.1.2",
-      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz",
-      "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==",
+      "version": "2.1.4",
+      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
+      "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
       "dev": true,
       "dependencies": {
         "ajv": "^6.12.4",
@@ -1236,21 +1236,21 @@
       }
     },
     "node_modules/@eslint/js": {
-      "version": "8.51.0",
-      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.51.0.tgz",
-      "integrity": "sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==",
+      "version": "8.55.0",
+      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.55.0.tgz",
+      "integrity": "sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==",
       "dev": true,
       "engines": {
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
       }
     },
     "node_modules/@humanwhocodes/config-array": {
-      "version": "0.11.11",
-      "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz",
-      "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==",
+      "version": "0.11.13",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz",
+      "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==",
       "dev": true,
       "dependencies": {
-        "@humanwhocodes/object-schema": "^1.2.1",
+        "@humanwhocodes/object-schema": "^2.0.1",
         "debug": "^4.1.1",
         "minimatch": "^3.0.5"
       },
@@ -1272,9 +1272,9 @@
       }
     },
     "node_modules/@humanwhocodes/object-schema": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
-      "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz",
+      "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==",
       "dev": true
     },
     "node_modules/@isaacs/cliui": {
@@ -1700,18 +1700,18 @@
       "dev": true
     },
     "node_modules/@types/archiver": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-6.0.0.tgz",
-      "integrity": "sha512-I1bHy/iSj8/u552lUjnOMG7rUhVkcbGIoNOj7CB/EMah1In+oQAwVZjw2mTI6qx04e5lF2V0JH/1w78jm/Dejw==",
+      "version": "6.0.2",
+      "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-6.0.2.tgz",
+      "integrity": "sha512-KmROQqbQzKGuaAbmK+ZcytkJ51+YqDa7NmbXjmtC5YBLSyQYo21YaUnQ3HbaPFKL1ooo6RQ6OPYPIDyxfpDDXw==",
       "dev": true,
       "dependencies": {
         "@types/readdir-glob": "*"
       }
     },
     "node_modules/@types/babel__core": {
-      "version": "7.20.2",
-      "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.2.tgz",
-      "integrity": "sha512-pNpr1T1xLUc2l3xJKuPtsEky3ybxN3m4fJkknfIpTCTfIZCDW57oAg+EfCgIIp2rvCe0Wn++/FfodDS4YXxBwA==",
+      "version": "7.20.5",
+      "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+      "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
       "dev": true,
       "dependencies": {
         "@babel/parser": "^7.20.7",
@@ -1722,18 +1722,18 @@
       }
     },
     "node_modules/@types/babel__generator": {
-      "version": "7.6.5",
-      "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.5.tgz",
-      "integrity": "sha512-h9yIuWbJKdOPLJTbmSpPzkF67e659PbQDba7ifWm5BJ8xTv+sDmS7rFmywkWOvXedGTivCdeGSIIX8WLcRTz8w==",
+      "version": "7.6.7",
+      "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.7.tgz",
+      "integrity": "sha512-6Sfsq+EaaLrw4RmdFWE9Onp63TOUue71AWb4Gpa6JxzgTYtimbM086WnYTy2U67AofR++QKCo08ZP6pwx8YFHQ==",
       "dev": true,
       "dependencies": {
         "@babel/types": "^7.0.0"
       }
     },
     "node_modules/@types/babel__template": {
-      "version": "7.4.2",
-      "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.2.tgz",
-      "integrity": "sha512-/AVzPICMhMOMYoSx9MoKpGDKdBRsIXMNByh1PXSZoa+v6ZoLa8xxtsT/uLQ/NJm0XVAWl/BvId4MlDeXJaeIZQ==",
+      "version": "7.4.4",
+      "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+      "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
       "dev": true,
       "dependencies": {
         "@babel/parser": "^7.1.0",
@@ -1741,18 +1741,18 @@
       }
     },
     "node_modules/@types/babel__traverse": {
-      "version": "7.20.2",
-      "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.2.tgz",
-      "integrity": "sha512-ojlGK1Hsfce93J0+kn3H5R73elidKUaZonirN33GSmgTUMpzI/MIFfSpF3haANe3G1bEBS9/9/QEqwTzwqFsKw==",
+      "version": "7.20.4",
+      "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.4.tgz",
+      "integrity": "sha512-mSM/iKUk5fDDrEV/e83qY+Cr3I1+Q3qqTuEn++HAWYjEa1+NxZr6CNrcJGf2ZTnq4HoFGC3zaTPZTobCzCFukA==",
       "dev": true,
       "dependencies": {
         "@babel/types": "^7.20.7"
       }
     },
     "node_modules/@types/blueimp-md5": {
-      "version": "2.18.0",
-      "resolved": "https://registry.npmjs.org/@types/blueimp-md5/-/blueimp-md5-2.18.0.tgz",
-      "integrity": "sha512-f4A+++lGZGJvVSgeyMkqA7BEf2BVQli6F+qEykKb49c5ieWQBkfpn6CP5c1IZr2Yi2Ofl6Fj+v0e1fN18Z8Cnw==",
+      "version": "2.18.2",
+      "resolved": "https://registry.npmjs.org/@types/blueimp-md5/-/blueimp-md5-2.18.2.tgz",
+      "integrity": "sha512-dJ9yRry9Olt5GAWlgCtE5dK9d/Dfhn/V7hna86eEO2Pn76+E8Y0S0n61iEUEGhWXXgtKtHxtZLVNwL8X+vLHzg==",
       "dev": true
     },
     "node_modules/@types/chai": {
@@ -1823,18 +1823,18 @@
       }
     },
     "node_modules/@types/imagemin": {
-      "version": "8.0.2",
-      "resolved": "https://registry.npmjs.org/@types/imagemin/-/imagemin-8.0.2.tgz",
-      "integrity": "sha512-RcyM00BFTQQ5oFv88APJu4VFnm6xwtLEUiy2Z+lM8lay2NVwVAFetr1Koop1DNnwX+ZGKUgU4v8eZS8MsBEttg==",
+      "version": "8.0.5",
+      "resolved": "https://registry.npmjs.org/@types/imagemin/-/imagemin-8.0.5.tgz",
+      "integrity": "sha512-tah3dm+5sG+fEDAz6CrQ5evuEaPX9K6DF3E5a01MPOKhA2oGBoC+oA5EJzSugB905sN4DE19EDzldT2Cld2g6Q==",
       "dev": true,
       "dependencies": {
         "@types/node": "*"
       }
     },
     "node_modules/@types/imagemin-jpegtran": {
-      "version": "5.0.2",
-      "resolved": "https://registry.npmjs.org/@types/imagemin-jpegtran/-/imagemin-jpegtran-5.0.2.tgz",
-      "integrity": "sha512-Ov/wC42m+1Hgui9h4aXflLRUE+Sloqb8W2xQ0IF2oLfOVulGTwB7cgjDsdwIPvSnmsYIVJ0t5IYCmmURwlnFHA==",
+      "version": "5.0.4",
+      "resolved": "https://registry.npmjs.org/@types/imagemin-jpegtran/-/imagemin-jpegtran-5.0.4.tgz",
+      "integrity": "sha512-PSMxOeJa8q94Y+qx8Yriw+qj1+vH5xWpvar63o6SGO0Xi5RlKuwHHfJmN2GRUngPrlhe394jOUmpVq8jQlVmFA==",
       "dev": true,
       "dependencies": {
         "@types/imagemin": "*"
@@ -1882,9 +1882,9 @@
       "dev": true
     },
     "node_modules/@types/node": {
-      "version": "20.8.10",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.10.tgz",
-      "integrity": "sha512-TlgT8JntpcbmKUFzjhsyhGfP2fsiz1Mv56im6enJ905xG1DAYesxJaeSbGqQmAw8OWPdhyJGhGSQGKRNJ45u9w==",
+      "version": "20.10.4",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.4.tgz",
+      "integrity": "sha512-D08YG6rr8X90YB56tSIuBaddy/UXAA9RKJoFvrsnogAum/0pmjkgi4+2nx96A330FmioegBWmEYQ+syqCFaveg==",
       "devOptional": true,
       "dependencies": {
         "undici-types": "~5.26.4"
@@ -1902,9 +1902,9 @@
       "dev": true
     },
     "node_modules/@types/react": {
-      "version": "18.2.34",
-      "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.34.tgz",
-      "integrity": "sha512-U6eW/alrRk37FU/MS2RYMjx0Va2JGIVXELTODaTIYgvWGCV4Y4TfTUzG8DdmpDNIT0Xpj/R7GfyHOJJrDttcvg==",
+      "version": "18.2.43",
+      "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.43.tgz",
+      "integrity": "sha512-nvOV01ZdBdd/KW6FahSbcNplt2jCJfyWdTos61RYHV+FVv5L/g9AOX1bmbVcWcLFL8+KHQfh1zVIQrud6ihyQA==",
       "dev": true,
       "dependencies": {
         "@types/prop-types": "*",
@@ -1958,16 +1958,16 @@
       "dev": true
     },
     "node_modules/@typescript-eslint/eslint-plugin": {
-      "version": "6.7.5",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.5.tgz",
-      "integrity": "sha512-JhtAwTRhOUcP96D0Y6KYnwig/MRQbOoLGXTON2+LlyB/N35SP9j1boai2zzwXb7ypKELXMx3DVk9UTaEq1vHEw==",
+      "version": "6.13.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.13.2.tgz",
+      "integrity": "sha512-3+9OGAWHhk4O1LlcwLBONbdXsAhLjyCFogJY/cWy2lxdVJ2JrcTF2pTGMaLl2AE7U1l31n8Py4a8bx5DLf/0dQ==",
       "dev": true,
       "dependencies": {
         "@eslint-community/regexpp": "^4.5.1",
-        "@typescript-eslint/scope-manager": "6.7.5",
-        "@typescript-eslint/type-utils": "6.7.5",
-        "@typescript-eslint/utils": "6.7.5",
-        "@typescript-eslint/visitor-keys": "6.7.5",
+        "@typescript-eslint/scope-manager": "6.13.2",
+        "@typescript-eslint/type-utils": "6.13.2",
+        "@typescript-eslint/utils": "6.13.2",
+        "@typescript-eslint/visitor-keys": "6.13.2",
         "debug": "^4.3.4",
         "graphemer": "^1.4.0",
         "ignore": "^5.2.4",
@@ -1993,13 +1993,13 @@
       }
     },
     "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": {
-      "version": "6.7.5",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.7.5.tgz",
-      "integrity": "sha512-GAlk3eQIwWOJeb9F7MKQ6Jbah/vx1zETSDw8likab/eFcqkjSD7BI75SDAeC5N2L0MmConMoPvTsmkrg71+B1A==",
+      "version": "6.13.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.13.2.tgz",
+      "integrity": "sha512-CXQA0xo7z6x13FeDYCgBkjWzNqzBn8RXaE3QVQVIUm74fWJLkJkaHmHdKStrxQllGh6Q4eUGyNpMe0b1hMkXFA==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "6.7.5",
-        "@typescript-eslint/visitor-keys": "6.7.5"
+        "@typescript-eslint/types": "6.13.2",
+        "@typescript-eslint/visitor-keys": "6.13.2"
       },
       "engines": {
         "node": "^16.0.0 || >=18.0.0"
@@ -2010,9 +2010,9 @@
       }
     },
     "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": {
-      "version": "6.7.5",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.5.tgz",
-      "integrity": "sha512-WboQBlOXtdj1tDFPyIthpKrUb+kZf2VroLZhxKa/VlwLlLyqv/PwUNgL30BlTVZV1Wu4Asu2mMYPqarSO4L5ZQ==",
+      "version": "6.13.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.13.2.tgz",
+      "integrity": "sha512-7sxbQ+EMRubQc3wTfTsycgYpSujyVbI1xw+3UMRUcrhSy+pN09y/lWzeKDbvhoqcRbHdc+APLs/PWYi/cisLPg==",
       "dev": true,
       "engines": {
         "node": "^16.0.0 || >=18.0.0"
@@ -2023,12 +2023,12 @@
       }
     },
     "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": {
-      "version": "6.7.5",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.5.tgz",
-      "integrity": "sha512-3MaWdDZtLlsexZzDSdQWsFQ9l9nL8B80Z4fImSpyllFC/KLqWQRdEcB+gGGO+N3Q2uL40EsG66wZLsohPxNXvg==",
+      "version": "6.13.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.2.tgz",
+      "integrity": "sha512-OGznFs0eAQXJsp+xSd6k/O1UbFi/K/L7WjqeRoFE7vadjAF9y0uppXhYNQNEqygjou782maGClOoZwPqF0Drlw==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "6.7.5",
+        "@typescript-eslint/types": "6.13.2",
         "eslint-visitor-keys": "^3.4.1"
       },
       "engines": {
@@ -2087,13 +2087,13 @@
       }
     },
     "node_modules/@typescript-eslint/type-utils": {
-      "version": "6.7.5",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.7.5.tgz",
-      "integrity": "sha512-Gs0qos5wqxnQrvpYv+pf3XfcRXW6jiAn9zE/K+DlmYf6FcpxeNYN0AIETaPR7rHO4K2UY+D0CIbDP9Ut0U4m1g==",
+      "version": "6.13.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.13.2.tgz",
+      "integrity": "sha512-Qr6ssS1GFongzH2qfnWKkAQmMUyZSyOr0W54nZNU1MDfo+U4Mv3XveeLZzadc/yq8iYhQZHYT+eoXJqnACM1tw==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/typescript-estree": "6.7.5",
-        "@typescript-eslint/utils": "6.7.5",
+        "@typescript-eslint/typescript-estree": "6.13.2",
+        "@typescript-eslint/utils": "6.13.2",
         "debug": "^4.3.4",
         "ts-api-utils": "^1.0.1"
       },
@@ -2114,9 +2114,9 @@
       }
     },
     "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": {
-      "version": "6.7.5",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.5.tgz",
-      "integrity": "sha512-WboQBlOXtdj1tDFPyIthpKrUb+kZf2VroLZhxKa/VlwLlLyqv/PwUNgL30BlTVZV1Wu4Asu2mMYPqarSO4L5ZQ==",
+      "version": "6.13.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.13.2.tgz",
+      "integrity": "sha512-7sxbQ+EMRubQc3wTfTsycgYpSujyVbI1xw+3UMRUcrhSy+pN09y/lWzeKDbvhoqcRbHdc+APLs/PWYi/cisLPg==",
       "dev": true,
       "engines": {
         "node": "^16.0.0 || >=18.0.0"
@@ -2127,13 +2127,13 @@
       }
     },
     "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": {
-      "version": "6.7.5",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.5.tgz",
-      "integrity": "sha512-NhJiJ4KdtwBIxrKl0BqG1Ur+uw7FiOnOThcYx9DpOGJ/Abc9z2xNzLeirCG02Ig3vkvrc2qFLmYSSsaITbKjlg==",
+      "version": "6.13.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.13.2.tgz",
+      "integrity": "sha512-SuD8YLQv6WHnOEtKv8D6HZUzOub855cfPnPMKvdM/Bh1plv1f7Q/0iFUDLKKlxHcEstQnaUU4QZskgQq74t+3w==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "6.7.5",
-        "@typescript-eslint/visitor-keys": "6.7.5",
+        "@typescript-eslint/types": "6.13.2",
+        "@typescript-eslint/visitor-keys": "6.13.2",
         "debug": "^4.3.4",
         "globby": "^11.1.0",
         "is-glob": "^4.0.3",
@@ -2154,12 +2154,12 @@
       }
     },
     "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": {
-      "version": "6.7.5",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.5.tgz",
-      "integrity": "sha512-3MaWdDZtLlsexZzDSdQWsFQ9l9nL8B80Z4fImSpyllFC/KLqWQRdEcB+gGGO+N3Q2uL40EsG66wZLsohPxNXvg==",
+      "version": "6.13.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.2.tgz",
+      "integrity": "sha512-OGznFs0eAQXJsp+xSd6k/O1UbFi/K/L7WjqeRoFE7vadjAF9y0uppXhYNQNEqygjou782maGClOoZwPqF0Drlw==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "6.7.5",
+        "@typescript-eslint/types": "6.13.2",
         "eslint-visitor-keys": "^3.4.1"
       },
       "engines": {
@@ -2213,17 +2213,17 @@
       }
     },
     "node_modules/@typescript-eslint/utils": {
-      "version": "6.7.5",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.7.5.tgz",
-      "integrity": "sha512-pfRRrH20thJbzPPlPc4j0UNGvH1PjPlhlCMq4Yx7EGjV7lvEeGX0U6MJYe8+SyFutWgSHsdbJ3BXzZccYggezA==",
+      "version": "6.13.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.13.2.tgz",
+      "integrity": "sha512-b9Ptq4eAZUym4idijCRzl61oPCwwREcfDI8xGk751Vhzig5fFZR9CyzDz4Sp/nxSLBYxUPyh4QdIDqWykFhNmQ==",
       "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.7.5",
-        "@typescript-eslint/types": "6.7.5",
-        "@typescript-eslint/typescript-estree": "6.7.5",
+        "@typescript-eslint/scope-manager": "6.13.2",
+        "@typescript-eslint/types": "6.13.2",
+        "@typescript-eslint/typescript-estree": "6.13.2",
         "semver": "^7.5.4"
       },
       "engines": {
@@ -2238,13 +2238,13 @@
       }
     },
     "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": {
-      "version": "6.7.5",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.7.5.tgz",
-      "integrity": "sha512-GAlk3eQIwWOJeb9F7MKQ6Jbah/vx1zETSDw8likab/eFcqkjSD7BI75SDAeC5N2L0MmConMoPvTsmkrg71+B1A==",
+      "version": "6.13.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.13.2.tgz",
+      "integrity": "sha512-CXQA0xo7z6x13FeDYCgBkjWzNqzBn8RXaE3QVQVIUm74fWJLkJkaHmHdKStrxQllGh6Q4eUGyNpMe0b1hMkXFA==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "6.7.5",
-        "@typescript-eslint/visitor-keys": "6.7.5"
+        "@typescript-eslint/types": "6.13.2",
+        "@typescript-eslint/visitor-keys": "6.13.2"
       },
       "engines": {
         "node": "^16.0.0 || >=18.0.0"
@@ -2255,9 +2255,9 @@
       }
     },
     "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": {
-      "version": "6.7.5",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.5.tgz",
-      "integrity": "sha512-WboQBlOXtdj1tDFPyIthpKrUb+kZf2VroLZhxKa/VlwLlLyqv/PwUNgL30BlTVZV1Wu4Asu2mMYPqarSO4L5ZQ==",
+      "version": "6.13.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.13.2.tgz",
+      "integrity": "sha512-7sxbQ+EMRubQc3wTfTsycgYpSujyVbI1xw+3UMRUcrhSy+pN09y/lWzeKDbvhoqcRbHdc+APLs/PWYi/cisLPg==",
       "dev": true,
       "engines": {
         "node": "^16.0.0 || >=18.0.0"
@@ -2268,13 +2268,13 @@
       }
     },
     "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": {
-      "version": "6.7.5",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.5.tgz",
-      "integrity": "sha512-NhJiJ4KdtwBIxrKl0BqG1Ur+uw7FiOnOThcYx9DpOGJ/Abc9z2xNzLeirCG02Ig3vkvrc2qFLmYSSsaITbKjlg==",
+      "version": "6.13.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.13.2.tgz",
+      "integrity": "sha512-SuD8YLQv6WHnOEtKv8D6HZUzOub855cfPnPMKvdM/Bh1plv1f7Q/0iFUDLKKlxHcEstQnaUU4QZskgQq74t+3w==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "6.7.5",
-        "@typescript-eslint/visitor-keys": "6.7.5",
+        "@typescript-eslint/types": "6.13.2",
+        "@typescript-eslint/visitor-keys": "6.13.2",
         "debug": "^4.3.4",
         "globby": "^11.1.0",
         "is-glob": "^4.0.3",
@@ -2295,12 +2295,12 @@
       }
     },
     "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": {
-      "version": "6.7.5",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.5.tgz",
-      "integrity": "sha512-3MaWdDZtLlsexZzDSdQWsFQ9l9nL8B80Z4fImSpyllFC/KLqWQRdEcB+gGGO+N3Q2uL40EsG66wZLsohPxNXvg==",
+      "version": "6.13.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.2.tgz",
+      "integrity": "sha512-OGznFs0eAQXJsp+xSd6k/O1UbFi/K/L7WjqeRoFE7vadjAF9y0uppXhYNQNEqygjou782maGClOoZwPqF0Drlw==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "6.7.5",
+        "@typescript-eslint/types": "6.13.2",
         "eslint-visitor-keys": "^3.4.1"
       },
       "engines": {
@@ -2899,9 +2899,9 @@
       "dev": true
     },
     "node_modules/babel-plugin-jsx-dom-expressions": {
-      "version": "0.37.2",
-      "resolved": "https://registry.npmjs.org/babel-plugin-jsx-dom-expressions/-/babel-plugin-jsx-dom-expressions-0.37.2.tgz",
-      "integrity": "sha512-u3VKB+On86cYSLAbw9j0m0X8ZejL4MR7oG7TRlrMQ/y1mauR/ZpM2xkiOPZEUlzHLo1GYGlTdP9s5D3XuA6iSQ==",
+      "version": "0.37.9",
+      "resolved": "https://registry.npmjs.org/babel-plugin-jsx-dom-expressions/-/babel-plugin-jsx-dom-expressions-0.37.9.tgz",
+      "integrity": "sha512-6w+zs2i14fVanj4e1hXCU5cp+x0U0LJ5jScknpMZZUteHhwFRGJflHMVJ+xAcW7ku41FYjr7DgtK9mnc2SXlJg==",
       "dev": true,
       "dependencies": {
         "@babel/helper-module-imports": "7.18.6",
@@ -2927,12 +2927,12 @@
       }
     },
     "node_modules/babel-preset-solid": {
-      "version": "1.8.0",
-      "resolved": "https://registry.npmjs.org/babel-preset-solid/-/babel-preset-solid-1.8.0.tgz",
-      "integrity": "sha512-TCsC3kTNYRi+0/mHYFvC2VsSq++GZPFyHF3QTP7L37TXaVFD0HZQPyLQnf+waOGPHQuAhKXo0GEQziquSwBAVw==",
+      "version": "1.8.6",
+      "resolved": "https://registry.npmjs.org/babel-preset-solid/-/babel-preset-solid-1.8.6.tgz",
+      "integrity": "sha512-Ened42CHjU4EFkvNeS042/3Pm21yvMWn8p4G4ddzQTlKaMwSGGD1VciA/e7EshBVHJCcBj9vHiUd/r3A4qLPZA==",
       "dev": true,
       "dependencies": {
-        "babel-plugin-jsx-dom-expressions": "^0.37.0"
+        "babel-plugin-jsx-dom-expressions": "^0.37.9"
       },
       "peerDependencies": {
         "@babel/core": "^7.0.0"
@@ -3401,9 +3401,9 @@
       }
     },
     "node_modules/browserslist": {
-      "version": "4.21.11",
-      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.11.tgz",
-      "integrity": "sha512-xn1UXOKUz7DjdGlg9RrUr0GGiWzI97UQJnugHtH0OLDfJB7jMgoIkYvRIEO1l9EeEERVqeqLYOcFBW9ldjypbQ==",
+      "version": "4.22.2",
+      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz",
+      "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==",
       "dev": true,
       "funding": [
         {
@@ -3420,9 +3420,9 @@
         }
       ],
       "dependencies": {
-        "caniuse-lite": "^1.0.30001538",
-        "electron-to-chromium": "^1.4.526",
-        "node-releases": "^2.0.13",
+        "caniuse-lite": "^1.0.30001565",
+        "electron-to-chromium": "^1.4.601",
+        "node-releases": "^2.0.14",
         "update-browserslist-db": "^1.0.13"
       },
       "bin": {
@@ -3598,9 +3598,9 @@
       }
     },
     "node_modules/caniuse-lite": {
-      "version": "1.0.30001539",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001539.tgz",
-      "integrity": "sha512-hfS5tE8bnNiNvEOEkm8HElUHroYwlqMMENEzELymy77+tJ6m+gA2krtHl5hxJaj71OlpC2cHZbdSMX1/YEqEkA==",
+      "version": "1.0.30001568",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001568.tgz",
+      "integrity": "sha512-vSUkH84HontZJ88MiNrOau1EBrCqEQYgkC5gIySiDlpsm8sGVrhU7Kx4V6h0tnqaHzIHZv08HlJIwPbL4XL9+A==",
       "dev": true,
       "funding": [
         {
@@ -4529,9 +4529,9 @@
       "dev": true
     },
     "node_modules/electron-to-chromium": {
-      "version": "1.4.529",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.529.tgz",
-      "integrity": "sha512-6uyPyXTo8lkv8SWAmjKFbG42U073TXlzD4R8rW3EzuznhFS2olCIAfjjQtV2dV2ar/vRF55KUd3zQYnCB0dd3A==",
+      "version": "1.4.609",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.609.tgz",
+      "integrity": "sha512-ihiCP7PJmjoGNuLpl7TjNA8pCQWu09vGyjlPYw1Rqww4gvNuCcmvl+44G+2QyJ6S2K4o+wbTS++Xz0YN8Q9ERw==",
       "dev": true
     },
     "node_modules/emoji-regex": {
@@ -4731,18 +4731,19 @@
       }
     },
     "node_modules/eslint": {
-      "version": "8.51.0",
-      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.51.0.tgz",
-      "integrity": "sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA==",
+      "version": "8.55.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.55.0.tgz",
+      "integrity": "sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==",
       "dev": true,
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.2.0",
         "@eslint-community/regexpp": "^4.6.1",
-        "@eslint/eslintrc": "^2.1.2",
-        "@eslint/js": "8.51.0",
-        "@humanwhocodes/config-array": "^0.11.11",
+        "@eslint/eslintrc": "^2.1.4",
+        "@eslint/js": "8.55.0",
+        "@humanwhocodes/config-array": "^0.11.13",
         "@humanwhocodes/module-importer": "^1.0.1",
         "@nodelib/fs.walk": "^1.2.8",
+        "@ungap/structured-clone": "^1.2.0",
         "ajv": "^6.12.4",
         "chalk": "^4.0.0",
         "cross-spawn": "^7.0.2",
@@ -5760,9 +5761,9 @@
       }
     },
     "node_modules/globals": {
-      "version": "13.22.0",
-      "resolved": "https://registry.npmjs.org/globals/-/globals-13.22.0.tgz",
-      "integrity": "sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==",
+      "version": "13.23.0",
+      "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz",
+      "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==",
       "dev": true,
       "dependencies": {
         "type-fest": "^0.20.2"
@@ -8222,9 +8223,9 @@
       }
     },
     "node_modules/node-releases": {
-      "version": "2.0.13",
-      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
-      "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==",
+      "version": "2.0.14",
+      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
+      "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==",
       "dev": true
     },
     "node_modules/nopt": {
@@ -11262,15 +11263,6 @@
         "node": ">=0.10.0"
       }
     },
-    "node_modules/source-map": {
-      "version": "0.6.1",
-      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/source-map-js": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
@@ -11279,16 +11271,6 @@
         "node": ">=0.10.0"
       }
     },
-    "node_modules/source-map-support": {
-      "version": "0.5.21",
-      "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
-      "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
-      "dev": true,
-      "dependencies": {
-        "buffer-from": "^1.0.0",
-        "source-map": "^0.6.0"
-      }
-    },
     "node_modules/spdx-correct": {
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz",
@@ -12465,18 +12447,20 @@
       }
     },
     "node_modules/tsx": {
-      "version": "3.14.0",
-      "resolved": "https://registry.npmjs.org/tsx/-/tsx-3.14.0.tgz",
-      "integrity": "sha512-xHtFaKtHxM9LOklMmJdI3BEnQq/D5F73Of2E1GDrITi9sgoVkvIsrQUTY1G8FlmGtA+awCI4EBlTRRYxkL2sRg==",
+      "version": "4.6.2",
+      "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.6.2.tgz",
+      "integrity": "sha512-QPpBdJo+ZDtqZgAnq86iY/PD2KYCUPSUGIunHdGwyII99GKH+f3z3FZ8XNFLSGQIA4I365ui8wnQpl8OKLqcsg==",
       "dev": true,
       "dependencies": {
         "esbuild": "~0.18.20",
-        "get-tsconfig": "^4.7.2",
-        "source-map-support": "^0.5.21"
+        "get-tsconfig": "^4.7.2"
       },
       "bin": {
         "tsx": "dist/cli.mjs"
       },
+      "engines": {
+        "node": ">=18.0.0"
+      },
       "optionalDependencies": {
         "fsevents": "~2.3.3"
       }
@@ -13890,9 +13874,9 @@
       }
     },
     "node_modules/vite": {
-      "version": "4.5.0",
-      "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.0.tgz",
-      "integrity": "sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==",
+      "version": "4.5.1",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.1.tgz",
+      "integrity": "sha512-AXXFaAJ8yebyqzoNB9fu2pHoo/nWX+xZlaRwoeYUxEqBO+Zj4msE5G+BhGBll9lYEKv9Hfks52PAF2X7qDYXQA==",
       "dependencies": {
         "esbuild": "^0.18.10",
         "postcss": "^8.4.27",
@@ -13976,22 +13960,22 @@
       }
     },
     "node_modules/vite-plugin-solid": {
-      "version": "2.7.1",
-      "resolved": "https://registry.npmjs.org/vite-plugin-solid/-/vite-plugin-solid-2.7.1.tgz",
-      "integrity": "sha512-1rTuJdsDqCZiAWfiEkUeRRz4MaxWhqzgJ6PURN+bBSp4uACA/esgYN9Un++YE+xeXUOHlOwTF+VSdvieEcMz0w==",
+      "version": "2.8.0",
+      "resolved": "https://registry.npmjs.org/vite-plugin-solid/-/vite-plugin-solid-2.8.0.tgz",
+      "integrity": "sha512-n5FAm7ZmTl94VWUoiJCgG7bouF2NlC9CA1wY/qbVnkFbYDWk++bFWyNoU48aLJ+lMtzNeYzJypJXOHzFKxL9xA==",
       "dev": true,
       "dependencies": {
-        "@babel/core": "^7.23.0",
-        "@babel/preset-typescript": "^7.23.0",
-        "@types/babel__core": "^7.20.2",
-        "babel-preset-solid": "^1.8.0",
+        "@babel/core": "^7.23.3",
+        "@babel/preset-typescript": "^7.23.3",
+        "@types/babel__core": "^7.20.4",
+        "babel-preset-solid": "^1.8.4",
         "merge-anything": "^5.1.7",
         "solid-refresh": "^0.5.3",
-        "vitefu": "^0.2.4"
+        "vitefu": "^0.2.5"
       },
       "peerDependencies": {
         "solid-js": "^1.7.2",
-        "vite": "^3.0.0 || ^4.0.0"
+        "vite": "^3.0.0 || ^4.0.0 || ^5.0.0"
       }
     },
     "node_modules/vite-plugin-static-copy": {
@@ -14379,12 +14363,12 @@
       }
     },
     "node_modules/vitefu": {
-      "version": "0.2.4",
-      "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.4.tgz",
-      "integrity": "sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g==",
+      "version": "0.2.5",
+      "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.5.tgz",
+      "integrity": "sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==",
       "dev": true,
       "peerDependencies": {
-        "vite": "^3.0.0 || ^4.0.0"
+        "vite": "^3.0.0 || ^4.0.0 || ^5.0.0"
       },
       "peerDependenciesMeta": {
         "vite": {
diff --git a/package.json b/package.json
index 431f6d26..5a158d48 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "web-scrobbler",
-  "version": "3.2.0",
+  "version": "3.3.0",
   "author": "David Šabata <posta@davidsabata.cz>",
   "contributors": [
     "@mountainash (https://mountainash.id.au/)"
@@ -32,26 +32,26 @@
     "@web-scrobbler/metadata-filter": "3.0.2",
     "blueimp-md5": "2.19.0",
     "stylelint": "15.11.0",
-    "vite": "4.5.0",
+    "vite": "4.5.1",
     "webextension-polyfill": "0.10.0"
   },
   "devDependencies": {
-    "@types/archiver": "6.0.0",
-    "@types/blueimp-md5": "2.18.0",
+    "@types/archiver": "6.0.2",
+    "@types/blueimp-md5": "2.18.2",
     "@types/fs-extra": "11.0.3",
-    "@types/imagemin": "8.0.2",
-    "@types/imagemin-jpegtran": "5.0.2",
-    "@types/node": "20.8.10",
-    "@types/react": "18.2.34",
+    "@types/imagemin": "8.0.5",
+    "@types/imagemin-jpegtran": "5.0.4",
+    "@types/node": "20.10.4",
+    "@types/react": "18.2.43",
     "@types/webextension-polyfill": "0.10.4",
-    "@typescript-eslint/eslint-plugin": "6.7.5",
+    "@typescript-eslint/eslint-plugin": "6.13.2",
     "@web-scrobbler/eslint-config": "3.0.0",
     "@web-scrobbler/stylelint-config": "1.0.1",
     "archiver": "6.0.1",
     "canvas": "2.11.2",
     "chokidar": "3.5.3",
     "esbuild": "0.19.5",
-    "eslint": "8.51.0",
+    "eslint": "8.55.0",
     "eslint-plugin-react": "7.33.2",
     "eslint-plugin-solid": "0.13.0",
     "eslint-plugin-tsdoc": "0.2.17",
@@ -67,10 +67,10 @@
     "remark-preset-lint-recommended": "6.1.3",
     "sass": "1.69.5",
     "solid-js": "1.8.5",
-    "tsx": "3.14.0",
+    "tsx": "4.6.2",
     "typescript": "5.2.2",
     "vite-plugin-conditional-compiler": "0.2.1",
-    "vite-plugin-solid": "2.7.1",
+    "vite-plugin-solid": "2.8.0",
     "vite-plugin-static-copy": "0.17.0",
     "vitest": "0.34.6"
   },
diff --git a/src/connectors/aiircom.ts b/src/connectors/aiircom.ts
new file mode 100644
index 00000000..58130922
--- /dev/null
+++ b/src/connectors/aiircom.ts
@@ -0,0 +1,51 @@
+export {};
+
+interface TrackInfo {
+	[key: string]: string | null | number | undefined;
+}
+
+interface Field {
+	[key: string]: [
+		string,
+		(
+			selectors: string | string[],
+		) => (string | null) | (number | undefined),
+	];
+}
+
+const fields: Field = {
+	artist: ['span', Util.getTextFromSelectors],
+	track: ['strong', Util.getTextFromSelectors],
+	trackArt: ['img', Util.extractImageUrlFromSelectors],
+};
+
+Connector.playerSelector = '#playbar';
+
+Connector.getTrackInfo = () => {
+	const context: string = getCurrentContext();
+	const trackInfo: TrackInfo = {};
+	for (const field in fields) {
+		const [selector, fn] = fields[field];
+		trackInfo[field] = fn(`${context} ${selector}`);
+	}
+	return trackInfo;
+};
+
+Connector.isPlaying = () => {
+	const href =
+		Util.getAttrFromSelectors(
+			'[data-player-target="playButtonIcon"]',
+			'href',
+		) ?? '';
+	return ['#icon-pause', '#icon-stop'].includes(href);
+};
+
+Connector.isPodcast = () => Util.isElementVisible('[role="navigation"]');
+
+function getCurrentContext() {
+	const isVisible = Util.isElementVisible(
+		'[data-player-target="playBarSecondaryContainer"]',
+	);
+	const kind = isVisible ? 'secondary' : 'primary';
+	return `.c-playbar__${kind}`;
+}
diff --git a/src/connectors/audius.ts b/src/connectors/audius.ts
new file mode 100644
index 00000000..06758a90
--- /dev/null
+++ b/src/connectors/audius.ts
@@ -0,0 +1,33 @@
+export {};
+
+Connector.playerSelector = '[class*="playBarContentWrapper"]';
+
+Connector.playButtonSelector = `${Connector.playerSelector} [aria-label="play track"]`;
+
+Connector.artistSelector = `${Connector.playerSelector} [class*="_artistName_"]`;
+
+Connector.trackSelector = `${Connector.playerSelector} [class*="_trackTitle_"]`;
+
+Connector.durationSelector = `${Connector.playerSelector} [class*="timestampEnd"]`;
+
+Connector.currentTimeSelector = `${Connector.playerSelector} [class*="timestampStart"]`;
+
+Connector.trackArtSelector =
+	'[aria-label="View currently playing track"] div div:nth-child(2)';
+
+const filter = MetadataFilter.createFilter({
+	track: trimArtist,
+});
+
+Connector.applyFilter(filter);
+
+function trimArtist(track: string): string {
+	const separator = ' - ';
+	const chunks = track.split(separator);
+	const artist = Connector.getArtist();
+	const trackArtist = chunks.shift();
+	if (artist?.toLowerCase() === trackArtist?.toLowerCase()) {
+		return chunks.join(separator);
+	}
+	return track;
+}
diff --git a/src/connectors/beeline-music.ts b/src/connectors/beeline-music.ts
new file mode 100644
index 00000000..a22e85f2
--- /dev/null
+++ b/src/connectors/beeline-music.ts
@@ -0,0 +1,15 @@
+export {};
+
+Connector.playerSelector = '.player-container';
+
+Connector.artistSelector = '.player__track_artist';
+
+Connector.trackSelector = '.player__track_title';
+
+Connector.trackArtSelector = '.cover__inner';
+
+Connector.isPodcast = () => Util.hasElementClass('.player', 'player-podcast');
+
+Connector.isPlaying = () =>
+	Util.getAttrFromSelectors('.player__controls_button-play', 'title') ===
+	'Пауза';
diff --git a/src/connectors/fmspins.ts b/src/connectors/fmspins.ts
new file mode 100644
index 00000000..e89b3fb3
--- /dev/null
+++ b/src/connectors/fmspins.ts
@@ -0,0 +1,9 @@
+export {};
+
+Connector.playerSelector = '#main';
+Connector.artistSelector =
+	'#current-spins .list-group-item.spin:first-child .artist a';
+Connector.trackSelector =
+	'#current-spins .list-group-item.spin:first-child .track a';
+Connector.trackArtSelector = '#current-spins .list-group-item.spin:first-child';
+Connector.playButtonSelector = '.actions .fa-play';
diff --git a/src/connectors/overcast.ts b/src/connectors/overcast.ts
new file mode 100644
index 00000000..02801a02
--- /dev/null
+++ b/src/connectors/overcast.ts
@@ -0,0 +1,34 @@
+export {};
+
+Connector.playerSelector = '.pure-u-1';
+
+Connector.artistSelector = `${Connector.playerSelector} h3`;
+
+Connector.trackSelector = `${Connector.playerSelector} h2`;
+
+Connector.trackArtSelector = `${Connector.playerSelector} .fullart`;
+
+Connector.playButtonSelector = '#playpausebutton_playicon';
+
+Connector.currentTimeSelector = '#timeelapsed';
+
+Connector.remainingTimeSelector = '#timeremaining';
+
+Connector.isPodcast = () => true;
+
+const filter = MetadataFilter.createFilter({
+	track: trimPodcaster,
+});
+
+Connector.applyFilter(filter);
+
+function trimPodcaster(track: string) {
+	const separator = ': ';
+	const chunks = track.split(separator);
+	const artist = Connector.getArtist();
+	const trackArtist = chunks.shift();
+	if (artist?.toLowerCase() === trackArtist?.toLowerCase()) {
+		return chunks.join(separator);
+	}
+	return track;
+}
diff --git a/src/connectors/primordialradio.ts b/src/connectors/primordialradio.ts
new file mode 100644
index 00000000..e143f4db
--- /dev/null
+++ b/src/connectors/primordialradio.ts
@@ -0,0 +1,16 @@
+export {};
+
+Connector.playerSelector = '.radio-player-top__left, .player__player';
+
+Connector.artistSelector = '.js-current-track-artist';
+
+Connector.trackSelector = '.js-current-track-title';
+
+Connector.artistTrackSelector = '.js-current-track';
+
+Connector.trackArtSelector = '.js-current-track-artwork';
+
+Connector.isTrackArtDefault = (url) => url?.includes('no-artwork');
+
+Connector.isPlaying = () =>
+	Util.hasElementClass('.js-play-button .fas', 'fa-stop');
diff --git a/src/connectors/q-dance.ts b/src/connectors/q-dance.ts
new file mode 100644
index 00000000..34facf5c
--- /dev/null
+++ b/src/connectors/q-dance.ts
@@ -0,0 +1,17 @@
+export {};
+
+Connector.playerSelector = '.audioplayer__wrapper';
+
+Connector.artistSelector = '.audioplayer-nowplaying__artist';
+
+Connector.trackSelector = '.audioplayer-nowplaying__track';
+
+Connector.trackArtSelector = '.audioplayer-nowplaying__image img';
+
+Connector.isTrackArtDefault = (url) => url?.endsWith('0.jpg');
+
+Connector.isPlaying = () =>
+	Util.hasElementClass(
+		'.audioplayer-controls__icon',
+		'audioplayer-controls__icon--pause',
+	);
diff --git a/src/connectors/radio.de.ts b/src/connectors/radio.de.ts
new file mode 100644
index 00000000..2d68e0af
--- /dev/null
+++ b/src/connectors/radio.de.ts
@@ -0,0 +1,15 @@
+export {};
+
+Connector.playerSelector = 'div.player-nav-holder';
+
+Connector.getArtistTrack = () => {
+	const text = Util.getTextFromSelectors('span');
+	const m = text?.match(/(.*?) - (.*?) auf (.*?)/);
+	if (m && m.length === 4) {
+		return { artist: m[1], track: m[2] };
+	}
+	return null;
+};
+
+Connector.isPlaying = () =>
+	Util.getAttrFromSelectors('.player_playbutton', 'title') !== 'Wiedergabe';
diff --git a/src/connectors/radiofrance.ts b/src/connectors/radiofrance.ts
index 685f4185..ff0cd60e 100644
--- a/src/connectors/radiofrance.ts
+++ b/src/connectors/radiofrance.ts
@@ -1,17 +1,36 @@
 export {};
 
-const PLAY_BUTTON = '[data-testid="mainButton"]';
-
-const BUTTON_STATE_TEXT = 'STOP';
+const SEPARATOR = ' \u2022 ';
 
 Connector.playerSelector = '#player';
 
-Connector.playButtonSelector = PLAY_BUTTON;
+Connector.isPlaying = () => {
+	const text = Util.getTextFromSelectors('[data-testid="mainButton"]') ?? '';
+	return ['STOP', 'PAUSE'].includes(text);
+};
 
-Connector.isPlaying = () =>
-	Util.getTextFromSelectors(PLAY_BUTTON) === BUTTON_STATE_TEXT;
+Connector.isPodcast = () => Util.isElementVisible('.Seekbar input');
 
 Connector.getArtistTrack = () => {
-	const track = Util.getTextFromSelectors('section > div.Line > p > span');
-	return Util.splitArtistTrack(track, [' \u2022 ']);
+	const artist = Util.getTextFromSelectors('.Metadata .firstline');
+	const track = Util.getTextFromSelectors('.Metadata .secondline');
+	if (!hasSeparator(artist) && !hasSeparator(track)) {
+		return { artist, track };
+	}
+	if (hasSeparator(track)) {
+		return Util.splitArtistTrack(track, [SEPARATOR]);
+	}
+	const [artistAsTrack] = Util.splitString(artist, [SEPARATOR], true);
+	return { artist: track, track: artistAsTrack };
 };
+
+Connector.getTrackArt = () =>
+	Util.getAttrFromSelectors('.Cover picture source', 'srcset');
+
+Connector.currentTimeSelector = '.time span:nth-child(1)';
+
+Connector.durationSelector = '.time span:nth-child(3)';
+
+function hasSeparator(text: string | null): boolean {
+	return Boolean(text?.includes(SEPARATOR));
+}
diff --git a/src/connectors/rpfm.ts b/src/connectors/rpfm.ts
new file mode 100644
index 00000000..bca1218d
--- /dev/null
+++ b/src/connectors/rpfm.ts
@@ -0,0 +1,40 @@
+export {};
+
+interface Property {
+	[key: string]: string;
+}
+
+const props: Property = {
+	trackSelector: '.radio-current-track',
+	artistSelector: '.radio-current-artist',
+	trackArtSelector: '.radio-current-cover',
+	durationSelector: '.track-time-duration',
+	currentTimeSelector: '.track-time-position',
+};
+
+Connector.playerSelector = '.app-player';
+
+for (const prop in props) {
+	const selector = props[prop];
+	Object.defineProperty(Connector, prop, {
+		get: () => `${getCurrentContext()} ${selector}`,
+	});
+}
+
+Connector.isTrackArtDefault = (url) => url?.includes('no-cover');
+
+Connector.isPlaying = () =>
+	Util.getDataFromSelectors('.app-player', 'status') === 'play';
+
+Connector.isStateChangeAllowed = () => {
+	const artist = Connector.getArtist();
+	return artist !== null && artist !== 'Реклама';
+};
+
+function getCurrentContext(): string {
+	const single = Util.hasElementClass(
+		Connector.playerSelector,
+		'single-player',
+	);
+	return single ? '.app-player-panel' : '#broadcast-panel';
+}
diff --git a/src/connectors/vk-save.ts b/src/connectors/vk-save.ts
new file mode 100644
index 00000000..22e15910
--- /dev/null
+++ b/src/connectors/vk-save.ts
@@ -0,0 +1,13 @@
+export {};
+
+Connector.playerSelector = '.popup_player_wrapper > .popup_player_content';
+
+Connector.artistSelector = '.info_wr > .author > a';
+
+Connector.trackSelector = '.info_wr > .info > .title';
+
+Connector.currentTimeSelector = '.durations_wrap > .durations_start';
+
+Connector.remainingTimeSelector = '.durations_wrap > .durations_end';
+
+Connector.playButtonSelector = '.footer_player > .footer_play';
diff --git a/src/core/connectors.ts b/src/core/connectors.ts
index 2c44a43a..07215a36 100644
--- a/src/core/connectors.ts
+++ b/src/core/connectors.ts
@@ -150,6 +150,12 @@ export default <ConnectorMeta[]>[
 		js: 'radioplus.js',
 		id: 'radioplus',
 	},
+	{
+		label: 'Радио Premium',
+		matches: ['*://rpfm.ru/*'],
+		js: 'rpfm.js',
+		id: 'rpfm',
+	},
 	{
 		label: 'Douban.FM',
 		matches: ['*://douban.fm/*', '*://fm.douban.fm/*'],
@@ -244,6 +250,12 @@ export default <ConnectorMeta[]>[
 		js: 'odnoklassniki.js',
 		id: 'odnoklassniki',
 	},
+	{
+		label: 'Overcast',
+		matches: ['*://overcast.fm/*'],
+		js: 'overcast.js',
+		id: 'overcast',
+	},
 	{
 		label: 'Online Radio Box',
 		matches: ['*://onlineradiobox.com/*'],
@@ -292,6 +304,15 @@ export default <ConnectorMeta[]>[
 		js: 'radionomy.js',
 		id: 'radionomy',
 	},
+	{
+		label: 'J1 Radio',
+		matches: [
+			'*://www.j1fm.tokyo/player/*',
+			'*://www.j1fm.tokyo/michi/player/*',
+		],
+		js: 'aiircom.js',
+		id: 'j1-radio',
+	},
 	{
 		label: 'JazzAndRain',
 		matches: ['*://*.jazzandrain.com/*'],
@@ -370,6 +391,12 @@ export default <ConnectorMeta[]>[
 		js: 'audacy.js',
 		id: 'audacy',
 	},
+	{
+		label: 'Audius',
+		matches: ['*://audius.co/*'],
+		js: 'audius.js',
+		id: 'audius',
+	},
 	{
 		label: 'GetWorkDoneMusic',
 		matches: ['*://*.getworkdonemusic.com/*'],
@@ -477,6 +504,12 @@ export default <ConnectorMeta[]>[
 		js: 'beatport.js',
 		id: 'beatport',
 	},
+	{
+		label: 'Beeline Music',
+		matches: ['*://music.beeline.ru/*'],
+		js: 'beeline-music.js',
+		id: 'beeline-music',
+	},
 	{
 		label: 'wavo',
 		matches: ['*://wavo.me/*'],
@@ -664,6 +697,12 @@ export default <ConnectorMeta[]>[
 		js: 'musicat.js',
 		id: 'qcbeats',
 	},
+	{
+		label: 'Q-Dance',
+		matches: ['*://www.q-dance.com/*'],
+		js: 'q-dance.js',
+		id: 'q-dance',
+	},
 	{
 		label: 'Sawdust City Sounds',
 		matches: ['*://sawdustcitysounds.org/*'],
@@ -1167,6 +1206,12 @@ export default <ConnectorMeta[]>[
 		js: 'pretzel.js',
 		id: 'pretzel',
 	},
+	{
+		label: 'Primordial Radio',
+		matches: ['*://primordialradio.com/*'],
+		js: 'primordialradio.js',
+		id: 'primordialradio',
+	},
 	{
 		label: 'Radio Kyivstar',
 		matches: ['*://radio.kyivstar.ua/*'],
@@ -2237,4 +2282,22 @@ export default <ConnectorMeta[]>[
 		js: 'wdr.js',
 		id: 'wdr',
 	},
+	{
+		label: 'radio.de',
+		matches: ['*://www.radio.de/*'],
+		js: 'radio.de.js',
+		id: 'radiode',
+	},
+	{
+		label: 'FMSpins',
+		matches: ['*://*.fmspins.com/*'],
+		js: 'fmspins.js',
+		id: 'fmspins',
+	},
+	{
+		label: 'vk-save',
+		matches: ['*://*.vk-save.com/*'],
+		js: 'vk-save.js',
+		id: 'vk-save',
+	},
 ];
diff --git a/src/core/object/controller/controller.ts b/src/core/object/controller/controller.ts
index 97f67371..3382c562 100644
--- a/src/core/object/controller/controller.ts
+++ b/src/core/object/controller/controller.ts
@@ -3,6 +3,7 @@ import * as Options from '@/core/storage/options';
 import {
 	areAllResults,
 	DebugLogType,
+	parseScrobblePercent,
 	getSecondsToScrobble,
 	isAnyResult,
 } from '@/util/util';
@@ -876,13 +877,11 @@ export default class Controller {
 			return;
 		}
 
-		const percent = await Options.getOption(
+		const rawPercent = await Options.getOption(
 			Options.SCROBBLE_PERCENT,
 			this.connector.meta.id,
 		);
-		if (typeof percent !== 'number') {
-			return;
-		}
+		const percent = parseScrobblePercent(rawPercent);
 
 		const secondsToScrobble = getSecondsToScrobble(duration, percent);
 
diff --git a/src/core/storage/options.ts b/src/core/storage/options.ts
index 6620546a..7b86005e 100644
--- a/src/core/storage/options.ts
+++ b/src/core/storage/options.ts
@@ -1,6 +1,7 @@
 import connectors, { ConnectorMeta } from '@/core/connectors';
 import * as BrowserStorage from '@/core/storage/browser-storage';
 import { debugLog } from '../content/util';
+import { DEFAULT_SCROBBLE_PERCENT } from '@/util/util';
 
 const options = BrowserStorage.getStorage(BrowserStorage.OPTIONS);
 const connectorsOptions = BrowserStorage.getStorage(
@@ -87,7 +88,7 @@ const DEFAULT_OPTIONS: GlobalOptions = {
 	[SCROBBLE_RECOGNIZED_TRACKS]: true,
 	[SCROBBLE_EDITED_TRACKS_ONLY]: false,
 	[DEBUG_LOGGING_ENABLED]: false,
-	[SCROBBLE_PERCENT]: 50,
+	[SCROBBLE_PERCENT]: DEFAULT_SCROBBLE_PERCENT,
 	[USE_INFOBOX]: true,
 	[DISABLED_CONNECTORS]: {},
 };
diff --git a/src/ui/options/components/inputs.tsx b/src/ui/options/components/inputs.tsx
index d9dac1c5..957108e5 100644
--- a/src/ui/options/components/inputs.tsx
+++ b/src/ui/options/components/inputs.tsx
@@ -324,7 +324,7 @@ export function RangeOptionEntry(props: {
 						}
 						const newOptions = {
 							...o,
-							[key]: e.currentTarget.value,
+							[key]: parseInt(e.currentTarget.value),
 						};
 						return newOptions;
 					});
@@ -338,7 +338,7 @@ export function RangeOptionEntry(props: {
 						}
 						const newOptions = {
 							...o,
-							[key]: e.currentTarget.value,
+							[key]: parseInt(e.currentTarget.value),
 						};
 						globalOptions.set(newOptions);
 						return newOptions;
diff --git a/src/util/util.ts b/src/util/util.ts
index b393166e..22905884 100644
--- a/src/util/util.ts
+++ b/src/util/util.ts
@@ -26,6 +26,12 @@ export const HIDDEN_PLACEHOLDER = '[hidden]';
  */
 export const DEFAULT_SCROBBLE_TIME = 30;
 
+/**
+ * Percentage of track to playback before the track is scrobbled.
+ * This value is used only if scrobble percent storage is somehow corrupted.
+ */
+export const DEFAULT_SCROBBLE_PERCENT = 50;
+
 /**
  * Minimum number of seconds of scrobbleable track.
  */
@@ -54,6 +60,22 @@ export function debugLog(text: unknown, logType: DebugLogType = 'log'): void {
 	logFunc(text);
 }
 
+/**
+ * Narrow the typing of scrobble percent.
+ * Fallback to default if scrobble percent is not a number.
+ *
+ * @param percent - Scrobble percent value from settings
+ * @returns percentage of track to play before scrobbling
+ */
+export function parseScrobblePercent(percent: unknown): number {
+	return percent &&
+		typeof percent === 'number' &&
+		!isNaN(percent) &&
+		isFinite(percent)
+		? percent
+		: DEFAULT_SCROBBLE_PERCENT;
+}
+
 /**
  * Return total number of seconds of playback needed for this track
  * to be scrobbled.


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index cdbd73d1..30146143 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -37,6 +37,7 @@ jobs:
         uses: actions/setup-node@v4
         with:
           node-version-file: '.nvmrc'
+          cache: 'npm'
 
       - name: Install dependencies
         run: npm ci