pstream is dead; long live pstream taciturnaxolotl.github.io/pstream-ng/
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

update sudo-flix

+579 -94
+2
Dockerfile
··· 23 23 ARG DISALLOWED_IDS 24 24 ARG CDN_REPLACEMENTS 25 25 ARG TURNSTILE_KEY 26 + ARG ALLOW_AUTOPLAY="false" 26 27 27 28 ENV VITE_PWA_ENABLED=${PWA_ENABLED} 28 29 ENV VITE_GA_ID=${GA_ID} ··· 39 40 ENV VITE_DISALLOWED_IDS=${DISALLOWED_IDS} 40 41 ENV VITE_CDN_REPLACEMENTS=${CDN_REPLACEMENTS} 41 42 ENV VITE_TURNSTILE_KEY=${TURNSTILE_KEY} 43 + ENV VITE_ALLOW_AUTOPLAY=${ALLOW_AUTOPLAY} 42 44 43 45 COPY . ./ 44 46 RUN pnpm run build
+3 -3
package.json
··· 1 1 { 2 2 "name": "movie-web", 3 - "version": "4.6.6", 3 + "version": "4.7.0", 4 4 "private": true, 5 5 "homepage": "https://github.com/movie-web/movie-web", 6 6 "scripts": { ··· 30 30 "@headlessui/react": "^1.7.18", 31 31 "@ladjs/country-language": "^1.0.3", 32 32 "@movie-web/providers": "github:sussy-code/sudo-providers", 33 - "@noble/hashes": "^1.4.0", 34 - "@plasmohq/messaging": "^0.6.2", 33 + "@noble/hashes": "^1.3.3", 34 + "@plasmohq/messaging": "^0.6.1", 35 35 "@react-spring/web": "^9.7.3", 36 36 "@scure/bip39": "^1.3.0", 37 37 "@sozialhelden/ietf-language-tags": "^5.4.2",
+308
pnpm-lock.yaml
··· 334 334 '@jridgewell/gen-mapping': 0.3.5 335 335 '@jridgewell/trace-mapping': 0.3.25 336 336 337 + /@ampproject/remapping@2.3.0: 338 + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} 339 + engines: {node: '>=6.0.0'} 340 + dependencies: 341 + '@jridgewell/gen-mapping': 0.3.5 342 + '@jridgewell/trace-mapping': 0.3.25 343 + dev: true 344 + 337 345 /@apideck/better-ajv-errors@0.3.6(ajv@8.12.0): 338 346 resolution: {integrity: sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==} 339 347 engines: {node: '>=10'} ··· 396 404 '@jridgewell/trace-mapping': 0.3.25 397 405 jsesc: 2.5.2 398 406 407 + /@babel/generator@7.24.1: 408 + resolution: {integrity: sha512-DfCRfZsBcrPEHUfuBMgbJ1Ut01Y/itOs+hY2nFLgqsqXd52/iSiVq5TITtUasIUgm+IIKdY2/1I7auiQOEeC9A==} 409 + engines: {node: '>=6.9.0'} 410 + dependencies: 411 + '@babel/types': 7.24.0 412 + '@jridgewell/gen-mapping': 0.3.5 413 + '@jridgewell/trace-mapping': 0.3.25 414 + jsesc: 2.5.2 415 + dev: true 416 + 399 417 /@babel/helper-annotate-as-pure@7.22.5: 400 418 resolution: {integrity: sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==} 401 419 engines: {node: '>=6.9.0'} ··· 465 483 - supports-color 466 484 dev: true 467 485 486 + /@babel/helper-define-polyfill-provider@0.6.1(@babel/core@7.24.3): 487 + resolution: {integrity: sha512-o7SDgTJuvx5vLKD6SFvkydkSMBvahDKGiNJzG22IZYXhiqoe9efY7zocICBgzHV4IRg5wdgl2nEL/tulKIEIbA==} 488 + peerDependencies: 489 + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 490 + dependencies: 491 + '@babel/core': 7.24.3 492 + '@babel/helper-compilation-targets': 7.23.6 493 + '@babel/helper-plugin-utils': 7.24.0 494 + debug: 4.3.4 495 + lodash.debounce: 4.0.8 496 + resolve: 1.22.8 497 + transitivePeerDependencies: 498 + - supports-color 499 + dev: true 500 + 468 501 /@babel/helper-environment-visitor@7.22.20: 469 502 resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==} 470 503 engines: {node: '>=6.9.0'} ··· 508 541 '@babel/helper-split-export-declaration': 7.22.6 509 542 '@babel/helper-validator-identifier': 7.22.20 510 543 544 + /@babel/helper-module-transforms@7.23.3(@babel/core@7.24.3): 545 + resolution: {integrity: sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==} 546 + engines: {node: '>=6.9.0'} 547 + peerDependencies: 548 + '@babel/core': ^7.0.0 549 + dependencies: 550 + '@babel/core': 7.24.3 551 + '@babel/helper-environment-visitor': 7.22.20 552 + '@babel/helper-module-imports': 7.22.15 553 + '@babel/helper-simple-access': 7.22.5 554 + '@babel/helper-split-export-declaration': 7.22.6 555 + '@babel/helper-validator-identifier': 7.22.20 556 + dev: true 557 + 511 558 /@babel/helper-optimise-call-expression@7.22.5: 512 559 resolution: {integrity: sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==} 513 560 engines: {node: '>=6.9.0'} ··· 543 590 '@babel/helper-optimise-call-expression': 7.22.5 544 591 dev: true 545 592 593 + /@babel/helper-replace-supers@7.24.1(@babel/core@7.24.3): 594 + resolution: {integrity: sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ==} 595 + engines: {node: '>=6.9.0'} 596 + peerDependencies: 597 + '@babel/core': ^7.0.0 598 + dependencies: 599 + '@babel/core': 7.24.3 600 + '@babel/helper-environment-visitor': 7.22.20 601 + '@babel/helper-member-expression-to-functions': 7.23.0 602 + '@babel/helper-optimise-call-expression': 7.22.5 603 + dev: true 604 + 546 605 /@babel/helper-simple-access@7.22.5: 547 606 resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==} 548 607 engines: {node: '>=6.9.0'} ··· 565 624 /@babel/helper-string-parser@7.24.1: 566 625 resolution: {integrity: sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==} 567 626 engines: {node: '>=6.9.0'} 627 + 628 + /@babel/helper-string-parser@7.24.1: 629 + resolution: {integrity: sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==} 630 + engines: {node: '>=6.9.0'} 631 + dev: true 568 632 569 633 /@babel/helper-validator-identifier@7.22.20: 570 634 resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} ··· 1905 1969 '@jridgewell/resolve-uri': 3.1.2 1906 1970 '@jridgewell/sourcemap-codec': 1.4.15 1907 1971 1972 + /@jridgewell/trace-mapping@0.3.25: 1973 + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} 1974 + dependencies: 1975 + '@jridgewell/resolve-uri': 3.1.2 1976 + '@jridgewell/sourcemap-codec': 1.4.15 1977 + dev: true 1978 + 1908 1979 /@ladjs/country-language@1.0.3: 1909 1980 resolution: {integrity: sha512-FJROu9/hh4eqVAGDyfL8vpv6Vb0qKHX1ozYLRZ+beUzD5xFf+3r0J+SVIWKviEa7W524Qvqou+ta1WrsRgzxGw==} 1910 1981 engines: {node: '>= 14'} ··· 2247 2318 resolution: {integrity: sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==} 2248 2319 dependencies: 2249 2320 undici-types: 5.26.5 2321 + 2322 + /@types/node@20.12.2: 2323 + resolution: {integrity: sha512-zQ0NYO87hyN6Xrclcqp7f8ZbXNbRfoGWNcMvHTPQp9UUrwI0mI7XBz+cu7/W6/VClYo2g63B0cjull/srU7LgQ==} 2324 + dependencies: 2325 + undici-types: 5.26.5 2326 + dev: true 2250 2327 2251 2328 /@types/pako@2.0.3: 2252 2329 resolution: {integrity: sha512-bq0hMV9opAcrmE0Byyo0fY3Ew4tgOevJmQ9grUhpXQhYfyLJ1Kqg3P33JT5fdbT2AjeAjR51zqqVjAL/HMkx7Q==} ··· 2533 2610 resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} 2534 2611 engines: {node: '>=0.4.0'} 2535 2612 hasBin: true 2613 + dev: false 2614 + 2615 + /acorn@8.11.3: 2616 + resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} 2617 + engines: {node: '>=0.4.0'} 2618 + hasBin: true 2619 + dev: true 2536 2620 2537 2621 /agent-base@7.1.1: 2538 2622 resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} ··· 2707 2791 is-shared-array-buffer: 1.0.3 2708 2792 dev: true 2709 2793 2794 + /arraybuffer.prototype.slice@1.0.3: 2795 + resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} 2796 + engines: {node: '>= 0.4'} 2797 + dependencies: 2798 + array-buffer-byte-length: 1.0.1 2799 + call-bind: 1.0.7 2800 + define-properties: 1.2.1 2801 + es-abstract: 1.23.3 2802 + es-errors: 1.3.0 2803 + get-intrinsic: 1.2.4 2804 + is-array-buffer: 3.0.4 2805 + is-shared-array-buffer: 1.0.3 2806 + dev: true 2807 + 2710 2808 /assertion-error@1.1.0: 2711 2809 resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} 2712 2810 dev: true ··· 2750 2848 possible-typed-array-names: 1.0.0 2751 2849 dev: true 2752 2850 2851 + /available-typed-arrays@1.0.7: 2852 + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} 2853 + engines: {node: '>= 0.4'} 2854 + dependencies: 2855 + possible-typed-array-names: 1.0.0 2856 + dev: true 2857 + 2753 2858 /axe-core@4.7.0: 2754 2859 resolution: {integrity: sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ==} 2755 2860 engines: {node: '>=4'} ··· 2793 2898 dependencies: 2794 2899 '@babel/core': 7.24.4 2795 2900 '@babel/helper-define-polyfill-provider': 0.6.1(@babel/core@7.24.4) 2901 + transitivePeerDependencies: 2902 + - supports-color 2903 + dev: true 2904 + 2905 + /babel-plugin-polyfill-regenerator@0.6.1(@babel/core@7.24.3): 2906 + resolution: {integrity: sha512-JfTApdE++cgcTWjsiCQlLyFBMbTUft9ja17saCc93lgV33h4tuCVj7tlvu//qpLwaG+3yEz7/KhahGrUMkVq9g==} 2907 + peerDependencies: 2908 + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 2909 + dependencies: 2910 + '@babel/core': 7.24.3 2911 + '@babel/helper-define-polyfill-provider': 0.6.1(@babel/core@7.24.3) 2796 2912 transitivePeerDependencies: 2797 2913 - supports-color 2798 2914 dev: true ··· 2844 2960 node-releases: 2.0.14 2845 2961 update-browserslist-db: 1.0.13(browserslist@4.23.0) 2846 2962 2963 + /browserslist@4.23.0: 2964 + resolution: {integrity: sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==} 2965 + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} 2966 + hasBin: true 2967 + dependencies: 2968 + caniuse-lite: 1.0.30001603 2969 + electron-to-chromium: 1.4.722 2970 + node-releases: 2.0.14 2971 + update-browserslist-db: 1.0.13(browserslist@4.23.0) 2972 + dev: true 2973 + 2847 2974 /buffer-from@1.1.2: 2848 2975 resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} 2849 2976 dev: true ··· 2856 2983 /cac@6.7.14: 2857 2984 resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} 2858 2985 engines: {node: '>=8'} 2986 + dev: true 2987 + 2988 + /call-bind@1.0.7: 2989 + resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} 2990 + engines: {node: '>= 0.4'} 2991 + dependencies: 2992 + es-define-property: 1.0.0 2993 + es-errors: 1.3.0 2994 + function-bind: 1.1.2 2995 + get-intrinsic: 1.2.4 2996 + set-function-length: 1.2.2 2859 2997 dev: true 2860 2998 2861 2999 /call-bind@1.0.7: ··· 3238 3376 gopd: 1.0.1 3239 3377 dev: true 3240 3378 3379 + /define-data-property@1.1.4: 3380 + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} 3381 + engines: {node: '>= 0.4'} 3382 + dependencies: 3383 + es-define-property: 1.0.0 3384 + es-errors: 1.3.0 3385 + gopd: 1.0.1 3386 + dev: true 3387 + 3241 3388 /define-lazy-prop@2.0.0: 3242 3389 resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} 3243 3390 engines: {node: '>=8'} ··· 3348 3495 3349 3496 /electron-to-chromium@1.4.736: 3350 3497 resolution: {integrity: sha512-Rer6wc3ynLelKNM4lOCg7/zPQj8tPOCB2hzD32PX9wd3hgRRi9MxEbmkFCokzcEhRVMiOVLjnL9ig9cefJ+6+Q==} 3498 + 3499 + /electron-to-chromium@1.4.722: 3500 + resolution: {integrity: sha512-5nLE0TWFFpZ80Crhtp4pIp8LXCztjYX41yUcV6b+bKR2PqzjskTMOOlBi1VjBHlvHwS+4gar7kNKOrsbsewEZQ==} 3501 + dev: true 3351 3502 3352 3503 /emoji-regex@8.0.0: 3353 3504 resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} ··· 4104 4255 hasown: 2.0.2 4105 4256 dev: true 4106 4257 4258 + /get-intrinsic@1.2.4: 4259 + resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} 4260 + engines: {node: '>= 0.4'} 4261 + dependencies: 4262 + es-errors: 1.3.0 4263 + function-bind: 1.1.2 4264 + has-proto: 1.0.3 4265 + has-symbols: 1.0.3 4266 + hasown: 2.0.2 4267 + dev: true 4268 + 4107 4269 /get-own-enumerable-property-symbols@3.0.2: 4108 4270 resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==} 4109 4271 dev: true ··· 4245 4407 engines: {node: '>= 0.4'} 4246 4408 dev: true 4247 4409 4410 + /has-proto@1.0.3: 4411 + resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} 4412 + engines: {node: '>= 0.4'} 4413 + dev: true 4414 + 4248 4415 /has-symbols@1.0.3: 4249 4416 resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} 4250 4417 engines: {node: '>= 0.4'} ··· 4391 4558 side-channel: 1.0.6 4392 4559 dev: true 4393 4560 4561 + /internal-slot@1.0.7: 4562 + resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} 4563 + engines: {node: '>= 0.4'} 4564 + dependencies: 4565 + es-errors: 1.3.0 4566 + hasown: 2.0.2 4567 + side-channel: 1.0.6 4568 + dev: true 4569 + 4394 4570 /invariant@2.2.4: 4395 4571 resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} 4396 4572 dependencies: 4397 4573 loose-envify: 1.4.0 4398 4574 dev: false 4575 + 4576 + /is-array-buffer@3.0.4: 4577 + resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} 4578 + engines: {node: '>= 0.4'} 4579 + dependencies: 4580 + call-bind: 1.0.7 4581 + get-intrinsic: 1.2.4 4582 + dev: true 4399 4583 4400 4584 /is-array-buffer@3.0.4: 4401 4585 resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} ··· 4454 4638 is-typed-array: 1.1.13 4455 4639 dev: true 4456 4640 4641 + /is-data-view@1.0.1: 4642 + resolution: {integrity: sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==} 4643 + engines: {node: '>= 0.4'} 4644 + dependencies: 4645 + is-typed-array: 1.1.13 4646 + dev: true 4647 + 4457 4648 /is-date-object@1.0.5: 4458 4649 resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} 4459 4650 engines: {node: '>= 0.4'} ··· 4509 4700 engines: {node: '>= 0.4'} 4510 4701 dev: true 4511 4702 4703 + /is-negative-zero@2.0.3: 4704 + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} 4705 + engines: {node: '>= 0.4'} 4706 + dev: true 4707 + 4512 4708 /is-number-object@1.0.7: 4513 4709 resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} 4514 4710 engines: {node: '>= 0.4'} ··· 4550 4746 /is-set@2.0.3: 4551 4747 resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} 4552 4748 engines: {node: '>= 0.4'} 4749 + dev: true 4750 + 4751 + /is-shared-array-buffer@1.0.3: 4752 + resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} 4753 + engines: {node: '>= 0.4'} 4754 + dependencies: 4755 + call-bind: 1.0.7 4553 4756 dev: true 4554 4757 4555 4758 /is-shared-array-buffer@1.0.3: ··· 5164 5367 resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} 5165 5368 dev: true 5166 5369 5370 + /object-inspect@1.13.1: 5371 + resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} 5372 + dev: true 5373 + 5167 5374 /object-keys@1.1.1: 5168 5375 resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} 5169 5376 engines: {node: '>= 0.4'} ··· 5795 6002 /regenerator-runtime@0.14.1: 5796 6003 resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} 5797 6004 6005 + /regenerator-runtime@0.14.1: 6006 + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} 6007 + dev: true 6008 + 5798 6009 /regenerator-transform@0.15.2: 5799 6010 resolution: {integrity: sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==} 5800 6011 dependencies: ··· 5811 6022 set-function-name: 2.0.2 5812 6023 dev: true 5813 6024 6025 + /regexp.prototype.flags@1.5.2: 6026 + resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} 6027 + engines: {node: '>= 0.4'} 6028 + dependencies: 6029 + call-bind: 1.0.7 6030 + define-properties: 1.2.1 6031 + es-errors: 1.3.0 6032 + set-function-name: 2.0.2 6033 + dev: true 6034 + 5814 6035 /regexpu-core@5.3.2: 5815 6036 resolution: {integrity: sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==} 5816 6037 engines: {node: '>=4'} ··· 5953 6174 resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} 5954 6175 dependencies: 5955 6176 queue-microtask: 1.2.3 6177 + dev: true 6178 + 6179 + /safe-array-concat@1.1.2: 6180 + resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} 6181 + engines: {node: '>=0.4'} 6182 + dependencies: 6183 + call-bind: 1.0.7 6184 + get-intrinsic: 1.2.4 6185 + has-symbols: 1.0.3 6186 + isarray: 2.0.5 5956 6187 dev: true 5957 6188 5958 6189 /safe-array-concat@1.1.2: ··· 5978 6209 is-regex: 1.1.4 5979 6210 dev: true 5980 6211 6212 + /safe-regex-test@1.0.3: 6213 + resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} 6214 + engines: {node: '>= 0.4'} 6215 + dependencies: 6216 + call-bind: 1.0.7 6217 + es-errors: 1.3.0 6218 + is-regex: 1.1.4 6219 + dev: true 6220 + 5981 6221 /safer-buffer@2.1.2: 5982 6222 resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} 5983 6223 dev: true ··· 6043 6283 has-property-descriptors: 1.0.2 6044 6284 dev: true 6045 6285 6286 + /set-function-name@2.0.2: 6287 + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} 6288 + engines: {node: '>= 0.4'} 6289 + dependencies: 6290 + define-data-property: 1.1.4 6291 + es-errors: 1.3.0 6292 + functions-have-names: 1.2.3 6293 + has-property-descriptors: 1.0.2 6294 + dev: true 6295 + 6046 6296 /set-harmonic-interval@1.0.1: 6047 6297 resolution: {integrity: sha512-AhICkFV84tBP1aWqPwLZqFvAwqEoVA9kxNMniGEUvzOlm4vLmOFLiTT3UZ6bziJTy4bOVpzWGTfSCbmaayGx8g==} 6048 6298 engines: {node: '>=6.9'} ··· 6062 6312 /shebang-regex@3.0.0: 6063 6313 resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 6064 6314 engines: {node: '>=8'} 6315 + dev: true 6316 + 6317 + /side-channel@1.0.6: 6318 + resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} 6319 + engines: {node: '>= 0.4'} 6320 + dependencies: 6321 + call-bind: 1.0.7 6322 + es-errors: 1.3.0 6323 + get-intrinsic: 1.2.4 6324 + object-inspect: 1.13.1 6065 6325 dev: true 6066 6326 6067 6327 /side-channel@1.0.6: ··· 6218 6478 6219 6479 /string.prototype.trimend@1.0.8: 6220 6480 resolution: {integrity: sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==} 6481 + dependencies: 6482 + call-bind: 1.0.7 6483 + define-properties: 1.2.1 6484 + es-object-atoms: 1.0.0 6485 + dev: true 6486 + 6487 + /string.prototype.trimstart@1.0.8: 6488 + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} 6489 + engines: {node: '>= 0.4'} 6221 6490 dependencies: 6222 6491 call-bind: 1.0.7 6223 6492 define-properties: 1.2.1 ··· 6604 6873 possible-typed-array-names: 1.0.0 6605 6874 dev: true 6606 6875 6876 + /typed-array-length@1.0.6: 6877 + resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} 6878 + engines: {node: '>= 0.4'} 6879 + dependencies: 6880 + call-bind: 1.0.7 6881 + for-each: 0.3.3 6882 + gopd: 1.0.1 6883 + has-proto: 1.0.3 6884 + is-typed-array: 1.1.13 6885 + possible-typed-array-names: 1.0.0 6886 + dev: true 6887 + 6607 6888 /typescript@4.9.5: 6608 6889 resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} 6609 6890 engines: {node: '>=4.2.0'} ··· 6679 6960 engines: {node: '>= 10.0.0'} 6680 6961 dev: true 6681 6962 6963 + /universalify@2.0.1: 6964 + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} 6965 + engines: {node: '>= 10.0.0'} 6966 + dev: true 6967 + 6682 6968 /unpacker@1.0.1: 6683 6969 resolution: {integrity: sha512-0HTljwp8+JBdITpoHcK1LWi7X9U2BspUmWv78UWZh7NshYhbh1nec8baY/iSbe2OQTZ2bhAtVdnr6/BTD0DKVg==} 6684 6970 dev: false ··· 6707 6993 browserslist: 4.23.0 6708 6994 escalade: 3.1.2 6709 6995 picocolors: 1.0.0 6996 + 6997 + /update-browserslist-db@1.0.13(browserslist@4.23.0): 6998 + resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} 6999 + hasBin: true 7000 + peerDependencies: 7001 + browserslist: '>= 4.21.0' 7002 + dependencies: 7003 + browserslist: 4.23.0 7004 + escalade: 3.1.1 7005 + picocolors: 1.0.0 7006 + dev: true 6710 7007 6711 7008 /uri-js@4.4.1: 6712 7009 resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} ··· 7076 7373 is-set: 2.0.3 7077 7374 is-weakmap: 2.0.2 7078 7375 is-weakset: 2.0.3 7376 + dev: true 7377 + 7378 + /which-typed-array@1.1.15: 7379 + resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} 7380 + engines: {node: '>= 0.4'} 7381 + dependencies: 7382 + available-typed-arrays: 1.0.7 7383 + call-bind: 1.0.7 7384 + for-each: 0.3.3 7385 + gopd: 1.0.1 7386 + has-tostringtag: 1.0.2 7079 7387 dev: true 7080 7388 7081 7389 /which-typed-array@1.1.15:
+6 -3
src/assets/locales/en.json
··· 400 400 } 401 401 }, 402 402 "nextEpisode": { 403 - "replay": "Replay", 404 - "nextIn": "Next episode in {{seconds}}", 405 - "next": "Next Episode" 403 + "cancel": "Cancel", 404 + "next": "Next episode", 405 + "nextSeason": "Next season" 406 406 }, 407 407 "playbackError": { 408 408 "badge": "Playback error", ··· 582 582 "thumbnail": "Generate thumbnails", 583 583 "thumbnailDescription": "Most of the time, videos don't have thumbnails. You can enable this setting to generate them on the fly but they can make your video slower.", 584 584 "thumbnailLabel": "Generate thumbnails", 585 + "autoplay": "Autoplay", 586 + "autoplayDescription": "Automatically play the next episode in a series after reaching the end. Can be enabled by users with the browser extension, a custom proxy, or with the default setup if allowed by the host.", 587 + "autoplayLabel": "Autoplay", 585 588 "title": "Preferences" 586 589 }, 587 590 "reset": "Reset",
+10 -10
src/assets/locales/fa.json
··· 312 312 }, 313 313 "noEmbeds": { 314 314 "text": "نتوانستیم اطلاعات را پیدا کنیم، لطفا منبع دیگری را امتحان کنید.", 315 - "title": "اطلاعات پیدا نشد" 315 + "title": "هیج اطلاعاتی پیدا نشد" 316 316 }, 317 317 "noStream": { 318 318 "text": "فیلم یا سریال شما در این منبع وجود ندارد.", ··· 372 372 "badge": "مشکلی در پخش به وجود آمده", 373 373 "errors": { 374 374 "errorAborted": "دریافت محتوا با درخواست کاربر لغو شد.", 375 - "errorDecode": "با وجود اینکه قبلا مشخص شده بود که قابل استفاده است، یک خطا در هنگام تلاش برای رمزگشایی رسانه رخ داد که باعث مشکل شد.", 375 + "errorDecode": "با وجود اینکه قبلا مشخص شده بود که قابل استفاده است، یک خطا در هنگام تلاش برای رمزگشایی محتوا رخ داد که باعث مشکل شد.", 376 376 "errorGenericMedia": "خطای محتوای ناشناخته رخ داد.", 377 377 "errorNetwork": "با وجود اینکه قبلا در دسترس بود، نوعی خطای شبکه رخ داد که مانع از دریافت محتوا شد.", 378 - "errorNotSupported": "محتوا یا ارائه دهنده رسانه پشتیبانی نمی‌شود." 378 + "errorNotSupported": "محتوا یا ارائه دهنده محتوا پشتیبانی نمی‌شود." 379 379 }, 380 380 "homeButton": "بازگشت به خانه", 381 381 "text": "مشکلی در پخش محتوا وجود داشت. لطفا دوباره تلاش کنید.", ··· 398 398 "badge": "پیدا نشد", 399 399 "detailsButton": "نمایش جزئیات", 400 400 "homeButton": "بازگشت به خانه", 401 - "text": "ما در ارائه دهندگان جستجو کرده ایم ولی نمی توانیم محتوایی را که به دنبال آن را هستید پیدا کنیم! ما رسانه ها را میزبانی نمی کنیم و هیچ کنترلی بر آنچه در دسترس است نداریم. لطفا برای جزئیات بیشتر روی \"نمایش جزئیات\" در زیر کلیک کنید.", 401 + "text": "ما در ارائه دهندگان جستجو کرده ایم ولی نمی توانیم محتوایی را که به دنبال آن را هستید پیدا کنیم! ما محتواها را میزبانی نمی کنیم و هیچ کنترلی بر آنچه در دسترس است نداریم. لطفا برای جزئیات بیشتر روی \"نمایش جزئیات\" در زیر کلیک کنید.", 402 402 "title": "نتونستیم پیداش کنیم" 403 403 } 404 404 }, ··· 503 503 }, 504 504 "redoSetup": "تنظیم مجدد", 505 505 "successStatus": { 506 - "description": "همه چیز برای شروع تماشای محتوای مورد علاقه‌تان آماده است.", 506 + "description": "همه چیز برای شروع تماشای فیلم مورد علاقه‌تان آماده است.", 507 507 "title": "همه چیز تنظیم شده است!" 508 508 }, 509 509 "unsetStatus": { ··· 516 516 "addButton": "اضافه کردن worker جدید", 517 517 "description": "برای ایجاد عملکرد برنامه، تمام ترافیک از طریق پروکسی ها هدایت می شود. اگر میخواید این کار انجام دهید حتما از worker های خودتان استفاده کنید. <0>دستورالعمل ها.</0>", 518 518 "emptyState": "هنوز هیچ worker ای وجود ندارد، یکی اضافه کنید", 519 - "label": "استفاده از worker های پروکسی سفارشی", 519 + "label": "از پروکسی worker کاستوم استفاده کنید", 520 520 "urlLabel": "لینک worker ها", 521 521 "urlPlaceholder": "https://" 522 522 } ··· 525 525 "language": "زبان برنامه", 526 526 "languageDescription": "زبان برای کل برنامه اعمال شد.", 527 527 "thumbnail": "ایجاد تامبنیل", 528 - "thumbnailDescription": "بیشتر اوقات، ویدیوها تامبنیل ندارند. شما می توانید این تنظیم را فعال کنید تا آنها را در لحظه تولید کنید، اما آنها می توانند ویدیوی شما را کندتر کنند.", 528 + "thumbnailDescription": "بیشتر اوقات، ویدیوها تامبنیل ندارند. شما می توانید این تنظیم را فعال کنید تا آنها را در لحظه ببینید، اما آنها می توانند ویدیوی شما را کندتر کنند.", 529 529 "thumbnailLabel": "ایجاد تامبنیل", 530 530 "title": "اولویت ها" 531 531 }, ··· 536 536 "appVersion": "نسخه برنامه", 537 537 "backendUrl": "لینک بک-اند", 538 538 "backendVersion": "نسخه بک-اند", 539 - "hostname": "نام میزبان", 540 - "insecure": "نا امن", 539 + "hostname": "نام هاست", 540 + "insecure": "ناامن", 541 541 "notLoggedIn": "شما وارد نشده اید", 542 542 "secure": "امن", 543 543 "title": "اطلاعات برنامه", ··· 551 551 "colorLabel": "رنگ", 552 552 "previewQuote": "نباید بترسم، ترس قاتل ذهن است.", 553 553 "textSizeLabel": "اندازه متن", 554 - "title": "زیرنویس" 554 + "title": "زیرنویسها" 555 555 }, 556 556 "unsaved": "شما تغییرات ذخیره نشده دارید" 557 557 }
+56 -49
src/assets/locales/fr.json
··· 7 7 "title": "D'où vient le contenu ?" 8 8 }, 9 9 "q2": { 10 - "body": "Il est impossible de solliciter une émission ou un film car sudo-flix ne gère aucun contenu. Les sources sur Internet sont utilisées pour consulter tous les contenus.", 11 - "body": "Il est impossible de demander un film ou une série car sudo-flix ne gère aucun contenu. Le contenu est récupéré en explorant d'autres sites sur Internet.", 10 + "body": "Il est impossible de demander un film ou une série, car movie-web ne gère aucun contenu. Le contenu est récupéré en explorant d'autres sites sur Internet.", 12 11 "title": "Où puis-je demander une série ou un film ?" 13 12 }, 14 13 "q3": { ··· 26 25 "deviceNameLabel": "Nom de l'appareil", 27 26 "deviceNamePlaceholder": "Téléphone personnel", 28 27 "generate": { 29 - "description": "Le nom d'utilisateur et le mot de passe sont obtenus à partir de votre passphrase. Vous devrez la saisir pour accéder à votre compte, alors gardez-la précieusement", 30 - "next": "J'ai sauvegardé ma passphrase", 31 - "passphraseFrameLabel": "Passphrase", 32 - "title": "Votre passphrase" 28 + "description": "Le nom d'utilisateur et le mot de passe sont obtenus à partir de votre phrase d'accès. Vous devrez la saisir pour accéder à votre compte, alors gardez-la précieusement", 29 + "next": "J'ai sauvegardé ma phrase d'accès", 30 + "passphraseFrameLabel": "Phrase d'accès", 31 + "title": "Votre phrase d'accès" 33 32 }, 34 33 "hasAccount": "Avez-vous déjà un compte ? <0>Connectez-vous ici.</0>", 35 34 "login": { 36 - "description": "Veuillez saisir votre passphrase pour accéder à votre compte", 35 + "description": "Veuillez saisir votre phrase d'accès pour accéder à votre compte", 37 36 "deviceLengthError": "Veuillez saisir un nom d'appareil", 38 - "passphraseLabel": "Passphrase de 12 mots", 39 - "passphrasePlaceholder": "Passphrase", 37 + "passphraseLabel": "Phrase d'accès de 12 mots", 38 + "passphrasePlaceholder": "Phrase d'accès", 40 39 "submit": "Se connecter", 41 40 "title": "Connectez-vous à votre compte", 42 - "validationError": "Passphrase incorrecte ou incomplète" 41 + "validationError": "Phrase d'accès incorrecte ou incomplète" 43 42 }, 44 43 "register": { 45 44 "information": { 46 45 "color1": "Première couleur de profil", 47 46 "color2": "Seconde couleur de profil", 48 - "header": "Veuillez entrer un nom pour votre appareil, choisir une couleur et une icône utilisateur de votre choix", 47 + "header": "Veuillez entrer un nom pour votre appareil, choisir vos couleurs et une icône utilisateur de votre choix", 49 48 "icon": "Icône d'utilisateur", 50 49 "next": "Suivant", 51 50 "title": "Informations du compte" ··· 64 63 "yes": "Je fais confiance à ce serveur" 65 64 }, 66 65 "verify": { 67 - "description": "Veuillez saisir votre passphrase pour confirmer que vous l'avez enregistrée et pour créer votre compte", 66 + "description": "Veuillez saisir votre phrase d'accès pour confirmer que vous l'avez enregistrée et pour créer votre compte", 68 67 "invalidData": "Les données ne sont pas valides", 69 - "noMatch": "La passphrase ne correspond pas", 70 - "passphraseLabel": "Votre passphrase de 12 mots", 71 - "recaptchaFailed": "La validation ReCaptcha a échouée", 68 + "noMatch": "La phrase d'accès ne correspond pas", 69 + "passphraseLabel": "Votre phrase d'accès de 12 mots", 70 + "recaptchaFailed": "La validation ReCaptcha a échoué", 72 71 "register": "Créer un compte", 73 - "title": "Resaisissez votre passphrase" 72 + "title": "Ressaisissez votre phrase d'accès" 74 73 } 75 74 }, 76 75 "errors": { ··· 83 82 "footer": { 84 83 "legal": { 85 84 "disclaimer": "Avertissement", 86 - "disclaimerText": "Le site sudo-flix ne stocke pas de fichiers, mais propose des liens vers des services externes. Les problèmes juridiques doivent être traités avec les fournisseurs et les hébergeurs de fichiers. Les fichiers multimédias diffusés par les fournisseurs de vidéos ne sont pas couverts par sudo-flix." 85 + "disclaimerText": "movie-web ne stocke pas de fichiers, mais propose des liens vers des services externes. Les problèmes juridiques doivent être traités avec les fournisseurs et les hébergeurs de fichiers. Les fichiers multimédias diffusés par les fournisseurs de vidéos ne sont pas couverts par movie-web." 87 86 }, 88 87 "links": { 89 88 "discord": "Discord", ··· 116 115 }, 117 116 "search": { 118 117 "allResults": "C'est tout ce que nous avons !", 119 - "failed": "Le média n'a pas été trouvé, veuillez réessayez !", 118 + "failed": "Le média n'a pas été trouvé, veuillez réessayer !", 120 119 "loading": "Chargement...", 121 120 "noResults": "Nous n'avons rien trouvé !", 122 121 "placeholder": { 123 122 "default": "Que voulez-vous voir ?", 124 123 "extra": [ 125 124 "Que voulez-vous explorer ?", 126 - "Que y a-t-il dans votre liste de lecture?", 125 + "Qu'y a-t-il dans votre liste de lecture ?", 127 126 "Quel est votre film préféré ?", 128 127 "Quelle est votre série préférée ?" 129 128 ] ··· 156 155 "types": { 157 156 "movie": "Film", 158 157 "show": "Série" 159 - } 158 + }, 159 + "unreleased": "Non publié" 160 160 }, 161 161 "navigation": { 162 162 "banner": { 163 - "offline": "Vérifiez votre connexion internet" 163 + "offline": "Veuillez vérifier votre connexion internet" 164 164 }, 165 165 "menu": { 166 166 "about": "À propos de nous", ··· 174 174 "notFound": { 175 175 "badge": "Introuvable", 176 176 "goHome": "Retourner à l'accueil", 177 - "message": "Nous avons cherché partout : sous les poubelles, dans le placard, derrière le proxy, mais nous n'avons finalement pas trouvé la page que vous cherchez.", 177 + "message": "Nous avons cherché partout : sous les poubelles, dans le placard, derrière le proxy, mais nous n'avons pas pu trouver la page que vous cherchez.", 178 178 "title": "Impossible de trouver cette page" 179 179 }, 180 180 "onboarding": { 181 181 "defaultConfirm": { 182 182 "cancel": "Annuler", 183 183 "confirm": "Utiliser la configuration par défaut", 184 - "description": "La configuration par défaut n'offre pas les meilleurs flux et peut être insupportablement lente.", 184 + "description": "La configuration par défaut n'offre pas les meilleurs flux et peut-être insupportablement lente.", 185 185 "title": "Êtes-vous sûr ?" 186 186 }, 187 187 "extension": { 188 188 "back": "Revenir en arrière", 189 189 "explainer": "En utilisant l'extension de navigateur, vous pouvez obtenir les meilleurs flux que nous avons à offrir. Avec juste une simple installation.", 190 - "explainerIos": "Malheureusement, l'extension web n'est pas prise en charge sur iOS, appuyez sur <bold> Revenir en arrière </bold> pour choisir une autre option.", 191 - "extensionHelp": "Si vous avez installé l'extension mais qu'elle n'est pas détectée, <bold>ouvrez l'extension via le menu des extensions de votre navigateur</bold> et suivez les étapes à l'écran.", 190 + "explainerIos": "Malheureusement, l'extension web n'est pas prise en charge sur iOS, appuyez sur <bold>Retour</bold> pour choisir une autre option.", 191 + "extensionHelp": "Si vous avez installé l'extension, mais qu'elle n'est pas détectée, <bold>ouvrez l'extension via le menu des extensions de votre navigateur</bold> et suivez les étapes à l'écran.", 192 192 "linkChrome": "Installer l'extension Chrome", 193 193 "linkFirefox": "Installer l'extension Firefox", 194 - "notDetecting": "L'extension est installée sur Chrome mais le site ne la détecte pas ? Essayez de rafraîchir la page !", 194 + "notDetecting": "L'extension est installée sur Chrome, mais le site ne la détecte pas ? Essayez de rafraîchir la page !", 195 195 "notDetectingAction": "Rafraîchir la page", 196 196 "status": { 197 197 "disallowed": "L'extension n'est pas activée pour cette page", ··· 205 205 "title": "Commençons par une extension" 206 206 }, 207 207 "proxy": { 208 - "back": "Revenir en arrière", 208 + "back": "Retour", 209 209 "explainer": "Avec la méthode du proxy, vous pouvez obtenir des flux de bonne qualité en créant un proxy en libre-service.", 210 210 "input": { 211 211 "errorConnection": "Impossible de se connecter au proxy", 212 212 "errorInvalidUrl": "URL non valide", 213 - "errorNotProxy": "Je m'attendais à un proxy mais j'ai obtenu un site Web", 213 + "errorNotProxy": "Je m'attendais à un proxy, mais j'ai obtenu un site Web", 214 214 "label": "URL du proxy", 215 215 "placeholder": "https://" 216 216 }, ··· 222 222 "explainer": "Pour obtenir les meilleurs flux possibles, vous devrez choisir la méthode de streaming que vous souhaitez utiliser.", 223 223 "options": { 224 224 "default": { 225 - "text": "Je ne veux pas de flux de bonne qualité,<0 /> <1>Utiliser le flux par défaut</1>" 225 + "text": "Je ne veux pas de flux de bonne qualité,<0 /> <1>utiliser le flux par défaut</1>" 226 226 }, 227 227 "extension": { 228 228 "action": "Installer l'extension", 229 229 "description": "Installez l'extension pour navigateur et accédez aux meilleures sources.", 230 - "quality": "Meilleur qualité", 230 + "quality": "Meilleure qualité", 231 231 "title": "Extension du navigateur" 232 232 }, 233 233 "proxy": { 234 234 "action": "Configurez le proxy", 235 - "description": "Configurez un proxy en seulement 5 minutes et accédez à d'excellentes sources.", 235 + "description": "Configurez un proxy en seulement cinq minutes et accédez à d'excellentes sources.", 236 236 "quality": "Bonne qualité", 237 237 "title": "Proxy personnalisé" 238 238 } ··· 257 257 "disclaimer": "Les téléchargements sont effectués directement par le fournisseur. sudo-flix n'a aucun contrôle sur la manière dont les téléchargements sont effectués.", 258 258 "downloadSubtitle": "Télécharger les sous-titres", 259 259 "downloadVideo": "Télécharger la vidéo", 260 - "hlsDisclaimer": "Les téléchargements sont effectués directement auprès du fournisseur. sudo-flix n'a aucun contrôle sur la façon dont les téléchargements sont fournis.<br /><br />Veuillez noter que vous téléchargez une liste de lecture HLS, <bold>il n'est pas recommandé de la télécharger si vous n'êtes pas familier avec les formats de streaming avancés. </bold>. Essayez différentes sources pour différents formats.", 260 + "hlsDisclaimer": "Les téléchargements sont effectués directement auprès du fournisseur. movie-web n'a aucun contrôle sur la façon dont les téléchargements sont fournis.<br /><br />Veuillez noter que vous téléchargez une liste de lecture HLS, <bold>il n'est pas recommandé de la télécharger si vous n'êtes pas familier avec les formats de streaming avancés</bold>. Essayez différentes sources pour différents formats.", 261 261 "onAndroid": { 262 - "1": "Pour télécharger sur Android, cliquez sur le bouton de téléchargement puis, sur la nouvelle page, <bold>tapez et maintenez </bold> sur la vidéo, puis sélectionnez <bold>enregistrer</bold>.", 262 + "1": "Pour télécharger sur Android, cliquez sur le bouton de téléchargement, puis, sur la nouvelle page, <bold>tapez et maintenez</bold> sur la vidéo, et sélectionnez <bold>enregistrer</bold>.", 263 263 "shortTitle": "Télécharger / Android", 264 264 "title": "Téléchargement sur Android" 265 265 }, 266 266 "onIos": { 267 - "1": "Pour télécharger sur iOS, cliquez sur le bouton de téléchargement puis, sur la nouvelle page, cliquez sur <bold><ios_share /></bold>, puis <bold>Enregistrer dans les fichiers <ios_files /></bold>.", 267 + "1": "Pour télécharger sur iOS, cliquez sur le bouton de téléchargement, puis, sur la nouvelle page, cliquez sur <bold><ios_share /></bold>, et <bold>Enregistrer dans les fichiers <ios_files /></bold>.", 268 268 "shortTitle": "Télécharger / iOS", 269 269 "title": "Télécharger sur iOS" 270 270 }, ··· 282 282 "loadingError": "Erreur lors du chargement de la saison", 283 283 "loadingList": "Chargement...", 284 284 "loadingTitle": "Chargement...", 285 - "unairedEpisodes": "Un ou plusieurs épisodes de cette saison ont été désactivés car ils n'ont pas encore été diffusés." 285 + "unairedEpisodes": "Un ou plusieurs épisodes de cette saison ont été désactivés, car ils n'ont pas encore été diffusés." 286 286 }, 287 287 "playback": { 288 288 "speedLabel": "Vitesse de lecture", ··· 291 291 "quality": { 292 292 "automaticLabel": "Qualité automatique", 293 293 "hint": "Vous pouvez essayer de <0>changer de source</0> pour obtenir différentes options de qualité.", 294 - "iosNoQuality": "En raison des limitations définies par Apple, la sélection de la qualité n'est pas disponible sur iOS pour cette source. Vous pouvez essayer <0>de passer à une autre source</0> pour obtenir des options de qualité différentes.", 294 + "iosNoQuality": "En raison des limitations définies par Apple, la sélection de la qualité n'est pas disponible sur iOS pour cette source. Vous pouvez essayer <0>de changer de source</0> pour obtenir des options de qualité différentes.", 295 295 "title": "Qualité" 296 296 }, 297 297 "settings": { ··· 312 312 }, 313 313 "noEmbeds": { 314 314 "text": "Nous n'avons pas trouvé de liens, veuillez essayer une autre source.", 315 - "title": "Pas d'embeds trouvés" 315 + "title": "Pas d'intégrations (embeds) trouvées" 316 316 }, 317 317 "noStream": { 318 318 "text": "Cette source n'a pas de flux pour ce film ou cette série.", ··· 375 375 "errorDecode": "Bien qu'elle ait été jugée utilisable, une erreur s'est produite lors de la tentative de décodage de la ressource multimédia, ce qui a entraîné une erreur.", 376 376 "errorGenericMedia": "Une erreur de média inconnue est survenue.", 377 377 "errorNetwork": "Une erreur de réseau s'est produite qui a empêché la récupération du média, bien qu'il ait été disponible auparavant.", 378 - "errorNotSupported": "L'objet du media ou de la source du média n'est pas supporté." 378 + "errorNotSupported": "L'objet du média ou de la source du média n'est pas supporté." 379 379 }, 380 380 "homeButton": "Revenir à l'accueil", 381 381 "text": "Une erreur s'est produite lors de la lecture du média. Veuillez réessayer.", 382 382 "title": "Oups, c'est coupé !" 383 383 }, 384 384 "scraping": { 385 + "extensionFailure": { 386 + "badge": "Extension désactivée", 387 + "enableExtension": "Activer l'extension", 388 + "homeButton": "Revenir à l'accueil", 389 + "text": "Vous avez installé l'extension movie-web. Pour commencer à l'utiliser, vous devez activer l'extension pour ce site.", 390 + "title": "Veuillez activer l'extension" 391 + }, 385 392 "items": { 386 393 "failure": "Une erreur est survenue", 387 394 "notFound": "N'a pas la vidéo", ··· 410 417 }, 411 418 "screens": { 412 419 "dmca": { 413 - "text": "Bienvenue sur la page de contact DMCA de sudo-flix ! Nous respectons les droits de propriété intellectuelle et souhaitons répondre rapidement à toute question relative aux droits d'auteur. Si vous pensez que votre œuvre protégée par des droits d'auteur a été utilisée de manière inappropriée sur notre plateforme, veuillez envoyer une notification DMCA détaillée à l'adresse électronique ci-dessous. Veuillez inclure une description du matériel protégé par des droits d'auteur, vos coordonnées et une déclaration de bonne foi. Nous nous engageons à résoudre ces problèmes rapidement et vous remercions de votre coopération pour que sudo-flix reste un lieu respectueux de la créativité et des droits d'auteur.", 420 + "text": "Bienvenue sur la page de contact DMCA de movie-web ! Nous respectons les droits de propriété intellectuelle et souhaitons répondre rapidement à toute question relative aux droits d'auteur. Si vous pensez que votre œuvre protégée par des droits d'auteur a été utilisée de manière inappropriée sur notre plateforme, veuillez envoyer une notification DMCA détaillée à l'adresse électronique ci-dessous. Veuillez inclure une description du matériel protégé par des droits d'auteur, vos coordonnées et une déclaration de bonne foi. Nous nous engageons à résoudre ces problèmes rapidement et vous remercions pour votre coopération pour que movie-web reste un lieu respectueux de la créativité et des droits d'auteur.", 414 421 "title": "DMCA" 415 422 }, 416 423 "loadingApp": "Chargement de l'application", ··· 422 429 "textWithReset": "Echec du chargement de votre profil à partir de votre serveur personnalisé, souhaitez-vous revenir au serveur par défaut ?" 423 430 }, 424 431 "migration": { 425 - "failed": "La migration de vos données a échouée.", 432 + "failed": "La migration de vos données a échoué.", 426 433 "inProgress": "Veuillez patienter, nous sommes en train de migrer vos données. Cela ne devrait pas prendre longtemps." 427 434 } 428 435 }, ··· 459 466 "userIcon": "Icône de l'utilisateur" 460 467 }, 461 468 "register": { 462 - "cta": "Démarrer", 469 + "cta": "Commencer", 463 470 "text": "Partagez la progression de vos films et séries entre vos appareils et gardez-les synchronisés.", 464 471 "title": "Synchroniser au Cloud" 465 472 }, ··· 472 479 "default": "Défaut", 473 480 "gray": "Gris", 474 481 "red": "Rouge", 475 - "teal": "Saphir" 482 + "teal": "Bleu canard" 476 483 }, 477 484 "title": "Apparence" 478 485 }, ··· 496 503 }, 497 504 "redoSetup": "Refaire la configuration", 498 505 "successStatus": { 499 - "description": "Tout est réuni pour que vous puissiez commencer à regarder vos médias préférés.", 500 - "title": "Tout est mis en place !" 506 + "description": "Tout prêt pour que vous puissiez commencer à regarder vos médias préférés.", 507 + "title": "Tout est en place !" 501 508 }, 502 509 "unsetStatus": { 503 510 "description": "Pour commencer le processus de configuration, veuillez cliquer sur le bouton à droite.", ··· 515 522 } 516 523 }, 517 524 "preferences": { 518 - "language": "Language de l'application", 525 + "language": "Langage de l'application", 519 526 "languageDescription": "Langue appliquée à l’ensemble de l’application.", 520 - "thumbnail": "Générer des miniatures", 527 + "thumbnail": "Générer les miniatures", 521 528 "thumbnailDescription": "La plupart du temps, les vidéos n'ont pas de miniatures. Vous pouvez activer ce paramètre pour les générer à la volée, mais ils peuvent ralentir votre vidéo.", 522 - "thumbnailLabel": "Générer des miniatures", 529 + "thumbnailLabel": "Générer les miniatures", 523 530 "title": "Préférences" 524 531 }, 525 532 "reset": "Réinitialiser", ··· 527 534 "sidebar": { 528 535 "info": { 529 536 "appVersion": "Version de l'application", 530 - "backendUrl": "URL de Backend", 537 + "backendUrl": "URL du Backend", 531 538 "backendVersion": "Version du Backend", 532 539 "hostname": "Nom d'hôte", 533 540 "insecure": "Non sécurisé", ··· 540 547 }, 541 548 "subtitles": { 542 549 "backgroundBlurLabel": "Flou d'arrière-plan", 543 - "backgroundLabel": "Opacité du fond", 550 + "backgroundLabel": "Opacité de l'arrière-plan", 544 551 "colorLabel": "Couleur", 545 552 "previewQuote": "Plus l'obscurité est profonde, plus la lumière brille.", 546 553 "textSizeLabel": "Taille des textes",
+22 -11
src/backend/metadata/tmdb.ts
··· 143 143 }; 144 144 } 145 145 146 - const baseURL = "https://api.themoviedb.org/3"; 146 + const tmdbBaseUrl1 = "https://api.themoviedb.org/3"; 147 + const tmdbBaseUrl2 = "https://api.tmdb.org/3"; 147 148 148 149 const apiKey = conf().TMDB_READ_API_KEY; 149 150 150 - const headers = { 151 + const tmdbHeaders = { 151 152 accept: "application/json", 152 153 Authorization: `Bearer ${apiKey}`, 153 154 }; 154 155 155 156 export async function get<T>(url: string, params?: object): Promise<T> { 156 157 if (!apiKey) throw new Error("TMDB API key not set"); 157 - 158 - const res = await proxiedFetch<any>(encodeURI(url), { 159 - headers, 160 - baseURL, 161 - params: { 162 - ...params, 163 - }, 164 - }); 165 - return res; 158 + try { 159 + return await mwFetch<T>(encodeURI(url), { 160 + headers: tmdbHeaders, 161 + baseURL: tmdbBaseUrl1, 162 + params: { 163 + ...params, 164 + }, 165 + signal: AbortSignal.timeout(5000), 166 + }); 167 + } catch (err) { 168 + return mwFetch<T>(encodeURI(url), { 169 + headers: tmdbHeaders, 170 + baseURL: tmdbBaseUrl2, 171 + params: { 172 + ...params, 173 + }, 174 + signal: AbortSignal.timeout(30000), 175 + }); 176 + } 166 177 } 167 178 168 179 export async function multiSearch(
+90 -13
src/components/player/atoms/NextEpisodeButton.tsx
··· 1 1 import classNames from "classnames"; 2 - import React, { useCallback } from "react"; 2 + import { useCallback, useEffect, useRef } from "react"; 3 3 import { useTranslation } from "react-i18next"; 4 + import { useAsync } from "react-use"; 4 5 6 + import { getMetaFromId } from "@/backend/metadata/getmeta"; 7 + import { MWMediaType, MWSeasonMeta } from "@/backend/metadata/types/mw"; 5 8 import { Icon, Icons } from "@/components/Icon"; 6 9 import { usePlayerMeta } from "@/components/player/hooks/usePlayerMeta"; 7 10 import { Transition } from "@/components/utils/Transition"; 8 11 import { PlayerMeta } from "@/stores/player/slices/source"; 9 12 import { usePlayerStore } from "@/stores/player/store"; 13 + import { usePreferencesStore } from "@/stores/preferences"; 10 14 import { useProgressStore } from "@/stores/progress"; 15 + import { isAutoplayAllowed } from "@/utils/autoplay"; 16 + 17 + import { hasAired } from "../utils/aired"; 11 18 12 19 function shouldShowNextEpisodeButton( 13 20 time: number, ··· 39 46 ); 40 47 } 41 48 49 + function useSeasons(mediaId: string, isLastEpisode: boolean = false) { 50 + const state = useAsync(async () => { 51 + if (isLastEpisode) { 52 + const data = await getMetaFromId(MWMediaType.SERIES, mediaId ?? ""); 53 + if (data?.meta.type !== MWMediaType.SERIES) return null; 54 + return data.meta.seasons; 55 + } 56 + }, [mediaId, isLastEpisode]); 57 + 58 + return state; 59 + } 60 + 61 + function useNextSeasonEpisode( 62 + nextSeason: MWSeasonMeta | undefined, 63 + mediaId: string, 64 + ) { 65 + const state = useAsync(async () => { 66 + if (nextSeason) { 67 + const data = await getMetaFromId( 68 + MWMediaType.SERIES, 69 + mediaId ?? "", 70 + nextSeason?.id, 71 + ); 72 + if (data?.meta.type !== MWMediaType.SERIES) return null; 73 + 74 + const nextSeasonEpisodes = data?.meta?.seasonData?.episodes 75 + .filter((episode) => hasAired(episode.air_date)) 76 + .map((episode) => ({ 77 + number: episode.number, 78 + title: episode.title, 79 + tmdbId: episode.id, 80 + })); 81 + 82 + if (nextSeasonEpisodes.length > 0) return nextSeasonEpisodes[0]; 83 + } 84 + }, [mediaId, nextSeason?.id]); 85 + return state; 86 + } 87 + 42 88 export function NextEpisodeButton(props: { 43 89 controlsShowing: boolean; 44 90 onChange?: (meta: PlayerMeta) => void; ··· 56 102 (s) => s.setShouldStartFromBeginning, 57 103 ); 58 104 const updateItem = useProgressStore((s) => s.updateItem); 105 + const enableAutoplay = usePreferencesStore((s) => s.enableAutoplay); 106 + 107 + const isLastEpisode = 108 + meta?.episode?.number === meta?.episodes?.at(-1)?.number; 109 + 110 + const seasons = useSeasons(meta?.tmdbId ?? "", isLastEpisode); 111 + 112 + const nextSeason = seasons.value?.find( 113 + (season) => season.number === (meta?.season?.number ?? 0) + 1, 114 + ); 115 + 116 + const nextSeasonEpisode = useNextSeasonEpisode( 117 + nextSeason, 118 + meta?.tmdbId ?? "", 119 + ); 59 120 60 121 let show = false; 122 + const hasAutoplayed = useRef(false); 61 123 if (showingState === "always") show = true; 62 124 else if (showingState === "hover" && props.controlsShowing) show = true; 63 125 if (isHidden || status !== "playing" || duration === 0) show = false; ··· 69 131 ? bottom 70 132 : "bottom-[calc(3rem+env(safe-area-inset-bottom))]"; 71 133 72 - const nextEp = meta?.episodes?.find( 73 - (v) => v.number === (meta?.episode?.number ?? 0) + 1, 74 - ); 134 + const nextEp = isLastEpisode 135 + ? nextSeasonEpisode.value 136 + : meta?.episodes?.find( 137 + (v) => v.number === (meta?.episode?.number ?? 0) + 1, 138 + ); 75 139 76 140 const loadNextEpisode = useCallback(() => { 77 141 if (!meta || !nextEp) return; 78 142 const metaCopy = { ...meta }; 79 143 metaCopy.episode = nextEp; 144 + metaCopy.season = 145 + isLastEpisode && nextSeason 146 + ? { 147 + ...nextSeason, 148 + tmdbId: nextSeason.id, 149 + } 150 + : metaCopy.season; 80 151 setShouldStartFromBeginning(true); 81 152 setDirectMeta(metaCopy); 82 153 props.onChange?.(metaCopy); ··· 92 163 props, 93 164 setShouldStartFromBeginning, 94 165 updateItem, 166 + isLastEpisode, 167 + nextSeason, 95 168 ]); 96 169 97 - const startCurrentEpisodeFromBeginning = useCallback(() => { 98 - if (!meta || !meta.episode) return; 99 - const metaCopy = { ...meta }; 100 - setShouldStartFromBeginning(true); 101 - setDirectMeta(metaCopy); 102 - props.onChange?.(metaCopy); 103 - }, [setDirectMeta, meta, props, setShouldStartFromBeginning]); 170 + useEffect(() => { 171 + if (!enableAutoplay || metaType !== "show") return; 172 + const onePercent = duration / 100; 173 + const isEnding = time >= duration - onePercent && duration !== 0; 104 174 105 - // TODO: If the type is a movie add a Go home button instead! 175 + if (duration === 0) hasAutoplayed.current = false; 176 + if (isEnding && isAutoplayAllowed() && !hasAutoplayed.current) { 177 + hasAutoplayed.current = true; 178 + loadNextEpisode(); 179 + } 180 + }, [duration, enableAutoplay, loadNextEpisode, metaType, time]); 106 181 107 182 if (!meta?.episode || !nextEp) return null; 108 183 if (metaType !== "show") return null; ··· 133 208 className="bg-buttons-primary hover:bg-buttons-primaryHover text-buttons-primaryText flex justify-center items-center" 134 209 > 135 210 <Icon className="text-xl mr-1" icon={Icons.SKIP_EPISODE} /> 136 - {t("player.nextEpisode.next")} 211 + {isLastEpisode && nextEp 212 + ? t("player.nextEpisode.nextSeason") 213 + : t("player.nextEpisode.next")} 137 214 </Button> 138 215 </div> 139 216 </Transition>
+15 -1
src/hooks/useSettingsState.ts
··· 51 51 } 52 52 | undefined, 53 53 enableThumbnails: boolean, 54 + enableAutoplay: boolean, 54 55 ) { 55 56 const [proxyUrlsState, setProxyUrls, resetProxyUrls, proxyUrlsChanged] = 56 57 useDerived(proxyUrls); ··· 84 85 resetEnableThumbnails, 85 86 enableThumbnailsChanged, 86 87 ] = useDerived(enableThumbnails); 88 + const [ 89 + enableAutoplayState, 90 + setEnableAutoplayState, 91 + resetEnableAutoplay, 92 + enableAutoplayChanged, 93 + ] = useDerived(enableAutoplay); 87 94 88 95 function reset() { 89 96 resetTheme(); ··· 95 102 resetDeviceName(); 96 103 resetProfile(); 97 104 resetEnableThumbnails(); 105 + resetEnableAutoplay(); 98 106 } 99 107 100 108 const changed = ··· 105 113 backendUrlChanged || 106 114 proxyUrlsChanged || 107 115 profileChanged || 108 - enableThumbnailsChanged; 116 + enableThumbnailsChanged || 117 + enableAutoplayChanged; 109 118 110 119 return { 111 120 reset, ··· 149 158 state: enableThumbnailsState, 150 159 set: setEnableThumbnailsState, 151 160 changed: enableThumbnailsChanged, 161 + }, 162 + enableAutoplay: { 163 + state: enableAutoplayState, 164 + set: setEnableAutoplayState, 165 + changed: enableAutoplayChanged, 152 166 }, 153 167 }; 154 168 }
+11 -3
src/pages/Settings.tsx
··· 123 123 const enableThumbnails = usePreferencesStore((s) => s.enableThumbnails); 124 124 const setEnableThumbnails = usePreferencesStore((s) => s.setEnableThumbnails); 125 125 126 + const enableAutoplay = usePreferencesStore((s) => s.enableAutoplay); 127 + const setEnableAutoplay = usePreferencesStore((s) => s.setEnableAutoplay); 128 + 126 129 const account = useAuthStore((s) => s.account); 127 130 const updateProfile = useAuthStore((s) => s.setAccountProfile); 128 131 const updateDeviceName = useAuthStore((s) => s.updateDeviceName); ··· 145 148 backendUrlSetting, 146 149 account?.profile, 147 150 enableThumbnails, 151 + enableAutoplay, 148 152 ); 149 153 150 154 useEffect(() => { ··· 197 201 } 198 202 199 203 setEnableThumbnails(state.enableThumbnails.state); 204 + setEnableAutoplay(state.enableAutoplay.state); 200 205 setAppLanguage(state.appLanguage.state); 201 206 setTheme(state.theme.state); 202 207 setSubStyling(state.subtitleStyling.state); ··· 218 223 setBackendUrl(url); 219 224 } 220 225 }, [ 221 - state, 222 226 account, 223 227 backendUrl, 224 228 setEnableThumbnails, 229 + state, 230 + setEnableAutoplay, 225 231 setAppLanguage, 226 232 setTheme, 227 233 setSubStyling, 234 + setProxySet, 228 235 updateDeviceName, 229 236 updateProfile, 230 - setProxySet, 237 + logout, 231 238 setBackendUrl, 232 - logout, 233 239 ]); 234 240 return ( 235 241 <SubPageLayout> ··· 270 276 setLanguage={state.appLanguage.set} 271 277 enableThumbnails={state.enableThumbnails.state} 272 278 setEnableThumbnails={state.enableThumbnails.set} 279 + enableAutoplay={state.enableAutoplay.state} 280 + setEnableAutoplay={state.enableAutoplay.set} 273 281 /> 274 282 </div> 275 283 <div id="settings-appearance" className="mt-48">
+32
src/pages/parts/settings/PreferencesPart.tsx
··· 1 + import classNames from "classnames"; 1 2 import { useTranslation } from "react-i18next"; 2 3 3 4 import { Toggle } from "@/components/buttons/Toggle"; ··· 5 6 import { Dropdown } from "@/components/form/Dropdown"; 6 7 import { Heading1 } from "@/components/utils/Text"; 7 8 import { appLanguageOptions } from "@/setup/i18n"; 9 + import { isAutoplayAllowed } from "@/utils/autoplay"; 8 10 import { getLocaleInfo, sortLangCodes } from "@/utils/language"; 9 11 10 12 export function PreferencesPart(props: { ··· 12 14 setLanguage: (l: string) => void; 13 15 enableThumbnails: boolean; 14 16 setEnableThumbnails: (v: boolean) => void; 17 + enableAutoplay: boolean; 18 + setEnableAutoplay: (v: boolean) => void; 15 19 }) { 16 20 const { t } = useTranslation(); 17 21 const sorted = sortLangCodes(appLanguageOptions.map((item) => item.code)); 22 + 23 + const allowAutoplay = isAutoplayAllowed(); 18 24 19 25 const options = appLanguageOptions 20 26 .sort((a, b) => sorted.indexOf(a.code) - sorted.indexOf(b.code)) ··· 59 65 <Toggle enabled={props.enableThumbnails} /> 60 66 <p className="flex-1 text-white font-bold"> 61 67 {t("settings.preferences.thumbnailLabel")} 68 + </p> 69 + </div> 70 + </div> 71 + <div> 72 + <p className="text-white font-bold mb-3"> 73 + {t("settings.preferences.autoplay")} 74 + </p> 75 + <p className="max-w-[25rem] font-medium"> 76 + {t("settings.preferences.autoplayDescription")} 77 + </p> 78 + <div 79 + onClick={() => 80 + allowAutoplay 81 + ? props.setEnableAutoplay(!props.enableAutoplay) 82 + : null 83 + } 84 + className={classNames( 85 + "bg-dropdown-background hover:bg-dropdown-hoverBackground select-none my-4 cursor-pointer space-x-3 flex items-center max-w-[25rem] py-3 px-4 rounded-lg", 86 + allowAutoplay 87 + ? "cursor-pointer opacity-100 pointer-events-auto" 88 + : "cursor-not-allowed opacity-50 pointer-events-none", 89 + )} 90 + > 91 + <Toggle enabled={props.enableAutoplay && allowAutoplay} /> 92 + <p className="flex-1 text-white font-bold"> 93 + {t("settings.preferences.autoplayLabel")} 62 94 </p> 63 95 </div> 64 96 </div>
+5 -1
src/setup/config.ts
··· 25 25 ONBOARDING_CHROME_EXTENSION_INSTALL_LINK: string; 26 26 ONBOARDING_FIREFOX_EXTENSION_INSTALL_LINK: string; 27 27 ONBOARDING_PROXY_INSTALL_LINK: string; 28 + ALLOW_AUTOPLAY: boolean; 28 29 } 29 30 30 31 export interface RuntimeConfig { ··· 42 43 TURNSTILE_KEY: string | null; 43 44 CDN_REPLACEMENTS: Array<string[]>; 44 45 HAS_ONBOARDING: boolean; 46 + ALLOW_AUTOPLAY: boolean; 45 47 ONBOARDING_CHROME_EXTENSION_INSTALL_LINK: string | null; 46 48 ONBOARDING_FIREFOX_EXTENSION_INSTALL_LINK: string | null; 47 49 ONBOARDING_PROXY_INSTALL_LINK: string | null; ··· 68 70 TURNSTILE_KEY: import.meta.env.VITE_TURNSTILE_KEY, 69 71 CDN_REPLACEMENTS: import.meta.env.VITE_CDN_REPLACEMENTS, 70 72 HAS_ONBOARDING: import.meta.env.VITE_HAS_ONBOARDING, 73 + ALLOW_AUTOPLAY: import.meta.env.VITE_ALLOW_AUTOPLAY, 71 74 }; 72 75 73 76 function coerceUndefined(value: string | null | undefined): string | undefined { ··· 113 116 .map((v) => v.trim()) 114 117 .filter((v) => v.length > 0), 115 118 NORMAL_ROUTER: getKey("NORMAL_ROUTER", "false") === "true", 116 - HAS_ONBOARDING: getKey("HAS_ONBOARDING", "false") === "true", 119 + HAS_ONBOARDING: getKey("HAS_ONBOARDING", "true") === "true", 120 + ALLOW_AUTOPLAY: getKey("ALLOW_AUTOPLAY", "false") === "true", 117 121 TURNSTILE_KEY: getKey("TURNSTILE_KEY"), 118 122 DISALLOWED_IDS: getKey("DISALLOWED_IDS", "") 119 123 .split(",")
+8
src/stores/preferences/index.tsx
··· 5 5 export interface PreferencesStore { 6 6 enableThumbnails: boolean; 7 7 setEnableThumbnails(v: boolean): void; 8 + enableAutoplay: boolean; 9 + setEnableAutoplay(v: boolean): void; 8 10 } 9 11 10 12 export const usePreferencesStore = create( ··· 14 16 setEnableThumbnails(v) { 15 17 set((s) => { 16 18 s.enableThumbnails = v; 19 + }); 20 + }, 21 + enableAutoplay: false, 22 + setEnableAutoplay(v) { 23 + set((s) => { 24 + s.enableAutoplay = v; 17 25 }); 18 26 }, 19 27 })),
+11
src/utils/autoplay.ts
··· 1 + import { isExtensionActiveCached } from "@/backend/extension/messaging"; 2 + import { conf } from "@/setup/config"; 3 + import { useAuthStore } from "@/stores/auth"; 4 + 5 + export function isAutoplayAllowed() { 6 + return Boolean( 7 + conf().ALLOW_AUTOPLAY || 8 + isExtensionActiveCached() || 9 + useAuthStore.getState().proxySet, 10 + ); 11 + }