From 068cd2e4073ab6391ae29793990edcb029d540da Mon Sep 17 00:00:00 2001 From: MerlinScheurer Date: Fri, 23 May 2025 18:54:29 +0200 Subject: [PATCH 01/16] Add theme handling when without user context --- frontend/src/api/actions/updateUserConfig.ts | 8 +++++ .../src/configuration/colours/Colours.tsx | 30 +++++++++++++++++-- .../configuration/colours/colourConstant.ts | 7 ----- frontend/src/pages/SettingsUser.tsx | 5 +++- 4 files changed, 40 insertions(+), 10 deletions(-) delete mode 100644 frontend/src/configuration/colours/colourConstant.ts diff --git a/frontend/src/api/actions/updateUserConfig.ts b/frontend/src/api/actions/updateUserConfig.ts index 84c5ce3d..ac806e20 100644 --- a/frontend/src/api/actions/updateUserConfig.ts +++ b/frontend/src/api/actions/updateUserConfig.ts @@ -8,6 +8,14 @@ export type ColourVariants = | 'midnight.css' | 'custom.css'; +export const ColourConstant = { + Dark: 'dark.css', + Light: 'light.css', + Matrix: 'matrix.css', + Midnight: 'midnight.css', + Custom: 'custom.css', +}; + export const FileSizeUnits = { Binary: 'binary', Metric: 'metric', diff --git a/frontend/src/configuration/colours/Colours.tsx b/frontend/src/configuration/colours/Colours.tsx index c4654f32..312be577 100644 --- a/frontend/src/configuration/colours/Colours.tsx +++ b/frontend/src/configuration/colours/Colours.tsx @@ -1,14 +1,40 @@ +import { ColourConstant, ColourVariants } from '../../api/actions/updateUserConfig'; import { useUserConfigStore } from '../../stores/UserConfigStore'; -import { ColourConstant } from './colourConstant'; import CustomStylesheet from './components/Custom'; import DarkStylesheet from './components/Dark'; import LightStylesheet from './components/Light'; import MatrixStylesheet from './components/Matrix'; import MidnightStylesheet from './components/Midnight'; +function getThemeFromLocalStorage(stylesheet: string): ColourVariants { + // Check when localStorage when its possibly the default theme. ( e.g. login page ) + if (stylesheet === ColourConstant.Dark) { + const fromLocalStorage = localStorage.getItem('stylesheet'); + + if (!fromLocalStorage) { + localStorage.setItem('stylesheet', stylesheet); + } + + if (fromLocalStorage) { + stylesheet = fromLocalStorage; + } + } else { + const fromLocalStorage = localStorage.getItem('stylesheet'); + + // Re-sync when localStorage is not the same as in userConfig + if (stylesheet !== fromLocalStorage) { + localStorage.setItem('stylesheet', stylesheet); + } + } + + return stylesheet as ColourVariants; +} + const Colours = () => { const { userConfig } = useUserConfigStore(); - const stylesheet = userConfig?.stylesheet; + let stylesheet = userConfig?.stylesheet; + + stylesheet = getThemeFromLocalStorage(stylesheet); switch (stylesheet) { case ColourConstant.Dark: diff --git a/frontend/src/configuration/colours/colourConstant.ts b/frontend/src/configuration/colours/colourConstant.ts deleted file mode 100644 index b910fe9a..00000000 --- a/frontend/src/configuration/colours/colourConstant.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const ColourConstant = { - Dark: 'dark.css', - Light: 'light.css', - Matrix: 'matrix.css', - Midnight: 'midnight.css', - Custom: 'custom.css', -}; diff --git a/frontend/src/pages/SettingsUser.tsx b/frontend/src/pages/SettingsUser.tsx index b822eb05..3efe7693 100644 --- a/frontend/src/pages/SettingsUser.tsx +++ b/frontend/src/pages/SettingsUser.tsx @@ -1,4 +1,5 @@ import updateUserConfig, { + ColourConstant, ColourVariants, FileSizeUnits, UserConfigType, @@ -10,7 +11,6 @@ import useIsAdmin from '../functions/useIsAdmin'; import { useUserConfigStore } from '../stores/UserConfigStore'; import { useEffect, useState } from 'react'; import ToggleConfig from '../components/ToggleConfig'; -import { ColourConstant } from '../configuration/colours/colourConstant'; const SettingsUser = () => { const { userConfig, setUserConfig } = useUserConfigStore(); @@ -38,6 +38,9 @@ const SettingsUser = () => { const handleStyleSheetChange = async (selectedStyleSheet: ColourVariants) => { handleUserConfigUpdate({ stylesheet: selectedStyleSheet }); setStyleSheet(selectedStyleSheet); + + // Store in local storage for pages like login, without a userConfig + localStorage.setItem('stylesheet', selectedStyleSheet); }; const handlePageSizeChange = async () => { From 717d2b3098b2cadab0c97ecdff92bf9201c5e0b6 Mon Sep 17 00:00:00 2001 From: MerlinScheurer Date: Fri, 23 May 2025 18:55:29 +0200 Subject: [PATCH 02/16] Update frontend dependencies --- frontend/package-lock.json | 1253 +++++------------------------------- frontend/package.json | 18 +- 2 files changed, 167 insertions(+), 1104 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 51ec571f..a5be2f31 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,26 +8,26 @@ "name": "tubearchivist-frontend", "version": "0.5.1", "dependencies": { - "dompurify": "^3.2.5", + "dompurify": "^3.2.6", "react": "^19.1.0", "react-dom": "^19.1.0", "react-router-dom": "^7.6.0", - "zustand": "^5.0.4" + "zustand": "^5.0.5" }, "devDependencies": { - "@types/react": "^19.1.3", - "@types/react-dom": "^19.1.3", - "@typescript-eslint/eslint-plugin": "^8.32.0", - "@typescript-eslint/parser": "^8.32.0", - "@vitejs/plugin-react-swc": "^3.9.0", - "eslint": "^9.26.0", + "@types/react": "^19.1.5", + "@types/react-dom": "^19.1.5", + "@typescript-eslint/eslint-plugin": "^8.32.1", + "@typescript-eslint/parser": "^8.32.1", + "@vitejs/plugin-react-swc": "^3.10.0", + "eslint": "^9.27.0", "eslint-config-prettier": "^10.1.5", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", "globals": "^16.1.0", "prettier": "3.5.3", "typescript": "^5.8.3", - "typescript-eslint": "^8.32.0", + "typescript-eslint": "^8.32.1", "vite": ">=6.3.5", "vite-plugin-checker": "^0.9.3" } @@ -561,9 +561,9 @@ } }, "node_modules/@eslint/core": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", - "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", + "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -635,13 +635,16 @@ } }, "node_modules/@eslint/js": { - "version": "9.26.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.26.0.tgz", - "integrity": "sha512-I9XlJawFdSMvWjDt6wksMCrgns5ggLNfFwFvnShsleWruvXM514Qxk8V246efTw+eo9JABvVz+u3q2RiAowKxQ==", + "version": "9.27.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz", + "integrity": "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==", "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, "node_modules/@eslint/object-schema": { @@ -655,13 +658,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", - "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz", + "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.13.0", + "@eslint/core": "^0.14.0", "levn": "^0.4.1" }, "engines": { @@ -734,28 +737,6 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@modelcontextprotocol/sdk": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.11.1.tgz", - "integrity": "sha512-9LfmxKTb1v+vUS1/emSk1f5ePmTLkb9Le9AxOB5T0XM59EUumwcS45z05h7aiZx3GI0Bl7mjb3FMEglYj+acuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "content-type": "^1.0.5", - "cors": "^2.8.5", - "cross-spawn": "^7.0.3", - "eventsource": "^3.0.2", - "express": "^5.0.1", - "express-rate-limit": "^7.5.0", - "pkce-challenge": "^5.0.0", - "raw-body": "^3.0.0", - "zod": "^3.23.8", - "zod-to-json-schema": "^3.24.1" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -794,6 +775,13 @@ "node": ">= 8" } }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.9", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.9.tgz", + "integrity": "sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==", + "dev": true, + "license": "MIT" + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.39.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.39.0.tgz", @@ -1075,9 +1063,9 @@ ] }, "node_modules/@swc/core": { - "version": "1.11.21", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.11.21.tgz", - "integrity": "sha512-/Y3BJLcwd40pExmdar8MH2UGGvCBrqNN7hauOMckrEX2Ivcbv3IMhrbGX4od1dnF880Ed8y/E9aStZCIQi0EGw==", + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.11.29.tgz", + "integrity": "sha512-g4mThMIpWbNhV8G2rWp5a5/Igv8/2UFRJx2yImrLGMgrDDYZIopqZ/z0jZxDgqNA1QDx93rpwNF7jGsxVWcMlA==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", @@ -1093,16 +1081,16 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.11.21", - "@swc/core-darwin-x64": "1.11.21", - "@swc/core-linux-arm-gnueabihf": "1.11.21", - "@swc/core-linux-arm64-gnu": "1.11.21", - "@swc/core-linux-arm64-musl": "1.11.21", - "@swc/core-linux-x64-gnu": "1.11.21", - "@swc/core-linux-x64-musl": "1.11.21", - "@swc/core-win32-arm64-msvc": "1.11.21", - "@swc/core-win32-ia32-msvc": "1.11.21", - "@swc/core-win32-x64-msvc": "1.11.21" + "@swc/core-darwin-arm64": "1.11.29", + "@swc/core-darwin-x64": "1.11.29", + "@swc/core-linux-arm-gnueabihf": "1.11.29", + "@swc/core-linux-arm64-gnu": "1.11.29", + "@swc/core-linux-arm64-musl": "1.11.29", + "@swc/core-linux-x64-gnu": "1.11.29", + "@swc/core-linux-x64-musl": "1.11.29", + "@swc/core-win32-arm64-msvc": "1.11.29", + "@swc/core-win32-ia32-msvc": "1.11.29", + "@swc/core-win32-x64-msvc": "1.11.29" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" @@ -1114,9 +1102,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.11.21", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.11.21.tgz", - "integrity": "sha512-v6gjw9YFWvKulCw3ZA1dY+LGMafYzJksm1mD4UZFZ9b36CyHFowYVYug1ajYRIRqEvvfIhHUNV660zTLoVFR8g==", + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.11.29.tgz", + "integrity": "sha512-whsCX7URzbuS5aET58c75Dloby3Gtj/ITk2vc4WW6pSDQKSPDuONsIcZ7B2ng8oz0K6ttbi4p3H/PNPQLJ4maQ==", "cpu": [ "arm64" ], @@ -1131,9 +1119,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.11.21", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.11.21.tgz", - "integrity": "sha512-CUiTiqKlzskwswrx9Ve5NhNoab30L1/ScOfQwr1duvNlFvarC8fvQSgdtpw2Zh3MfnfNPpyLZnYg7ah4kbT9JQ==", + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.11.29.tgz", + "integrity": "sha512-S3eTo/KYFk+76cWJRgX30hylN5XkSmjYtCBnM4jPLYn7L6zWYEPajsFLmruQEiTEDUg0gBEWLMNyUeghtswouw==", "cpu": [ "x64" ], @@ -1148,9 +1136,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.11.21", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.11.21.tgz", - "integrity": "sha512-YyBTAFM/QPqt1PscD8hDmCLnqPGKmUZpqeE25HXY8OLjl2MUs8+O4KjwPZZ+OGxpdTbwuWFyMoxjcLy80JODvg==", + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.11.29.tgz", + "integrity": "sha512-o9gdshbzkUMG6azldHdmKklcfrcMx+a23d/2qHQHPDLUPAN+Trd+sDQUYArK5Fcm7TlpG4sczz95ghN0DMkM7g==", "cpu": [ "arm" ], @@ -1165,9 +1153,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.11.21", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.11.21.tgz", - "integrity": "sha512-DQD+ooJmwpNsh4acrftdkuwl5LNxxg8U4+C/RJNDd7m5FP9Wo4c0URi5U0a9Vk/6sQNh9aSGcYChDpqCDWEcBw==", + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.11.29.tgz", + "integrity": "sha512-sLoaciOgUKQF1KX9T6hPGzvhOQaJn+3DHy4LOHeXhQqvBgr+7QcZ+hl4uixPKTzxk6hy6Hb0QOvQEdBAAR1gXw==", "cpu": [ "arm64" ], @@ -1182,9 +1170,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.11.21", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.11.21.tgz", - "integrity": "sha512-y1L49+snt1a1gLTYPY641slqy55QotPdtRK9Y6jMi4JBQyZwxC8swWYlQWb+MyILwxA614fi62SCNZNznB3XSA==", + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.11.29.tgz", + "integrity": "sha512-PwjB10BC0N+Ce7RU/L23eYch6lXFHz7r3NFavIcwDNa/AAqywfxyxh13OeRy+P0cg7NDpWEETWspXeI4Ek8otw==", "cpu": [ "arm64" ], @@ -1199,9 +1187,9 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.11.21", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.11.21.tgz", - "integrity": "sha512-NesdBXv4CvVEaFUlqKj+GA4jJMNUzK2NtKOrUNEtTbXaVyNiXjFCSaDajMTedEB0jTAd9ybB0aBvwhgkJUWkWA==", + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.11.29.tgz", + "integrity": "sha512-i62vBVoPaVe9A3mc6gJG07n0/e7FVeAvdD9uzZTtGLiuIfVfIBta8EMquzvf+POLycSk79Z6lRhGPZPJPYiQaA==", "cpu": [ "x64" ], @@ -1216,9 +1204,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.11.21", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.11.21.tgz", - "integrity": "sha512-qFV60pwpKVOdmX67wqQzgtSrUGWX9Cibnp1CXyqZ9Mmt8UyYGvmGu7p6PMbTyX7vdpVUvWVRf8DzrW2//wmVHg==", + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.11.29.tgz", + "integrity": "sha512-YER0XU1xqFdK0hKkfSVX1YIyCvMDI7K07GIpefPvcfyNGs38AXKhb2byySDjbVxkdl4dycaxxhRyhQ2gKSlsFQ==", "cpu": [ "x64" ], @@ -1233,9 +1221,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.11.21", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.11.21.tgz", - "integrity": "sha512-DJJe9k6gXR/15ZZVLv1SKhXkFst8lYCeZRNHH99SlBodvu4slhh/MKQ6YCixINRhCwliHrpXPym8/5fOq8b7Ig==", + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.11.29.tgz", + "integrity": "sha512-po+WHw+k9g6FAg5IJ+sMwtA/fIUL3zPQ4m/uJgONBATCVnDDkyW6dBA49uHNVtSEvjvhuD8DVWdFP847YTcITw==", "cpu": [ "arm64" ], @@ -1250,9 +1238,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.11.21", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.11.21.tgz", - "integrity": "sha512-TqEXuy6wedId7bMwLIr9byds+mKsaXVHctTN88R1UIBPwJA92Pdk0uxDgip0pEFzHB/ugU27g6d8cwUH3h2eIw==", + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.11.29.tgz", + "integrity": "sha512-h+NjOrbqdRBYr5ItmStmQt6x3tnhqgwbj9YxdGPepbTDamFv7vFnhZR0YfB3jz3UKJ8H3uGJ65Zw1VsC+xpFkg==", "cpu": [ "ia32" ], @@ -1267,9 +1255,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.11.21", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.11.21.tgz", - "integrity": "sha512-BT9BNNbMxdpUM1PPAkYtviaV0A8QcXttjs2MDtOeSqqvSJaPtyM+Fof2/+xSwQDmDEFzbGCcn75M5+xy3lGqpA==", + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.11.29.tgz", + "integrity": "sha512-Q8cs2BDV9wqDvqobkXOYdC+pLUSEpX/KvI0Dgfun1F+LzuLotRFuDhrvkU9ETJA6OnD2+Fn/ieHgloiKA/Mn/g==", "cpu": [ "x64" ], @@ -1315,9 +1303,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "19.1.3", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.3.tgz", - "integrity": "sha512-dLWQ+Z0CkIvK1J8+wrDPwGxEYFA4RAyHoZPxHVGspYmFVnwGSNT24cGIhFJrtfRnWVuW8X7NO52gCXmhkVUWGQ==", + "version": "19.1.5", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.5.tgz", + "integrity": "sha512-piErsCVVbpMMT2r7wbawdZsq4xMvIAhQuac2gedQHysu1TZYEigE6pnFfgZT+/jQnrRuF5r+SHzuehFjfRjr4g==", "devOptional": true, "license": "MIT", "dependencies": { @@ -1325,9 +1313,9 @@ } }, "node_modules/@types/react-dom": { - "version": "19.1.3", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.3.tgz", - "integrity": "sha512-rJXC08OG0h3W6wDMFxQrZF00Kq6qQvw0djHRdzl3U5DnIERz0MRce3WVc7IS6JYBwtaP/DwYtRRjVlvivNveKg==", + "version": "19.1.5", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.5.tgz", + "integrity": "sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg==", "dev": true, "license": "MIT", "peerDependencies": { @@ -1342,19 +1330,19 @@ "optional": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.32.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.32.0.tgz", - "integrity": "sha512-/jU9ettcntkBFmWUzzGgsClEi2ZFiikMX5eEQsmxIAWMOn4H3D4rvHssstmAHGVvrYnaMqdWWWg0b5M6IN/MTQ==", + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.32.1.tgz", + "integrity": "sha512-6u6Plg9nP/J1GRpe/vcjjabo6Uc5YQPAMxsgQyGC/I0RuukiG1wIe3+Vtg3IrSCVJDmqK3j8adrtzXSENRtFgg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.32.0", - "@typescript-eslint/type-utils": "8.32.0", - "@typescript-eslint/utils": "8.32.0", - "@typescript-eslint/visitor-keys": "8.32.0", + "@typescript-eslint/scope-manager": "8.32.1", + "@typescript-eslint/type-utils": "8.32.1", + "@typescript-eslint/utils": "8.32.1", + "@typescript-eslint/visitor-keys": "8.32.1", "graphemer": "^1.4.0", - "ignore": "^5.3.1", + "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, @@ -1371,17 +1359,27 @@ "typescript": ">=4.8.4 <5.9.0" } }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.4.tgz", + "integrity": "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/@typescript-eslint/parser": { - "version": "8.32.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.32.0.tgz", - "integrity": "sha512-B2MdzyWxCE2+SqiZHAjPphft+/2x2FlO9YBx7eKE1BCb+rqBlQdhtAEhzIEdozHd55DXPmxBdpMygFJjfjjA9A==", + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.32.1.tgz", + "integrity": "sha512-LKMrmwCPoLhM45Z00O1ulb6jwyVr2kr3XJp+G+tSEZcbauNnScewcQwtJqXDhXeYPDEjZ8C1SjXm015CirEmGg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.32.0", - "@typescript-eslint/types": "8.32.0", - "@typescript-eslint/typescript-estree": "8.32.0", - "@typescript-eslint/visitor-keys": "8.32.0", + "@typescript-eslint/scope-manager": "8.32.1", + "@typescript-eslint/types": "8.32.1", + "@typescript-eslint/typescript-estree": "8.32.1", + "@typescript-eslint/visitor-keys": "8.32.1", "debug": "^4.3.4" }, "engines": { @@ -1397,14 +1395,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.32.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.32.0.tgz", - "integrity": "sha512-jc/4IxGNedXkmG4mx4nJTILb6TMjL66D41vyeaPWvDUmeYQzF3lKtN15WsAeTr65ce4mPxwopPSo1yUUAWw0hQ==", + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.32.1.tgz", + "integrity": "sha512-7IsIaIDeZn7kffk7qXC3o6Z4UblZJKV3UBpkvRNpr5NSyLji7tvTcvmnMNYuYLyh26mN8W723xpo3i4MlD33vA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.32.0", - "@typescript-eslint/visitor-keys": "8.32.0" + "@typescript-eslint/types": "8.32.1", + "@typescript-eslint/visitor-keys": "8.32.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1415,14 +1413,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.32.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.32.0.tgz", - "integrity": "sha512-t2vouuYQKEKSLtJaa5bB4jHeha2HJczQ6E5IXPDPgIty9EqcJxpr1QHQ86YyIPwDwxvUmLfP2YADQ5ZY4qddZg==", + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.32.1.tgz", + "integrity": "sha512-mv9YpQGA8iIsl5KyUPi+FGLm7+bA4fgXaeRcFKRDRwDMu4iwrSHeDPipwueNXhdIIZltwCJv+NkxftECbIZWfA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.32.0", - "@typescript-eslint/utils": "8.32.0", + "@typescript-eslint/typescript-estree": "8.32.1", + "@typescript-eslint/utils": "8.32.1", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -1439,9 +1437,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.32.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.32.0.tgz", - "integrity": "sha512-O5Id6tGadAZEMThM6L9HmVf5hQUXNSxLVKeGJYWNhhVseps/0LddMkp7//VDkzwJ69lPL0UmZdcZwggj9akJaA==", + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.32.1.tgz", + "integrity": "sha512-YmybwXUJcgGqgAp6bEsgpPXEg6dcCyPyCSr0CAAueacR/CCBi25G3V8gGQ2kRzQRBNol7VQknxMs9HvVa9Rvfg==", "dev": true, "license": "MIT", "engines": { @@ -1453,14 +1451,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.32.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.32.0.tgz", - "integrity": "sha512-pU9VD7anSCOIoBFnhTGfOzlVFQIA1XXiQpH/CezqOBaDppRwTglJzCC6fUQGpfwey4T183NKhF1/mfatYmjRqQ==", + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.32.1.tgz", + "integrity": "sha512-Y3AP9EIfYwBb4kWGb+simvPaqQoT5oJuzzj9m0i6FCY6SPvlomY2Ei4UEMm7+FXtlNJbor80ximyslzaQF6xhg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.32.0", - "@typescript-eslint/visitor-keys": "8.32.0", + "@typescript-eslint/types": "8.32.1", + "@typescript-eslint/visitor-keys": "8.32.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -1480,16 +1478,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.32.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.32.0.tgz", - "integrity": "sha512-8S9hXau6nQ/sYVtC3D6ISIDoJzS1NsCK+gluVhLN2YkBPX+/1wkwyUiDKnxRh15579WoOIyVWnoyIf3yGI9REw==", + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.32.1.tgz", + "integrity": "sha512-DsSFNIgLSrc89gpq1LJB7Hm1YpuhK086DRDJSNrewcGvYloWW1vZLHBTIvarKZDcAORIy/uWNx8Gad+4oMpkSA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.32.0", - "@typescript-eslint/types": "8.32.0", - "@typescript-eslint/typescript-estree": "8.32.0" + "@typescript-eslint/scope-manager": "8.32.1", + "@typescript-eslint/types": "8.32.1", + "@typescript-eslint/typescript-estree": "8.32.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1504,13 +1502,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.32.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.32.0.tgz", - "integrity": "sha512-1rYQTCLFFzOI5Nl0c8LUpJT8HxpwVRn9E4CkMsYfuN6ctmQqExjSTzzSk0Tz2apmXy7WU6/6fyaZVVA/thPN+w==", + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.32.1.tgz", + "integrity": "sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.32.0", + "@typescript-eslint/types": "8.32.1", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -1535,32 +1533,19 @@ } }, "node_modules/@vitejs/plugin-react-swc": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.9.0.tgz", - "integrity": "sha512-jYFUSXhwMCYsh/aQTgSGLIN3Foz5wMbH9ahb0Zva//UzwZYbMiZd7oT3AU9jHT9DLswYDswsRwPU9jVF3yA48Q==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.10.0.tgz", + "integrity": "sha512-ZmkdHw3wo/o/Rk05YsXZs/DJAfY2CdQ5DUAjoWji+PEr+hYADdGMCGgEAILbiKj+CjspBTuTACBcWDrmC8AUfw==", "dev": true, "license": "MIT", "dependencies": { - "@swc/core": "^1.11.21" + "@rolldown/pluginutils": "1.0.0-beta.9", + "@swc/core": "^1.11.22" }, "peerDependencies": { "vite": "^4 || ^5 || ^6" } }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/acorn": { "version": "8.14.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", @@ -1644,27 +1629,6 @@ "dev": true, "license": "MIT" }, - "node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.0", - "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", - "on-finished": "^2.4.1", - "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -1688,47 +1652,6 @@ "node": ">=8" } }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1799,63 +1722,6 @@ "dev": true, "license": "MIT" }, - "node_modules/content-disposition": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1903,90 +1769,15 @@ "dev": true, "license": "MIT" }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/dompurify": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.5.tgz", - "integrity": "sha512-mLPd29uoRe9HpvwP2TxClGQBzGXeEC/we/q+bFlmPPmj2p2Ugl3r6ATu/UU1v77DXNcehiBg9zsr1dREyA/dJQ==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz", + "integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true, - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/esbuild": { "version": "0.25.2", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", @@ -2028,13 +1819,6 @@ "@esbuild/win32-x64": "0.25.2" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true, - "license": "MIT" - }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -2049,9 +1833,9 @@ } }, "node_modules/eslint": { - "version": "9.26.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.26.0.tgz", - "integrity": "sha512-Hx0MOjPh6uK9oq9nVsATZKE/Wlbai7KFjfCuw9UHaguDW3x+HF0O5nIi3ud39TWgrTjTO5nHxmL3R1eANinWHQ==", + "version": "9.27.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.27.0.tgz", + "integrity": "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==", "dev": true, "license": "MIT", "dependencies": { @@ -2059,14 +1843,13 @@ "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.20.0", "@eslint/config-helpers": "^0.2.1", - "@eslint/core": "^0.13.0", + "@eslint/core": "^0.14.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.26.0", - "@eslint/plugin-kit": "^0.2.8", + "@eslint/js": "9.27.0", + "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", - "@modelcontextprotocol/sdk": "^1.8.0", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", @@ -2090,8 +1873,7 @@ "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "zod": "^3.24.2" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" @@ -2294,98 +2076,6 @@ "node": ">=0.10.0" } }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eventsource": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", - "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eventsource-parser": "^3.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/eventsource-parser": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.1.tgz", - "integrity": "sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", - "dev": true, - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.0", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express-rate-limit": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", - "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/express-rate-limit" - }, - "peerDependencies": { - "express": "^4.11 || 5 || ^5.0.0-beta.1" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2473,24 +2163,6 @@ "node": ">=8" } }, - "node_modules/finalhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -2529,26 +2201,6 @@ "dev": true, "license": "ISC" }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2564,55 +2216,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -2639,19 +2242,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -2669,62 +2259,6 @@ "node": ">=8" } }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2762,23 +2296,6 @@ "node": ">=0.8.19" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2812,13 +2329,6 @@ "node": ">=0.12.0" } }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "dev": true, - "license": "MIT" - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2914,39 +2424,6 @@ "dev": true, "license": "MIT" }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -2971,29 +2448,6 @@ "node": ">=8.6" } }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -3043,16 +2497,6 @@ "dev": true, "license": "MIT" }, - "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/npm-run-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", @@ -3083,52 +2527,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -3192,16 +2590,6 @@ "node": ">=6" } }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3222,16 +2610,6 @@ "node": ">=8" } }, - "node_modules/path-to-regexp": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", - "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - } - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -3252,16 +2630,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pkce-challenge": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", - "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16.20.0" - } - }, "node_modules/postcss": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", @@ -3317,20 +2685,6 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -3341,22 +2695,6 @@ "node": ">=6" } }, - "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -3378,32 +2716,6 @@ ], "license": "MIT" }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", - "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.6.3", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/react": { "version": "19.1.0", "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", @@ -3547,23 +2859,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -3588,34 +2883,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, - "license": "MIT" - }, "node_modules/scheduler": { "version": "0.26.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", @@ -3623,9 +2890,9 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, "license": "ISC", "bin": { @@ -3635,58 +2902,12 @@ "node": ">=10" } }, - "node_modules/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.5", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/set-cookie-parser": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", "license": "MIT" }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true, - "license": "ISC" - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -3710,82 +2931,6 @@ "node": ">=8" } }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -3796,16 +2941,6 @@ "node": ">=0.10.0" } }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/strip-ansi": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", @@ -3913,16 +3048,6 @@ "node": ">=8.0" } }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -3949,21 +3074,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", - "dev": true, - "license": "MIT", - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", @@ -3979,15 +3089,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.32.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.32.0.tgz", - "integrity": "sha512-UMq2kxdXCzinFFPsXc9o2ozIpYCCOiEC46MG3yEh5Vipq6BO27otTtEBZA1fQ66DulEUgE97ucQ/3YY66CPg0A==", + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.32.1.tgz", + "integrity": "sha512-D7el+eaDHAmXvrZBy1zpzSNIRqnCOrkwTgZxTu3MUqRWk8k0q9m9Ho4+vPf7iHtgUfrK/o8IZaEApsxPlHTFCg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.32.0", - "@typescript-eslint/parser": "8.32.0", - "@typescript-eslint/utils": "8.32.0" + "@typescript-eslint/eslint-plugin": "8.32.1", + "@typescript-eslint/parser": "8.32.1", + "@typescript-eslint/utils": "8.32.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4014,16 +3124,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -4034,16 +3134,6 @@ "punycode": "^2.1.0" } }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/vite": { "version": "6.3.5", "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", @@ -4255,13 +3345,6 @@ "node": ">=0.10.0" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -4275,30 +3358,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/zod": { - "version": "3.24.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.4.tgz", - "integrity": "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zod-to-json-schema": { - "version": "3.24.5", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", - "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", - "dev": true, - "license": "ISC", - "peerDependencies": { - "zod": "^3.24.1" - } - }, "node_modules/zustand": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.4.tgz", - "integrity": "sha512-39VFTN5InDtMd28ZhjLyuTnlytDr9HfwO512Ai4I8ZABCoyAj4F1+sr7sD1jP/+p7k77Iko0Pb5NhgBFDCX0kQ==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.5.tgz", + "integrity": "sha512-mILtRfKW9xM47hqxGIxCv12gXusoY/xTSHBYApXozR0HmQv299whhBeeAcRy+KrPPybzosvJBCOmVjq6x12fCg==", "license": "MIT", "engines": { "node": ">=12.20.0" diff --git a/frontend/package.json b/frontend/package.json index bf89f32b..6d205445 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,26 +11,26 @@ "preview": "vite preview" }, "dependencies": { - "dompurify": "^3.2.5", + "dompurify": "^3.2.6", "react": "^19.1.0", "react-dom": "^19.1.0", "react-router-dom": "^7.6.0", - "zustand": "^5.0.4" + "zustand": "^5.0.5" }, "devDependencies": { - "@types/react": "^19.1.3", - "@types/react-dom": "^19.1.3", - "@typescript-eslint/eslint-plugin": "^8.32.0", - "@typescript-eslint/parser": "^8.32.0", - "@vitejs/plugin-react-swc": "^3.9.0", - "eslint": "^9.26.0", + "@types/react": "^19.1.5", + "@types/react-dom": "^19.1.5", + "@typescript-eslint/eslint-plugin": "^8.32.1", + "@typescript-eslint/parser": "^8.32.1", + "@vitejs/plugin-react-swc": "^3.10.0", + "eslint": "^9.27.0", "eslint-config-prettier": "^10.1.5", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", "globals": "^16.1.0", "prettier": "3.5.3", "typescript": "^5.8.3", - "typescript-eslint": "^8.32.0", + "typescript-eslint": "^8.32.1", "vite": ">=6.3.5", "vite-plugin-checker": "^0.9.3" } From fd3ccbec3accd610da1f9c50d46bdbe500496ac0 Mon Sep 17 00:00:00 2001 From: MerlinScheurer Date: Sat, 31 May 2025 11:32:33 +0200 Subject: [PATCH 03/16] Fix video player skip 5s did not work without video player focus #898 --- frontend/src/components/VideoPlayer.tsx | 30 ++++++++++++++++++------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/VideoPlayer.tsx b/frontend/src/components/VideoPlayer.tsx index 4778ba4b..152883c5 100644 --- a/frontend/src/components/VideoPlayer.tsx +++ b/frontend/src/components/VideoPlayer.tsx @@ -301,16 +301,25 @@ const VideoPlayer = ({ }, [subtitlesPressed]); useEffect(() => { - if (arrowLeftPressed) { - infoDialog('- 5 seconds'); - } - }, [arrowLeftPressed]); + if (arrowLeftPressed || arrowRightPressed) { + let timeStep = 5; - useEffect(() => { - if (arrowRightPressed) { - infoDialog('+ 5 seconds'); + if (arrowLeftPressed) { + infoDialog('- 5 seconds'); + timeStep *= -1; + } + + if (arrowRightPressed) { + infoDialog('+ 5 seconds'); + } + + const currentCurrentTime = videoRef.current?.currentTime; + + if (currentCurrentTime !== undefined && videoRef.current) { + videoRef.current.currentTime = currentCurrentTime + timeStep; + } } - }, [arrowRightPressed]); + }, [arrowLeftPressed, arrowRightPressed]); useEffect(() => { if (questionmarkPressed) { @@ -361,6 +370,11 @@ const VideoPlayer = ({ }); }} onEnded={handleVideoEnd(videoId, watched)} + onKeyDown={e => { + if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') { + e.preventDefault(); + } + }} autoPlay={autoplay} controls width="100%" From 02b5ed6917f51ce977f04208ca66dab076a27b75 Mon Sep 17 00:00:00 2001 From: Jurrer <72750942+Jurrer@users.noreply.github.com> Date: Mon, 2 Jun 2025 16:41:48 +0200 Subject: [PATCH 04/16] Add Ignored tab to Channel page (#927) * Add Ignored tab to Channel page * fix for Ignored button action * use ignored parameter as bool --------- Co-authored-by: Simon --- backend/channel/serializers.py | 1 + backend/channel/src/nav.py | 19 +++++++++++++++++++ frontend/src/api/loader/loadChannelNav.ts | 1 + .../src/configuration/routes/RouteList.ts | 3 ++- frontend/src/pages/ChannelBase.tsx | 7 ++++++- frontend/src/pages/Download.tsx | 7 ++++++- 6 files changed, 35 insertions(+), 3 deletions(-) diff --git a/backend/channel/serializers.py b/backend/channel/serializers.py index 568e4eed..7b2a9ec8 100644 --- a/backend/channel/serializers.py +++ b/backend/channel/serializers.py @@ -90,6 +90,7 @@ class ChannelNavSerializer(serializers.Serializer): """serialize channel navigation""" has_pending = serializers.BooleanField() + has_ignored = serializers.BooleanField() has_playlists = serializers.BooleanField() has_videos = serializers.BooleanField() has_streams = serializers.BooleanField() diff --git a/backend/channel/src/nav.py b/backend/channel/src/nav.py index 6ff3e047..44b15f8b 100644 --- a/backend/channel/src/nav.py +++ b/backend/channel/src/nav.py @@ -13,6 +13,7 @@ class ChannelNav: """build nav items""" nav = { "has_pending": self._get_has_pending(), + "has_ignored": self._get_has_ignored(), "has_playlists": self._get_has_playlists(), } nav.update(self._get_vid_types()) @@ -63,6 +64,24 @@ class ChannelNav: return bool(response["hits"]["hits"]) + def _get_has_ignored(self): + """Check if there are ignored videos in the download queue""" + data = { + "size": 1, + "query": { + "bool": { + "must": [ + {"term": {"status": {"value": "ignore"}}}, + {"term": {"channel_id": {"value": self.channel_id}}}, + ] + } + }, + "_source": False, + } + response, _ = ElasticWrap("ta_download/_search").get(data=data) + + return bool(response["hits"]["hits"]) + def _get_has_playlists(self): """check if channel has playlists""" path = "ta_playlist/_search" diff --git a/frontend/src/api/loader/loadChannelNav.ts b/frontend/src/api/loader/loadChannelNav.ts index 26c97c10..361e0d38 100644 --- a/frontend/src/api/loader/loadChannelNav.ts +++ b/frontend/src/api/loader/loadChannelNav.ts @@ -5,6 +5,7 @@ export type ChannelNavResponseType = { has_shorts: boolean; has_playlists: boolean; has_pending: boolean; + has_ignored: boolean; }; const loadChannelNav = async (youtubeChannelId: string) => { diff --git a/frontend/src/configuration/routes/RouteList.ts b/frontend/src/configuration/routes/RouteList.ts index 41f36558..df1513a5 100644 --- a/frontend/src/configuration/routes/RouteList.ts +++ b/frontend/src/configuration/routes/RouteList.ts @@ -10,7 +10,8 @@ const Routes = { Playlists: '/playlist/', Playlist: (id: string) => `/playlist/${id}`, Downloads: '/downloads/', - DownloadsByChannelId: (channelId: string) => `/downloads/?channel=${channelId}`, + DownloadsByChannelId: (channelId: string) => `/downloads/?channel=${channelId}&ignored=false`, + IgnoredByChannelId: (channelId: string) => `/downloads/?channel=${channelId}&ignored=true`, Search: '/search/', SettingsDashboard: '/settings/', SettingsUser: '/settings/user/', diff --git a/frontend/src/pages/ChannelBase.tsx b/frontend/src/pages/ChannelBase.tsx index 05bf21af..aba6e579 100644 --- a/frontend/src/pages/ChannelBase.tsx +++ b/frontend/src/pages/ChannelBase.tsx @@ -27,7 +27,7 @@ const ChannelBase = () => { const { data: channelNavData } = channelNav ?? {}; const channel = channelResponseData; - const { has_streams, has_shorts, has_playlists, has_pending } = channelNavData || {}; + const { has_streams, has_shorts, has_playlists, has_pending, has_ignored } = channelNavData || {}; useEffect(() => { (async () => { @@ -84,6 +84,11 @@ const ChannelBase = () => {

Downloads

)} + {has_ignored && isAdmin && ( + +

Ignored

+ + )} { const { currentPage, setCurrentPage } = useOutletContext() as OutletContextType; const channelFilterFromUrl = searchParams.get('channel'); + const ignoredOnlyParam = searchParams.get('ignored'); const [refresh, setRefresh] = useState(false); const [showHiddenForm, setShowHiddenForm] = useState(false); @@ -82,7 +83,8 @@ const Download = () => { const view = userConfig.view_style_downloads; const gridItems = userConfig.grid_items; - const showIgnored = userConfig.show_ignored_only; + const showIgnored = + ignoredOnlyParam !== null ? ignoredOnlyParam === 'true' : userConfig.show_ignored_only; const isGridView = view === ViewStyles.grid; const gridView = isGridView ? `boxed-${gridItems}` : ''; const gridViewGrid = isGridView ? `grid-${gridItems}` : ''; @@ -234,6 +236,9 @@ const Download = () => { id="showIgnored" onChange={() => { handleUserConfigUpdate({ show_ignored_only: !showIgnored }); + const newParams = new URLSearchParams(searchParams.toString()); + newParams.set('ignored', String(!showIgnored)); + setSearchParams(newParams); setRefresh(true); }} type="checkbox" From efe1401518c80ff336b4b8d5cc2263b2831c96ba Mon Sep 17 00:00:00 2001 From: krufab <10956489+krufab@users.noreply.github.com> Date: Mon, 2 Jun 2025 16:55:59 +0200 Subject: [PATCH 05/16] Bugfix #934: Download a video from the queue (#935) * Bugfix #934: Download a video from the queue Signed-off-by: Fabio Kruger <10956489+krufab@users.noreply.github.com> * Bugfix #934: Download a video from the queue Signed-off-by: Fabio Kruger <10956489+krufab@users.noreply.github.com> * Bugfix #934: Download a video from the queue Signed-off-by: Fabio Kruger <10956489+krufab@users.noreply.github.com> * Bugfix #934: Download a video from the queue Signed-off-by: Fabio Kruger <10956489+krufab@users.noreply.github.com> * Bugfix #934: Download a video from the queue Signed-off-by: Fabio Kruger <10956489+krufab@users.noreply.github.com> * Bugfix #934: Download a video from the queue Signed-off-by: Fabio Kruger <10956489+krufab@users.noreply.github.com> * Improved code * Improved code Signed-off-by: Fabio Kruger <10956489+krufab@users.noreply.github.com> * Fixed file because of black pre-commit Signed-off-by: Fabio Kruger <10956489+krufab@users.noreply.github.com> * Fixed file because of black pre-commit Signed-off-by: Fabio Kruger <10956489+krufab@users.noreply.github.com> --------- Signed-off-by: Fabio Kruger <10956489+krufab@users.noreply.github.com> --- backend/download/src/queue.py | 16 +++++++++++----- backend/task/tasks.py | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/backend/download/src/queue.py b/backend/download/src/queue.py index eae697d5..08be6906 100644 --- a/backend/download/src/queue.py +++ b/backend/download/src/queue.py @@ -167,14 +167,14 @@ class PendingList(PendingIndex): self.to_skip = False self.missing_videos = False - def parse_url_list(self): + def parse_url_list(self, auto_start=False): """extract youtube ids from list""" self.missing_videos = [] self.get_download() self.get_indexed() total = len(self.youtube_ids) for idx, entry in enumerate(self.youtube_ids): - self._process_entry(entry) + self._process_entry(entry, auto_start=auto_start) if not self.task: continue @@ -183,11 +183,11 @@ class PendingList(PendingIndex): progress=(idx + 1) / total, ) - def _process_entry(self, entry): + def _process_entry(self, entry, auto_start=False): """process single entry from url list""" vid_type = self._get_vid_type(entry) if entry["type"] == "video": - self._add_video(entry["url"], vid_type) + self._add_video(entry["url"], vid_type, auto_start=auto_start) elif entry["type"] == "channel": self._parse_channel(entry["url"], vid_type) elif entry["type"] == "playlist": @@ -204,8 +204,14 @@ class PendingList(PendingIndex): return VideoTypeEnum(vid_type_str) - def _add_video(self, url, vid_type): + def _add_video(self, url, vid_type, auto_start=False): """add video to list""" + if auto_start and url in set( + i["youtube_id"] for i in self.all_pending + ): + PendingInteract(youtube_id=url, status="priority").update_status() + return + if url not in self.missing_videos and url not in self.to_skip: self.missing_videos.append((url, vid_type)) else: diff --git a/backend/task/tasks.py b/backend/task/tasks.py index 31d7eb83..525938b4 100644 --- a/backend/task/tasks.py +++ b/backend/task/tasks.py @@ -156,7 +156,7 @@ def extrac_dl(self, youtube_ids, auto_start=False, status="pending"): to_add = youtube_ids pending_handler = PendingList(youtube_ids=to_add, task=self) - pending_handler.parse_url_list() + pending_handler.parse_url_list(auto_start=auto_start) videos_added = pending_handler.add_to_pending( status=status, auto_start=auto_start ) From 58b4e22df4f3c4d584e5485c2a8a722c21a52c80 Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 2 Jun 2025 22:47:21 +0700 Subject: [PATCH 06/16] serialize watched_date, #913 --- backend/video/serializers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/video/serializers.py b/backend/video/serializers.py index 5e0be9f8..afd8b0b4 100644 --- a/backend/video/serializers.py +++ b/backend/video/serializers.py @@ -12,6 +12,7 @@ class PlayerSerializer(serializers.Serializer): """serialize player""" watched = serializers.BooleanField() + watched_date = serializers.IntegerField(required=False) duration = serializers.IntegerField() duration_str = serializers.CharField() progress = serializers.FloatField(required=False) From a82e4b1c51b19fa9be26dcd9119327bf41ef478d Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 2 Jun 2025 23:36:37 +0700 Subject: [PATCH 07/16] add play/pause toggle shortcut key, #898 --- frontend/src/components/VideoPlayer.tsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/frontend/src/components/VideoPlayer.tsx b/frontend/src/components/VideoPlayer.tsx index 152883c5..7684e24a 100644 --- a/frontend/src/components/VideoPlayer.tsx +++ b/frontend/src/components/VideoPlayer.tsx @@ -150,6 +150,7 @@ const VideoPlayer = ({ const resetPlaybackSpeedPressed = useKeyPress('='); const arrowRightPressed = useKeyPress('ArrowRight'); const arrowLeftPressed = useKeyPress('ArrowLeft'); + const pPausedPressed = useKeyPress('p'); const videoId = video.youtube_id; const videoUrl = video.media_url; @@ -214,6 +215,16 @@ const VideoPlayer = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [mutePressed]); + useEffect(() => { + if (pPausedPressed) { + if (videoRef.current?.paused) { + videoRef.current.play(); + } else { + videoRef.current?.pause(); + } + } + }, [pPausedPressed]); + useEffect(() => { if (increasePlaybackSpeedPressed) { const newSpeed = playbackSpeedIndex + 1; @@ -400,6 +411,10 @@ const VideoPlayer = ({ Show help ? + + Toggle pause play + p + Toggle mute m From 37a6922718eb8036270cbda90e8f427f1ed22733 Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 2 Jun 2025 23:48:45 +0700 Subject: [PATCH 08/16] ES check handle no shards, fail on none 404 status for appsettings default, #975 --- backend/config/management/commands/ta_connection.py | 7 ++++++- backend/config/management/commands/ta_startup.py | 9 ++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/backend/config/management/commands/ta_connection.py b/backend/config/management/commands/ta_connection.py index 3edb42db..4ad511d1 100644 --- a/backend/config/management/commands/ta_connection.py +++ b/backend/config/management/commands/ta_connection.py @@ -99,7 +99,12 @@ class Command(BaseCommand): continue if status_code and status_code == 200: - path = "_cluster/health?wait_for_status=yellow&timeout=60s" + path = ( + "_cluster/health?" + "wait_for_status=yellow&" + "timeout=60s&" + "wait_for_active_shards=1" + ) _, _ = ElasticWrap(path).get(timeout=60) self.stdout.write( self.style.SUCCESS(" ✓ ES connection established") diff --git a/backend/config/management/commands/ta_startup.py b/backend/config/management/commands/ta_startup.py index e80af6b7..980354b1 100644 --- a/backend/config/management/commands/ta_startup.py +++ b/backend/config/management/commands/ta_startup.py @@ -263,7 +263,7 @@ class Command(BaseCommand): def _init_app_config(self) -> None: """init default app config to ES""" self.stdout.write("[10] Check AppConfig") - _, status_code = ElasticWrap("ta_config/_doc/appsettings").get() + response, status_code = ElasticWrap("ta_config/_doc/appsettings").get() if status_code in [200, 201]: self.stdout.write( self.style.SUCCESS(" skip completed appsettings init") @@ -276,6 +276,13 @@ class Command(BaseCommand): return + if status_code != 404: + message = " 🗙 ta_config index lookup failed" + self.stdout.write(self.style.ERROR(message)) + self.stdout.write(response) + sleep(60) + raise CommandError(message) + handler = AppConfig.__new__(AppConfig) _, status_code = handler.sync_defaults() self.stdout.write( From ec5204cd6cc93209b3e91b5b73df22f5feb135d0 Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 3 Jun 2025 20:19:13 +0700 Subject: [PATCH 09/16] delay import, fix circular problems --- backend/channel/src/index.py | 5 ++++- backend/playlist/src/index.py | 5 +++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/backend/channel/src/index.py b/backend/channel/src/index.py index 929aa5c9..65503ef7 100644 --- a/backend/channel/src/index.py +++ b/backend/channel/src/index.py @@ -14,7 +14,6 @@ from common.src.helper import rand_sleep from common.src.index_generic import YouTubeItem from download.src.thumbnails import ThumbManager from download.src.yt_dlp_base import YtWrap -from playlist.src.index import YoutubePlaylist class YoutubeChannel(YouTubeItem): @@ -215,6 +214,8 @@ class YoutubeChannel(YouTubeItem): def delete_playlists(self): """delete all indexed playlist from es""" + from playlist.src.index import YoutubePlaylist + all_playlists = self.get_indexed_playlists() for playlist in all_playlists: YoutubePlaylist(playlist["playlist_id"]).delete_metadata() @@ -277,6 +278,8 @@ class YoutubeChannel(YouTubeItem): @staticmethod def _index_single_playlist(playlist): """add single playlist if needed""" + from playlist.src.index import YoutubePlaylist + playlist = YoutubePlaylist(playlist[0]) playlist.update_playlist(skip_on_empty=True) diff --git a/backend/playlist/src/index.py b/backend/playlist/src/index.py index 584342b3..3533205a 100644 --- a/backend/playlist/src/index.py +++ b/backend/playlist/src/index.py @@ -7,7 +7,6 @@ functionality: import json from datetime import datetime -from channel.src import index as channel from common.src.env_settings import EnvironmentSettings from common.src.es_connect import ElasticWrap, IndexPaginate from common.src.index_generic import YouTubeItem @@ -74,8 +73,10 @@ class YoutubePlaylist(YouTubeItem): def _ensure_channel(self): """make sure channel is indexed""" + from channel.src.index import YoutubeChannel + channel_id = self.json_data["playlist_channel_id"] - channel_handler = channel.YoutubeChannel(channel_id) + channel_handler = YoutubeChannel(channel_id) channel_handler.build_json(upload=True) def get_local_vids(self) -> list[str]: From a961c8f17578debad5dd418b9d17afb6bd00de52 Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 3 Jun 2025 20:40:58 +0700 Subject: [PATCH 10/16] sort playlist videos by idx, #889 --- backend/video/src/query_building.py | 42 +++++++++++++++++++ .../tests/test_src/test_query_building.py | 1 - 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/backend/video/src/query_building.py b/backend/video/src/query_building.py index 248a0e44..fe4bc0b7 100644 --- a/backend/video/src/query_building.py +++ b/backend/video/src/query_building.py @@ -1,6 +1,7 @@ """build query for video fetching""" from common.src.ta_redis import RedisArchivist +from playlist.src.index import YoutubePlaylist from video.src.constants import OrderEnum, SortEnum, VideoTypeEnum @@ -84,6 +85,11 @@ class QueryBuilder: def parse_sort(self) -> dict | None: """build sort key""" + playlist = self.request_params.get("playlist") + if playlist: + # overwrite sort based on idx in playlist + return self._get_playlist_sort(playlist_id=playlist) + sort = self.request_params.get("sort") if not sort: return None @@ -100,3 +106,39 @@ class QueryBuilder: order_by = getattr(OrderEnum, order.upper()).value return {"sort": [{sort_field: {"order": order_by}}]} + + def _get_playlist_sort(self, playlist_id: str): + """get sort for playlist""" + playlist = YoutubePlaylist(playlist_id) + playlist.get_from_es() + if not playlist.json_data: + raise ValueError(f"playlist {playlist_id} not found") + + sort_score = { + i["youtube_id"]: i["idx"] + for i in playlist.json_data["playlist_entries"] + if i["downloaded"] + } + script = ( + "if(params.scores.containsKey(doc['youtube_id'].value)) " + + "{return params.scores[doc['youtube_id'].value];} " + + "return 100000;" + ) + + sort = { + "sort": [ + { + "_script": { + "type": "number", + "script": { + "lang": "painless", + "source": script, + "params": {"scores": sort_score}, + }, + "order": "asc", + } + } + ], + } + + return sort diff --git a/backend/video/tests/test_src/test_query_building.py b/backend/video/tests/test_src/test_query_building.py index 606d07c1..8dcbf50e 100644 --- a/backend/video/tests/test_src/test_query_building.py +++ b/backend/video/tests/test_src/test_query_building.py @@ -16,7 +16,6 @@ def test_build_data(): qb = QueryBuilder( user_id=1, channel="test_channel", - playlist="test_playlist", watch="watched", type="videos", sort="published", From 62ea518e1bed87481dcd495d6870cc488e7cd664 Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 3 Jun 2025 20:58:57 +0700 Subject: [PATCH 11/16] use check_formats only on download, not in info extract --- backend/download/src/yt_dlp_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/download/src/yt_dlp_base.py b/backend/download/src/yt_dlp_base.py index 05043123..c80f9d7d 100644 --- a/backend/download/src/yt_dlp_base.py +++ b/backend/download/src/yt_dlp_base.py @@ -20,7 +20,6 @@ class YtWrap: OBS_BASE = { "default_search": "ytsearch", "quiet": True, - "check_formats": "selected", "socket_timeout": 10, "extractor_retries": 3, "retries": 10, @@ -66,6 +65,7 @@ class YtWrap: def download(self, url): """make download request""" + self.obs.update({"check_formats": "selected"}) with yt_dlp.YoutubeDL(self.obs) as ydl: try: ydl.download([url]) From d196d2e4f5d6156079ff26b3d7f7fe86ea159d67 Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 3 Jun 2025 21:41:57 +0700 Subject: [PATCH 12/16] index date published as iso timestamp, #902 --- backend/common/src/helper.py | 7 ++++--- backend/common/tests/test_src/test_helper.py | 4 ++-- backend/download/src/queue.py | 18 ++++++++++++++---- backend/video/src/index.py | 17 +++++++++++++---- 4 files changed, 33 insertions(+), 13 deletions(-) diff --git a/backend/common/src/helper.py b/backend/common/src/helper.py index 263018db..fcf75dfe 100644 --- a/backend/common/src/helper.py +++ b/backend/common/src/helper.py @@ -8,7 +8,7 @@ import os import random import string import subprocess -from datetime import datetime +from datetime import datetime, timezone from time import sleep from typing import Any from urllib.parse import urlparse @@ -106,13 +106,14 @@ def requests_headers() -> dict[str, str]: def date_parser(timestamp: int | str) -> str: """return formatted date string""" if isinstance(timestamp, int): - date_obj = datetime.fromtimestamp(timestamp) + date_obj = datetime.fromtimestamp(timestamp, tz=timezone.utc) elif isinstance(timestamp, str): date_obj = datetime.strptime(timestamp, "%Y-%m-%d") + date_obj = date_obj.replace(tzinfo=timezone.utc) else: raise TypeError(f"invalid timestamp: {timestamp}") - return date_obj.date().isoformat() + return date_obj.isoformat() def time_parser(timestamp: str) -> float: diff --git a/backend/common/tests/test_src/test_helper.py b/backend/common/tests/test_src/test_helper.py index e99808cd..013e774b 100644 --- a/backend/common/tests/test_src/test_helper.py +++ b/backend/common/tests/test_src/test_helper.py @@ -22,14 +22,14 @@ def test_randomizor_with_positive_length(): def test_date_parser_with_int(): """unix timestamp""" timestamp = 1621539600 - expected_date = "2021-05-20" + expected_date = "2021-05-20T19:40:00+00:00" assert date_parser(timestamp) == expected_date def test_date_parser_with_str(): """iso timestamp""" date_str = "2021-05-21" - expected_date = "2021-05-21" + expected_date = "2021-05-21T00:00:00+00:00" assert date_parser(date_str) == expected_date diff --git a/backend/download/src/queue.py b/backend/download/src/queue.py index 08be6906..7110ceaf 100644 --- a/backend/download/src/queue.py +++ b/backend/download/src/queue.py @@ -336,9 +336,6 @@ class PendingList(PendingIndex): def _parse_youtube_details(self, vid, vid_type=VideoTypeEnum.VIDEOS): """parse response""" vid_id = vid.get("id") - published = datetime.strptime(vid["upload_date"], "%Y%m%d").strftime( - "%Y-%m-%d" - ) # build dict youtube_details = { @@ -348,10 +345,23 @@ class PendingList(PendingIndex): "title": vid["title"], "channel_id": vid["channel_id"], "duration": get_duration_str(vid["duration"]), - "published": published, + "published": self._build_published(vid), "timestamp": int(datetime.now().timestamp()), "vid_type": vid_type.value, "channel_indexed": vid["channel_id"] in self.all_channels, } return youtube_details + + @staticmethod + def _build_published(vid): + """build published date or timestamp""" + timestamp = vid["timestamp"] + if timestamp: + return timestamp + + upload_date = vid["upload_date"] + upload_date_time = datetime.strptime(upload_date, "%Y%m%d") + published = upload_date_time.strftime("%Y-%m-%d") + + return published diff --git a/backend/video/src/index.py b/backend/video/src/index.py index 42ce02ac..01b8435b 100644 --- a/backend/video/src/index.py +++ b/backend/video/src/index.py @@ -173,9 +173,6 @@ class YoutubeVideo(YouTubeItem, YoutubeSubtitle): self._validate_id() # extract self.channel_id = self.youtube_meta["channel_id"] - upload_date = self.youtube_meta["upload_date"] - upload_date_time = datetime.strptime(upload_date, "%Y%m%d") - published = upload_date_time.strftime("%Y-%m-%d") last_refresh = int(datetime.now().timestamp()) # base64_blur = ThumbManager().get_base64_blur(self.youtube_id) base64_blur = False @@ -187,7 +184,7 @@ class YoutubeVideo(YouTubeItem, YoutubeSubtitle): "vid_thumb_url": self.youtube_meta["thumbnail"], "vid_thumb_base64": base64_blur, "tags": self.youtube_meta.get("tags", []), - "published": published, + "published": self._build_published(), "vid_last_refresh": last_refresh, "date_downloaded": last_refresh, "youtube_id": self.youtube_id, @@ -196,6 +193,18 @@ class YoutubeVideo(YouTubeItem, YoutubeSubtitle): "active": True, } + def _build_published(self): + """build published date or timestamp""" + timestamp = self.youtube_meta["timestamp"] + if timestamp: + return timestamp + + upload_date = self.youtube_meta["upload_date"] + upload_date_time = datetime.strptime(upload_date, "%Y%m%d") + published = upload_date_time.strftime("%Y-%m-%d") + + return published + def _validate_id(self): """validate expected video ID, raise value error on mismatch""" remote_id = self.youtube_meta["id"] From 8c59d65ee53070fe48273e5532cf05bb7f79b179 Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 3 Jun 2025 23:03:13 +0700 Subject: [PATCH 13/16] auto restart beat after 1h, #967 --- Dockerfile | 1 + docker_assets/beat_auto_spawn.sh | 32 ++++++++++++++++++++++++++++++++ docker_assets/run.sh | 13 ++++++++++--- 3 files changed, 43 insertions(+), 3 deletions(-) create mode 100755 docker_assets/beat_auto_spawn.sh diff --git a/Dockerfile b/Dockerfile index 950be92b..b8260679 100644 --- a/Dockerfile +++ b/Dockerfile @@ -70,6 +70,7 @@ RUN sed -i 's/^user www\-data\;$/user root\;/' /etc/nginx/nginx.conf COPY ./backend /app COPY ./docker_assets/run.sh /app COPY ./docker_assets/backend_start.py /app +COPY ./docker_assets/beat_auto_spawn.sh /app COPY --from=node-builder ./frontend/dist /app/static diff --git a/docker_assets/beat_auto_spawn.sh b/docker_assets/beat_auto_spawn.sh new file mode 100755 index 00000000..16002afc --- /dev/null +++ b/docker_assets/beat_auto_spawn.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# auto restart beat scheduler +# https://github.com/celery/django-celery-beat/issues/894 + +if [[ -n "$DJANGO_DEBUG" ]]; then + LOGLEVEL="DEBUG" +else + LOGLEVEL="INFO" +fi + +COMMAND="celery -A task beat --loglevel=$LOGLEVEL --scheduler django_celery_beat.schedulers:DatabaseScheduler" +TIMEOUT=3600 + +while true; do + echo "Starting process beat scheduler" + + $COMMAND & + PID=$! + + sleep $TIMEOUT + + # Kill the process if still running + if kill -0 $PID 2>/dev/null; then + echo "Killing beat process after $TIMEOUT seconds" + kill $PID + # Wait a bit to allow graceful shutdown, then force kill if needed + sleep 10 + kill -9 $PID 2>/dev/null + fi + + echo "Restarting beat..." +done diff --git a/docker_assets/run.sh b/docker_assets/run.sh index cabc6afb..9f065d7b 100644 --- a/docker_assets/run.sh +++ b/docker_assets/run.sh @@ -3,6 +3,12 @@ set -e +if [[ -n "$DJANGO_DEBUG" ]]; then + LOGLEVEL="DEBUG" +else + LOGLEVEL="INFO" +fi + # stop on pending manual migration python manage.py ta_stop_on_error @@ -18,10 +24,11 @@ python manage.py ta_startup # start all tasks nginx & celery -A task.celery worker \ - --loglevel=INFO \ + --loglevel=$LOGLEVEL \ --concurrency 4 \ --max-tasks-per-child 5 \ --max-memory-per-child 150000 & -celery -A task beat --loglevel=INFO \ - --scheduler django_celery_beat.schedulers:DatabaseScheduler & + +./beat_auto_spawn.sh & + python backend_start.py From 8a227fc9b8698b217b403ad00589822c3eced0da Mon Sep 17 00:00:00 2001 From: joshrivers Date: Tue, 3 Jun 2025 09:19:12 -0700 Subject: [PATCH 14/16] Multiple Authentication Backends can be configured (#970) --- README.md | 69 +++++++++++++++++++++----------------- backend/config/settings.py | 24 +++++++++++-- 2 files changed, 61 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index b63fbd68..7d3b2a96 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![Tube Archivist](assets/tube-archivist-front.jpg?raw=true "Tube Archivist Banner") +![Tube Archivist](assets/tube-archivist-front.jpg?raw=true "Tube Archivist Banner") [*more screenshots and video*](SHOWCASE.MD)
@@ -23,13 +23,13 @@ ------------------------ ## Core functionality -Once your YouTube video collection grows, it becomes hard to search and find a specific video. That's where Tube Archivist comes in: By indexing your video collection with metadata from YouTube, you can organize, search and enjoy your archived YouTube videos without hassle offline through a convenient web interface. This includes: +Once your YouTube video collection grows, it becomes hard to search and find a specific video. That's where Tube Archivist comes in: By indexing your video collection with metadata from YouTube, you can organize, search and enjoy your archived YouTube videos without hassle offline through a convenient web interface. This includes: * Subscribe to your favorite YouTube channels * Download Videos using **[yt-dlp](https://github.com/yt-dlp/yt-dlp)** * Index and make videos searchable * Play videos * Keep track of viewed and unviewed videos - + ## Resources - [Discord](https://www.tubearchivist.com/discord): Connect with us on our Discord server. - [r/TubeArchivist](https://www.reddit.com/r/TubeArchivist/): Join our Subreddit. @@ -38,8 +38,8 @@ Once your YouTube video collection grows, it becomes hard to search and find a s - [Plex Plugin](https://github.com/tubearchivist/tubearchivist-plex): Add your videos to Plex ## Installing -For minimal system requirements, the Tube Archivist stack needs around 2GB of available memory for a small testing setup and around 4GB of available memory for a mid to large sized installation. Minimal with dual core with 4 threads, better quad core plus. -This project requires docker. Ensure it is installed and running on your system. +For minimal system requirements, the Tube Archivist stack needs around 2GB of available memory for a small testing setup and around 4GB of available memory for a mid to large sized installation. Minimal with dual core with 4 threads, better quad core plus. +This project requires docker. Ensure it is installed and running on your system. The documentation has additional user provided instructions for [Unraid](https://docs.tubearchivist.com/installation/unraid/), [Synology](https://docs.tubearchivist.com/installation/synology/) and [Podman](https://docs.tubearchivist.com/installation/podman/). @@ -49,7 +49,7 @@ Take a look at the example [docker-compose.yml](https://github.com/tubearchivist All environment variables are explained in detail in the docs [here](https://docs.tubearchivist.com/installation/env-vars/). -**TubeArchivist**: +**TubeArchivist**: | Environment Var | Value | | | ----------- | ----------- | ----------- | | TA_HOST | Server IP or hostname `http://tubearchivist.local:8000` | Required | @@ -72,8 +72,17 @@ All environment variables are explained in detail in the docs [here](https://doc | TA_LDAP | Configure TA to use LDAP Authentication | [Read more](https://docs.tubearchivist.com/configuration/ldap/) | | DISABLE_STATIC_AUTH | Remove authentication from media files, (Google Cast...) | [Read more](https://docs.tubearchivist.com/installation/env-vars/#disable_static_auth) | | DJANGO_DEBUG | Return additional error messages, for debug only | Optional | +| TA_LOGIN_AUTH_MODE | Configure the order of login authentication backends (Default: single) | Optional | -**ElasticSearch** +| TA_LOGIN_AUTH_MODE value | Description | +| ------------------------ | ----------- | +| single | Only use a single backend (default, or LDAP, or Forward auth, selected by TA_LDAP or TA_ENABLE_AUTH_PROXY) | +| local | Use local password database only | +| ldap | Use LDAP backend only | +| forwardauth | Use reverse proxy headers only | +| ldap_local | Use LDAP backend in addition to the local password database | + +**ElasticSearch** | Environment Var | Value | State | | ----------- | ----------- | ----------- | | ELASTIC_PASSWORD | Matching password `ELASTIC_PASSWORD` from TubeArchivist | Required | @@ -81,58 +90,58 @@ All environment variables are explained in detail in the docs [here](https://doc ## Update -Always use the *latest* (the default) or a named semantic version tag for the docker images. The *unstable* tags are only for your testing environment, there might not be an update path for these testing builds. +Always use the *latest* (the default) or a named semantic version tag for the docker images. The *unstable* tags are only for your testing environment, there might not be an update path for these testing builds. -You will see the current version number of **Tube Archivist** in the footer of the interface. There is a daily version check task querying tubearchivist.com, notifying you of any new releases in the footer. To update, you need to update the docker images, the method for which will depend on your platform. For example, if you're using `docker-compose`, run `docker-compose pull` and then restart with `docker-compose up -d`. After updating, check the footer to verify you are running the expected version. +You will see the current version number of **Tube Archivist** in the footer of the interface. There is a daily version check task querying tubearchivist.com, notifying you of any new releases in the footer. To update, you need to update the docker images, the method for which will depend on your platform. For example, if you're using `docker-compose`, run `docker-compose pull` and then restart with `docker-compose up -d`. After updating, check the footer to verify you are running the expected version. - - This project is tested for updates between one or two releases maximum. Further updates back may or may not be supported and you might have to reset your index and configurations to update. Ideally apply new updates at least once per month. - - There can be breaking changes between updates, particularly as the application grows, new environment variables or settings might be required for you to set in the your docker-compose file. *Always* check the **release notes**: Any breaking changes will be marked there. - - All testing and development is done with the Elasticsearch version number as mentioned in the provided *docker-compose.yml* file. This will be updated when a new release of Elasticsearch is available. Running an older version of Elasticsearch is most likely not going to result in any issues, but it's still recommended to run the same version as mentioned. Use `bbilly1/tubearchivist-es` to automatically get the recommended version. + - This project is tested for updates between one or two releases maximum. Further updates back may or may not be supported and you might have to reset your index and configurations to update. Ideally apply new updates at least once per month. + - There can be breaking changes between updates, particularly as the application grows, new environment variables or settings might be required for you to set in the your docker-compose file. *Always* check the **release notes**: Any breaking changes will be marked there. + - All testing and development is done with the Elasticsearch version number as mentioned in the provided *docker-compose.yml* file. This will be updated when a new release of Elasticsearch is available. Running an older version of Elasticsearch is most likely not going to result in any issues, but it's still recommended to run the same version as mentioned. Use `bbilly1/tubearchivist-es` to automatically get the recommended version. ## Getting Started 1. Go through the **settings** page and look at the available options. Particularly set *Download Format* to your desired video quality before downloading. **Tube Archivist** downloads the best available quality by default. To support iOS or MacOS and some other browsers a compatible format must be specified. For example: ``` bestvideo[vcodec*=avc1]+bestaudio[acodec*=mp4a]/mp4 ``` -2. Subscribe to some of your favorite YouTube channels on the **channels** page. +2. Subscribe to some of your favorite YouTube channels on the **channels** page. 3. On the **downloads** page, click on *Rescan subscriptions* to add videos from the subscribed channels to your Download queue or click on *Add to download queue* to manually add Video IDs, links, channels or playlists. -4. Click on *Start download* and let **Tube Archivist** to it's thing. +4. Click on *Start download* and let **Tube Archivist** to it's thing. 5. Enjoy your archived collection! -### Port Collisions -If you have a collision on port `8000`, best solution is to use dockers *HOST_PORT* and *CONTAINER_PORT* distinction: To for example change the interface to port 9000 use `9000:8000` in your docker-compose file. +### Port Collisions +If you have a collision on port `8000`, best solution is to use dockers *HOST_PORT* and *CONTAINER_PORT* distinction: To for example change the interface to port 9000 use `9000:8000` in your docker-compose file. For more information on port collisions, check the docs. -## Common Errors -Here is a list of common errors and their solutions. +## Common Errors +Here is a list of common errors and their solutions. ### `vm.max_map_count` **Elastic Search** in Docker requires the kernel setting of the host machine `vm.max_map_count` to be set to at least 262144. -To temporary set the value run: +To temporary set the value run: ``` sudo sysctl -w vm.max_map_count=262144 -``` -To apply the change permanently depends on your host operating system: +``` +To apply the change permanently depends on your host operating system: - For example on Ubuntu Server add `vm.max_map_count = 262144` to the file `/etc/sysctl.conf`. - - On Arch based systems create a file `/etc/sysctl.d/max_map_count.conf` with the content `vm.max_map_count = 262144`. - - On any other platform look up in the documentation on how to pass kernel parameters. + - On Arch based systems create a file `/etc/sysctl.d/max_map_count.conf` with the content `vm.max_map_count = 262144`. + - On any other platform look up in the documentation on how to pass kernel parameters. ### Permissions for elasticsearch -If you see a message similar to `Unable to access 'path.repo' (/usr/share/elasticsearch/data/snapshot)` or `failed to obtain node locks, tried [/usr/share/elasticsearch/data]` and `maybe these locations are not writable` when initially starting elasticsearch, that probably means the container is not allowed to write files to the volume. +If you see a message similar to `Unable to access 'path.repo' (/usr/share/elasticsearch/data/snapshot)` or `failed to obtain node locks, tried [/usr/share/elasticsearch/data]` and `maybe these locations are not writable` when initially starting elasticsearch, that probably means the container is not allowed to write files to the volume. To fix that issue, shutdown the container and on your host machine run: ``` chown 1000:0 -R /path/to/mount/point ``` -This will match the permissions with the **UID** and **GID** of elasticsearch process within the container and should fix the issue. +This will match the permissions with the **UID** and **GID** of elasticsearch process within the container and should fix the issue. ### Disk usage -The Elasticsearch index will turn to ***read only*** if the disk usage of the container goes above 95% until the usage drops below 90% again, you will see error messages like `disk usage exceeded flood-stage watermark`. +The Elasticsearch index will turn to ***read only*** if the disk usage of the container goes above 95% until the usage drops below 90% again, you will see error messages like `disk usage exceeded flood-stage watermark`. Similar to that, TubeArchivist will become all sorts of messed up when running out of disk space. There are some error messages in the logs when that happens, but it's best to make sure to have enough disk space before starting to download. @@ -142,7 +151,7 @@ If you are seeing errors like `failed to create shim: OCI runtime create failed` This can happen if you have nested virtualizations, e.g. LXC running Docker in Proxmox. ## Known limitations -- Video files created by Tube Archivist need to be playable in your browser of choice. Not every codec is compatible with every browser and might require some testing with format selection. +- Video files created by Tube Archivist need to be playable in your browser of choice. Not every codec is compatible with every browser and might require some testing with format selection. - Every limitation of **yt-dlp** will also be present in Tube Archivist. If **yt-dlp** can't download or extract a video for any reason, Tube Archivist won't be able to either. - There is no flexibility in naming of the media files. @@ -191,7 +200,7 @@ Implemented: - [X] Scan your file system to index already downloaded videos [2021-09-14] ## User Scripts -This is a list of useful user scripts, generously created from folks like you to extend this project and its functionality. Make sure to check the respective repository links for detailed license information. +This is a list of useful user scripts, generously created from folks like you to extend this project and its functionality. Make sure to check the respective repository links for detailed license information. This is your time to shine, [read this](https://github.com/tubearchivist/tubearchivist/blob/master/CONTRIBUTING.md#user-scripts) then open a PR to add your script here. @@ -200,13 +209,13 @@ This is your time to shine, [read this](https://github.com/tubearchivist/tubearc - [DarkFighterLuke/ta_base_url_nginx](https://gist.github.com/DarkFighterLuke/4561b6bfbf83720493dc59171c58ac36): Set base URL with Nginx when you can't use subdomains. - [lamusmaser/ta_migration_helper](https://github.com/lamusmaser/ta_migration_helper): Advanced helper script for migration issues to TubeArchivist v0.4.4 or later. - [lamusmaser/create_info_json](https://gist.github.com/lamusmaser/837fb58f73ea0cad784a33497932e0dd): Script to generate `.info.json` files using `ffmpeg` collecting information from downloaded videos. -- [lamusmaser/ta_fix_for_video_redirection](https://github.com/lamusmaser/ta_fix_for_video_redirection): Script to fix videos that were incorrectly indexed by YouTube's "Video is Unavailable" response. +- [lamusmaser/ta_fix_for_video_redirection](https://github.com/lamusmaser/ta_fix_for_video_redirection): Script to fix videos that were incorrectly indexed by YouTube's "Video is Unavailable" response. - [RoninTech/ta-helper](https://github.com/RoninTech/ta-helper): Helper script to provide a symlink association to reference TubeArchivist videos with their original titles. - [tangyjoust/Tautulli-Notify-TubeArchivist-of-Plex-Watched-State](https://github.com/tangyjoust/Tautulli-Notify-TubeArchivist-of-Plex-Watched-State) Mark videos watched in Plex (through streaming not manually) through Tautulli back to TubeArchivist - [Dhs92/delete_shorts](https://github.com/Dhs92/delete_shorts): A script to delete ALL YouTube Shorts from TubeArchivist ## Donate -The best donation to **Tube Archivist** is your time, take a look at the [contribution page](CONTRIBUTING.md) to get started. +The best donation to **Tube Archivist** is your time, take a look at the [contribution page](CONTRIBUTING.md) to get started. Second best way to support the development is to provide for caffeinated beverages: * [GitHub Sponsor](https://github.com/sponsors/bbilly1) become a sponsor here on GitHub * [Paypal.me](https://paypal.me/bbilly1) for a one time coffee diff --git a/backend/config/settings.py b/backend/config/settings.py index 5b51647e..b2d9318f 100644 --- a/backend/config/settings.py +++ b/backend/config/settings.py @@ -195,7 +195,6 @@ if bool(environ.get("TA_LDAP")): ldap.OPT_X_TLS_REQUIRE_CERT: ldap.OPT_X_TLS_NEVER, } - AUTHENTICATION_BACKENDS = ("django_auth_ldap.backend.LDAPBackend",) # Database # https://docs.djangoproject.com/en/3.2/ref/settings/#databases @@ -239,10 +238,31 @@ if bool(environ.get("TA_ENABLE_AUTH_PROXY")): MIDDLEWARE.append("user.src.remote_user_auth.HttpRemoteUserMiddleware") + +# Configure Authentication Backend Combinations +_login_auth_mode = (environ.get("TA_LOGIN_AUTH_MODE") or "single").casefold() +if _login_auth_mode == "local": + AUTHENTICATION_BACKENDS = ("django.contrib.auth.backends.ModelBackend",) +elif _login_auth_mode == "ldap": + AUTHENTICATION_BACKENDS = ("django_auth_ldap.backend.LDAPBackend",) +elif _login_auth_mode == "forwardauth": AUTHENTICATION_BACKENDS = ( "django.contrib.auth.backends.RemoteUserBackend", ) - +elif _login_auth_mode == "ldap_local": + AUTHENTICATION_BACKENDS = ( + "django_auth_ldap.backend.LDAPBackend", + "django.contrib.auth.backends.ModelBackend", + ) +else: + # If none of these cases match, AUTHENTICATION_BACKENDS is unset, which + # means the ModelBackend should be used by default + if bool(environ.get("TA_LDAP")): + AUTHENTICATION_BACKENDS = ("django_auth_ldap.backend.LDAPBackend",) + if bool(environ.get("TA_ENABLE_AUTH_PROXY")): + AUTHENTICATION_BACKENDS = ( + "django.contrib.auth.backends.RemoteUserBackend", + ) # Internationalization # https://docs.djangoproject.com/en/3.2/topics/i18n/ From b239f4bd841e08c72b38752d3ac91c2bc55c578a Mon Sep 17 00:00:00 2001 From: skilletskills <151943998+skilletskills3@users.noreply.github.com> Date: Wed, 4 Jun 2025 01:29:19 +0900 Subject: [PATCH 15/16] gracefully handle missing appconfig by creating default settings in elasticsearch (#983) Co-authored-by: skillet --- backend/appsettings/src/index_setup.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/backend/appsettings/src/index_setup.py b/backend/appsettings/src/index_setup.py index d22f07d3..657199ed 100644 --- a/backend/appsettings/src/index_setup.py +++ b/backend/appsettings/src/index_setup.py @@ -201,7 +201,15 @@ class ElasitIndexWrap: if self.backup_run: return - config = AppConfig().config + try: + config = AppConfig().config + except ValueError: + # create defaults in ES if config not found + print("AppConfig not found, creating defaults...") + handler = AppConfig.__new__(AppConfig) + handler.sync_defaults() + config = AppConfig.CONFIG_DEFAULTS + if config["application"]["enable_snapshot"]: # take snapshot if enabled ElasticSnapshot().take_snapshot_now(wait=True) From e68a9f9f1f2a9af248156b1ca8b2070649c7a878 Mon Sep 17 00:00:00 2001 From: Baku <83136747+IAmABakuAMA@users.noreply.github.com> Date: Wed, 4 Jun 2025 02:37:18 +1000 Subject: [PATCH 16/16] Update SettingsActions.tsx (#985) * Update SettingsActions.tsx Fix typo ("infos" > "info") * fix pre-commit --------- Co-authored-by: Simon --- frontend/src/pages/SettingsActions.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/SettingsActions.tsx b/frontend/src/pages/SettingsActions.tsx index f56383d3..577c0262 100644 --- a/frontend/src/pages/SettingsActions.tsx +++ b/frontend/src/pages/SettingsActions.tsx @@ -204,8 +204,8 @@ const SettingsActions = () => { deleted videos from the filesystem.

- Rescan your media folder looking for missing videos and clean up index. More infos on - the Github{' '} + Rescan your media folder looking for missing videos and clean up index. More info on the + Github{' '}