diff --git a/README.md b/README.md index 630ee72a6..9e5163f44 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,7 @@ See the [configuration documentation](https://jamcalli.github.io/Pulsarr/docs/in - **Discord Bot Integration**: Complete approval management directly from Discord with interactive commands - **Flexible Notifications**: Discord bot, Plex mobile push, webhooks, and 80+ services via Apprise - **Advanced Lifecycle Management**: Watchlist-based or tag-based deletion with playlist protection +- **Watchlist Exclusions**: Prevent specific watchlist items from being routed to Sonarr/Radarr - **Plex Session Monitoring**: Auto-search for next seasons when users near season finales - **User Tagging**: Track who requested what content in Sonarr/Radarr - **Comprehensive Analytics**: Detailed dashboards with usage stats, genre analysis, and content distribution diff --git a/docs/docs/api-documentation.md b/docs/docs/api-documentation.md index 73f5601ce..f3deefac7 100644 --- a/docs/docs/api-documentation.md +++ b/docs/docs/api-documentation.md @@ -37,6 +37,7 @@ The API is organized into the following categories: - **[Tags](./api/tags)** - User tagging configuration and synchronization - **[TMDB](./api/tmdb)** - TMDB metadata and streaming provider data - **[Users](./api/users)** - User management and watchlist access +- **[Watchlist Exclusions](./api/watchlist-exclusions)** - Watchlist exclusion management - **[Watchlist Workflow](./api/watchlist-workflow)** - Start, stop, and monitor the main workflow **Benefits:** diff --git a/docs/docs/api/bulk-manage-rolling-monitored-shows.api.mdx b/docs/docs/api/bulk-manage-rolling-monitored-shows.api.mdx index 03affddc7..2a69e9a93 100644 --- a/docs/docs/api/bulk-manage-rolling-monitored-shows.api.mdx +++ b/docs/docs/api/bulk-manage-rolling-monitored-shows.api.mdx @@ -5,7 +5,7 @@ description: "Enroll new shows into rolling monitoring and/or change the monitor sidebar_label: "Bulk manage rolling monitored shows" hide_title: true hide_table_of_contents: true -api: eJy9V9tu2zgQ/RVinnYBuXLaLIroLb08BNtgjbh7AQw/0NLYYk2RKi9xDMP/vhiSshRb2W2BonlJwsuZ4Znb0QF0i4Y7odVdBQWsvNzec8U3+KClFGpzr5Vw2mA1r/XOQgaObywUC5ijtUIrlg4ItYFlBhXa0oiW8KCAj8poKZnCHbN0nQnlNDMRmTWnm4yrKteGlTVXG2SuxuGm27fI9JpxaZBX+wkGVKxOmIy8hgwMfvVo3Ttd7aE4hH+FwQoKZzxmUGrlUDna4m0rRRmenX+x5OsBbFljw+kvMggF6NUXLB1k0BoiyQm04Vwgoj/GjeF7yEA4bOw3XNeKGzNHI9AS5afzQjncoIEM8KmU3opHvBdKNL7pHtDwp/jvzXT69urm5vVv12+vpzc3V8cswd4p67gq8QcCO+EkDtCsC7E+ZrDxovoWIrobxwxS6CmXoovKS8lXZCB6kq4o36zQxCunKC7OuRt5dedv5925ySVB9rn1OZg79zQDVMTGAlohtUuVABmshbFujtxq1S9yKePSbHh4GVy36Ab10RtaaS2Rq8sHpiI783B5PMaTttXKxjx6PZ3Sr+cF9wHX3EvHHtJJ+HFZ78sSrR17QwYNWss341nSVetgs4suvbMSa/HCpt2Kth3dO2ct+dY7MjA7MNJDLjPgVSWICS5ng4euubQY2b7+yQQ77rx9ryscJaN8vjHg1xhtRndejEoGjVCfUG1cDcXVBZu9I8lsZ6SH/H/+gn1XaxoqrbbhvZzsQf54lds4PCZ9nuepTrslrPLU1S2aRzQ0cw7gjYQCDq3RTpdaHos8P9TaumNxaLVxRzifQO+9dbph84ABGTxyI6jfBMo7GPq7q/jauRay8MvGgRYCDkW38xz/c42sg2HeYsWcZqVuGq8oEZDthKvDQIvPoLiQwzGzOmipSy7ryNIlPm0o3iDTht3NGK8qg9bSRCTcmZeWGzPAJyae47+ZTt+Mu66NY1qxXS3K+gQlLDNeqdi0j1lHe+CkyPO99mbSxrMTkXrvq1I3FyZmRjTc7Dv6vwOqGHX5gzBYugTHbkPVs18CxfSSX88sFHl+YnYc8FO3PQq2pOQrvRFuH7KPt+J33N96SuPFkmylPD4tDW7Mqe5jng3v9dMyLNK4JEdq5FVIUAo0FPDP5HZ2N4kHnrt8O7tjW9wz7l2NyqV2w7wlqXS6xhIgtdGhjy+YL7XeCuzNp5BcGE+ib7LilOqjLkQoS5Z3uKq13s6xNOi+8/kpFyfx8oUjf0focRe4d3qyQUXClmRigHjF5kEs5A+8oiRPzlnGDbJSq7XYeINVV7DCJgoDWsPJhJT7V2H4UG9/6MXmxyfetFEkJXG4uNR50zGNNj3pq743J1m16FaWF7JpSnl2LmLOtcqF+CCBdSS+1zrEIdntiv52djeaa5UufYPKRYLX2pzaxIRxNpP4xHbclbUU1rEgNU1/NFJO8p5F2qkFo7ER/q+r2K6sa3iYlSn677zcsiZ8hZx/LHSq/9zVQz+Gf/ZXR0poh08ubyUXQRGFJnRII28Bj1dwKsTB0OvlaT/2IAsfYRT2OCgWcDhQvf1p5PFIy189GmpIy36cxX7UlRGl3xb3NP4iKZOQInRc+lh8ZzqFelm8QX2wdf95djkY7bM/5p/J4/TJ1QSVAobv6Gl8BwVABjoEKbTCsHYAydXGB20CEZN+/gVwtxyx +api: eJy9V9uO2zYQ/RVinlpAjrzJFsHqbXN5WDSLGuv0Ahh+oKWxxZgiFV7Waxj+92JIytLa2jYB2vjFNi9nhodzOTyAbtFwJ7S6q6CAlZfbe674Bh+0lEJt7rUSThus5rXeWcjA8Y2FYgFztFZoxdICoTawzKBCWxrREh4U8FEZLSVTuGOWtjOhnGYmIrPmtJNxVeXasLLmaoPM1TicdPsWmV4zLg3yaj/BgIrVCZOR15CBwa8erXunqz0Uh/BXGKygcMZjBqVWDpWjKd62UpTh2PkXS74ewJY1Npx+kUEoQK++YOkgg9YQSU6gDesCEf0ybgzfQwbCYWO/YbtW3Jg5GoGWKD+tF8rhBg1k0AglGt9AMc0An0rprXjE+24wHqbhT/HvzXT69urm5vUv12+vpzc3V8csmbhT1nFV4v9kxAkncYBsXYiBYwYbL6pvIajbccwghQTFWHRXeSn5igxET9IW5ZsVmrjldLuLc05HGOj87bw7N7kkyD7mPgdz555mgIrYWEArpHYpQyCDtTDWzZFbrfpBLmUcmg0XL4PrFt0gb3pDK60lcnV5wJR8Zx4uj8e40rZa2Rhfr6dT+nqeiB9wzb107CGthP8uG3xZorVjZ8igQWv5ZjxKuiweTHa3S+esxFq8MGm3om1H585ZS771jgzMDoz0kMsMeFUJYoLL2eCgay4tRravfzDBjjtv3+sKR8kon08M+DVGm9GZF28lFIVPqDauhuLqgs3ekWS2M9JD/jt/wb6rNTWbVttwXk72IH+8ym1sKpM+zvOUp90QVnmq9hbNIxrqRQfwRkIBh9Zop0stj0WeH2pt3bE4tNq4I5x3pvfeOt2wecCADB65EVRvAuUdDP3uMr52roUsfNnY6MKFQ9HNPMf/XCPrYJi3WDGnWambxisKBGQ74erQ6OIx6F7I4RhZHbTUJZd1ZOkSnyYUb5Bpw+5mjFeVQWupUxLuzEvLjRngExPP8d9Mp2/GXdfGMa3YrhZlfYISlhmvVCzax6yjPXBS5PleezNp49qJSLX3VambCxMzIxpu9h393wFVjLr8QRgsXYJjtyHr2U+BYjrJz2cWijw/MTsO+KmbHgVbUvCV3gi3D9HHW/Er7m89hfFiSbZSHJ+GBjvmlPcxzob7+m4ZBqldkiM18ioEKF00FPDX5HZ2N4kLnrt8O7tjW9wz7l2NyqVyw7wlCXXaxhIgldGhjy+YL7XeCuzNpyu5MJ7E4GTFKdRHXYhQlizvcFVrvZ1jadB95/FTLE7i5gtH/ozQ4y5w7/Rkg4oEL8nHAPGKzYNYyB94RUGenLOMG2SlVmux8QarLmGFTRQGtIaTCSn3r0Lzodr+0IvQj0+8aaNISqJxcan/pmN6bXrSV31tTrJq0Y0sL2TTlOLsXMSca5UL8UEC60h8r3W4h2S3S/rb2d1orFW69A0qFwlea3MqExPG2UziE9txV9ZSWMeC7DT90kg5yX4WaacSjMZG+D+uYrmyruGhV6bbf+flljXhdXL+iOheA+euHvo2/KNfIymgHT65vJVcBEUUitAhtbwFPF7BKREHTa+Xp33bgyw8zujaY6NYwOFA+fa7kccjDX/1aKggLft2FutRl0YUflvcU/uLpExCiNBy6WPynekUqmVxB9XB1v3j2uWgtc9+m38mj9NTrAkqBQzf0dH4DgqADHS4pFAKw9gBJFcbH7QJREz6/A0OQSRd sidebar_class_name: "post api-method" info_path: docs/api/pulsarr-api custom_edit_url: null @@ -54,7 +54,7 @@ Enroll new shows into rolling monitoring and/or change the monitoring type of al diff --git a/docs/docs/api/configure-plex-notifications.api.mdx b/docs/docs/api/configure-plex-notifications.api.mdx index 4cf2d8f31..0623be071 100644 --- a/docs/docs/api/configure-plex-notifications.api.mdx +++ b/docs/docs/api/configure-plex-notifications.api.mdx @@ -5,7 +5,7 @@ description: "Configure Plex webhook notifications for Radarr and Sonarr instanc sidebar_label: "Configure Plex notifications" hide_title: true hide_table_of_contents: true -api: eJztV1lv4zYQ/ivEPLWAHDnHYhG9pbsFGnTbGnF6AIYfaGlscSORWh6xDUP/vRiSsuRjm0276EvrF0uamW9Ozgx3oBrU3Aol7wvIIFdyKVZO46TCzc/KiqXIPdVAApavDGQzIBrMEyjQ5Fo0RIYM3nWijOhsjYtSqScmhyBsqTR74AXXmnFZsKmS9CiksVzmSEo0fnJo7Heq2EK2869CYwGZ1Q4TMtCitETiTVNF4PSjIRt2YPISa05PdtsgZKAWHzG3kECjyVMr0BC1qXDzqJ5QDliN1UKuIIFayA8oV7aE7LJNPO8PytgvZJ0o7VkLXHJXWciur27G46QTFdLiCjUkgJu8ckY8409CitrVnYs134TX2/H47eXt7dWbm7c349tbUuAMTk11AL/klcE9/EKpCrmEtk0GsZsNHB44NG/bwGcaJU2IzNV4HOCHuX0flLGHyAlfLRHG5TkaM2Dce5BAjcbwFZ4GPhjtKmte1qB9uQ34uNZ8CwkIi/UXyItiwCNdvUBN+iWvzxv2Nz06ypcoIOroEXvxeQK8KARFnFeTgbm+GAjL+JP13/L6CCwmfh+Ll4M2lD5V39fci4a0Cdz8y+fIcuvMO1Xg2bzlh4Q+b6i10mcpn03aUdc7jltvSFTbKXlFItsE3vwfv38QP6/flopmekOtPoGGkz5Iny9TGgDpftSP5NGcN6ifUdOo34HTFWSwa7SyKldVm6XprlTGttmuUdq2cLIGOGNVzaYeAxJ45lrwRRXHboShZ5Q042ZQWttA4v9M2CriYOsoh/iPJbIOhjmDBbOK5aqunSQXkK2FLZktkQU3KBVlnN49dKVyXpUhMKf4RKAuxJRm9xPGi0KjMUwtPe7EVYa2lh6/OR75cD0eX583XWnLlGTrUuTlHkoYpp2UoSW2SRd2H5MsTbfK6VETeEfdsnSRq/pExUSLmuttF/5XQGVnTX4vNOY2wrE73xLZNz7E5Mm3RxqyNN1H9jzgh458FmxOxZc7LezWVx9vxI+4vXNUubM56TJojFBy/2kgMaWjHupsKNdPQP+RRiAZUiIvfIGGkQZ/jO4m96PAcGjy3eSePeGWcWdLlDYeFOaMkCu2F2MRkGbh0MbPqM+VehLYq48pOVE+DVijBadSP2tCgDKkOS7dU8w12le6H2txFIRPDPk97vNnTeDOqtEKJV0lsGDGQ1zE9T6NC380zjCuke27T9EdWGFiCD1azUlFVW0v/JJA7fyhvxl8v+F1U+HRJt832H5lP/wWdvO4kHe7dGiaFJel8vESlqChO5x3k/uzNVGo3NUobQgE3Ww6iRHj8RLEbV5Wwljm137ds8abD12CHro9hZpugP/tMrQVY2vux1jM0tEd67hxH9i460fj17qbxUqyuLFpU3Hhlzt/+ndxvMzg+TLGOszmsyNm3rXkGex2VNm/6qpt6fMnh5qO/rwfHOHkdwVLXeEJt8En8m70SEYRe+VCmR8tAdQ1ggR1nMb+Je98MDcnv0wfIYFFvInWfgUAzde0CfI1ZAAJqCa45C8aa/qruFw5P/ghYNLvT3tQX+k= +api: eJztV1lv4zYQ/ivEPLWAHDnHYhG9pbsFGnTbGnF6AIYfaGlscSORWh6xDUP/vRiSsuRjm0276EvrF0vizDcn59iBalBzK5S8LyCDXMmlWDmNkwo3PysrliL3pwYSsHxlIJsBncE8gQJNrkVDx5DBu46V0Tlb46JU6onJIQhbKs0eeMG1ZlwWbKokPQppLJc5khCNnxwa+50qtpDt/KvQWEBmtcOEFLQoLR3xpqkicPrRkA47MHmJNacnu20QMlCLj5hbSKDRZKkVaOi0qXDzqJ5QDkiN1UKuIIFayA8oV7aE7LJNPO0PytgvJJ0o7UkLXHJXWciur27G46RjFdLiCnXgFbWrIRsngJu8ckY840/dx2BuzTfh9XY8fnt5e3v15ubtzfj2loQ5g1NTHYha8srgXtRCqQq5hLZNBn6cDYwfGDdv20BnGiVN8NLVeBzgh3F+H4Sxh0gJXy0oxuU5GjMg3FuQQI3G8BWeBiEo7SprXpagfeoN6LjWfAsJCIv1F/CLYkAjXb1ATfIlr88r9jctOoqXKCDK6BF79nkCvCgEeZxXk4G6PhkIy/hb9t+y+ggsBn7vi5edNuQ+Fd/n3IuKtAnc/Mv3yHLrzDtV4Nm45YcHfdxQa6XPnnw2aEcV8NhvvSJRbCfkFYFsE3jzv//+gf+8fFsq6u8NlfoEGk7yIH2+TKkBpPu2P5JHPd+gfkZNbX8HTleQwa7RyqpcVW2WprtSGdtmu0Zp28LJSOCMVTWbegxI4JlrwRdVbMERhp5RUo+bQWltA4n/M2HCiI2tOznEfyyRdTDMGSyYVSxXde0kmYBsLWzJbIksmEGhKGMn76ErlfOqDI45xacDqkJMaXY/YbwoNBrD1NLjTlxlaILp8Zvj9g/X4/H1edWVtkxJti5FXu6hhGHaSRlKYpt0bvc+ydJ0q5weNYF21A1OF7mqT0RMtKi53nbufwVUdlbl90JjbiMcu/MlkX3jXUyWfHskIUvTvWfPA37ojs+CzSn5cqeF3frs4434Ebd3jjJ3NidZBo0RSu4/DTimdNVDng35+g7oP1ILJEVK5IVP0NDS4I/R3eR+FAgOVb6b3LMn3DLubInSxovCnBFyxfZsLAJSLxzq+BnxuVJPAnvxMSQnwqcBa7TglOpnVQhQhiTHAXyKuUb7SvNjLo4C84kiv8fZ/qwK3Fk1WqGktQILZjzERRz10zj8R+UM4xrZvvoU3YUVJrrQo9WcRFTV9sIPCVTOH/ot4fsNr5sKj6b6vsD24/vhtzCnx+G8m6VD0SS/LJX3l7AEDd3lvJvcn82JQuWuRmmDI2jL6ThGjMeFiNu8rISxzK8AuieNWxAtRA/dnEJFN8D/dhnKirE1920sRulo3zou3Ac67vrW+LX2tJhJFjc2bSou/HDnb/8utpcZPF9GX4fefLbFzLuSPIPdjjL7V121LX3+5FDT1Z/3jSPc/C5hqSo84TbYRNaNHkkpIq9cSPOjIYCqRuCgitPYv6SdD/rm5JfpIySwiFtp7UcA0HxNkyBfQwaQgGqCSX7RWNNfxeXK+cYPAZN+fwJGzWO/ sidebar_class_name: "post api-method" info_path: docs/api/pulsarr-api custom_edit_url: null @@ -54,7 +54,7 @@ Configure Plex webhook notifications for Radarr and Sonarr instances diff --git a/docs/docs/api/create-radarr-instance.api.mdx b/docs/docs/api/create-radarr-instance.api.mdx index b6fc79bac..4be95b275 100644 --- a/docs/docs/api/create-radarr-instance.api.mdx +++ b/docs/docs/api/create-radarr-instance.api.mdx @@ -5,7 +5,7 @@ description: "Create a new Radarr instance configuration" sidebar_label: "Create Radarr instance" hide_title: true hide_table_of_contents: true -api: eJztV1mP2zYQ/ivEPLWAHNs5EKze3E0LGE2xRjY9AMMPtDS2mKVIhYd3DUP/PRiSsuQjaYIWeSi6LyuTnG8ODme+OYBu0HAntJqXkENhkDt8x0tuzFxZx1WBkIHjWwv5EuIGrDIo0RZGNCQIOdwGMcaZwkcWDzGRxFmh1UZsfdQCGRj86NG6n3S5h/wQfgqDJeTOeMyg0MqhcrTFm0aKIsiNP1jSdABbVFhz+nL7BiEHvf6AhYMMGkO+OIGWdhWvcXDKOiPUFjKohXqLausqyKdtBmtu8Xcjr53caFNzBzl4I6DNgDfiV9x/BeZHz6Vw+4XRGyGDEcpLydf0HX3kan+3gXx5DtVmxxXl6zUaaFdtBkZr94uWJZprYOcQsN433Nr5VukQ1gOUuOFeOsg3XNpeYq21RK5IxCI3RXWnZuWpwImGwflaKFH7erbjQvK1IHdP5MCgRG6xhHP7MkDla0omrpT2qghnhLoVCmtuQ34kUXK91ko4bU7Ba70TeKfk/gvowzPhe6bKWy0lFikNlVYYVMTkHuAvV0dYbgwnBOGwtpdX37YZCPumE/yqSO/J5e5tDTE/p6vLBFLWP5ZlzPA+g48JumrTUdtoZaOK55NpNG/4apPd7F06Cf/a4xPl4IxQDrdo6G6eCumt2OFvMX26/Kr5U/x5M5m8nt7cPH/18vXLyc3N9NxlUVLt4WUpyCwuFwOtIeLR9ZeTyff01jruvL3VJV65NdJSXqlEbQZoTMzsi50areXbv69fZ+EZGJLUdkp6yK+L33fNlv9c/F79n3//IH5Bv6s08ZFG2+AvJ30w3k3HJrCLsTgWUGpeZofGhn7qqZfDoTHa6ULLNh+PD5W2rs0PjTauhQvq4q3TNbsPGJDBjhtB3TVEtoOh766xVM41kIV/NjKhri2lnVP89xWyDoZ5iyVzmhW6rr2i+0b2KFzFXIUsukHhJ4NPO57UBZdVDMYlPm1QN2DasPmC8bI0aC3Tm4C78NISIevxKRKn+C8mkxfXTdfGMa3YYyWK6gglLDNeqdgCibTEsIeY5OPxXnszauLZUXdTzwpdX6hYGFFzs+/C/w1Q+VWT3wiDhUtwbFYUFIcfQojJkx/PNOTj8TGy1wHfdttXwVaUfIU3gf4sD6kDzzxl63JFuixaK7Q6Lg0k7ul5xzwbyvV8ILZzIkdkLvIyJGgktvDXaLaYj+KBU5Nnizl7wD3j3lWoXKoqzFuhtuwoxhJgIH8DGz+jvtD6QWCvPl3JhfL7iDUiVlJeNyFCWdL8iOtK64d7LAy6b3Q/5eIoCl8Y8meEvm4C906Ptqho9MGS2QDxjN1rRbUlDTDJOMu46YcYLLsHK2wKYUCrOamQcv8skEIq4e/6OefnJ143aRSI9h/r6XEA6Ze6QaNfOZ8o+p3haDAAPZ0AEhk9IfmJeV3j8UPyfuTfp6Q7zYNJ4eqEBXfazonuckJsW6iNDrcsXPCkKymzxfxqJpe68DUqF69vo82xCI0YZwuJT+yRu6KSwjoWqKbpj8YLZVyVaSqlAo/GRvg/prEYWldzNbibNM2ezbHn1h369v1t82/Kb4dPbtxILsJY4OMMGhvdEnZTutvO5L7ZrbrmsITDIaVO29LyR49mH2eXroXFGtQ9HapPDyGtbqPdo/dkCB2XPj64MwpC9StKUO1r3BfPrgZde3F3/56yME34dSAgYPhjcOoRcoAMdIhjKH9h7QCSq60PtAMiJv19AkASyZY= +api: eJztV0uP2zYQ/ivEnFpAju08EKxu7qYFjKZYI5s+AMMHWhpbzFKkwod3DUP/PRiSsuRH0gQtcii6l5XJmW+Gw+HMNwfQDRruhFbzEnIoDHKH73jJjZkr67gqEDJwfGshX0LcgFUGJdrCiIYUIYfboMY4U/jIohATSZ0VWm3E1kcrkIHBjx6t+0mXe8gP4acwWELujMcMCq0cKkdbvGmkKILe+IMlSwewRYU1py+3bxBy0OsPWDjIoDF0FifQ0q7iNQ6krDNCbSGDWqi3qLaugnzaZrDmFn838prkRpuaO8jBGwFtBrwRv+L+KzA/ei6F2y+M3ggZnFBeSr6m73hGrvZ3G8iX51BtdlxRvl6jgXbVZmC0dr9oWaK5BnYOAet9w62db5UOYT1AiRvupYN8w6XtNdZaS+SKVCxyU1R3alaeKpxYGMjXQona17MdF5KvBR33RA8MSuQWSzj3LwNUvqZk4kppr4ogI9StUFhzG/IjqdLRa62E0+YUvNY7gXdK7r+APpQJ3zNV3mopsUhpqLTCYCIm9wB/uTrCcmM4IQiHtb28+rbNQNg3neJXRXpPR+7e1hDzc7a6TCBj/WNZxgzvM/iYoKs2idpGKxtNPJ9Mo3vDV5v8Zu+SJPxrj0+UAxmhHG7RwDFrIJ9kgE+F9Fbs8LduMeZazZ/iz5vJ5PX05ub5q5evX05ubqbnxxcl1SFeloJc5HIx8CBEP4bh5WTyPU9uHXfe3uoSr9wgWSmvVKU2AzQmZvnFTo3W8u3f17Kz8AwcSWY7Iz3k18Xvu2bOfy5+r/7Pv38Qv2DfVZq4SaNtOC8nezDeTccmMI2xOBZTamRmh8aG3uqpr8OhMdrpQss2H48PlbauzQ+NNq6FCxrjrdM1uw8YkMGOG0GdNkS2g6HvrslUzjWQhX82sqKuRaWdU/z3FbIOhnmLJXOaFbquvaL7RvYoXMVchSweg8JPDp92P6kLLqsYjEt82qDOwLRh8wXjZWnQWqY3AXfhpSVy1uNTJE7xX0wmL667ro1jWrHHShTVEUpYZrxSsR0SgYlhDzHJx+O99mbURNlRd1PPCl1fmFgYUXOz78L/DVD5VZffCIOFS3BsVhQUhx9CiOkkP55ZyMfjY2SvA77ttq+CrSj5Cm8CFVoeUjeeecrW5YpsWbRWaHVcGmjc0/OOeTbU67lBbO1ElMhd5GVI0Ehy4a/RbDEfRYFTl2eLOXvAPePeVahcqirMW6G27KjGEmAgggMfP2O+0PpBYG8+XcmF8fuINSKGUl53IUJZsvyI60rrh3ssDLpvPH7KxVFUvnDkzwh93QXunR5tUdEYhCWzAeIZu9eKaksaZpJzlnHTDzRYdg9W2BTCgFZzMiHl/lkgiFTC3/Uzz89PvG7SWBD9P9bT4zDSL3VDR79yPl30O8MxYQB6Og0kYnpC+BPzusbph0T+yMVPCXiaDZPB1Qkj7qydk97lhJi3UBsdblm4cJKupMwW86uZXOrC16hcvL6NNsciNGKcLSQ+sUfuikoK61ignaYXjRfKuCrThEoFHo2N8H9MYzG0ruZqcDdpsj2bac+9O/Tt+9tm4ZTfDp/cuJFchBHBx3k0Nrol7KZ0t53LfbNbdc1hCYdDSp22peWPHs0+zjFdC4s1qHs6VJ8eQlrdRr9H78kREpc+PrgzCkL1K2pQ7WvcF2VXg669uLt/T1mYpv06EBAw/DEc6hFygAx0iGMof2HtAJKrrQ+0AyIm/X0CpSrNbA== sidebar_class_name: "post api-method" info_path: docs/api/pulsarr-api custom_edit_url: null @@ -62,7 +62,7 @@ Create a new Radarr instance configuration diff --git a/docs/docs/api/create-radarr-tag.api.mdx b/docs/docs/api/create-radarr-tag.api.mdx index 6978d89f5..679176899 100644 --- a/docs/docs/api/create-radarr-tag.api.mdx +++ b/docs/docs/api/create-radarr-tag.api.mdx @@ -5,7 +5,7 @@ description: "Create a new tag in a Radarr instance" sidebar_label: "Create Radarr tag" hide_title: true hide_table_of_contents: true -api: eJztV0uP2zYQ/ivEoIcWldZ2skFg3bZJD4umqBFvH4DhArQ0tpiVSIUcrtcR9N+LISU/1m6RRdqe4osskvPNzMd5qQXToJWkjL4tIIPcoiR8Lwtp7Z3cQAIkNw6yBcQ1WCZQoMutalgGMngTJIQUGreC5EYoLaSIp4XSjqTOERKw+NGjox9MsYOsDa/KYgEZWY8J5EYTauIt2TSVyoNNow+OlbTg8hJryf9o1yBkYFYfMCdIoLHsASl0vDsoZGf2Z5Um3KCFBPAxr7xTD/iz0qr29aC9lo/xdToev55Mpy9eXb++Hk+nky6BSq6wOkJzZJVmZmql36HeUAnZJIFGEqFlRv5cyPTTOJ2my++/ga5LjnxdHBs4QC8TkEWh2F9ZzY7cWcvKYddFCNcY7aKTL8Zjfpzew1tcS1+ReN+fhH+P0/+cyzOSnkfO9f/LhyNJ3r0xBR6d1b5eoYWOtZxsDC4mgNYae3GnRufk5oLUaZA9penIkF7toOQA+Xn8XX/l7wv4e/U1/r6Av6CfSsPtpzEu+CtZH4weJiMbGsko9qWUQktyaB/QcldqwdsKMmgba8jkpuqy0agtjaMuaxtjqYOzfuUdmVrMAwYk8CCtkqsqUjvA8H/UXMQWUBI1kISHi+0vXCxkw84p/l2JYoAR3mEhyIjc1LXXfOEotopKQSWK6AbzzwbHCBqgK5PLqoxsnOPzhpY1CmPF7UzIorDonDDrgDvzlePme8BnJk7xX47HLy+bbiwJo8W2VHm5h1JOWK91rNVdMtAeOMlGo53xNm3i2XTocFe5qc9UzKyqpd0N9D8DKrto8ltlMaceTtzkOfPwbaCYPfnuiYZsNNozexnw3bB9EWzJwZd7q2gXok826ifc3XgO18WSdTl0Thm9XzqSmHN+xzg7ltvnXFyEBBQbUqIsQoDyRUMGf6Q3s9s0Hjg1+WZ2K+5xJ6SnEjX1ZUV4p/RG7MVED9glpzb+jfrcmHuFB/X9lZwpn0esdCU51C+aEKEca97iqjTmfo65RXqm+30splH4zJDfI/RlE6Qnk25Q86iLhXAB4krMjebi0g+rvXFOSIsiN3qtNt5iMSSscj2FAa2WrKKqdldheuEa/v4w4f74KOumwqcT6Xg/AR0VX6XXJtCgiCVgyLmb2e3Fqy5M7mvUFP1bG7vP0lRIMavwUWwl5WWlHIkwrtnD0eixkLroR3SugGhdhP9tEquFo1qGltST38/4PU+xCJ8Y1h5622d/EPTXTvhIo6aSSrPukKpt3wAW8DDhL4fB0KMmsByK5gLalmPvV1t1HS9/9Gg5OZeH0h5zcwgpztt73LGl0eb0ji3h45WPgfikN3NeRwmuCQ3949nlUTub/TK/gwRW/TdPHTozWLkNXm0hA0jABA5DWQhrLVRSb3zoxxAx+fcXQgy75A== +api: eJztV9uO2zYQ/RVi0IcWldZ2skFgvW2TPiyaoka8vQCGC9DS2GJWIhVyuF5H0L8XQ0q+rN0iixR5il9k8XLmzOFcqBZMg1aSMvq2gAxyi5LwvSyktXdyAwmQ3DjIFhDHYJlAgS63quE9kMGbsENIoXErSG6E0kKKuFoo7UjqHCEBix89OvrJFDvI2vCqLBaQkfWYQG40oSaekk1TqTxwGn1wbKQFl5dYS/5HuwYhA7P6gDlBAo1lD0ih49nBIDuzX6s04QYtJFArrWpfQzZOAB/zyjv1gL8Og5FJLR/j63Q8fj2ZTl+8un59PZ5OJ10ClVxhdYTsyCq9icDvUG+ohGySQCOJ0LI6fy9k+mmcTtPlj99B1yVHfi+OyQ7QywRkUSj2XVazI9fWsnLYdRHCNUa76PCL8Zgfp2fyFtfSVyTe9yvh/9P3q+p6JtjzhLr+uto4kuTdG1Pg0Vrt6xVa6NjKycTgYgJorbEXZ2p0Tm4u7DoNuKcyHRHpzQ5GDpCfp9/1N/2+QL9X3+LvC/QL9qk03JYa44K/ku3B6GEysqHBjGK/Sim0Kof2AS13qxa8rSCDtrGGTG6qLhuN2tI46rK2MZY6OOtj3pGpxTxgQAIP0iq5qqK0Awz/R81FbAElUQNJeLjYFsPBQjbMnOLflSgGGOEdFoKMyE1de80HjmKrqBRUoohusP5MOEbQAF2ZXFZlVOMcnye0rFEYK25nQhaFReeEWQfcma8cN+UDPitxiv9yPH55mbqxJIwW21Ll5R5KOWG91rFWd8kge9AkG412xtu0iWvTodtd5aY+MzGzqpZ2N8j/DKjsIuW3ymJOPZy4yXPW4fsgMXvywxML2Wi0V/Yy4Lth+iLYkoMv91bRLkSfbNQvuLvxHK6LJdty6Jwyej90tGPO+R3j7HjfPufiICSgmEiJsggBygcNGfyV3sxu07jglPLN7Fbc405ITyVq6suK8E7pjdhvEz1gl5xy/BfzuTH3Cg/m+yM5Mz6PWOlKcqhfpBChHFve4qo05n6OuUV6pvt9LKZx8xmRPyP0ZQrSk0k3qPkKjIVwAeJKzI3m4tJfYntyTkiLIjd6rTbeYjEkrHK9hAGtlmyiqnZX4fbCNfz94eb786Osmwqf3lTH+xvQUfFVem2CDIp4Bww5dzO7vXjUhcl9jZqif2tj91maCilmFT6KraS8rJQjEa5u9rA0eiykLvqrO1dAtC7C/zGJ1cJRLUNL6sXv7/69TrEInxBrD73tsz8U+mMnfKRRU0ml2XZI1bZvAAt4mPAXxUD0qAksh6K5gLbl2PvdVl3Hwx89Wk7O5aG0x9wcQorz9h53zDRyTu+YCS+vfAzEJ72Z8zru4JrQ0H+uXR61s9lv8ztIYNV/C9WhM4OV2+DVFjKABEzQMJSFMNZCJfXGh34MEZN//wB8TMOQ sidebar_class_name: "post api-method" info_path: docs/api/pulsarr-api custom_edit_url: null @@ -54,7 +54,7 @@ Create a new tag in a Radarr instance @@ -62,7 +62,7 @@ Create a new tag in a Radarr instance diff --git a/docs/docs/api/create-schedule.api.mdx b/docs/docs/api/create-schedule.api.mdx index 1bdeac744..59b078642 100644 --- a/docs/docs/api/create-schedule.api.mdx +++ b/docs/docs/api/create-schedule.api.mdx @@ -5,7 +5,7 @@ description: "Create a new job schedule or update an existing one" sidebar_label: "Create job schedule" hide_title: true hide_table_of_contents: true -api: eJy9V0tv4zYQ/ivEnFpAXjsbJC10S3d7MLrFGnH6AAwfaGlsMaFILR9+QNB/XwwpyYrtBEiKrS+WxOE33wznxRp0hYY7odU0hxQyg9zhPCsw9xIhAcc3FtIFdJ8MLBPI0WZGVLQLUvgU9jDOFO7Yo14x28oybZiv8rCoGO6FdUJtmFYEbPCbR+t+0/kB0jq8CoM5pM54TCDTyqFytMSrSooscBw/WlJZA6koOT1phV/XkC5qcIcKIQW9esTMQQKVIducQEtycbWXss4ItYEEUPmSDBTKodlyCcsmAcXLi8KlUF9QbVwB6VUTSK7FZiD4gu6cHwYcgqoNGlK+z6S3Yot/CiVKIhKtL/k+vl7f3jQJFNqbdwL8+svtpAnEvcN3Ytx8vLmdEIrFTKv8vaZc3VzfTgKO8WpalpgL7lAeBnArrSVyBU3T0MnwlaSIqCHHNffSdZhn0skgfhZxuT3E/pCWTfJfQyQzWv2I8MB9ZdBaEUP7VdATSwc7lz/AY8vkhHnTxJ220spG8h8nk6hvWBI+R+3svpWENyT0676yPsvQ2ksxk0CJ1vLNhaM5tbdDOW5ZJsDzXBAnLmcDlWsuLUa7b/5nUx133n7S+dAg5csVGgjRlV+yNAE0RpuLKy/65/UoGxBp1XZK3uK/oN8VmtpMpW2wl5M+GG+vxl3PMP0TnY5Fs0UqfYsavJGQQl0Z7XSmZZOOx3WhrWvSutLGNXDWl7x1umTzgAEJbLkRlB/BuR1MyL82vwvnKkjCn41trs2hbuU5/kOBrINh3mLOnGaZLkuv6MiR7YQrmCuQRTMgVHLrnqUnSJ1xWUR/nOPTAmUltdLpjPE8p3xneh1wZ15abswAnzzxHP96Mrm+TF0bx7Riu0JkRQ8lLDNeqZg0VDKj24NP0vH4oL0ZVVF2JJR1XGX4IdPlmYqZESU3h879b4BKL1L+LAxmroVjdyF/2U/BxWTJzyca0vG49+xlwC/d8kWwZeh23gh3CNHHK/EHHu48Bewi9BIbC2//abCDhqUyxtlwX5918SMkIIhIgTwPARrbCvw7uptNR1HgOeW72ZQ94YFx7wpUri0szFsaq/ptrAUMDXvA8QX1mdZPAo/q2yM5Uz6PWKMVp1C/SCFCWdK8w1Wh9dMcM4Pujea3sTiKm8+I/BOhL1Pg3unRBhUNtZgzGyA+sLlW3JjxPc8pyFtylnGDLHY7bzDvElbY1oUBreSkQsrDh9BGqIrfH2fX3/e8rOSwpPaDZG9RX2SPA0GcCCf9aDcZjGiTwaA1OR+WqKEPe337LtRaBxbCER3oEvpuNr0YR7nOfInKReettelLwIhxNpO4ZzvuskIK61gY88xRNLqTcZWz6FIqr2jiCAN/X8VSZF3JQ8dr/dBeFIZXhFNq9bF5vvNe0Z6Cw70bV5KLMBiEulC3/WYB2yvqLf2N5vgcyn6s0Quoawr1v4xsGvr8zaOhWrA8dpJYCroIpjLxhAdiHm0YPcSxasulj3F/MgxQGYk7qARV7lXZ5aB/zr7OHyCBVXt5KsMoAIbv6GLFd5ACJKCDT0MVCt9qkFxtfBgAIGLS7zu33N4+ +api: eJy9V0tv4zYQ/ivEnFpAXisbJC10S3d7MLrFGnH6AAwfaGlsMZFILR9+QNB/L4aUZMVWAqTNNpdYJOebB2e+GdagKtTcCiVnGSSQauQWF2mOmSsQIrB8ayBZQrekYRVBhibVoiIpSOCTl2GcSdyzR7Vmpj3LlGauyvymZHgQxgq5ZUoSsMZvDo39RWVHSGr/KTRmkFjtMIJUSYvS0havqkKk3sbpoyGVNZCKktMvJfHrBpJlDfZYISSg1o+YWoig0uSbFWjoXNjtTxmrhdxCBChdSQ4KaVHveAGrJgLJy9HDpZBfUG5tDslV443ciO3g4Au6M34c2OBVbVEHPFGS/jgCPKSFM2KHv3eLIRIlP4TP69ubJoJcOf0OYD//dBs3XsZZfAe8m483tzEhGkyVzN7D3aub69vYY2onZ2WJmeAWi+MAeq1UgVxC0zR0k3xdUAbVkOGGu8J2mBeno0G+LcN2e+n9pa6a6L+mVKqV/B7phIdKozEilMKroGeeDiRX3yFiq+jM8qYJkqZS0gTjP8Zx0DekkM9BO7tvT8IbCOD1WBmXpmjMWM5EUKIxfDtyNef+dignkVUEPMsE2cSL+UDlhhcGg983/7OrlltnPqls6JB05Ro1+OzKxjyNALVWenTnxfi8nmUDQ1q1nZK3xM/rt7mitlQp4/3lpA+mu6tp12N0/4tux6DeIdHjsganC0igrrSyKlVFk0ynda6MbZK6Uto2cNHHnLGqZAuPARHsuBZUHz64HYyvv7a+c2sriPw/E9piW0PdznP8hxxZB8OcwYxZxVJVlk7SlSPbC5szmyMLboBne2OflScUKuVFHuJxiU8bVJXUemdzxrOM6p2pjcedu8JwrQf4FInn+NdxfD1uutKWKcn2uUjzHkoYpp2UoWiIMkPYfUyS6fSonJ5U4exESGO5TPFDqsoLFXMtSq6PXfjfAJWMmvxZaExtC8fufP2yH3yIyZMfzzQk02kf2XHAL932KNjKdz6nhT367OOV+A2Pd44Sdul7iQnE2y8NJGi4KkOeDeX6qguLEIEgQ3LkmU/Q0Fbg78ndfDYJB56bfDefsSc8Mu5sjtK2xMKcoTGsF2MtoG/eAxtfUJ8q9STwpL69kgvli4A1WXNK9VETApQhzXtc50o9LTDVaN/ofpuLkyB8YchfAXrcBO6smmxR0hCMGTMe4gNbKMm1nt7zjJK8Nc4wrpGFbuc0Zl3BCtOG0KOVnFQUxfGDbyPE4venWffXAy+rYkip/eDZe9ST7GkgCBNk3I9/8WB0iwdDV3w5LFFDH/b69lvIjfJWCEvmQFfQd/PZaB5lKnUlShuCt1G6p4AJ42xe4IHtuU3zQhjL/MinT0dDOBmXGQshJXpFHUYY+PMqUJGxJfcdr41D+7AYPinOTatPzfNfvkPaW7B4sNOq4MIPBp4X6rbfLGF3Rb2lfwGdfnvaDxy9hLqmVP9DF01Dy98cauKC1amTBCroMpho4gmPZHnwYfIQxqodL1zI+7NhgGgkSBAFVfbVs6tB/5x/XTxABOv2sVX6UQA039NDjO8hAYhA+Zh6FvJrNRRcbp0fACBg0t8/yCftlg== sidebar_class_name: "post api-method" info_path: docs/api/pulsarr-api custom_edit_url: null @@ -54,7 +54,7 @@ Create a new job schedule or update an existing one diff --git a/docs/docs/api/create-sonarr-instance.api.mdx b/docs/docs/api/create-sonarr-instance.api.mdx index 23317eeff..32eb93a6e 100644 --- a/docs/docs/api/create-sonarr-instance.api.mdx +++ b/docs/docs/api/create-sonarr-instance.api.mdx @@ -5,7 +5,7 @@ description: "Create a new Sonarr instance configuration" sidebar_label: "Create Sonarr instance" hide_title: true hide_table_of_contents: true -api: eJztWEuP2zYQ/ivEnFpAju1kg2B12yYtYDRpjGz6AAwfaHFsMSuRCh/rFQz992JIyZJtJdlFixyK7mVtcuabB4fDb3wAXaHhTmq1EJBCZpA7vNWKG7NQ1nGVISTg+M5CuoK4AesEBNrMyIoUIYXXQY1xpnDPohCTrTrLtNrKnY9WIAGDnz1a95MWNaSH8FUaFJA64zGBTCuHytEWr6pCZkFv+smSpQPYLMeS0ydXVwgp6M0nzBwkUBmKxUm0tKt4iQMp64xUO0iglOotqp3LIZ03CWy4xd9NMSa51abkDlLwRkKTAK/kr1g/AvOz54V09dLorSyCE8oXBd/Q5xgjV/X7LaSrc6gmOa4oX27QQLNuEjBau190IdCMgZ1DwKauuLWLndIhrQcQuOW+cJBueWF7jY3WBXJFKha51eqdVtLpADPUAl4UMGKnjOK/4X7hsLTf1kkAlS+pkOKu0gphHc2bLH+vbsSpvycBDtxtyzQ4HRNjHxdnLOSB5Gp9FOPG8BoSkF0wZwE3CUj7plN8VFZrlaHo7tEQ80u2ulMPOTES7cewPkwsgQluxFeyOxDhSpZ0gwWXRQ1rAu4v3Crekv4WHIt83TRR1FZa2ej689k8ejK8+W0+2IdWEv61CyzFQEYqhzs0FONDVngr7/GdVLKkeGORlPwhfr2ezV7Nr6+fv7x6dTW7vp6fhywF9S8uhCS3eLEcWA0nGUO/ms2+Z7TWceftay1wpBrIihjpZk0CaIw2ozslWst33+6BZ+kZONKa7Yz0kI/L33etlv9c/l7+X3//IH/Bvss1cZpK2xAvJ3swvZ9PbWAoU3lszKHb3odXZHUAT3wADpXRTme6aNLp9JBr65r0UGnjGrigP946XbLbgAEJ3HMj6YUOme1g6HPXoHPnKkjCPxvZVNfd251T/I85sg6GeYuCOc0yXZZe0Xkj20uXM5cji2FQ+snh04ej0Bkv8piMS3zaoNeAacMWS8aFMGgt09uAu/SFJVLX41MmTvFfzGYvxl3XxjGt2D6XWX6EkpYZr1R8Won4xLSHnKTTaa29mVRRdtKd1LNMlxcmlkaW3NRd+p8AlY66/EYazFwLx26yjPLwQ0gxRfLjmYV0Oj1mdhzwbbc9Cram4su8ka4O1Rdf4BtP1bpaky2L1kqtjksDjVu63rHOhno9z4jPeQKSHMmRi1CgkRzDX5Ob5WISBU5dvlku2B3WjHuXo3JtV2HeSrVjRzXWAga2MvDxC+Yzre8k9ubbI7kwfhuxJsRKxLgLEcqS5T1ucq3vbjEz6J4YfluLk6h84cifEXrcBe6dnuxQ0fiEgtkA8aydfqYfuKAib52zjJt+EELRXVhp2xQGtJKTiaKonwUCSC38Qz8r/fzAy6odJ6L/x356HGL6pW5Y6VfOp5J+ZzheDEBPp4iW5F4OCi2XvxgG2vUTat9OeGPsvSPR7aTZurE+4dydD+e0ejVbn/LlngETaVdbHWpCuhB314BulovRuhc68yUqFw97q82xZU0YZ8sCH9ieuywvpHUsEFPTi7bDL1eCxRKg5wCNjfB/zGPrtK7kanCS7fx8Njmfe3foH/unTdztbXD44KZVwWUYTnyceuOzuIL7OR1WnO8pZ11y191TsoLDoS20pqHlzx5NHSeo7sGLHau7aNTN7kIRvo5+T8LpkHjh4/U8IyzU7aIGdcrKfVV2PXjjl+9vP1LNtr8plIGugOF7Km++hxQgAR3yGJplWDtAwdXOB5ICEZP+/gb/6vDp +api: eJztWEuP2zYQ/ivEnFpAju1kg2B12yYtYDRpjGz6AAwfaHFsMSuRCh/rFQz992JIyZJtJdlFixyK7mVtcuabB+fpA+gKDXdSq4WAFDKD3OGtVtyYhbKOqwwhAcd3FtIVxAtYJyDQZkZWxAgpvA5sjDOFexaJmGzZWabVVu58lAIJGPzs0bqftKghPYSv0qCA1BmPCWRaOVSOrnhVFTILfNNPliQdwGY5lpw+ubpCSEFvPmHmIIHKkC1OoqVbxUscUFlnpNpBAqVUb1HtXA7pvElgwy3+booxyq02JXeQgjcSmgR4JX/F+hGYnz0vpKuXRm9lEZRQvij4hj5HG7mq328hXZ1DNcnxRPlygwaadZOA0dr9oguBZgzsHAI2dcWtXeyUDm49gMAt94WDdMsL23NstC6QK2KxyK1W77SSTgeYIRfwooAROWUk/w33C4el/TZPAqh8SYEUb5VWCOso3mT5e3UjTvU9MXCgbhumQenoGPs4O2MgDyhX6yMZN4bXkIDsjDkzuElA2jcd46O8WqsMRZdHQ8wvyepePfjESLQfw/nQsQQmuBFf8e6AhCtZUgYLLosa1gTcJ9wqZkmfBccgXzdNJLWVVjaq/nw2j5oMM7/1B/vQUsK/lsBSDGikcrhDE3NNlmTmLAF8yApv5T2+6w5jwJT8IX69ns1eza+vn7+8enU1u76en5svBdUyLoQkFXmxHGgQXjW64Wo2+56WW8edt6+1wJHIIClipLI1CaAx2ozelGgt3327Hp65Z6BIK7YT0kM+zn/fNXL+c/57+X/8/QP/Bfku1zTfVNoGeznJg+n9fGrDtDKVxyIdKu996CirA3iaDeBQGe10posmnU4PubauSQ+VNq6Bi1HIW6dLdhswIIF7biR16+DZDoY+d8U6d66CJPyzcbLqKn17c4r/MUfWwTBvUTCnWabL0it6b2R76XLmcmTRDHI/KXzaRAqd8SKPzrjEpwvqDEwbtlgyLoRBa5neBtylLywNeD0+eeIU/8Vs9mJcdW0c04rtc5nlRyhpmfFKxTZLQ1B0e/BJOp3W2ptJFWkn3Us9y3R5IWJpZMlN3bn/CVDpqMpvpMHMtXDsJsvIDz8EF5MlP55JSKfTo2fHAd9216Ngawq+zBvp6hB9sRvfeIrW1ZpkWbRWanU8GnDcUnrHOBvy9TNHbO0JSFIkRy5CgMZBGf6a3CwXk0hwqvLNcsHusGbcuxyVa6sK81aqHTuysRYwTC4DHb8gPtP6TmIvvn2SC+G3EWtCE4oYVyFCWZK8x02u9d0tZgbdE81vY3ESmS8U+TNCj6vAvdOTHSpapVAwGyCetZvQ9AMXFOStcpZx0y9FKLqElbZ1YUArOYkoivpZGAaphH/o96afH3hZtatF1P9YT48LTX/ULS79yfmG0t8MV40B6OlG0Q68l0tDO9dfLAbt+cmY3257Y5N8N1C3W2erxvpk/u50OB+xV7P16ezcT8M0wKutDjEhXbC7K0A3y8Vo3Aud+RKVi4+91eZYsiaMs2WBD2zPXZYX0joWhlTTk7aLMFeCxRCgdoDGRvg/5rF0WldyNXjJdpc+26LPtTv0zf5p23ebDQ4f3LQquAyLio8bcGyLK7if02PFXZ981jl33bWSFRwObaA1DR1/9mjquE11DS9WrC7RqJrdhSB8HfWehNch8sLH9DwbWKjaRQ6qlJX7Ku160OOX728/Usy2vy+UYVwBw/cU3nwPKUACOvgxFMtwdoCCq50PQwpETPr7GyYH9L8= sidebar_class_name: "post api-method" info_path: docs/api/pulsarr-api custom_edit_url: null @@ -62,7 +62,7 @@ Create a new Sonarr instance configuration diff --git a/docs/docs/api/create-sonarr-tag.api.mdx b/docs/docs/api/create-sonarr-tag.api.mdx index cb43acfdb..936372132 100644 --- a/docs/docs/api/create-sonarr-tag.api.mdx +++ b/docs/docs/api/create-sonarr-tag.api.mdx @@ -5,7 +5,7 @@ description: "Create a new tag in a Sonarr instance" sidebar_label: "Create Sonarr tag" hide_title: true hide_table_of_contents: true -api: eJztV0uP2zYQ/ivEoIcWlVZ2skFg3bZJD4umqBFvH4DhArQ0tpiVSIUcrtcx9N+LISU/1m6RRdqe4osskvPNzMd5aQemRStJGX1bQg6FRUk4M1paeyfXkADJtYN8DnENFgmU6AqrWpaBHN4ECSGFxo0guRZKCyniaaG0I6kLhAQsfvTo6AdTbiHfhVdlsYScrMcECqMJNfGWbNtaFcGm7INjJTtwRYWN5H+0bRFyMMsPWBAk0Fr2gBQ63h0UsjP7s0oTrtFCAvhY1N6pB/xZadX4ZtDeyMf4OhmNXo8nkxevrl9fjyaTcZdALZdYH6E5skozM43S71CvqYJ8nEAridAyI3/OZfpplE7SxfffQNclR77Ojw0coBcJyLJU7K+sp0furGTtsOsihGuNdtHJF6MRP07v4S2upK9JvO9Pwr/H6X/O5RlJzyPn+v/lw5Ek796YEo/Oat8s0ULHWk42BhcTQGuNvbjToHNyfUHqNMie0nRkSK92UHKA/Dz+rr/y9wX8vfoaf1/AX9BPleH20xoX/JWsD7KHceZCI8liX0optCSH9gEtd6UdeFtDDrvWGjKFqbs8y3aVcdTlu9ZY6uCsX3lHphGzgAEJPEir5LKO1A4w/B81F7E5VEQtJOHhYvsLFwv5sHOKf1ehGGCEd1gKMqIwTeM1XziKjaJKUIUiusH8s8Exggbo2hSyriIb5/i8oWWDwlhxOxWyLC06J8wq4E597bj5HvCZiVP8l6PRy8umG0vCaLGpVFHtoZQT1msda3WXDLQHTvIs2xpv0zaeTYcOd1WY5kzF1KpG2u1A/zOg8osmv1UWC+rhxE1RMA/fBorZk++eaMizbM/sZcB3w/ZFsAUHX+Gtom2IPtmqn3B74zlc5wvW5dA5ZfR+6Uhixvkd4+xYbp9zcRESUGxIhbIMAcoXDTn8kd5Mb9N44NTkm+mtuMetkJ4q1NSXFeGd0muxFxM9YJec2vg36gtj7hUe1PdXcqZ8FrHSpeRQv2hChHKseYPLypj7GRYW6Znu97GYRuEzQ36P0JdNkJ5MukbNoy6WwgWIq35Kzd7LkoO8N84JaVEURq/U2lssh4RVrqcwoDWSVdT19ipML1zD3x8m3B8fZdPW+HQiHe0noKPiq/TKBBoUsQQMOXczvb141aUpfIOaon8rY/dZmgoppjU+io2koqqVIxHGNXs42s/lUpcies0VEK2L8L+NY7Vw1MjQknry+xm/F45F+MSw3aG3ffYHQX/thI+UtbVUmnWHVN31DWAOD2Mu9/HLI4GjJrAYiuYcdjuOvV9t3XW8/NGj5eRcHEp7zM0hpDhv73HLlkab0zu2hI/XPgbik97MeR0luCa09I9nF0ftbPrL7A4SWPbfPE3ozGDlhr+H5AZygARM4DCUhbC2g1rqtQ/9GCIm//4CQWS8kw== +api: eJztV0uP2zYQ/ivEoIcWlVZ2skFg3bZJD4umqBFvH4DhArQ0tpiVSIUcrtcx9N+LISU/1m6RRYqc4osskvPNzMd5aQemRStJGX1bQg6FRUk4M1paeyfXkADJtYN8DnENFgmU6AqrWpaBHN4ECSGFxo0guRZKCyniaaG0I6kLhAQsfvTo6CdTbiHfhVdlsYScrMcECqMJNfGWbNtaFcGm7INjJTtwRYWN5H+0bRFyMMsPWBAk0Fr2gBQ63h0UsjP7s0oTrtFCAo3SqvEN5KME8LGovVMP+OuwGC1p5GN8nYxGr8eTyYtX16+vR5PJuEuglkusj5AdWaXXEfgd6jVVkI8TaCURWmbn77lMP43SSbr48TvouuTI7/mxsQP0IgFZlop9l/X0yLWVrB12XYRwrdEuOvxiNOLH6Z28xZX0NYn3/Un4//j9qryeEfY8oq6/LjeOJHn3xpR4dFb7ZokWOtZysjG4mABaa+zFnQadk+sLUqcB95SmI0N6tYOSA+Tn8Xf9jb8v4O/Vt/j7Av6CfqoMt6XWuOCvZH2QPYwzFxpMFvtVSqFVObQPaLlb7cDbGnLYtdaQKUzd5Vm2q4yjLt+1xlIHZ33MOzKNmAUMSOBBWiWXdaR2gOH/qLmIzaEiaiEJDxfbYrhYyIedU/y7CsUAI7zDUpARhWkar/nCUWwUVYIqFNEN5p8NjhE0QNemkHUV2TjH5w0tGxTGitupkGVp0TlhVgF36mvHTfmAz0yc4r8cjV5eNt1YEkaLTaWKag+lnLBe61iru2SgPXCSZ9nWeJu28Ww6dLurwjRnKqZWNdJuB/qfAZVfNPmtslhQDyduioJ5+D5QzJ788ERDnmV7Zi8Dvhu2L4ItOPgKbxVtQ/TJVv2C2xvP4TpfsC6Hzimj90tHEjPO7xhnx3L7nIuLkIBiQyqUZQhQvmjI4a/0ZnqbxgOnJt9Mb8U9boX0VKGmvqwI75Rei72Y6AG75NTGf1FfGHOv8KC+v5Iz5bOIlS4lh/pFEyKUY80bXFbG3M+wsEjPdL+PxTQKnxnyZ4S+bIL0ZNI1ah6BsRQuQFz102v2XpYc5L1xTkiLojB6pdbeYjkkrHI9hQGtkayirrdXYXrhGv7+MPn+/Cibtsank+poPwEdFV+lVybQoIglYMi5m+ntxasuTeEb1BT9Wxm7z9JUSDGt8VFsJBVVrRyJMLrZw9F+Xpe6FNFrroBoXYT/YxyrhaNGhpbUk9/P/r1wLMInhu0Ove2zPxT6ayd8pKytpdKsO6Tqrm8Ac3gYc7mPXyQJHDWBxVA057Dbcez9buuu4+WPHi0n5+JQ2mNuDiHFeXuPW7Y02pzesSV8vPYxEJ/0Zs7rKME1oaX/PLs4amfT32Z3kMCy/xZqQmcGKzf8nSQ3kAMkYAKHoSyEtR3UUq996McQMfn3D4KsxD8= sidebar_class_name: "post api-method" info_path: docs/api/pulsarr-api custom_edit_url: null @@ -54,7 +54,7 @@ Create a new tag in a Sonarr instance @@ -62,7 +62,7 @@ Create a new tag in a Sonarr instance diff --git a/docs/docs/api/get-all-dashboard-stats.api.mdx b/docs/docs/api/get-all-dashboard-stats.api.mdx index 4c68c5e2c..6b3200f4b 100644 --- a/docs/docs/api/get-all-dashboard-stats.api.mdx +++ b/docs/docs/api/get-all-dashboard-stats.api.mdx @@ -5,7 +5,7 @@ description: "Retrieve comprehensive dashboard statistics including genres, cont sidebar_label: "Get all dashboard statistics" hide_title: true hide_table_of_contents: true -api: eJztWdtu2zgQ/RWCT7uAXDu9oIjfgnZRBNsFjKZ7AQJDoKSxxYYiVZKyYxj698WQkqzYtONLu9uHPsURyTNnrhxp1lSVoJnlSt5mdEznYG+EeM9MniimszvLrKERtWxu6Pie4v/cWJ4aOo1oBibVvMTDdEw/gdUcFkBSVZQacpCGL4BkLRYx3WHCZSqqjMs5mYPUYCKSKmlB2ohUBrSJCJMZYanlC25XNKIl06wACxpprKlJcygYHa9pBjNWCUvHV6OI2lUJdEy5tDAHTSMKj6mokMYfXPKiKujY6goiWrBH/+/1aPT26vr65ZvXb1+Prq+v6ohyVOZrBRrlSlYgouAFt7SOgpJfhSQXrcDR6dIytjK0nkZUgymVNGBQ3svRyIvtG/29J0E+NTtpRBtL4l5WloKnzrvDLwYP9BVoOKvkC6QWbawxFiz34qwqY++c3l6mNUOi3EJhnsdw53vbjNVczmmNJCtPsVmRVZGApnWNSn+tuIYM480DtNunEWVZxlEdJiY9STMmDODZQhkbL5lNc8hik6vlBeQtt+I08hG1eVUkuCIrIViC533A7WDMK54dQ649UXeOjf3K9o6IgsQYu6eFWnA0GupPp3VEXUqdImvLC94Srd6tlid6w7H66Y7/0R2Y0cfK3uMEX6DOz2d3/vh0xhujMnHGUVBS+Zp3LncPdgn7BuF4/v0Q+UZahKPteB3c6vEaaEhRge4mfj5CYOlTXnBj422lNqnZuDbNmZxDeItUls+a68vEBo4KsID4HWFB6GdsgTe1sUymEDLGiU7soHgW1Lxbf662GCWZxnZDswx/YHnZm6NILj4yTPoMt/lEbRr3AJ+PJLZgXLCEC25XseXFJXfB4dLrhM1j10WFrFtweWCRPe5fPNJ4T+j1yPRE9wQdn4xzzZIEstiq2Acw/v5pyTMs2RQEq5k0bt8FJpxpVcQHLherDq3+4A7oK9dXJfrerpkJtfxhfbK/4zzgrXMt27Z5HfTzNuxfbw73mMZCWSbiJxdjUMNktW2XH7uDSVbu3pcgLqiSOwAns24hTiKO3fr5rPF0fGnHvgE5lvm2uwJxtYmiJ/7Z6Hx8M9YmS6KBPWRqeUFrvacZ22vBc5ozb439bXGpecH06sCOZLVbun78NyH0criqfuuG5bzO4qzQdr1xkxsNTt+/297s+27XIienVfd5LvjZK/z1pf8RIPx6feildfd1MPRWFGz1DzWvz2peR/T1f/vx01vmncogmIPp04VN8IHWSgdXCjCGzYP1ouDyI8i5zen4KpxojkgjthWygTzGfm9+2u8C+zn5NlfNhMTNI1AcHS6uhq6/GjKB95cBvWhnFJUWdEzXpVZWpUrU4+FwnStj6/G6VNrWdHuA8q4yVhXkzmHQiC6Y5vjR0lm0hcHf7QWTW1vSyP1p5jHNRKJdeYr/OQfSwuCYJSNW4bCmqCT6GciS25zYHIhXA82OhGl/2EGFSplwj0P4uIAVkShNbieEZZkGY4iaOdxJJQzTuoePlniK/2o0ehWmrrQlSpJlztO8g+KG6ErK9kNpa3Znk/FwuFKVHpR+76CtVS9SVeyImPha3Zr/BKhxkPJ7riG1DRy5SVO0wy/OxKjJr1sSxsNhZ9kw4Md2OQg2xeBLK+2+S91j9vLfYXVTYZjeT93kCozhSnaPeifuMK19nPXPbe5o95A206ocWOYCtBlX/TO4mdwO/IanlG8mt+QBVoRVNgdpm2pCKoOTv+4YaQDx5a/PcY/4VKkHDhvxjUt2hN95rEHCMNSDFDyUQclLSHKlHu4g1WBPVL+JxYE/vEPkbw8dpsAqqwZzkDiEhYwYB/GC3LnecfjJdY6kIWcI0zhelTM+rzRkbcJy05jQoRUMRQixeuGaHi5nim7mJ7RNnJvJbdBfmUqrAqT1JGdKd6k2IIxMBDyS7gMrcfNOvdnqabvhraeOZQy08fB/XfmUN7ZgcjNOoB/AEiZEcFS8zXG9uaK+78S58b2FRzssBeMSubt8XTfV/54urpouCmniDTBtK+Y9Xa8x8P7Uoq7xsR/xYmZm3GBVz5r7JaIPsOpGzFj1RYWCXX3Ys7f5pLLZOt3cFj7d2yhFgf4MFo2yL2DnfkeU7pr78NtnWtf/Ai1mTfY= +api: eJztWdtu2zgQ/RWCT7uAXNu9oIjfgmZRBNsFjKZ7AQJDoKSxxYYiVZKyYxj698WQkizb8r3dzUOf4ojkmTNXjjQrqnLQzHIl7xM6ojOwt0LcMZNGiunkwTJraEAtmxk6eqT4PzeWx4ZOApqAiTXP8TAd0c9gNYc5kFhluYYUpOFzIEmNRUxzmHAZiyLhckZmIDWYgMRKWpA2IIUBbQLCZEJYbPmc2yUNaM40y8CCRhorauIUMkZHK5rAlBXC0tFwEFC7zIGOKJcWZqBpQDMueVZkdDQIKDzHokBKf9QPrS4goBl79v/eDAbvhzc3r9+9ff92cHMzLAPKUbFvBWjkIFmG6IJn3NIy6GTx5hiLc6UlbGloOQmoBpMracCgvNeDgRfbdsCdJ0E+VztpQCur4l6W54LHztP9rwYPtBWoOKvoK8QW7a0xLiz34qzKQ++o1l6mNUOi3EJmjmO4861txmouZ7REkoWnWK3IIotA07JEpb8VXEOCsecB6u2TgLIk4agOE+OWpCkTBvBspowNF8zGKSShSdXiCvKWW3Ee+YDatMgiXJGFECzC8z7gdjBmBU9OIVefKBvHhn5le0dAQWKMPdJMzTkaDfWnkzKgLr3OkbXlBW+JWu9ayzO94Vj9dMf/6A7M6FNl73GCL1CX57M7f3o64+1RmDDhKCgqfM27lLsHu4Z9hXA6/3aIfCctuqPtdB3c6ukaaIhRgeZWPh4hsPApL7ix4bZS69SsXBunTM6ge4tUlk+r68uEBk4KsA7xO8I6oY/YAm9qY5mMocsYZzqxgeJJp+bN+rHaYpRkGtsNzRL8geVlb44iufDEMGkz3OYT1GncAjweSWzOuGARF9wuQ8uza+6Cw6XXCZuFrovqsm7G5YFF9rx/8UTjbdBrkWmJbgk6PRlnmkURJKFVoQ9g/P3TkhdYsioIVjNp3L4rTDjVKgsPXC5WHVp94Q5oK9dWJfjRrpkKtXixPtnfcR7w1qWWrdu8Bvq4DdvXm8M9pbFQlolw42Ls1DBabtvlZXcw0dLd+xLEFVVyB+Bs1jXEWcSxW7+cNZ4Or+3Y1yCnMt92V0dcraNowz9rnU9vxupkiTSwp0Qtrmit9zRjey14SXPmrbG/Lc41z5heHtgRLXdL18t/E0Ivd1fV792wXNZZXBTarjeucqPCaft325tt3+1a5Oy0aj7PdX726v760v4I0P16feildfd1sOutqLPVP9S8HtW8DOjb//bjp7fMB5VAZw7Gmwvr4AOtle5cycAYNuusFxmXn0DObEpHw+5Ec0QqsbWQNeQp9nv3035X2M/Jt6mqpiVuNoHiaH8+7Lv+qs8E3l8G9LyeVxRa0BFd5VpZFStRjvr9VaqMLUerXGlb0u1hyofCWJWRB4dBAzpnmuNHS2fRGgZ/1xdMam1OA/enms1UE4l6ZRP/SwqkhsGRS0KswsFNVkj0M5AFtymxKRCvBpodCdP2sIMKFTPhHnfh4wJWRKI0uR8TliQajCFq6nDHhTBM6xY+WmIT/81g8KabutKWKEkWKY/TBoobogsp6w+ltdmdTUb9/lIVupf7vb26Vr2KVbYjYuxrdW3+M6BGnZTvuIbYVnDkNo7RDr84E6Mmv25JGPX7jWW7AT/Vy51gEwy+uNDuu9QjZi//HZa3BYbp48RNrsAYrmTzqHXiAdPax1n73PqOdg9pNa1KgSUuQKtx1T+92/F9z2/YpHw7vidPsCSssClIW1UTUhicAjbHSAWIL39tjnvEx0o9cViLr1yyI/zBY/UihqHeScFDGZS8gChV6ukBYg32TPWrWOz5wztE/vbQ3RRYYVVvBhIHspAQ4yBekQfXO/Y/u86RVOQMYRpHrXLKZ4WGpE5YbioTOrSMoQghlq9c08PlVNH1/ITWiXM7vu/0V6LiIgNpPcmp0k2q9QgjYwHPpPnASty8U6+3etpukOupYxkDbTz8X0Of8sZmTK7HCfQjWMKE6Bwbb3Ncra+oHzt9rnxv4dn2c8G4RO4uX1dV9X+k82HVRSFNvAEmdcV8pKsVBt6fWpQlPvYjXszMhBus6kl1vwT0CZbNiBmrvihQsKsPe/ZWn1TWWyfr28Knex2lKNCfwaKRtwXs3O+I0lxzH3/7QsvyX/CwUcw= sidebar_class_name: "get api-method" info_path: docs/api/pulsarr-api custom_edit_url: null @@ -47,7 +47,7 @@ Retrieve comprehensive dashboard statistics including genres, content, users, an diff --git a/docs/docs/api/get-all-schedules.api.mdx b/docs/docs/api/get-all-schedules.api.mdx index dbc0a06de..725dfa725 100644 --- a/docs/docs/api/get-all-schedules.api.mdx +++ b/docs/docs/api/get-all-schedules.api.mdx @@ -5,7 +5,7 @@ description: "Retrieve all configured job schedules and their status" sidebar_label: "Get all job schedules" hide_title: true hide_table_of_contents: true -api: eJztWF1v2zYU/SvEfdoAuXYaJBv0FrRDEazDjKb7AAyjoKVriw1FquSlE8PQfx8uKduyoyRIgO1lzYsVkTz38NwPXmoLtkEnSVlzXUIOK6QrrW+KCsug0UMGJFce8hns3jmYZ1CiL5xqeBnk8AnJKVyjkFqLwpqlWgWHpfhqF8LvoIQ0paAKlROeJAXGdugbazx6yLfwdjLhn2Po97iUQZP41M2EDAprCA3xXNk0WhWR/fir5wVbYIO15CfaNAg5SOfkBjJQhHU0JM3m9yXks/0Mu/iKBUEGjWM1SCVCquyhmFAv0EGbgZE19gY8OWVWPJBenA5kgCbUrKAyhG4tNczbuIulWvWmP0KilBvfm8UYK3SMel/o4NUaf1NG1WyBXMAManmf/j2/vGgzqGxwrwT4+afLSZtBrUwgfCXGxduLywmjeCysKV+7lbOL88tJxHHBXNc1lkoS6k0PbmGtRmmgbTOQZak4KKSe9sRcSu2xZX/IhcZyaG0GWnr64kIMJRO05qk7Pk97itQjcdGF+xORUdi60UhYQgZLqXR8aNCUPI2jBZ2zbhAcPala0vB2WC/8FpTj4VliuOczf04ng/ffpUhSFA4Z+IukQcuhKR8fPrGseEOxhnQq7mvBITJ7YdhzwxGNI6PP8f+vK13hrHlRlcP7xqH3KpXwU9hamY9oVlRBfnYqZ2/ls178nvjfE/9/lfjzlv8yuPiXWqtHoiCJ+86WOFhWiuOBnh8f9XCN3svVYM15qjj0iHRmd0YOkM9ImASskSrb9ce8XcnmYLw+G+/6W7d/4sbWo1sj912zLQSnIYdt4yzZwuo2H4+3lfXU5tvGOmrhtJ1+FzzZWtxEDMhgLZ3i8Ija7mBi1exSpyJqIIs/PnXn0bOQ70aO8T9XKHYwIngsBVlR2LoOhj2O4k5Rxa26SNuA2EZ6SiG0g9a2kDq+HsLnAY51YZ24ngpZllylhV1G3GnQXjrXw2cljvHPJ5PzYerWkbBG3FWqqPZQygsXjEmJxydekj1qko/HGxvcqElzR8p4kqbAN4WtH5iYOlVLt9nJ/wKofJDye+WwoA5OXBUF6/BDlJh38uOJhXw83is7DPhxNzwINo+tdnCKNjH6ZKN+xc1V4ICdzdmWT8fl/lVvBd/x6hRn/XWHq1R8yXcpJlKhLGOApj4B/h5dTa9HacIx5avptbjFjZCBKjTU1RURvDIrsV8mOsB4W+hxfMR8Ye2twoP5ziUPjN8krNFCcqgPUkhQni3f4aKy9vYGC4f0wu13sThKix8Q+StBD1OQgexohYYv41gKHyHeiBtrpHPjT7LkIO/IeSEd9i/aXcIq30kY0WrJJrTevIlHkTJLm45o4tMcdolzNb0e9Fdpi1CjoURyad0+1UZCiqnGe3Enqai08iTiXc4dpiba8cqfqHMZQ5caPPjzLKW8p1qmJiMp+AEpfkI4+m5wSm57OKVe/+GhcyjhPY0bLVXsAGMSbrviPoP1GRfy/VePw3OssakgzmC75bj6w+m25dffAjpOvPmhbKe824UL5+QtbljkosCGYn3XIYXXyZHLqbk/eT788hna9h+GDimQ +api: eJztWG1v2zYQ/ivEfdoAuXYaJBv0LWiHIliHGc3eAMMoaOlssaFIlTw6MQz99+FIyZZdpUGHbl/WfLHCl+cePrw7HrkH26CTpKy5LSGHDdKN1ndFhWXQ6CEDkhsP+QL6NgfLDEr0hVMNT4Mc3iE5hVsUUmtRWLNWm+CwFB/sSvgeSkhTCqpQOeFJUmBsh76xxqOHfA8vZzP+OYV+jWsZNIl33UjIoLCG0BCPlU2jVRHZTz94nrAHNlhL/qJdg5CDdE7uIANFWEdD0ux+XUO+OIywqw9YEGTQOFaDVCKkygGKCfUKHbQZGFnjoMOTU2bDHanhvCMDNKFmBZUhdFupYdnGVazVZjD8CRKl3PnBKMbYoIMMamVUzcCzDPCx0MGrLf7SN5ILmEEtH9O/l9dXbQaVDe4rgP34w/WsjXMC4VfAu3p5dT1jRI+FNeXXWO7F1eX1LGK6YG7rGkslCfVuAL2yVqM00LYZyLJU7ERSzwfir6X22PL+yZXGcmxuBlp6eu9CdD0TtOahPZ/P7yypJ/yoC4/PeFJh60YjYQkZrKXS8aNBU/Iw9i50zrpRcPSkaknjy2G98GNQjrsXieGBz/I5nQw+fpMiSVE4ZOD3kkYth6Z8uvvMsuIFxZzTqXjIHUfPHLjhYBtOaJwYfY7/f50ZC2fNF2VFfGwceq9Syj+HrZV5i2ZDFeQX53IOZj67i98C/1vg/68Cf9nyXwZX/1Ip9oQXJHFf2RJH00px2jHYxyd3uEbv5WY053wuOQyIdGZ7I0fIZyRMAtZIle3qaV6uZHMw3V5M+3rYHb64EPbotsi12WIPwWnIYd84S7awus2n031lPbX5vrGOWjgvv18FT7YWdxEDMthKp9g9orY9TMyaXehURA1k8cenaj7uLOR9zyn+bxWKHkYEj6UgKwpb18HwjqN4UFRxaS/SMiCWmp6SC/XQ2hZSx+YxfO5gXxfWidu5kGXJWVrYdcSdB+2lcwN8VuIU/3I2uxynbh0Ja8RDpYrqAKW8cMGYFHh84iXZoyb5dLqzwU2aNHaijCdpCnxR2PoTE3Onaul2vfxfAJWPUn6tHBbUwYmbomAdvosS80q+P7OQT6cHZccB3/bdo2DLWHYHp2gXvU826mfc3QR22MWSbfl0XB6aBjP4TlgnPxvOO169YiPfvZhIhbKMDprqBPhrcjO/naQBp5Rv5rfiHndCBqrQUJdXRPDKbMRhmugA481hwPEJ84W19wqP5rst+cT4XcKarCS7+iiFBOXZ8gOuKmvv77BwSF+4/M4XJ2nyJ0T+TNDjFGQgO9mg4cs7lsJHiBfizhrp3PSdLNnJO3JeSIfDi3kXsMp3Eka0WrIJrXcv4lGkzNqmI5r4NIc+cG7mt6P7Vdoi1GgokVxbdwi1iZBirvFRPEgqKq08iXivc8ehiXZ8IkjUOY2hSwUe/HGRQt5TLVORkRR8gxSfHE7eGc7J7Y+n1D9/qOg2lPCRpo2WKlaAMQj3XXJfwPaCE/nhleT4HXNsSogL2O/Zr353um25+WNAx4G3PKbtFHe9u3BM3uOORS4KbCjmdx2Se50duRyah5PnzU+/Qdv+DTbgOOg= sidebar_class_name: "get api-method" info_path: docs/api/pulsarr-api custom_edit_url: null @@ -54,7 +54,7 @@ Retrieve all configured job schedules and their status diff --git a/docs/docs/api/get-most-watched-movies.api.mdx b/docs/docs/api/get-most-watched-movies.api.mdx index d92695985..ea47896f6 100644 --- a/docs/docs/api/get-most-watched-movies.api.mdx +++ b/docs/docs/api/get-most-watched-movies.api.mdx @@ -5,7 +5,7 @@ description: "Retrieve the most watchlisted movies with optional date filtering sidebar_label: "Get most watched movies" hide_title: true hide_table_of_contents: true -api: eJztVl1v2zYU/SvEfdoAObLbFEX0FrRDEawBjLhbBwTGQEvXFhuJVMlLJ4ah/15cUrLlRMESDN3TnuKIl+cc3u89mAatJGX0VQEZbJCujaOvkvISi2uzVeggAZIbB9ktLEiScqRyB8sECnS5VQ1fhgxukKzCLQoqUdTGkbhnlEo5wkLUAUrcKyqFCVdkJQpJKNaqIrRKb4TUhWjkRumgBxJopJU1Elom34PLS6wlZHsocC19RZDNpgnQrkHIQGnCDVpIAB/yyju1xWulVe1ryMh6TKCWD/Hfi+n0/ezi4s278/fn04uLWZuA4id892h3kICWNSNWqlYEbTLKPEZc93zT15OZ9drhf8ZWyJ2DdpmARdcY7dAx3ZvpNLIO4/oxahA3nSUkkBtNqIltZdNUKg8BS785vjDU32mW1komV4S1G3w3q2+YEwfachqSijJIUYUDM0ecHtAysY+03Yn29Qotn1Dp6xWfaF9VcsX3Y9CfYGy8KtwLxPU32sN7/44njy0SQM2uv4WQ5JCAK809LNsEvAu5+3KuliPy3SuLBQNGT/Tv7l+5TEAWhYpFNB+4bi0rh20bYM5/UiyfiZkjSd59MAWOhic/PThGA601dvSkRufkZtTftdKfUW+ohGz22GUDIR1tT3KE/GcHJvDuf//9C/8FfipNN1NCL2c6SLezlClcWvfDxaHd9i3e2woy2DfWkMlN1WZpui+NozbbN8ZSC4+nzgfvyNRiETAgga20iss/OLWH4d99jZZEDSThTzfEuhbbn5zifylR9DDCOywEGZGbuvaaQ41xovHIi89gz7Pgk+4NlcllFT6P4fMB92VhrLiaC1kUFp0TZh1w575y0toBPnviFP/tdPp2XLqxJIwW96XKywOUcsJ6rfuW07s9+CRL053xdtJE24nSjqTO8Sw39ROKuVW1tLve/a+AykYlf1QWc+rgxGWesx9+CS7ml/z6iCFL04NnxwE/98ejYEtOvtxbRbuQfbJRv+Pu0nOm3i7DKEbnlNGHT4MbC67smGfDe8deHz5CN39LlEVI0G4A/zW5nF9NosGp5Mv5lbjDnZCeStTUNRThHa9Ih2uiA2yTU43P0OfG3IXR1NF3IXlCvohYk5XkVB+VEKEcM9/jqjTmboG5RXrl87tcnMTLT4R8jdDjEqQnM9mg5s0VC+ECxJlYGC2tTW9kwUneiXNCWhS50Wu18RaLvmCV61wY0GrJFFW1OwsDX+m1GWwi0BfO5fxqNF6FyX2NmqLItbGHUpsIKeYVPhyXYRE2OHs0jbLD9hulcxtD6yL8n7NY8o5qGSZK58FPSIMt+7BhP5a3Pw6on7Ghd8EmfKC0qaTSLDYU6L7r+LewnXGT557PIySqXPZd8hb2e062P2zVtvw5LqpcjYVy3MmLbqwkcIe7w1bOnb7yzB16wjO23VL9MuOwEw9Nl8dxEvtBn8asLt7hrtIMCZ7sAIxyGIWffvsCbfsDMOulPQ== +api: eJztVl1v2zYU/SvEfdoAObLbFEX0FrRDEawBjLhbBwTGQEvXFhuJVMlLJ4ah/z5cUrLlREETDN3TnuKIl+cc3u89mAatJGX0VQEZbJCujaOvkvISi2uzVeggAZIbB9ktLEiScqRyB8sECnS5VQ1fhgxukKzCLQoqUdTGkbhnlEo5wkLUAUrcKyqFCVdkJQpJKNaqIrRKb4TUhWjkRumgBxJopJU1Elom34PLS6wlZHsocC19RZDNpgnQrkHIQGnCDVpIoFZa1b6GbJoAPuSVd2qL1/1Hsh4TqOVD/PdiOn0/u7h48+78/fn04mLWJqD4Od892h0koGXN6JWqFUGbjKr4kYjXkpn12uF/xlbInYN2mYBF1xjt0DHdm+k0sg5j/DFqEDedJSSQG02oiW1l01QqD8FLvzm+MNTfaZbWSiZXhLUbfDerb5gTB91ySpKKMkhRhQMzR5wq0DKxj7Tdifb1Ci2fUOnrFZ9oX1Vyxfdj0J9gbLwq3AvE9Tfaw3v/jiePLRJAza6/hZDwkIArzT0s2wS8C3n8cq6WI/LdK4sFA0ZP9O/uX7lMQBaFigU1H7huLSuHbRtgzn9SLJ+JmSNJ3n0wBY6GJz89OEYDrTV29KRG5+Rm1N+10p9Rb6iEbPbYZQMhHW1PcoT8sQMTePe///6F/wI/laabL6GvMx2k21nKFC6t+0Hj0G77du9tBRnsG2vI5KZqszTdl8ZRm+0bY6mFxxPog3dkarEIGJDAVlrF5R+c2sPw775GS6IGkvCnG2hdi+1PTvG/lCh6GOEdFoKMyE1de82hxjjdePzFZ7DnWfBJ94bK5LIKn8fw+YD7sjBWXM2FLAqLzgmzDrhzXzlp7QCfPXGK/3Y6fTsu3VgSRov7UuXlAUo5Yb3Wfcvp3R58kqXpzng7aaLtRGlHUud4lpv6CcXcqlraXe/+V0Blo5I/Kos5dXDiMs/ZD78EF/NLfn3EkKXpwbPjgJ/741GwJSdf7q2iXcg+2ajfcXfpOVNvl2EUo3PK6MOnwY0FV3bMs+G9Y68PH6GbvyXKIiRoN4D/mlzOrybR4FTy5fxK3OFOSE8lauoaivCO16XDNdEBtsmpxmfoc2Puwmjq6LuQPCFfRKzJSnKqj0qIUI6Z73FVGnO3wNwivfL5XS5O4uUnQr5G6HEJ0pOZbFDzFouFcAHiTCyMltamN7LgJO/EOSEtitzotdp4i0VfsMp1LgxotWSKqtqdhYGv9NoMNhHoC+dyfjUar8LkvkZNUeTa2EOpTYQU8wofjouxCBucPZpG2WETjtK5jaF1Ef7PWSx5R7UME6Xz4CekwcZ92LYfy9sfB9TP2Na7YBM+UNpUUmkWGwp033X8W9jOuMlzz+cRElUu+y55C/s9J9sftmpb/hwXVa7GQjnu5EU3VhK4w91hK+dOX3nmDj3hGdtuqX6ZcdiJh6bL4ziJ/aBPY1YX73BXaYYET3YARjmMwk+/fYG2/QdSqKkT sidebar_class_name: "get api-method" info_path: docs/api/pulsarr-api custom_edit_url: null @@ -47,7 +47,7 @@ Retrieve the most watchlisted movies with optional date filtering and pagination diff --git a/docs/docs/api/get-most-watched-shows.api.mdx b/docs/docs/api/get-most-watched-shows.api.mdx index e341faa39..708f99bd3 100644 --- a/docs/docs/api/get-most-watched-shows.api.mdx +++ b/docs/docs/api/get-most-watched-shows.api.mdx @@ -5,7 +5,7 @@ description: "Retrieve the most watchlisted TV shows with optional date filterin sidebar_label: "Get most watched shows" hide_title: true hide_table_of_contents: true -api: eJztVt9v2zYQ/leIe9oAObb7A0X0FrRDEawFjDprBwTGQEtni41EquTRiWHofx+OpGw5UbAWQ/e0pzgi+X0fvzve3QFMi1aSMvq6hBy2SB+Noy+SigrLZWXuHWRAcusgv4UlSVKOVOFglUGJrrCq5bOQwyckq3CHgioUjXEk7hmkVo6wFDefhWMwca+oEiYckrUoJaHYqJrQKr0VUpeilVulgyDIoJVWNkhomf4ArqiwkZAfoMSN9DVBPp9lQPsWIQelCbdoIQN8KGrv1A4/Kq0a30BO1mMGjXyI/17OZm/ml5cvXr9682p2eTnvMlB8iW8e7R4y0LJhxFo1iqDLRpnHiJueb/bjZGazcfifsZVy76BbZWDRtUY7dEz3YjaLrMPIvosaxKe0EzIojCbUxHtl29aqCAGbfnV8YKg/aZbWSiZXhI0bfDfrr1gQB9pyHpKKMkhRjYNtjjg9oGNiH2nTivbNGi2vUOWbNa9oX9dyzedj0J9gbL0q3XeI6090x/v+FVce78gANVt/C43ZKfaHcx1WXQbehdz9fq6OI/LNK4slA0Yn+nv3t1xlIMtSxUe0GFi3kbXDrgswr35SLJ+JmSNJ3r01JY6GpzhfOEUDrTV2dKVB5+R21O9G6Q+ot1RBPn9s2UBIou1JTpD/bGAGr//371/4F/ipMqmphFrOdDDdzadM4aYudReHdtdXeG9ryOHQWkOmMHWXT6eHyjjq8kNrLHXwuO289Y5MI5YBAzLYSav49QdPexj+3T/RiqiFLPxJXSxV2H7lHP+mQtHDCO+wFGREYZrGa440xobGPS9eg41nwWfFG2pTyDp8HsPnBS7LwlhxvRCyLC06J8wm4C587aS1A3x24hz/5Wz2cly6sSSMFveVKqojlHLCeq37itPbHjzJp9O98XbSxr0TpR1JXeBFYZonFAurGmn3vf0/AJWPSn6nLBaU4MRVUbAPvwSL+Sa/PmLIp9Ojs+OAH/rlUbAVJ1/hraJ9yD7Zqt9xf+U5UW9XoROjc8ro46fBiSU/7Jhnw3OnUh8+Qmq/FcoyJGjqv39OrhbXk7jhXPLV4lrc4V5ITxVqSvVEeMcT0vGYSIBddq7xGfrCmLvQmRJ9CskT8mXEmqwlp/qohAjlmPke15Uxd0ssLNIPXj/l4iQefiLkS4QelyA9mckWNU+uWAoXIC7E0mhp7fSTLDnJkzgnpEVRGL1RW2+x7B+scsnCgNZIpqjr/UXo90pvzGAQgf7hXC2uR+NVmsI3qCmK3Bh7fGoTIcWixofTNCzCAGdPW6PsMPxG6VzG0LoI/3ken7yjRoaGkhx8jzQYs9mFVE7P1B1O7ennTOgp2oQPNG1rqTSrDS/0kCr+LezmXOW55qfRKBTeWCVv4XDgZPvD1l3Hn+Ocyq+xVI4reZm6SgZ3uD8O5Vzpa8/UoSY8szfN1N+3OYzEw62rUzuJ9aBPY1YXz3BVaYcET0YARjl2wve/3UDX/Q2wc6SX +api: eJztVlFrGzkQ/itinu5gXdttSsm+hfYo4Vowca49COaQd8deNbvSVho5MWb/exlJa68ThyYcvad7irOSvu/TN6OZ2YFp0UpSRl+WkMMa6bNx9FVSUWE5r8ydgwxIrh3kNzAnScqRKhwsMijRFVa1fBZyuEKyCjcoqELRGEfijkFq5QhLcf1FOAYTd4oqYcIhWYtSEoqVqgmt0mshdSlauVY6CIIMWmllg4SW6XfgigobCfkOSlxJXxPk00kGtG0RclCacI0WMmiUVo1vIJ9kgPdF7Z3a4Of+I1mPGTTyPv57Ppm8m56fv3579u5scn4+7TJQfKHvHu0WMtCyYfRaNYqgy06q+JmIl5KZ1crhf8ZWyq2DbpGBRdca7dAx3evJJLIOo/whahBXaSdkUBhNqIn3yratVRGCN/7m+MBQf9IsrZVMrggbN/hult+wIA665ZwkFWWQohoH2xxxqkDHxD7SphXtmyVaXqHKN0te0b6u5ZLPx6A/wlh7VbpniOtPdPv7/hNXHu7IADVbfwON2Sj2h/MeFl0G3oU8fj5XxxH57pXFkgGjE/29+1suMpBlqeKDmg2sW8naYdcFmLNfFMsnYuZIknfvTYknw1McLxyigdYae3KlQefk+qTfjdKfUK+pgnz60LKBkETbkxwgf25gBm//9+9f+Bf4qTKpwYS6znQw3kzHTOHGLnUah3bTV3tva8hh11pDpjB1l4/Hu8o46vJdayx18LAFvfeOTCPmAQMy2Eir+PUHT3sY/t0/0YqohSz8SR0tVdh+5Rj/ukLRwwjvsBRkRGGaxmuONMbmxv0vXoONZ8FHxRtqU8g6fD6FzwtcloWx4nImZFladE6YVcCd+dpJawf47MQx/pvJ5M1p6caSMFrcVaqo9lDKCeu17itOb3vwJB+Pt8bbURv3jpR2JHWBrwrTPKKYWdVIu+3tfwFUflLyB2WxoAQnLoqCffgtWMw3+f0BQz4e7509DfipXz4JtuDkK7xVtA3ZJ1v1J24vPCfqzSJ0YnROGb3/NDgx54cd82x47lDqw0dI7bdCWYYETf3379HF7HIUNxxLvphdilvcCumpQk2pngjveFraHxMJsMuONT5BXxhzGzpTok8heUQ+j1ijpeRUPykhQjlmvsNlZcztHAuL9MLrp1wcxcOPhHyN0KclSE9mtEbNUyyWwgWIV2JutLR2fCVLTvIkzglpURRGr9TaWyz7B6tcsjCgNZIp6nr7KvR7pVdmMIhA/3AuZpcn41WawjeoKYpcGbt/aiMhxazG+8NkLMIAZw9bo+wwCEfpXMbQugj/ZRqfvKNGhoaSHPyINBi52YVUTo/U7Q7t6ddM6ynahPc0bmupNKsNL3SXKv4NbKZc5bnmp9EoFN5YJW9gt+Nk+8vWXcef45zKr7FUjit5mbpKBre43Q/lXOlrz9ShJjyxN83Uz9scRuLh1sWhncR60Kcxq4tnuKq0Q4JHIwCj7Dvhxz+uoet+AM7qqG0= sidebar_class_name: "get api-method" info_path: docs/api/pulsarr-api custom_edit_url: null @@ -47,7 +47,7 @@ Retrieve the most watchlisted TV shows with optional date filtering and paginati diff --git a/docs/docs/api/get-radarr-quality-profiles.api.mdx b/docs/docs/api/get-radarr-quality-profiles.api.mdx index 476edda23..71288d20b 100644 --- a/docs/docs/api/get-radarr-quality-profiles.api.mdx +++ b/docs/docs/api/get-radarr-quality-profiles.api.mdx @@ -5,7 +5,7 @@ description: "Retrieve quality profiles from a Radarr instance" sidebar_label: "Get Radarr quality profiles" hide_title: true hide_table_of_contents: true -api: eJztV0tv2zgQ/ivEnFpAju02RRHdgnZRGNsFvEn3AQQ50NLYYiORCjl0Yhj678WQlJ/qdrsF9tSTbQ7nm28enBlvwbRoJSmjZyXksEK6kaW09ncva0WbuTVLVaODDEiuHOR3EOVwn0GJrrCqZWXI4QbJKlyjeIyqok26YmlNI6SIikJpR1IXCBm00soGCS0Db8EVFTYS8i3QpkXIQWnCFVrIAJ+L2ju1xt+UVo1vICfrMYNGPsefV5PJ2+nV1as3l28vJ1dX0y4DxawePdoNZKBlExGj8VkJGVh89MpiGcG6ez5xrdEOHZN4NZnwx7GX73EpfU3iJt2EDAqjCTXxXdm2tSpCOMefHSsMeGUWn7Egdt9y8ElFc84XBTp3cHFhTI1SQ/AlBe2bMKo8uKN9s0DLANH/ncCRVXrFgoV0+IetB2TdYYTuGDjB7JXuM5BlqdhfWc8PeCxl7bDL4PGkjPZWpLWSE6MIGz7vTu318Thw/hzvGwQC6OX/nEeS5N07U+JgIopjwT4RaK2xg5IGnZOrAa0MGqU/ol5RBfn0LIB7Islsb2QP+e/id/kzfj8Qvzc/6+8H4hfsU2XSdApTg83BeD0d2zBSxqkrjNr9tHJo1/1c8dzeYNtaQ6YwdZePx9vKOOrybWssdXA6yt55R6YRtwEDMlhLq+Qi9a8ehr+j5tlzBxVRC1n4cHEyhuxC3kuO8T9VKHoY4R2WgowoTNN4zVlH8aSoElShiG5wEphwLKMeujaFrMPxED4LuFsLY8VsLmRZWnROmGXAnfva8TDe43MkjvFfTyavh6kbS8Jo8VSpotpBKSes1zqOji7rwx5iko/HG+PtqI13R31HvyhMc2ZiblUj7aYP/3dA5YOU3yuLBSU4cR2mingRQsyevDyxkI/Hu8gOA37sxYNg91x8hbeKNqH6ZKt+xc2156K9u2dbDp1TRu+ODjRu+ZHHOjvU20/NcAhpualQlqFA03bz9+h6PhvFC8eUr+cz8YAbIT1VqCn1FuGd0iuxUxMJsMuOOX7FfGHMg8K9+ZSSM+O3EWvEW0M5TCFCObb8hIvKmIdbLCzSd7qfanEUlc+I/BWhhylIT2a0Qs3rMJbCBYgLcWs0t5i0vCZyTkiLojB6qVbeYtk/WOVSCANaI9lEXW8uwjKl9NIEXxTVTLd/ONfz2WC+SlP4BjVFkktjd09tJKSY1/gsniQVVa0cibAq2/3VSFtIXaa9m9sYWhfh/5zGJ++okWG4pAh+QOrX9NMt/pTidj+v/svqnxJK+EzjtpYqbLk+7qGxwd/BespLes/+rMnf903xDrbbtJF2HR/HpZ8fX6kcN+4yDZR/8OHFTRpdL8XX6D3g5vQvxFrWni+GZ9+PifjO+/JkGlGTu0VLB1pnY55RdtPuwy+foOu+AMyvwQw= +api: eJztV0tv2zgQ/ivEnFpAju02RRHdgnZRGNsFvEn3AQQ50NLYYiORCjl0Yhj678WQlJ/qdrsF9tSTbQ7nm28enBlvwbRoJSmjZyXksEK6kaW09ncva0WbuTVLVaODDEiuHOR3EOVwn0GJrrCqZWXI4QbJKlyjeIyqok26YmlNI6SIikJpR1IXCBm00soGCS0Db8EVFTYS8i3QpkXIQWnCFVrIoFFaNb6BfJIBPhe1d2qNv/WHZD1m0Mjn+PNqMnk7vbp69eby7eXk6mraZaCY4aNHu4EMtGwieiQyKyEDi49eWSwjWHfPJ6412qFjQq8mE/449vg9LqWvSdykm5BBYTShJr4r27ZWRQjt+LNjhQEPzeIzFsShsJwIUtGc80WBzh1cXBhTo9QQfEkB/CaMKg/uaN8s0DJA9H8ncGSVXrFgIR3+YesBWXcYoTsGTjB7pfsMZFkq9lfW8wMeS1k77DJ4PCmpvRVpreTEKMKGz7tTe308Dpw/x/sGgQB6+T/nkSR5986UOJiI4liwTwRaa+ygpEHn5GpAK7yRj6hXVEE+PQvgnkgy2xvZQ/67+F3+jN8PxO/Nz/r7gfgF+1SZNKnCBGFzMF5PxzaMl3HqCqN2P7kc2nU/Yzy3N9i21pApTN3l4/G2Mo66fNsaSx2cjrV33pFpxG3AgAzW0iq5SP2rh+HvqHn23EFF1EIWPlyckiG7kPeSY/xPFYoeRniHpSAjCtM0XnPWUTwpqgRVKKIbnAQmHMuoh65NIetwPITPAu7WwlgxmwtZlhadE2YZcOe+djyY9/gciWP815PJ62HqxpIwWjxVqqh2UMoJ67WOo6PL+rCHmOTj8cZ4O2rj3VHf0S8K05yZmFvVSLvpw/8dUPkg5ffKYkEJTlyHqSJehBCzJy9PLOTj8S6yw4Afe/Eg2D0XX+Gtok2oPtmqX3Fz7blo7+7ZlkPnlNG7owONW37ksc4O9fZTMxxCWm4qlGUo0LTd/D26ns9G8cIx5ev5TDzgRkhPFWpKvUV4p/RK7NREAuyyY45fMV8Y86Bwbz6l5Mz4bcQa8dZQDlOIUI4tP+GiMubhFguL9J3up1ocReUzIn9F6GEK0pMZrVDzaoylcAHiQtwazS0mLbKJnBPSoiiMXqqVt1j2D1a5FMKA1kg2Udebi7BMKb00wRdFNdPtH871fDaYr9IUvkFNkeTS2N1TGwkp5jU+iydJRVUrRyKszXZ/NdIWUpdpB+c2htZF+D+n8ck7amQYLimCH5D6lf10oz+luN3Pq//yNyAllPCZxm0tVdhyfdxDY4O/g/WUl/Se/VmTv++b4h1st2kj7To+jks/P75SOW7cZRoo/+DDi5s0ul6Kr9F7wM3pX4i1rD1fDM++HxPxnfflyTSiJneLlg60zsY8o+ym3YdfPkHXfQGCm8Ti sidebar_class_name: "get api-method" info_path: docs/api/pulsarr-api custom_edit_url: null @@ -47,7 +47,7 @@ Retrieve quality profiles from a Radarr instance diff --git a/docs/docs/api/get-radarr-root-folders.api.mdx b/docs/docs/api/get-radarr-root-folders.api.mdx index 66983d9d5..f0ca612c8 100644 --- a/docs/docs/api/get-radarr-root-folders.api.mdx +++ b/docs/docs/api/get-radarr-root-folders.api.mdx @@ -5,7 +5,7 @@ description: "Retrieve root folders from a Radarr instance" sidebar_label: "Get Radarr root folders" hide_title: true hide_table_of_contents: true -api: eJztV01v2zgQ/SvEnFpAju02RRHdgna3MLYLGEn3AzB8oKmxxUYiVXLoxDD034shJX8k6naDLvbUkxOS8+bxzXBmtAfboJOkrZkVkMMG6UYW0rkba+lXWxXoPGRAcuMhX0Dag2UGBXrldMOGkMMNktO4ReGsJbFOdmLtbC2kSEZCG0/SKIQMGulkjcTY+WIPXpVYS8j3QLsGIQdtCDfoIAN8UFXweou/a6PrUENOLmAGtXxI/15NJm+nV1ev3ly+vZxcXU3bDDQz+hLQ7SADI+uEmJzPCsjA4ZegHRYJrF3yim+s8eiZxKvJhH/Ob/ge1zJUJG66k5CBsobQEJ+VTVNpFWUcf/ZsMHAru/qMivj6jkUnndz5oBR6f3JwZW2F0kC8Syfad2F08Z8JmDQ7gHly2myYzUp6/MNVA3vtqaoLJtPBHI2WGcii0KyRrOYn3Ney8sgAJyl39CCdkxxITVjzevvYV6/fiVjnWN9xHAEv/+eYk6Tg39niVGcT6hU61lmdbxwDgM5ZN7hTo/dyM2CVQa3NRzQbKiGfPhHvSKRz2zs5Qv47/S5/6vcD+r35mX8/oF/0T6XtOljsMOwOxtvp2MX2M+aKMFofOppHt+37T+CSBvvGWbLKVm0+Hu9L66nN94111MLjdvcueLK1uI0YkMFWOi1XVdK2h+G/0XCJXUBJ1EAWf3zqnjGykPc75/ifShQ9jAgeC0FWKFvXwXDEUdxrKgWVKNI1OABMOKVQD11ZJau4PITPG1yhhXViNheyKBx6L+w64s5D5blpH/FZiXP815PJ62Hq1pGwRtyXWpUHKO2FC8akdtFmvexRk3w83tngRk06O+or+YWy9RMXc6dr6Xa9/M+Aygcpv9cOFXVw4jp2E/EiSsw3efnIQz4eH5QdBvzYbw+CLTn5VHCadjH7ZKN/w9114IRdLNmXR++1NYelE4tbfuApz07tjt0yLkI3BJUoi5ig3RT09+h6PhulA+eUr+czcYc7IQOVaKirKyJ4bTbiYCY6wDY75/gN98raO41H911Inji/TVgjnhSKYQoJyrPne1yV1t7donJIz7x+l4ujZPyEyF8JepiCDGRHGzQ8LmMhfIS4ELfWcHnphtyOnBfSoVDWrPUmOCz6B6t9J2FEqyW7qKrdRRygtFnbeBdNFdPtH871fDYYr8KqUKOhRHJt3eGpjYQU8wofxL0kVVbak4gToTseTbSFNEU3n3MZQ+cT/J/T9OQ91TI2lk7BD0j9OH866T+mtz/2qed+GnSBJHygcVNJHafgkGbOVNQXsJ3yhNezPivsy74QLmC/7ybPtuXl9EHAD67Qnot10TWQf+D+4qZrVS/Ft6jd4e7x58VWVoEPxqfet4b0tvuUZBrJkitEQydWT9o6oxy624dfPkHbfgXCyMOz +api: eJztV01v2zgQ/SvEnFpAju02RRHdgna3MLYLGEn3AzB8oKmxxUYiVXLoxDD034shJX8k6naDYvfUkxOS8+bxzXBmtAfboJOkrZkVkMMG6UYW0rkba+lXWxXoPGRAcuMhX0Dag2UGBXrldMOGkMMNktO4ReGsJbFOdmLtbC2kSEZCG0/SKIQMGulkjcTY+WIPXpVYS8j3QLsGIQdtCDfoIINaG12HGvJJBvigquD1Fn/vF8kFzKCWD+nfq8nk7fTq6tWby7eXk6uraZuBZnZfArodZGBkndATkVkBGTj8ErTDIoG1S17xjTUePRN6NZnwz/lt3+NahorETXcSMlDWEBris7JpKq2ipOPPng0GbmhXn1ERS+E4AKSTOx+UQu9PDq6srVAaiHfpBPwujC7+EzGTfgdgT06bDTNbSY9/uGpgrz1VeMHEOpij0TIDWRSa9ZLV/OQea1l5ZICTVDx6kM5JDqomrHm9feyr1/JEuHOs7ziOgJf/c/xJUvDvbHGqswn1Ch3rrM43jgFA56wb3KnRe7kZsIrp8BHNhkrIp0/EOxLp3PZOjpD/Tr/Ln/r9gH5vfubfD+gX/VNpu84WOw+7g/F2OnaxLY25IozWh07n0W37vhS4pMG+cZasslWbj8f70npq831jHbXwuA2+C55sLW4jBmSwlU7LVZW07WH4bzRcYhdQEjWQxR+fumqMLOT9zjn+pxJFDyOCx0KQFcrWdTAccRT3mkpBJYp0DQ4AE04p1ENXVskqLg/h8wZXaGGdmM2FLAqH3gu7jrjzUHlu5kd8VuIc//Vk8nqYunUkrBH3pVblAUp74YIxqV20WS971CQfj3c2uFGTzo76Sn6hbP3ExdzpWrpdL/8zoPJByu+1Q0UdnLiO3US8iBLzTV4+8pCPxwdlhwE/9tuDYEtOPhWcpl3MPtno33B3HThhF0v25dF7bc1h6cTilh94yrNTu2O3jIvQDUQlyiImaDcR/T26ns9G6cA55ev5TNzhTshAJRrq6ooIXpuNOJiJDrDNzjl+w72y9k7j0X0XkifObxPWiCeFYphCgvLs+R5XpbV3t6gc0jOv3+XiKBk/IfJXgh6mIAPZ0QYNj9FYCB8hLsStNVxeuuG3I+eFdCiUNWu9CQ6L/sFq30kY0WrJLqpqdxEHKG3WNt5FU8V0+4dzPZ8NxquwKtRoKJFcW3d4aiMhxbzCB3EvSZWV9iTidOiORxNtIU3Rze1cxtD5BP/nND15T7WMjaVT8ANSP+affgE8prc/9qnnfjJ0gSR8oHFTSR0n4pBmzlTUF7Cd8oTXsz4r7Mu+EC5gv+8mz7bl5fRxwA+u0J6LddE1kH/g/uKma1Uvxbeo3eHu8afGVlaBD8an3reG9Lb7lGQayZIrREMnVk/aOqMcutuHXz5B234FMNXLXw== sidebar_class_name: "get api-method" info_path: docs/api/pulsarr-api custom_edit_url: null @@ -47,7 +47,7 @@ Retrieve root folders from a Radarr instance @@ -62,7 +62,7 @@ Retrieve root folders from a Radarr instance diff --git a/docs/docs/api/get-radarr-tags.api.mdx b/docs/docs/api/get-radarr-tags.api.mdx index 3dcce77aa..7a61b3b65 100644 --- a/docs/docs/api/get-radarr-tags.api.mdx +++ b/docs/docs/api/get-radarr-tags.api.mdx @@ -5,7 +5,7 @@ description: "Retrieve tags from a Radarr instance" sidebar_label: "Get Radarr tags" hide_title: true hide_table_of_contents: true -api: eJztV01v2zgQ/SvEnFpAju02RRHdgnZRGNsFjCT7ARg50NLYYiORKjl0Yhj678WQlD8SdZNugT315IQcvnnzOJwZ7cC0aCUpo2cl5LBGupKltPZGrh1kQPyTLyAuwm0GJbrCqpZPQA5XSFbhBgUbipU1jZAiGgulHUldIGTQSisbJLQMtgNXVNhIyHdA2xYhB6UJ12ghA3woau/UBv9QWjW+gZysxwwa+RD/vZhM3k8vLt68O39/Prm4mHYZKGby1aPdQgZaNhExOp+VkIHFr15ZLCNYd8srrjXaoWMSbyYT/jmN7COupK9JXCVLyKAwmlAT28q2rVURdBt/cXxgICqz/IIFcfiWVSYV3TlfFOjckeHSmBqlhhBLEu1ZGFUe2WjfLNEyQIx/v+HIKr3mjaV0+KetB/a6Y4UWDJxgDoduM5BlqTheWc+PeKxk7bDrE2UPLa2VfBuKsHH/OZZaLvGlhKPtMzwfn+yv4kj3FMqzQF0G5/9z3pAk7z6YEgfFKk43DheP1ho7uNOgc3I9cCqDRunPqNdUQT59otqBSHLbOzlAvky/81/6/YR+737l30/oF/xTZVLbC12K3cF4Mx3b0MLGFLugQ7vpe5fnEgq71hoyham7fDzeVcZRl+9aY6mDxy3yg3dkGnEdMCCDjbRKLuuoaQ/Df6Pm/raAiqiFLPy42HHDjULe75zi31QoehjhHZaCjChM03jNN43iXlElqEIRw2DhmXBMnR66NoWsw/IQPm9wRxDGitlcyLK06Jwwq4A797Xjhn/AZyVO8d9OJm+HqRtLwmhxX6mi2kMpJ6zXOlb7LutlD5rk4/HWeDtqo+2oL91nhWmeuJhb1Ui77eX/Aah8kPJHZbGgBCcuQ/sQr4LEHMnrRx7y8Xiv7DDg5357EOyWk6/wVtE2ZJ9s1e+4vfScqItb9uXQOWX0funoxDU/7Jhnx+cOTTosQhqgKpRlSNA0Qf0zupzPRtHglPLlfCbucCukpwo1pXoivFN6LfbHRALsslOO33FfGHOn8OA+XckT59cRa8STSTlMIUI59nyPy8qYu2ssLNIPhp9ycRQPPyHyd4QepiA9mdEaNc/WWAoXIM7EtdFcVtKAnMg5IS2KwuiVWnuLZf9glUsSBrRGsou63p6F+UfplQmxKKqZbv9wLuezwfsqTeEb1BRJrozdP7WRkGJe44O4l1RUtXIkwjhuD6aRtpC6TLM9lzG0LsL/NY1P3lEjQ0NJCn5C6j8FUh09obU79KWXfkakiyN8oHFbSxUmZh9n2li8F7CZ8sDfs+xnuVjwFrDbpYm263g5fjTwwyqV46JcpgbxL1xfXaVW9Fp8j9Idbh9/gmxk7dkwPOm+BcQ33Kce04gnuRK0dHTqSdtmlH33+vTbDXTdN1VWyPg= +api: eJztV01v2zgQ/SvEnFpAju02RRHdgnZRGNsFjCT7ARg50NLYYiORKjl0Yhj678WQlD8SdZNugT315IQcvnnzOJwZ7cC0aCUpo2cl5LBGupKltPZGrh1kQPyTLyAuwm0GJbrCqpZPQA5XSFbhBgUbipU1jZAiGgulHUldIGTQSisbJLQMtgNXVNhIyHdA2xYhB6UJ12ghg0Zp1fgG8kkG+FDU3qkN/tEvkvWYQSMf4r8Xk8n76cXFm3fn788nFxfTLgPFrL56tFvIQMsmokcisxIysPjVK4tlBOtuecW1Rjt0TOjNZMI/p1F+xJX0NYmrZAkZFEYTamJb2ba1KoKG4y+ODwxEaJZfsCCWwrLipKI754sCnTsyXBpTo9QQYkkCPgujyiMb7ZslWgaI8e83HFml17yxlA7/tPXAXnes0IKBE8zh0G0GsiwVxyvr+RGPlawddn3S7KGltZJvQxE27j/HUsslvpRwtH2G5+OT/VUc6Z5CeRaoy+D8f84bkuTdB1PioFjF6cbh4tFaYwd3GnROrgdOhTf5GfWaKsinT1Q7EElueycHyJfpd/5Lv5/Q792v/PsJ/YJ/qkxqgaFjsTsYb6ZjG9rZmGJHdGg3fR/zXEJh11pDpjB1l4/Hu8o46vJdayx18LhdfvCOTCOuAwZksJFWyWUdNe1h+G/U3N8WUBG1kIUfF7tvuFHI+51T/JsKRQ8jvMNSkBGFaRqv+aZR3CuqBFUoYhgsPBOOqdND16aQdVgewucN7gjCWDGbC1mWFp0TZhVw57523PwP+KzEKf7byeTtMHVjSRgt7itVVHso5YT1Wsdq32W97EGTfDzeGm9HbbQd9aX7rDDNExdzqxppt738PwCVD1L+qCwWlODEZWgf4lWQmCN5/chDPh7vlR0G/NxvD4LdcvIV3irahuyTrfodt5eeE3Vxy74cOqeM3i8dnbjmhx3z7PjcoUmHRUgDVIWyDAmaJqh/Rpfz2SganFK+nM/EHW6F9FShplRPhHdKr8X+mEiAXXbK8TvuC2PuFB7cpyt54vw6Yo14MimHKUQox57vcVkZc3eNhUX6wfBTLo7i4SdE/o7QwxSkJzNao+Y5G0vhAsSZuDaay0oalhM5J6RFURi9UmtvsewfrHJJwoDWSHZR19uzMP8ovTIhFkU10+0fzuV8NnhfpSl8g5oiyZWx+6c2ElLMa3wQ95KKqlaORBjN7cE00hZSl2nO5zKG1kX4v6bxyTtqZGgoScFPSP1nQaqjJ7R2h7700k+KdHGEDzRua6nCxOzjTBuL9wI2Ux74e5b9LBcL3gJ2uzTRdh0vx48GflilclyUy9Qg/oXrq6vUil6L71G6w+3jT5CNrD0bhifdt4D4hvvUYxrxJFeClo5OPWnbjLLvXp9+u4Gu+waO8szO sidebar_class_name: "get api-method" info_path: docs/api/pulsarr-api custom_edit_url: null @@ -47,7 +47,7 @@ Retrieve tags from a Radarr instance diff --git a/docs/docs/api/get-schedule-by-name.api.mdx b/docs/docs/api/get-schedule-by-name.api.mdx index 6df129b97..a4e3f96f6 100644 --- a/docs/docs/api/get-schedule-by-name.api.mdx +++ b/docs/docs/api/get-schedule-by-name.api.mdx @@ -5,7 +5,7 @@ description: "Retrieve a specific job schedule by its name" sidebar_label: "Get job schedule by name" hide_title: true hide_table_of_contents: true -api: eJztWF1v2zYU/SsEn1pArp2myQa9Ze1QBOs2I+k+gMAoaOraYkORKnnpRDD034dLSracKAlSFHtpnyyL5LmH537wUltua3AClTXnBc/5GvBSllAEDb80f4gKeMZRrD3Pr3g/4Pgi4wV46VRNC3nOLwCdgg0wwXwNUq2UZJ/tkvluCVs2TKFnJiHWwokKEBzhbjnNqgTPtxybGnjOPTpl1rzNuCL0WmDJMx4X57zDcPAlKAcFz9EFaBf0xtfWePCE9Ho2o59Dmu9gJYJGdtHN5BmX1iAYpLmirrWSUYvpZ08LhtSEaf5cRbodSbv8DBJpN440RJUMq2KwEROqJTjaSCI/ssP04u5AxsGEilRXBsFthOaLNrJdqfVg+gMkCtH4wSzCWIMj1Fupg1cb+F0ZVZEFUi/jlbhNf49PT9qMlza4rwT4+afTWZvxSpmA8JUYJ69PTmeE4kFaU3ztVo5Ojk9nEccFc15VUCiBoJsB3NJaDcLwts24KApFzhd6PhBzJbSHlvwhlhqKsbUZ18LjJxdiyJigNU3t+TzuKVQPxIVHgcE/FhnSVrUGhIJnfCWUjg81mIKmUbSAc9aNgoNHVQkc3047TK6rxHDHZ/GUTgZuf0iRpJAOCPiTwFHLoS4eHr5jWRV9AexU3NWCfWQOwnDghgMaB0af4v9/VzrprHlWlYPb2oH3KpXqu7CVMh/ArLHk+dFdOQcrn/Tij8T/kfjfVeIv2pZYvJm9+bYd1ONBkLR9awsYrSrycGDgxgcdXIH3Yj1ach6rDQMindneyB7yCQWjfiffugP9rvSL9rG03aUkXhrIHJ9ujqb9tcLtnvx0S0nSUpKC2/Q3i+A0z/m2dhattLrNp9NtaT22+ba2Dmn6oX/eBo+2YpcRg2d8I5yiJIsS9zDx7OkKUIlY8yz++HQvig7meT9yiP+xBNbDsOChYGiZtFUVDDke2I3CkmEJLG2Dx2bcY4qkHlpbKXR8PYZPAyQGs46dz5koCjrrmF1F3HnQXjg3wCclDvGPZ7PjcerWIbOG3ZRKljso5ZkLxqTyRX1Dkj1qkk+njQ1uUqe5E2U8CiPhlbTVPRNzpyrhml7+Z0Dlo5TfKQcSOzh2JiXp8CJKTDt5ecdCPp3ulB0H/NAPj4It4oUlOIVNjD5Rq9+gOQsUt1cLsuVT07F7NVhBt+sqxdlw3S730kveXYlLEEUM0O5S/O/kbH4+SRMOKZ/Nz9k1NEwELMFgV15Y8Mqs2W4Z6wDjnWvA8QHz0tprBXvznUvuGb9MWJOloFAfpZCgPFm+gWVp7fUlSAf4zO13sThJi+8R+SdBj1MQAe1kDYY+hEDBfIR4xS6tEc5NL0RBQd6R80w4YOkIDg6KPmGV7ySMaJUgE1o3r+KBrszKpkYHqSfifeKczc9H/VVYGSowmEiurNul2oQJNtdwy24EylIrjyzeiN1+aqLNhClYok5lDFxqk/nfRynlPVYitWpJwfeA977YdG3HAb/t/rx67lefzpMItzittVCxgY7Zt+2K+xXfHFEF331o2j97nvE8Ai36injFt1sKrL+cblt6/SWAo8xb7Ot2zMNC+a6Fj8fLIzt6cdEdZC/ZQ4T7eDRNPB50oH8849fQ9B+nWmo8uxAl+2mIKkaNg0X3TnsqB7tD7/2vH3nb/ge6q9GK +api: eJztWNtu2zgQ/RVinlpArp2myS70lm0XRbDdXSPpXoDAKGhpbLGRSJUcOhEM/ftiSMmWEyVBgGBf2ifLvJw5PJwZDrkFU6OVpIw+zyGFNdJlVmDuS/yl+UNWCAmQXDtIr6DvsLBIIEeXWVXzREjhAskq3KCQwtWYqZXKxFezFK6bIpaNUOSEjoi1tLJCQsu4W+BRlYR0C9TUCCk4skqvoU1AMXotqYAEwuQUOgyL37yymENK1mO74BZXG+3QMdLb2Yx/Dml+wJX0JYmLbiQkkBlNqInHyrouVRa0mH51PGFITermz1Wg25E0y6+YEa/GsoakomGVDxaifbVEywuJ5EdWGBvudiSA2lesutKEdiNLWLSB7UqtB8MfIJHLxg1GMcYaLSRQKa0qBp4lgLdZ6Z3a4O99IyuZQCVv49/j05M2gcJ4+wJgP/90OmvDHE/4Angnb09OZ4zoMDM6f4nlHp0cn84CpvX6vKowV5KwbAbQS2NKlBraNgGZ54qdRZbzgfgrWTpsef/kssR8bG4CpXT0xfrgYtqXJQ/t+Ty+s6Qe8CNHkrx7zJMyU9UlEuaQwEqqMnzUqHMext6F1ho7Co6OVCVpfDntMBivIsMdn8VTOmm8/SFFlCKzyMBfJI1a9nX+cPcdyyrvE2an4i537D1z4IaDbTigcWD0Kf7/d2bMrNHPyop4W1t0TsXUfhe2UvoT6jUVkB7dlXMw88ld/BH4PwL/uwr8Rdsyi3ezdy9bcT3uBFHb9ybH0aySHXYMtvHBDa7QObkeTTmP5YYBkc5sb2QP+YSCQb+Tl65Yvyv9gn0qTHeJCZcMNgfTzdG0v4bY3ZebbjlIWg5StJv+JuJtCSlsa2vIZKZs0+l0WxhHbbqtjSUefrg/770jU4nLgAEJbKRVHGRB4h4mnD1dAiqIakjCj4v3qLDBkPY9h/ifCxQ9jPAOc0FGZKaqvOaNR3GjqBBUoIjLgFCwO4qe1EOXJpNlaB7D5w4WQxgrzudC5jmfdcKsAu7cl05aO8BnJQ7xj2ez43HqxpIwWtwUKit2UMoJ67WO6Yvrhih70CSdThvj7aSOYydKO5I6wzeZqe6ZmFtVSdv08j8DKh2l/EFZzKiDE2dZxjq8ChLzSl7fsZBOpztlxwE/9d2jYItwefFWURO8T9bqN2zOPPvt1YJtuVh07JoGM/g2XkU/G87bxV5shO4KXaDMg4N2l+h/J2fz80kccEj5bH4urrER0lOBmrr0IrxTei1200QHGO5fA44PmM+MuVa4N99tyT3jlxFrspTs6qMUIpRjyze4LIy5vsTMIj1z+Z0vTuLke0T+idDjFKQnM1mj5ocTzIULEG/EpdHS2umFzNnJO3JOSIsiHsHeYt4HrHKdhAGtkmyiLJs34UBXemVioUNcE0EfOGfz89H9yk3mK9QUSa6M3YXaREgxL/FW3EjKilI5EuF2bPdDI20hdS4idU5jaGOZDH8fxZB3VMlYqkUFPyLde+Hpyo4Dftv9efXcV6JuJwlvaVqXUoUCOkTftkvuV7A54gy+e5jafztIIA1Aiz4jXsF2y471ly3blpu/ebQceYt93g5xmCvXlfDheHlkRa8uuoPstXiIcO+PugnHQ+n5HyRwjU3/mNVy4dm5KNuPXZwxahpMunfaczrYHXoff/0MbfsfMF/g4g== sidebar_class_name: "get api-method" info_path: docs/api/pulsarr-api custom_edit_url: null @@ -62,7 +62,7 @@ Retrieve a specific job schedule by its name diff --git a/docs/docs/api/get-sonarr-quality-profiles.api.mdx b/docs/docs/api/get-sonarr-quality-profiles.api.mdx index 2528fea00..2ca6b78ab 100644 --- a/docs/docs/api/get-sonarr-quality-profiles.api.mdx +++ b/docs/docs/api/get-sonarr-quality-profiles.api.mdx @@ -5,7 +5,7 @@ description: "Retrieve quality profiles from a Sonarr instance" sidebar_label: "Get Sonarr quality profiles" hide_title: true hide_table_of_contents: true -api: eJztV0uP2zgM/iuCTi3gjJN2iiK+Be2iCLYLZGe6DyDIQbGZWB1b8khUZoLA/72gJMd5uNvtFthTT0lE8ePHh0jmwHUDRqDUal7wjG8B77USxvzuRCVxvzB6IyuwPOEotpZnSx7kfJXwAmxuZEPKPON3gEbCDthjUGVN1GUbo2smWFBkUlkUKgee8EYYUQOCIeADt3kJteDZgeO+AZ5xqRC2YHjC4TmvnJU7+E0qWbuaZ2gcJLwWz+HndDx+O5lOX725fXs7nk4nbcIlsXp0YPY84UrUATEYnxc84QYenTRQBLB2RSe20cqCJRKvxmP6OPfyPWyEq5DdxZs84blWCArprmiaSuY+nOlnSwoDXun1Z8iR3DcUfJTBnHV5DtaeXFxrXYFQ3PsSg/ZNGFmc3FGuXoMhgOD/UWDRSLUlwVpY+MNUA7L2NEJLAo4wvdIq4aIoJPkrqsUJj42oLLQJf7woo96KMEZQYiRCTeftpb0uHifOX+N9g4AHvf2f84gCnX2nCxhMRH4u6BMBxmgzKKnBWrEd0Ep4LdVHUFsseTa5CmBPJJrtjPSQ/y5+tz/j9wPxe/Oz/n4gft4+ljpOJz81yBxPd5PU+pGSxq4wavppZcHsurniqL3xQ2M06lxXbZamh1JbbLNDow22/HKUvXMWdc3uPQZP+E4YKdaxf3Uw9B0UzZ4lLxEbnvgPGyajzy7POsk5/qcSWAfDnIWCoWa5rmunKOvAniSWDEtgwQ1KAhEOZdRBVzoXlT8ewicBdWumDZsvmCgKA9YyvfG4C1dZGsY9PkXiHP/1ePx6mLo2yLRiT6XMyyOUtMw4pcLoaJMu7D4mWZrutTOjJtwddR39Jtf1lYmFkbUw+y783wGVDVJ+Lw3kGOHYzE8V9sKHmDx5eWEhS9NjZIcBP3biQbAVFV/ujMS9rz7RyF9hP3NUtMsV2bJgrdTqeHSicU+PPNTZqV4/Nf0hj8tNCaLwBRq3m79Hs8V8FC6cU54t5uwB9kw4LEFh7C3MWam27KjGImCbnHP8ivlc6wcJvfmYkivj9wFrRFtDMUwhQFmy/ATrUuuHe8gN4He6H2txFJSviPwVoIcpCId6tAVF6zAUzHqIm7i1pneioCKP5CwTBliu1UZunYGie7DSxhB6tFqQiara3/hlSqqN9r5IrIhu93Bmi/lgvgqduxoUBpIbbY5PbcQEW1TwzJ4E5mUlLTK/Kpv+aly2hSpYoE5tDIwN8H9OwpO3WAs/XGIEPwB2mpdb/CXFQz+v/svqHxOK8IxpUwnpt1wX9tDQ4Jd8N6FGHv5uHFe/vsmvuqa45IdD3Ejblo7D0k+Pr5CWGncRB8o/+PDiLo6ul+xr9B5gf/kXYicqRxf9s+/GRHjnXXkSjaBJ3aLBE62rMU8ox2n34ZdPvG2/AMQFwbs= +api: eJztV0uP2zgM/isCTy3gjJN2iiK+Be2iCLYLZGe6DyDIQbGZWB1b8uiRmSDwfy8oyXEe7na7BfbUU2ZE8ePHh0j6AKpBza1Qcl5ABlu090pyrX93vBJ2v9BqIyo0kIDlWwPZEoIcVgkUaHItGlKGDO7QaoE7ZI9BlTVRl220qhlnQZEJaSyXOUICDde8RouagA9g8hJrDtkB7L5ByEBIi1vUkEAtpKhdDdk4AXzOK2fEDn/rDq12mEDNn8O/0/H47WQ6ffXm9u3teDqdtAkIYvjoUO8hAcnrgB6IzAtIQOOjExqLANau6MQ0Sho0ROjVeEw/5x6/xw13lWV38SYkkCtpUVq6y5umErkPbfrZkMKAh2r9GXNLodCUCCuCOePyHI05ubhWqkIuwfsSA/hNGFGc3JGuXqMmgOD/UWCsFnJLgjU3+IeuBmTtaYSWBBxheqVVArwoBPnLq8UJjw2vDLYJPF6UVG+Fa80pMcJiTeftpb0uHifOX+N9g4AHvf2f82i5deadKnAwEfm5oE8Eaq30oKRGY/h2QMu/kY8ot7aEbHIVwJ5INNsZ6SH/Xfxuf8bvB+L35mf9/UD8vH1bqjip/AQhc5DuJqnx4yWNXWHU9JPLoN51M8ZRe4NDo5VVuaraLE0PpTK2zQ6N0raFy7H2zhmranbvMSCBHdeCr2P/6mDob5Q0e5ZQWttA4n9MmJI+u5B1knP8TyWyDoY5gwWziuWqrp2krCN7ErZktkQW3KAkEOFQRh10pXJe+eMhfBJQt2ZKs/mC8aLQaAxTG4+7cJWhwdzjUyTO8V+Px6+HqSttmZLsqRR5eYQShmknZRgdbdKF3cckS9O9cnrUhLujrqPf5Kq+MrHQouZ634X/O6CyQcrvhcbcRjg281OFvfAhJk9eXljI0vQY2WHAj514EGxFxZc7LezeVx9vxK+4nzkq2uWKbBk0Rih5PDrRuKdHHursVK+fmv4Q4nJTIi98gcbt5u/RbDEfhQvnlGeLOXvAPePOliht7C3MGSG37KjGImCbnHP8ivlcqQeBvfmYkivj9wFrRFtDMUwhQBmy/ITrUqmHe8w12u90P9biKChfEfkrQA9T4M6q0RYlrcZYMOMhbuIGm97xgoo8kjOMa2S5khuxdRqL7sEKE0Po0WpOJqpqf+OXKSE3yvsibEV0u4czW8wH81Wo3NUobSC5Ufr41EaMs0WFz+yJ27yshLHMr826vxoXby4LFqhTG0NtAvyfk/Dkja25Hy4xgh/QdpqXG/0lxUM/r/7LZ0BMqMVnmzYVF37LdWEPDQ1+CbsJNfLw6XFc/fomv+qa4hIOh7iRti0dh6WfHl8hDDXuIg6Uf/DhxV0cXS/Z1+g94P7yE2LHK0cX/bPvxkR45115Eo2gSd2isSdaV2OeUI7T7sMvn6BtvwB9dcWR sidebar_class_name: "get api-method" info_path: docs/api/pulsarr-api custom_edit_url: null @@ -47,7 +47,7 @@ Retrieve quality profiles from a Sonarr instance diff --git a/docs/docs/api/get-sonarr-root-folders.api.mdx b/docs/docs/api/get-sonarr-root-folders.api.mdx index 258b5c969..f14d9e544 100644 --- a/docs/docs/api/get-sonarr-root-folders.api.mdx +++ b/docs/docs/api/get-sonarr-root-folders.api.mdx @@ -5,7 +5,7 @@ description: "Retrieve root folders from a Sonarr instance" sidebar_label: "Get Sonarr root folders" hide_title: true hide_table_of_contents: true -api: eJztV01v2zgQ/SvEnFpAjuw2RRHdjHa3MLYLGEn3AzB8oKWxxUYiVXLoxDD034shJX/E6naDLvbUkxOS8+bxzXBmtAfToJWkjJ4VkMEG6c5oae2tMfSrqQq0DhIguXGQLSDuwTKBAl1uVcOGkMEtklW4RWGNIbGOdmJtTS2kiEZCaUdS5wgJNNLKGomxs8UeXF5iLSHbA+0ahAyUJtyghQTwMa+8U1v8XWlV+xoysh4TqOVj/PdmPH47ubl59eb67fX45mbSJqCY0RePdgcJaFlHxOh8VkACFr94ZbGIYO2SV1xjtEPHJF6Nx/xzfsP3uJa+InHbnYQEcqMJNfFZ2TSVyoOM6WfHBgO3MqvPmBNf37LopKI75/McnTs5uDKmQqkh3KUT7bswqvjPBIyaHcAcWaU3zGYlHf5hq4G99lTVBZPpYI5GywRkUSjWSFbzE+5rWTlkgJOUO3qQ1koOpCKseb196qvX70Ssc6zvOA6A1/9zzEmSd+9Mcaqz9vUKLeucn28cA4DWGju4U6NzcjNglUCt9EfUGyohm1yIdyTSue2dHCH/nX7XP/X7Af3e/My/H9Av+KfSdB0sdBh2B+l2krrQflKuCKP1oaM5tNu+/3guabBvrCGTm6rN0nRfGkdttm+MpRaetrt33pGpxV3AgAS20iq5qqK2PQz/jZpL7AJKogaS8ONi9wyRhazfOcf/VKLoYYR3WAgyIjd17TVHHMWDolJQiSJegwPAhGMK9dCVyWUVlofweYMrtDBWzOZCFoVF54RZB9y5rxw37SM+K3GO/3o8fj1M3VgSRouHUuXlAUo5Yb3WsV20SS970CRL053xdtTEs6O+kl/lpr5wMbeqlnbXy/8MqGyQ8ntlMacOTkxDNxEvgsR8k5dPPGRpelB2GPBjvz0ItuTky71VtAvZJxv1G+6mnhN2sWRfDp1TRh+WTizu+IHHPDu1O3bLsAjdEFSiLEKCdlPQ36PpfDaKB84pT+czcY87IT2VqKmrK8I7pTfiYCY6wDY55/gN97kx9wqP7ruQXDi/i1gjnhSKYQoRyrHnB1yVxtzfYW6Rnnn9LhdH0fiCyF8RepiC9GRGG9Q8LmMhXIC46qbb9FYWnOQdOSekRZEbvVYbb7HoH6xynYQBrZbsoqp2V2GAUnptwl0UVUy3fzjT+WwwXoXJfY2aIsm1sYenNhJSzCt8FA+S8rJSjkSYCO3xaDeUS12ISJ3LGFoX4f+cxCfvqJahsXQKfkDqLU8n/af09sc+9dxPgy6QhI+UNpVUYQr2ceaMRX0B2wkX8PgpEke9Q2Ff9oVwAft9N3m2LS/HDwJ+cIVyXKyLroH8A/cXt12reim+Re0ed08/L7ay8nwwPPW+NcS33ack04iWXCEaOrG6aOuMcuhuH375BG37Fb2ixGI= +api: eJztV01v2zgQ/SvEnFpAjuw2RRHdjHa3MLYLGEn3AzB8oKWxxUYiVXLoxDD034shJX/E6naDYvfUkxOS8+bxzXBmtAfToJWkjJ4VkMEG6c5oae2tMfSrqQq0DhIguXGQLSDuwTKBAl1uVcOGkMEtklW4RWGNIbGOdmJtTS2kiEZCaUdS5wgJNNLKGomxs8UeXF5iLSHbA+0ahAyUJtyghQRqpVXta8jGCeBjXnmntvh7v0jWYwK1fIz/3ozHbyc3N6/eXL+9Ht/cTNoEFLP74tHuIAEt64geicwKSMDiF68sFhGsXfKKa4x26JjQq/GYf85v+x7X0lckbruTkEBuNKEmPiubplJ5kDT97Nhg4IZm9RlzYiksB4BUdOd8nqNzJwdXxlQoNYS7dAJ+F0YV/4mYUb8DsCOr9IaZraTDP2w1sNeeKrxgYh3M0WiZgCwKxXrJan5yj7WsHDLASSoePUhrJQdVEda83j711Wt5Itw51nccB8Dr/zn+JMm7d6Y41Vn7eoWWdc7PN44BQGuNHdyp0Tm5GbAK6fAR9YZKyCYX4h2JdG57J0fIf6ff9U/9fkC/Nz/z7wf0C/6pNF1nC52H3UG6naQutKWUK8Jofeh0Du2270ueSxrsG2vI5KZqszTdl8ZRm+0bY6mFp23wnXdkanEXMCCBrbRKrqqobQ/Df6PmEruAkqiBJPy42FVDZCHrd87xP5UoehjhHRaCjMhNXXvNEUfxoKgUVKKI1+AAMOGYQj10ZXJZheUhfN7gCi2MFbO5kEVh0Tlh1gF37ivHzfyIz0qc478ej18PUzeWhNHioVR5eYBSTlivdWwXbdLLHjTJ0nRnvB018eyor+RXuakvXMytqqXd9fI/AyobpPxeWcypgxPT0E3EiyAx3+TlEw9Zmh6UHQb82G8Pgi05+XJvFe1C9slG/Ya7qeeEXSzZl0PnlNGHpROLO37gMc9O7Y7dMixCNxCVKIuQoN1E9PdoOp+N4oFzytP5TNzjTkhPJWrq6orwTumNOJiJDrBNzjl+w31uzL3Co/suJBfO7yLWiCeFYphChHLs+QFXpTH3d5hbpGdev8vFUTS+IPJXhB6mID2Z0QY1j9FYCBcgrrqpN72VBSd5R84JaVHkRq/Vxlss+gerXCdhQKslu6iq3VUYoJRem3AXRRXT7R/OdD4bjFdhcl+jpkhybezhqY2EFPMKH8WDpLyslCMRpkN7PNoN61IXIlLnMobWRfg/J/HJO6plaCydgh+QesvTL4Cn9PbHPvXcT4YukISPlDaVVGEi9nHmjEV9AdsJF/D4iRJHvUNhX/aFcAH7fTd5ti0vx48DfnCFclysi66B/AP3F7ddq3opvkXtHndPPzW2svJ8MDz1vjXEt92nJNOIllwhGjqxumjrjHLobh9++QRt+xUyt8wO sidebar_class_name: "get api-method" info_path: docs/api/pulsarr-api custom_edit_url: null @@ -47,7 +47,7 @@ Retrieve root folders from a Sonarr instance @@ -62,7 +62,7 @@ Retrieve root folders from a Sonarr instance diff --git a/docs/docs/api/get-sonarr-shows.api.mdx b/docs/docs/api/get-sonarr-shows.api.mdx index 2e849cdad..efddd1881 100644 --- a/docs/docs/api/get-sonarr-shows.api.mdx +++ b/docs/docs/api/get-sonarr-shows.api.mdx @@ -5,7 +5,7 @@ description: "Retrieve all Pulsarr-tracked Sonarr shows with their rolling monit sidebar_label: "Get Sonarr shows with enrollment status" hide_title: true hide_table_of_contents: true -api: eJy9Vktv2zgQ/ivEnHYBObbbLIroFrSLwtgWMOLsAwhyYKSxxYYiVXLoxDD034shJdmOFWR72D3JJjnfvL557ME26CQpaxYl5LBBWlkjnVtV9slDBiQ3HvI7WKH3yhrx1RpF1imzgfsMSvSFUw2LQw43SE7hFoXUWiyD9tK5CTlZPGIpEqzwjCueFFWCKlROOKu1MhtRD8ACDR/WaEh4khTYjkY6WSOhY2v24IsKawn5HmjXIOSgDOEGHWSAz4UOXm3xqzKqDjXk5AJmUMvn9PdqNvswv7p699vlh8vZ1dW8zUCx+d8Duh1kYGSdED1JU+CihPY+A4e+scajZ6XvZjP+nLr/CdcyaBI33UvIoLCG0BC/lU2jVREjPf3mWWDEC/vwDQtidx3nhVRS50NRoPdHDx+s1SgNtBnEiB5dSecku6EIa/829pOkotLKE+d/eGxC/YAuwse8LQ7BeP3RCp1C/8oTUqTx6MZTJFGbwSao8t840Eu0GXSkYZImdSZoLR9YQUr2mfoDvW7j1asinZYM0DBZ7qBR2tJNUggZrJXztELprTkcSq3T0fL48X00Fb8H5bBkqONYj0T2LI591PoYvXT8zK37DGRZKiaZ1MujPK+l9ti+tKfnVU+iN8XbDC7/Z+rHBvDRljjKquL04kAqdM660ZsavZebEakMamW+oNlQBfn8LFYHQzq1vZID5Nvxi/qpsl2vjY2N1cF0O5/61GMnh6ROEyEmvuvGHt22b4HBachh3zhLtrC6zafTfWU9tfm+sY5aeNmePwZPthariAEZbKVTXAAxzj0M/+6ZXxE1kMWPT90+Zhny/uYU/7ZC0cOI4LEUZEVh6zoYzj4OXV8kNzgZbHCiUw+tbSF1PB7D5wvuz8I6sVgKWZYOvRd2HXG7mXOEz5E4xX8/m70fN906EtaIp0oV1QClvHDBmNR42qwPe4xJPp3ubHCTpht1/cS4KGx9pmLpVC3drg//T0DloyZ/Ug4L6uDEdSxk8UsMMXvy6wsN+XQ6RHYc8Et/PQp2z+QrglO0i+yTjfoDd9eByXt3z7o69g5HRxIrLvbEs2O5Q8ePh9DN4QplGQnaDeJ/JtfLxSQ9ODX5erkQj7gTMlCFhroeI4LnLWIQEx0gz6ljG19RX1j7qPCgvkvJmfJuI5o8SKb6qAkJyrPmJ3yorH1cYeGQftL9fpVKwmeG/J2gx02Qgexkg4aXPCyFjxAX3TY2vZElk7wzzgvpUBTWrNUmOCz7glW+C2FEqyWr0Hp3EUexMmsbfUnzHfrCuV4uRvNV2iLwapeMXFs3lNpESLHU+CyGMSniVucOT7slUppSJNO5jaHzCf6veSp5T7WMQ6aL4GekkfVzbMs8MXd/mGH/6WrbsYDwmaaNlipudbFy9910uIPtHAb2Hs2HYWWYDAM8ddQ72O+ZmH863bZ8nJZbrtxSee76ZTeVMnjE3em6y6NBB7Yp1n0/J1Kh9/xkqCTJ7aKhI6mzec8ow9j7/PsttO0PC0di4Q== +api: eJy9Vktv2zgQ/ivEnHYBObbbLIroFrSLwtgWMOLsAwhyYKSxxYYiVXLoxDD034shJdmOFWR72D3JJjnfvL557ME26CQpaxYl5LBBWlkjnVtV9slDBiQ3HvI7WKH3yhrx1RpF1imzgfsMSvSFUw2LQw43SE7hFoXUWiyD9tK5CTlZPGIpEqzwjCueFFWCKlROOKu1MhtRD8ACDR/WaEh4khTYjkY6WSOhY2v24IsKawn5HmjXIOSgDOEGHWRQK6PqUEM+ywCfCx282uLX/pBcwAxq+Zz+Xs1mH+ZXV+9+u/xwObu6mrcZKHble0C3gwyMrBO6J2kKXJTQ3mfg0DfWePRswLvZjD+nofiEaxk0iZvuJWRQWENoiN/KptGqiFGffvMsMOKRffiGBbHrjnNEKqnzoSjQ+6OHD9ZqlAbaDGJ0j66kc5LdUIS1fxv7SVJRaeWJuTA8NqF+QBfhYw4Xh2C8/miFTqF/5Qkp0nh04ykSqs1gE1T5bxzoJdoMOgIxYZM6E7SWD6wgJftM/YFqt/HqVZFOSwZomCx30Cht6SYphAzWynlaofTWHA6l1uloefz4PpqK34NyWDLUcaxHInsWxz5qfYxeOn7m1n0GsiwVk0zq5VGe11J7bF/a0/OqJ9Gb4m0Gl/8z9WMz+GhLHGVVcXpxIBU6Z93oTY3ey82IVGwjX9BsqIJ8fhargyGd2l7JAfLt+EX9VNmu78Ymx+pgup1Pfeq3k0NSp4kQE991Zo9u27fD4DTksG+cJVtY3ebT6b6yntp831hHLbxs1R+DJ1uLVcSADLbSKS6AGOcehn/3zK+IGsjix6fOH7MMeX9zin9boehhRPBYCrKisHUdDGcfhwkgkhucDDY40amH1raQOh6P4fMF92dhnVgshSxLh94Lu4643fw5wudInOK/n83ej5tuHQlrxFOlimqAUl64YExqPG3Whz3GJJ9Odza4SdONvX5iXBS2PlOxdKqWbteH/yeg8lGTPymHBXVw4joWsvglhpg9+fWFhnw6HSI7Dvilvx4Fu2fyFcEp2kX2yUb9gbvrwOS9u2ddHXuHoyOJFRd74tmx3KHjx0Po5nCFsowE7QbxP5Pr5WKSHpyafL1ciEfcCRmoQkNdjxHB80YxiIkOkOfUsY2vqC+sfVR4UN+l5Ex5tx1NHiRTfdSEBOVZ8xM+VNY+rrBwSD/pfr9WJeEzQ/5O0OMmyEB2skHDCx+WwkeIi24zm97IkkneGeeFdCgKa9ZqExyWfcEq34UwotWSVWi9u4ijWJm1jb6k+Q594VwvF6P5Km0ReM1LRq6tG0ptIqRYanwWw5gUccNzh6fdQilNKZLp3MbQ+QT/1zyVvKdaxiHTRfAz0sgqOrZxnpi7P8yw/3TN7VhA+EzTRksVt7pYuftuOtzBdg4De4/mw7AyTIYBnjrqHez3TMw/nW5bPk7LLVduqTx3/bKbShk84u503eXRoAPbFOu+nxOp0Ht+MlSS5HbR0JHU2bxnlGHsff79Ftr2B3OoZrc= sidebar_class_name: "get api-method" info_path: docs/api/pulsarr-api custom_edit_url: null @@ -47,7 +47,7 @@ Retrieve all Pulsarr-tracked Sonarr shows with their rolling monitoring enrollme diff --git a/docs/docs/api/get-sonarr-tags.api.mdx b/docs/docs/api/get-sonarr-tags.api.mdx index 1f88ab3c9..2d84c0ce4 100644 --- a/docs/docs/api/get-sonarr-tags.api.mdx +++ b/docs/docs/api/get-sonarr-tags.api.mdx @@ -5,7 +5,7 @@ description: "Retrieve tags from a Sonarr instance" sidebar_label: "Get Sonarr tags" hide_title: true hide_table_of_contents: true -api: eJztV01v2zgQ/SvEnFpAju02RRHdgnZRGNsFjCT7ARg50NLYYiORKjl0Yhj678WQlD8SdZNugT315ITkvHnzOJwZ7cC0aCUpo2cl5LBGujZaWnsj1w4yIP7JFxAX4TaDEl1hVcsWkMMVklW4QcEHxcqaRkgRDwulHUldIGTQSisbJLQMtgNXVNhIyHdA2xYhB6UJ12ghA3woau/UBv9QWjW+gZysxwwa+RD/vZhM3k8vLt68O39/Prm4mHYZKGby1aPdQgZaNhExOp+VkIHFr15ZLCNYd8srrjXaoWMSbyYT/jmN7COupK9JXKWTkEFhNKEmPivbtlZF0G38xbHBQFRm+QUL4vAtq0wqunO+KNC5o4NLY2qUGkIsSbRnYVR5dEb7ZomWAWL8+w1HVuk1byylwz9tPbDXHSu0YOAEczC6zUCWpeJ4ZT0/4rGStcOuT5Q9tLRW8m0owsb951hqucSXEo5nn+H52LK/iiPdUyjPAnUZnP/PeUOSvPtgShwUqzjdOFw8Wmvs4E6Dzsn1gFUGjdKfUa+pgnz6RLUDkeS2d3KAfJl+57/0+wn93v3Kv5/QL/inyqS2F7oUu4PxZjp2oYWNKXZBh3bT9y7PJRR2rTVkClN3+Xi8q4yjLt+1xlIHj1vkB+/INOI6YEAGG2mVXNZR0x6G/0bN/W0BFVELWfhxseOGG4W83znFv6lQ9DDCOywFGVGYpvGabxrFvaJKUIUihsHCM+GYOj10bQpZh+UhfN7gjiCMFbO5kGVp0TlhVgF37mvHDf+Az0qc4r+dTN4OUzeWhNHivlJFtYdSTlivdaz2XdbLHjTJx+Ot8XbUxrOjvnSfFaZ54mJuVSPttpf/B6DyQcoflcWCEpy4DO1DvAoScySvH3nIx+O9ssOAn/vtQbBbTr7CW0XbkH2yVb/j9tJzoi5u2ZdD55TR+6Uji2t+2DHPju0OTTosQhqgKpRlSNA0Qf0zupzPRvHAKeXL+Uzc4VZITxVqSvVEeKf0WuzNRALsslOO33FfGHOn8OA+XckT59cRa8STSTlMIUI59nyPy8qYu2ssLNIPhp9ycRSNnxD5O0IPU5CezGiNmmdrLIULEGdpMh5fyZKTPJFzQloUhdErtfYWy/7BKpckDGiNZBd1vT0L84/SKxNiUVQz3f7hXM5ng/dVmsI3qCmSXBm7f2ojIcW8xgdxL6moauVIhHHcHo6mgV7qUkTqXMbQugj/1zQ+eUeNDA0lKfgJqbdMdfSE1u7Ql176GZEujvCBxm0tVZiYfZxpY/FewGbKBTt+rhxmuVjwFrDbpYm263g5fjTwwyqV46JcpgbxL1xfXaVW9Fp8j9Idbh9/gmxk7flgeNJ9C4hvuE89phEtuRK0dGT1pG0zyr57ffrtBrruG1F1yac= +api: eJztV01v2zgQ/SvEnFpAju02RRHdgnZRGNsFjCT7ARg50NLYYiORKjl0Yhj678WQlD8SdZNugT315ITkvHnzOJwZ7cC0aCUpo2cl5LBGujZaWnsj1w4yIP7JFxAX4TaDEl1hVcsWkMMVklW4QcEHxcqaRkgRDwulHUldIGTQSisbJLQMtgNXVNhIyHdA2xYhB6UJ12ghg0Zp1fgG8kkG+FDU3qkN/tEvkvWYQSMf4r8Xk8n76cXFm3fn788nFxfTLgPFrL56tFvIQMsmokcisxIysPjVK4tlBOtuecW1Rjt0TOjNZMI/p1F+xJX0NYmrdBIyKIwm1MRnZdvWqggajr84NhiI0Cy/YEEshWXFSUV3zhcFOnd0cGlMjVJDiCUJ+CyMKo/OaN8s0TJAjH+/4cgqveaNpXT4p60H9rpjhRYMnGAORrcZyLJUHK+s50c8VrJ22PVJs4eW1kq+DUXYuP8cSy2X+FLC8ewzPB9b9ldxpHsK5VmgLoPz/zlvSJJ3H0yJg2IVpxuHi0drjR3cadA5uR6wCm/yM+o1VZBPn6h2IJLc9k4OkC/T7/yXfj+h37tf+fcT+gX/VJnUAkPHYncw3kzHLrSzMcWO6NBu+j7muYTCrrWGTGHqLh+Pd5Vx1OW71ljq4HG7/OAdmUZcBwzIYCOtkss6atrD8N+oub8toCJqIQs/LnbfcKOQ9zun+DcVih5GeIelICMK0zRe802juFdUCapQxDBYeCYcU6eHrk0h67A8hM8b3BGEsWI2F7IsLTonzCrgzn3tuPkf8FmJU/y3k8nbYerGkjBa3FeqqPZQygnrtY7Vvst62YMm+Xi8Nd6O2nh21Jfus8I0T1zMrWqk3fby/wBUPkj5o7JYUIITl6F9iFdBYo7k9SMP+Xi8V3YY8HO/PQh2y8lXeKtoG7JPtup33F56TtTFLfty6Jwyer90ZHHNDzvm2bHdoUmHRUgDVIWyDAmaJqh/Rpfz2SgeOKV8OZ+JO9wK6alCTameCO+UXou9mUiAXXbK8TvuC2PuFB7cpyt54vw6Yo14MimHKUQox57vcVkZc3eNhUX6wfBTLo6i8RMif0foYQrSkxmtUfOcjaVwAeIsTcnjK1lykidyTkiLojB6pdbeYtk/WOWShAGtkeyirrdnYf5RemVCLIpqpts/nMv5bPC+SlP4BjVFkitj909tJKSY1/gg7iUVVa0ciTCa28PRNNxLXYpIncsYWhfh/5rGJ++okaGhJAU/IfWWqY6e0Nod+tJLPynSxRE+0LitpQoTs48zbSzeC9hMuWDHT5fDLBcL3gJ2uzTRdh0vx48GflilclyUy9Qg/oXrq6vUil6L71G6w+3jT5CNrD0fDE+6bwHxDfepxzSiJVeClo6snrRtRtl3r0+/3UDXfQOOlc19 sidebar_class_name: "get api-method" info_path: docs/api/pulsarr-api custom_edit_url: null @@ -47,7 +47,7 @@ Retrieve tags from a Sonarr instance diff --git a/docs/docs/api/get-top-genres.api.mdx b/docs/docs/api/get-top-genres.api.mdx index c5f743765..7f64a3f67 100644 --- a/docs/docs/api/get-top-genres.api.mdx +++ b/docs/docs/api/get-top-genres.api.mdx @@ -5,7 +5,7 @@ description: "Retrieve the most popular genres from watchlists" sidebar_label: "Get top genres" hide_title: true hide_table_of_contents: true -api: eJztVttu2zgQ/RVinnYBubLbFEX0FrSLINguYMTZCxD4gZHGEhuJVIdDJ4ahfy+GknxJVCy6i31bv9jm5Zwzw5lD7sG1SJqNszcFZFAi37n2Gi2hhwRYlx6ye1ixZuPZ5B7WCRToczKtbIIMbpHJ4BYVV6ga51m1rg21JlVGGLUh16gnzXlVG88C22rSDTKSgO/B5xU2GrI9FLjRoWbIFvMEeNciZGAsY4kECeBzXgdvtvibsaYJDWRMARNo9HP/93I+/7C4vHz7/uLDxfzyctElYETi14C0gwSsbgSxNo1h6NYJEPrWWY9eyN/O572G0+g+9YrU7bASEsidZbQsa3Xb1iaP6Uu/eNlwGs0QgCbSwm4YG38y7h6+YM6SDpJDYNPLiFk7WeaZjC2hE+LQ0w4zNjQPSNB1EsjXYAgLOaweYFy+TkAXhRGJul6eMG107bGTTwIX/1Ho3wnRs+bgP7oCJ6IRlmI6AUjkaHKmQe91ObErgcbYz2hLriBbvEzViZCBdiQ5Qv59AhN4/3/+/kX+Ij9XbjCgaBBCB+l2kQqFT8vRkTzSdvSNQDVksG/Jsctd3WVpuq+c5y7bt464g5dW9TF4do1aRQxIYKvJ6Ie6T+oII7/Rip3cQ8XcQhK/Bucb/GmcOce/q1CNMCp4LBQ7lbumCVaOGtWT4Sr6ZB+GZF4En1kf1C7XdRyewpcJ8THlSN0slS4KQu+V20TcZai9JjrBl0yc47+bz99NS3fEyln1VJm8OkAZryhYGwul65Ix7TEnWZruXKBZ26+dGetZ2xzf5K55RbEk02jajen/AahsUvInQ5jzAKeu8lzy8FNMsUTy8wuGLE0PmZ0G/DxOT4KtpfjyQIZ3sfp0a37F3VWQSr1fC5dH742zh6GTHSvp7L7OTvcd74g4CMN9VaEuYoEOF9Zfs6vlzaxfcC75anmjHnGndOAKLQ+GooI3tlSHbWoA7JJzjd+hz517NHikH47kFfmqx5o9aCn1SQk9lBfmJ3yonHtcYU7IPxj+UIuzfvMrIX/20NMSdGA3K9HKMwcL5SPEG7VyVhOlt7qQIh/EeaUJVe7sxpSBsBgb1vghhRGt0UJR17s38e41duNiLIZrkTs2ztXyZvK8CpeHBi33IjeODq02U1ota3w+vpZUfP3QcWkvW2lbqF662BiS7+H/WPQt77nR8UYZMniNrNi16mCjZ6r2x3vpn7zmhjNkfOa0rbWxoiH23X4w8nvYLsS7xcohgUHFejS/e9jvpYZ+p7rrZLh/r0mTFcaLQRfDbZHAI+4OLzgx8DoId+zO0c37dhyrSFD6TdLU7emuV1ewoBxuoutf7qDrvgG04d7z +api: eJztVttu2zgQ/RVinnYBubLbFEX0FrSLINguYMTZCxD4gZHGEhuJVIdDJ4ahfy+GknxJVCy6i31bv9jm5cyZw5lD7sG1SJqNszcFZFAi37n2Gi2hhwRYlx6ye1ixZuPZ5B7WCRToczKtbIIMbpHJ4BYVV6ga51m1rg21JlVGGLUh16gnzXlVG88C22rSDTKSgO/B5xU2GrI9FLjRoWbIFvMEeNciZGAsY4kECTTGmiY0kM0TwOe8Dt5s8bdxkClgAo1+7v9ezucfFpeXb99ffLiYX14uugSM0P0akHaQgNWNoNemMQzdOgFC3zrr0QuRt/N5z+c00089O3U7rIQEcmcZLcta3ba1yaOU6RcvG04zG5LRRFqiG8bGn4y7hy+Ys0hDciBsehpRwZNlnsnYEjoJHPqww4wNzQMSdJ0k8jUYwkIOrgcYl68T0EVhhKKulyeRNrr22MkngYv/KPXvpOhZc/AfXYET2UiUYloAJHI0OdOg97qc2BUr6DPakivIFi+lOiEyhB2DHCH/XsAE3v+v37/QL8bnyg1mFM1CwkG6XaQSwqfl6E4eaTt6SKAaMti35Njlru6yNN1XznOX7VtH3MFL2/oYPLtGrSIGJLDVZPRD3Ys6wshvtGIn91Axt5DEr8EFB68aZ87x7ypUI4wKHgvFTuWuaYKVo0b1ZLiKntmnIcoL4TMbhNrluo7DU/gyIT6mHKmbpdJFQei9cpuIuwy110Qn+KLEOf67+fzdNHVHrJxVT5XJqwOU8YqCtbFQui4ZZY+aZGm6c4Fmbb92ZqxnbXN8k7vmVYglmUbTbpT/B6CyScqfDGHOA5y6ynPR4acosWTy84sIWZoelJ0G/DxOT4KtpfjyQIZ3sfp0a37F3VWQSr1fSyyP3htnD0MnO1bS2X2dne473hFxEIb7qkJdxAIdLqy/ZlfLm1m/4Jzy1fJGPeJO6cAVWh4MRQVvbKkO29QA2CXnHL8TPnfu0eAx/HAkr4KveqzZg5ZSn6TQQ3mJ/IQPlXOPK8wJ+QfTH2px1m9+ReTPHnqagg7sZiVaefJgoXyEeKNWzmqi9FYXUuQDOa80ocqd3ZgyEBZjwxo/SBjRGi0h6nr3Jt69xm5czMVwLXTHxrla3kyeV+Hy0KDlnuTG0aHVZkqrZY3Px5eTii8hOi7taSttC9VTFxtD8j38H4u+5T03Ot4og4LXyIpdqw42esZqf7yX/snLbjhDxmdO21obKxxi3+0HI7+H7UK8W6wcEhhYrEfzu4f9Xmrod6q7Tob795o0WWG8GHQx3BYJPOLu8IITA6+DxI7dObp5345jFQlKv0mauj3d9eoKFpTDTXT9yx103TfJp+LJ sidebar_class_name: "get api-method" info_path: docs/api/pulsarr-api custom_edit_url: null @@ -47,7 +47,7 @@ Retrieve the most popular genres from watchlists diff --git a/docs/docs/api/get-top-users.api.mdx b/docs/docs/api/get-top-users.api.mdx index 4dd3e7f7a..506817a4e 100644 --- a/docs/docs/api/get-top-users.api.mdx +++ b/docs/docs/api/get-top-users.api.mdx @@ -5,7 +5,7 @@ description: "Retrieve users with the most watchlist items" sidebar_label: "Get top users" hide_title: true hide_table_of_contents: true -api: eJztVk1v2zgQ/SvEnLaAHNltiiK6Be2iCLYLGHG6u4DhAyONLTYSqQ6HTgxD/70YUfJHomIRFHvbXJKQnPfejGYeuQfXIGk2zt4UkMEG+c41Xz2ShwRYbzxkS1iwZuPZ5B5WCRToczKNxEAGt8hkcIsqSJB6NFwqLlHVzrN61JyXlfGsDGMtkI0mXSMLfrbcg89LrDVkeyhwrUPFkM2mCfCuQcjAWMYNEiSAT3kVvNnin8aaOtSQMQVMoNZP8d+r6fTD7Orq7fvLD5fTq6tZm4ARed8D0g4SsLoWxMrUhqFdJUDoG2c9eiF/O51GDaeZfYqK1G1/EhLInWW0LGd101Qm7yqXfvMScJpNn4Am0sIesz+uu/tvmLOUg6T+bKKMKPJwyjMZu4FWeENk7XdsqO+RoG0lj+/BEBbynbr44fQqAV0URgTqan7Cs9aVx1Z+Erj8jxL/SYKeNQf/0RU4koywFOP5I5Gj0Z0avdebkagEamO/oN1wCdnseaVOhPS0A8kR8t8LmMD7/+v3C/Xr+Ll0vfN09iB0kG5nqVD4NPRW5JG2g2kEqiCDfUOOXe6qNkvTfek8t9m+ccQtPPeoj8Gzq9Wiw4AEtpqMvq9iTQcY+RuteMkSSuYGku5Xb3m9OQ075/h3JaoBRnywUOxU7uo6WPnSeHTFmIYUXgSf+R5ULtdVtzyGLxsy38qRupkrXRSE3iu37nDnofKa6ARfKnGO/246fTcu3RErZ9VjafLyAGW8omBt1ydtmwxl72qSpenOBZo08ezEWM/a5niRu/oFxZxMrWk3lP8VUNmo5E+GMOceTl3nudTht67EksmbZwxZmh4qOw74ZdgeBVtJ8+WBDO+67tON+QN310EadbkSLo/eG2cPSycRCxns2GencccLoluE/rIqURddg/a31T+T6/nNJB44l3w9v1EPuFM6cImWez9RwRu7UYcw1QO2ybnGn9Dnzj0YPNL3n+QF+SJiTe61tPqohAjlhfkR70vnHhaYE/Ir0+97cRKDXwj5O0KPS9CB3WSDVp43WCjfQVyohbOaKL3VhTR5L84rTahyZ9dmEwiLYWCN70vYodVaKKpqd9HdvMauXZeL4UrkDoNzPb8Z/V6Fy0ONlqPItaPDqE2UVvMKn07fS/L0oePRKFtpW6goXWwMyUf4v2Zx5D3X2h5fEvAZWbFr1OCiZ6L2x1vpta+4/vMxPnHaVNpYoe9Gbt9b+BK2M7FtMXFIIApYDba3hP1euucrVW0ry/GZJuNVGC/WXPTXRAIPuDs83MS6qyDU3VwOPh4HcegfQYlBMs7NadSLu1dQDlfQ59/voG1/AM6H2Y8= +api: eJztVk1v2zgQ/SvEnLaAHNltiiK6Be2iCLYLGHG6u4DhA0ONLTYSqZJDJ4ah/14MKfkjUbEIir1tLkkozntvhjOP3INt0UnS1tyUUMAG6c62Xz06DxmQ3HgolrAgSdqTVh5WGZToldMtx0ABt0hO4xZF4CDxqKkSVKForCfxKElVtfYkNGHDkK10skFi/GK5B68qbCQUeyhxLUNNUMymGdCuRShAG8INOsig0UY3oYFimgE+qTp4vcU/h0VyATNo5FP692o6/TC7unr7/vLD5fTqatZloFnq94BuBxkY2TB6rRtN0K0ycOhbazx6FvJ2Ok16TrP8lNSJ234nZKCsITTEe2Xb1lrFKubfPAecZtYnI52TzJ4qcVy3999QEZfG8VmQTjKSyMMuT06bDXTMGxJr/8WE5h4ddB3n8T1ohyWfWYwfdq8ykGWpWaCs5yc8a1l77Pgng8v/KPGfJOhJUvAfbYkjyTBLOZ4/Omfd6JcGvZebkajYP1/QbKiCYva8UidCetqB5Aj57wXM4P3/9fuF+kV+qmzvQtEqmA7y7SxnCp+H3pY8uu1gIMHVUMC+dZassnVX5Pm+sp66Yt9aRx0896uPwZNtxCJiQAZb6bS8r1NNBxj+Gw17yRIqohay+Ku3v96ohi/n+HcVigGGPbEUZIWyTRMMnzQeHTKlwYVnwWceCLVVso7LY/j8gedbWCdu5kKWpUPvhV1H3HmovXTuBJ8rcY7/bjp9Ny7dOhLWiMdKq+oApb1wwZjYJ12XDWWPNSnyfGeDm7Rp70QbT9IovFC2eUExd7qRbjeU/xVQxajkT9qhoh5OXCvFdfgtlpgzefOMocjzQ2XHAb8Mn0fBVtx8KjhNu9h9stV/4O46cKMuV8zl0XttzWHpJGLBg5367DTueEHERegvqwplGRu0v63+mVzPbyZpw7nk6/mNeMCdkIEqNNT7iQhem404hIkesMvONf6EXln7oPFI3x/JC/JFwprcS271UQkJyjPzI95X1j4sUDmkV6bf9+IkBb8Q8neCHpcgA9nJBg0/dbAUPkJciIU10rn8Vpbc5L04L6RDoaxZ601wWA4Dq31fwojWSKao691FvHm1WduYi6aa5Q6Dcz2/GT2v0qrQoKEkcm3dYdQmQop5jU+nbyd+Brnj1iRbSFOKJJ1tDJ1P8H/N0sh7aqQ5viTgM5Ig24rBRc9E7Y+30mtfdP3xET5R3tZSG6aPI7fvLXwJ2xnbNps4ZJAErAbbW8J+z93z1dVdx8vpmcbjVWrP1lz210QGD7g7PNzYuuvA1HEuBx9Pgzj0D6OkIB7n9jTqxd3LKIcr6PPvd9B1PwCph91l sidebar_class_name: "get api-method" info_path: docs/api/pulsarr-api custom_edit_url: null @@ -47,7 +47,7 @@ Retrieve users with the most watchlist items diff --git a/docs/docs/api/get-user-by-id.api.mdx b/docs/docs/api/get-user-by-id.api.mdx index daef798b1..a8ca0880d 100644 --- a/docs/docs/api/get-user-by-id.api.mdx +++ b/docs/docs/api/get-user-by-id.api.mdx @@ -5,7 +5,7 @@ description: "Retrieve a specific user by their ID" sidebar_label: "Get user by ID" hide_title: true hide_table_of_contents: true -api: eJzdV9tu2zgQ/RWCTy0gx3aboojess2iMLYLGEm7u4BhGDQ5tthQpMqLE8HQvxdDSr7UcuO+9GFffCGHZw6HZ4bDLTUVWOal0RNBc7oG/8WB/aOeCJpRz9aO5jOKQ47OMyrAcSsrNKc5vQdvJWyAMOIq4HIlOQkOLFnWxBcgLZnc0YxWzLISPELksy11vICS0XxLfV0BzanUHtZgaUbhmavg5Ab+llqWoaS5twEyWrLn9PdmNHo/vrl58+76/fXo5mbcZFQik4r5gmZUszICIncL34K0IBJGM8cRVxntwKHvN6MRfh1v6A5WLChP7ltLmlFutAft0ZZVlZI8xmr41eGCns2Y5VfgHndtMbJeJncucA7OHRgujVHANG0yWoJzbA0Hk85bqdc4h/F8GV6KAxsdyiVYXJzi0YPKqspKF+d0UIotFXSxPrVVkrmLLIV03FixSGxeNNfGy1W9OOByGprWpkW+xGZRgk7H+RPbSsHzojRLqc745UwvXK15/2yrLRe5mw1T/WbSLSorS2brhTePcIZS5BLChUFjG+aZvfQ4KsXqRaeCFxesrAQtFtwC8yAWzF+06tj8VMCVOD/dHObpLCVupLuXaCfAI3mdiOdEKWdl0auBgwPvO92eozza99Eu5xllQkh0xtT0IE1XTDn4cctdYdhXgTbnX4JBoOvR9W8tYp754D4YAb3Vhh9P7CUA1hrbO3O28mW0lPoT6LUvaD4+CdqeSOu2c7KHvCR+737zJfD/il/07wvT9g3xpkd3dLgZD1HDrv3cStHQjDqwm64JCFbRnG4ra7zhRjX5cLgtjPNNvq2M9Wh+fCofgvOmJA8Rg2Z0w6zEshQD28Hgb9DYKMxo4X1Fs/jVti7xWGnezRzjfy6AdDDYxAjiDeGmLIPG4wbyJH2BXQ1J28DoI+Gknw5aGc5UHO7DxwmsbsRYMpkSJoQF54hZRdxpUI5Ze4CPkTjGfzsave2nbqwnRpOnQvJiByUdsUHrVGqbrAt7jEk+HNYm2EGVbAdSO880hytuyhMX01T7uvD/AlTeS/lOWuC+hSO3sQSSVzHEuJPXP3jIh8NdZPsBP3XTvWBzFB8PVvo6qo9V8i+obwOqdTZHXw6ck0bvhg5WPGB2J50drttlXBqkbSdaABNRoG0v+t/gdjoZJINjyrfTCXmEmrDgC7ybUlEhwUm9JrtlpAVssmOOZ9xzYx4l7N23R3Li/CFhDZYMpd5LIUE59PwEy8KYxwfgFvwvbr/V4iAtPiHyb4Lup8CCN4M1aHyhgCAuQlyRB6OZtcN7JlDkLTlHmAXCjV7JdbAguoSVrg1hRCsZulCqvorNh9QrE/ciPXY4tEuc2+mk97yE4QEbiURyZewu1QaEkamCZ/LEPC+UdJ7Ed43dmybahGlBEnUsY2Bdgv9nnFLe+ZLFW6WN4Efwu0dVfE4dsdru76ZLn2PtuXl49sNKMRmb0Jhr27aAz+hm3LYh7uA7lwILaap6M7rdoni+WNU0OPwtgMXsmu9rc8w1IR3+Fu3F8RP+r+7bK+o1OUez05yu4xWgAv6jGX2EOr37mjnW5SRC9J4msCZU/mDJyS2OCb+7zD7++Zk2zXdXKEQZ +api: eJzdV9tu2zgQ/RWCTy0gx3aboojess2iMLYLGEm7u4BhGDQ5tthQpMqLE8HQvxdDSr7UcuO+9GFffCGHZw6HZ4bDLTUVWOal0RNBc7oG/8WB/aOeCJpRz9aO5jOKQ47OMyrAcSsrNKc5vQdvJWyAMOIq4HIlOQkOLFnWxBcgLZnc0YxWzLISPELksy11vICS0XxLfV0BzanUHtZgaUZLqWUZSpqPMgrPXAUnN/B3N+htgIyW7Dn9vRmN3o9vbt68u35/Pbq5GTcZlciqYr6gGdWsjOC4DwvfgrQgEkYzxxFXGe3AIY83oxF+HW/uDlYsKE/uW0uaUW60B+3RllWVkjzGbfjV4YKejZnlV+AeI2Axyl4mdy5wDs4dGC6NUcA0bTJagnNsDQeTzlup1ziHsX0ZXooDGx3KJVhcnOLRg8qqykoX53RQii0VdLE+tVWSuYsshXTcWLFIbF4018bLVb044HIamtamRb7EZlGCTsf5E9tKwfOiNEupzvjlTC9crXn/bKstF7mbDVP9ZtItKitLZuuFN49whlLkEsKFQWMb5pm99DgqxepFp4IXF6ysBC0W3ALzIBbMX7Tq2PxUwJU4P90c5uksJW6ku5doJ8AjeZ2I50QpZ2XRq4GDA+873Z6jPNr30S7nGWVCSHTG1PQgTVdMOfhxy11h2FeBNudfgkGg69H1by1invngPhgBvdWGH0/sJQDWGts7c7byxXvhE+i1L2g+PgnankjrtnOyh7wkfu9+8yXw/4pf9O8L0/YQ8dZHd3S4GQ9Rw6793ErR0Iw6sJuuIQhW0ZxuK2u84UY1+XC4LYzzTb6tjPVofnwqH4LzpiQPEYNmdMOsxLIUA9vB4G/Q2CjMaOF9RbP41bYx8Vhp3s0c438ugHQw2NAI4g3hpiyDxuMG8iR9gR0OSdvA6CPhpJ8OWhnOVBzuw8cJrG7EWDKZEiaEBeeIWUXcaVCOWXuAj5E4xn87Gr3tp26sJ0aTp0LyYgclHbFB61Rqm6wLe4xJPhzWJthBlWwHUjvPNIcrbsoTF9NU+7rw/wJU3kv5TlrgvoUjt7EEklcxxLiT1z94yIfDXWT7AT91071gcxQfD1b6OqqPVfIvqG8DqnU2R18OnJNG74YOVjxgdiedHa7bZVwapG0nWgATUaBtL/rf4HY6GSSDY8q30wl5hJqw4Au8m1JRIcFJvSa7ZaQFbLJjjmfcc2MeJezdt0dy4vwhYQ2WDKXeSyFBOfT8BMvCmMcH4Bb8L26/1eIgLT4h8m+C7qfAgjeDNWh8rYAgLkJckQejmbXDeyZQ5C05R5gFwo1eyXWwILqEla4NYUQrGbpQqr6KzYfUKxP3Ij12OLRLnNvppPe8hOEBG4lEcmXsLtUGhJGpgmfyxDwvlHSexDeO3Zsm2oRpQRJ1LGNgXYL/Z5xS3vmSxVuljeBH8LsHVnxaHbHa7u+mS59m7bl5ePbDSjEZm9CYa9u2gM/oZty2Ie7gO5cCC2mqejO63aJ4vljVNDj8LYDF7Jrva3PMNSEd/hbtxfET/q/u2yvqNTlHs9OcruMVoAL+oxl9hDq9+5o51uUkQvSeJrAmVP5gycktjgm/u8w+/vmZNs13kkVH7w== sidebar_class_name: "get api-method" info_path: docs/api/pulsarr-api custom_edit_url: null @@ -47,7 +47,7 @@ Retrieve a specific user by their ID diff --git a/docs/docs/api/sync-instance.api.mdx b/docs/docs/api/sync-instance.api.mdx index 3d45b7589..dffaccdbe 100644 --- a/docs/docs/api/sync-instance.api.mdx +++ b/docs/docs/api/sync-instance.api.mdx @@ -5,7 +5,7 @@ description: "Synchronize watchlist items to a specific Radarr or Sonarr instanc sidebar_label: "Sync specific instance" hide_title: true hide_table_of_contents: true -api: eJzdVktv2zgQ/ivEnFpAjuw2RWHdgvRibBdrxN0HEPjAUGOLjUSqfDjRCvrvxZCSLdfKAgGKHvZkmZz55psZzqMFXaPhTmq1yiED2yixUtZxJRAScHxvIbuHTaMEbBPI0QojaxKHLJwWRiv5L7In7kRRSuuYdFhZ5jTjzNYo5E4KdsdzbgzThm20oi95slFzwyt0aMhSC1YUWHHIWnBNjUTJGan2kAAqXxEZE8AgARuwYNslIInPN4+mgQQUr0gx6Cdg8JuXBnPInPHYJRMmpHK4R4LEZ1F6Kw/4u1SyInuklEDFn+Pf5Xz+cbFcvvtw/fF6vlwuBts1d8XJ9ODeKr8ksKUTW2tl0RKHd/M5/ZyH9hPuuC8du+slIQGhlUPlSJbXdSlFyFr61ZLChFP64SsKRwE2lGMno7mQnltdSyJ0FFa+ekADXQIVWsv3eJmArhu7cn8GdFLbJsDzXBI1Xq5Hlne8tNh1hHI9v/6VHlvHnbe3OsdJh8X5xeBtAmiMNpM3L8YogUqqz6j2roBs8WPERkR6s4OR18Xvw699Mf+z+AX7rtDU7mptg79UvBmkh0VKDTAdyjdtT4XcUcNBcxj6lDclZNDWRjstdNlladoW2roua2ttHImfJ+jWW6crtgkYkMCBG8kfyhjjAYa+hz5XOFdDEn5sbL4hw5ANN+f4XwpkAwzzFnPqwUJXlVeUeWRP0hXMFciiG5QIIhyf0gBdasHLIoblEp8uqMVRJ1+tGc9zg9YyvQu4a19aau4nfIrEOf77+fz9NHVtHNOKPRVSFEcoaZnxSsX+Q707hj3EJEvTRnszq6PsbMjVldDVhYm1kRU3zRD+V0Blk5Q/SYPC9XDsRgiKw5sQYvLk7Q8WsjQ9RnYa8PNwPQm2pccnvJGuCa+P1/I3bG48vdv7bRhqaK3U6ng00thQocd3NtY7Fl88hH6SFcjz8ED7WfbP7Ga9mkWBc8o36xV7xIZx7wpUru8vzFup9uyoxnrALjnn+IJ5ofWjxJP5PiUXxjcRa/bA6alPUohQliw/4UOh9eMGhUH3Svf7tziLyhdE/o7Q0xS4d3q2R0U7FubMBoirfgtK+7WoJ2cZN8iEVju59wbzoWCl7UMY0CpOJsqyuQoTWaqdDr5IVxLdoXBu1qvJfOVa+AqViyR32hxLbcY4W5f4PF7laC8yJ9F+eeMq7zc6amNobIT/axFL3rqKhwHTR5DWxNMqONr8zti1p3H1kxbLPr0On11al1wqohdKsu07/j0cFtTWabtNYKSajRa47dAm76Ft6bX9acquo+O4b1I55tJSK8/7MfMfrr256wfaW/YSxUdsTtvrgZeeREILGEbGz7c5lIJqxjYHLqNodLRt9zVCLKIAtazajVQv9g2ifxy76z82X6DrvgP2i1dc +api: eJzdVktv2zgQ/ivEnFpAjuw2RWHdgvRibBdrxN0HEPjAUGOLjUSqfDjRCvrvxZCSLdfKAgGKHvZkmRx+8817WtA1Gu6kVqscMrCNEitlHVcCIQHH9xaye9g0SsA2gRytMLImccjCaWG0kv8ie+JOFKW0jkmHlWVOM85sjULupGB3POfGMG3YRiv6kicdNTe8QoeGNLVgRYEVh6wF19RIlJyRag8JoPIVkTEBDBKwAQu2XQKS+HzzaBpIQPGKHob3CRj85qXBHDJnPHbJhAqpHO6RICupZEVq5gngsyi9lQf8fTgkgAQq/hz/Lufzj4vl8t2H64/X8+VyMfCouStONAZTV/klmS2d2Fori5b4vJvP6efczZ9wx33p2F0vCQkIrRwqR7K8rkspQgTTr5YeTBioH76icORsQ/F2MqoLobrVtSRCR2Hlqwc00CVQobV8j5fB6LqxKfdnQKdn2wR4nkuixsv1SPOOlxa7jlCu59e/0mLruPP2Vuc4abA4vxisTQCN0Wby5kUfhWT6jGrvCsgWP3psRKRXOyh5nf8+/NqM+Z/5L+h3habWV2sb7KXizSA9LFJqhulQvml7KuSOmg+aw9CzvCkhg7Y22mmhyy5L07bQ1nVZW2vjSPw8QLfeOl2xTcCABA7cSP5QRh8PMPQ99LzCuRqS8GNjIw4Rhmy4Ocf/UiAbYJi3mFM/FrqqvKLII3uSrmCuQBbNoEAQ4ZhKA3SpBS+L6JZLfLqgFkddfbVmPM8NWsv0LuCufWmp0Z/wyRPn+O/n8/fT1LVxTCv2VEhRHKGkZcYrFfsP9fHo9uCTLE0b7c2sjrKzIVZXQlcXKtZGVtw0g/tfAZVNUv4kDQrXw7EbIcgPb4KLyZK3P2jI0vTo2WnAz8P1JNiWkk94I10Tso/X8jdsbjzl7f02DDi0Vmp1PBq92FChxzwbvzsWXzyEfpIVyPOQoP0s+2d2s17NosA55Zv1ij1iw7h3BSrX9xfmrVR7dnzGesAuOef4gnqh9aPEk/o+JBfKNxFr9sAp1ScpRChLmp/wodD6cYPCoHul+X0uzuLjCyJ/R+hpCtw7Pdujon0Lc2YDxFW/EaX9itSTs4wbZEKrndx7g/lQsNL2LgxoFScVZdlchYks1U4HW6Qrie5QODfr1WS8ci18hcpFkjttjqU2Y5ytS3wer3W0I5mTaL/IcZX32x21MTQ2wv+1iCVvXcXDgOk9SCvjaS0cbYFn7NrTuPpJS2YfXofPLq1LLhXRCyXZ9h3/Hg4Lauu06SYwepqNFrjt0CbvoW0p2/40ZdfRcdw9qRxzaamV5/2Y+Q/T3tz1A+0te4niIzanTfbAS08ioQUMI+Pn6xxKQTVjnQOXkTc62rz7GiEWUYBaVu1GTy/2DaJ/HLvrPzZfoOu+A1ftWzI= sidebar_class_name: "post api-method" info_path: docs/api/pulsarr-api custom_edit_url: null @@ -47,7 +47,7 @@ Synchronize watchlist items to a specific Radarr or Sonarr instance diff --git a/docs/docs/api/update-schedule.api.mdx b/docs/docs/api/update-schedule.api.mdx index 7aef2154d..a40f74bd1 100644 --- a/docs/docs/api/update-schedule.api.mdx +++ b/docs/docs/api/update-schedule.api.mdx @@ -5,7 +5,7 @@ description: "Update an existing job schedule configuration" sidebar_label: "Update job schedule" hide_title: true hide_table_of_contents: true -api: eJzdV0tv4zYQ/ivEnHYBeeVsNmmhW5rtwegWNfJoCxg+0NLYYiKRWj78gKD/vhhSsmVHCeAU2EN9sURyvnlwHp9qUBVqboWSkwwScFXGLd6nOWauQIjA8pWBZAbdkoZ5BBmaVIuKpCCBRy/DuGS4FcYKuWJPasFMK8FSJZdi5YIWiEDjd4fG/qayHSS1fxUaM0isdhhBqqRFaWmLV1UhUi8XPxlSVgPBlpyelMS/lpDMarC7CiEBtXjC1EIElSavrEBD58Lu/pSxWsgVRIDSleSakBb1mhcwb7z2pVj1jr8CmvFdD9xjrFAT6jYtnBFr/FNIUZKG4FbJt+H18vqqiSBXTr8T4NdfrsdNBKWQzuI7Ma4+X12PCcVgqmT2Xlcuri6vxx5HOzkpS8wEt1jsenALpQrkEpqmoZDzRUFXPbAd9TJhFrbnTfRfLzfVSp51sbitNBojQradwpZCfkO5sjkkF6cm9yTn7/F1Hp2Y1viYVVzzEi1Susz6+X9sWxOBoGqsuM0hAslL2vR/0UmNkSaNplLSBJ8/j8c+p4/K+isuuSssu2tPwhml+XaIjUtTNGYoMhGUaAxfDdzpadQ6lIPIPAKeZYJs4sW0p3LJC4Mhml/GX36qq5ZbZ25V1ndIunKBGnxSZkOeRoBaKz2482p83k7OniGt2k7JefG7+smp8v+Kn9dvc0WjtnLeXSrXBOL1RdwNTL1/MnFNBdwA9Wm97lqA0wUkUFdaWZWqokniuM6VsU1SV0pbOn58P7fOWFWye48BEay5FtScfIg7GN/82q6ZW1tB5P9MGPj+giHpdo7xH3JkHQxzBjNmFUtVWTpJF49sI2zObI4suAF+ABobMqmDLlTKC788hE8bFAymNJtMGc8yarZMLT3u1BWGa93Dp0gc41+Ox5fDpittmZJsk4s030MJw7STMrQeGkQh7D4mSRzvlNOjKpwdCWkslyl+SlX5QsVUi5LrXRf+M6CSQZO/Co2pbeHYje+C7IMPMXny8URDEsf7yA4Dfuu2B8HmniQ4LezOZx+vxB+4u3GUtzM/oU2YevulngTRxjLkWV9uX3thEdrZlSPPfIK20+vf0c10MgoHjk2+mU7YM+4YdzZHadv2wpwh9rkXYy2g5zk9G19Rnyr1LPCgvr2SF8rvA9ZowSnVB00IUIY0b3CRK/V8j6lGe6b7bS6OgvALQ/4J0MMmcGfVaIWS6D1mzHiIT+xeSa51fMczSvLWOMO4PhB1zLqCFaYNoUcrOakoit0nP4ypl98duPzvW15WRb+x7ol1n30F4jzeM+Bxj8mOe3x0/JJTeu7SI1btu5BL5bUKS+qhK+Cb6WQwbzKVuhKlDcFaKr0v+RHjbFrglm24TfNCGMs8G9aHoyF8jMuMhRBSO0Ud+CL8fRFaj7El93Ouvcn2E6n/WXRqWn0YmWd/UbXxtri1cVVw4YmU7wB1O2BmsL6gKbL/ijs8E4NKvJ3zrivPoK4puR910TS0/N2hpuqfH2aH7wWZMO1d+BH3hksf7tph+pG9ZnBXE3LnR1Th6A0ieMZdx2QbItZtmZD+sHUbtIweCOAg+oJ3UK8KEtTnKvvm2XlvVE8fHyCCRfvBWnrSAZpviFnzTbBSead9p/NrNRRcrpynGhAg6fcDtZZfEg== +api: eJzdV0tv4zYQ/ivEnHYBea1sNmmhW5rtwegWNfJoCxg+0NLYYiKRWj78gKD/Xgwp2bKjBHAb7KG+WCI53zw4j081qAo1t0LJSQYJuCrjFu/THDNXIERg+cpAMoNuScM8ggxNqkVFUpDAo5dhXDLcCmOFXLEntWCmlWCpkkuxckELRKDxu0Njf1HZDpLavwqNGSRWO4wgVdKitLTFq6oQqZcbPxlSVgPBlpyelMQ/lpDMarC7CiEBtXjC1EIElSavrEBD58Lu/pSxWsgVRIDSleSakBb1mhcwb7z2pVj1jr8CmvFdD9xjrFBDBKWQoiTgOALcpoUzYo2/d4vBxZJvw+vl9VUTQa6cfgewn3+6jhsv4yy+A97V56vrmBANpkpm7+HuxdXldewxtZOTssRMcIvFrge9UKpALqFpGroivigoNQa2o17mzML2vIn+azKkWsmzEgG3lUZjRMjOU9hSyG8oVzaH5OLU5J7k/N/4Oo9OTGt8zCqueYkWKaVm/Xo5tq2JQFD1VtzmEIHkJW36v+ikJkmTRlMpaYLPn+PY18BRG/iKS+4Ky+7ak3BGKb8dYuPSFI0ZikwEJRrDVwN3ehq1DuUgMo+AZ5kgm3gx7alc8sJgiOaX+MsPddVy68ytyvoOSVcuUINPymzI0whQa6UHd16Nz9vJ2TOkVdspOS9+Vz84Vf5f8fP6ba5oNFfOu0vlmsB4fTHuBqzeP5lxTQXcAPVsve5agNMFJFBXWlmVqqJJxuM6V8Y2SV0pben48f3cOmNVye49BkSw5lpQc/Ih7mB882u7Zm5tBZH/M4Eg+AuGpNs5xn/IkXUwzBnMmFUsVWXpJF08so2wObM5suAG+CFpbMikDrpQKS/88hA+bVAwmNJsMmU8y6jZMrX0uFNXGK51D58icYx/GceXw6YrbZmSbJOLNN9DCcO0kzK0HhpEIew+Jsl4vFNOj6pwdiSksVym+ClV5QsVUy1Krndd+M+ASgZN/io0praFYze+C7IPPsTkyccTDcl4vI/sMOC3bnsQbO4Jg9PC7nz28Ur8hrsbR3k78xPahKm3X+pJEM0sQ5715fa1FxahnV058swnaDu9/h7dTCejcODY5JvphD3jjnFnc5S2bS/MGWKrezHWAnrO07PxFfWpUs8CD+rbK3mh/D5gjRacUn3QhABlSPMGF7lSz/eYarRnut/m4igIvzDkrwA9bAJ3Vo1WKOlzADNmPMQndq8k13p8xzNK8tY4w7g+EHvMuoIVpg2hRys5qSiK3Sc/jKmX3x24/69bXlZFv7HuiXiffQWiHe9ZctxjuHGPm8YvOaXnLj1i1b4LuVReq7CkHroCvplOBvMmU6krUdoQrKXS+5IfMc6mBW7Zhts0L4SxzDNjfTgawse4zFgIIbVT1IEvwp8XofUYW3I/59qbbD+p+p9Rp6bVh5F59hdYG2+LWzuuCi48kfIdoG4HzAzWFzRF9l99h2diUIm3c9515RnUNSX3oy6ahpa/O9RU/fPD7PC9IBOmvQs/4t5w6cNdO0w/stcM7mpC7vyIKhy9QQTPuOuYbEPEui0T0h+2boOW0QMBHERf8A7qVUGC+lxl3zw7743q6eMDRLBoP3BLTzpA8w0xa74JVirvtO90fq2GgsuV81QDAiT9/gHEk25q sidebar_class_name: "put api-method" info_path: docs/api/pulsarr-api custom_edit_url: null @@ -54,7 +54,7 @@ Update an existing job schedule configuration diff --git a/docs/docs/api/update-user.api.mdx b/docs/docs/api/update-user.api.mdx index 0746e3ad0..7cab64661 100644 --- a/docs/docs/api/update-user.api.mdx +++ b/docs/docs/api/update-user.api.mdx @@ -5,7 +5,7 @@ description: "Update an existing user by ID" sidebar_label: "Update user" hide_title: true hide_table_of_contents: true -api: eJztWN9v2zYQ/lcIPrWAXDttgiJ6y9IBC9YBRtJsAwzDoMWzxYYiVf5wLBj+34sjJUuK5SbFtj4M9YNtkcfvTncf747cUV2CYU5odcNpSn3JmYN7C4Ym1LG1pemM4qOl84RysJkRJUrTlN4HWcIUga2wTqg18RYMWVbk5gNNqIEvHqz7RfOKprvwKAxwmjrjIaGZVg6UwylWllJkwYrxZ4vgO2qzHAqG/1xVAk2pXn6GzNGElgZtdgIszipWQEfKOiPUmia0EOojqLXLafouoQXbNk9vLy72Cao0woaVykvJlhIaw/pIKCsFs89LPquTC5tpwxeCv0it0k6sqkXH0lpkqbUEpjoyNfJLZBYFqBjAb8iWEraLQi+FPKE3Y2phK5UNz9ahtsF2vWFySCx8EloywwpwyLB0NhB2oRysAx1hm0lvxQb+EEoUvmgcV7BtfLycTN6fXV6+vTh/fz65vDzbJ1QgUUvmcprUTKGC0+QJGfdzHLGlVjaS6u1kgj99vn+AFfPSkdtakv5rHLY+y8DaYWcWYC1bD3B8n1Dcb8/Diy4xlC+WYEKsB3fOf7M3frL/SEzYRWlEwUy1cPoBTpgUbPH+hU5jG+aYeWk4SsmqRcOCZxesjADFF5kB5oAvmHvRqr74MYFDDTkxve/u01ncuMHclqINAXv0OiLPEVNO0mKQA52AD0V3IJS99+695TyhjHOBypicdrbpikkLT1+5SQxtFqj3/HMwCHQ+Of+hScwx5+215jCYbbL+REsBMEabwZmTma9Xac+OnNYaUqttlLSQL/Pf5U///QP/XfzgIvr/8l/Q73LNYwOTYQcTGpmUjjdnY8wCtv7eCb6nCbVgNk0b5Y2kKd2VRjudablPx+Ndrq3bp7tSG4fi/bhce+t0Qe4CBk3ohhmBiT24toHB/6Cw1ZrR3LmSJuGnPhuEwNK0menjf8qBNDB4TODEaZLpovAKAw7kUbicuBxIfA30PxocGdRAS50xGYaH8HEC6wPRhtxMCePcgLVErwLu1EvLjOngoyf6+O8mk3fDpmvjiFbkMRdZfoASlhivVCxW+6Rxe/BJOh5X2ptRGWVHQlnHVAZvMl0cqZjG6tG4/zug0kGTPwgDmavhyFUoIuRVcDG+yesnGtLx+ODZYcCPzfQg2BzJl3kjXBXYx0rxO1RXHtk6m6MuC9YKrQ5DnRV3uL8jz7rrDnsuDtK6l8+B8UDQupv/e3Q1vRlFgb7JV9Mb8gAVYd7lWN1jWiHe4kH1sIzUgPukb+MJ9ZnWDwJa9XVIjpTfRazRkiHVB02IUBY1P8Iy1/rhDjID7jtfv+biKC4+MuSvCD1sAvNOj9ag8AYAOLEB4g2504oZM75lHEleG2cJM0AyrVZi7Q3wZsMKW7swoBUMVUhZvQntG+bw2/Ya4NctK8rYVNf2HxLqoc/vDMWjRTvQPUG0o08PCrERfXo0GBptDwO92V77X99VHBr++DzQ4odzJEZppUP0hMPltEkVV9ObQYZynXk0I4Zlpc0huYwII1MJW/KI2V8K60g4C5tWNAaKMMVJDBYmbjA2wv95FpOcdQVTHZ/X9zY+XvL0TNq1xfjZ652anw62blxKJsJxJeSUXV2oZnRzVjestvObCo4FI2b3Gd3tcJPcG7nf4/AXDwazyLytQSGncGHxP69L5DcMf3VbF+PX5JSZzd5SVSh10uMTTegDVPGGYD/H+hM3G2qPE9dRx+gTLm8XHnUtmPHiCsyWpfum7LxT6KdXn65/owld1ndmRWhaqGGPePJgj9FKHV46ZMwwtqOSqbUPrQqNoPj5Cu108cA= +api: eJztWN9v2zYQ/leIe2oBuXbaBEP0lqUDFqwDjKTdBhiGQYtni41EqvyR2DD8vw9HSpYUy02KbX0Y6gfbIo/fne4+3h25A12h4U5qdSMgBV8J7vCTRQMJOL62kM6AHi3MExBoMyMrkoYUPgVZxhXDjbROqjXzFg1bbtnNe0jA4BeP1v2sxRbSXXiUBgWkznhMINPKoXI0xauqkFmwYvzZEvgObJZjyemf21YIKejlZ8wcJFAZstlJtDSreIkdKeuMVGtIoJTqA6q1yyF9l0DJN83T24uLfUIqjbRhpfJFwZcFNob1kUi2kNw+L/msTiFtpo1YSPEitUo7udouOpbWIkutC+SqI1Mjv0RmUaKKAfyKbFXgZlHqpSxO6M24WtityoZn61DbYLt+4MWQWPgkUHHDS3TEsHQ2EHapHK4DHUupZOlLSCcJ4CYrvJUP+HszGJ1Y8k18vJxMfjq7vHx7cf7T+eTy8myfgCTSVtzlkNSsASkgeULM/ZxGbKWVjQR7O5nQT5/773HFfeHYbS0J/xqfrc8ytHbYsSVay9cDfN8nQHvveXjZJYny5RJNiPvgLvpv9smPnXAkJu2iMrLkZrtw+h5PmBRs8f6FTuMP3HHz0nBUBd8uGhY8u2BlJCqxyAxyh2LB3YtW9cWPCRzqyYnpfXefzuLGDea2FG0I2KPXEXmOmHKSFoMc6AR8KLoDoey9d+8t5wlwISQp48W0s01XvLD49JWbxNBmgXrPPwdDQOeT8++axBx33l5rgYPZJutPtBRAY7QZnDmZ+XpV9+zIaa0htdpGSQv5Mv9d/vDfP/DfxXcuov8v/wX9LtciNjAZdTChkUlh/HA2pixg6++dFHtIwKJ5aFoqbwpIYVcZ7XSmi306Hu9ybd0+3VXaOBLvx+XaW6dLdhcwIIEHbiQl9uDaBob+o6JWawa5cxUk4ac+J4TAQtrM9PE/5sgaGDoyCOY0y3RZekUBR/YoXc5cjiy+BvmfDI4MaqALnfEiDA/h0wTVB6YNu5kyLoRBa5leBdypLyw3poNPnujjv5tM3g2bro1jWrHHXGb5AUpaZrxSsVjtk8btwSfpeLzV3oyqKDuSyjquMnyT6fJIxTRWj8b93wCVDpr8XhrMXA3HrkIRYa+Ci+lNXj/RkI7HB88OA35opgfB5kS+zBvptoF9vJK/4fbKE1tnc9Jl0Vqp1WGos+KO9nfkWXfdYc/FQah7+Ry5CAStu/m/RlfTm1EU6Jt8Nb1h97hl3LucqntMK8xbOrQelrEacJ/0bTyhPtP6XmKrvg7JkfK7iDVacqL6oAkRypLmR1zmWt/fYWbQfePr11wcxcVHhvwZoYdN4N7p0RoV3QagYDZAvGF3WnFjxrdcEMlr4yzjBlmm1UquvUHRbFhpaxcGtJKTiqLYvgntG+Xw2/ZK4JcNL6vYVNf2HxLqoc/vDMWjRTvQPUG0o08PCrERfXo0GBptDwO92V77X99bHBr++DzQ4odzJEVppUP0pKPl0KSKq+nNIEOFzjyZEcOy0uaQXEaMs2mBG/ZI2b+Q1rFwLjataAwU40qwGCxK3GhshP/jLCY560quOj6v73B8vPDpmbRri/GzVz01Px1u3LgquAzHlZBTdnWhmsHDWd2w2s5vKgUVjJjdZ7Db0Sb5ZIr9noa/eDSUReZtDQo5RUhL/0VdIr9i+Kvbuhi/ZqfMbPaW2oZSV3h6ggTucRtvCPZzqj9xs5H2OHEddYw+0vJ24VHXQhkvrqBsWbmvys47hX569fH6V0hgWd+flaFpAcMf6eTBH6OVOrx0yJhhbAcFV2sfWhWIoPT5Gz4j9ZY= sidebar_class_name: "patch api-method" info_path: docs/api/pulsarr-api custom_edit_url: null @@ -47,7 +47,7 @@ Update an existing user by ID diff --git a/docs/docs/intro.md b/docs/docs/intro.md index 8a94bd39d..f875f4e90 100644 --- a/docs/docs/intro.md +++ b/docs/docs/intro.md @@ -38,6 +38,7 @@ Real-time RSS monitoring requires Plex Pass. Without it, Pulsarr polls every 5 m | **User Management** | Granular controls, tag-based tracking, automatic multi-user monitoring | | **Plex Label Sync** | Sync user labels to Plex content, import Radarr/Sonarr tags as Plex labels | | **Delete Sync** | Automatically remove content when users remove from watchlists | +| **Watchlist Exclusions** | Prevent specific watchlist items from being routed to Sonarr/Radarr | | **Session Monitoring** | Auto-search next seasons when users near end of current season | | **Plex Library Refresh** | Auto-configure webhooks in Sonarr/Radarr to refresh Plex libraries instantly | | **Playlist Protection** | Preserve important content from automatic deletion | diff --git a/docs/docs/utilities/watchlist-exclusions.md b/docs/docs/utilities/watchlist-exclusions.md new file mode 100644 index 000000000..a8c1fc6db --- /dev/null +++ b/docs/docs/utilities/watchlist-exclusions.md @@ -0,0 +1,57 @@ +import useBaseUrl from '@docusaurus/useBaseUrl'; + +# Watchlist Exclusions + +Prevent specific watchlist items from being routed to Sonarr and Radarr, allowing you to control which content gets added to your library. + +## Quick Setup + +1. Navigate to **Utilities → Watchlist Exclusions** +2. Find the item you want to exclude using search or filters +3. Click **Exclude** on the item to block it from being added to Sonarr/Radarr +4. Click **Unexclude** to unblock it + +Watchlist Exclusions Interface + +## How It Works + +When a user adds something to their Plex watchlist, the sync engine normally routes it to Sonarr or Radarr. An exclusion is a per-user veto on that routing for a specific item — the sync engine sees the item, checks for an exclusion, and skips it if one exists. + +Common reasons to use this: + +- **You don't want a title auto-requested** even though a user has it watchlisted (e.g. content you've chosen not to host) +- **Prevent re-request loops after Delete Sync** — when content is removed but stays on a user's watchlist, an exclusion stops the next sync from re-requesting it + +Exclusions clear automatically when the user removes the item from their Plex watchlist, so re-adding it later works normally. + +:::info Interaction with Delete Sync +Excluded items are treated as unwatchlisted by Delete Sync. If you exclude something that's already in Sonarr/Radarr, the next Delete Sync run will remove it from your library. +::: + +## Page Features + +The Watchlist Exclusions page shows all users' watchlist items in a sortable, filterable table: + +| Feature | Description | +|---------|-------------| +| **Search** | Filter items by title | +| **User Filter** | Show items for specific users | +| **Type Filter** | Filter by Movie or Show | +| **Sorting** | Sort by title, status, or date added (default: newest first) | + +## Best Practices + +- Prefer excluding over asking users to remove items from their watchlists — the exclusion approach lets the item stay watchlisted (so they can still see it in Plex) without triggering a request +- Exclusions are per-user. If you want to block something across everyone, you'll need to exclude it for each user + +## Troubleshooting + +| Problem | Solution | +|---------|----------| +| **Item still being requested** | Verify the exclusion exists for the correct user; check sync engine logs | +| **Exclusion disappeared** | User likely removed the item from their Plex watchlist, which clears exclusions automatically | +| **Item not showing in table** | Item may not be on any user's watchlist; check Plex watchlist status | + +## API Reference + +See the [Watchlist Exclusions API documentation](/docs/api/watchlist-exclusions) for detailed endpoint information. diff --git a/docs/sidebars.ts b/docs/sidebars.ts index f65b0a31b..f5263df3a 100644 --- a/docs/sidebars.ts +++ b/docs/sidebars.ts @@ -60,6 +60,7 @@ const sidebars: SidebarsConfig = { 'utilities/public-content-notifications', 'utilities/session-monitoring', 'utilities/user-tagging', + 'utilities/watchlist-exclusions', ], }, { diff --git a/docs/static/img/Watchlist-Exclusions.png b/docs/static/img/Watchlist-Exclusions.png new file mode 100644 index 000000000..93ca36a70 Binary files /dev/null and b/docs/static/img/Watchlist-Exclusions.png differ diff --git a/docs/static/openapi.json b/docs/static/openapi.json index b0d921261..0d14ba21b 100644 --- a/docs/static/openapi.json +++ b/docs/static/openapi.json @@ -532,21 +532,25 @@ "properties": { "days": { "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 365 }, "hours": { "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 8760 }, "minutes": { "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 525600 }, "seconds": { "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 31536000 }, @@ -813,21 +817,25 @@ "properties": { "days": { "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 365 }, "hours": { "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 8760 }, "minutes": { "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 525600 }, "seconds": { "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 31536000 }, @@ -993,21 +1001,25 @@ "properties": { "days": { "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 365 }, "hours": { "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 8760 }, "minutes": { "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 525600 }, "seconds": { "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 31536000 }, @@ -1301,21 +1313,25 @@ "properties": { "days": { "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 365 }, "hours": { "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 8760 }, "minutes": { "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 525600 }, "seconds": { "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 31536000 }, @@ -2783,6 +2799,7 @@ { "schema": { "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 9007199254740991 }, @@ -2917,11 +2934,13 @@ "properties": { "sonarrSeriesId": { "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 9007199254740991 }, "sonarrInstanceId": { "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 9007199254740991 }, @@ -20252,6 +20271,7 @@ { "schema": { "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 9007199254740991 }, @@ -20477,6 +20497,7 @@ { "schema": { "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 9007199254740991 }, @@ -27773,6 +27794,7 @@ "plexPort": { "default": 32400, "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 9007199254740991 }, @@ -29516,6 +29538,7 @@ "properties": { "instanceId": { "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 9007199254740991 }, @@ -29544,6 +29567,7 @@ "properties": { "id": { "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 9007199254740991 }, @@ -29671,6 +29695,7 @@ { "schema": { "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 9007199254740991 }, @@ -29695,6 +29720,7 @@ "properties": { "id": { "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 9007199254740991 }, @@ -30042,6 +30068,7 @@ "properties": { "id": { "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 9007199254740991 } @@ -30481,6 +30508,7 @@ { "schema": { "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 9007199254740991 }, @@ -30661,6 +30689,7 @@ { "schema": { "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 9007199254740991 }, @@ -30949,6 +30978,7 @@ "schema": { "default": 10, "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 9007199254740991 }, @@ -31703,6 +31733,7 @@ "schema": { "default": 10, "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 9007199254740991 }, @@ -31817,6 +31848,7 @@ "schema": { "default": 10, "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 9007199254740991 }, @@ -31975,6 +32007,7 @@ "schema": { "default": 10, "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 9007199254740991 }, @@ -32133,6 +32166,7 @@ "schema": { "default": 10, "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 9007199254740991 }, @@ -33275,6 +33309,7 @@ "properties": { "instanceId": { "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 9007199254740991 }, @@ -33303,6 +33338,7 @@ "properties": { "id": { "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 9007199254740991 }, @@ -33430,6 +33466,7 @@ { "schema": { "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 9007199254740991 }, @@ -33454,6 +33491,7 @@ "properties": { "id": { "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 9007199254740991 }, @@ -33597,6 +33635,7 @@ { "schema": { "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 9007199254740991 }, @@ -33777,6 +33816,7 @@ { "schema": { "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 9007199254740991 }, @@ -34252,6 +34292,7 @@ "properties": { "id": { "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 9007199254740991 } @@ -34853,6 +34894,7 @@ { "schema": { "type": "integer", + "minimum": 0, "exclusiveMinimum": true, "maximum": 9007199254740991 }, diff --git a/migrations/migrations/091_20260516_add_watchlist_exclusions.ts b/migrations/migrations/091_20260516_add_watchlist_exclusions.ts new file mode 100644 index 000000000..e06f05eed --- /dev/null +++ b/migrations/migrations/091_20260516_add_watchlist_exclusions.ts @@ -0,0 +1,26 @@ +import type { Knex } from 'knex' + +export async function up(knex: Knex): Promise { + await knex.schema.createTable('watchlist_exclusions', (table) => { + table.increments('id').primary() + table + .integer('user_id') + .notNullable() + .references('id') + .inTable('users') + .onDelete('CASCADE') + table.string('key').notNullable() + table.string('title').notNullable() + table.string('type').notNullable() + table.json('guids').notNullable().defaultTo('[]') + table.timestamp('excluded_at').notNullable().defaultTo(knex.fn.now()) + + table.unique(['user_id', 'key']) + table.index('user_id') + table.index('key') + }) +} + +export async function down(knex: Knex): Promise { + await knex.schema.dropTableIfExists('watchlist_exclusions') +} diff --git a/package.json b/package.json index 9cb83bc48..c96e82866 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pulsarr", - "version": "0.15.4", + "version": "0.15.5", "description": "Plex watchlist tracker and notification center that integrates with the Arr stack", "main": "build/server.js", "type": "module", diff --git a/src/client/components/AppSidebar.tsx b/src/client/components/AppSidebar.tsx index 54f8a66ad..e7664d02d 100644 --- a/src/client/components/AppSidebar.tsx +++ b/src/client/components/AppSidebar.tsx @@ -200,6 +200,10 @@ const data = { title: 'User Tags', url: '/utilities/user-tags', }, + { + title: 'Watchlist Exclusions', + url: '/utilities/watchlist-exclusions', + }, ], }, ], diff --git a/src/client/components/table/data-table-select-column.tsx b/src/client/components/table/data-table-select-column.tsx new file mode 100644 index 000000000..1d819a9d2 --- /dev/null +++ b/src/client/components/table/data-table-select-column.tsx @@ -0,0 +1,38 @@ +import type { ColumnDef, Row, Table } from '@tanstack/react-table' +import { Checkbox } from '@/components/ui/checkbox' + +interface SelectColumnOptions { + disabled?: (row: Row) => boolean + headerDisabled?: (table: Table) => boolean + meta?: ColumnDef['meta'] +} + +export function createSelectColumn( + options: SelectColumnOptions = {}, +): ColumnDef { + return { + id: 'select', + header: ({ table }) => ( + table.toggleAllPageRowsSelected(!!value)} + aria-label="Select all" + disabled={options.headerDisabled?.(table) ?? false} + /> + ), + cell: ({ row }) => ( + row.toggleSelected(!!value)} + aria-label="Select row" + disabled={!row.getCanSelect() || (options.disabled?.(row) ?? false)} + /> + ), + enableSorting: false, + enableHiding: false, + ...(options.meta !== undefined && { meta: options.meta }), + } +} diff --git a/src/client/features/approvals/components/approval-table-columns.tsx b/src/client/features/approvals/components/approval-table-columns.tsx index 9d52c0fc4..d1b8cefdf 100644 --- a/src/client/features/approvals/components/approval-table-columns.tsx +++ b/src/client/features/approvals/components/approval-table-columns.tsx @@ -13,9 +13,9 @@ import { XCircle, } from 'lucide-react' +import { createSelectColumn } from '@/components/table/data-table-select-column' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' -import { Checkbox } from '@/components/ui/checkbox' import { DropdownMenu, DropdownMenuContent, @@ -39,28 +39,7 @@ interface ApprovalTableActions { export const createApprovalColumns = ( actions: ApprovalTableActions, ): ColumnDef[] => [ - { - id: 'select', - header: ({ table }) => ( - table.toggleAllPageRowsSelected(!!value)} - aria-label="Select all" - /> - ), - cell: ({ row }) => ( - row.toggleSelected(!!value)} - aria-label="Select row" - /> - ), - enableSorting: false, - enableHiding: false, - }, + createSelectColumn(), { accessorKey: 'contentTitle', id: 'contentTitle', diff --git a/src/client/features/plex/components/user/user-table.tsx b/src/client/features/plex/components/user/user-table.tsx index ee756836c..d218249f5 100644 --- a/src/client/features/plex/components/user/user-table.tsx +++ b/src/client/features/plex/components/user/user-table.tsx @@ -24,9 +24,9 @@ import { import * as React from 'react' import { useId } from 'react' import { toast } from 'sonner' +import { createSelectColumn } from '@/components/table/data-table-select-column' import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' import { Button } from '@/components/ui/button' -import { Checkbox } from '@/components/ui/checkbox' import { DropdownMenu, DropdownMenuCheckboxItem, @@ -143,30 +143,10 @@ export default function UserTable({ } = useUserWatchlist() const columns: ColumnDef[] = [ - { - id: 'select', - header: ({ table }) => ( - table.toggleAllPageRowsSelected(!!value)} - aria-label="Select all" - disabled={isLoading} - /> - ), - cell: ({ row }) => ( - row.toggleSelected(!!value)} - aria-label="Select row" - disabled={isLoading || !row.getCanSelect()} - /> - ), - enableSorting: false, - enableHiding: false, - }, + createSelectColumn({ + disabled: (row) => isLoading || !row.getCanSelect(), + headerDisabled: () => isLoading, + }), { accessorKey: 'name', meta: { diff --git a/src/client/features/utilities/components/session-monitoring/manage-rolling-sheet.tsx b/src/client/features/utilities/components/session-monitoring/manage-rolling-sheet.tsx index 224ca7e33..473d7502d 100644 --- a/src/client/features/utilities/components/session-monitoring/manage-rolling-sheet.tsx +++ b/src/client/features/utilities/components/session-monitoring/manage-rolling-sheet.tsx @@ -24,9 +24,9 @@ import { X, } from 'lucide-react' import * as React from 'react' +import { createSelectColumn } from '@/components/table/data-table-select-column' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' -import { Checkbox } from '@/components/ui/checkbox' import { Drawer, DrawerContent, @@ -124,32 +124,12 @@ export function ManageRollingSheet({ }, [isMultiInstance]) const columns: ColumnDef[] = [ - { - id: 'select', - header: ({ table }) => ( - table.toggleAllPageRowsSelected(!!value)} - aria-label="Select all" - /> - ), - cell: ({ row }) => ( - row.toggleSelected(!!value)} - aria-label="Select row" - /> - ), - enableSorting: false, - enableHiding: false, + createSelectColumn({ meta: { className: 'w-10', headerClassName: 'w-10', }, - }, + }), { accessorKey: 'title', header: ({ column }) => { diff --git a/src/client/features/utilities/components/watchlist-exclusions/watchlist-exclusions-bulk-modal.tsx b/src/client/features/utilities/components/watchlist-exclusions/watchlist-exclusions-bulk-modal.tsx new file mode 100644 index 000000000..45aa0fe00 --- /dev/null +++ b/src/client/features/utilities/components/watchlist-exclusions/watchlist-exclusions-bulk-modal.tsx @@ -0,0 +1,189 @@ +import { AlertTriangle, Ban, Check, Loader2 } from 'lucide-react' +import { useId, useMemo, useState } from 'react' +import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' +import { Button } from '@/components/ui/button' +import { Checkbox } from '@/components/ui/checkbox' +import { + Credenza, + CredenzaBody, + CredenzaContent, + CredenzaDescription, + CredenzaHeader, + CredenzaTitle, +} from '@/components/ui/credenza' +import type { WatchlistExclusionTableRow } from '@/features/utilities/components/watchlist-exclusions/watchlist-exclusions-table-columns' + +export type BulkExclusionScope = 'per-user' | 'global' +export type BulkExclusionStatus = 'idle' | 'loading' | 'success' | 'error' + +interface WatchlistExclusionsBulkModalProps { + open: boolean + onOpenChange: (open: boolean) => void + selectedRows: WatchlistExclusionTableRow[] + onBulkExclude: ( + rows: WatchlistExclusionTableRow[], + scope: BulkExclusionScope, + ) => Promise | void + actionStatus: BulkExclusionStatus +} + +const IN_LIBRARY_STATUSES = new Set(['grabbed', 'notified']) + +export function WatchlistExclusionsBulkModal({ + open, + onOpenChange, + selectedRows, + onBulkExclude, + actionStatus, +}: WatchlistExclusionsBulkModalProps) { + const [scope, setScope] = useState('per-user') + const globalScopeId = useId() + + const { excludableRows, alreadyExcludedCount, inLibraryCount } = + useMemo(() => { + const excludable: WatchlistExclusionTableRow[] = [] + let alreadyExcluded = 0 + let inLibrary = 0 + for (const row of selectedRows) { + if (row.isExcluded) { + alreadyExcluded++ + } else { + excludable.push(row) + if (row.status && IN_LIBRARY_STATUSES.has(row.status)) inLibrary++ + } + } + return { + excludableRows: excludable, + alreadyExcludedCount: alreadyExcluded, + inLibraryCount: inLibrary, + } + }, [selectedRows]) + + const canExclude = excludableRows.length > 0 + const isLoading = actionStatus === 'loading' + const isBusy = actionStatus !== 'idle' + + const handleOpenChange = (next: boolean) => { + if (isLoading) return + onOpenChange(next) + } + + const handleExclude = () => onBulkExclude(excludableRows, scope) + + return ( + + + + + Bulk Exclude Watchlist Items + + + Excluding {excludableRows.length} of {selectedRows.length} selected + watchlist {selectedRows.length === 1 ? 'item' : 'items'} + + + + + + + Warning + + You are about to exclude {excludableRows.length} watchlist{' '} + {excludableRows.length === 1 ? 'item' : 'items'}. Future sync + cycles will skip routing for{' '} + {excludableRows.length === 1 ? 'it' : 'them'}. + + + + {alreadyExcludedCount > 0 && ( + + + + {alreadyExcludedCount}{' '} + {alreadyExcludedCount === 1 ? 'item is' : 'items are'} already + excluded and will be skipped. + + + )} + + {canExclude && inLibraryCount > 0 && ( + + + + {inLibraryCount}{' '} + {inLibraryCount === 1 ? 'item is' : 'items are'} already in your + library. The next Delete Sync run will remove{' '} + {inLibraryCount === 1 ? 'it' : 'them'}. + + + )} + + {canExclude && ( + <> +
+ + setScope(checked ? 'global' : 'per-user') + } + disabled={isBusy} + /> + +
+

+ When enabled, vetoes routing of these keys for every current and + future user. Otherwise, excludes each item only for the user who + watchlisted it. +

+ + )} + +
+
+ {canExclude && ( + + )} +
+ +
+ +
+
+
+
+
+ ) +} diff --git a/src/client/features/utilities/components/watchlist-exclusions/watchlist-exclusions-bulk-remove-modal.tsx b/src/client/features/utilities/components/watchlist-exclusions/watchlist-exclusions-bulk-remove-modal.tsx new file mode 100644 index 000000000..155dcd581 --- /dev/null +++ b/src/client/features/utilities/components/watchlist-exclusions/watchlist-exclusions-bulk-remove-modal.tsx @@ -0,0 +1,101 @@ +import { AlertTriangle, Check, Loader2, Trash2 } from 'lucide-react' +import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' +import { Button } from '@/components/ui/button' +import { + Credenza, + CredenzaBody, + CredenzaContent, + CredenzaDescription, + CredenzaHeader, + CredenzaTitle, +} from '@/components/ui/credenza' +import type { BulkExclusionStatus } from '@/features/utilities/components/watchlist-exclusions/watchlist-exclusions-bulk-modal' + +interface WatchlistExclusionsBulkRemoveModalProps { + open: boolean + onOpenChange: (open: boolean) => void + onConfirm: () => Promise + actionStatus: BulkExclusionStatus + count: number +} + +export function WatchlistExclusionsBulkRemoveModal({ + open, + onOpenChange, + onConfirm, + actionStatus, + count, +}: WatchlistExclusionsBulkRemoveModalProps) { + const isLoading = actionStatus === 'loading' + const isBusy = actionStatus !== 'idle' + + const handleOpenChange = (next: boolean) => { + if (isLoading) return + onOpenChange(next) + } + + return ( + + + + + Bulk Remove Exclusions + + + Removing {count} {count === 1 ? 'exclusion' : 'exclusions'} + + + + + + + Warning + + You are about to remove {count}{' '} + {count === 1 ? 'exclusion' : 'exclusions'}. Any matching items + still on a watchlist may be routed to Sonarr/Radarr during the + next sync cycle. + + + +
+
+ +
+ +
+ +
+
+
+
+
+ ) +} diff --git a/src/client/features/utilities/components/watchlist-exclusions/watchlist-exclusions-delete-confirmation-modal.tsx b/src/client/features/utilities/components/watchlist-exclusions/watchlist-exclusions-delete-confirmation-modal.tsx new file mode 100644 index 000000000..d0cec3978 --- /dev/null +++ b/src/client/features/utilities/components/watchlist-exclusions/watchlist-exclusions-delete-confirmation-modal.tsx @@ -0,0 +1,100 @@ +import { AlertTriangle, Check, Loader2, Trash2 } from 'lucide-react' +import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' +import { Button } from '@/components/ui/button' +import { + Credenza, + CredenzaBody, + CredenzaContent, + CredenzaDescription, + CredenzaHeader, + CredenzaTitle, +} from '@/components/ui/credenza' +import type { BulkExclusionStatus } from '@/features/utilities/components/watchlist-exclusions/watchlist-exclusions-bulk-modal' + +interface WatchlistExclusionsDeleteConfirmationModalProps { + open: boolean + onOpenChange: (open: boolean) => void + onConfirm: () => Promise + actionStatus: BulkExclusionStatus + username: string +} + +export function WatchlistExclusionsDeleteConfirmationModal({ + open, + onOpenChange, + onConfirm, + actionStatus, + username, +}: WatchlistExclusionsDeleteConfirmationModalProps) { + const isLoading = actionStatus === 'loading' + const isBusy = actionStatus !== 'idle' + + const handleOpenChange = (next: boolean) => { + if (isLoading) return + onOpenChange(next) + } + + return ( + + + + + Remove Exclusion + + + Removing exclusion for {username} + + + + + + + Warning + + Are you sure you want to remove this exclusion for {username}? If + this item is still on their watchlist, it will be routed to + Sonarr/Radarr during the next sync cycle. + + + +
+
+ +
+ +
+ +
+
+
+
+
+ ) +} diff --git a/src/client/features/utilities/components/watchlist-exclusions/watchlist-exclusions-exclude-confirmation-modal.tsx b/src/client/features/utilities/components/watchlist-exclusions/watchlist-exclusions-exclude-confirmation-modal.tsx new file mode 100644 index 000000000..77416f1e5 --- /dev/null +++ b/src/client/features/utilities/components/watchlist-exclusions/watchlist-exclusions-exclude-confirmation-modal.tsx @@ -0,0 +1,105 @@ +import { AlertTriangle, Ban, Check, Loader2 } from 'lucide-react' +import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' +import { Button } from '@/components/ui/button' +import { + Credenza, + CredenzaBody, + CredenzaContent, + CredenzaDescription, + CredenzaHeader, + CredenzaTitle, +} from '@/components/ui/credenza' +import type { BulkExclusionStatus } from '@/features/utilities/components/watchlist-exclusions/watchlist-exclusions-bulk-modal' + +interface WatchlistExclusionsExcludeConfirmationModalProps { + open: boolean + onOpenChange: (open: boolean) => void + onConfirm: () => Promise + actionStatus: BulkExclusionStatus + title: string + username: string + status: string +} + +export function WatchlistExclusionsExcludeConfirmationModal({ + open, + onOpenChange, + onConfirm, + actionStatus, + title, + username, + status, +}: WatchlistExclusionsExcludeConfirmationModalProps) { + const isInLibrary = status === 'grabbed' || status === 'notified' + const isLoading = actionStatus === 'loading' + const isBusy = actionStatus !== 'idle' + + const handleOpenChange = (next: boolean) => { + if (isLoading) return + onOpenChange(next) + } + + return ( + + + + + Exclude Watchlist Item + + + Excluding "{title}" for {username} + + + + + + + Warning + + {isInLibrary + ? `This will block "${title}" from being routed for ${username}. "${title}" is already in your library, so the next Delete Sync run will remove it.` + : `This will block "${title}" from being routed for ${username} on future sync cycles.`} + + + +
+
+ +
+ +
+ +
+
+
+
+
+ ) +} diff --git a/src/client/features/utilities/components/watchlist-exclusions/watchlist-exclusions-skeleton.tsx b/src/client/features/utilities/components/watchlist-exclusions/watchlist-exclusions-skeleton.tsx new file mode 100644 index 000000000..bf704c65e --- /dev/null +++ b/src/client/features/utilities/components/watchlist-exclusions/watchlist-exclusions-skeleton.tsx @@ -0,0 +1,59 @@ +import { Skeleton } from '@/components/ui/skeleton' +import { TableSkeleton } from '@/components/ui/table-skeleton' +import { UtilitySectionHeader } from '@/components/ui/utility-section-header' + +export function WatchlistExclusionsSkeleton() { + return ( +
+ + +
+
+ +
+
+ + + +
+
+ + +
+
+ + +
+ +
+ + +
+
+
+ ) +} diff --git a/src/client/features/utilities/components/watchlist-exclusions/watchlist-exclusions-table-columns.tsx b/src/client/features/utilities/components/watchlist-exclusions/watchlist-exclusions-table-columns.tsx new file mode 100644 index 000000000..7dcbb84a5 --- /dev/null +++ b/src/client/features/utilities/components/watchlist-exclusions/watchlist-exclusions-table-columns.tsx @@ -0,0 +1,327 @@ +import type { ColumnDef } from '@tanstack/react-table' +import { + ArrowUpDown, + Ban, + CheckCircle, + Film, + Loader2, + Trash2, + Tv, +} from 'lucide-react' +import { createSelectColumn } from '@/components/table/data-table-select-column' +import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from '@/components/ui/tooltip' +import type { + useCreateWatchlistExclusion, + useRemoveWatchlistExclusion, +} from '@/features/utilities/hooks/useWatchlistExclusionMutations' + +export type WatchlistExclusionRowKind = 'watchlist' | 'global' | 'orphan-user' + +export const GLOBAL_USER_LABEL = 'Global' + +export interface WatchlistExclusionTableRow { + id: string + rowKind: WatchlistExclusionRowKind + title: string + key: string + type: string + status: string | null + added: string | null + excluded_at: string | null + guids: string[] + userId: number + username: string + isExcluded: boolean + exclusionId: number | null + isGloballyBlocked: boolean +} + +interface WatchlistExclusionColumnsProps { + onExclude: (row: WatchlistExclusionTableRow) => void + onRemove: (row: WatchlistExclusionTableRow) => void + createMutation: ReturnType + removeMutation: ReturnType +} + +const statusBadgeOrder = ['pending', 'requested', 'grabbed', 'notified'] + +function dateTimeOrNegInfinity(value: string | null): number { + return value ? new Date(value).getTime() : Number.NEGATIVE_INFINITY +} + +export function createWatchlistExclusionColumns({ + onExclude, + onRemove, + createMutation, + removeMutation, +}: WatchlistExclusionColumnsProps): ColumnDef[] { + return [ + createSelectColumn({ + meta: { className: 'w-10', headerClassName: 'w-10' }, + }), + { + accessorKey: 'title', + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const type = row.original.type + const Icon = type === 'movie' ? Film : Tv + return ( +
+ +
+
+ {row.getValue('title')} +
+
+ {type} +
+
+
+ ) + }, + }, + { + accessorKey: 'username', + header: () =>
User
, + cell: ({ row }) => { + if (row.original.rowKind === 'global') { + return ( + + Global + + ) + } + return ( +
{row.getValue('username')}
+ ) + }, + enableColumnFilter: false, + }, + { + id: 'userId', + accessorFn: (row) => String(row.userId), + header: () => null, + cell: () => null, + enableSorting: false, + enableHiding: false, + size: 0, + minSize: 0, + maxSize: 0, + filterFn: (row, id, filterValue: string[]) => { + if (!filterValue?.length) return true + return filterValue.includes(row.getValue(id) as string) + }, + }, + { + accessorKey: 'type', + id: 'type', + header: () => null, + cell: () => null, + enableSorting: false, + enableHiding: false, + size: 0, + minSize: 0, + maxSize: 0, + filterFn: (row, id, filterValue: string[]) => { + if (!filterValue?.length) return true + return filterValue.includes(row.getValue(id) as string) + }, + }, + { + accessorKey: 'status', + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const status = row.original.status + const showGlobalBadge = + row.original.rowKind === 'watchlist' && row.original.isGloballyBlocked + return ( +
+ {status ? ( + + {status} + + ) : ( + - + )} + {showGlobalBadge && ( + + Global + + )} +
+ ) + }, + sortingFn: (rowA, rowB) => { + const a = rowA.original.status + const b = rowB.original.status + const ia = a ? statusBadgeOrder.indexOf(a) : -1 + const ib = b ? statusBadgeOrder.indexOf(b) : -1 + return ia - ib + }, + meta: { + className: 'w-25', + }, + }, + { + accessorKey: 'added', + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const added = row.getValue('added') as string | null + if (!added) return - + + const date = new Date(added) + return ( + + {date.toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric', + })} + + ) + }, + sortingFn: (rowA, rowB) => + dateTimeOrNegInfinity(rowA.getValue('added') as string | null) - + dateTimeOrNegInfinity(rowB.getValue('added') as string | null), + }, + { + accessorKey: 'excluded_at', + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const excludedAt = row.getValue('excluded_at') as string | null + if (!excludedAt) return - + + const date = new Date(excludedAt) + return ( + + {date.toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric', + })} + + ) + }, + sortingFn: (rowA, rowB) => + dateTimeOrNegInfinity(rowA.getValue('excluded_at') as string | null) - + dateTimeOrNegInfinity(rowB.getValue('excluded_at') as string | null), + }, + { + id: 'actions', + enableSorting: false, + enableHiding: false, + header: () =>
Actions
, + cell: ({ row }) => { + if (row.original.isExcluded) { + const isRemoving = + removeMutation.isPending && + removeMutation.variables === row.original.exclusionId + return ( +
+ + + Excluded + + + + + + +

Remove exclusion

+
+
+
+ ) + } + + const isExcluding = + createMutation.isPending && + createMutation.variables?.key === row.original.key && + createMutation.variables?.userIds.includes(row.original.userId) + + return ( +
+ +
+ ) + }, + meta: { + className: 'w-44', + }, + }, + ] +} diff --git a/src/client/features/utilities/components/watchlist-exclusions/watchlist-exclusions-table-toolbar.tsx b/src/client/features/utilities/components/watchlist-exclusions/watchlist-exclusions-table-toolbar.tsx new file mode 100644 index 000000000..1f18be2b4 --- /dev/null +++ b/src/client/features/utilities/components/watchlist-exclusions/watchlist-exclusions-table-toolbar.tsx @@ -0,0 +1,167 @@ +import type { Table } from '@tanstack/react-table' +import { + ChevronDown, + Edit, + Film, + Loader2, + RefreshCw, + Trash2, + Tv, + Users, + X, +} from 'lucide-react' +import { DataTableFacetedFilter } from '@/components/table/data-table-faceted-filter' +import { Button } from '@/components/ui/button' +import { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu' +import { Input } from '@/components/ui/input' +import type { WatchlistExclusionTableRow } from '@/features/utilities/components/watchlist-exclusions/watchlist-exclusions-table-columns' + +interface WatchlistExclusionsTableToolbarProps { + table: Table + userFilterOptions: Array<{ label: string; value: string }> + isFiltered: boolean + onResetFilters: () => void + isRefreshing: boolean + onRefresh: () => void + onBulkExclude?: (selectedRows: WatchlistExclusionTableRow[]) => void + onBulkRemove?: (selectedRows: WatchlistExclusionTableRow[]) => void +} + +const typeFilterOptions = [ + { label: 'Movie', value: 'movie', icon: Film }, + { label: 'Show', value: 'show', icon: Tv }, +] + +export function WatchlistExclusionsTableToolbar({ + table, + userFilterOptions, + isFiltered, + onResetFilters, + isRefreshing, + onRefresh, + onBulkExclude, + onBulkRemove, +}: WatchlistExclusionsTableToolbarProps) { + const selectedRows = table + .getFilteredSelectedRowModel() + .rows.map((r) => r.original) + const excludableSelected = selectedRows.filter( + (r) => r.rowKind === 'watchlist' && !r.isExcluded, + ) + const removableSelected = selectedRows.filter((r) => r.isExcluded) + + return ( +
+
+ + table.getColumn('title')?.setFilterValue(event.target.value) + } + className="w-full max-w-sm min-w-0" + /> +
+ + {(excludableSelected.length > 0 || removableSelected.length > 0) && ( +
+ {onBulkExclude && excludableSelected.length > 0 && ( + + )} + {onBulkRemove && removableSelected.length > 0 && ( + + )} +
+ )} + +
+
+ {userFilterOptions.length > 0 && ( + 5} + /> + )} + +
+
+ + {isFiltered && ( + + )} + + + + + + {table + .getAllColumns() + .filter((column) => column.getCanHide()) + .map((column) => ( + + column.toggleVisibility(!!value) + } + > + {column.id} + + ))} + + +
+
+
+ ) +} diff --git a/src/client/features/utilities/components/watchlist-exclusions/watchlist-exclusions-table.tsx b/src/client/features/utilities/components/watchlist-exclusions/watchlist-exclusions-table.tsx new file mode 100644 index 000000000..f634decb2 --- /dev/null +++ b/src/client/features/utilities/components/watchlist-exclusions/watchlist-exclusions-table.tsx @@ -0,0 +1,307 @@ +import { + type ColumnFiltersState, + flexRender, + getCoreRowModel, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + type SortingState, + useReactTable, + type VisibilityState, +} from '@tanstack/react-table' +import { ChevronLeft, ChevronRight, ListX } from 'lucide-react' +import * as React from 'react' +import { Button } from '@/components/ui/button' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select' +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/ui/table' +import { + createWatchlistExclusionColumns, + type WatchlistExclusionTableRow, +} from '@/features/utilities/components/watchlist-exclusions/watchlist-exclusions-table-columns' +import { WatchlistExclusionsTableToolbar } from '@/features/utilities/components/watchlist-exclusions/watchlist-exclusions-table-toolbar' +import type { + useCreateWatchlistExclusion, + useRemoveWatchlistExclusion, +} from '@/features/utilities/hooks/useWatchlistExclusionMutations' +import { useTablePagination } from '@/hooks/use-table-pagination' + +interface ColumnMetaType { + className?: string + headerClassName?: string +} + +interface WatchlistExclusionsTableProps { + data: WatchlistExclusionTableRow[] + userFilterOptions: Array<{ label: string; value: string }> + isRefreshing: boolean + onRefresh: () => void + onExclude: (row: WatchlistExclusionTableRow) => void + onRemove: (row: WatchlistExclusionTableRow) => void + createMutation: ReturnType + removeMutation: ReturnType + onBulkExclude?: (selectedRows: WatchlistExclusionTableRow[]) => void + onBulkRemove?: (selectedRows: WatchlistExclusionTableRow[]) => void +} + +export interface WatchlistExclusionsTableRef { + clearSelection: () => void +} + +export const WatchlistExclusionsTable = React.forwardRef< + WatchlistExclusionsTableRef, + WatchlistExclusionsTableProps +>(function WatchlistExclusionsTable( + { + data, + userFilterOptions, + isRefreshing, + onRefresh, + onExclude, + onRemove, + createMutation, + removeMutation, + onBulkExclude, + onBulkRemove, + }, + ref, +) { + const [sorting, setSorting] = React.useState([ + { id: 'excluded_at', desc: true }, + ]) + const [columnFilters, setColumnFilters] = React.useState( + [], + ) + const [columnVisibility, setColumnVisibility] = + React.useState({ added: false }) + const [rowSelection, setRowSelection] = React.useState({}) + const { pageSize, setPageSize } = useTablePagination( + 'watchlist-exclusions', + 10, + ) + + React.useImperativeHandle(ref, () => ({ + clearSelection: () => setRowSelection({}), + })) + + const columns = createWatchlistExclusionColumns({ + onExclude, + onRemove, + createMutation, + removeMutation, + }) + + const table = useReactTable({ + data, + columns, + getRowId: (row) => row.id, + enableRowSelection: true, + onSortingChange: setSorting, + onColumnFiltersChange: setColumnFilters, + onRowSelectionChange: setRowSelection, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + getSortedRowModel: getSortedRowModel(), + getFilteredRowModel: getFilteredRowModel(), + onColumnVisibilityChange: setColumnVisibility, + autoResetPageIndex: false, + initialState: { + pagination: { + pageSize, + }, + }, + state: { + sorting, + columnFilters, + columnVisibility, + rowSelection, + }, + }) + + React.useEffect(() => { + table.setPageSize(pageSize) + }, [pageSize, table]) + + // Reset to first page when sorting or filters change, but not when row data + // updates (so excluding/unexcluding keeps you on the current page) + // biome-ignore lint/correctness/useExhaustiveDependencies: sorting and columnFilters are intentional re-run triggers + React.useEffect(() => { + table.setPageIndex(0) + }, [sorting, columnFilters, table]) + + return ( +
+ 0} + onResetFilters={() => setColumnFilters([])} + isRefreshing={isRefreshing} + onRefresh={onRefresh} + onBulkExclude={onBulkExclude} + onBulkRemove={onBulkRemove} + /> + +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + const headerClassName = `px-2 py-2 ${ + (header.column.columnDef.meta as ColumnMetaType) + ?.headerClassName || '' + }` + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext(), + )} + + ) + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => { + const cellClassName = `px-2 py-2 ${ + (cell.column.columnDef.meta as ColumnMetaType) + ?.className || '' + }` + return ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext(), + )} + + ) + })} + + )) + ) : ( + + + {data.length === 0 ? ( +
+ +

No watchlist items found

+

+ Watchlist items from configured users will appear here +

+
+ ) : ( + 'No results.' + )} +
+
+ )} +
+
+
+ +
+
+ +

+ per page +

+
+ +
+ {(() => { + const filteredCount = table.getFilteredRowModel().rows.length + const pageIndex = table.getState().pagination.pageIndex + const currentPageSize = table.getState().pagination.pageSize + const pageCount = table.getPageCount() + const start = pageIndex * currentPageSize + 1 + const end = Math.min( + (pageIndex + 1) * currentPageSize, + filteredCount, + ) + return ( + <> + + {filteredCount > 0 + ? `Showing ${start}-${end} of ${filteredCount}` + : 'No results'} + + + {filteredCount > 0 + ? `Page ${pageIndex + 1} of ${pageCount}` + : 'No results'} + + + ) + })()} +
+ +
+ + +
+
+
+ ) +}) diff --git a/src/client/features/utilities/hooks/useWatchlistExclusionMutations.ts b/src/client/features/utilities/hooks/useWatchlistExclusionMutations.ts new file mode 100644 index 000000000..f8cd8968f --- /dev/null +++ b/src/client/features/utilities/hooks/useWatchlistExclusionMutations.ts @@ -0,0 +1,64 @@ +import { + type CreateWatchlistExclusion, + type CreateWatchlistExclusionResponse, + CreateWatchlistExclusionResponseSchema, +} from '@root/schemas/watchlist-exclusions/watchlist-exclusions.schema' +import { apiClient } from '@/lib/apiClient' +import { queryClient } from '@/lib/queryClient' +import { useAppMutation } from '@/lib/useAppQuery' +import { watchlistExclusionKeys } from './useWatchlistExclusions' + +/** + * Invalidates watchlist exclusion caches. + * Called after mutations that change exclusion state. + */ +function invalidateWatchlistExclusionCaches() { + queryClient.invalidateQueries({ queryKey: watchlistExclusionKeys.all }) +} + +/** + * Mutation hook for creating a watchlist exclusion for one or more users. + * + * @example + * ```typescript + * const { mutateAsync, isPending } = useCreateWatchlistExclusion() + * await mutateAsync({ key: 'tt0111161', userIds: [1, 2] }) + * ``` + */ +export function useCreateWatchlistExclusion() { + return useAppMutation< + CreateWatchlistExclusionResponse, + Error, + CreateWatchlistExclusion + >({ + mutationFn: (body) => + apiClient.post( + '/v1/watchlist-exclusions', + body, + CreateWatchlistExclusionResponseSchema, + ), + onSuccess: () => { + invalidateWatchlistExclusionCaches() + }, + }) +} + +/** + * Mutation hook for removing a single watchlist exclusion by ID. + * + * @example + * ```typescript + * const { mutateAsync, isPending, variables } = useRemoveWatchlistExclusion() + * await mutateAsync(42) + * // For per-row state, compare `variables === row.id` with `isPending`. + * ``` + */ +export function useRemoveWatchlistExclusion() { + return useAppMutation({ + mutationFn: (id) => + apiClient.delete(`/v1/watchlist-exclusions/${id}`), + onSuccess: () => { + invalidateWatchlistExclusionCaches() + }, + }) +} diff --git a/src/client/features/utilities/hooks/useWatchlistExclusions.ts b/src/client/features/utilities/hooks/useWatchlistExclusions.ts new file mode 100644 index 000000000..de14b6c72 --- /dev/null +++ b/src/client/features/utilities/hooks/useWatchlistExclusions.ts @@ -0,0 +1,40 @@ +import { + type GetWatchlistExclusionsResponse, + GetWatchlistExclusionsResponseSchema, +} from '@root/schemas/watchlist-exclusions/watchlist-exclusions.schema' +import { apiClient } from '@/lib/apiClient' +import { useAppQuery } from '@/lib/useAppQuery' + +/** + * Query key factory for watchlist exclusion queries. + * Centralized key management enables targeted cache invalidation. + */ +export const watchlistExclusionKeys = { + all: ['watchlist-exclusions'] as const, + lists: () => [...watchlistExclusionKeys.all, 'list'] as const, +} + +/** + * React Query hook for fetching all watchlist exclusions. + * + * Uses `useAppQuery` wrapper which enforces minimum loading duration + * for consistent skeleton loader behavior. + * + * @returns Query result with the list of exclusions, loading state, and error + * + * @example + * ```typescript + * const { data, isLoading, error } = useWatchlistExclusions() + * const exclusions = data?.exclusions ?? [] + * ``` + */ +export function useWatchlistExclusions() { + return useAppQuery({ + queryKey: watchlistExclusionKeys.lists(), + queryFn: () => + apiClient.get( + '/v1/watchlist-exclusions', + GetWatchlistExclusionsResponseSchema, + ), + }) +} diff --git a/src/client/features/utilities/pages/watchlist-exclusions.tsx b/src/client/features/utilities/pages/watchlist-exclusions.tsx new file mode 100644 index 000000000..13cf24525 --- /dev/null +++ b/src/client/features/utilities/pages/watchlist-exclusions.tsx @@ -0,0 +1,482 @@ +import type { GetUserWatchlistResponse } from '@root/schemas/users/watchlist.schema' +import * as React from 'react' +import { toast } from 'sonner' +import { UtilitySectionHeader } from '@/components/ui/utility-section-header' +import { + type BulkExclusionScope, + type BulkExclusionStatus, + WatchlistExclusionsBulkModal, +} from '@/features/utilities/components/watchlist-exclusions/watchlist-exclusions-bulk-modal' +import { WatchlistExclusionsBulkRemoveModal } from '@/features/utilities/components/watchlist-exclusions/watchlist-exclusions-bulk-remove-modal' +import { WatchlistExclusionsDeleteConfirmationModal } from '@/features/utilities/components/watchlist-exclusions/watchlist-exclusions-delete-confirmation-modal' +import { WatchlistExclusionsExcludeConfirmationModal } from '@/features/utilities/components/watchlist-exclusions/watchlist-exclusions-exclude-confirmation-modal' +import { WatchlistExclusionsSkeleton } from '@/features/utilities/components/watchlist-exclusions/watchlist-exclusions-skeleton' +import { + WatchlistExclusionsTable, + type WatchlistExclusionsTableRef, +} from '@/features/utilities/components/watchlist-exclusions/watchlist-exclusions-table' +import { + GLOBAL_USER_LABEL, + type WatchlistExclusionTableRow, +} from '@/features/utilities/components/watchlist-exclusions/watchlist-exclusions-table-columns' +import { + useCreateWatchlistExclusion, + useRemoveWatchlistExclusion, +} from '@/features/utilities/hooks/useWatchlistExclusionMutations' +import { useWatchlistExclusions } from '@/features/utilities/hooks/useWatchlistExclusions' +import { useInitializeWithMinDuration } from '@/hooks/useInitializeWithMinDuration' +import { useUserOptions } from '@/hooks/useUserOptions' +import { api } from '@/lib/api' +import { useConfigStore } from '@/stores/configStore' + +interface WatchlistItemWithUser { + title: string + key: string + type: string + status: string + added: string | null + guids: string[] + userId: number + username: string +} + +export function WatchlistExclusionsPage() { + const { isInitialized, initialize } = useConfigStore() + const users = useConfigStore((state) => state.users) + const { options: realUserOptions } = useUserOptions() + const isInitializing = useInitializeWithMinDuration(initialize) + + const { + data: exclusionsData, + refetch: refetchExclusions, + isLoading: exclusionsLoading, + } = useWatchlistExclusions() + const exclusions = exclusionsData?.exclusions ?? [] + + const createExclusionMutation = useCreateWatchlistExclusion() + const removeExclusionMutation = useRemoveWatchlistExclusion() + + const [watchlistItems, setWatchlistItems] = React.useState< + WatchlistItemWithUser[] + >([]) + const [hasLoadedWatchlists, setHasLoadedWatchlists] = React.useState(false) + const [isRefreshing, setIsRefreshing] = React.useState(false) + const [pendingUnexclude, setPendingUnexclude] = React.useState<{ + exclusionId: number + key: string + username: string + } | null>(null) + const [pendingExclude, setPendingExclude] = React.useState<{ + key: string + userId: number + title: string + type: string + guids: string[] + username: string + status: string + } | null>(null) + const tableRef = React.useRef(null) + const [pendingBulk, setPendingBulk] = React.useState< + WatchlistExclusionTableRow[] | null + >(null) + const [bulkStatus, setBulkStatus] = + React.useState('idle') + const [pendingBulkRemove, setPendingBulkRemove] = React.useState< + number[] | null + >(null) + const [bulkRemoveStatus, setBulkRemoveStatus] = + React.useState('idle') + const [unexcludeStatus, setUnexcludeStatus] = + React.useState('idle') + const [excludeStatus, setExcludeStatus] = + React.useState('idle') + + const fetchAllWatchlistItems = React.useCallback(async () => { + if (!users?.length) { + setWatchlistItems([]) + setHasLoadedWatchlists(true) + return + } + + const usersWithItems = users.filter((u) => u.watchlist_count > 0) + const results = await Promise.all( + usersWithItems.map(async (user) => { + try { + const response = await fetch(api(`/v1/users/${user.id}/watchlist`)) + if (!response.ok) return [] + const data: GetUserWatchlistResponse = await response.json() + return data.data.watchlistItems.map((item) => ({ + title: item.title, + key: item.key, + type: item.type, + status: item.status, + added: item.added, + guids: item.guids, + userId: user.id, + username: user.name, + })) + } catch { + return [] + } + }), + ) + setWatchlistItems(results.flat()) + setHasLoadedWatchlists(true) + }, [users]) + + React.useEffect(() => { + if (isInitialized && !hasLoadedWatchlists) { + fetchAllWatchlistItems() + } + }, [isInitialized, hasLoadedWatchlists, fetchAllWatchlistItems]) + + const tableData = React.useMemo(() => { + const perUserExclusionByUserAndKey = new Map< + string, + (typeof exclusions)[number] + >() + const globalKeys = new Set() + for (const exclusion of exclusions) { + if (exclusion.user_id === 0) { + globalKeys.add(exclusion.key) + } else { + perUserExclusionByUserAndKey.set( + `${exclusion.user_id}:${exclusion.key}`, + exclusion, + ) + } + } + + const watchlistRows: WatchlistExclusionTableRow[] = watchlistItems.map( + (item) => { + const exclusion = perUserExclusionByUserAndKey.get( + `${item.userId}:${item.key}`, + ) + return { + id: `wl-${item.userId}:${item.key}`, + rowKind: 'watchlist', + title: item.title, + key: item.key, + type: item.type, + status: item.status, + added: item.added, + excluded_at: exclusion?.excluded_at ?? null, + guids: item.guids, + userId: item.userId, + username: item.username, + isExcluded: !!exclusion, + exclusionId: exclusion?.id ?? null, + isGloballyBlocked: globalKeys.has(item.key), + } + }, + ) + + const matchedPerUserExclusionIds = new Set( + watchlistRows + .map((r) => r.exclusionId) + .filter((id): id is number => id !== null), + ) + + const orphanRows: WatchlistExclusionTableRow[] = [] + for (const exclusion of exclusions) { + if (exclusion.user_id === 0) { + orphanRows.push({ + id: `excl-${exclusion.id}`, + rowKind: 'global', + title: exclusion.title, + key: exclusion.key, + type: exclusion.type, + status: null, + added: null, + excluded_at: exclusion.excluded_at, + guids: exclusion.guids, + userId: 0, + username: GLOBAL_USER_LABEL, + isExcluded: true, + exclusionId: exclusion.id, + isGloballyBlocked: true, + }) + } else if (!matchedPerUserExclusionIds.has(exclusion.id)) { + orphanRows.push({ + id: `excl-${exclusion.id}`, + rowKind: 'orphan-user', + title: exclusion.title, + key: exclusion.key, + type: exclusion.type, + status: null, + added: null, + excluded_at: exclusion.excluded_at, + guids: exclusion.guids, + userId: exclusion.user_id, + username: exclusion.username, + isExcluded: true, + exclusionId: exclusion.id, + isGloballyBlocked: globalKeys.has(exclusion.key), + }) + } + } + + return [...watchlistRows, ...orphanRows] + }, [watchlistItems, exclusions]) + + const userFilterOptions = React.useMemo(() => { + const nonSystem = realUserOptions.filter((o) => o.value !== '0') + return [...nonSystem, { label: GLOBAL_USER_LABEL, value: '0' }] + }, [realUserOptions]) + + const handleExclude = (row: WatchlistExclusionTableRow) => { + if (row.rowKind !== 'watchlist' || row.isExcluded) return + setPendingExclude({ + key: row.key, + userId: row.userId, + title: row.title, + type: row.type, + guids: row.guids, + username: row.username, + status: row.status ?? '', + }) + } + + const handleRemove = (row: WatchlistExclusionTableRow) => { + if (!row.isExcluded || row.exclusionId === null) return + setPendingUnexclude({ + exclusionId: row.exclusionId, + key: row.key, + username: row.username, + }) + } + + const handleConfirmExclude = async () => { + if (!pendingExclude) return + setExcludeStatus('loading') + try { + await createExclusionMutation.mutateAsync({ + key: pendingExclude.key, + userIds: [pendingExclude.userId], + title: pendingExclude.title, + type: pendingExclude.type, + guids: pendingExclude.guids, + }) + setExcludeStatus('success') + toast.success('Exclusion created successfully') + setTimeout(() => { + setPendingExclude(null) + setExcludeStatus('idle') + }, 1000) + } catch (err) { + const errorMessage = + err instanceof Error ? err.message : 'Failed to create exclusion' + toast.error(errorMessage) + setExcludeStatus('idle') + } + } + + const handleConfirmUnexclude = async () => { + if (!pendingUnexclude) return + setUnexcludeStatus('loading') + try { + await removeExclusionMutation.mutateAsync(pendingUnexclude.exclusionId) + setUnexcludeStatus('success') + toast.success('Exclusion removed successfully') + setTimeout(() => { + setPendingUnexclude(null) + setUnexcludeStatus('idle') + }, 1000) + } catch (err) { + const errorMessage = + err instanceof Error ? err.message : 'Failed to remove exclusion' + toast.error(errorMessage) + setUnexcludeStatus('idle') + } + } + + const handleRefresh = async () => { + setIsRefreshing(true) + try { + await Promise.all([fetchAllWatchlistItems(), refetchExclusions()]) + } catch { + toast.error('Failed to refresh data') + } finally { + setIsRefreshing(false) + } + } + + const handleBulkExcludeStart = (rows: WatchlistExclusionTableRow[]) => { + setPendingBulk(rows) + setBulkStatus('idle') + } + + const handleBulkExclude = async ( + rows: WatchlistExclusionTableRow[], + scope: BulkExclusionScope, + ) => { + if (rows.length === 0) return + setBulkStatus('loading') + + const byKey = new Map< + string, + { + title: string + type: string + guids: string[] + userIds: Set + } + >() + for (const row of rows) { + const userId = scope === 'global' ? 0 : row.userId + const entry = byKey.get(row.key) ?? { + title: row.title, + type: row.type, + guids: row.guids, + userIds: new Set(), + } + entry.userIds.add(userId) + byKey.set(row.key, entry) + } + + const results = await Promise.allSettled( + Array.from(byKey.entries()).map( + ([key, { title, type, guids, userIds }]) => + createExclusionMutation.mutateAsync({ + key, + userIds: Array.from(userIds), + title, + type, + guids, + }), + ), + ) + const failures = results.filter((r) => r.status === 'rejected').length + setBulkStatus(failures === 0 ? 'success' : 'error') + await refetchExclusions() + tableRef.current?.clearSelection() + setTimeout(() => { + setPendingBulk(null) + setBulkStatus('idle') + }, 1000) + if (failures === 0) { + toast.success('Bulk exclude complete') + } else { + toast.error(`Bulk exclude completed with ${failures} failure(s)`) + } + } + + const handleBulkRemoveStart = (rows: WatchlistExclusionTableRow[]) => { + const ids = rows + .map((r) => r.exclusionId) + .filter((id): id is number => id !== null) + if (ids.length === 0) return + setPendingBulkRemove(ids) + setBulkRemoveStatus('idle') + } + + const handleConfirmBulkRemove = async () => { + if (!pendingBulkRemove || pendingBulkRemove.length === 0) return + setBulkRemoveStatus('loading') + + const results = await Promise.allSettled( + pendingBulkRemove.map((id) => removeExclusionMutation.mutateAsync(id)), + ) + const failures = results.filter((r) => r.status === 'rejected').length + setBulkRemoveStatus(failures === 0 ? 'success' : 'error') + await refetchExclusions() + tableRef.current?.clearSelection() + setTimeout(() => { + setPendingBulkRemove(null) + setBulkRemoveStatus('idle') + }, 1000) + if (failures === 0) { + toast.success('Bulk remove complete') + } else { + toast.error(`Bulk remove completed with ${failures} failure(s)`) + } + } + + const isInitialLoad = + isInitializing || + !isInitialized || + !hasLoadedWatchlists || + exclusionsLoading + + if (isInitialLoad) { + return + } + + return ( + <> + { + if (!open && unexcludeStatus !== 'loading') { + setPendingUnexclude(null) + } + }} + onConfirm={handleConfirmUnexclude} + actionStatus={unexcludeStatus} + username={pendingUnexclude?.username || ''} + /> + + { + if (!open && excludeStatus !== 'loading') { + setPendingExclude(null) + } + }} + onConfirm={handleConfirmExclude} + actionStatus={excludeStatus} + title={pendingExclude?.title || ''} + username={pendingExclude?.username || ''} + status={pendingExclude?.status || ''} + /> + + { + if (!open && bulkStatus !== 'loading') setPendingBulk(null) + }} + selectedRows={pendingBulk ?? []} + onBulkExclude={handleBulkExclude} + actionStatus={bulkStatus} + /> + + { + if (!open && bulkRemoveStatus !== 'loading') { + setPendingBulkRemove(null) + } + }} + onConfirm={handleConfirmBulkRemove} + actionStatus={bulkRemoveStatus} + count={pendingBulkRemove?.length ?? 0} + /> + +
+ + +
+ +
+
+ + ) +} + +export default WatchlistExclusionsPage diff --git a/src/client/router/router.tsx b/src/client/router/router.tsx index 7beab49dd..e2a4ed20e 100644 --- a/src/client/router/router.tsx +++ b/src/client/router/router.tsx @@ -30,6 +30,9 @@ const PlexLabelsPage = lazy( () => import('@/features/utilities/pages/plex-labels'), ) const ApiKeysPage = lazy(() => import('@/features/utilities/pages/api-keys')) +const WatchlistExclusionsPage = lazy( + () => import('@/features/utilities/pages/watchlist-exclusions'), +) const LogViewerPage = lazy( () => import('@/features/utilities/pages/log-viewer'), ) @@ -265,6 +268,16 @@ export const router = createBrowserRouter( ), }, + { + path: 'watchlist-exclusions', + element: ( + + }> + + + + ), + }, { path: 'log-viewer', element: ( diff --git a/src/plugins/external/swagger.ts b/src/plugins/external/swagger.ts index 6a0f21477..818488cc2 100644 --- a/src/plugins/external/swagger.ts +++ b/src/plugins/external/swagger.ts @@ -126,6 +126,10 @@ const createOpenapiConfig = (fastify: FastifyInstance, pathSuffix: string) => { name: 'Content Router', description: 'Content routing and rule management endpoints', }, + { + name: 'Watchlist Exclusions', + description: 'Watchlist exclusion management endpoints', + }, { name: 'Labels', description: 'Plex label synchronization and management endpoints', diff --git a/src/routes/v1/watchlist-exclusions/watchlist-exclusions.ts b/src/routes/v1/watchlist-exclusions/watchlist-exclusions.ts new file mode 100644 index 000000000..6c671dace --- /dev/null +++ b/src/routes/v1/watchlist-exclusions/watchlist-exclusions.ts @@ -0,0 +1,164 @@ +import { + CreateWatchlistExclusionResponseSchema, + CreateWatchlistExclusionSchema, + GetUserWatchlistExclusionsParamsSchema, + GetUserWatchlistExclusionsResponseSchema, + GetWatchlistExclusionsResponseSchema, + RemoveWatchlistExclusionParamsSchema, + WatchlistExclusionErrorSchema, +} from '@schemas/watchlist-exclusions/watchlist-exclusions.schema.js' +import { logRouteError } from '@utils/route-errors.js' +import type { FastifyPluginAsyncZodOpenApi } from 'fastify-zod-openapi' + +const plugin: FastifyPluginAsyncZodOpenApi = async (fastify) => { + // Create Watchlist Exclusion + fastify.post( + '/', + { + schema: { + summary: 'Create watchlist exclusion', + operationId: 'createWatchlistExclusion', + description: + 'Create watchlist exclusion records for a key and set of users', + body: CreateWatchlistExclusionSchema, + response: { + 201: CreateWatchlistExclusionResponseSchema, + 400: WatchlistExclusionErrorSchema, + 500: WatchlistExclusionErrorSchema, + }, + tags: ['Watchlist Exclusions'], + }, + }, + async (request, reply) => { + try { + const { key, userIds, title, type, guids } = request.body + const created = await fastify.db.excludeWatchlistItem( + key, + userIds, + title, + type, + guids, + ) + + reply.status(201) + return { + success: true, + message: `Created ${created} exclusion(s)`, + created, + } + } catch (error) { + logRouteError(fastify.log, request, error, { + message: 'Failed to create exclusion', + }) + return reply.internalServerError('Failed to create exclusion') + } + }, + ) + + // Get All Watchlist Exclusions + fastify.get( + '/', + { + schema: { + summary: 'Get all watchlist exclusions', + operationId: 'getAllWatchlistExclusions', + description: 'Retrieve all watchlist exclusions with user information', + response: { + 200: GetWatchlistExclusionsResponseSchema, + 500: WatchlistExclusionErrorSchema, + }, + tags: ['Watchlist Exclusions'], + }, + }, + async (request, reply) => { + try { + const exclusions = await fastify.db.getAllExclusions() + + return { + success: true, + message: 'Exclusions retrieved successfully', + exclusions, + } + } catch (error) { + logRouteError(fastify.log, request, error, { + message: 'Failed to retrieve exclusions', + }) + return reply.internalServerError('Failed to retrieve exclusions') + } + }, + ) + + // Get Watchlist Exclusions for User + fastify.get( + '/user/:userId', + { + schema: { + summary: 'Get user watchlist exclusions', + operationId: 'getUserWatchlistExclusions', + description: 'Retrieve all watchlist exclusions for a specific user', + params: GetUserWatchlistExclusionsParamsSchema, + response: { + 200: GetUserWatchlistExclusionsResponseSchema, + 500: WatchlistExclusionErrorSchema, + }, + tags: ['Watchlist Exclusions'], + }, + }, + async (request, reply) => { + try { + const exclusions = await fastify.db.getExclusionsForUser( + request.params.userId, + ) + + return { + success: true, + message: 'User exclusions retrieved successfully', + exclusions, + } + } catch (error) { + logRouteError(fastify.log, request, error, { + message: 'Failed to retrieve user exclusions', + }) + return reply.internalServerError('Failed to retrieve user exclusions') + } + }, + ) + + // Remove Watchlist Exclusion + fastify.delete( + '/:id', + { + schema: { + summary: 'Remove watchlist exclusion', + operationId: 'removeWatchlistExclusion', + description: 'Remove a watchlist exclusion by ID', + params: RemoveWatchlistExclusionParamsSchema, + response: { + 204: { type: 'null', description: 'No Content' }, + 404: WatchlistExclusionErrorSchema, + 500: WatchlistExclusionErrorSchema, + }, + tags: ['Watchlist Exclusions'], + }, + }, + async (request, reply) => { + try { + const removed = await fastify.db.removeExclusion(request.params.id) + + if (!removed) { + return reply.notFound('Exclusion not found') + } + + reply.status(204) + return + } catch (error) { + logRouteError(fastify.log, request, error, { + message: 'Failed to remove exclusion', + }) + return reply.internalServerError('Failed to remove exclusion') + } + }, + ) +} + +export default plugin diff --git a/src/schemas/watchlist-exclusions/watchlist-exclusions.schema.ts b/src/schemas/watchlist-exclusions/watchlist-exclusions.schema.ts new file mode 100644 index 000000000..09e6830ae --- /dev/null +++ b/src/schemas/watchlist-exclusions/watchlist-exclusions.schema.ts @@ -0,0 +1,89 @@ +import { ErrorSchema } from '@root/schemas/common/error.schema.js' +import { z } from 'zod' + +// Common Watchlist Exclusion Schema +const WatchlistExclusionSchema = z.object({ + id: z.number(), + user_id: z.number(), + key: z.string(), + title: z.string(), + type: z.string(), + guids: z.array(z.string()), + excluded_at: z.string(), +}) + +const WatchlistExclusionWithUserSchema = WatchlistExclusionSchema.extend({ + username: z.string(), +}) + +// Create Watchlist Exclusion Schema +export const CreateWatchlistExclusionSchema = z.object({ + key: z.string().trim().min(1, { error: 'Key is required' }), + userIds: z + .array(z.number()) + .min(1, { error: 'At least one user ID is required' }), + title: z.string().trim().min(1, { error: 'Title is required' }), + type: z.string().trim().min(1, { error: 'Type is required' }), + guids: z.array(z.string()).default([]), +}) + +export const CreateWatchlistExclusionResponseSchema = z.object({ + success: z.boolean(), + message: z.string(), + created: z.number(), +}) + +// Get All Watchlist Exclusions Schema +export const GetWatchlistExclusionsResponseSchema = z.object({ + success: z.boolean(), + message: z.string(), + exclusions: z.array(WatchlistExclusionWithUserSchema), +}) + +// Get User Watchlist Exclusions Schema +export const GetUserWatchlistExclusionsParamsSchema = z.object({ + userId: z.coerce.number(), +}) + +export const GetUserWatchlistExclusionsResponseSchema = z.object({ + success: z.boolean(), + message: z.string(), + exclusions: z.array(WatchlistExclusionSchema), +}) + +// Remove Watchlist Exclusion Schema +export const RemoveWatchlistExclusionParamsSchema = z.object({ + id: z.coerce.number(), +}) + +export const RemoveWatchlistExclusionResponseSchema = z.object({ + success: z.boolean(), + message: z.string(), +}) + +// Exported inferred types +export type CreateWatchlistExclusion = z.infer< + typeof CreateWatchlistExclusionSchema +> +export type CreateWatchlistExclusionResponse = z.infer< + typeof CreateWatchlistExclusionResponseSchema +> +export type GetWatchlistExclusionsResponse = z.infer< + typeof GetWatchlistExclusionsResponseSchema +> +export type GetUserWatchlistExclusionsParams = z.infer< + typeof GetUserWatchlistExclusionsParamsSchema +> +export type GetUserWatchlistExclusionsResponse = z.infer< + typeof GetUserWatchlistExclusionsResponseSchema +> +export type RemoveWatchlistExclusionParams = z.infer< + typeof RemoveWatchlistExclusionParamsSchema +> +export type RemoveWatchlistExclusionResponse = z.infer< + typeof RemoveWatchlistExclusionResponseSchema +> + +// Re-export shared error schema with domain-specific alias +export { ErrorSchema as WatchlistExclusionErrorSchema } +export type WatchlistExclusionError = z.infer diff --git a/src/services/approval.service.ts b/src/services/approval.service.ts index f61964ed3..8d91d5fed 100644 --- a/src/services/approval.service.ts +++ b/src/services/approval.service.ts @@ -10,6 +10,7 @@ import type { ApprovalMetadata } from '@root/types/progress.types.js' import type { RadarrItem } from '@root/types/radarr.types.js' import type { ContentItem } from '@root/types/router.types.js' import type { SonarrItem } from '@root/types/sonarr.types.js' +import { isArrAlreadyAddedError } from '@utils/arr-error.js' import { getGuidMatchScore } from '@utils/guid-handler.js' import { createServiceLogger } from '@utils/logger.js' import type { FastifyBaseLogger, FastifyInstance } from 'fastify' @@ -327,15 +328,6 @@ export class ApprovalService { } } - /** - * Checks if an error indicates the content already exists in Sonarr/Radarr. - * Sonarr/Radarr return 400 with "This series/movie has already been added". - */ - private isAlreadyAddedError(error: unknown): boolean { - if (!(error instanceof Error)) return false - return error.message.toLowerCase().includes('already been added') - } - /** * Processes an approved request by executing the stored router decision. * This is an internal method - external callers should use approveAndRoute(). @@ -404,7 +396,7 @@ export class ApprovalService { ) routingResults.succeeded.push(targetInstanceId) } catch (error) { - if (this.isAlreadyAddedError(error)) { + if (isArrAlreadyAddedError(error)) { this.log.info( { instanceId: targetInstanceId, title: request.contentTitle }, 'Movie already exists in Radarr instance, treating as successful routing', @@ -452,7 +444,7 @@ export class ApprovalService { ) routingResults.succeeded.push(targetInstanceId) } catch (error) { - if (this.isAlreadyAddedError(error)) { + if (isArrAlreadyAddedError(error)) { this.log.info( { instanceId: targetInstanceId, title: request.contentTitle }, 'Series already exists in Sonarr instance, treating as successful routing', diff --git a/src/services/content-router.service.ts b/src/services/content-router.service.ts index 8d7730435..fdd986301 100644 --- a/src/services/content-router.service.ts +++ b/src/services/content-router.service.ts @@ -19,6 +19,7 @@ import type { RoutingEvaluator, } from '@root/types/router.types.js' import type { SonarrItem } from '@root/types/sonarr.types.js' +import { isArrAlreadyAddedError } from '@utils/arr-error.js' import { createServiceLogger } from '@utils/logger.js' import { parseQualityProfileId } from '@utils/quality-profile.js' import type { FastifyBaseLogger, FastifyInstance } from 'fastify' @@ -1536,6 +1537,7 @@ export class ContentRouterService { return { routedInstances: [], routingDetails: [] } case 'approved': + case 'auto_approved': this.log.info( `Using previously approved routing for "${item.title}" by user ${context.userName || context.userId}`, ) @@ -2556,14 +2558,16 @@ export class ContentRouterService { const routedInstances: number[] = [] const instanceId = proposedRouting.instanceId + const syncedInstanceIds = proposedRouting.syncedInstances ?? [] const contentType = approvedRequest.contentType + const userId = approvedRequest.userId ?? 0 if (contentType === 'movie') { try { await this.fastify.radarrManager.routeItemToRadarr( item as RadarrItem, context.itemKey, - approvedRequest.userId ?? 0, + userId, instanceId, context.syncing, proposedRouting.rootFolder || undefined, @@ -2577,17 +2581,52 @@ export class ContentRouterService { `Successfully routed approved content "${item.title}" to Radarr instance ${instanceId}`, ) } catch (error) { - this.log.error( - { error }, - `Failed to route approved content "${item.title}" to Radarr instance ${instanceId}`, - ) + if (isArrAlreadyAddedError(error)) { + this.log.info( + `Approved content "${item.title}" already exists in Radarr instance ${instanceId}, treating as routed`, + ) + routedInstances.push(instanceId) + } else { + this.log.error( + { error }, + `Failed to route approved content "${item.title}" to Radarr instance ${instanceId}`, + ) + } + } + + for (const syncedId of syncedInstanceIds) { + try { + await this.fastify.radarrManager.routeItemToRadarr( + item as RadarrItem, + context.itemKey, + userId, + syncedId, + true, + ) + routedInstances.push(syncedId) + this.log.info( + `Successfully routed approved content "${item.title}" to synced Radarr instance ${syncedId}`, + ) + } catch (error) { + if (isArrAlreadyAddedError(error)) { + this.log.info( + `Approved content "${item.title}" already exists in synced Radarr instance ${syncedId}, treating as routed`, + ) + routedInstances.push(syncedId) + } else { + this.log.error( + { error }, + `Failed to route approved content "${item.title}" to synced Radarr instance ${syncedId}`, + ) + } + } } } else { try { await this.fastify.sonarrManager.routeItemToSonarr( item as SonarrItem, context.itemKey, - approvedRequest.userId ?? 0, + userId, instanceId, context.syncing, proposedRouting.rootFolder || undefined, @@ -2602,10 +2641,45 @@ export class ContentRouterService { `Successfully routed approved content "${item.title}" to Sonarr instance ${instanceId}`, ) } catch (error) { - this.log.error( - { error }, - `Failed to route approved content "${item.title}" to Sonarr instance ${instanceId}`, - ) + if (isArrAlreadyAddedError(error)) { + this.log.info( + `Approved content "${item.title}" already exists in Sonarr instance ${instanceId}, treating as routed`, + ) + routedInstances.push(instanceId) + } else { + this.log.error( + { error }, + `Failed to route approved content "${item.title}" to Sonarr instance ${instanceId}`, + ) + } + } + + for (const syncedId of syncedInstanceIds) { + try { + await this.fastify.sonarrManager.routeItemToSonarr( + item as SonarrItem, + context.itemKey, + userId, + syncedId, + true, + ) + routedInstances.push(syncedId) + this.log.info( + `Successfully routed approved content "${item.title}" to synced Sonarr instance ${syncedId}`, + ) + } catch (error) { + if (isArrAlreadyAddedError(error)) { + this.log.info( + `Approved content "${item.title}" already exists in synced Sonarr instance ${syncedId}, treating as routed`, + ) + routedInstances.push(syncedId) + } else { + this.log.error( + { error }, + `Failed to route approved content "${item.title}" to synced Sonarr instance ${syncedId}`, + ) + } + } } } diff --git a/src/services/database.service.ts b/src/services/database.service.ts index 9e4bf6b18..8fd92241b 100644 --- a/src/services/database.service.ts +++ b/src/services/database.service.ts @@ -76,6 +76,7 @@ import './database/types/webhook-methods.js' import './database/types/webhook-endpoint-methods.js' import './database/types/api-key-methods.js' import './database/types/dashboard-methods.js' +import './database/types/watchlist-exclusion-methods.js' import * as analyticsMethods from './database/methods/analytics.js' import * as animeMethods from './database/methods/anime.js' import * as apiKeyMethods from './database/methods/api-keys.js' @@ -94,6 +95,7 @@ import * as sessionMethods from './database/methods/session.js' import * as sonarrInstanceMethods from './database/methods/sonarr-instance.js' import * as userMethods from './database/methods/users.js' import * as watchlistMethods from './database/methods/watchlist.js' +import * as watchlistExclusionMethods from './database/methods/watchlist-exclusion.js' import * as webhookMethods from './database/methods/webhook.js' import * as webhookEndpointMethods from './database/methods/webhook-endpoints.js' @@ -213,6 +215,7 @@ export class DatabaseService { approvalMethods, configMethods, dashboardMethods, + watchlistExclusionMethods, junctionMethods, notificationMethods, plexLabelSyncMethods, diff --git a/src/services/database/methods/watchlist-exclusion.ts b/src/services/database/methods/watchlist-exclusion.ts new file mode 100644 index 000000000..06628b437 --- /dev/null +++ b/src/services/database/methods/watchlist-exclusion.ts @@ -0,0 +1,180 @@ +import type { WatchlistExclusion } from '@root/types/watchlist-exclusion.types.js' +import type { DatabaseService } from '@services/database.service.js' +import { parseGuids } from '@utils/guid-handler.js' + +/** + * Sentinel user id representing a global exclusion that vetoes routing for the + * matching key for all users. + */ +export const SYSTEM_USER_ID = 0 + +/** + * Inserts an exclusion row for each user, skipping duplicates. + * + * @returns Number of rows inserted (excludes duplicates) + */ +export async function excludeWatchlistItem( + this: DatabaseService, + key: string, + userIds: number[], + title: string, + type: string, + guids: string[], +): Promise { + if (userIds.length === 0) return 0 + + const guidsJson = JSON.stringify(guids) + + const rows = userIds.map((userId) => ({ + user_id: userId, + key, + title, + type, + guids: guidsJson, + excluded_at: this.timestamp, + })) + + const insertedRows = await this.knex('watchlist_exclusions') + .insert(rows) + .onConflict(['user_id', 'key']) + .ignore() + .returning('id') + + return insertedRows.length +} + +/** + * Removes exclusion rows for the user across the given keys. + * + * @returns Number of rows deleted + */ +export async function clearExclusions( + this: DatabaseService, + userId: number, + keys: string[], +): Promise { + if (keys.length === 0) return 0 + + return await this.knex('watchlist_exclusions') + .where('user_id', userId) + .whereIn('key', keys) + .delete() +} + +/** + * Returns a map of key → set of user ids that have excluded that key. + */ +export async function getExclusionMap( + this: DatabaseService, +): Promise>> { + const rows = await this.knex('watchlist_exclusions').select('key', 'user_id') + + const map = new Map>() + + for (const row of rows) { + let userSet = map.get(row.key) + if (!userSet) { + userSet = new Set() + map.set(row.key, userSet) + } + userSet.add(row.user_id) + } + + return map +} + +/** + * Returns the subset of given keys that the user currently has excluded. + */ +export async function findExcludedKeys( + this: DatabaseService, + userId: number, + keys: string[], +): Promise { + if (keys.length === 0) return [] + + const rows = await this.knex('watchlist_exclusions') + .where('user_id', userId) + .whereIn('key', keys) + .select('key') + + return rows.map((r) => r.key) +} + +/** + * Returns all exclusions for a user, most recent first. + */ +export async function getExclusionsForUser( + this: DatabaseService, + userId: number, +): Promise { + const rows = await this.knex('watchlist_exclusions') + .where('user_id', userId) + .orderBy('excluded_at', 'desc') + .select('*') + + return rows.map((row) => ({ ...row, guids: parseGuids(row.guids) })) +} + +/** + * Returns all exclusions joined with the owning user's name. + */ +export async function getAllExclusions( + this: DatabaseService, +): Promise> { + const rows = await this.knex('watchlist_exclusions as we') + .join('users as u', 'we.user_id', 'u.id') + .select( + 'we.id', + 'we.user_id', + 'we.key', + 'we.title', + 'we.type', + 'we.guids', + 'we.excluded_at', + 'u.name as username', + ) + .orderBy('we.excluded_at', 'desc') + + return rows.map((row) => ({ ...row, guids: parseGuids(row.guids) })) +} + +/** + * Removes a single exclusion by id. + * + * @returns True if a row was deleted + */ +export async function removeExclusion( + this: DatabaseService, + id: number, +): Promise { + const deleted = await this.knex('watchlist_exclusions') + .where('id', id) + .delete() + return deleted > 0 +} + +/** + * Deletes routed (status != 'pending') watchlist_items rows whose key is + * excluded for the same user or globally. Pending rows are already vetoed at + * the routing gate and left in place to avoid RSS-recreate churn. Exclusion + * rows themselves are preserved. + * + * @returns Number of watchlist_items rows deleted + */ +export async function cleanupExcludedWatchlistItems( + this: DatabaseService, +): Promise { + return await this.knex('watchlist_items') + .whereExists(function () { + this.select(1) + .from('watchlist_exclusions') + .whereRaw( + '(watchlist_exclusions.user_id = watchlist_items.user_id OR watchlist_exclusions.user_id = ?)', + [SYSTEM_USER_ID], + ) + .whereRaw('watchlist_exclusions.key = watchlist_items.key') + }) + .whereNot('status', 'pending') + .delete() +} diff --git a/src/services/database/methods/watchlist.ts b/src/services/database/methods/watchlist.ts index f17fc0f67..634b1799f 100644 --- a/src/services/database/methods/watchlist.ts +++ b/src/services/database/methods/watchlist.ts @@ -1524,6 +1524,12 @@ export async function deleteWatchlistItems( } } + // Clear exclusions so re-adding the item to a watchlist allows routing again + await trx('watchlist_exclusions') + .where('user_id', numericUserId) + .whereIn('key', keys) + .delete() + // Delete watchlist items (CASCADE will handle notifications with non-NULL watchlist_item_id) await trx('watchlist_items') .where('user_id', numericUserId) diff --git a/src/services/database/types/watchlist-exclusion-methods.ts b/src/services/database/types/watchlist-exclusion-methods.ts new file mode 100644 index 000000000..118d8afd1 --- /dev/null +++ b/src/services/database/types/watchlist-exclusion-methods.ts @@ -0,0 +1,62 @@ +import type { WatchlistExclusion } from '@root/types/watchlist-exclusion.types.js' + +declare module '@services/database.service.js' { + interface DatabaseService { + /** + * Inserts an exclusion row for each user, skipping duplicates. + * + * @returns Number of rows inserted + */ + excludeWatchlistItem( + key: string, + userIds: number[], + title: string, + type: string, + guids: string[], + ): Promise + + /** + * Removes exclusion rows for the user across the given keys. + * + * @returns Number of rows deleted + */ + clearExclusions(userId: number, keys: string[]): Promise + + /** + * Returns a map of key → set of user ids that have excluded that key. + */ + getExclusionMap(): Promise>> + + /** + * Returns the subset of given keys that the user currently has excluded. + */ + findExcludedKeys(userId: number, keys: string[]): Promise + + /** + * Returns all exclusions for a user, most recent first. + */ + getExclusionsForUser(userId: number): Promise + + /** + * Returns all exclusions joined with the owning user's name. + */ + getAllExclusions(): Promise< + Array + > + + /** + * Removes a single exclusion by id. + * + * @returns True if a row was deleted + */ + removeExclusion(id: number): Promise + + /** + * Deletes routed watchlist_items rows whose key is excluded for the same + * user or globally. Exclusion rows themselves are preserved. + * + * @returns Number of watchlist_items rows deleted + */ + cleanupExcludedWatchlistItems(): Promise + } +} diff --git a/src/services/delete-sync.service.ts b/src/services/delete-sync.service.ts index 8077b4214..7af23f074 100644 --- a/src/services/delete-sync.service.ts +++ b/src/services/delete-sync.service.ts @@ -283,6 +283,20 @@ export class DeleteSyncService { ) } + // Step 4.5: Drop watchlist_items rows for routed content the user has + // excluded. Removing them here ensures their GUIDs don't appear in the + // protected set built below, so the standard *arr deletion pass prunes + // the content naturally. Exclusion rows themselves are preserved. + if (!dryRun) { + const cleanedExcluded = + await this.dbService.cleanupExcludedWatchlistItems() + if (cleanedExcluded > 0) { + this.log.info( + `Removed ${cleanedExcluded} routed watchlist item(s) for excluded content`, + ) + } + } + // Step 5: Get all watchlisted content GUIDs const allWatchlistItems = await this.getAllWatchlistItems( this.config.respectUserSyncSetting, diff --git a/src/services/plex-watchlist/orchestration/removal-handler.ts b/src/services/plex-watchlist/orchestration/removal-handler.ts index 90bf3fdc0..1765c5e9a 100644 --- a/src/services/plex-watchlist/orchestration/removal-handler.ts +++ b/src/services/plex-watchlist/orchestration/removal-handler.ts @@ -103,6 +103,7 @@ export async function handleRemovedItems( } await db.deleteWatchlistItems(userId, removedKeys) + await db.clearExclusions(userId, removedKeys) } } diff --git a/src/services/plex-watchlist/orchestration/unified-processor.ts b/src/services/plex-watchlist/orchestration/unified-processor.ts index 943da15ec..d67a3a34d 100644 --- a/src/services/plex-watchlist/orchestration/unified-processor.ts +++ b/src/services/plex-watchlist/orchestration/unified-processor.ts @@ -195,7 +195,7 @@ export async function processItemsForUser( userId: user.userId, username: user.username, brandNew: brandNewCount, - linked: linkedCount, + linked: linkedItems.length, }, 'Items processed for user', ) diff --git a/src/services/watchlist-workflow/orchestration/sync-engine.ts b/src/services/watchlist-workflow/orchestration/sync-engine.ts index de01f0cea..a1434f43e 100644 --- a/src/services/watchlist-workflow/orchestration/sync-engine.ts +++ b/src/services/watchlist-workflow/orchestration/sync-engine.ts @@ -8,6 +8,7 @@ import type { TemptRssWatchlistItem } from '@root/types/plex.types.js' import type { Item as RadarrItem } from '@root/types/radarr.types.js' import type { Item as SonarrItem } from '@root/types/sonarr.types.js' +import { SYSTEM_USER_ID } from '@services/database/methods/watchlist-exclusion.js' import { extractTmdbId, extractTvdbId, @@ -34,6 +35,7 @@ export interface SyncResult { skippedDueToUserSetting: number skippedDueToMissingIds: number skippedDueToWatchlistCap: number + skippedDueToExclusion: number } /** @@ -81,6 +83,7 @@ export async function syncWatchlistItems( skippedDueToUserSetting: 0, skippedDueToMissingIds: 0, skippedDueToWatchlistCap: 0, + skippedDueToExclusion: 0, } } @@ -101,6 +104,7 @@ export async function syncWatchlistItems( skippedDueToUserSetting: 0, skippedDueToMissingIds: 0, skippedDueToWatchlistCap: 0, + skippedDueToExclusion: 0, } } @@ -122,6 +126,7 @@ export async function syncWatchlistItems( skippedDueToUserSetting: 0, skippedDueToMissingIds: 0, skippedDueToWatchlistCap: 0, + skippedDueToExclusion: 0, } } } @@ -157,6 +162,9 @@ export async function syncWatchlistItems( allWatchlistItems, ) + // --- Exclusion gate --- + const exclusionMap = await deps.db.getExclusionMap() + // Fire cap notifications (debounced by notification service) for (const entry of cappedEntries) { const user = userById.get(entry.userId) @@ -184,6 +192,7 @@ export async function syncWatchlistItems( let skippedDueToUserSetting = 0 let skippedDueToMissingIds = 0 let skippedDueToWatchlistCap = 0 + let skippedDueToExclusion = 0 const skippedItems: { shows: string[]; movies: string[] } = { shows: [], movies: [], @@ -260,6 +269,18 @@ export async function syncWatchlistItems( return { type: 'skipped', reason: 'watchlist_cap' } } + // Check exclusion gate (per-user or global SYSTEM_USER_ID veto) + const excludedUsers = exclusionMap.get(item.key) + if ( + excludedUsers?.has(numericUserId) || + excludedUsers?.has(SYSTEM_USER_ID) + ) { + deps.logger.debug( + `Skipping item "${item.title}" for user ${numericUserId} due to exclusion`, + ) + return { type: 'skipped', reason: 'exclusion' } + } + // Parse GUIDs and genres once for reuse const parsedGuids = parseGuids(item.guids) const parsedGenres = parseGenres(item.genres) @@ -380,6 +401,8 @@ export async function syncWatchlistItems( skippedDueToUserSetting++ } else if (value.reason === 'watchlist_cap') { skippedDueToWatchlistCap++ + } else if (value.reason === 'exclusion') { + skippedDueToExclusion++ } else if (value.reason === 'missing_id') { skippedDueToMissingIds++ if (value.contentType === 'show') { @@ -421,6 +444,7 @@ export async function syncWatchlistItems( skippedDueToUserSetting, skippedDueToMissingIds, skippedDueToWatchlistCap, + skippedDueToExclusion, } deps.logger.info( @@ -430,6 +454,7 @@ export async function syncWatchlistItems( skippedDueToUserSetting: summary.skippedDueToUserSetting, skippedDueToMissingIds: summary.skippedDueToMissingIds, skippedDueToWatchlistCap: summary.skippedDueToWatchlistCap, + skippedDueToExclusion: summary.skippedDueToExclusion, }, 'Watchlist sync completed', ) diff --git a/src/types/watchlist-exclusion.types.ts b/src/types/watchlist-exclusion.types.ts new file mode 100644 index 000000000..276c05629 --- /dev/null +++ b/src/types/watchlist-exclusion.types.ts @@ -0,0 +1,9 @@ +export interface WatchlistExclusion { + id: number + user_id: number + key: string + title: string + type: string + guids: string[] + excluded_at: string +} diff --git a/src/utils/arr-error.ts b/src/utils/arr-error.ts index 91d3c46fe..863627595 100644 --- a/src/utils/arr-error.ts +++ b/src/utils/arr-error.ts @@ -35,3 +35,10 @@ export function parseArrErrorMessage(errorData: unknown): string { return '' } + +// Sonarr/Radarr return 400 with "This series/movie has already been added" +// when the item already exists. +export function isArrAlreadyAddedError(error: unknown): boolean { + if (!(error instanceof Error)) return false + return error.message.toLowerCase().includes('already been added') +}