diff --git a/package-lock.json b/package-lock.json index c7c15822..b85772e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "aiostreams", - "version": "2.0.0", + "version": "1.22.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "aiostreams", - "version": "2.0.0", + "version": "1.22.0", "license": "MIT", "workspaces": [ "packages/*" @@ -238,6 +238,73 @@ "kuler": "^2.0.0" } }, + "node_modules/@dnd-kit/accessibility": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", + "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/core": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", + "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", + "license": "MIT", + "dependencies": { + "@dnd-kit/accessibility": "^3.1.1", + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/modifiers": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/modifiers/-/modifiers-9.0.0.tgz", + "integrity": "sha512-ybiLc66qRGuZoC20wdSSG6pDXFikui/dCNGthxv4Ndy8ylErY0N3KVxY2bgo7AWwIbxDmXDg3ylAFmnrjcbVvw==", + "license": "MIT", + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.3.0", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/sortable": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz", + "integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==", + "license": "MIT", + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.3.0", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/utilities": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", + "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/@emnapi/core": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz", @@ -973,6 +1040,15 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@internationalized/number": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.2.tgz", + "integrity": "sha512-E5QTOlMg9wo5OrKdHD6edo1JJlIoOsylh0+mbf0evi1tHJwMZfJSaBpGtnJV9N7w3jeiioox9EG/EWRWPh82vg==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1593,6 +1669,35 @@ } } }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.15.tgz", + "integrity": "sha512-mIBnOjgwo9AH3FyKaSWoSu/dYj6VdhJ7frEPiGTeXCdUFHjl9h3mFh2wwhEtINOmYXWhdpf1rY2minFsmaNgVQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-menu": "2.1.15", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-focus-guards": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.2.tgz", @@ -1651,6 +1756,46 @@ } } }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.15.tgz", + "integrity": "sha512-tVlmA3Vb9n8SZSd+YSbuFR66l87Wiy4du+YE+0hzKQEANA+7cWKH1WgqcEX4pXqxUFQKrWQGHdvEfw00TjFiew==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.10", + "@radix-ui/react-focus-guards": "1.1.2", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.7", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.10", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popover": { "version": "1.1.14", "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.14.tgz", @@ -1791,6 +1936,37 @@ } } }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.10.tgz", + "integrity": "sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-select": { "version": "2.2.5", "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.5.tgz", @@ -2708,6 +2884,16 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/bcrypt": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz", + "integrity": "sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/body-parser": { "version": "1.19.5", "dev": true, @@ -2725,11 +2911,28 @@ "@types/node": "*" } }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.6", - "dev": true, "license": "MIT" }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, "node_modules/@types/express": { "version": "5.0.1", "dev": true, @@ -2759,6 +2962,15 @@ "@types/send": "*" } }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/http-errors": { "version": "2.0.4", "dev": true, @@ -2778,11 +2990,26 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/mime": { "version": "1.3.5", "dev": true, "license": "MIT" }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "20.17.10", "dev": true, @@ -2867,7 +3094,6 @@ "version": "19.1.4", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.4.tgz", "integrity": "sha512-EB1yiiYdvySuIITtD5lhW4yPyJ31RkJkkDw794LaQYrxCSaQV/47y5o1FMC4zF9ZyjUjzJMZwbovEnT5yHTW6g==", - "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -2906,6 +3132,12 @@ "version": "1.3.5", "license": "MIT" }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.32.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.32.1.tgz", @@ -3152,6 +3384,12 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, "node_modules/@unrs/resolver-binding-darwin-arm64": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.7.2.tgz", @@ -3492,6 +3730,85 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@zag-js/anatomy": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@zag-js/anatomy/-/anatomy-1.13.1.tgz", + "integrity": "sha512-o0syzwTVPei8g3Puy7NVhviVcB2KLvhYHpT19r3OMt4WtK4i9Qxy5V/7H6RFAf4Z5+xOYXfllJk9qtu3L3cwSg==", + "license": "MIT" + }, + "node_modules/@zag-js/core": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@zag-js/core/-/core-1.13.1.tgz", + "integrity": "sha512-8sDA9c2K5JMMLN7F7d4HUmeKxF+bpc7bB5DGkagevLZ+iomDdpg2B0sDDV88guq5/lYNfMJ6krSfO80JMO3Gjg==", + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "1.13.1", + "@zag-js/utils": "1.13.1" + } + }, + "node_modules/@zag-js/dom-query": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@zag-js/dom-query/-/dom-query-1.13.1.tgz", + "integrity": "sha512-BWQB77N7dbbUauYGmG2YmL5eNCot9v/kuCVzeR3WHueApum7f14ZvbFM8Lr9+tCL4BibPyAc8mZzAiB1reHWWQ==", + "license": "MIT", + "dependencies": { + "@zag-js/types": "1.13.1" + } + }, + "node_modules/@zag-js/number-input": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@zag-js/number-input/-/number-input-1.13.1.tgz", + "integrity": "sha512-4HSSWUiUM876K16+eaHsIVeuu1TErAE3B4RrXX3fTHoqduM5EQTYtIXEuWzgvTscl9L8pmF9v9uSHpezHPoNPA==", + "license": "MIT", + "dependencies": { + "@internationalized/number": "3.6.2", + "@zag-js/anatomy": "1.13.1", + "@zag-js/core": "1.13.1", + "@zag-js/dom-query": "1.13.1", + "@zag-js/types": "1.13.1", + "@zag-js/utils": "1.13.1" + } + }, + "node_modules/@zag-js/react": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@zag-js/react/-/react-1.13.1.tgz", + "integrity": "sha512-UBzy9bnZBdDNaWppsn8W5vao9fLm8OMCo3Erf+3gf2Z/dHdD8bHd9ObNOxXLUYJhELBSDJvoJiSMNmX8N8RLVg==", + "license": "MIT", + "dependencies": { + "@zag-js/core": "1.13.1", + "@zag-js/store": "1.13.1", + "@zag-js/types": "1.13.1", + "@zag-js/utils": "1.13.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@zag-js/store": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@zag-js/store/-/store-1.13.1.tgz", + "integrity": "sha512-v5Y1S+7EB3pH5NgLKd1DN4w04SGGgYFUzRsBJD3jQuF/kuMqSYAEOfBM/cezAmphsAZRE2H7JmvhtZUSB7pzrw==", + "license": "MIT", + "dependencies": { + "proxy-compare": "3.0.1" + } + }, + "node_modules/@zag-js/types": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@zag-js/types/-/types-1.13.1.tgz", + "integrity": "sha512-i74ng4uyEFLKxw3JWxDtt2fFpJ0HkSuSHQQ+k+ZpHuErDbAA7r9BKtSqLne3Hw/gvjel4yU/uH88Ogdl1J55zg==", + "license": "MIT", + "dependencies": { + "csstype": "3.1.3" + } + }, + "node_modules/@zag-js/utils": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@zag-js/utils/-/utils-1.13.1.tgz", + "integrity": "sha512-2nnBUxlf8eUv9khFBR6gbMogy1+/CSlIVYJrirN/w+v5r516QUXgzjlM6ZNSYGiF5kPgbxOUdgsNgTnvjyNvmA==", + "license": "MIT" + }, "node_modules/abbrev": { "version": "1.1.1", "license": "ISC", @@ -3964,6 +4281,16 @@ "node": ">= 0.4" } }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "devOptional": true, @@ -3987,6 +4314,29 @@ ], "license": "MIT" }, + "node_modules/bcrypt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz", + "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^8.3.0", + "node-gyp-build": "^4.8.4" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/bcrypt/node_modules/node-addon-api": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.3.1.tgz", + "integrity": "sha512-lytcDEdxKjGJPTLEfW4mYMigRezMlyJY8W4wxJK8zE533Jlb8L8dRuObJFWg2P+AuOIxoCgKF+2Oq4d4Zd0OUA==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -4282,6 +4632,16 @@ ], "license": "CC-BY-4.0" }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chai": { "version": "5.1.2", "dev": true, @@ -4314,6 +4674,46 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/check-error": { "version": "2.1.1", "dev": true, @@ -4402,6 +4802,22 @@ "node": ">=6" } }, + "node_modules/cmdk": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz", + "integrity": "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-id": "^1.1.0", + "@radix-ui/react-primitive": "^2.0.2" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" + } + }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -4489,6 +4905,16 @@ "node": ">= 0.8" } }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -4621,7 +5047,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "devOptional": true, "license": "MIT" }, "node_modules/damerau-levenshtein": { @@ -4701,7 +5126,6 @@ }, "node_modules/debug": { "version": "4.4.0", - "devOptional": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -4722,6 +5146,19 @@ "optional": true, "peer": true }, + "node_modules/decode-named-character-reference": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.1.0.tgz", + "integrity": "sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/decompress-response": { "version": "6.0.0", "license": "MIT", @@ -4815,6 +5252,15 @@ "node": ">= 0.8" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/destroy": { "version": "1.2.0", "license": "MIT", @@ -4836,6 +5282,19 @@ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", "license": "MIT" }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -5664,6 +6123,16 @@ "node": ">=4.0" } }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/estree-walker": { "version": "3.0.3", "dev": true, @@ -5772,6 +6241,12 @@ "version": "2.0.0", "license": "MIT" }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -6409,6 +6884,46 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/html-encoding-sniffer": { "version": "4.0.0", "dev": true, @@ -6422,6 +6937,16 @@ "node": ">=18" } }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/http-cache-semantics": { "version": "4.2.0", "license": "BSD-2-Clause", @@ -6570,6 +7095,12 @@ "version": "1.3.8", "license": "ISC" }, + "node_modules/inline-style-parser": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", + "license": "MIT" + }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -6604,6 +7135,30 @@ "node": ">= 0.10" } }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -6766,6 +7321,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -6832,6 +7397,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-lambda": { "version": "1.0.1", "license": "MIT", @@ -6877,6 +7452,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "dev": true, @@ -7577,6 +8164,16 @@ "node": ">= 12.0.0" } }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -7699,6 +8296,159 @@ "node": ">= 0.4" } }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/media-typer": { "version": "0.3.0", "license": "MIT", @@ -7730,6 +8480,448 @@ "node": ">= 0.6" } }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -8132,6 +9324,17 @@ "node": ">= 10.12.0" } }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/node-gyp/node_modules/rimraf": { "version": "3.0.2", "license": "ISC", @@ -8469,6 +9672,31 @@ "node": ">=6" } }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, "node_modules/parse-torrent-title": { "version": "1.3.0", "resolved": "git+ssh://git@github.com/TheBeastLT/parse-torrent-title.git#1169487e316a4eed898dcfd900957f89a253604c", @@ -9016,6 +10244,16 @@ "react-is": "^16.13.1" } }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "license": "MIT", @@ -9027,6 +10265,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-3.0.1.tgz", + "integrity": "sha512-V9plBAt3qjMlS1+nC8771KNf6oJ12gExvaxnNzN/9yVRLdTv/lc+oJlnSzrdYDAvBfTStPCoiaCOTmTs0adv7Q==", + "license": "MIT" + }, "node_modules/pump": { "version": "3.0.2", "license": "MIT", @@ -9147,6 +10391,33 @@ "dev": true, "license": "MIT" }, + "node_modules/react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, "node_modules/react-remove-scroll": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.0.tgz", @@ -9295,6 +10566,39 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", @@ -9892,6 +11196,16 @@ "node": ">=0.10.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/sprintf-js": { "version": "1.1.3", "license": "BSD-3-Clause", @@ -10134,6 +11448,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "devOptional": true, @@ -10176,6 +11504,24 @@ "node": ">=0.10.0" } }, + "node_modules/style-to-js": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.16.tgz", + "integrity": "sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.8" + } + }, + "node_modules/style-to-object": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz", + "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.4" + } + }, "node_modules/styled-jsx": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", @@ -10608,6 +11954,16 @@ "node": ">=18" } }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/triple-beam": { "version": "1.4.1", "license": "MIT", @@ -10615,6 +11971,16 @@ "node": ">= 14.0.0" } }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -10871,6 +12237,25 @@ "dev": true, "license": "MIT" }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/unique-filename": { "version": "1.1.1", "license": "ISC", @@ -10887,6 +12272,74 @@ "imurmurhash": "^0.1.4" } }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/unpipe": { "version": "1.0.0", "license": "MIT", @@ -11045,6 +12498,34 @@ "node": ">= 0.8" } }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/vite": { "version": "5.4.11", "dev": true, @@ -11675,10 +13156,21 @@ "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "packages/core": { "name": "@aiostreams/core", - "version": "2.0.0", + "version": "0.0.0", "dependencies": { + "bcrypt": "^6.0.0", "dotenv": "^16.4.7", "envalid": "^8.0.0", "moment-timezone": "^0.5.48", @@ -11692,19 +13184,24 @@ "zod": "^3.24.4" }, "devDependencies": { + "@types/bcrypt": "^5.0.2", "@types/node": "^20.14.10", "@types/pg": "^8.15.2" } }, "packages/frontend": { "name": "@aiostreams/frontend", - "version": "2.0.0", + "version": "0.0.0", "dependencies": { - "@aiostreams/core": "^2", + "@aiostreams/core": "^0.0.0", + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/modifiers": "^9.0.0", + "@dnd-kit/sortable": "^10.0.0", "@headlessui/react": "^2.2.4", "@headlessui/tailwindcss": "^0.2.2", "@radix-ui/react-accordion": "^1.2.11", "@radix-ui/react-dialog": "^1.1.14", + "@radix-ui/react-dropdown-menu": "^2.1.15", "@radix-ui/react-popover": "^1.1.14", "@radix-ui/react-select": "^2.2.5", "@radix-ui/react-separator": "^1.1.7", @@ -11712,8 +13209,11 @@ "@radix-ui/react-tooltip": "^1.2.7", "@tailwindcss/forms": "^0.5.10", "@tailwindcss/typography": "^0.5.16", + "@zag-js/number-input": "^1.13.1", + "@zag-js/react": "^1.13.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "cmdk": "^1.1.1", "framer-motion": "^12.12.2", "lucide-react": "^0.511.0", "next": "15.3.2", @@ -11721,6 +13221,7 @@ "react": "^19.0.0", "react-dom": "^19.0.0", "react-icons": "^5.5.0", + "react-markdown": "^10.1.0", "sonner": "^2.0.3", "tailwind-merge": "^3.3.0", "tailwind-scrollbar-hide": "~1.1.7", @@ -11921,9 +13422,9 @@ }, "packages/server": { "name": "@aiostreams/server", - "version": "2.0.0", + "version": "0.0.0", "dependencies": { - "@aiostreams/core": "^2.0.0", + "@aiostreams/core": "^0.0.0", "express": "^4.21.2", "express-rate-limit": "^7.5.0" }, @@ -11934,4 +13435,4 @@ } } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index 875c9db4..c204e59f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "aiostreams", - "version": "2.0.0", + "version": "1.22.0", "description": "AIOStreams consolidates multiple Stremio addons and debrid services into a single, easily configurable addon. It allows highly customisable filtering, sorting, and formatting of results and supports proxying all your streams through MediaFlow Proxy or StremThru for improved compatibility and IP restriction bypassing.", "main": "dist/index.js", "scripts": { diff --git a/packages/core/package.json b/packages/core/package.json index 49b0c2b1..685ad06d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@aiostreams/core", - "version": "2.0.0", + "version": "0.0.0", "main": "dist/index.js", "scripts": { "test": "vitest run --passWithNoTests", @@ -9,6 +9,7 @@ }, "description": "Combine all your streams into one addon and display them with consistent formatting, sorting, and filtering.", "dependencies": { + "bcrypt": "^6.0.0", "dotenv": "^16.4.7", "envalid": "^8.0.0", "moment-timezone": "^0.5.48", @@ -22,6 +23,7 @@ "zod": "^3.24.4" }, "devDependencies": { + "@types/bcrypt": "^5.0.2", "@types/node": "^20.14.10", "@types/pg": "^8.15.2" } diff --git a/packages/core/src/db/schemas.ts b/packages/core/src/db/schemas.ts index a337e749..1130cc40 100644 --- a/packages/core/src/db/schemas.ts +++ b/packages/core/src/db/schemas.ts @@ -113,12 +113,8 @@ const AddonSchema = z.object({ // preset objects are transformed into addons by a preset transformer. const PresetSchema = z.object({ id: z.string().min(1), - name: z.string().min(1), - enabled: z.boolean().optional(), - baseUrl: z.string().url().optional(), - timeout: z.number().min(1).optional(), - resources: ResourceList.optional(), - options: z.record(z.string().min(1), z.any()).optional(), + enabled: z.boolean(), + options: z.record(z.string().min(1), z.any()), }); const AddonList = z.array(AddonSchema); @@ -156,7 +152,14 @@ const OptionDefinition = z.object({ id: z.string().min(1), name: z.string().min(1), description: z.string().min(1), - type: z.enum(['string', 'number', 'boolean', 'select', 'multi-select']), + type: z.enum([ + 'string', + 'number', + 'boolean', + 'select', + 'multi-select', + 'url', + ]), required: z.boolean().optional(), default: z.any().optional(), sensitive: z.boolean().optional(), @@ -170,9 +173,8 @@ const OptionDefinition = z.object({ .optional(), constraints: z .object({ - min: z.number().min(1).optional(), - max: z.number().min(1).optional(), - enum: z.array(z.any()).optional(), + min: z.number().min(1).optional(), // for string inputs, consider this the minimum length. + max: z.number().min(1).optional(), // and for number inputs, consider this the minimum and maximum value. }) .optional(), }); @@ -250,8 +252,7 @@ export const UserDataSchema = z.object({ precacheNextEpisode: z.boolean().optional(), hideZeroSeederResults: z.boolean().optional(), services: ServiceList.optional(), - addons: AddonList, - presets: PresetList.optional(), + presets: PresetList, }); export type UserData = z.infer; @@ -259,9 +260,9 @@ export type UserData = z.infer; export const TABLES = { USERS: ` uuid TEXT PRIMARY KEY, - config TEXT NOT NULL, password_hash TEXT NOT NULL, - password_salt TEXT NOT NULL, + config TEXT NOT NULL, + config_salt TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, accessed_at DATETIME DEFAULT CURRENT_TIMESTAMP @@ -543,18 +544,21 @@ const PresetMetadataSchema = z.object({ USER_AGENT: z.string(), SUPPORTED_SERVICES: z.array(z.string()), OPTIONS: z.array(OptionDefinition), - TAGS: z.array(z.string()), - RESOURCES: z.array(ResourceSchema), - REQUIRES_SERVICE: z.boolean(), + SUPPORTED_STREAM_TYPES: z.array(StreamTypes), + SUPPORTED_RESOURCES: z.array(ResourceSchema), }); const StatusResponseSchema = z.object({ version: z.string(), + tag: z.string(), commit: z.string(), buildTime: z.string(), commitTime: z.string(), users: z.number(), settings: z.object({ + baseUrl: z.string().url().optional(), + addonName: z.string(), + customHtml: z.string().optional(), disabledAddons: z.array(z.string()), disabledServices: z.array(z.string()), forced: z.object({ @@ -578,6 +582,7 @@ const StatusResponseSchema = z.object({ proxiedAddons: z.array(z.string()).or(z.null()), proxiedServices: z.array(z.string()).or(z.null()), }), + timeout: z.number().or(z.null()), preferredRegex: z.array(z.string()), requiredRegex: z.array(z.string()), excludedRegex: z.array(z.string()), diff --git a/packages/core/src/db/users.ts b/packages/core/src/db/users.ts index f2a08a4f..0d14908e 100644 --- a/packages/core/src/db/users.ts +++ b/packages/core/src/db/users.ts @@ -12,6 +12,7 @@ import { createLogger, constants, Env, + verifyHash, } from '../utils'; const APIError = constants.APIError; @@ -46,11 +47,11 @@ export class UserRepository { const uuid = await this.generateUUID(); config.uuid = uuid; - const { encryptedConfig, salt } = await this.encryptConfig( + const { encryptedConfig, salt: configSalt } = await this.encryptConfig( config, password ); - const { hash: hashedPassword } = getTextHash(password, salt); + const hashedPassword = await getTextHash(password); const { success, data } = encryptString(password); if (success === false) { @@ -61,8 +62,8 @@ export class UserRepository { const tx = await db.begin(); try { await tx.execute( - 'INSERT INTO users (uuid, password_hash, password_salt, config) VALUES (?, ?, ?, ?)', - [uuid, hashedPassword, salt, encryptedConfig] + 'INSERT INTO users (uuid, password_hash, config, config_salt) VALUES (?, ?, ?, ?)', + [uuid, hashedPassword, encryptedConfig, configSalt] ); await tx.commit(); logger.info(`Created a new user with UUID: ${uuid}`); @@ -89,19 +90,17 @@ export class UserRepository { } } + // with stremio auth, we are given the encrypted password + // with api use, we are given the password + // GET /user should also return + static async getUser( uuid: string, - encryptedPassword?: string, - password?: string + password: string ): Promise { try { - if (!encryptedPassword && !password) { - return Promise.reject( - new APIError(constants.ErrorCode.MISSING_REQUIRED_FIELDS) - ); - } const result = await db.query( - 'SELECT config, password_salt FROM users WHERE uuid = ?', + 'SELECT config, config_salt FROM users WHERE uuid = ?', [uuid] ); @@ -114,14 +113,6 @@ export class UserRepository { [uuid] ); - if (!password) { - const { success, data } = decryptString(encryptedPassword!); - if (!success) { - return Promise.reject(new APIError(constants.ErrorCode.USER_ERROR)); - } - password = data; - } - const isValid = await this.verifyUserPassword(uuid, password); if (!isValid) { return Promise.reject( @@ -132,7 +123,7 @@ export class UserRepository { const decryptedConfig = await this.decryptConfig( result[0].config, password, - result[0].password_salt + result[0].config_salt ); decryptedConfig.admin = @@ -157,7 +148,7 @@ export class UserRepository { const tx = await db.begin(); try { const currentUser = await tx.execute( - 'SELECT password_salt FROM users WHERE uuid = ?', + 'SELECT config_salt FROM users WHERE uuid = ?', [uuid] ); @@ -179,7 +170,7 @@ export class UserRepository { const { encryptedConfig } = await this.encryptConfig( config, password, - currentUser.rows[0].password_salt + currentUser.rows[0].config_salt ); await tx.execute( @@ -253,7 +244,7 @@ export class UserRepository { password: string ): Promise { const result = await db.query( - 'SELECT password_hash, password_salt FROM users WHERE uuid = ?', + 'SELECT password_hash FROM users WHERE uuid = ?', [uuid] ); @@ -261,9 +252,8 @@ export class UserRepository { return false; } - const { password_hash: hashedPassword, password_salt: salt } = result[0]; - const { hash } = getTextHash(password, salt); - return hash === hashedPassword; + const { password_hash: storedHash } = result[0]; + return verifyHash(password, storedHash); } private static async encryptConfig( @@ -274,7 +264,7 @@ export class UserRepository { encryptedConfig: string; salt: string; }> { - const { key, salt: saltUsed } = deriveKey(password, salt); + const { key, salt: saltUsed } = await deriveKey(password, salt); const configString = JSON.stringify(config); const { success, data, error } = encryptString(configString, key); @@ -290,7 +280,7 @@ export class UserRepository { password: string, salt: string ): Promise { - const { key } = deriveKey(password, salt); + const { key } = await deriveKey(password, salt); const { success, data: decryptedString, diff --git a/packages/core/src/main.ts b/packages/core/src/main.ts index 9ca1c1fd..20ab52ae 100644 --- a/packages/core/src/main.ts +++ b/packages/core/src/main.ts @@ -26,6 +26,7 @@ export class AIOStreams { private finalCatalogs: Manifest['catalogs'] = []; private finalAddonCatalogs: Manifest['addonCatalogs'] = []; private isInitialised: boolean = false; + private addons: Addon[] = []; constructor(userData: UserData) { this.userData = userData; @@ -310,29 +311,26 @@ export class AIOStreams { // step 4 return addonCatalogs; } - // convers presets to addons + // converts all addons to private async applyPresets() { if (!this.userData.presets) { return; } - for (const preset of this.userData.presets) { + for (const preset of this.userData.presets.filter((p) => p.enabled)) { const addons = await PresetManager.fromId(preset.id).generateAddons( this.userData, - preset.options, - preset.baseUrl, - preset.name, - preset.timeout, - preset.resources + preset.options ); - this.userData.addons.push(...addons); + + this.addons.push(...addons); } } private async fetchManifests() { this.manifests = Object.fromEntries( await Promise.all( - this.userData.addons.map(async (addon, index) => [ + this.addons.map(async (addon, index) => [ index, await new Wrapper(addon).getManifest(), ]) @@ -356,7 +354,7 @@ export class AIOStreams { return resource; }); - const addon = this.userData.addons[Number(index)]; + const addon = this.addons[Number(index)]; // Filter and merge resources for (const resource of addonResources) { @@ -429,7 +427,7 @@ export class AIOStreams { } private getAddon(index: number): Addon { - return this.userData.addons[index]; + return this.addons[index]; } private shouldProxyAddon(addon: Addon): boolean { @@ -524,7 +522,7 @@ export class AIOStreams { if (this.userData.proxy) { proxyIp = await this.getProxyIp(); } - for (const addon of this.userData.addons) { + for (const addon of this.addons) { const proxy = this.shouldProxyAddon(addon); if (proxy) { addon.ip = proxyIp; diff --git a/packages/core/src/parser/streams.ts b/packages/core/src/parser/streams.ts index a671771f..1a536fc3 100644 --- a/packages/core/src/parser/streams.ts +++ b/packages/core/src/parser/streams.ts @@ -3,7 +3,7 @@ import { constants } from '../utils'; import FileParser from './file'; class StreamParser { - constructor(private readonly addon: Addon) {} + constructor(protected readonly addon: Addon) {} parse(stream: Stream): ParsedStream { let parsedStream: ParsedStream = { @@ -21,6 +21,7 @@ class StreamParser { parsedStream.service = this.getService(stream); parsedStream.duration = this.getDuration(stream); parsedStream.type = this.getStreamType(stream, parsedStream.service); + parsedStream.inLibrary = this.getInLibrary(stream); if (parsedStream.filename) { parsedStream.parsedFile = FileParser.parse(parsedStream.filename); @@ -176,6 +177,10 @@ class StreamParser { throw new Error('Invalid stream, missing a required stream property'); } + protected getInLibrary(stream: Stream): boolean { + return this.addon.library ?? false; + } + private normaliseFilename(filename: string): string { // remove all non-alphanumeric characters, replace spaces with . and remove trailing and leading . or spaces return filename diff --git a/packages/core/src/presets/custom.ts b/packages/core/src/presets/custom.ts new file mode 100644 index 00000000..3aaf5ac4 --- /dev/null +++ b/packages/core/src/presets/custom.ts @@ -0,0 +1,96 @@ +import { Addon, Option, UserData } from '../db'; +import { Preset, baseOptions } from './preset'; +import { Env, RESOURCES } from '../utils'; + +export class CustomPreset extends Preset { + static override get METADATA() { + const options: Option[] = [ + { + id: 'name', + name: 'Name', + description: 'What to call this addon', + type: 'string', + required: true, + default: 'Custom Addon', + }, + { + id: 'manifestUrl', + name: 'Manifest URL', + description: 'Provide the Manifest URL for this custom addon.', + type: 'url', + required: true, + }, + { + id: 'libraryAddon', + name: 'Library Addon', + description: + 'Whether to mark this addon as a library addon. This will result in all streams from this addon being marked as library streams.', + type: 'boolean', + required: false, + default: false, + }, + { + id: 'timeout', + name: 'Timeout', + description: 'The timeout for this addon', + type: 'number', + default: Env.DEFAULT_TIMEOUT, + }, + { + id: 'resources', + name: 'Resources', + description: + 'Optionally override the resources that are fetched from this addon ', + type: 'multi-select', + required: false, + default: undefined, + options: RESOURCES.map((resource) => ({ + label: resource, + value: resource, + })), + }, + ]; + + return { + ID: 'custom', + NAME: 'Custom', + LOGO: '', + URL: '', + TIMEOUT: Env.DEFAULT_TIMEOUT, + USER_AGENT: Env.DEFAULT_USER_AGENT, + SUPPORTED_SERVICES: [], + DESCRIPTION: 'Custom preset', + OPTIONS: options, + SUPPORTED_STREAM_TYPES: [], + SUPPORTED_RESOURCES: [], + }; + } + + static async generateAddons( + userData: UserData, + options: Record + ): Promise { + if (!options.url || !options.url.endsWith('/manifest.json')) { + throw new Error('Invalid manifest URL'); + } + return [this.generateAddon(userData, options)]; + } + + private static generateAddon( + userData: UserData, + options: Record + ): Addon { + return { + name: options.name || this.METADATA.NAME, + manifestUrl: options.url, + enabled: true, + library: options.libraryAddon ?? false, + resources: options.resources || [], + timeout: options.timeout || this.METADATA.TIMEOUT, + fromPresetId: this.METADATA.ID, + headers: { + 'User-Agent': this.METADATA.USER_AGENT, + }, + }; + } +} diff --git a/packages/core/src/presets/preset.ts b/packages/core/src/presets/preset.ts index 3131533f..5275c2f8 100644 --- a/packages/core/src/presets/preset.ts +++ b/packages/core/src/presets/preset.ts @@ -7,6 +7,7 @@ import { PresetMetadata, } from '../db'; import { StreamParser } from '../parser'; +import { Env } from '../utils'; /** * * What modifications are needed for each preset: @@ -23,6 +24,55 @@ import { StreamParser } from '../parser'; * torrentio: extract folder name from first line */ +// name: z.string().min(1), +// enabled: z.boolean().optional(), +// baseUrl: z.string().url().optional(), +// timeout: z.number().min(1).optional(), +// resources: ResourceList.optional(), + +export const baseOptions = ( + name: string, + resources: Resource[], + timeout: number = Env.DEFAULT_TIMEOUT +): Option[] => [ + { + id: 'name', + name: 'Name', + description: 'What to call this addon', + type: 'string', + required: true, + default: name, + }, + { + id: 'url', + name: 'URL', + description: + 'Optionally override either the manifest generated, or override the base url used when generating the manifests', + type: 'url', + required: false, + default: undefined, + }, + { + id: 'timeout', + name: 'Timeout', + description: 'The timeout for this addon', + type: 'number', + default: timeout, + }, + { + id: 'resources', + name: 'Resources', + description: 'Optionally override the resources to use ', + type: 'multi-select', + required: false, + default: resources, + options: resources.map((resource) => ({ + label: resource, + value: resource, + })), + }, +]; + export abstract class Preset { static get METADATA(): PresetMetadata { throw new Error('METADATA must be implemented by derived classes'); diff --git a/packages/core/src/presets/presetManager.ts b/packages/core/src/presets/presetManager.ts index 3d183525..e1010efa 100644 --- a/packages/core/src/presets/presetManager.ts +++ b/packages/core/src/presets/presetManager.ts @@ -1,9 +1,10 @@ import { PresetMetadata } from '../db'; +import { CustomPreset } from './custom'; import { Preset } from './preset'; import { StremthruStorePreset } from './stremthruStore'; import { TorrentioPreset } from './torrentio'; -const PRESET_LIST: string[] = ['torrentio', 'stremthruStore']; +const PRESET_LIST: string[] = ['torrentio', 'stremthruStore', 'custom']; export class PresetManager { static getPresetList(): PresetMetadata[] { @@ -16,6 +17,8 @@ export class PresetManager { return TorrentioPreset; case 'stremthruStore': return StremthruStorePreset; + case 'custom': + return CustomPreset; default: throw new Error(`Preset ${id} not found`); } diff --git a/packages/core/src/presets/stremthruStore.ts b/packages/core/src/presets/stremthruStore.ts index 8871258a..af152ad5 100644 --- a/packages/core/src/presets/stremthruStore.ts +++ b/packages/core/src/presets/stremthruStore.ts @@ -1,11 +1,11 @@ import { Addon, Option, UserData, Resource } from '../db'; -import { Preset } from './preset'; +import { baseOptions, Preset } from './preset'; import { Env } from '../utils'; import { constants, ServiceId } from '../utils'; export class StremthruStorePreset extends Preset { static override get METADATA() { - const supportedServices = [ + const supportedServices: ServiceId[] = [ constants.REALDEBRID_SERVICE, constants.PREMIUMIZE_SERVICE, constants.ALLEDEBRID_SERVICE, @@ -15,30 +15,43 @@ export class StremthruStorePreset extends Preset { constants.OFFCLOUD_SERVICE, ]; + const supportedResources = [ + constants.STREAM_RESOURCE, + constants.CATALOG_RESOURCE, + constants.META_RESOURCE, + ]; + const options: Option[] = [ + ...baseOptions( + 'Stremthru Store', + supportedResources, + Env.DEFAULT_STREMTHRU_STORE_TIMEOUT + ), { id: 'services', name: 'Services', - description: 'The services to use', + description: 'The services that will be used', type: 'multi-select', required: true, options: supportedServices.map((service) => ({ value: service, - label: service, + label: constants.SERVICE_DETAILS[service].name, })), + default: supportedServices, }, - { - id: 'hideCatalog', - name: 'Hide Catalog', - description: 'Hide the catalog', - type: 'boolean', - }, - { - id: 'hideStream', - name: 'Hide Stream', - description: 'Hide the stream', - type: 'boolean', - }, + // THESE OPTIONS are covered by the resource option, so no need to provide them here. + // { + // id: 'hideCatalog', + // name: 'Hide Catalog', + // description: 'Hide the catalog', + // type: 'boolean', + // }, + // { + // id: 'hideStream', + // name: 'Hide Stream', + // description: 'Hide the stream', + // type: 'boolean', + // }, { id: 'webDl', name: 'Web DL', @@ -49,46 +62,29 @@ export class StremthruStorePreset extends Preset { return { ID: 'stremthruStore', - NAME: 'Stremthru Store', + NAME: 'StremThru Store', LOGO: 'https://emojiapi.dev/api/v1/sparkles/256.png', URL: Env.STREMTHRU_STORE_URL, TIMEOUT: Env.DEFAULT_STREMTHRU_STORE_TIMEOUT || Env.DEFAULT_TIMEOUT, USER_AGENT: Env.DEFAULT_STREMTHRU_STORE_USER_AGENT || Env.DEFAULT_USER_AGENT, SUPPORTED_SERVICES: supportedServices, - REQUIRES_SERVICE: true, - DESCRIPTION: 'Stremthru Store preset', + DESCRIPTION: + 'StremThru Store allows you to access streams from your debrid library, either through its own catalogs, or through streams for other catalogs.', OPTIONS: options, - TAGS: [constants.P2P_TAG, constants.DEBRID_TAG], - RESOURCES: [ - constants.STREAM_RESOURCE, - constants.CATALOG_RESOURCE, - constants.META_RESOURCE, - ], + SUPPORTED_STREAM_TYPES: [constants.DEBRID_STREAM_TYPE], + SUPPORTED_RESOURCES: supportedResources, }; } static async generateAddons( userData: UserData, - options?: Record, - baseUrl?: string, - name?: string, - timeout?: number, - resources?: Resource[] + options: Record ): Promise { - // baseUrl can either be something like https://torrentio.com/ or it can be a custom manifest url. + // url can either be something like https://torrentio.com/ or it can be a custom manifest url. // if it is a custom manifest url, return a single addon with the custom manifest url. - if (baseUrl?.endsWith('/manifest.json')) { - return [ - this.generateAddon( - userData, - undefined, - baseUrl, - timeout, - name, - resources - ), - ]; + if (options?.url?.endsWith('/manifest.json')) { + return [this.generateAddon(userData, options, undefined)]; } // get all services that are supported by the preset and enabled @@ -110,44 +106,21 @@ export class StremthruStorePreset extends Preset { } return usableServices.map((service) => - this.generateAddon( - userData, - service.id, - baseUrl, - timeout, - name, - resources, - options?.hideCatalog, - options?.hideStream, - options?.webDl - ) + this.generateAddon(userData, options, service.id) ); } private static generateAddon( userData: UserData, - serviceId: ServiceId | undefined, - baseUrl?: string, - timeout?: number, - name?: string, - resources?: Resource[], - hideCatalog?: boolean, - hideStream?: boolean, - webDl?: boolean + options: Record, + serviceId?: ServiceId ): Addon { return { - name: name || this.METADATA.NAME, - manifestUrl: this.generateManifestUrl( - userData, - serviceId, - baseUrl, - hideCatalog, - hideStream, - webDl - ), + name: options.name || this.METADATA.NAME, + manifestUrl: this.generateManifestUrl(userData, options, serviceId), enabled: true, - resources: resources || this.METADATA.RESOURCES, - timeout: timeout || this.METADATA.TIMEOUT, + resources: options.resources || this.METADATA.SUPPORTED_RESOURCES, + timeout: options.timeout || this.METADATA.TIMEOUT, fromPresetId: this.METADATA.ID, headers: { 'User-Agent': this.METADATA.USER_AGENT, @@ -157,25 +130,23 @@ export class StremthruStorePreset extends Preset { private static generateManifestUrl( userData: UserData, - serviceId: ServiceId | undefined, - baseUrl?: string, - hideCatalog: boolean = false, - hideStream: boolean = false, - webDl: boolean = false + options: Record, + serviceId: ServiceId | undefined ) { - const url = baseUrl || this.METADATA.URL; + let url = options.url || this.METADATA.URL; if (url.endsWith('/manifest.json')) { return url; } + url = url.replace(/\/$/, ''); if (!serviceId) { throw new Error('Service is required'); } const configString = this.base64EncodeJSON({ store_name: serviceId, store_token: this.getServiceCredential(serviceId, userData), - hide_catalog: hideCatalog, - hide_stream: hideStream, - web_dl: webDl, + hide_catalog: false, + hide_stream: false, + web_dl: options.webDl ?? false, }); return `${url}${configString ? '/' + configString : ''}/manifest.json`; diff --git a/packages/core/src/presets/torrentio.ts b/packages/core/src/presets/torrentio.ts index 450b7e20..d800a7f1 100644 --- a/packages/core/src/presets/torrentio.ts +++ b/packages/core/src/presets/torrentio.ts @@ -1,6 +1,6 @@ import { Addon, Option, UserData, Resource, Stream } from '../db'; -import { Preset } from './preset'; -import { Env } from '../utils'; +import { Preset, baseOptions } from './preset'; +import { Env, SERVICE_DETAILS } from '../utils'; import { constants, ServiceId } from '../utils'; import { StreamParser } from '../parser'; @@ -21,7 +21,7 @@ export class TorrentioPreset extends Preset { } static override get METADATA() { - const supportedServices = [ + const supportedServices: ServiceId[] = [ constants.REALDEBRID_SERVICE, constants.PREMIUMIZE_SERVICE, constants.ALLEDEBRID_SERVICE, @@ -30,8 +30,18 @@ export class TorrentioPreset extends Preset { constants.PUTIO_SERVICE, constants.OFFCLOUD_SERVICE, ]; + const supportedResources = [ + constants.STREAM_RESOURCE, + constants.CATALOG_RESOURCE, + constants.META_RESOURCE, + ]; const options: Option[] = [ + ...baseOptions( + 'Torrentio', + supportedResources, + Env.DEFAULT_TORRENTIO_TIMEOUT + ), { id: 'services', name: 'Services', @@ -40,8 +50,9 @@ export class TorrentioPreset extends Preset { required: true, options: supportedServices.map((service) => ({ value: service, - label: service, + label: constants.SERVICE_DETAILS[service].name, })), + default: supportedServices, }, { id: 'useMultipleInstances', @@ -65,8 +76,11 @@ export class TorrentioPreset extends Preset { REQUIRES_SERVICE: false, DESCRIPTION: 'Torrentio preset', OPTIONS: options, - TAGS: [constants.P2P_TAG, constants.DEBRID_TAG], - RESOURCES: [ + SUPPORTED_STREAM_TYPES: [ + constants.P2P_STREAM_TYPE, + constants.DEBRID_STREAM_TYPE, + ], + SUPPORTED_RESOURCES: [ constants.STREAM_RESOURCE, constants.META_RESOURCE, constants.CATALOG_RESOURCE, @@ -76,18 +90,12 @@ export class TorrentioPreset extends Preset { static async generateAddons( userData: UserData, - options?: Record, - baseUrl?: string, - name?: string, - timeout?: number, - resources?: Resource[] + options: Record ): Promise { // baseUrl can either be something like https://torrentio.com/ or it can be a custom manifest url. // if it is a custom manifest url, return a single addon with the custom manifest url. - if (baseUrl?.endsWith('/manifest.json')) { - return [ - this.generateAddon(userData, [], baseUrl, name, timeout, resources), - ]; + if (options?.url?.endsWith('/manifest.json')) { + return [this.generateAddon(userData, options, [])]; } // get all services that are supported by the preset and enabled @@ -105,22 +113,13 @@ export class TorrentioPreset extends Preset { // if no services are usable, return a single addon with no services if (!usableServices || usableServices.length === 0) { - return [ - this.generateAddon(userData, [], baseUrl, name, timeout, resources), - ]; + return [this.generateAddon(userData, options, [])]; } // if user has specified useMultipleInstances, return a single addon for each service if (options?.useMultipleInstances) { return usableServices.map((service) => - this.generateAddon( - userData, - [service.id], - baseUrl, - name, - timeout, - resources - ) + this.generateAddon(userData, options, [service.id]) ); } @@ -128,28 +127,23 @@ export class TorrentioPreset extends Preset { return [ this.generateAddon( userData, - usableServices.map((service) => service.id), - baseUrl, - name, - timeout + options, + usableServices.map((service) => service.id) ), ]; } private static generateAddon( userData: UserData, - services: ServiceId[], - baseUrl?: string, - name?: string, - timeout?: number, - resources?: Resource[] + options: Record, + services: ServiceId[] ): Addon { return { - name: name || this.METADATA.NAME, - manifestUrl: this.generateManifestUrl(userData, services, baseUrl), + name: options.name || this.METADATA.NAME, + manifestUrl: this.generateManifestUrl(userData, services, options.url), enabled: true, - resources: resources || this.METADATA.RESOURCES, - timeout: timeout || this.METADATA.TIMEOUT, + resources: options.resources || this.METADATA.SUPPORTED_RESOURCES, + timeout: options.timeout || this.METADATA.TIMEOUT, fromPresetId: this.METADATA.ID, headers: { 'User-Agent': this.METADATA.USER_AGENT, @@ -160,9 +154,9 @@ export class TorrentioPreset extends Preset { private static generateManifestUrl( userData: UserData, services: ServiceId[], - baseUrl?: string + url?: string ) { - const url = baseUrl || this.METADATA.URL; + url = url || this.METADATA.URL; if (url.endsWith('/manifest.json')) { return url; } diff --git a/packages/core/src/utils/constants.ts b/packages/core/src/utils/constants.ts index 39d3b9a5..e4163134 100644 --- a/packages/core/src/utils/constants.ts +++ b/packages/core/src/utils/constants.ts @@ -496,12 +496,6 @@ const OPTION_TYPES = [ MULTI_SELECT_OPTION_TYPE, ] as const; -const P2P_TAG = 'p2p'; -const DIRECT_TAG = 'direct'; -const DEBRID_TAG = 'debrid'; -const USENET_TAG = 'usenet'; -const PRESET_TAGS = [P2P_TAG, DIRECT_TAG, DEBRID_TAG, USENET_TAG] as const; - export const DEDUPLICATOR_KEYS = ['filename', 'infoHash'] as const; const RESOLUTIONS = [ @@ -588,16 +582,26 @@ const SORT_CRITERIA = [ ] as const; const SORT_DIRECTIONS = ['asc', 'desc'] as const; +export const P2P_STREAM_TYPE = 'p2p' as const; +export const LIVE_STREAM_TYPE = 'live' as const; +export const USENET_STREAM_TYPE = 'usenet' as const; +export const DEBRID_STREAM_TYPE = 'debrid' as const; +export const HTTP_STREAM_TYPE = 'http' as const; +export const EXTERNAL_STREAM_TYPE = 'external' as const; +export const YOUTUBE_STREAM_TYPE = 'youtube' as const; + const STREAM_TYPES = [ - 'p2p', - 'live', - 'usenet', - 'debrid', - 'http', - 'external', - 'youtube', + P2P_STREAM_TYPE, + LIVE_STREAM_TYPE, + USENET_STREAM_TYPE, + DEBRID_STREAM_TYPE, + HTTP_STREAM_TYPE, + EXTERNAL_STREAM_TYPE, + YOUTUBE_STREAM_TYPE, ] as const; +export type StreamType = (typeof STREAM_TYPES)[number]; + const STREAM_RESOURCE = 'stream' as const; const SUBTITLES_RESOURCE = 'subtitles' as const; const CATALOG_RESOURCE = 'catalog' as const; @@ -808,7 +812,6 @@ export { LANGUAGES, RESOURCES, OPTION_TYPES, - PRESET_TAGS, STREAM_RESOURCE, SUBTITLES_RESOURCE, CATALOG_RESOURCE, @@ -830,9 +833,5 @@ export { BOOLEAN_OPTION_TYPE, SELECT_OPTION_TYPE, MULTI_SELECT_OPTION_TYPE, - P2P_TAG, - DIRECT_TAG, - DEBRID_TAG, - USENET_TAG, HEADERS_FOR_IP_FORWARDING, }; diff --git a/packages/core/src/utils/crypto.ts b/packages/core/src/utils/crypto.ts index bc25e56b..e6cb7097 100644 --- a/packages/core/src/utils/crypto.ts +++ b/packages/core/src/utils/crypto.ts @@ -6,12 +6,15 @@ import { pbkdf2Sync, randomUUID, } from 'crypto'; +import { genSalt, hash, compare } from 'bcrypt'; import { deflateSync, inflateSync } from 'zlib'; import { Env } from './env'; import { createLogger } from './logger'; const logger = createLogger('crypto'); +const saltRounds = 10; + const compressData = (data: string): Buffer => { return deflateSync(Buffer.from(data, 'utf-8'), { level: 9, @@ -129,58 +132,29 @@ export function decryptString(data: string, secretKey?: Buffer): Response { } export function getSimpleTextHash(text: string): string { - // Use SHA-256 for a simple hash for non-sensitive data - const hash = createHash('sha256').update(text).digest('hex'); - return hash; + return createHash('sha256').update(text).digest('hex'); } /** * Creates a secure hash of text using PBKDF2 * @param text Text to hash - * @param salt Optional salt, will be generated if not provided - * @param iterations Number of iterations for PBKDF2, defaults to 10000 * @returns Object containing the hash and salt used */ -export function getTextHash( - text: string, - salt?: string, - iterations: number = 10000 -): { hash: string; salt: string } { - // Generate a random salt if not provided - const usedSalt = salt || randomBytes(16).toString('hex'); - - // Use PBKDF2 with SHA-512 - const hash = createHash('sha512') - .update(text + usedSalt) - .digest('hex'); - - // Apply multiple iterations - let result = hash; - for (let i = 0; i < iterations; i++) { - result = createHash('sha512') - .update(result + usedSalt) - .digest('hex'); - } - - return { hash: result, salt: usedSalt }; +export async function getTextHash(text: string): Promise { + return await hash(text, await genSalt(saltRounds)); } /** * Verifies if the provided text matches a previously generated hash * @param text Text to verify * @param storedHash Previously generated hash - * @param salt Salt used in the original hash - * @param iterations Number of iterations used in the original hash * @returns Boolean indicating if the text matches the hash */ -export function verifyHash( +export async function verifyHash( text: string, - storedHash: string, - salt: string, - iterations: number = 10000 -): boolean { - const { hash } = getTextHash(text, salt, iterations); - return hash === storedHash; + storedHash: string +): Promise { + return compare(text, storedHash); } /** @@ -189,11 +163,11 @@ export function verifyHash( * @param salt Optional salt, will be generated if not provided * @returns Object containing the key and salt used */ -export function deriveKey( +export async function deriveKey( password: string, salt?: string -): { key: Buffer; salt: string } { - salt = salt || randomBytes(16).toString('hex'); +): Promise<{ key: Buffer; salt: string }> { + salt = salt || (await genSalt(saltRounds)); const key = pbkdf2Sync( Buffer.from(password, 'utf-8'), Buffer.from(salt, 'hex'), diff --git a/packages/core/src/utils/env.ts b/packages/core/src/utils/env.ts index 55390366..c1eca415 100644 --- a/packages/core/src/utils/env.ts +++ b/packages/core/src/utils/env.ts @@ -163,6 +163,10 @@ export const Env = cleanEnv(process.env, { default: metadata?.version || 'unknown', desc: 'Version of the addon', }), + TAG: readonly({ + default: metadata?.tag || 'unknown', + desc: 'Tag of the addon', + }), DESCRIPTION: readonly({ default: metadata?.description || 'unknown', desc: 'Description of the addon', @@ -176,10 +180,6 @@ export const Env = cleanEnv(process.env, { default: metadata?.commitHash || 'unknown', desc: 'Git commit hash of the addon', }), - GIT_TAG: readonly({ - default: metadata?.latestTag || 'unknown', - desc: 'Git tag of the addon', - }), BUILD_TIME: readonly({ default: metadata?.buildTime || 'unknown', desc: 'Build time of the addon', @@ -188,6 +188,10 @@ export const Env = cleanEnv(process.env, { default: metadata?.commitTime || 'unknown', desc: 'Build commit time of the addon', }), + BASE_URL: url({ + default: undefined, + desc: 'Base URL of the addon e.g. https://aiostreams.com', + }), ADDON_NAME: str({ default: 'AIOStreams', desc: 'Name of the addon', @@ -200,9 +204,9 @@ export const Env = cleanEnv(process.env, { default: 3000, desc: 'Port to run the addon on', }), - BRANDING: str({ - default: process.env.NEXT_PUBLIC_ELFHOSTED_BRANDING, - desc: 'Branding for the addon', + CUSTOM_HTML: str({ + default: undefined, + desc: 'Custom HTML for the addon', }), SECRET_KEY: secretKey({ desc: 'Secret key for the addon, used for encryption and must be 64 characters of hex', @@ -770,6 +774,13 @@ export const Env = cleanEnv(process.env, { STREAM_API_RATE_LIMIT_MAX_REQUESTS: num({ default: 20, // allow 100 requests per IP per minute }), + FORMAT_API_RATE_LIMIT_WINDOW: num({ + default: 10, // 10 seconds + desc: 'Time window for format API rate limiting in seconds', + }), + FORMAT_API_RATE_LIMIT_MAX_REQUESTS: num({ + default: 50, // allow 50 requests per IP per 10 seconds + }), STREMIO_STREAM_RATE_LIMIT_WINDOW: num({ default: 60, // 1 minute desc: 'Time window for Stremio stream rate limiting in seconds', diff --git a/packages/frontend/package.json b/packages/frontend/package.json index d1530a5f..103e8ca9 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -1,6 +1,6 @@ { "name": "@aiostreams/frontend", - "version": "2.0.0", + "version": "0.0.0", "private": true, "scripts": { "dev": "next dev", @@ -9,11 +9,15 @@ "lint": "next lint" }, "dependencies": { - "@aiostreams/core": "^2", + "@aiostreams/core": "^0.0.0", + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/modifiers": "^9.0.0", + "@dnd-kit/sortable": "^10.0.0", "@headlessui/react": "^2.2.4", "@headlessui/tailwindcss": "^0.2.2", "@radix-ui/react-accordion": "^1.2.11", "@radix-ui/react-dialog": "^1.1.14", + "@radix-ui/react-dropdown-menu": "^2.1.15", "@radix-ui/react-popover": "^1.1.14", "@radix-ui/react-select": "^2.2.5", "@radix-ui/react-separator": "^1.1.7", @@ -21,8 +25,11 @@ "@radix-ui/react-tooltip": "^1.2.7", "@tailwindcss/forms": "^0.5.10", "@tailwindcss/typography": "^0.5.16", + "@zag-js/number-input": "^1.13.1", + "@zag-js/react": "^1.13.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "cmdk": "^1.1.1", "framer-motion": "^12.12.2", "lucide-react": "^0.511.0", "next": "15.3.2", @@ -30,6 +37,7 @@ "react": "^19.0.0", "react-dom": "^19.0.0", "react-icons": "^5.5.0", + "react-markdown": "^10.1.0", "sonner": "^2.0.3", "tailwind-merge": "^3.3.0", "tailwind-scrollbar-hide": "~1.1.7", diff --git a/packages/frontend/src/app/main-sidebar.tsx b/packages/frontend/src/app/main-sidebar.tsx index db88ff0f..ce254452 100644 --- a/packages/frontend/src/app/main-sidebar.tsx +++ b/packages/frontend/src/app/main-sidebar.tsx @@ -5,7 +5,7 @@ import { AppSidebar, useAppSidebarContext } from '@/components/ui/app-layout'; import { cn } from '@/components/ui/core/styling'; import { VerticalMenu, VerticalMenuItem } from '@/components/ui/vertical-menu'; import { useStatus } from '@/context/status'; -import { useMenu, MenuId } from '@/context/menu'; +import { useMenu, MenuId, VALID_MENUS } from '@/context/menu'; import { useUserData } from '@/context/userData'; import { ConfigModal } from '@/components/config-modal'; import { @@ -31,6 +31,7 @@ import { } from '@/components/shared/confirmation-dialog'; import { Modal } from '@/components/ui/modal'; import { TextInput } from '@/components/ui/text-input'; +import { toast } from 'sonner'; type MenuItem = VerticalMenuItem & { id: MenuId; @@ -56,6 +57,12 @@ export function MainSidebar() { setInitialUuid(extractedUuid); signInModal.open(); } + // check for menu query param + const params = new URLSearchParams(window.location.search); + const menu = params.get('menu'); + if (menu && VALID_MENUS.includes(menu)) { + setSelectedMenu(menu); + } }, [pathname]); const { status, error, loading } = useStatus(); @@ -66,6 +73,7 @@ export function MainSidebar() { onConfirm: () => { user.setUserData(null); user.setUuid(null); + user.setPassword(null); }, }); @@ -194,7 +202,11 @@ export function MainSidebar() {
logo - {status ? `v${status.version}` : ''} + {status + ? status.tag.includes('nightly') + ? 'nightly' + : status.tag + : ''}
{ signInModal.close(); + toast.success('Signed in successfully'); }} onOpenChange={(v) => { if (!v) { diff --git a/packages/frontend/src/components/config-modal.tsx b/packages/frontend/src/components/config-modal.tsx index 65f21369..2b68fc69 100644 --- a/packages/frontend/src/components/config-modal.tsx +++ b/packages/frontend/src/components/config-modal.tsx @@ -4,6 +4,7 @@ import { TextInput } from '@/components/ui/text-input'; import { Button } from '@/components/ui/button'; import { UserConfigAPI } from '@/services/api'; import { useUserData } from '@/context/userData'; +import { toast } from 'sonner'; interface ConfigModalProps { open: boolean; @@ -12,57 +13,39 @@ interface ConfigModalProps { initialUuid?: string; } -{ - /* -
- setConfigId(e.target.value)} - /> - -
-
*/ -} - export function ConfigModal({ open, onSuccess, onOpenChange, initialUuid, }: ConfigModalProps) { - const { setUserData, setUuid, setPassword } = useUserData(); + const { setUserData, setUuid, setPassword, setEncryptedPassword } = + useUserData(); const [uuid, setUuidInput] = React.useState(initialUuid || ''); const [password, setPasswordInput] = React.useState(''); - const [error, setError] = React.useState(''); const [loading, setLoading] = React.useState(false); console.log(`received initialUuid: ${initialUuid}`); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - setError(''); setLoading(true); try { const result = await UserConfigAPI.loadConfig(uuid, password); if (!result.success || !result.data) { - setError(result.error || 'Failed to load configuration'); + toast.error(result.error || 'Failed to load configuration'); return; } setUserData(result.data.config); setUuid(uuid); setPassword(password); + setEncryptedPassword(result.data.encryptedPassword); onSuccess(); } catch (err) { - setError( + toast.error( err instanceof Error ? err.message : 'Failed to load configuration' ); } finally { @@ -74,7 +57,6 @@ export function ConfigModal({ React.useEffect(() => { if (!open) { setPasswordInput(''); - setError(''); } }, [open]); @@ -120,7 +102,6 @@ export function ConfigModal({ required /> - {error &&
{error}
}
+ +
+ + + ); +} diff --git a/packages/frontend/src/components/menu/addons.tsx b/packages/frontend/src/components/menu/addons.tsx index 8e3810f3..128e07f8 100644 --- a/packages/frontend/src/components/menu/addons.tsx +++ b/packages/frontend/src/components/menu/addons.tsx @@ -1,29 +1,697 @@ 'use client'; +import React, { useState, useMemo, useEffect } from 'react'; import { PageWrapper } from '../shared/page-wrapper'; +import { useStatus } from '@/context/status'; +import { useUserData } from '@/context/userData'; +import { SettingsCard } from '../shared/settings-card'; +import { Button, IconButton } from '../ui/button'; +import { Modal } from '../ui/modal'; +import { Switch } from '../ui/switch'; +import { DndContext } from '@dnd-kit/core'; +import { restrictToVerticalAxis } from '@dnd-kit/modifiers'; +import { + arrayMove, + SortableContext, + verticalListSortingStrategy, + useSortable, +} from '@dnd-kit/sortable'; +import { CSS } from '@dnd-kit/utilities'; +import { PlusIcon, SearchIcon, FilterIcon } from 'lucide-react'; +import TemplateOption from '../shared/template-option'; +import * as constants from '../../../../core/src/utils/constants'; +import { TextInput } from '../ui/text-input'; + +import { Popover } from '../ui/popover'; +import { BiEdit, BiTrash } from 'react-icons/bi'; +import { Option, Resource } from '@aiostreams/core'; +import { toast } from 'sonner'; export function AddonsMenu() { return ( - <> - - - - + + + ); } function Content() { + const { status } = useStatus(); + const { userData, setUserData } = useUserData(); + const [search, setSearch] = useState(''); + // Filter states + const [serviceFilters, setServiceFilters] = useState([]); + const [streamTypeFilters, setStreamTypeFilters] = useState< + constants.StreamType[] + >([]); + const [resourceFilters, setResourceFilters] = useState([]); + // Modal states + const [modalOpen, setModalOpen] = useState(false); + const [modalMode, setModalMode] = useState<'add' | 'edit'>('add'); + const [modalPreset, setModalPreset] = useState(null); + const [modalInitialValues, setModalInitialValues] = useState< + Record + >({}); + const [editingAddonId, setEditingAddonId] = useState(null); + + // Filtering and search for marketplace + const filteredPresets = useMemo(() => { + if (!status?.settings?.presets) return []; + return status.settings.presets.filter((preset) => { + const matchesService = + serviceFilters.length === 0 || + preset.ID === 'custom' || + (preset.SUPPORTED_SERVICES && + serviceFilters.every((s) => preset.SUPPORTED_SERVICES.includes(s))); + const matchesStreamType = + streamTypeFilters.length === 0 || + preset.ID === 'custom' || + (preset.SUPPORTED_STREAM_TYPES && + streamTypeFilters.every((t) => + preset.SUPPORTED_STREAM_TYPES.includes(t) + )); + const matchesResource = + resourceFilters.length === 0 || + preset.ID === 'custom' || + (preset.SUPPORTED_RESOURCES && + resourceFilters.every((r) => + preset.SUPPORTED_RESOURCES.includes(r as Resource) + )); + const matchesSearch = + !search || + preset.NAME.toLowerCase().includes(search.toLowerCase()) || + preset.DESCRIPTION.toLowerCase().includes(search.toLowerCase()); + return ( + matchesService && matchesStreamType && matchesResource && matchesSearch + ); + }); + }, [status, search, serviceFilters, streamTypeFilters, resourceFilters]); + + // My Addons (user's enabled/added presets) + + // AddonModal handlers + function handleAddPreset(preset: any) { + setModalPreset(preset); + setModalInitialValues({ + options: Object.fromEntries( + (preset.OPTIONS || []).map((opt: any) => [opt.id, opt.default ?? '']) + ), + }); + setModalMode('add'); + setEditingAddonId(null); + setModalOpen(true); + } + + function handleModalSubmit(values: Record) { + if (modalMode === 'add' && modalPreset) { + // Always add a new preset with default values, never edit + const newPreset = { + id: modalPreset.ID, + enabled: true, + options: values.options, + }; + const newKey = getPresetUniqueKey(newPreset); + // Prevent adding if a preset with the same unique key already exists + if (userData.presets.some((a) => getPresetUniqueKey(a) === newKey)) { + setModalOpen(false); + return; + } + setUserData((prev) => ({ + ...prev, + presets: [...prev.presets, newPreset], + })); + setModalOpen(false); + } else if (modalMode === 'edit' && editingAddonId) { + // Edit existing preset (should not be triggered from marketplace) + setUserData((prev) => ({ + ...prev, + presets: prev.presets.map((a) => + getPresetUniqueKey(a) === editingAddonId + ? { ...a, options: values.options } + : a + ), + })); + setModalOpen(false); + } + } + + // DND for My Addons + function handleDragEnd(event: any) { + const { active, over } = event; + if (!over) return; + if (active.id !== over.id) { + const oldIndex = userData.presets.findIndex( + (a) => getPresetUniqueKey(a) === active.id + ); + const newIndex = userData.presets.findIndex( + (a) => getPresetUniqueKey(a) === over.id + ); + const newPresets = arrayMove(userData.presets, oldIndex, newIndex); + setUserData((prev) => ({ + ...prev, + presets: newPresets, + })); + } + } + + // Service, stream type, and resource options + const serviceOptions = Object.values(constants.SERVICE_DETAILS).map( + (service) => ({ label: service.name, value: service.id }) + ); + const streamTypeOptions = (constants.STREAM_TYPES || []).map( + (type: string) => ({ label: type, value: type }) + ); + const resourceOptions = (constants.RESOURCES || []).map((res: string) => ({ + label: res, + value: res, + })); + const activeFilterCount = + serviceFilters.length + streamTypeFilters.length + resourceFilters.length; + return ( <>

Addons

- Add your addons here. Either choose from presets, or add your own - with its' manifest URLs. + Add your addons here. Choose from the marketplace below.

+ + {/* Marketplace Section */} +
+
+
+ ) => + setSearch(e.target.value) + } + placeholder="Search addons..." + className="w-64" + leftIcon={} + /> + + } + intent={activeFilterCount > 0 ? 'primary' : 'primary-outline'} + aria-label="Filters" + /> + +
+
+ {/* Scrollable Addon Cards Grid */} +
+
+ {filteredPresets.map((preset: any) => { + // Always allow adding, never show edit + return ( + handleAddPreset(preset)} + /> + ); + })} +
+
+
+ + {/* Add/Edit Addon Modal */} + + + {/* My Addons Section */} + + + getPresetUniqueKey(a))} + strategy={verticalListSortingStrategy} + > +
+
    + {userData.presets.length === 0 ? ( +
  • +
    + + Looks like you don't have any addons. +
    + Add some from the marketplace above. +
    +
    +
  • + ) : ( + userData.presets.map((addon) => { + const preset = status?.settings?.presets.find( + (p: any) => p.ID === addon.id + ); + return ( + { + setModalPreset(preset); + setModalInitialValues({ + options: { ...addon.options }, + }); + setModalMode('edit'); + setEditingAddonId(getPresetUniqueKey(addon)); + setModalOpen(true); + }} + onRemove={() => { + setUserData((prev) => ({ + ...prev, + presets: prev.presets.filter( + (a) => + getPresetUniqueKey(a) !== + getPresetUniqueKey(addon) + ), + })); + }} + onToggleEnabled={(v: boolean) => { + setUserData((prev) => ({ + ...prev, + presets: prev.presets.map((a) => + getPresetUniqueKey(a) === + getPresetUniqueKey(addon) + ? { ...a, enabled: v } + : a + ), + })); + }} + /> + ); + }) + )} +
+
+
+
+
); } + +// Helper to generate a unique key for a user preset +function getPresetUniqueKey(preset: { + id: string; + enabled: boolean; + options: Record; +}) { + return JSON.stringify({ + id: preset.id, + enabled: preset.enabled, + options: preset.options, + }); +} + +// Sortable Addon Item for DND (handles both preset and custom addon) +function SortableAddonItem({ + addon, + preset, + onEdit, + onRemove, + onToggleEnabled, +}: { + addon: any; + preset: any; + onEdit: () => void; + onRemove: () => void; + onToggleEnabled: (v: boolean) => void; +}) { + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + isDragging, + } = useSortable({ + id: getPresetUniqueKey(addon), + }); + const style = { + transform: CSS.Transform.toString(transform), + transition, + opacity: isDragging ? 0.5 : 1, + }; + return ( +
  • +
    +
    + +
    +

    + {addon.options.name} +

    +
    + +
    + + } + intent="primary-subtle" + onClick={onEdit} + /> + } + intent="alert-subtle" + onClick={onRemove} + /> +
    +
    +
  • + ); +} + +// AddonCard component +function AddonCard({ + preset, + isAdded, + onAdd, +}: { + preset: any; + isAdded: boolean; + onAdd: () => void; +}) { + return ( +
    + {/* Top: Logo + Name/Description */} +
    + {preset.ID === 'custom' ? ( +
    + +
    + ) : ( + {preset.NAME} + )} +
    +
    {preset.NAME}
    +
    + {preset.DESCRIPTION} +
    +
    +
    + {/* Tags Section */} +
    +
    + {preset.SUPPORTED_SERVICES?.length > 0 && ( + + Services: + + )} + {preset.SUPPORTED_SERVICES?.map((sid: string) => ( + + {constants.SERVICE_DETAILS[ + sid as keyof typeof constants.SERVICE_DETAILS + ]?.shortName || sid} + + ))} +
    +
    + {preset.SUPPORTED_RESOURCES?.length > 0 && ( + + Resources: + + )} + {preset.SUPPORTED_RESOURCES?.map((res: string) => ( + + {res} + + ))} +
    +
    + {preset.SUPPORTED_STREAM_TYPES?.length > 0 && ( + + Stream Types: + + )} + {preset.SUPPORTED_STREAM_TYPES?.map((type: string) => ( + + {type} + + ))} +
    +
    + {/* Button at bottom */} +
    + +
    +
    + ); +} + +function AddonModal({ + open, + onOpenChange, + mode, + presetMetadata, + initialValues = {}, + onSubmit, +}: { + open: boolean; + onOpenChange: (v: boolean) => void; + mode: 'add' | 'edit'; + presetMetadata?: any; + initialValues?: Record; + onSubmit: (values: Record) => void; +}) { + const [values, setValues] = useState>(initialValues); + useEffect(() => { + setValues(initialValues); + }, [open, initialValues]); + const dynamicOptions: Option[] = presetMetadata?.OPTIONS || []; + + // Check if all required fields are filled + const allRequiredFilled = dynamicOptions.every((opt: any) => { + if (!opt.required) return true; + const val = values.options?.[opt.id]; + // For booleans, false is valid; for others, check for empty string/null/undefined + if (opt.type === 'boolean') return typeof val === 'boolean'; + return val !== undefined && val !== null && val !== ''; + }); + + function handleFormSubmit(e: React.FormEvent) { + e.preventDefault(); + + for (const opt of dynamicOptions) { + if (opt.constraints) { + const val = values.options?.[opt.id]; + if (typeof val === 'string') { + if (opt.constraints.min && val.length < opt.constraints.min) { + toast.error( + `${opt.name} must be at least ${opt.constraints.min} characters` + ); + return false; + } + if (opt.constraints.max && val.length > opt.constraints.max) { + toast.error( + `${opt.name} must be at most ${opt.constraints.max} characters` + ); + return false; + } + } else if (typeof val === 'number') { + if (opt.constraints.min && val < opt.constraints.min) { + toast.error(`${opt.name} must be at least ${opt.constraints.min}`); + return false; + } + if (opt.constraints.max && val > opt.constraints.max) { + toast.error(`${opt.name} must be at most ${opt.constraints.max}`); + return false; + } + } + } + } + if (allRequiredFilled) { + onSubmit(values); + } else { + toast.error('Please fill in all required fields'); + } + } + + return ( + +
    + {dynamicOptions.map((opt: any) => ( +
    + + setValues((val) => ({ + ...val, + options: { ...val.options, [opt.id]: v }, + })) + } + disabled={false} + /> +
    + ))} + +
    +
    + ); +} + +function AddonFilterPopover({ + serviceOptions, + streamTypeOptions, + resourceOptions, + serviceFilters, + setServiceFilters, + streamTypeFilters, + setStreamTypeFilters, + resourceFilters, + setResourceFilters, + children, +}: any) { + const [open, setOpen] = useState(false); + return ( + +
    +
    +
    +
    + Services +
    + {serviceOptions.map((opt: any) => ( +
    + { + setServiceFilters((prev: string[]) => + checked + ? [...prev, opt.value] + : prev.filter((v) => v !== opt.value) + ); + }} + size="sm" + /> + {opt.label} +
    + ))} +
    +
    +
    + Stream Types +
    + {streamTypeOptions.map((opt: any) => ( +
    + { + setStreamTypeFilters((prev: string[]) => + checked + ? [...prev, opt.value] + : prev.filter((v) => v !== opt.value) + ); + }} + size="sm" + /> + {opt.label} +
    + ))} +
    +
    +
    + Resources +
    + {resourceOptions.map((opt: any) => ( +
    + { + setResourceFilters((prev: string[]) => + checked + ? [...prev, opt.value] + : prev.filter((v) => v !== opt.value) + ); + }} + size="sm" + /> + {opt.label} +
    + ))} +
    +
    + +
    +
    + ); +} diff --git a/packages/frontend/src/components/menu/formatter.tsx b/packages/frontend/src/components/menu/formatter.tsx index 39ab2ec9..06914680 100644 --- a/packages/frontend/src/components/menu/formatter.tsx +++ b/packages/frontend/src/components/menu/formatter.tsx @@ -21,15 +21,18 @@ import { toast } from 'sonner'; const formatterChoices = Object.values(constants.FORMATTER_DETAILS); -// Simple debounce utility -function debounce any>( +// Simple throttle utility +let lastCall = 0; +function throttle any>( func: T, wait: number ): (...args: Parameters) => void { - let timeout: NodeJS.Timeout; return (...args: Parameters) => { - clearTimeout(timeout); - timeout = setTimeout(() => func(...args), wait); + const now = Date.now(); + if (now - lastCall >= wait) { + lastCall = now; + func(...args); + } }; } @@ -183,19 +186,31 @@ function Content() { }; let data; if (selectedFormatter === constants.CUSTOM_FORMATTER) { - data = ( - await UserConfigAPI.formatStream(stream, selectedFormatter, { + const res = await UserConfigAPI.formatStream( + stream, + selectedFormatter, + { name: customName, description: customDescription, - }) - ).data; + } + ); + if (!res.success) { + toast.error(res.error || 'Failed to format stream'); + return; + } + data = res.data; } else { - data = (await UserConfigAPI.formatStream(stream, selectedFormatter)) - .data; + const res = await UserConfigAPI.formatStream(stream, selectedFormatter); + if (!res.success) { + toast.error(res.error || 'Failed to format stream'); + return; + } + data = res.data; } setFormattedStream(data ?? null); } catch (error) { console.error('Error formatting stream:', error); + toast.error('Failed to format stream'); } finally { setIsFormatting(false); } @@ -220,14 +235,14 @@ function Content() { regexMatched, ]); - // Debounced format function - const debouncedFormat = useCallback(debounce(formatStream, 200), [ + // Throttled format function + const throttledFormat = useCallback(throttle(formatStream, 200), [ formatStream, ]); // Call format whenever relevant values change useEffect(() => { - debouncedFormat(); + throttledFormat(); }, [ filename, folder, diff --git a/packages/frontend/src/components/menu/save-install.tsx b/packages/frontend/src/components/menu/save-install.tsx index d6319b7d..030ede18 100644 --- a/packages/frontend/src/components/menu/save-install.tsx +++ b/packages/frontend/src/components/menu/save-install.tsx @@ -11,6 +11,8 @@ import { cn } from '@/components/ui/core/styling'; import { CardTitle } from '@/components/ui/card'; import { toast } from 'sonner'; import { DownloadIcon, ImportIcon, UploadIcon } from 'lucide-react'; +import { useStatus } from '@/context/status'; +// import { UserDataSchema } from '../../../../core/src/db/schemas'; export function SaveInstallMenu() { return ( @@ -23,19 +25,37 @@ export function SaveInstallMenu() { } function Content() { - const { userData, setUserData, uuid, setUuid, password, setPassword } = - useUserData(); + const { + userData, + setUserData, + uuid, + setUuid, + password, + setPassword, + encryptedPassword, + setEncryptedPassword, + } = useUserData(); const [newPassword, setNewPassword] = React.useState(''); - const [encryptedPassword, setEncryptedPassword] = React.useState(''); const [loading, setLoading] = React.useState(false); const [passwordRequirements, setPasswordRequirements] = React.useState< string[] >([]); - const baseUrl = window.location.origin; + const { status } = useStatus(); + const baseUrl = status?.settings?.baseUrl || window.location.origin; + const importFileRef = React.useRef(null); React.useEffect(() => { const requirements: string[] = []; + console.log(uuid, password); + // already created a config + if (uuid && password) { + return; + } + + console.log(newPassword); + console.log('checking requirements'); + if (newPassword.length < 8) { requirements.push('Password must be at least 8 characters long'); } @@ -105,7 +125,13 @@ function Content() { reader.onload = (event) => { try { const json = JSON.parse(event.target?.result as string); + // const validate = UserDataSchema.safeParse(json); + // if (!validate.success) { + // toast.error('Failed to import configuration: Invalid JSON file'); + // return; + // } setUserData(json); + toast.success('Configuration imported successfully'); } catch (err) { toast.error('Failed to import configuration: Invalid JSON file'); } @@ -274,9 +300,10 @@ function Content() {